dsh-plugin-shop 0.9.0 → 0.9.1-beta.0

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
@@ -4333,7 +4333,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4333
4333
  _undefined(),
4334
4334
  literal(false),
4335
4335
  literal(true)
4336
- ]).optional()
4336
+ ]).optional(),
4337
+ "icon": union([_undefined(), string()]).optional()
4337
4338
  })),
4338
4339
  "denied": array(object({
4339
4340
  "name": string(),
@@ -4342,6 +4343,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4342
4343
  })),
4343
4344
  "notAShop": union([_undefined(), array(string())]).optional(),
4344
4345
  "stars": record(string(), number()),
4346
+ "icons": record(string(), string()),
4345
4347
  "incompatible": record(string(), array(string())),
4346
4348
  "incompatibleHarness": record(string(), object({
4347
4349
  "dsh": union([_undefined(), object({
@@ -4531,7 +4533,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4531
4533
  },
4532
4534
  sourceLocation: {
4533
4535
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4534
- "line": 1314,
4536
+ "line": 1317,
4535
4537
  "column": 9
4536
4538
  }
4537
4539
  },
@@ -4550,7 +4552,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4550
4552
  },
4551
4553
  sourceLocation: {
4552
4554
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4553
- "line": 1821,
4555
+ "line": 1825,
4554
4556
  "column": 9
4555
4557
  }
4556
4558
  },
@@ -4569,7 +4571,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4569
4571
  },
4570
4572
  sourceLocation: {
4571
4573
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4572
- "line": 1812,
4574
+ "line": 1816,
4573
4575
  "column": 9
4574
4576
  }
4575
4577
  },
@@ -4599,7 +4601,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4599
4601
  },
4600
4602
  sourceLocation: {
4601
4603
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4602
- "line": 1440,
4604
+ "line": 1444,
4603
4605
  "column": 9
4604
4606
  }
4605
4607
  },
@@ -4628,7 +4630,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4628
4630
  },
4629
4631
  sourceLocation: {
4630
4632
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4631
- "line": 1788,
4633
+ "line": 1792,
4632
4634
  "column": 3
4633
4635
  }
4634
4636
  },
@@ -4647,7 +4649,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4647
4649
  },
4648
4650
  sourceLocation: {
4649
4651
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4650
- "line": 2072,
4652
+ "line": 2076,
4651
4653
  "column": 9
4652
4654
  }
4653
4655
  },
@@ -4676,7 +4678,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4676
4678
  },
4677
4679
  sourceLocation: {
4678
4680
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4679
- "line": 966,
4681
+ "line": 969,
4680
4682
  "column": 9
4681
4683
  }
4682
4684
  },
@@ -4706,7 +4708,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4706
4708
  },
4707
4709
  sourceLocation: {
4708
4710
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4709
- "line": 1966,
4711
+ "line": 1970,
4710
4712
  "column": 9
4711
4713
  }
4712
4714
  },
@@ -4735,7 +4737,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4735
4737
  },
4736
4738
  sourceLocation: {
4737
4739
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4738
- "line": 2179,
4740
+ "line": 2183,
4739
4741
  "column": 9
4740
4742
  }
4741
4743
  },
@@ -4754,7 +4756,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4754
4756
  },
4755
4757
  sourceLocation: {
4756
4758
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4757
- "line": 2136,
4759
+ "line": 2140,
4758
4760
  "column": 9
4759
4761
  }
4760
4762
  }
@@ -4771,21 +4773,21 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4771
4773
  * apart. */
4772
4774
  title: "插件商店",
4773
4775
  /** The page's intro line, under its title, as dsh's own pages carry one. */
4774
- intro: "浏览、安装和更新目录中的插件。",
4776
+ intro: "浏览、安装和更新插件。",
4775
4777
  /** The Plugins page's button on the shop's own bundle page (design
4776
4778
  * 2026-10-16-shop-sidebar-page §7). */
4777
4779
  openShop: "打开插件商店",
4778
4780
  /** Its button on a page of a bundle the shelf lists. */
4779
4781
  viewInShop: "在插件商店中查看",
4780
- loading: "正在读取目录…",
4781
- error: "暂时无法读取目录。",
4782
- installedLoading: "正在读取已安装列表…",
4783
- installedError: "暂时无法读取已安装列表。",
4782
+ loading: "正在加载插件列表…",
4783
+ error: "插件列表加载失败,请重试。",
4784
+ installedLoading: "正在加载已安装插件列表…",
4785
+ installedError: "已安装插件列表加载失败,请重试。",
4784
4786
  retry: "重试",
4785
4787
  /** The shop's own error boundary caught a render error; the error's message
4786
4788
  * follows on its own line, and Retry remounts the tab (design
4787
4789
  * 2026-09-26-market-borrowings §4). */
4788
- tabCrashed: "插件商店在显示这一页时出错了。",
4790
+ tabCrashed: "插件商店暂时无法显示,请重试。",
4789
4791
  search: "搜索插件",
4790
4792
  /** One subdued line under the search box when a Chinese query fired a
4791
4793
  * dictionary intent (design 2026-10-08-search-query-expansion §4).
@@ -4794,16 +4796,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4794
4796
  zhExpansionNote: "同时匹配:{terms}",
4795
4797
  all: "全部",
4796
4798
  catalog: "插件目录",
4797
- catalogStats: "{count} 个插件 · 构建于 {date}",
4799
+ catalogStats: "{count} 个插件 · 目录更新于 {date}",
4798
4800
  /** The shelf's order control, and the date each card carries under the two
4799
4801
  * date orders (design 2026-10-12-shelf-orders §4). */
4800
4802
  sortLabel: "排序",
4801
- sortStars: "星标",
4803
+ sortStars: "星标最多",
4802
4804
  sortAdded: "最新上架",
4803
4805
  sortUpdated: "最近更新",
4804
- listedOn: "上架 {date}",
4805
- updatedOn: "更新 {date}",
4806
- stars: "{count} 星",
4806
+ listedOn: "{date} 上架",
4807
+ updatedOn: "{date} 更新",
4808
+ stars: "{count} 个星标",
4807
4809
  categoryTool: "工具",
4808
4810
  categoryProvider: "模型服务",
4809
4811
  categoryUi: "界面",
@@ -4811,16 +4813,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4811
4813
  categoryIntegration: "集成",
4812
4814
  categoryTheme: "主题",
4813
4815
  categoryOther: "其他",
4814
- empty: "目录中暂无插件。",
4815
- emptySearch: "没有匹配的插件。",
4816
+ empty: "暂无插件。",
4817
+ emptySearch: "没有找到匹配的插件。试试其他关键词或分类。",
4816
4818
  tierVerified: "已审核",
4817
- tierVerifiedStale: "审核版本过期",
4819
+ tierVerifiedStale: "当前版本未审核",
4818
4820
  tierCommunity: "社区",
4819
4821
  /** The github repository GitHub reports as archived (design 2026-10-08 §1.3).
4820
4822
  * Subdued register, like `tierCommunity`: nothing is wrong with the install
4821
4823
  * itself; the owner has simply frozen the project. */
4822
4824
  archivedBadge: "已归档",
4823
- capabilitiesNote: "作者自述,无强制约束",
4825
+ capabilitiesNote: "权限由作者声明,插件的实际权限可能超出这一范围。",
4824
4826
  install: "安装",
4825
4827
  cancel: "取消",
4826
4828
  dismiss: "知道了",
@@ -4831,44 +4833,44 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4831
4833
  lessRedLines: "收起",
4832
4834
  installing: "正在安装…",
4833
4835
  downloading: "正在下载…",
4834
- installedRestartNotice: "已安装;重启 dsh 后生效",
4836
+ installedRestartNotice: "已安装,重启 dsh 后生效。",
4835
4837
  incompatibleBadge: "不兼容",
4836
- incompatibleDetail: "缺少组件/插件:{modules}\n此插件可能是为其他版本的 DSH 设计的。",
4838
+ incompatibleDetail: "缺少所需的组件或插件:{modules}\n此插件可能适用于其他版本的 dsh。",
4837
4839
  /** The author's own `dsh.compatibility`, unmet on this machine (design
4838
4840
  * 2026-09-01 §8.2). Each names BOTH sides — what the author declared and
4839
4841
  * what this dsh runs — because the host sends both, and a bare
4840
4842
  * "unsupported" leaves the reader guessing which side is off. */
4841
- harnessRangeDetail: "作者声明支持的 dsh 版本:{range};本机运行的是 {running}。",
4842
- harnessProfileDetail: "作者声明支持的 profile:{declared};此 dsh 以 {running} profile 启动。",
4843
+ harnessRangeDetail: "作者声明支持 dsh {range},当前版本为 {running}。",
4844
+ harnessProfileDetail: "作者声明支持的配置(profile):{declared}。当前使用的是 {running}。",
4843
4845
  /** dsh's OWN refusal (0.1.7 on), not the author's claim: the card shows the
4844
4846
  * refused label in the install button's place (design
4845
4847
  * 2026-09-26-dsh-017-readiness, B1, amended 2026-10-14). `{peers}`
4846
4848
  * is each refused peer with the range the package declared. The remedy
4847
4849
  * leads into the exemption command, rendered on its own line after it, and
4848
4850
  * names the Refresh button by its label. */
4849
- harnessPeersDetail: "本机的 dsh {running} 会拒绝安装此插件:它要求 {peers}。",
4850
- harnessPeersRemedy: "若愿意承担崩溃或数据丢失的风险,可在终端运行以下命令,为这个确切版本授予豁免,然后点击“刷新”:",
4851
- hotNoPatchNotice: "该插件没有可热挂载的补丁文件;重启 dsh 后生效",
4852
- hotNotSimpleNotice: "该插件的补丁包含无法热挂载的配置;重启 dsh 后生效",
4853
- hotHostUnsupportedNotice: "当前环境不支持热挂载;重启 dsh 后生效",
4854
- hotTimeoutNotice: "热挂载超时;重启 dsh 后生效",
4855
- hotMountFailedNotice: "热挂载失败;重启 dsh 后生效",
4851
+ harnessPeersDetail: "此插件要求 {peers},当前的 dsh {running} 不满足要求,无法安装。",
4852
+ harnessPeersRemedy: "继续安装可能导致崩溃或数据丢失。若接受这些风险,可在终端运行以下命令,允许安装此版本,然后点击“刷新”:",
4853
+ hotNoPatchNotice: "已安装,但未找到可即时加载的插件配置。请重启 dsh 使其生效。",
4854
+ hotNotSimpleNotice: "已安装,但插件配置需要重启 dsh 才能加载。",
4855
+ hotHostUnsupportedNotice: "已安装,当前环境需要重启 dsh 才能加载插件。",
4856
+ hotTimeoutNotice: "已安装,但加载超时。请重启 dsh 使其生效。",
4857
+ hotMountFailedNotice: "已安装,但加载失败。请重启 dsh 使其生效。",
4856
4858
  /** No mount was tried: this dsh already loaded the package once, and Node
4857
4859
  * will not load a second copy from the same place (design
4858
4860
  * 2026-09-26-market-borrowings §1). Covers an update and a reinstall alike,
4859
4861
  * so it names the new files rather than a "new version". */
4860
- hotAlreadyLoadedNotice: "已安装;这次运行的 dsh 已经加载过这个插件,新装的文件要重启 dsh 后才会生效",
4861
- installedNoRestartNotice: "已安装并热挂载;现在即可使用,无需重启",
4862
- installedReloadNotice: "已生效;刷新页面即可看到",
4862
+ hotAlreadyLoadedNotice: "已安装。dsh 已加载过此插件,新安装的文件将在重启后生效。",
4863
+ installedNoRestartNotice: "已安装并生效,无需重启。",
4864
+ installedReloadNotice: "已生效,刷新页面即可看到更改。",
4863
4865
  reload: "刷新页面",
4864
- reloadNote: "服务器已经是新状态,当前页面显示的还是刷新前的内容。刷新会丢弃这个页面上未完成的内容。",
4866
+ reloadNote: "更改已在服务器上生效,刷新页面后即可看到。当前页面中未完成的内容会丢失。",
4865
4867
  installFailed: "安装失败",
4866
- installTransportFailed: "安装请求未能送达。请稍后重试。",
4868
+ installTransportFailed: "安装请求失败,请稍后重试。",
4867
4869
  refresh: "刷新",
4868
4870
  refreshing: "刷新中…",
4869
- refreshFailed: "刷新失败,仍显示上次的目录",
4870
- staleLabel: "显示 {date} 的快照",
4871
- showing: "显示前 {shown} 个(共 {total})· 继续滚动加载",
4871
+ refreshFailed: "刷新失败,当前显示的是上次加载的插件列表。",
4872
+ staleLabel: "当前显示 {date} 的插件列表",
4873
+ showing: "已显示 {shown} 个,共 {total} 个 · 向下滚动加载更多",
4872
4874
  repository: "仓库",
4873
4875
  npmPage: "npm 页面",
4874
4876
  authorLine: "作者:{author}",
@@ -4878,236 +4880,236 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4878
4880
  * was npm's word: a commit-pinned github entry unpacks nothing, its figure
4879
4881
  * being the sum of its git tree's blobs. One phrase now covers both sources
4880
4882
  * and stays true of npm, whose number is unchanged. */
4881
- sizeLabel: "磁盘占用 {size}",
4883
+ sizeLabel: "磁盘占用:{size}",
4882
4884
  /** The incompatible filter's label. FIXED — the switch beside it carries the
4883
4885
  * state, so the words name what the switch does and never flip with it. */
4884
4886
  hideIncompatible: "隐藏不兼容 {count}",
4885
4887
  /** What the filter takes away is every card whose badge reads 不兼容 —
4886
4888
  * missing components OR an author's declaration this dsh does not meet —
4887
4889
  * so these three name the badge, not one of its reasons. */
4888
- incompatibleFilterTitle: "不显示标为不兼容的插件",
4889
- showIncompatibleTitle: "把标为不兼容的插件放回列表",
4890
- emptyIncompatibleFiltered: "匹配的插件都标为不兼容,已被「隐藏不兼容」筛掉。",
4891
- license: "许可",
4892
- customLicense: "自定义许可",
4893
- reviewedVersionLine: "已审核 {reviewed} / 当前 {current} 未审核",
4894
- reviewedPinLine: "已审核 {pin} / 当前 {current} 未审核",
4895
- deniedCode: "已拒绝",
4890
+ incompatibleFilterTitle: "隐藏标为不兼容的插件",
4891
+ showIncompatibleTitle: "显示标为不兼容的插件",
4892
+ emptyIncompatibleFiltered: "匹配的插件都标为不兼容。关闭“隐藏不兼容”即可查看。",
4893
+ license: "许可证",
4894
+ customLicense: "自定义许可证",
4895
+ reviewedVersionLine: "已审核版本:{reviewed} · 当前版本 {current} 尚未审核",
4896
+ reviewedPinLine: "已审核:{pin} · 当前版本 {current} 尚未审核",
4897
+ deniedCode: "不允许安装",
4896
4898
  notInCatalogCode: "不在目录中",
4897
4899
  versionMismatchCode: "版本不匹配",
4898
- needsAcknowledgementCode: "未确认",
4900
+ needsAcknowledgementCode: "需要确认安装",
4899
4901
  tarballIntegrityCode: "压缩包完整性校验失败",
4900
- ambiguousIdentityCode: "无法确定是哪一个条目",
4901
- nameTakenCode: "同名的另一个插件已安装",
4902
- desktopProfileCode: "由桌面版管理的 profile",
4903
- nameTakenBadge: "名称被占用",
4904
- nameTakenDetail: "已安装同名的另一个插件({holder})。\n两个同名插件声明相同的加载项 id,无法共存;安装它会替换掉已装的那个。",
4902
+ ambiguousIdentityCode: "无法确定要安装的插件",
4903
+ nameTakenCode: "已安装其他同名插件",
4904
+ desktopProfileCode: "此配置由桌面版管理",
4905
+ nameTakenBadge: "名称冲突",
4906
+ nameTakenDetail: "已安装同名插件({holder}),安装此插件会替换它。\n同一配置中只能安装一个同名插件。若要更换,请先卸载已安装的插件。",
4905
4907
  npmSource: "npm 包",
4906
- githubSource: "源码在 GitHub",
4908
+ githubSource: "GitHub 源码",
4907
4909
  /** The accessible name and the tooltip of a card's version, which say which
4908
4910
  * of three things the printed string is (market borrowings §11): the string
4909
4911
  * alone cannot tell a release tag from a version number, and a short commit
4910
4912
  * printed with a `v` read as one. */
4911
4913
  npmVersionLabel: "npm 版本 {version}",
4912
4914
  commitVersionLabel: "GitHub 提交 {version}",
4913
- releaseVersionLabel: "GitHub 发布 {version}",
4915
+ releaseVersionLabel: "GitHub 发布版本 {version}",
4914
4916
  /** The badge and the explanation for an installed github entry no catalog
4915
4917
  * row claims (C10 MVP). The mechanism synthesizes a row for ANY github spec
4916
4918
  * with no catalog row — shadowed by a same-named npm package, an unlisted
4917
4919
  * fork, a removed listing, or a same-named conflict with a different repo —
4918
4920
  * so the copy names the catalog fact and lists the causes, rather than
4919
4921
  * claiming the npm-twin one. */
4920
- shadowedBadge: "目录未列出此包",
4921
- shadowedDetail: "此包已安装,但当前的插件目录没有它(可能是被同名 npm 包遮蔽、作者已下架、或目录尚未收录);它照常运行。",
4922
- updatableSection: "可更新",
4922
+ shadowedBadge: "未在目录中列出",
4923
+ shadowedDetail: "此插件已安装,但当前目录未列出它,不影响现有使用。可能是目录只展示了同名 npm 包、插件已下架,或尚未被收录。",
4924
+ updatableSection: "有可用更新",
4923
4925
  /** `{version}` is the shelf's own version text (`versionLabel`), which
4924
4926
  * carries the `v` an npm version takes and no other kind does. */
4925
4927
  installedVersion: "已安装 {version}",
4926
- latestVersion: "最新 {version}",
4928
+ latestVersion: "最新版本 {version}",
4927
4929
  update: "更新",
4928
4930
  installed: "已安装",
4929
4931
  uninstall: "卸载",
4930
4932
  uninstalling: "正在卸载…",
4931
4933
  uninstallFailed: "卸载失败",
4932
- uninstallTransportFailed: "卸载请求未能送达。请稍后重试。",
4933
- uninstalledLiveNotice: "已卸载并立即停止。profile 的启动组合将在下次重启时应用该移除。",
4934
- uninstalledRestartNotice: "已卸载;重启 dsh 后生效",
4935
- uninstalledReloadNotice: "已卸载;刷新页面即可看到它消失。profile 的启动组合将在下次重启时应用该移除。",
4934
+ uninstallTransportFailed: "卸载请求失败,请稍后重试。",
4935
+ uninstalledLiveNotice: "已卸载并停止运行,下次启动 dsh 时也不会再加载此插件。",
4936
+ uninstalledRestartNotice: "已卸载,重启 dsh 后生效。",
4937
+ uninstalledReloadNotice: "已卸载,刷新页面后即可移除界面中的相关内容。下次启动 dsh 时也不会再加载此插件。",
4936
4938
  restart: "重启 dsh",
4937
- restartTitle: "确认重启 dsh",
4938
- restartBody: "重启会断开当前页面与服务器的连接,正在进行的对话和任务会中断;浏览器会自动跳转到重启后的地址,稍等片刻即可继续使用。",
4939
+ restartTitle: "重启 dsh?",
4940
+ restartBody: "重启会暂时断开连接,并中断正在进行的对话和任务。服务器就绪后,页面会自动重新连接。",
4939
4941
  restartConfirm: "重启",
4940
- restarting: "正在重启,服务器就绪后页面会自动刷新…",
4941
- restartFailedNotice: "重启后服务器没有回来。请手动启动:dsh web(新进程的日志在商店缓存目录的 restart.log)",
4942
+ restarting: "正在重启 dsh,服务器就绪后页面会自动刷新…",
4943
+ restartFailedNotice: "重启后未能连接到服务器。请运行 dsh web 手动启动;启动日志位于插件商店缓存目录中的 restart.log。",
4942
4944
  /** The same failure, when the host said where the new process writes —
4943
4945
  * which is where a boot that died explains itself (design
4944
4946
  * 2026-09-26-market-borrowings §3). "Or did not stay up": the page now
4945
4947
  * waits for the new server to keep answering, so this also covers a boot
4946
4948
  * that answered and then died. */
4947
- restartFailedLogNotice: "重启后服务器没有回来,或回来后没能保持运行。新进程的日志在 {log};也可以手动启动:dsh web",
4948
- restartTransportFailed: "重启请求未能送达。请稍后重试。",
4949
- notRestartedNotice: "dsh 没有重启:页面刷新后,回答的仍是被要求重启的那个进程,所以更改还没有生效。请手动重启:退出正在运行的 dsh,再运行 dsh web。",
4950
- notRestartedPidNotice: "dsh 没有重启:页面刷新后,回答的仍是被要求重启的那个进程(pid {pid}),所以更改还没有生效。请手动重启:运行 kill {pid}(进程仍不退出时用 kill -9 {pid}),再运行 dsh web。",
4951
- notRestartedLogNotice: "这次重启的经过记录在 {log}。",
4952
- restartBlockedDesktopNotice: "此 profile 由 DeepSeek Harness 桌面版管理,商店无法重启它。请重启桌面版,以应用更改。",
4953
- restartBlockedFromDefaultProfileNotice: "此 dsh 以 --from-default-profile 启动。从 dsh 0.2.1 起,重启会按模板重建 profile,删除其中安装的全部插件,包括商店。请去掉该参数后手动重启 dsh,以应用更改。",
4954
- restartBlockedWindowsNotice: "商店暂不支持在 Windows 上重启 dsh。请退出 dsh 后重新启动,以应用更改。",
4955
- restartBlockedSystemdNotice: "当前 dsh 由 systemd 服务托管,商店无法安全地重启它。请手动重启服务,或在商店行配置中设置 allowRestart: true。",
4956
- restartBlockedPortZeroNotice: "此 dsh 以 --port 0 启动,重启后会绑定到另一个端口,本页面无法跟上。请手动重启 dsh,以应用更改。",
4949
+ restartFailedLogNotice: "重启后未能连接到服务器,或服务器启动后再次停止。请查看启动日志 {log},也可运行 dsh web 手动启动。",
4950
+ restartTransportFailed: "重启请求失败,请稍后重试。",
4951
+ notRestartedNotice: "dsh 尚未重启,当前仍连接到原来的进程,更改尚未生效。请退出正在运行的 dsh,再运行 dsh web。",
4952
+ notRestartedPidNotice: "dsh 尚未重启,原来的进程(PID {pid})仍在运行,更改尚未生效。请运行 kill {pid} 结束该进程;若无法退出,可用 kill -9 {pid} 强制结束。然后运行 dsh web。",
4953
+ notRestartedLogNotice: "重启日志:{log}",
4954
+ restartBlockedDesktopNotice: "此配置由 DeepSeek Harness 桌面版管理。请重启桌面版以使更改生效。",
4955
+ restartBlockedFromDefaultProfileNotice: "dsh 启动时使用了 --from-default-profile 参数。从 dsh 0.2.1 起,这会在重启时按模板重建配置,并删除已安装的所有插件,包括插件商店。请去掉该参数后手动重启 dsh。",
4956
+ restartBlockedWindowsNotice: "Windows 上暂不支持从插件商店重启 dsh。请退出 dsh 后重新启动,使更改生效。",
4957
+ restartBlockedSystemdNotice: "dsh 由 systemd 服务管理。从插件商店重启可能导致服务停止后无法恢复,请手动重启服务。若要允许从商店重启,可在插件商店配置中设置 allowRestart: true。",
4958
+ restartBlockedPortZeroNotice: "dsh 启动时使用了 --port 0 参数,重启后端口会改变,页面无法自动重新连接。请手动重启 dsh,再打开新的地址。",
4957
4959
  updateFailed: "更新失败",
4958
- updateTransportFailed: "更新请求未能送达。请稍后重试。",
4960
+ updateTransportFailed: "更新请求失败,请稍后重试。",
4959
4961
  checkUpdate: "检查更新",
4960
4962
  checkUpdateTitle: "检查是否有新版本",
4961
4963
  upToDate: "已是最新版本",
4962
4964
  github: "GitHub",
4963
4965
  enabledSwitch: "启用",
4964
- toggleFailed: "设置失败,请重试。",
4965
- hotApplyNote: "已生效,无需重启",
4966
- toggleRestartNote: "已保存,重启 dsh 后生效"
4966
+ toggleFailed: "未能更改启用状态,请重试。",
4967
+ hotApplyNote: "已生效,无需重启。",
4968
+ toggleRestartNote: "已保存,重启 dsh 后生效。"
4967
4969
  };
4968
4970
  /** English dictionary checked against the Chinese key set. */
4969
4971
  const en = {
4970
4972
  title: "Plugin shop",
4971
- intro: "Browse, install, and update plugins from the catalog.",
4973
+ intro: "Browse, install, and update plugins.",
4972
4974
  openShop: "Open Plugin shop",
4973
4975
  viewInShop: "View in Plugin shop",
4974
- loading: "Reading the catalog…",
4975
- error: "The catalog is temporarily unavailable.",
4976
- installedLoading: "Reading the installed list…",
4977
- installedError: "The installed list is temporarily unavailable.",
4976
+ loading: "Loading plugins…",
4977
+ error: "Could not load the plugin catalog. Please try again.",
4978
+ installedLoading: "Loading installed plugins…",
4979
+ installedError: "Could not load your installed plugins. Please try again.",
4978
4980
  retry: "Retry",
4979
- tabCrashed: "The plugin shop hit an error while showing this page.",
4981
+ tabCrashed: "The plugin shop could not display this page. Please try again.",
4980
4982
  search: "Search plugins",
4981
4983
  /** One subdued line under the search box when a Chinese query fired a
4982
4984
  * dictionary intent (design 2026-10-08-search-query-expansion §4). */
4983
4985
  zhExpansionNote: "Also matching: {terms}",
4984
4986
  all: "All",
4985
4987
  catalog: "Plugin catalog",
4986
- catalogStats: "{count} packages · built {date}",
4988
+ catalogStats: "{count} plugins · Catalog updated {date}",
4987
4989
  /** The shelf's order control, and the date each card carries under the two
4988
4990
  * date orders (design 2026-10-12-shelf-orders §4). */
4989
4991
  sortLabel: "Sort",
4990
- sortStars: "Stars",
4992
+ sortStars: "Most stars",
4991
4993
  sortAdded: "Newest",
4992
4994
  sortUpdated: "Recently updated",
4993
4995
  listedOn: "Listed {date}",
4994
4996
  updatedOn: "Updated {date}",
4995
4997
  stars: "{count} stars",
4996
- categoryTool: "Tool",
4997
- categoryProvider: "Provider",
4998
- categoryUi: "UI",
4999
- categoryWorkflow: "Workflow",
5000
- categoryIntegration: "Integration",
4998
+ categoryTool: "Tools",
4999
+ categoryProvider: "Model providers",
5000
+ categoryUi: "Interface",
5001
+ categoryWorkflow: "Workflows",
5002
+ categoryIntegration: "Integrations",
5001
5003
  categoryTheme: "Themes",
5002
5004
  categoryOther: "Other",
5003
- empty: "No plugins in the catalog.",
5004
- emptySearch: "No matching plugins.",
5005
- tierVerified: "Verified",
5005
+ empty: "No plugins yet.",
5006
+ emptySearch: "No matching plugins. Try another search or category.",
5007
+ tierVerified: "Reviewed",
5006
5008
  /** The github repository GitHub reports as archived (design 2026-10-08 §1.3).
5007
5009
  * Subdued register: nothing is wrong with the install; the owner has simply
5008
5010
  * frozen the project. */
5009
5011
  archivedBadge: "Archived",
5010
- tierVerifiedStale: "Reviewed version stale",
5012
+ tierVerifiedStale: "Current version unreviewed",
5011
5013
  tierCommunity: "Community",
5012
- capabilitiesNote: "self-declared by the author, not enforced",
5014
+ capabilitiesNote: "These permissions are declared by the author. They do not limit what the plugin can access.",
5013
5015
  install: "Install",
5014
5016
  cancel: "Cancel",
5015
- dismiss: "Dismiss",
5017
+ dismiss: "Got it",
5016
5018
  moreRedLines: "Show more",
5017
5019
  lessRedLines: "Show less",
5018
5020
  installing: "Installing…",
5019
5021
  downloading: "Downloading…",
5020
- installedRestartNotice: "installed; restart dsh to activate",
5022
+ installedRestartNotice: "Installed. Restart dsh to apply the change.",
5021
5023
  incompatibleBadge: "Incompatible",
5022
- incompatibleDetail: "Missing components/plugins: {modules}\nThis plugin may be designed for another DSH version.",
5023
- harnessRangeDetail: "The author declares support for dsh {range}; this machine runs {running}.",
5024
- harnessProfileDetail: "The author declares support for the {declared} profile(s); this dsh was launched with the {running} profile.",
5025
- harnessPeersDetail: "dsh {running} on this machine refuses to install this plugin: it requires {peers}.",
5026
- harnessPeersRemedy: "To accept the risk of crashes or data loss for this exact version, run this in a terminal, then press Refresh:",
5027
- hotNoPatchNotice: "no patch file to hot-mount; restart dsh to activate",
5028
- hotNotSimpleNotice: "the patch has config rows that cannot be hot-mounted; restart dsh to activate",
5029
- hotHostUnsupportedNotice: "this harness cannot hot-mount; restart dsh to activate",
5030
- hotTimeoutNotice: "hot-mount timed out; restart dsh to activate",
5031
- hotMountFailedNotice: "hot-mount failed; restart dsh to activate",
5032
- hotAlreadyLoadedNotice: "installed; this dsh has already loaded the plugin once, so the files just installed take effect after a restart of dsh",
5033
- installedNoRestartNotice: "installed and hot-mounted; running now, no restart needed",
5034
- installedReloadNotice: "already applied; reload the page to see it",
5024
+ incompatibleDetail: "Missing required components or plugins: {modules}\nThis plugin may need a different version of dsh.",
5025
+ harnessRangeDetail: "The author lists support for dsh {range}. You are running {running}.",
5026
+ harnessProfileDetail: "The author lists support for these profiles: {declared}. You are using {running}.",
5027
+ harnessPeersDetail: "This plugin requires {peers}, which your dsh {running} does not satisfy. Installation is blocked.",
5028
+ harnessPeersRemedy: "Installing this version may cause crashes or data loss. If you accept these risks, run this command in a terminal to allow this exact version, then click Refresh:",
5029
+ hotNoPatchNotice: "Installed, but no plugin configuration could be loaded immediately. Restart dsh to apply the change.",
5030
+ hotNotSimpleNotice: "Installed. This plugin configuration needs a dsh restart to load.",
5031
+ hotHostUnsupportedNotice: "Installed. Your environment needs a dsh restart to load the plugin.",
5032
+ hotTimeoutNotice: "Installed, but loading timed out. Restart dsh to apply the change.",
5033
+ hotMountFailedNotice: "Installed, but the plugin could not load. Restart dsh to apply the change.",
5034
+ hotAlreadyLoadedNotice: "Installed. dsh has already loaded this plugin, so the new files will take effect after a restart.",
5035
+ installedNoRestartNotice: "Installed and ready to use. No restart needed.",
5036
+ installedReloadNotice: "Applied. Reload the page to see the change.",
5035
5037
  reload: "Reload the page",
5036
- reloadNote: "The server is already in the new state; this page is still showing what came before. Reloading discards anything in flight on this page.",
5038
+ reloadNote: "The change is active on the server. Reload the page to see it. Any unfinished work on this page will be lost.",
5037
5039
  installFailed: "Install failed",
5038
- installTransportFailed: "The install request could not be delivered. Please retry.",
5040
+ installTransportFailed: "The install request failed. Please try again.",
5039
5041
  refresh: "Refresh",
5040
5042
  refreshing: "Refreshing…",
5041
- refreshFailed: "Reload failed; still showing the last catalog",
5042
- staleLabel: "showing {date}",
5043
- showing: "showing first {shown} of {total} — scroll to load more",
5043
+ refreshFailed: "Could not refresh. Showing the last loaded catalog.",
5044
+ staleLabel: "Showing the catalog from {date}",
5045
+ showing: "Showing {shown} of {total} · Scroll down to load more",
5044
5046
  repository: "Repository",
5045
5047
  npmPage: "npm page",
5046
5048
  authorLine: "Author: {author}",
5047
5049
  sizeLabel: "{size} on disk",
5048
5050
  hideIncompatible: "Hide incompatible {count}",
5049
- incompatibleFilterTitle: "Leave out plugins marked incompatible with this dsh",
5050
- showIncompatibleTitle: "Put plugins marked incompatible back on the shelf",
5051
- emptyIncompatibleFiltered: "Every matching plugin is marked incompatible, and the incompatible filter is leaving them out.",
5051
+ incompatibleFilterTitle: "Hide plugins marked as incompatible",
5052
+ showIncompatibleTitle: "Show plugins marked as incompatible",
5053
+ emptyIncompatibleFiltered: "All matching plugins are marked as incompatible. Turn off \"Hide incompatible\" to see them.",
5052
5054
  license: "License",
5053
5055
  customLicense: "Custom license",
5054
- reviewedVersionLine: "reviewed {reviewed} / current {current} unreviewed",
5055
- reviewedPinLine: "reviewed {pin} / current {current} unreviewed",
5056
- deniedCode: "Denied",
5056
+ reviewedVersionLine: "Reviewed: {reviewed} · Current version {current} has not been reviewed",
5057
+ reviewedPinLine: "Reviewed: {pin} · Current version {current} has not been reviewed",
5058
+ deniedCode: "Installation blocked",
5057
5059
  notInCatalogCode: "Not in the catalog",
5058
5060
  versionMismatchCode: "Version mismatch",
5059
- needsAcknowledgementCode: "Not acknowledged",
5060
- tarballIntegrityCode: "tarball integrity check failed",
5061
- ambiguousIdentityCode: "Ambiguous entry",
5062
- nameTakenCode: "Another plugin holds this name",
5061
+ needsAcknowledgementCode: "Installation confirmation needed",
5062
+ tarballIntegrityCode: "Package integrity check failed",
5063
+ ambiguousIdentityCode: "Could not identify the plugin",
5064
+ nameTakenCode: "Another plugin with this name is installed",
5063
5065
  desktopProfileCode: "Managed by the desktop app",
5064
- nameTakenBadge: "Name taken",
5065
- 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.",
5066
+ nameTakenBadge: "Name conflict",
5067
+ nameTakenDetail: "Another plugin with this name is installed ({holder}). Installing this one would replace it.\nA profile can hold only one plugin of each name. To switch, uninstall the existing plugin first.",
5066
5068
  npmSource: "npm package",
5067
- githubSource: "source on GitHub",
5069
+ githubSource: "GitHub source",
5068
5070
  npmVersionLabel: "npm version {version}",
5069
5071
  commitVersionLabel: "GitHub commit {version}",
5070
5072
  releaseVersionLabel: "GitHub release {version}",
5071
5073
  shadowedBadge: "Not in the current catalog",
5072
- 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.",
5073
- updatableSection: "Updatable",
5074
- installedVersion: "installed {version}",
5075
- latestVersion: "latest {version}",
5074
+ shadowedDetail: "This plugin is installed but is not listed in the current catalog. You can keep using it. The catalog may show an npm package of the same name, or this plugin may have been removed or never listed.",
5075
+ updatableSection: "Updates available",
5076
+ installedVersion: "Installed {version}",
5077
+ latestVersion: "Latest version {version}",
5076
5078
  update: "Update",
5077
5079
  installed: "Installed",
5078
5080
  uninstall: "Uninstall",
5079
5081
  uninstalling: "Uninstalling…",
5080
5082
  uninstallFailed: "Uninstall failed",
5081
- uninstallTransportFailed: "The uninstall request could not be delivered. Please retry.",
5082
- uninstalledLiveNotice: "Removed and stopped immediately. The profile's boot composition picks up the removal at the next restart.",
5083
- uninstalledRestartNotice: "uninstalled; restart dsh to activate",
5084
- uninstalledReloadNotice: "removed; reload the page to see it gone. The profile's boot composition picks up the removal at the next restart.",
5083
+ uninstallTransportFailed: "The uninstall request failed. Please try again.",
5084
+ uninstalledLiveNotice: "Uninstalled and stopped. The plugin will no longer load when dsh starts.",
5085
+ uninstalledRestartNotice: "Uninstalled. Restart dsh to finish removing the plugin.",
5086
+ uninstalledReloadNotice: "Uninstalled. Reload the page to remove its interface. The plugin will no longer load when dsh starts.",
5085
5087
  restart: "Restart dsh",
5086
5088
  restartTitle: "Restart dsh?",
5087
- restartBody: "Restarting disconnects this page and interrupts any conversation or task in progress; the browser will jump to the restarted address automatically, so give it a few seconds.",
5089
+ restartBody: "Restarting will temporarily disconnect this page and interrupt ongoing conversations and tasks. The page will reconnect automatically when the server is ready.",
5088
5090
  restartConfirm: "Restart",
5089
- restarting: "Restarting; the page will refresh once the server is back…",
5090
- restartFailedNotice: "The server did not come back after the restart. Start it manually: dsh web (see restart.log in the shop cache directory)",
5091
- restartFailedLogNotice: "The server did not come back after the restart, or did not stay up. The new process logs to {log}; you can also start it manually: dsh web",
5092
- restartTransportFailed: "The restart request could not be delivered. Please retry.",
5093
- notRestartedNotice: "dsh did not restart: after the reload, the process that was asked to restart is still the one answering, so the change has not taken effect. Restart it by hand: quit the running dsh, then run dsh web.",
5094
- notRestartedPidNotice: "dsh did not restart: after the reload, the process that was asked to restart (pid {pid}) is still the one answering, so the change has not taken effect. Restart it by hand: run kill {pid} (kill -9 {pid} if it does not exit), then run dsh web.",
5095
- notRestartedLogNotice: "What this restart did is logged in {log}.",
5096
- restartBlockedDesktopNotice: "This profile is managed by the DeepSeek Harness desktop app, so the shop cannot restart it. Restart the app to apply the change.",
5097
- restartBlockedFromDefaultProfileNotice: "This dsh was launched with --from-default-profile. From dsh 0.2.1 a restart would recreate the profile from its template and delete every plugin installed in it, this shop included. Restart dsh manually without that flag to apply the change.",
5098
- restartBlockedWindowsNotice: "The shop cannot restart dsh on Windows yet. Quit dsh and start it again to apply the change.",
5099
- restartBlockedSystemdNotice: "This dsh process runs as a systemd service, so the shop cannot restart it safely. Restart the service manually, or set allowRestart: true in the shop row config.",
5100
- restartBlockedPortZeroNotice: "This dsh was launched with --port 0, so a restart would bind a different port and this page could not follow it. Restart dsh manually to apply the change.",
5091
+ restarting: "Restarting dsh. The page will reload when the server is ready…",
5092
+ restartFailedNotice: "Could not connect to the server after restarting. Run dsh web to start it manually. See restart.log in the plugin shop cache directory for the startup log.",
5093
+ restartFailedLogNotice: "Could not connect to the server after restarting, or the server stopped again. Check the startup log at {log}, or run dsh web to start it manually.",
5094
+ restartTransportFailed: "The restart request failed. Please try again.",
5095
+ notRestartedNotice: "dsh has not restarted. This page is still connected to the original process, so the change has not taken effect. Quit the running dsh, then run dsh web.",
5096
+ notRestartedPidNotice: "dsh has not restarted. The original process (PID {pid}) is still running, so the change has not taken effect. Run kill {pid} to stop it; if it will not exit, use kill -9 {pid} to force it to stop. Then run dsh web.",
5097
+ notRestartedLogNotice: "Restart log: {log}",
5098
+ restartBlockedDesktopNotice: "This profile is managed by the DeepSeek Harness desktop app. Restart the app to apply the change.",
5099
+ restartBlockedFromDefaultProfileNotice: "dsh was started with --from-default-profile. In dsh 0.2.1 and later, restarting with this flag recreates the profile from its template and deletes all installed plugins, including the shop. Restart dsh manually without this flag.",
5100
+ restartBlockedWindowsNotice: "Restarting dsh from the plugin shop is not supported on Windows yet. Quit dsh and start it again to apply the change.",
5101
+ restartBlockedSystemdNotice: "dsh is managed by systemd. Restarting from the shop could stop the service without bringing it back. Restart the service manually. To allow restarts from the shop, set allowRestart: true in the shop configuration.",
5102
+ restartBlockedPortZeroNotice: "dsh was started with --port 0. Its port will change after a restart, so this page cannot reconnect automatically. Restart dsh manually and open the new address.",
5101
5103
  updateFailed: "Update failed",
5102
- updateTransportFailed: "The update request could not be delivered. Please retry.",
5104
+ updateTransportFailed: "The update request failed. Please try again.",
5103
5105
  checkUpdate: "Check",
5104
5106
  checkUpdateTitle: "Check for updates",
5105
5107
  upToDate: "Up to date",
5106
5108
  github: "GitHub",
5107
5109
  enabledSwitch: "Enable",
5108
- toggleFailed: "Failed to update. Please retry.",
5109
- hotApplyNote: "applied without a restart",
5110
- toggleRestartNote: "saved; it takes effect after dsh restarts"
5110
+ toggleFailed: "Could not change the enabled state. Please try again.",
5111
+ hotApplyNote: "Applied. No restart needed.",
5112
+ toggleRestartNote: "Saved. Restart dsh to apply the change."
5111
5113
  };
5112
5114
  //#endregion
5113
5115
  //#region src/client/module-table.ts
@@ -7306,7 +7308,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7306
7308
  const { status } = event;
7307
7309
  if (!status.found) return {
7308
7310
  kind: "failed",
7309
- detail: status.detail ?? "install record lost",
7311
+ detail: status.detail ?? "The installation record is no longer available. Refresh the shop to check whether the plugin was installed.",
7310
7312
  log: status.log
7311
7313
  };
7312
7314
  if (!isTerminalInstallState(status.state)) return {
@@ -7471,6 +7473,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7471
7473
  return cleared.has(entry.name) || !isShopLike(entry.name) && (entry.repo === void 0 || !isShopLike(entry.repo));
7472
7474
  }
7473
7475
  /**
7476
+ * An entry's icon as the catalog's data URL, or undefined to draw the default:
7477
+ * it declares none, the catalog predates the icons file, or the sidecar
7478
+ * degraded and the map does not carry its sha256. The map is keyed by 64-hex
7479
+ * sha256, so no name from the catalog is ever used as a lookup here (design
7480
+ * 2026-10-23-shop-plugin-icons §7).
7481
+ */
7482
+ function iconOf(entry, icons) {
7483
+ const sha = entry?.icon;
7484
+ if (sha === void 0) return void 0;
7485
+ return Object.hasOwn(icons, sha) ? icons[sha] : void 0;
7486
+ }
7487
+ /**
7474
7488
  * One entry's star count from the sidecar, or undefined when it has none.
7475
7489
  *
7476
7490
  * The sidecar keys a github entry by its repo full name and an npm entry by
@@ -8417,136 +8431,142 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8417
8431
  if (typeof held?.dispose === "function") held.dispose();
8418
8432
  }
8419
8433
  //#endregion
8420
- //#region \0dsh-plugin-shop-css:9836f849
8421
- const css = "/* Shop styles — the shop's own page with its market shelf, and the shop's two\n * buttons on dsh's Plugins page. Every variable read here is a dsh theme\n * alias token (`--dsw-alias-*`), one of the shop's own properties declared in\n * this file (`--shop-*`, `--category-hue`, `--switch-hue`, `--track-hue`), or\n * one of two dsh variables outside the alias set, each read where the shop\n * copies dsh: `--dsh-frame-top-clearance`, so the page head clears the\n * desktop frame as dsh's own pages do, and `--dsw-radius-sm`, because the\n * Plugins page buttons copy dsh's Button (0.1.5-rc.3 lacks that token, and\n * has no Plugins page to draw them on). `state-error` and\n * `state-success` do not exist, so the state-*-primary set stands in for\n * them (measured 2026-10-17 on dsh 0.1.5-rc.3, 0.1.7-rc.2 and 0.2.0-rc.2,\n * which do define `bg-layer-3` and `interactive-bg-hover`, once listed here\n * as missing). 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.sac9242_client.sbb38d9_config.s3f448c_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.sac9242_client.sbcffb6_spec.s3f448c_ts` pins this. */\n\n.sdda1b3_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 6.14:1 on\n * the dark page itself (#151517, `bg-base`), where the self-update's\n * failure heading sits (4.69:1 on Settings' #2c2c2e, its ground through\n * 0.8.7), 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 `.sb7555e_searchInput` sits at\n * exactly (0, 0) of this box — it is the first child of `.sf8bc6e_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/* The shop's own page in dsh's main column (design\n * 2026-10-16-shop-sidebar-page §4.2), framed as dsh 0.2.0-rc.2 frames its\n * Plugins page — read from that page's stylesheet: the page is the scroll\n * container, its children one centred column of at most 960px, and the head\n * clears the desktop title bar. One declaration is the shop's own, `color`:\n * dsh's `.s17c3b8_page` inherits its colour from the shell around it, and the shop's\n * root is not inside dsh's panel stylesheet, so the shop's page states it. */\n.s17c3b8_page {\n box-sizing: border-box;\n height: 100%;\n overflow: auto;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 32px;\n padding: 0 clamp(24px, 4vw, 48px) 48px;\n color: var(--dsw-alias-label-primary);\n}\n\n.s17c3b8_page > * {\n width: 100%;\n max-width: 960px;\n}\n\n.s9bd177_pageHead {\n box-sizing: border-box;\n padding-top: calc(28px + var(--dsh-frame-top-clearance, 0px));\n}\n\n.s8701f9_pageTitle {\n margin: 0;\n font-size: 20px;\n font-weight: 500;\n line-height: 28px;\n}\n\n/* Secondary, as dsh's own intro: 5.80:1 on the light theme's white. */\n.se07ab1_pageIntro {\n margin: 4px 0 0;\n font-size: 13px;\n line-height: 20px;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The two buttons on dsh's Plugins page (design 2026-10-16-shop-sidebar-page\n * §7), drawn as that page draws its own small outline buttons beside them —\n * dsh 0.2.0-rc.2's Button, sm + outline: 28px tall, 12px text on an 18px\n * line, 10px of side padding, the small radius, the neutral stroke. That\n * stroke is 0.5px in dsh and 1px here: every border in this stylesheet is,\n * and Chromium paints 0.5px as 1px at every ratio measured. */\n.s2e85d9_detailAction {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 28px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l3);\n border-radius: var(--dsw-radius-sm);\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n}\n\n.s2e85d9_detailAction:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.s2e85d9_detailAction:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sae65aa_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.scf7e39_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.sc6fc10_skeletonGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n}\n\n.s26bc9c_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.s26bc9c_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.s47e7ae_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.s88d4ac_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.s335a0e_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.sd4ae24_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.s26bc9c_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 .s26bc9c_skeletonCard span { animation: none; opacity: 0.75; }\n .s26bc9c_skeletonCard::after { content: none; }\n}\n\n.se320ba_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.se320ba_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.se320ba_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sf8bc6e_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.sb1f0e2_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.s42e55a_versionBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.sea2e4c_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.s2a2b0f_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.s2a2b0f_updateSelfButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, transparent);\n}\n\n.s2a2b0f_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.s2b0e48_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.s2b0e48_checkUpdateButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n}\n\n.s2b0e48_checkUpdateButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.s2b0e48_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.sebb833_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.sebb833_githubLink:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sebb833_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.sf4760e_selfUpdatePanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n}\n\n.sb7555e_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.sb7555e_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.se91028_expansionNote {\n margin: 4px 0 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s9eff21_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.s422238_categoryBar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n margin-top: 6px;\n}\n\n.s1c2c12_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.s1c2c12_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 `.s1c2c12_categoryButton.s422f17_categoryButtonOn` — TWO classes — so it\n * matches `.s1c2c12_categoryButton:hover`'s specificity and, coming after it, wins.\n * As a bare `.s422f17_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.sac9242_client.sbcffb6_spec.s3f448c_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.s1c2c12_categoryButton.s422f17_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 .s1c2c12_categoryButton.s422f17_categoryButtonOn {\n outline: 2px solid ButtonText;\n outline-offset: -4px;\n }\n}\n\n.s1c2c12_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 `.s1c2c12_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.s34065e_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: `.s34065e_incompatibleFilter:hover .scd23e0_switch` outranks `.s6ba5b7_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.s34065e_incompatibleFilter:hover {\n color: var(--dsw-alias-label-primary);\n}\n\n.s34065e_incompatibleFilter:hover .scd23e0_switch:not(.s6ba5b7_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 `.s1c2c12_categoryButton.s422f17_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.s34065e_incompatibleFilter.s8b7f04_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.s34065e_incompatibleFilter .scd23e0_switch:not(.s6ba5b7_switchOn) .sdea483_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.s34065e_incompatibleFilter:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: 999px;\n}\n\n.se80247_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.sf4da7d_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.sbcc81f_sortGroup {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin-left: auto;\n}\n\n.s53a618_sortLabel {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.sa283c6_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 `.se320ba_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.sc2681d_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.sc2681d_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.sc2681d_catalogRefreshButton:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.sc2681d_catalogRefreshButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s3bc86f_catalogRefreshFailed {\n font-size: 11px;\n color: var(--shop-warn);\n}\n\n.sa758ba_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.sb8570f_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 .sb8570f_cards { animation: none; }\n}\n\n.s411ccd_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.saf4e10_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s5432f6_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s411ccd_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 .s411ccd_card { transition: none; }\n .s411ccd_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.s29077c_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. `.s1c2c12_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.s411ccd_card[data-category='tool'], .s1c2c12_categoryButton[data-category='tool'] { --category-hue: #4C8DFF; }\n.s411ccd_card[data-category='provider'], .s1c2c12_categoryButton[data-category='provider'] { --category-hue: #A78BFA; }\n.s411ccd_card[data-category='ui'], .s1c2c12_categoryButton[data-category='ui'] { --category-hue: #2DD4BF; }\n.s411ccd_card[data-category='workflow'], .s1c2c12_categoryButton[data-category='workflow'] { --category-hue: #F59E0B; }\n.s411ccd_card[data-category='integration'], .s1c2c12_categoryButton[data-category='integration'] { --category-hue: #34D399; }\n.s411ccd_card[data-category='theme'], .s1c2c12_categoryButton[data-category='theme'] { --category-hue: #F472B6; }\n.s411ccd_card[data-category='other'], .s1c2c12_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.sfa1350_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.s772eab_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.s772eab_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.s85cdde_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.s75c5b0_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.s85dccd_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.s85dccd_cardActions > .s86cdae_gate,\n.s85dccd_cardActions > .sda5e0f_installPanel,\n.s85dccd_cardActions > .s6a16f4_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.sb178f4_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.s8907e4_sourceBadge {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa859d0_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.sa859d0_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.sa859d0_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.sa859d0_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.sa859d0_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.sa859d0_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.s5fe867_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.s7ae585_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.saf9723_chevron, .s48b05a_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.s48b05a_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .saf9723_chevron, .s48b05a_chevronOpen { transition: none; }\n}\n\n.se348a3_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.s665b9a_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.s4ecb2d_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.s61ff4e_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.s19c6c2_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.sd1bf96_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.s8dbfd5_remedy {\n display: block;\n}\n\n.s8dbfd5_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 `.sed5ba4_foldBlock` collapsed and `.sde670b_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.sed5ba4_foldBlock {\n display: block;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n display: -webkit-box;\n overflow: hidden;\n}\n\n.sde670b_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.s37ce74_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.s37ce74_foldToggle:hover { border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2)); }\n.s37ce74_foldToggle:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }\n\n.s2e4bfe_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.sceb183_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.s2ca2e4_refusal {\n display: block;\n flex-basis: 100%;\n}\n\n.s2ca2e4_refusal > * + * { margin-top: 6px; }\n\n.s91ec65_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s591a82_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s1dc988_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s1dc988_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.s7ddbb4_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.sd74c84_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.sff39ff_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.sff39ff_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.sff39ff_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.sff39ff_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.sff39ff_detailRow a:hover {\n text-decoration: underline;\n}\n\n.sff39ff_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 * `.s85dccd_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.sd181e4_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: `.sd181e4_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 * `.s27a949_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.sef189b_size,\n.s27a949_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 `.sd181e4_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 `.sd181e4_cardMeta`. There is no middot:\n * `.sd181e4_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.s51ede0_author {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.sb76fb3_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.sda5e0f_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.s7ee92c_installButton,\n.s9a7a2b_confirmButton,\n.s99e4b2_cancelButton,\n.s87fa57_installedLabel,\n.s6c9846_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.s7ee92c_installButton, .s9a7a2b_confirmButton, .s99e4b2_cancelButton {\n cursor: pointer;\n}\n\n.s7ee92c_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.s7ee92c_installButton:disabled {\n cursor: wait;\n}\n\n.s7ee92c_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s9a7a2b_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.s9a7a2b_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s99e4b2_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.s99e4b2_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s7ee92c_installButton:focus-visible, .s9a7a2b_confirmButton:focus-visible, .s99e4b2_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.s87fa57_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.s6c9846_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.s6a16f4_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.s1254c2_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.s1254c2_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.s1254c2_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.saa04c4_restartSelfButton {\n /* The version row's third state. Geometry matches .s2b0e48_checkUpdateButton and\n * .s2a2b0f_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.saa04c4_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.sc76708_restartButton,\n.seeecd2_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.sc76708_restartButton:hover,\n.seeecd2_reloadButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.sc76708_restartButton:focus-visible,\n.seeecd2_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 * `.s6a16f4_installedActions` the way the restart gate is — a column of its own\n * rather than inline with the row. */\n.sbffa9e_reloadPanel {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 6px;\n}\n\n\n.s86cdae_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.s6312cc_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--shop-warn-on-tint);\n}\n\n.s5be6f2_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s92284e_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.saae32f_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.s42fb19_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s8d6465_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.s270c28_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s91ac15_notice {\n margin: 0;\n font-size: 12px;\n color: var(--shop-success);\n}\n\n.s621e14_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--shop-error);\n}\n\n.scc3b42_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s13b4b9_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--shop-error);\n}\n\n.s7916c4_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.s4c191e_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.sad7abb_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s6b9ac7_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.s56ba42_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.s56ba42_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.s7da274_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 .s75c5b0_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.s22f424_shadowedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.s9a711b_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.s324c82_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] overflowed the Settings dialog the shop\n * sat in through 0.8.7, and the page's column narrows with the window. */\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: `.s34065e_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 `.scd23e0_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.scd23e0_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.s6ba5b7_switchOn {\n border-color: var(--track-hue);\n background: color-mix(in srgb, var(--track-hue) 55%, transparent);\n}\n\n.sdea483_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.s6ba5b7_switchOn .sdea483_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.scd23e0_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 .scd23e0_switch, .sdea483_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.sded46d_switchWrap {\n display: inline-flex;\n align-items: center;\n /* Same reason as `.s324c82_outdatedActions`: `.sbffa9e_reloadPanel`'s own comment calls\n * itself \"a column of its own rather than inline with the row\", which held\n * for `.s6a16f4_installedActions` and not here until this line. */\n flex-wrap: wrap;\n gap: 8px;\n}\n";
8422
- if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"9836f849\"]")) {
8434
+ //#region \0dsh-plugin-shop-css:e3675a89
8435
+ const css = "/* Shop styles — the shop's own page with its market shelf, and the shop's two\n * buttons on dsh's Plugins page. Every variable read here is a dsh theme\n * alias token (`--dsw-alias-*`), one of the shop's own properties declared in\n * this file (`--shop-*`, `--category-hue`, `--switch-hue`, `--track-hue`), or\n * one of two dsh variables outside the alias set, each read where the shop\n * copies dsh: `--dsh-frame-top-clearance`, so the page head clears the\n * desktop frame as dsh's own pages do, and `--dsw-radius-sm`, because the\n * Plugins page buttons copy dsh's Button (0.1.5-rc.3 lacks that token, and\n * has no Plugins page to draw them on). `state-error` and\n * `state-success` do not exist, so the state-*-primary set stands in for\n * them (measured 2026-10-17 on dsh 0.1.5-rc.3, 0.1.7-rc.2 and 0.2.0-rc.2,\n * which do define `bg-layer-3` and `interactive-bg-hover`, once listed here\n * as missing). 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 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 6.14:1 on\n * the dark page itself (#151517, `bg-base`), where the self-update's\n * failure heading sits (4.69:1 on Settings' #2c2c2e, its ground through\n * 0.8.7), 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 `.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/* The shop's own page in dsh's main column (design\n * 2026-10-16-shop-sidebar-page §4.2), framed as dsh 0.2.0-rc.2 frames its\n * Plugins page — read from that page's stylesheet: the page is the scroll\n * container, its children one centred column of at most 960px, and the head\n * clears the desktop title bar. One declaration is the shop's own, `color`:\n * dsh's `.s3f14d7_page` inherits its colour from the shell around it, and the shop's\n * root is not inside dsh's panel stylesheet, so the shop's page states it. */\n.s3f14d7_page {\n box-sizing: border-box;\n height: 100%;\n overflow: auto;\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 32px;\n padding: 0 clamp(24px, 4vw, 48px) 48px;\n color: var(--dsw-alias-label-primary);\n}\n\n.s3f14d7_page > * {\n width: 100%;\n max-width: 960px;\n}\n\n.s9bc9a3_pageHead {\n box-sizing: border-box;\n padding-top: calc(28px + var(--dsh-frame-top-clearance, 0px));\n}\n\n.sd3fc57_pageTitle {\n margin: 0;\n font-size: 20px;\n font-weight: 500;\n line-height: 28px;\n}\n\n/* Secondary, as dsh's own intro: 5.80:1 on the light theme's white. */\n.s11ac5e_pageIntro {\n margin: 4px 0 0;\n font-size: 13px;\n line-height: 20px;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The two buttons on dsh's Plugins page (design 2026-10-16-shop-sidebar-page\n * §7), drawn as that page draws its own small outline buttons beside them —\n * dsh 0.2.0-rc.2's Button, sm + outline: 28px tall, 12px text on an 18px\n * line, 10px of side padding, the small radius, the neutral stroke. That\n * stroke is 0.5px in dsh and 1px here: every border in this stylesheet is,\n * and Chromium paints 0.5px as 1px at every ratio measured. */\n.sbaabcb_detailAction {\n box-sizing: border-box;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n height: 28px;\n padding: 0 10px;\n border: 1px solid var(--dsw-alias-border-l3);\n border-radius: var(--dsw-radius-sm);\n background: transparent;\n color: var(--dsw-alias-label-primary);\n font-size: 12px;\n line-height: 18px;\n cursor: pointer;\n}\n\n.sbaabcb_detailAction:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.sbaabcb_detailAction:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\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(--shop-warn-on-tint);\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: 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.s8583e3_client.sfd9d8a_spec.s5f67aa_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.s26d58e_categoryButton.s5f68c4_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 .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 /* 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.sa6825e_sortGroup {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin-left: auto;\n}\n\n.s268f9e_sortLabel {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\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(--shop-warn);\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/* The shelf's grid item is the <li> carrying a card's identity (ShopPage\n * reads it), not the card, and a grid item's min-width is auto: the 1fr track\n * could then never be narrower than the widest item's min-content, and every\n * card widens to it. Two things reach that width. Content that cannot wrap,\n * on a narrow window. And a card the browser skips (`content-visibility:\n * auto` below), which keeps the width it last rendered at: browsing a wide\n * window and narrowing it left every card 960px wide in a 662px column, its\n * right side clipped by the panel (measured on dsh 0.2.1-rc.2, 0.2.0-rc.2 and\n * 0.1.7-rc.2, 2026-10-22). Until the page took the main column the panel was\n * as wide as Settings made it, and a window resize did not move it. */\n.sf657f5_cards > li {\n min-width: 0;\n}\n\n.s65f383_card {\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 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/* One entry's icon: a 32px tile holding a 24px image (design\n * 2026-10-23-shop-plugin-icons §8.3). The border and radius are dsh's own\n * (`PackageArtwork`'s .s7ff339_cardIcon), so a card's icon reads the same here as on\n * dsh's Plugins page. No backplate in either theme (§8.4). */\n.sc2425d_entryIcon {\n box-sizing: border-box;\n flex: none;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 32px;\n height: 32px;\n border: 0.5px solid var(--dsw-alias-border-l3);\n border-radius: var(--dsw-radius-md);\n overflow: hidden;\n}\n\n.scc3f74_entryIconImage {\n width: 24px;\n height: 24px;\n object-fit: contain;\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: 10px 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.s9b5048_cardActions > .sc34531_receipt {\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 /* 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.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(--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.s5e82cb_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.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 /* 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.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(--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.s3d0044_remedy {\n display: block;\n}\n\n.s3d0044_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 `.s67d135_foldBlock` collapsed and `.s626303_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.s67d135_foldBlock {\n display: block;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n display: -webkit-box;\n overflow: hidden;\n}\n\n.s626303_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.sfbcf2c_foldToggle {\n /* The pill's own width. Under a card's red block it is an item of the card\n * body, a column, which stretches every child it is not told otherwise:\n * 934px against the Refresh pill's 59px at a 1440 window (2026-10-22). */\n align-self: flex-start;\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.sfbcf2c_foldToggle:hover { border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2)); }\n.sfbcf2c_foldToggle:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }\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 * 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.sc74941_refusal {\n display: block;\n flex-basis: 100%;\n}\n\n.sc74941_refusal > * + * { margin-top: 6px; }\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 *\n * `.s98d53f_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.s2d3c05_size,\n.s98d53f_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 `.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(--shop-warn-on-tint);\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/* 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.se7aaed_installButton,\n.sb43a49_confirmButton,\n.s6e45d1_cancelButton,\n.sb6fc74_installedLabel,\n.s906629_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.se7aaed_installButton, .sb43a49_confirmButton, .s6e45d1_cancelButton {\n cursor: pointer;\n}\n\n.se7aaed_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.se7aaed_installButton:disabled {\n cursor: wait;\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/* 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.s906629_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.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 /* 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.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/* A settled install's or uninstall's receipt: its log, what happened, and\n * what to do next, stacked. A column, so the log box spans the card as the\n * running and failed views' logs do, rather than being as wide as its longest\n * line (852, 409 and 260px on three cards of one page, 2026-10-22), and the\n * sentence never shares a line with a button it would sit misaligned beside. */\n.sc34531_receipt {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n/* The buttons under a receipt, a failure, or a reload or restart note, in one\n * row: the offer first (Reload, Restart dsh, Retry) and the way out (Dismiss)\n * beside it. They stood a line apart: Dismiss beside the reload note and\n * Reload under it, or Retry above Dismiss. */\n.sbe9357_offerActions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\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(--shop-warn-on-tint);\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(--shop-error);\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(--shop-success);\n}\n\n.seb9b08_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--shop-error);\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(--shop-error);\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: row;\n align-items: center;\n gap: 10px;\n min-width: 0;\n}\n\n/* The name and everything under it, beside the icon. */\n.s3ecfe4_outdatedText {\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] overflowed the Settings dialog the shop\n * sat in through 0.8.7, and the page's column narrows with the window. */\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";
8436
+ if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"e3675a89\"]")) {
8423
8437
  const tag = document.createElement("style");
8424
- tag.dataset.pluginCss = "9836f849";
8438
+ tag.dataset.pluginCss = "e3675a89";
8425
8439
  tag.textContent = css;
8426
8440
  document.head.appendChild(tag);
8427
8441
  }
8428
- var _dsh_plugin_shop_css_9836f849_default = {
8429
- "client": "sac9242_client",
8430
- "config": "sbb38d9_config",
8431
- "ts": "s3f448c_ts",
8432
- "spec": "sbcffb6_spec",
8433
- "panel": "sdda1b3_panel",
8434
- "searchInput": "sb7555e_searchInput",
8435
- "toolbar": "sf8bc6e_toolbar",
8436
- "page": "s17c3b8_page",
8437
- "pageHead": "s9bd177_pageHead",
8438
- "pageTitle": "s8701f9_pageTitle",
8439
- "pageIntro": "se07ab1_pageIntro",
8440
- "detailAction": "s2e85d9_detailAction",
8441
- "stateLine": "sae65aa_stateLine",
8442
- "srOnly": "scf7e39_srOnly",
8443
- "skeletonGrid": "sc6fc10_skeletonGrid",
8444
- "skeletonCard": "s26bc9c_skeletonCard",
8445
- "skeletonName": "s47e7ae_skeletonName",
8446
- "skeletonSummary": "s88d4ac_skeletonSummary",
8447
- "skeletonSummaryShort": "s335a0e_skeletonSummaryShort",
8448
- "skeletonActions": "sd4ae24_skeletonActions",
8449
- "actionButton": "se320ba_actionButton",
8450
- "toolbarRight": "sb1f0e2_toolbarRight",
8451
- "versionBlock": "s42e55a_versionBlock",
8452
- "versionText": "sea2e4c_versionText",
8453
- "updateSelfButton": "s2a2b0f_updateSelfButton",
8454
- "checkUpdateButton": "s2b0e48_checkUpdateButton",
8455
- "githubLink": "sebb833_githubLink",
8456
- "selfUpdatePanel": "sf4760e_selfUpdatePanel",
8457
- "expansionNote": "se91028_expansionNote",
8458
- "staleBadge": "s9eff21_staleBadge",
8459
- "categoryBar": "s422238_categoryBar",
8460
- "categoryButton": "s1c2c12_categoryButton",
8461
- "categoryButtonOn": "s422f17_categoryButtonOn",
8462
- "incompatibleFilter": "s34065e_incompatibleFilter",
8463
- "switch": "scd23e0_switch",
8464
- "switchOn": "s6ba5b7_switchOn",
8465
- "incompatibleFilterOn": "s8b7f04_incompatibleFilterOn",
8466
- "switchKnob": "sdea483_switchKnob",
8467
- "updatableHeading": "se80247_updatableHeading",
8468
- "catalogStatsRow": "sf4da7d_catalogStatsRow",
8469
- "sortGroup": "sbcc81f_sortGroup",
8470
- "sortLabel": "s53a618_sortLabel",
8471
- "catalogStats": "sa283c6_catalogStats",
8472
- "catalogRefreshButton": "sc2681d_catalogRefreshButton",
8473
- "catalogRefreshFailed": "s3bc86f_catalogRefreshFailed",
8474
- "emptyLine": "sa758ba_emptyLine",
8475
- "cards": "sb8570f_cards",
8476
- "card": "s411ccd_card",
8477
- "cardsSentry": "saf4e10_cardsSentry",
8478
- "showingLine": "s5432f6_showingLine",
8479
- "cardSpine": "s29077c_cardSpine",
8480
- "categoryBadge": "sfa1350_categoryBadge",
8481
- "entryHeader": "s772eab_entryHeader",
8482
- "name": "s85cdde_name",
8483
- "badges": "s75c5b0_badges",
8484
- "cardActions": "s85dccd_cardActions",
8485
- "gate": "s86cdae_gate",
8486
- "installPanel": "sda5e0f_installPanel",
8487
- "installedActions": "s6a16f4_installedActions",
8488
- "cardVersion": "sb178f4_cardVersion",
8489
- "sourceBadge": "s8907e4_sourceBadge",
8490
- "tierBadge": "sa859d0_tierBadge",
8491
- "incompatibleBadge": "s5fe867_incompatibleBadge",
8492
- "starsBadge": "s7ae585_starsBadge",
8493
- "chevron": "saf9723_chevron",
8494
- "chevronOpen": "s48b05a_chevronOpen",
8495
- "body": "se348a3_body",
8496
- "summary": "s665b9a_summary",
8497
- "summaryZh": "s4ecb2d_summaryZh",
8498
- "summaryExpanded": "s61ff4e_summaryExpanded",
8499
- "summaryZhExpanded": "s19c6c2_summaryZhExpanded",
8500
- "incompatibleDetail": "sd1bf96_incompatibleDetail",
8501
- "remedy": "s8dbfd5_remedy",
8502
- "foldBlock": "sed5ba4_foldBlock",
8503
- "foldBlockExpanded": "sde670b_foldBlockExpanded",
8504
- "foldToggle": "s37ce74_foldToggle",
8505
- "remedyLead": "s2e4bfe_remedyLead",
8506
- "allowCommand": "sceb183_allowCommand",
8507
- "refusal": "s2ca2e4_refusal",
8508
- "capabilitiesBlock": "s91ec65_capabilitiesBlock",
8509
- "capabilitiesNote": "s591a82_capabilitiesNote",
8510
- "capabilities": "s1dc988_capabilities",
8511
- "detail": "s7ddbb4_detail",
8512
- "detailRows": "sd74c84_detailRows",
8513
- "detailRow": "sff39ff_detailRow",
8514
- "cardMeta": "sd181e4_cardMeta",
8515
- "shelfDate": "s27a949_shelfDate",
8516
- "size": "sef189b_size",
8517
- "author": "s51ede0_author",
8518
- "reviewedLine": "sb76fb3_reviewedLine",
8519
- "installButton": "s7ee92c_installButton",
8520
- "confirmButton": "s9a7a2b_confirmButton",
8521
- "cancelButton": "s99e4b2_cancelButton",
8522
- "installedLabel": "s87fa57_installedLabel",
8523
- "refusedLabel": "s6c9846_refusedLabel",
8524
- "uninstallButton": "s1254c2_uninstallButton",
8525
- "restartSelfButton": "saa04c4_restartSelfButton",
8526
- "restartButton": "sc76708_restartButton",
8527
- "reloadButton": "seeecd2_reloadButton",
8528
- "reloadPanel": "sbffa9e_reloadPanel",
8529
- "gateTitle": "s6312cc_gateTitle",
8530
- "gateBody": "s5be6f2_gateBody",
8531
- "gateWarning": "s92284e_gateWarning",
8532
- "gateActions": "saae32f_gateActions",
8533
- "installing": "s42fb19_installing",
8534
- "log": "s8d6465_log",
8535
- "logLine": "s270c28_logLine",
8536
- "notice": "s91ac15_notice",
8537
- "failedHeading": "s621e14_failedHeading",
8538
- "failedDetail": "scc3b42_failedDetail",
8539
- "rejectedCode": "s13b4b9_rejectedCode",
8540
- "rejectedDetail": "s7916c4_rejectedDetail",
8541
- "outdatedSection": "s4c191e_outdatedSection",
8542
- "outdatedList": "sad7abb_outdatedList",
8543
- "outdatedRow": "s6b9ac7_outdatedRow",
8544
- "outdatedInfo": "s56ba42_outdatedInfo",
8545
- "outdatedVersions": "s7da274_outdatedVersions",
8546
- "shadowedSection": "s22f424_shadowedSection",
8547
- "shadowedDetail": "s9a711b_shadowedDetail",
8548
- "outdatedActions": "s324c82_outdatedActions",
8549
- "switchWrap": "sded46d_switchWrap"
8442
+ var _dsh_plugin_shop_css_e3675a89_default = {
8443
+ "client": "s8583e3_client",
8444
+ "config": "s942a79_config",
8445
+ "ts": "s5f67aa_ts",
8446
+ "spec": "sfd9d8a_spec",
8447
+ "panel": "s255e5a_panel",
8448
+ "searchInput": "s232ec4_searchInput",
8449
+ "toolbar": "s646509_toolbar",
8450
+ "page": "s3f14d7_page",
8451
+ "pageHead": "s9bc9a3_pageHead",
8452
+ "pageTitle": "sd3fc57_pageTitle",
8453
+ "pageIntro": "s11ac5e_pageIntro",
8454
+ "detailAction": "sbaabcb_detailAction",
8455
+ "stateLine": "s44a3ec_stateLine",
8456
+ "srOnly": "s3a0ece_srOnly",
8457
+ "skeletonGrid": "se0c3b4_skeletonGrid",
8458
+ "skeletonCard": "s520416_skeletonCard",
8459
+ "skeletonName": "s8c7d6f_skeletonName",
8460
+ "skeletonSummary": "sf5179e_skeletonSummary",
8461
+ "skeletonSummaryShort": "sbb0d25_skeletonSummaryShort",
8462
+ "skeletonActions": "s96e910_skeletonActions",
8463
+ "actionButton": "s73242a_actionButton",
8464
+ "toolbarRight": "s745e3c_toolbarRight",
8465
+ "versionBlock": "s4fe61f_versionBlock",
8466
+ "versionText": "sb9b924_versionText",
8467
+ "updateSelfButton": "sa04455_updateSelfButton",
8468
+ "checkUpdateButton": "s614a38_checkUpdateButton",
8469
+ "githubLink": "sa987f7_githubLink",
8470
+ "selfUpdatePanel": "sb81ba5_selfUpdatePanel",
8471
+ "expansionNote": "s76fb13_expansionNote",
8472
+ "staleBadge": "s3946f1_staleBadge",
8473
+ "categoryBar": "sb2c859_categoryBar",
8474
+ "categoryButton": "s26d58e_categoryButton",
8475
+ "categoryButtonOn": "s5f68c4_categoryButtonOn",
8476
+ "incompatibleFilter": "sfd887b_incompatibleFilter",
8477
+ "switch": "s11c019_switch",
8478
+ "switchOn": "s7580e8_switchOn",
8479
+ "incompatibleFilterOn": "s6e4cba_incompatibleFilterOn",
8480
+ "switchKnob": "saab2b3_switchKnob",
8481
+ "updatableHeading": "sebf017_updatableHeading",
8482
+ "catalogStatsRow": "seb0a1e_catalogStatsRow",
8483
+ "sortGroup": "sa6825e_sortGroup",
8484
+ "sortLabel": "s268f9e_sortLabel",
8485
+ "catalogStats": "sccc4d1_catalogStats",
8486
+ "catalogRefreshButton": "sff243d_catalogRefreshButton",
8487
+ "catalogRefreshFailed": "s194168_catalogRefreshFailed",
8488
+ "emptyLine": "s6de1ab_emptyLine",
8489
+ "cards": "sf657f5_cards",
8490
+ "card": "s65f383_card",
8491
+ "cardsSentry": "sd7ee01_cardsSentry",
8492
+ "showingLine": "s705cc6_showingLine",
8493
+ "cardSpine": "s4ce25d_cardSpine",
8494
+ "categoryBadge": "s9c8eaa_categoryBadge",
8495
+ "cardIcon": "s7ff339_cardIcon",
8496
+ "entryIcon": "sc2425d_entryIcon",
8497
+ "entryIconImage": "scc3f74_entryIconImage",
8498
+ "entryHeader": "sa97234_entryHeader",
8499
+ "name": "sc3f149_name",
8500
+ "badges": "s513026_badges",
8501
+ "cardActions": "s9b5048_cardActions",
8502
+ "gate": "sc4f2ac_gate",
8503
+ "installPanel": "s4d374c_installPanel",
8504
+ "installedActions": "sf53ca0_installedActions",
8505
+ "receipt": "sc34531_receipt",
8506
+ "cardVersion": "sc6ec6e_cardVersion",
8507
+ "sourceBadge": "s71f10f_sourceBadge",
8508
+ "tierBadge": "s5e82cb_tierBadge",
8509
+ "incompatibleBadge": "s4bbe02_incompatibleBadge",
8510
+ "starsBadge": "sff43b4_starsBadge",
8511
+ "chevron": "s73f7fb_chevron",
8512
+ "chevronOpen": "s61393c_chevronOpen",
8513
+ "body": "s820ebf_body",
8514
+ "summary": "sb8fbc8_summary",
8515
+ "summaryZh": "se507db_summaryZh",
8516
+ "summaryExpanded": "s10baf0_summaryExpanded",
8517
+ "summaryZhExpanded": "sb9dba9_summaryZhExpanded",
8518
+ "incompatibleDetail": "s6eca19_incompatibleDetail",
8519
+ "remedy": "s3d0044_remedy",
8520
+ "foldBlock": "s67d135_foldBlock",
8521
+ "foldBlockExpanded": "s626303_foldBlockExpanded",
8522
+ "foldToggle": "sfbcf2c_foldToggle",
8523
+ "remedyLead": "sdf203f_remedyLead",
8524
+ "allowCommand": "sc32335_allowCommand",
8525
+ "refusal": "sc74941_refusal",
8526
+ "capabilitiesBlock": "s9d2029_capabilitiesBlock",
8527
+ "capabilitiesNote": "s3bdbd5_capabilitiesNote",
8528
+ "capabilities": "s441702_capabilities",
8529
+ "detail": "sde3f0c_detail",
8530
+ "detailRows": "se98179_detailRows",
8531
+ "detailRow": "sfb4d2f_detailRow",
8532
+ "cardMeta": "s5fbaf9_cardMeta",
8533
+ "shelfDate": "s98d53f_shelfDate",
8534
+ "size": "s2d3c05_size",
8535
+ "author": "s1ff66f_author",
8536
+ "reviewedLine": "sd58c6d_reviewedLine",
8537
+ "installButton": "se7aaed_installButton",
8538
+ "confirmButton": "sb43a49_confirmButton",
8539
+ "cancelButton": "s6e45d1_cancelButton",
8540
+ "installedLabel": "sb6fc74_installedLabel",
8541
+ "refusedLabel": "s906629_refusedLabel",
8542
+ "uninstallButton": "s96967f_uninstallButton",
8543
+ "restartSelfButton": "sb00c36_restartSelfButton",
8544
+ "restartButton": "s6ac237_restartButton",
8545
+ "reloadButton": "s64a5ff_reloadButton",
8546
+ "offerActions": "sbe9357_offerActions",
8547
+ "reloadPanel": "sf79aef_reloadPanel",
8548
+ "gateTitle": "s559c38_gateTitle",
8549
+ "gateBody": "s3c0ace_gateBody",
8550
+ "gateWarning": "sc323e5_gateWarning",
8551
+ "gateActions": "s4b2373_gateActions",
8552
+ "installing": "s52b5a5_installing",
8553
+ "log": "s64ab71_log",
8554
+ "logLine": "s1531a8_logLine",
8555
+ "notice": "sf20dda_notice",
8556
+ "failedHeading": "seb9b08_failedHeading",
8557
+ "failedDetail": "sfd3021_failedDetail",
8558
+ "rejectedCode": "s50ba35_rejectedCode",
8559
+ "rejectedDetail": "se75489_rejectedDetail",
8560
+ "outdatedSection": "sa276cf_outdatedSection",
8561
+ "outdatedList": "sceae6e_outdatedList",
8562
+ "outdatedRow": "s8d2072_outdatedRow",
8563
+ "outdatedInfo": "s5c2545_outdatedInfo",
8564
+ "outdatedText": "s3ecfe4_outdatedText",
8565
+ "outdatedVersions": "s877c68_outdatedVersions",
8566
+ "shadowedSection": "s6c4649_shadowedSection",
8567
+ "shadowedDetail": "scc2a21_shadowedDetail",
8568
+ "outdatedActions": "sa1f556_outdatedActions",
8569
+ "switchWrap": "sd9f9e4_switchWrap"
8550
8570
  };
8551
8571
  //#endregion
8552
8572
  //#region src/client/ShopDetailAction.tsx
@@ -8606,7 +8626,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8606
8626
  if (name === null || !canOpen()) return null;
8607
8627
  if (own) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8608
8628
  type: "button",
8609
- className: _dsh_plugin_shop_css_9836f849_default.detailAction,
8629
+ className: _dsh_plugin_shop_css_e3675a89_default.detailAction,
8610
8630
  "data-shop-open-shop": true,
8611
8631
  onClick: openShop,
8612
8632
  children: t("openShop")
@@ -8615,7 +8635,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8615
8635
  const only = listed.keys.length === 1 ? listed.keys[0] ?? null : null;
8616
8636
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8617
8637
  type: "button",
8618
- className: _dsh_plugin_shop_css_9836f849_default.detailAction,
8638
+ className: _dsh_plugin_shop_css_e3675a89_default.detailAction,
8619
8639
  "data-shop-view-in-shop": true,
8620
8640
  onClick: () => {
8621
8641
  page.focus(name, only);
@@ -9934,13 +9954,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9934
9954
  const { rootRef, expandable, expanded, toggle } = useFoldableRedBlock();
9935
9955
  const kindName = attrName.replace(/^data-shop-/, "");
9936
9956
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9937
- className: expanded ? _dsh_plugin_shop_css_9836f849_default.foldBlockExpanded : _dsh_plugin_shop_css_9836f849_default.foldBlock,
9957
+ className: expanded ? _dsh_plugin_shop_css_e3675a89_default.foldBlockExpanded : _dsh_plugin_shop_css_e3675a89_default.foldBlock,
9938
9958
  [attrName]: true,
9939
9959
  ref: rootRef,
9940
9960
  children
9941
9961
  }), expandable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9942
9962
  type: "button",
9943
- className: _dsh_plugin_shop_css_9836f849_default.foldToggle,
9963
+ className: _dsh_plugin_shop_css_e3675a89_default.foldToggle,
9944
9964
  "data-shop-fold": kindName,
9945
9965
  "aria-expanded": expanded,
9946
9966
  onClick: toggle,
@@ -9948,6 +9968,49 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9948
9968
  })] });
9949
9969
  }
9950
9970
  //#endregion
9971
+ //#region src/client/EntryIcon.tsx
9972
+ /** The artwork an entry without a usable icon draws: dsh's own default,
9973
+ * `PluginArtworkDefault` from `dsh-client-ui-primitives` (MIT, DeepSeek — see
9974
+ * the dsh repository's LICENSE). Copied rather than imported because
9975
+ * 0.1.5-rc.3 exports nothing this build could borrow at run time.
9976
+ *
9977
+ * A constant data URL drawn through an `<img>`, not inline markup: the SVG
9978
+ * carries a gradient addressed by id, so thousands of inline copies would each
9979
+ * need one, while an image document is its own namespace and needs none
9980
+ * (design 2026-10-23-shop-plugin-icons §8.1). The id inside this string is
9981
+ * fixed for the same reason.
9982
+ */
9983
+ const DEFAULT_ICON_DATA_URL = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzYiIGhlaWdodD0iMzYiIHZpZXdCb3g9IjAgMCAzNiAzNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMjQuNjI5NCA4LjYzNjk2QzI2LjI4NjIgOC42MzcwNSAyNy42Mjk0IDkuOTgwMTYgMjcuNjI5NCAxMS42MzdWMTIuNzgyNUMyNy42MjkyIDE0LjQzOTEgMjYuMjg2MSAxNS43ODI0IDI0LjYyOTQgMTUuNzgyNUgyMy40ODM5QzIyLjY1MTkgMTUuNzgyNSAyMS41NDU0IDE2LjM0NTkgMjEuNTQ1NCAxNy4xNzc4VjE4LjE1NzNDMjEuNTQ1NCAxOC43NzU3IDIyLjIxNiAxOS4xOTY2IDIyLjgzNDUgMTkuMTk2NUgyNC42Mjk0QzI2LjI4NjEgMTkuMTk2NSAyNy42MjkyIDIwLjUzOTggMjcuNjI5NCAyMi4xOTY1VjIzLjk5MjRDMjcuNjI5MiAyNS42NDkxIDI2LjI4NjEgMjYuOTkyNCAyNC42Mjk0IDI2Ljk5MjRIMjIuODM0NUMyMS4xNzc4IDI2Ljk5MjQgMTkuODM0NyAyNS42NDkgMTkuODM0NSAyMy45OTI0VjIyLjE5NjVDMTkuODM0NSAyMS43MTQ4IDE5LjQ5NTcgMjEuMjMyNyAxOS4wMTQgMjEuMjMyN0gxNi40NzkyQzE1Ljc5NzUgMjEuMjMyNyAxNS4zMzc0IDIyLjAwNjEgMTUuMzM3NCAyMi42ODc3VjIzLjgzMzNDMTUuMzM3MyAyNS40OSAxMy45OTQyIDI2LjgzMzMgMTIuMzM3NCAyNi44MzMzSDExLjE5MTlDOS41MzUwOSAyNi44MzMzIDguMTkxOTggMjUuNDkgOC4xOTE4OSAyMy44MzMzVjIyLjY4NzdDOC4xOTE4OSAyMS4wMzA5IDkuNTM1MDQgMTkuNjg3NyAxMS4xOTE5IDE5LjY4NzdIMTIuMzM3NEMxMy4xOTY0IDE5LjY4NzcgMTQuMzk5OSAxOS4wOTU5IDE0LjM5OTkgMTguMjM2OVYxNi4wMTg1QzE0LjM5OTkgMTQuOTUxOCAxNS4yNjQ2IDE0LjA4NzIgMTYuMzMxMiAxNC4wODcySDE5LjQzNUMyMC4wNTk2IDE0LjA4NzIgMjAuNDg0IDEzLjQwNzEgMjAuNDgzOSAxMi43ODI1VjExLjYzN0MyMC40ODM5IDkuOTgwMTEgMjEuODI3IDguNjM2OTYgMjMuNDgzOSA4LjYzNjk2SDI0LjYyOTRaTTEzLjQxMTYgMTEuMjQ2OEMxMy40MTE2IDEyLjY4ODIgMTIuMjQzMSAxMy44NTY3IDEwLjgwMTggMTMuODU2N0M5LjM2MDM3IDEzLjg1NjcgOC4xOTE4OSAxMi42ODgyIDguMTkxODkgMTEuMjQ2OEM4LjE5MTg5IDkuODA1NDQgOS4zNjAzNyA4LjYzNjk2IDEwLjgwMTggOC42MzY5NkMxMi4yNDMxIDguNjM2OTYgMTMuNDExNiA5LjgwNTQ0IDEzLjQxMTYgMTEuMjQ2OFoiIGZpbGw9InVybCgjYSkiLz48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImEiIHgxPSIxNS4xNDgxIiB5MT0iOS45NDE4OCIgeDI9IjE1LjE0ODEiIHkyPSIxMy42Mzc1IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agc3RvcC1jb2xvcj0iIzU0RUNFNyIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzY1OEVGRiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjwvc3ZnPg==";
9984
+ /** One entry's icon: a 32px tile holding a 24px image (design §8.3).
9985
+ *
9986
+ * An author's icon reaches the DOM only here, as this `<img>`'s `src` — a
9987
+ * `data:` URL built from bytes the catalog carried. Never `innerHTML`, never
9988
+ * an inline `<svg>`: an image document runs no script and loads nothing
9989
+ * external, and that is the whole defence for an SVG (§8.5).
9990
+ *
9991
+ * `onError` swaps a source that will not decode for the default, as dsh's own
9992
+ * `PackageArtwork` does. The failed source, not a boolean, is remembered, so a
9993
+ * later entry reusing this component instance still tries its own icon.
9994
+ */
9995
+ function EntryIcon({ icon }) {
9996
+ const [failed, setFailed] = (0, react.useState)(void 0);
9997
+ const src = icon === void 0 || icon === failed ? DEFAULT_ICON_DATA_URL : icon;
9998
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9999
+ className: _dsh_plugin_shop_css_e3675a89_default.entryIcon,
10000
+ "aria-hidden": "true",
10001
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
10002
+ className: _dsh_plugin_shop_css_e3675a89_default.entryIconImage,
10003
+ src,
10004
+ width: 24,
10005
+ height: 24,
10006
+ alt: "",
10007
+ onError: () => {
10008
+ setFailed(icon);
10009
+ }
10010
+ })
10011
+ });
10012
+ }
10013
+ //#endregion
9951
10014
  //#region src/client/ShopTab.tsx
9952
10015
  /** The shop tab: browse the catalog, detail an entry, install on one click,
9953
10016
  * poll the install to its terminal state (§7.2).
@@ -9962,14 +10025,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9962
10025
  * those nodes, and React's next commit throws on the ones it no longer finds
9963
10026
  * (design 2026-09-26-market-borrowings §4). The tab renders no portals, so its
9964
10027
  * root is its whole surface. */
9965
- const PANEL_ROOT = `${_dsh_plugin_shop_css_9836f849_default.panel} notranslate`;
10028
+ const PANEL_ROOT = `${_dsh_plugin_shop_css_e3675a89_default.panel} notranslate`;
9966
10029
  function ChevronIcon({ open }) {
9967
10030
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
9968
10031
  viewBox: "0 0 16 16",
9969
10032
  width: "14",
9970
10033
  height: "14",
9971
10034
  "aria-hidden": "true",
9972
- className: open ? _dsh_plugin_shop_css_9836f849_default.chevronOpen : _dsh_plugin_shop_css_9836f849_default.chevron,
10035
+ className: open ? _dsh_plugin_shop_css_e3675a89_default.chevronOpen : _dsh_plugin_shop_css_e3675a89_default.chevron,
9973
10036
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
9974
10037
  fill: "currentColor",
9975
10038
  d: "M3.5 6 8 10.5 12.5 6l1 1L8 12.5 2.5 7l1-1Z"
@@ -9982,7 +10045,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9982
10045
  * install controls. An installed plugin's card carries its installed row:
9983
10046
  * current → the non-interactive installed label, behind → the update button;
9984
10047
  * uninstalled → the install button. */
9985
- const EntryCard = (0, react.memo)(function EntryCard({ entry, stars, order, shelfDay, installed, missing, harness, nameTakenBy, t, flow, uninstallFlow, restart, restartBlocked, reload, setEnabled, inInstalledView, page }) {
10048
+ const EntryCard = (0, react.memo)(function EntryCard({ entry, icon, stars, order, shelfDay, installed, missing, harness, nameTakenBy, t, flow, uninstallFlow, restart, restartBlocked, reload, setEnabled, inInstalledView, page }) {
9986
10049
  const key = entryKey(entry);
9987
10050
  const open = (0, react.useSyncExternalStore)(page.subscribeExpanded, () => page.isExpanded(key));
9988
10051
  const [, rerender] = (0, react.useReducer)((count) => count + 1, 0);
@@ -10002,102 +10065,108 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10002
10065
  repo: entry.repo,
10003
10066
  subdir: entry.subdir
10004
10067
  };
10068
+ const uninstalling = uninstallFlow.view.kind === "running";
10069
+ const removed = uninstallFlow.view.kind === "done";
10005
10070
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10006
- className: _dsh_plugin_shop_css_9836f849_default.card,
10071
+ className: _dsh_plugin_shop_css_e3675a89_default.card,
10007
10072
  "data-shop-entry": entry.name,
10008
10073
  "data-category": category,
10009
10074
  children: [
10010
10075
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10011
- className: _dsh_plugin_shop_css_9836f849_default.cardSpine,
10076
+ className: _dsh_plugin_shop_css_e3675a89_default.cardSpine,
10012
10077
  "aria-hidden": "true"
10013
10078
  }),
10014
10079
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
10015
10080
  type: "button",
10016
- className: _dsh_plugin_shop_css_9836f849_default.entryHeader,
10081
+ className: _dsh_plugin_shop_css_e3675a89_default.entryHeader,
10017
10082
  "aria-expanded": open,
10018
10083
  "aria-controls": detailId,
10019
10084
  onClick: () => {
10020
10085
  page.toggleExpanded(key);
10021
10086
  rerender();
10022
10087
  },
10023
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10024
- className: _dsh_plugin_shop_css_9836f849_default.name,
10025
- children: entry.name
10026
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10027
- className: _dsh_plugin_shop_css_9836f849_default.badges,
10028
- children: [
10029
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10030
- className: _dsh_plugin_shop_css_9836f849_default.categoryBadge,
10031
- children: t(categoryKey(entry))
10032
- }),
10033
- entry.source === "npm" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10034
- className: _dsh_plugin_shop_css_9836f849_default.sourceBadge,
10035
- "data-shop-source-npm": true,
10036
- role: "img",
10037
- "aria-label": t("npmSource"),
10038
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
10039
- viewBox: "0 0 16 16",
10040
- width: "12",
10041
- height: "12",
10042
- "aria-hidden": "true",
10043
- fill: "currentColor",
10044
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
10045
- fillRule: "evenodd",
10046
- d: "M0 0v16h16V0H0zm13 13h-2V6H8v7H3V3h10v10z"
10088
+ children: [
10089
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryIcon, { icon }),
10090
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10091
+ className: _dsh_plugin_shop_css_e3675a89_default.name,
10092
+ children: entry.name
10093
+ }),
10094
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10095
+ className: _dsh_plugin_shop_css_e3675a89_default.badges,
10096
+ children: [
10097
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10098
+ className: _dsh_plugin_shop_css_e3675a89_default.categoryBadge,
10099
+ children: t(categoryKey(entry))
10100
+ }),
10101
+ entry.source === "npm" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10102
+ className: _dsh_plugin_shop_css_e3675a89_default.sourceBadge,
10103
+ "data-shop-source-npm": true,
10104
+ role: "img",
10105
+ "aria-label": t("npmSource"),
10106
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
10107
+ viewBox: "0 0 16 16",
10108
+ width: "12",
10109
+ height: "12",
10110
+ "aria-hidden": "true",
10111
+ fill: "currentColor",
10112
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
10113
+ fillRule: "evenodd",
10114
+ d: "M0 0v16h16V0H0zm13 13h-2V6H8v7H3V3h10v10z"
10115
+ })
10047
10116
  })
10048
- })
10049
- }),
10050
- hasGithubHome(entry) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10051
- className: _dsh_plugin_shop_css_9836f849_default.sourceBadge,
10052
- "data-shop-source-github": true,
10053
- role: "img",
10054
- "aria-label": t("githubSource"),
10055
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
10056
- viewBox: "0 0 16 16",
10057
- width: "12",
10058
- height: "12",
10059
- "aria-hidden": "true",
10060
- fill: "currentColor",
10061
- 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" })
10062
- })
10063
- }),
10064
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10065
- className: _dsh_plugin_shop_css_9836f849_default.cardVersion,
10066
- "data-card-version": true,
10067
- role: "img",
10068
- "aria-label": versionName,
10069
- title: versionName,
10070
- children: version.text
10071
- }),
10072
- entry.tier !== "community" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10073
- className: _dsh_plugin_shop_css_9836f849_default.tierBadge,
10074
- "data-tier": entry.tier,
10075
- children: t(tierKey(entry.tier))
10076
- }),
10077
- entry.archived === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10078
- className: _dsh_plugin_shop_css_9836f849_default.tierBadge,
10079
- "data-tier": "archived",
10080
- children: t("archivedBadge")
10081
- }),
10082
- stars !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10083
- className: _dsh_plugin_shop_css_9836f849_default.starsBadge,
10084
- role: "img",
10085
- "aria-label": t("stars", { count: stars }),
10086
- children: ["★ ", formatStars(stars)]
10087
- }),
10088
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronIcon, { open })
10089
- ]
10090
- })]
10117
+ }),
10118
+ hasGithubHome(entry) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10119
+ className: _dsh_plugin_shop_css_e3675a89_default.sourceBadge,
10120
+ "data-shop-source-github": true,
10121
+ role: "img",
10122
+ "aria-label": t("githubSource"),
10123
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
10124
+ viewBox: "0 0 16 16",
10125
+ width: "12",
10126
+ height: "12",
10127
+ "aria-hidden": "true",
10128
+ fill: "currentColor",
10129
+ 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" })
10130
+ })
10131
+ }),
10132
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10133
+ className: _dsh_plugin_shop_css_e3675a89_default.cardVersion,
10134
+ "data-card-version": true,
10135
+ role: "img",
10136
+ "aria-label": versionName,
10137
+ title: versionName,
10138
+ children: version.text
10139
+ }),
10140
+ entry.tier !== "community" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10141
+ className: _dsh_plugin_shop_css_e3675a89_default.tierBadge,
10142
+ "data-tier": entry.tier,
10143
+ children: t(tierKey(entry.tier))
10144
+ }),
10145
+ entry.archived === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10146
+ className: _dsh_plugin_shop_css_e3675a89_default.tierBadge,
10147
+ "data-tier": "archived",
10148
+ children: t("archivedBadge")
10149
+ }),
10150
+ stars !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10151
+ className: _dsh_plugin_shop_css_e3675a89_default.starsBadge,
10152
+ role: "img",
10153
+ "aria-label": t("stars", { count: stars }),
10154
+ children: ["★ ", formatStars(stars)]
10155
+ }),
10156
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronIcon, { open })
10157
+ ]
10158
+ })
10159
+ ]
10091
10160
  }),
10092
10161
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10093
- className: _dsh_plugin_shop_css_9836f849_default.body,
10162
+ className: _dsh_plugin_shop_css_e3675a89_default.body,
10094
10163
  children: [
10095
10164
  summary !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10096
- className: open ? `${_dsh_plugin_shop_css_9836f849_default.summary} ${_dsh_plugin_shop_css_9836f849_default.summaryExpanded}` : _dsh_plugin_shop_css_9836f849_default.summary,
10165
+ className: open ? `${_dsh_plugin_shop_css_e3675a89_default.summary} ${_dsh_plugin_shop_css_e3675a89_default.summaryExpanded}` : _dsh_plugin_shop_css_e3675a89_default.summary,
10097
10166
  children: summary.en
10098
10167
  }),
10099
10168
  summary?.zh !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10100
- className: open ? `${_dsh_plugin_shop_css_9836f849_default.summaryZh} ${_dsh_plugin_shop_css_9836f849_default.summaryZhExpanded}` : _dsh_plugin_shop_css_9836f849_default.summaryZh,
10169
+ className: open ? `${_dsh_plugin_shop_css_e3675a89_default.summaryZh} ${_dsh_plugin_shop_css_e3675a89_default.summaryZhExpanded}` : _dsh_plugin_shop_css_e3675a89_default.summaryZh,
10101
10170
  children: summary.zh
10102
10171
  }),
10103
10172
  blockers.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FoldableRedBlock, {
@@ -10105,28 +10174,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10105
10174
  attrName: "data-shop-remedy",
10106
10175
  children: blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
10107
10176
  blocker,
10108
- className: _dsh_plugin_shop_css_9836f849_default.incompatibleDetail,
10177
+ className: _dsh_plugin_shop_css_e3675a89_default.incompatibleDetail,
10109
10178
  surface: "detail"
10110
10179
  }, blocker.kind))
10111
10180
  }),
10112
10181
  open && entry.catalog !== void 0 && entry.catalog.capabilities.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10113
- className: _dsh_plugin_shop_css_9836f849_default.capabilitiesBlock,
10182
+ className: _dsh_plugin_shop_css_e3675a89_default.capabilitiesBlock,
10114
10183
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10115
- className: _dsh_plugin_shop_css_9836f849_default.capabilitiesNote,
10184
+ className: _dsh_plugin_shop_css_e3675a89_default.capabilitiesNote,
10116
10185
  children: t("capabilitiesNote")
10117
10186
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10118
- className: _dsh_plugin_shop_css_9836f849_default.capabilities,
10187
+ className: _dsh_plugin_shop_css_e3675a89_default.capabilities,
10119
10188
  children: entry.catalog.capabilities.map((capability) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: capability }, capability))
10120
10189
  })]
10121
10190
  }),
10122
10191
  open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
10123
10192
  id: detailId,
10124
- className: _dsh_plugin_shop_css_9836f849_default.detail,
10193
+ className: _dsh_plugin_shop_css_e3675a89_default.detail,
10125
10194
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
10126
- className: _dsh_plugin_shop_css_9836f849_default.detailRows,
10195
+ className: _dsh_plugin_shop_css_e3675a89_default.detailRows,
10127
10196
  children: [
10128
10197
  npmUrl !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10129
- className: _dsh_plugin_shop_css_9836f849_default.detailRow,
10198
+ className: _dsh_plugin_shop_css_e3675a89_default.detailRow,
10130
10199
  "data-shop-npm": true,
10131
10200
  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", {
10132
10201
  href: npmUrl,
@@ -10136,7 +10205,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10136
10205
  }) })]
10137
10206
  }),
10138
10207
  entry.repository !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10139
- className: _dsh_plugin_shop_css_9836f849_default.detailRow,
10208
+ className: _dsh_plugin_shop_css_e3675a89_default.detailRow,
10140
10209
  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", {
10141
10210
  href: entry.repository,
10142
10211
  target: "_blank",
@@ -10145,12 +10214,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10145
10214
  }) : entry.repository })]
10146
10215
  }),
10147
10216
  entry.license !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10148
- className: _dsh_plugin_shop_css_9836f849_default.detailRow,
10217
+ className: _dsh_plugin_shop_css_e3675a89_default.detailRow,
10149
10218
  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 })]
10150
10219
  })
10151
10220
  ]
10152
10221
  }), entry.tier === "verified-stale" && entry.review !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10153
- className: _dsh_plugin_shop_css_9836f849_default.reviewedLine,
10222
+ className: _dsh_plugin_shop_css_e3675a89_default.reviewedLine,
10154
10223
  children: entry.review.reviewedVersion !== void 0 ? t("reviewedVersionLine", {
10155
10224
  reviewed: versionLabel("npm", entry.review.reviewedVersion).text,
10156
10225
  current: version.text
@@ -10163,9 +10232,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10163
10232
  ]
10164
10233
  }),
10165
10234
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10166
- className: _dsh_plugin_shop_css_9836f849_default.cardActions,
10235
+ className: _dsh_plugin_shop_css_e3675a89_default.cardActions,
10167
10236
  "data-shop-actions": true,
10168
- children: [installed === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [!inInstalledView && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
10237
+ children: [installed === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [uninstallFlow.view.kind !== "idle" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
10238
+ name: entry.name,
10239
+ t,
10240
+ restart,
10241
+ restartBlocked,
10242
+ reload,
10243
+ flow: uninstallFlow
10244
+ }, "uninstall"), !inInstalledView && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
10169
10245
  target: installTarget,
10170
10246
  tier: entry.tier,
10171
10247
  blockers,
@@ -10175,18 +10251,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10175
10251
  restart,
10176
10252
  restartBlocked,
10177
10253
  reload
10178
- }), uninstallFlow.view.kind !== "idle" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
10179
- name: entry.name,
10180
- t,
10181
- restart,
10182
- restartBlocked,
10183
- reload,
10184
- flow: uninstallFlow
10185
- })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
10186
- installed.outdated || flow.view.kind !== "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
10254
+ }, "install")] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
10255
+ removed ? null : installed.outdated || flow.view.kind !== "idle" && !(uninstalling && flow.view.kind !== "running") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
10187
10256
  target: installTarget,
10188
10257
  tier: entry.tier,
10189
10258
  variant: installed.outdated ? "update" : "install",
10259
+ locked: uninstalling,
10190
10260
  blockers,
10191
10261
  blockersStated: true,
10192
10262
  flow,
@@ -10194,15 +10264,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10194
10264
  restart,
10195
10265
  restartBlocked,
10196
10266
  reload
10197
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10198
- className: _dsh_plugin_shop_css_9836f849_default.installedLabel,
10267
+ }, "install") : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10268
+ className: _dsh_plugin_shop_css_e3675a89_default.installedLabel,
10199
10269
  "data-shop-installed": true,
10200
10270
  children: t("installed")
10201
10271
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerBadge, {
10202
10272
  blockers,
10203
10273
  t
10204
- })] }),
10205
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EnabledSwitch, {
10274
+ })] }, "installed"),
10275
+ !removed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EnabledSwitch, {
10206
10276
  row: installed,
10207
10277
  surface: "card",
10208
10278
  page,
@@ -10210,8 +10280,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10210
10280
  setEnabled,
10211
10281
  restart,
10212
10282
  restartBlocked,
10213
- reload
10214
- }),
10283
+ reload,
10284
+ locked: uninstalling
10285
+ }, "switch"),
10215
10286
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
10216
10287
  name: entry.name,
10217
10288
  t,
@@ -10219,17 +10290,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10219
10290
  restartBlocked,
10220
10291
  reload,
10221
10292
  flow: uninstallFlow
10222
- })
10293
+ }, "uninstall")
10223
10294
  ] }), (size !== void 0 || author !== null || shelfDay !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10224
- className: _dsh_plugin_shop_css_9836f849_default.cardMeta,
10295
+ className: _dsh_plugin_shop_css_e3675a89_default.cardMeta,
10225
10296
  children: [
10226
10297
  shelfDay !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10227
- className: _dsh_plugin_shop_css_9836f849_default.shelfDate,
10298
+ className: _dsh_plugin_shop_css_e3675a89_default.shelfDate,
10228
10299
  "data-shop-shelf-date": true,
10229
10300
  children: t(order === "added" ? "listedOn" : "updatedOn", { date: shelfDay })
10230
10301
  }),
10231
10302
  size !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10232
- className: _dsh_plugin_shop_css_9836f849_default.size,
10303
+ className: _dsh_plugin_shop_css_e3675a89_default.size,
10233
10304
  "data-shop-size": true,
10234
10305
  role: "img",
10235
10306
  "aria-label": t("sizeLabel", { size }),
@@ -10237,7 +10308,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10237
10308
  children: size
10238
10309
  }),
10239
10310
  author !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10240
- className: _dsh_plugin_shop_css_9836f849_default.author,
10311
+ className: _dsh_plugin_shop_css_e3675a89_default.author,
10241
10312
  "data-shop-author": true,
10242
10313
  children: t("authorLine", { author })
10243
10314
  })
@@ -10287,13 +10358,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10287
10358
  ...attribute,
10288
10359
  children: blocker.text
10289
10360
  }), blocker.remedy !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10290
- className: _dsh_plugin_shop_css_9836f849_default.remedy,
10361
+ className: _dsh_plugin_shop_css_e3675a89_default.remedy,
10291
10362
  "data-shop-remedy": blocker.kind,
10292
10363
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10293
- className: _dsh_plugin_shop_css_9836f849_default.remedyLead,
10364
+ className: _dsh_plugin_shop_css_e3675a89_default.remedyLead,
10294
10365
  children: blocker.remedy.lead
10295
10366
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
10296
- className: _dsh_plugin_shop_css_9836f849_default.allowCommand,
10367
+ className: _dsh_plugin_shop_css_e3675a89_default.allowCommand,
10297
10368
  "data-shop-allow-command": true,
10298
10369
  children: blocker.remedy.command
10299
10370
  })]
@@ -10331,7 +10402,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10331
10402
  if (lead === void 0 || word === null) return null;
10332
10403
  const detail = blockers.map((blocker) => blocker.text).join("\n");
10333
10404
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10334
- className: _dsh_plugin_shop_css_9836f849_default.incompatibleBadge,
10405
+ className: _dsh_plugin_shop_css_e3675a89_default.incompatibleBadge,
10335
10406
  "data-shop-blocker": lead.kind,
10336
10407
  role: "img",
10337
10408
  "aria-label": detail,
@@ -10354,8 +10425,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10354
10425
  * keeps a second press from becoming a second RPC. The page states the
10355
10426
  * dialog's facts nowhere since 2026-10-15 (the shop design's §7 amendment of
10356
10427
  * that date). */
10357
- function InstallPanel({ target, tier, blockers, blockersStated = false, variant = "install", flow, t, restart, restartBlocked, reload }) {
10358
- const { view, start, starting, reset } = flow;
10428
+ function InstallPanel({ target, tier, blockers, blockersStated = false, variant = "install", locked = false, flow, t, restart, restartBlocked, reload }) {
10429
+ const { view: flowView, start, starting, reset } = flow;
10430
+ const view = locked && flowView.kind !== "running" ? { kind: "idle" } : flowView;
10359
10431
  const request = {
10360
10432
  ...target,
10361
10433
  acknowledged: tier === "verified" ? void 0 : true
@@ -10363,37 +10435,39 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10363
10435
  const refused = refusesInstall(blockers);
10364
10436
  const word = blockerBadgeKey(blockers);
10365
10437
  if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10366
- className: _dsh_plugin_shop_css_9836f849_default.installPanel,
10438
+ className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
10367
10439
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10368
- className: _dsh_plugin_shop_css_9836f849_default.installing,
10440
+ className: _dsh_plugin_shop_css_e3675a89_default.installing,
10369
10441
  children: t(installPhaseKey(view.phase))
10370
10442
  }), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10371
- className: _dsh_plugin_shop_css_9836f849_default.log,
10443
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
10444
+ "data-shop-log": true,
10372
10445
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10373
- className: _dsh_plugin_shop_css_9836f849_default.logLine,
10446
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
10374
10447
  "data-shop-log-line": true,
10375
10448
  children: line
10376
10449
  }, index))
10377
10450
  })]
10378
10451
  });
10379
10452
  if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10380
- className: _dsh_plugin_shop_css_9836f849_default.installedActions,
10453
+ className: _dsh_plugin_shop_css_e3675a89_default.receipt,
10381
10454
  children: [
10382
10455
  view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10383
- className: _dsh_plugin_shop_css_9836f849_default.log,
10456
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
10457
+ "data-shop-log": true,
10384
10458
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10385
- className: _dsh_plugin_shop_css_9836f849_default.logLine,
10459
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
10386
10460
  "data-shop-log-line": true,
10387
10461
  children: line
10388
10462
  }, index))
10389
10463
  }),
10390
10464
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10391
- className: _dsh_plugin_shop_css_9836f849_default.notice,
10465
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
10392
10466
  "data-shop-restart-notice": true,
10393
10467
  children: t(activationNoticeKey(view.activation, view.restartReason))
10394
10468
  }),
10395
10469
  view.detail !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10396
- className: _dsh_plugin_shop_css_9836f849_default.notice,
10470
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
10397
10471
  "data-shop-done-note": true,
10398
10472
  children: view.detail
10399
10473
  }),
@@ -10403,68 +10477,73 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10403
10477
  restart,
10404
10478
  restartBlocked,
10405
10479
  reload,
10406
- where: "install"
10407
- }),
10408
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10409
- type: "button",
10410
- className: _dsh_plugin_shop_css_9836f849_default.cancelButton,
10411
- "data-shop-install-dismiss": true,
10412
- onClick: reset,
10413
- children: t("dismiss")
10480
+ where: "install",
10481
+ trailing: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10482
+ type: "button",
10483
+ className: _dsh_plugin_shop_css_e3675a89_default.cancelButton,
10484
+ "data-shop-install-dismiss": true,
10485
+ onClick: reset,
10486
+ children: t("dismiss")
10487
+ })
10414
10488
  })
10415
10489
  ]
10416
10490
  });
10417
10491
  if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10418
- className: _dsh_plugin_shop_css_9836f849_default.installPanel,
10492
+ className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
10419
10493
  children: [
10420
10494
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10421
- className: _dsh_plugin_shop_css_9836f849_default.failedHeading,
10495
+ className: _dsh_plugin_shop_css_e3675a89_default.failedHeading,
10422
10496
  children: t("installFailed")
10423
10497
  }),
10424
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10425
- className: _dsh_plugin_shop_css_9836f849_default.log,
10498
+ view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10499
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
10500
+ "data-shop-log": true,
10426
10501
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10427
- className: _dsh_plugin_shop_css_9836f849_default.logLine,
10502
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
10428
10503
  "data-shop-log-line": true,
10429
10504
  children: line
10430
10505
  }, index))
10431
10506
  }),
10432
10507
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10433
- className: _dsh_plugin_shop_css_9836f849_default.failedDetail,
10508
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
10434
10509
  children: view.detail === "" ? t("installTransportFailed") : view.detail
10435
10510
  }),
10436
- refused && word !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10437
- type: "button",
10438
- className: _dsh_plugin_shop_css_9836f849_default.cancelButton,
10439
- "data-shop-install-dismiss": true,
10440
- onClick: reset,
10441
- children: t("dismiss")
10442
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10443
- type: "button",
10444
- className: _dsh_plugin_shop_css_9836f849_default.installButton,
10445
- "data-shop-install-retry": true,
10446
- disabled: starting,
10447
- onClick: () => {
10448
- start(request);
10449
- },
10450
- children: t("retry")
10511
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10512
+ className: _dsh_plugin_shop_css_e3675a89_default.offerActions,
10513
+ "data-shop-offer": true,
10514
+ children: refused && word !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10515
+ type: "button",
10516
+ className: _dsh_plugin_shop_css_e3675a89_default.cancelButton,
10517
+ "data-shop-install-dismiss": true,
10518
+ onClick: reset,
10519
+ children: t("dismiss")
10520
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10521
+ type: "button",
10522
+ className: _dsh_plugin_shop_css_e3675a89_default.installButton,
10523
+ "data-shop-install-retry": true,
10524
+ disabled: starting,
10525
+ onClick: () => {
10526
+ start(request);
10527
+ },
10528
+ children: t("retry")
10529
+ })
10451
10530
  })
10452
10531
  ]
10453
10532
  });
10454
10533
  if (view.kind === "rejected") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10455
- className: _dsh_plugin_shop_css_9836f849_default.installPanel,
10534
+ className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
10456
10535
  children: [
10457
10536
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10458
- className: _dsh_plugin_shop_css_9836f849_default.rejectedCode,
10537
+ className: _dsh_plugin_shop_css_e3675a89_default.rejectedCode,
10459
10538
  children: t(rejectionCodeKey(view.code))
10460
10539
  }),
10461
10540
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10462
- className: _dsh_plugin_shop_css_9836f849_default.rejectedDetail,
10541
+ className: _dsh_plugin_shop_css_e3675a89_default.rejectedDetail,
10463
10542
  children: view.detail
10464
10543
  }),
10465
10544
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10466
10545
  type: "button",
10467
- className: _dsh_plugin_shop_css_9836f849_default.cancelButton,
10546
+ className: _dsh_plugin_shop_css_e3675a89_default.cancelButton,
10468
10547
  "data-shop-install-dismiss": true,
10469
10548
  onClick: reset,
10470
10549
  children: t("dismiss")
@@ -10474,7 +10553,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10474
10553
  const update = variant === "update";
10475
10554
  const detail = blockers.map((blocker) => blocker.text).join("\n");
10476
10555
  if (refused && word !== null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10477
- className: _dsh_plugin_shop_css_9836f849_default.refusedLabel,
10556
+ className: _dsh_plugin_shop_css_e3675a89_default.refusedLabel,
10478
10557
  "data-shop-refused": true,
10479
10558
  "data-shop-blocker-kind": blockers[0]?.kind,
10480
10559
  role: "img",
@@ -10486,15 +10565,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10486
10565
  attrName: "data-shop-refusal",
10487
10566
  children: blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
10488
10567
  blocker,
10489
- className: _dsh_plugin_shop_css_9836f849_default.gateWarning,
10568
+ className: _dsh_plugin_shop_css_e3675a89_default.gateWarning,
10490
10569
  surface: "warning"
10491
10570
  }, blocker.kind))
10492
10571
  })] });
10493
10572
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
10494
10573
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10495
10574
  type: "button",
10496
- className: _dsh_plugin_shop_css_9836f849_default.installButton,
10497
- disabled: starting,
10575
+ className: _dsh_plugin_shop_css_e3675a89_default.installButton,
10576
+ disabled: starting || locked,
10498
10577
  ...update ? { "data-shop-update": true } : { "data-shop-install": true },
10499
10578
  onClick: () => {
10500
10579
  start(request);
@@ -10510,7 +10589,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10510
10589
  attrName: "data-shop-blocker-lines",
10511
10590
  children: blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
10512
10591
  blocker,
10513
- className: _dsh_plugin_shop_css_9836f849_default.gateWarning,
10592
+ className: _dsh_plugin_shop_css_e3675a89_default.gateWarning,
10514
10593
  surface: "warning"
10515
10594
  }, blocker.kind))
10516
10595
  })
@@ -10525,32 +10604,34 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10525
10604
  function UninstallPanel({ name, t, restart, restartBlocked, reload, flow }) {
10526
10605
  const { view, start, reset } = flow;
10527
10606
  if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10528
- className: _dsh_plugin_shop_css_9836f849_default.installPanel,
10607
+ className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
10529
10608
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10530
- className: _dsh_plugin_shop_css_9836f849_default.installing,
10609
+ className: _dsh_plugin_shop_css_e3675a89_default.installing,
10531
10610
  children: t("uninstalling")
10532
10611
  }), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10533
- className: _dsh_plugin_shop_css_9836f849_default.log,
10612
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
10613
+ "data-shop-log": true,
10534
10614
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10535
- className: _dsh_plugin_shop_css_9836f849_default.logLine,
10615
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
10536
10616
  "data-shop-log-line": true,
10537
10617
  children: line
10538
10618
  }, index))
10539
10619
  })]
10540
10620
  });
10541
10621
  if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10542
- className: _dsh_plugin_shop_css_9836f849_default.installedActions,
10622
+ className: _dsh_plugin_shop_css_e3675a89_default.receipt,
10543
10623
  children: [
10544
10624
  view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10545
- className: _dsh_plugin_shop_css_9836f849_default.log,
10625
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
10626
+ "data-shop-log": true,
10546
10627
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10547
- className: _dsh_plugin_shop_css_9836f849_default.logLine,
10628
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
10548
10629
  "data-shop-log-line": true,
10549
10630
  children: line
10550
10631
  }, index))
10551
10632
  }),
10552
10633
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10553
- className: _dsh_plugin_shop_css_9836f849_default.notice,
10634
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
10554
10635
  "data-shop-uninstall-done": true,
10555
10636
  children: t(uninstallActivationNoticeKey(view.activation))
10556
10637
  }),
@@ -10560,56 +10641,60 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10560
10641
  restart,
10561
10642
  restartBlocked,
10562
10643
  reload,
10563
- where: "uninstall"
10564
- }),
10565
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10566
- type: "button",
10567
- className: _dsh_plugin_shop_css_9836f849_default.cancelButton,
10568
- "data-shop-uninstall-dismiss": true,
10569
- onClick: reset,
10570
- children: t("dismiss")
10644
+ where: "uninstall",
10645
+ trailing: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10646
+ type: "button",
10647
+ className: _dsh_plugin_shop_css_e3675a89_default.cancelButton,
10648
+ "data-shop-uninstall-dismiss": true,
10649
+ onClick: reset,
10650
+ children: t("dismiss")
10651
+ })
10571
10652
  })
10572
10653
  ]
10573
10654
  });
10574
10655
  if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10575
- className: _dsh_plugin_shop_css_9836f849_default.installPanel,
10656
+ className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
10576
10657
  children: [
10577
10658
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10578
- className: _dsh_plugin_shop_css_9836f849_default.failedHeading,
10659
+ className: _dsh_plugin_shop_css_e3675a89_default.failedHeading,
10579
10660
  children: t("uninstallFailed")
10580
10661
  }),
10581
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10582
- className: _dsh_plugin_shop_css_9836f849_default.log,
10662
+ view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10663
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
10664
+ "data-shop-log": true,
10583
10665
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10584
- className: _dsh_plugin_shop_css_9836f849_default.logLine,
10666
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
10585
10667
  "data-shop-log-line": true,
10586
10668
  children: line
10587
10669
  }, index))
10588
10670
  }),
10589
10671
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10590
- className: _dsh_plugin_shop_css_9836f849_default.failedDetail,
10672
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
10591
10673
  children: view.detail === "" ? t("uninstallTransportFailed") : view.detail
10592
10674
  }),
10593
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10594
- type: "button",
10595
- className: _dsh_plugin_shop_css_9836f849_default.uninstallButton,
10596
- "data-shop-uninstall-retry": true,
10597
- onClick: () => void start({ name }),
10598
- children: t("retry")
10599
- }),
10600
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10601
- type: "button",
10602
- className: _dsh_plugin_shop_css_9836f849_default.cancelButton,
10603
- "data-shop-uninstall-dismiss": true,
10604
- onClick: reset,
10605
- children: t("dismiss")
10675
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10676
+ className: _dsh_plugin_shop_css_e3675a89_default.offerActions,
10677
+ "data-shop-offer": true,
10678
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10679
+ type: "button",
10680
+ className: _dsh_plugin_shop_css_e3675a89_default.uninstallButton,
10681
+ "data-shop-uninstall-retry": true,
10682
+ onClick: () => void start({ name }),
10683
+ children: t("retry")
10684
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10685
+ type: "button",
10686
+ className: _dsh_plugin_shop_css_e3675a89_default.cancelButton,
10687
+ "data-shop-uninstall-dismiss": true,
10688
+ onClick: reset,
10689
+ children: t("dismiss")
10690
+ })]
10606
10691
  })
10607
10692
  ]
10608
10693
  });
10609
10694
  if (view.kind !== "idle") return null;
10610
10695
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10611
10696
  type: "button",
10612
- className: _dsh_plugin_shop_css_9836f849_default.uninstallButton,
10697
+ className: _dsh_plugin_shop_css_e3675a89_default.uninstallButton,
10613
10698
  "data-shop-uninstall": true,
10614
10699
  onClick: () => void start({ name }),
10615
10700
  children: t("uninstall")
@@ -10630,7 +10715,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10630
10715
  * period, refreshing the page once the NEW server keeps answering. A refused
10631
10716
  * restart renders the host's published detail above the offer, which stays;
10632
10717
  * a server that never comes back names the manual command. */
10633
- function RestartPanel({ t, restart, reload, gate }) {
10718
+ function RestartPanel({ t, restart, reload, gate, trailing }) {
10634
10719
  const [ownGateOpen, setOwnGateOpen] = (0, react.useState)(false);
10635
10720
  const gateOpen = gate === void 0 ? ownGateOpen : gate.open;
10636
10721
  const setGateOpen = (open) => {
@@ -10649,58 +10734,67 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10649
10734
  if (outcome.kind === "refused") setRefusal(outcome.detail);
10650
10735
  else if (outcome.kind === "unreachable") setRefusal(t("restartTransportFailed"));
10651
10736
  };
10652
- if (monitor?.kind === "requesting" || monitor?.kind === "restarting") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10653
- className: _dsh_plugin_shop_css_9836f849_default.notice,
10737
+ const tail = trailing === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10738
+ className: _dsh_plugin_shop_css_e3675a89_default.offerActions,
10739
+ "data-shop-offer": true,
10740
+ children: trailing
10741
+ });
10742
+ if (monitor?.kind === "requesting" || monitor?.kind === "restarting") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10743
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
10654
10744
  "data-shop-restarting": true,
10655
10745
  children: t("restarting")
10656
- });
10657
- if (monitor?.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10658
- className: _dsh_plugin_shop_css_9836f849_default.failedDetail,
10746
+ }), tail] });
10747
+ if (monitor?.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10748
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
10659
10749
  "data-shop-restart-error": true,
10660
10750
  children: monitor.logFile === void 0 ? t("restartFailedNotice") : t("restartFailedLogNotice", { log: monitor.logFile })
10661
- });
10662
- if (monitor?.kind === "not-restarted") return null;
10663
- if (gateOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10664
- className: _dsh_plugin_shop_css_9836f849_default.gate,
10751
+ }), tail] });
10752
+ if (monitor?.kind === "not-restarted") return tail;
10753
+ if (gateOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10754
+ className: _dsh_plugin_shop_css_e3675a89_default.gate,
10665
10755
  "data-shop-restart-gate": true,
10666
10756
  children: [
10667
10757
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10668
- className: _dsh_plugin_shop_css_9836f849_default.gateTitle,
10758
+ className: _dsh_plugin_shop_css_e3675a89_default.gateTitle,
10669
10759
  children: t("restartTitle")
10670
10760
  }),
10671
10761
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10672
- className: _dsh_plugin_shop_css_9836f849_default.gateBody,
10762
+ className: _dsh_plugin_shop_css_e3675a89_default.gateBody,
10673
10763
  children: t("restartBody")
10674
10764
  }),
10675
10765
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10676
- className: _dsh_plugin_shop_css_9836f849_default.gateActions,
10766
+ className: _dsh_plugin_shop_css_e3675a89_default.gateActions,
10677
10767
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10678
10768
  type: "button",
10679
- className: _dsh_plugin_shop_css_9836f849_default.confirmButton,
10769
+ className: _dsh_plugin_shop_css_e3675a89_default.confirmButton,
10680
10770
  "data-shop-restart-confirm": true,
10681
10771
  onClick: () => void onConfirm(),
10682
10772
  children: t("restartConfirm")
10683
10773
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10684
10774
  type: "button",
10685
- className: _dsh_plugin_shop_css_9836f849_default.cancelButton,
10775
+ className: _dsh_plugin_shop_css_e3675a89_default.cancelButton,
10686
10776
  onClick: () => setGateOpen(false),
10687
10777
  children: t("cancel")
10688
10778
  })]
10689
10779
  })
10690
10780
  ]
10691
- });
10781
+ }), tail] });
10692
10782
  const refused = refusal === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10693
- className: _dsh_plugin_shop_css_9836f849_default.failedDetail,
10783
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
10694
10784
  "data-shop-restart-error": true,
10695
10785
  children: refusal
10696
10786
  });
10697
- if (gate !== void 0) return refused;
10698
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [refused, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10699
- type: "button",
10700
- className: _dsh_plugin_shop_css_9836f849_default.restartButton,
10701
- "data-shop-restart": true,
10702
- onClick: () => setGateOpen(true),
10703
- children: t("restart")
10787
+ if (gate !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [refused, tail] });
10788
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [refused, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10789
+ className: _dsh_plugin_shop_css_e3675a89_default.offerActions,
10790
+ "data-shop-offer": true,
10791
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10792
+ type: "button",
10793
+ className: _dsh_plugin_shop_css_e3675a89_default.restartButton,
10794
+ "data-shop-restart": true,
10795
+ onClick: () => setGateOpen(true),
10796
+ children: t("restart")
10797
+ }), trailing]
10704
10798
  })] });
10705
10799
  }
10706
10800
  /**
@@ -10715,13 +10809,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10715
10809
  */
10716
10810
  function NotRestartedNotice({ t, pid, logFile }) {
10717
10811
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10718
- className: _dsh_plugin_shop_css_9836f849_default.selfUpdatePanel,
10812
+ className: _dsh_plugin_shop_css_e3675a89_default.selfUpdatePanel,
10719
10813
  "data-shop-not-restarted": true,
10720
10814
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10721
- className: _dsh_plugin_shop_css_9836f849_default.failedDetail,
10815
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
10722
10816
  children: pid === void 0 ? t("notRestartedNotice") : t("notRestartedPidNotice", { pid: String(pid) })
10723
10817
  }), logFile !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10724
- className: _dsh_plugin_shop_css_9836f849_default.failedDetail,
10818
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
10725
10819
  children: t("notRestartedLogNotice", { log: logFile })
10726
10820
  })]
10727
10821
  });
@@ -10739,39 +10833,51 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10739
10833
  * the `restartBlocked` gate changes, with nothing to make the second one
10740
10834
  * fail if it were missed.
10741
10835
  */
10742
- function ActivationOffer({ activation, t, restart, restartBlocked, reload, where }) {
10836
+ function ActivationOffer({ activation, t, restart, restartBlocked, reload, where, trailing }) {
10743
10837
  if (activation === "reload") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReloadPanel, {
10744
10838
  t,
10745
10839
  reload,
10746
- where
10840
+ where,
10841
+ trailing
10842
+ });
10843
+ const tail = trailing === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10844
+ className: _dsh_plugin_shop_css_e3675a89_default.offerActions,
10845
+ "data-shop-offer": true,
10846
+ children: trailing
10747
10847
  });
10748
- if (activation !== "restart") return null;
10749
- return restartBlocked === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RestartPanel, {
10848
+ if (activation !== "restart") return tail;
10849
+ if (restartBlocked === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RestartPanel, {
10750
10850
  t,
10751
10851
  restart,
10752
- reload
10753
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10754
- className: _dsh_plugin_shop_css_9836f849_default.notice,
10852
+ reload,
10853
+ trailing
10854
+ });
10855
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10856
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
10755
10857
  "data-shop-restart-disabled": true,
10756
10858
  children: t(restartBlockedNoticeKey(restartBlocked))
10757
- });
10859
+ }), tail] });
10758
10860
  }
10759
10861
  /** The §4 reload offer: the server already holds the new state and this tab
10760
10862
  * does not. Never automatic — a reload discards whatever the reader had in
10761
10863
  * flight (a conversation, a form, an upload), and the shop knows a reload
10762
10864
  * would help without knowing that now is a good time. */
10763
- function ReloadPanel({ t, reload, where }) {
10865
+ function ReloadPanel({ t, reload, where, trailing }) {
10764
10866
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10765
- className: _dsh_plugin_shop_css_9836f849_default.reloadPanel,
10867
+ className: _dsh_plugin_shop_css_e3675a89_default.reloadPanel,
10766
10868
  "data-shop-reload": where,
10767
10869
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10768
- className: _dsh_plugin_shop_css_9836f849_default.notice,
10870
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
10769
10871
  children: t("reloadNote")
10770
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10771
- type: "button",
10772
- className: _dsh_plugin_shop_css_9836f849_default.reloadButton,
10773
- onClick: reload,
10774
- children: t("reload")
10872
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10873
+ className: _dsh_plugin_shop_css_e3675a89_default.offerActions,
10874
+ "data-shop-offer": true,
10875
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10876
+ type: "button",
10877
+ className: _dsh_plugin_shop_css_e3675a89_default.reloadButton,
10878
+ onClick: reload,
10879
+ children: t("reload")
10880
+ }), trailing]
10775
10881
  })]
10776
10882
  });
10777
10883
  }
@@ -10782,7 +10888,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10782
10888
  * transport throw renders the localized failure line — its private detail
10783
10889
  * (which can name hosts and ports) never reaches the UI.
10784
10890
  */
10785
- function EnabledSwitch({ row, surface, page, t, setEnabled, restart, restartBlocked, reload }) {
10891
+ function EnabledSwitch({ row, surface, page, t, setEnabled, restart, restartBlocked, reload, locked = false }) {
10786
10892
  const [enabled, setEnabledState] = (0, react.useState)(row.enabled);
10787
10893
  const [toggle, setToggle] = (0, react.useState)({ kind: "idle" });
10788
10894
  const owedKey = switchKey(surface, identityKey(row));
@@ -10817,7 +10923,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10817
10923
  }
10818
10924
  };
10819
10925
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10820
- className: _dsh_plugin_shop_css_9836f849_default.switchWrap,
10926
+ className: _dsh_plugin_shop_css_e3675a89_default.switchWrap,
10821
10927
  "data-shop-enabled-switch": row.name,
10822
10928
  children: [
10823
10929
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -10826,18 +10932,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10826
10932
  "aria-checked": enabled,
10827
10933
  "aria-label": t("enabledSwitch"),
10828
10934
  "data-shop-toggle": true,
10829
- className: `${_dsh_plugin_shop_css_9836f849_default.switch} ${enabled ? _dsh_plugin_shop_css_9836f849_default.switchOn : ""}`,
10935
+ className: `${_dsh_plugin_shop_css_e3675a89_default.switch} ${enabled ? _dsh_plugin_shop_css_e3675a89_default.switchOn : ""}`,
10830
10936
  onClick: () => void onToggle(),
10831
- disabled: toggle.kind === "saving",
10832
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_9836f849_default.switchKnob })
10937
+ disabled: toggle.kind === "saving" || locked,
10938
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.switchKnob })
10833
10939
  }),
10834
10940
  toggle.kind === "saved" && toggle.live && owed === "nothing" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10835
- className: _dsh_plugin_shop_css_9836f849_default.notice,
10941
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
10836
10942
  "data-shop-hot-apply": true,
10837
10943
  children: t("hotApplyNote")
10838
10944
  }),
10839
10945
  owed === "restart" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10840
- className: _dsh_plugin_shop_css_9836f849_default.notice,
10946
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
10841
10947
  "data-shop-toggle-restart": true,
10842
10948
  children: t("toggleRestartNote")
10843
10949
  }),
@@ -10850,7 +10956,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10850
10956
  where: "toggle"
10851
10957
  }),
10852
10958
  toggle.kind === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10853
- className: _dsh_plugin_shop_css_9836f849_default.failedDetail,
10959
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
10854
10960
  "data-shop-toggle-error": true,
10855
10961
  children: toggle.detail
10856
10962
  })
@@ -10863,21 +10969,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10863
10969
  * carry a `latest` string — a catalog truth to compare against; §C10's
10864
10970
  * shadowed rows hold `latest: null` and never reach this control, so the
10865
10971
  * field is narrowed at the parameter. */
10866
- function OutdatedRow({ row, tier, missing, harness, t, setEnabled, flowFor, restart, restartBlocked, reload, page }) {
10972
+ function OutdatedRow({ row, icon, tier, missing, harness, t, setEnabled, flowFor, locked, restart, restartBlocked, reload, page }) {
10867
10973
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10868
- className: _dsh_plugin_shop_css_9836f849_default.outdatedRow,
10974
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedRow,
10869
10975
  "data-shop-outdated-entry": row.name,
10870
10976
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10871
- className: _dsh_plugin_shop_css_9836f849_default.outdatedInfo,
10872
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10873
- className: _dsh_plugin_shop_css_9836f849_default.name,
10874
- children: row.name
10875
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10876
- className: _dsh_plugin_shop_css_9836f849_default.outdatedVersions,
10877
- 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 }) })]
10977
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedInfo,
10978
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryIcon, { icon }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10979
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedText,
10980
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10981
+ className: _dsh_plugin_shop_css_e3675a89_default.name,
10982
+ children: row.name
10983
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10984
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersions,
10985
+ 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 }) })]
10986
+ })]
10878
10987
  })]
10879
10988
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10880
- className: _dsh_plugin_shop_css_9836f849_default.outdatedActions,
10989
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedActions,
10881
10990
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EnabledSwitch, {
10882
10991
  row,
10883
10992
  surface: "row",
@@ -10886,7 +10995,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10886
10995
  setEnabled,
10887
10996
  restart,
10888
10997
  restartBlocked,
10889
- reload
10998
+ reload,
10999
+ locked
10890
11000
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
10891
11001
  target: {
10892
11002
  name: row.name,
@@ -10897,6 +11007,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10897
11007
  },
10898
11008
  tier,
10899
11009
  variant: "update",
11010
+ locked,
10900
11011
  blockers: stateBlockers(blockersOf(missing, harness, void 0), t),
10901
11012
  flow: flowFor(identityKey(row)),
10902
11013
  t,
@@ -10926,33 +11037,35 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10926
11037
  * community entry's does); an entry absent from the catalog is read as
10927
11038
  * community, so its request acknowledges, which the host accepts for any
10928
11039
  * tier. */
10929
- function OutdatedSection({ state, entriesByKey, missingByKey, harnessVerdicts, t, setEnabled, flowFor, restart, restartBlocked, reload, page }) {
11040
+ function OutdatedSection({ state, entriesByKey, icons, missingByKey, harnessVerdicts, t, setEnabled, flowFor, uninstallFor, restart, restartBlocked, reload, page }) {
10930
11041
  if (state.kind === "loading") return null;
10931
11042
  if (state.kind === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10932
- className: _dsh_plugin_shop_css_9836f849_default.stateLine,
11043
+ className: _dsh_plugin_shop_css_e3675a89_default.stateLine,
10933
11044
  "data-shop-outdated-error": true,
10934
11045
  children: t("installedError")
10935
11046
  });
10936
11047
  const outdated = state.entries.filter((entry) => entry.outdated && entry.latest !== null);
10937
11048
  if (outdated.length === 0) return null;
10938
11049
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
10939
- className: _dsh_plugin_shop_css_9836f849_default.outdatedSection,
11050
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedSection,
10940
11051
  "data-shop-outdated": true,
10941
11052
  "aria-labelledby": UPDATABLE_HEADING_ID,
10942
11053
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
10943
- className: _dsh_plugin_shop_css_9836f849_default.updatableHeading,
11054
+ className: _dsh_plugin_shop_css_e3675a89_default.updatableHeading,
10944
11055
  id: UPDATABLE_HEADING_ID,
10945
11056
  children: t("updatableSection")
10946
11057
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10947
- className: _dsh_plugin_shop_css_9836f849_default.outdatedList,
11058
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedList,
10948
11059
  children: outdated.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedRow, {
10949
11060
  row,
11061
+ icon: iconOf(entriesByKey.get(identityKey(row)), icons),
10950
11062
  tier: entriesByKey.get(identityKey(row))?.tier ?? "community",
10951
11063
  missing: missingByKey.get(identityKey(row)) ?? [],
10952
11064
  harness: harnessVerdictOf(harnessVerdicts, identityKey(row)),
10953
11065
  t,
10954
11066
  setEnabled,
10955
11067
  flowFor,
11068
+ locked: uninstallFor(identityKey(row)).view.kind === "running" || uninstallFor(identityKey(row)).view.kind === "done",
10956
11069
  restart,
10957
11070
  restartBlocked,
10958
11071
  reload,
@@ -10968,41 +11081,44 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
10968
11081
  * naming the harness fact, and an Uninstall button as the only offered
10969
11082
  * action: there is no catalog truth to compare against, so no Install /
10970
11083
  * Update / Enable / Restart control makes sense. */
10971
- function ShadowedSection({ entries, t, uninstallFlows, restart, restartBlocked, reload }) {
11084
+ function ShadowedSection({ entries, icons, entriesByKey, t, uninstallFlows, restart, restartBlocked, reload }) {
10972
11085
  const shadowed = entries.filter((entry) => entry.shadowed === true);
10973
11086
  if (shadowed.length === 0) return null;
10974
11087
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
10975
- className: _dsh_plugin_shop_css_9836f849_default.shadowedSection,
11088
+ className: _dsh_plugin_shop_css_e3675a89_default.shadowedSection,
10976
11089
  "data-shop-shadowed": true,
10977
11090
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10978
- className: _dsh_plugin_shop_css_9836f849_default.outdatedList,
11091
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedList,
10979
11092
  children: shadowed.map((row) => {
10980
11093
  const key = identityKey(row);
10981
11094
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10982
- className: _dsh_plugin_shop_css_9836f849_default.outdatedRow,
11095
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedRow,
10983
11096
  "data-shop-shadowed-entry": row.name,
10984
11097
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10985
- className: _dsh_plugin_shop_css_9836f849_default.outdatedInfo,
10986
- children: [
10987
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10988
- className: _dsh_plugin_shop_css_9836f849_default.name,
10989
- children: row.name
10990
- }),
10991
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10992
- className: _dsh_plugin_shop_css_9836f849_default.badges,
10993
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10994
- className: _dsh_plugin_shop_css_9836f849_default.tierBadge,
10995
- "data-tier": "shadowed",
10996
- children: t("shadowedBadge")
11098
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedInfo,
11099
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryIcon, { icon: iconOf(entriesByKey.get(identityKey(row)), icons) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
11100
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedText,
11101
+ children: [
11102
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11103
+ className: _dsh_plugin_shop_css_e3675a89_default.name,
11104
+ children: row.name
11105
+ }),
11106
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11107
+ className: _dsh_plugin_shop_css_e3675a89_default.badges,
11108
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11109
+ className: _dsh_plugin_shop_css_e3675a89_default.tierBadge,
11110
+ "data-tier": "shadowed",
11111
+ children: t("shadowedBadge")
11112
+ })
11113
+ }),
11114
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11115
+ className: _dsh_plugin_shop_css_e3675a89_default.shadowedDetail,
11116
+ children: t("shadowedDetail")
10997
11117
  })
10998
- }),
10999
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11000
- className: _dsh_plugin_shop_css_9836f849_default.shadowedDetail,
11001
- children: t("shadowedDetail")
11002
- })
11003
- ]
11118
+ ]
11119
+ })]
11004
11120
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11005
- className: _dsh_plugin_shop_css_9836f849_default.outdatedActions,
11121
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedActions,
11006
11122
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
11007
11123
  name: row.name,
11008
11124
  t,
@@ -11052,16 +11168,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11052
11168
  role: "alert",
11053
11169
  children: [
11054
11170
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11055
- className: _dsh_plugin_shop_css_9836f849_default.stateLine,
11171
+ className: _dsh_plugin_shop_css_e3675a89_default.stateLine,
11056
11172
  children: say("tabCrashed", en.tabCrashed)
11057
11173
  }),
11058
11174
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11059
- className: _dsh_plugin_shop_css_9836f849_default.failedDetail,
11175
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
11060
11176
  children: error.message
11061
11177
  }),
11062
11178
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11063
11179
  type: "button",
11064
- className: _dsh_plugin_shop_css_9836f849_default.actionButton,
11180
+ className: _dsh_plugin_shop_css_e3675a89_default.actionButton,
11065
11181
  onClick: () => this.setState({ error: null }),
11066
11182
  children: say("retry", en.retry)
11067
11183
  })
@@ -11277,6 +11393,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11277
11393
  return map;
11278
11394
  }, [installedState]);
11279
11395
  const stars = (0, react.useMemo)(() => catalogState.kind === "ready" ? catalogState.result.stars : {}, [catalogState]);
11396
+ const icons = (0, react.useMemo)(() => catalogState.kind === "ready" ? catalogState.result.icons ?? {} : {}, [catalogState]);
11280
11397
  const starOrder = (0, react.useMemo)(() => catalogState.kind === "ready" ? sortByStars(catalogState.result.plugins, stars) : [], [catalogState, stars]);
11281
11398
  const builtAt = catalogState.kind === "ready" ? catalogState.result.builtAt : "";
11282
11399
  const sortedCatalog = (0, react.useMemo)(() => orderShelf(starOrder, order, builtAt), [
@@ -11387,19 +11504,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11387
11504
  "data-shop-tab": true,
11388
11505
  "aria-busy": "true",
11389
11506
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11390
- className: _dsh_plugin_shop_css_9836f849_default.srOnly,
11507
+ className: _dsh_plugin_shop_css_e3675a89_default.srOnly,
11391
11508
  children: t("loading")
11392
11509
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11393
- className: _dsh_plugin_shop_css_9836f849_default.skeletonGrid,
11510
+ className: _dsh_plugin_shop_css_e3675a89_default.skeletonGrid,
11394
11511
  "data-shop-skeleton": true,
11395
11512
  "aria-hidden": "true",
11396
11513
  children: Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11397
- className: _dsh_plugin_shop_css_9836f849_default.skeletonCard,
11514
+ className: _dsh_plugin_shop_css_e3675a89_default.skeletonCard,
11398
11515
  children: [
11399
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_9836f849_default.skeletonName }),
11400
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_9836f849_default.skeletonSummary }),
11401
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_9836f849_default.skeletonSummaryShort }),
11402
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_9836f849_default.skeletonActions })
11516
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonName }),
11517
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonSummary }),
11518
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonSummaryShort }),
11519
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonActions })
11403
11520
  ]
11404
11521
  }, i))
11405
11522
  })]
@@ -11409,11 +11526,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11409
11526
  translate: "no",
11410
11527
  "data-shop-tab": true,
11411
11528
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11412
- className: _dsh_plugin_shop_css_9836f849_default.stateLine,
11529
+ className: _dsh_plugin_shop_css_e3675a89_default.stateLine,
11413
11530
  children: t("error")
11414
11531
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11415
11532
  type: "button",
11416
- className: _dsh_plugin_shop_css_9836f849_default.actionButton,
11533
+ className: _dsh_plugin_shop_css_e3675a89_default.actionButton,
11417
11534
  onClick: () => {
11418
11535
  setRequest({
11419
11536
  kind: "retry",
@@ -11431,11 +11548,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11431
11548
  "data-shop-tab": true,
11432
11549
  children: [
11433
11550
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11434
- className: _dsh_plugin_shop_css_9836f849_default.toolbar,
11551
+ className: _dsh_plugin_shop_css_e3675a89_default.toolbar,
11435
11552
  children: [
11436
11553
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
11437
11554
  type: "search",
11438
- className: _dsh_plugin_shop_css_9836f849_default.searchInput,
11555
+ className: _dsh_plugin_shop_css_e3675a89_default.searchInput,
11439
11556
  "aria-label": t("search"),
11440
11557
  placeholder: t("search"),
11441
11558
  value: query,
@@ -11448,31 +11565,31 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11448
11565
  }),
11449
11566
  expansionMemo.intents.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11450
11567
  "data-shop-expansion-note": true,
11451
- className: _dsh_plugin_shop_css_9836f849_default.expansionNote,
11568
+ className: _dsh_plugin_shop_css_e3675a89_default.expansionNote,
11452
11569
  children: t("zhExpansionNote", { terms: expansionMemo.terms.slice(1).map((term) => term.startsWith("#") ? term.slice(1) : term).join(", ") })
11453
11570
  }),
11454
11571
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11455
- className: _dsh_plugin_shop_css_9836f849_default.toolbarRight,
11572
+ className: _dsh_plugin_shop_css_e3675a89_default.toolbarRight,
11456
11573
  children: [
11457
11574
  result.stale && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11458
- className: _dsh_plugin_shop_css_9836f849_default.staleBadge,
11575
+ className: _dsh_plugin_shop_css_e3675a89_default.staleBadge,
11459
11576
  children: t("staleLabel", { date: result.builtAt.slice(0, 10) })
11460
11577
  }),
11461
11578
  selfVersion !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11462
- className: _dsh_plugin_shop_css_9836f849_default.versionBlock,
11579
+ className: _dsh_plugin_shop_css_e3675a89_default.versionBlock,
11463
11580
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
11464
- className: _dsh_plugin_shop_css_9836f849_default.versionText,
11581
+ className: _dsh_plugin_shop_css_e3675a89_default.versionText,
11465
11582
  "data-shop-version": true,
11466
11583
  children: ["v", selfVersion.installed]
11467
11584
  }), restartMonitor !== null ? null : selfPending && restartBlocked === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11468
11585
  type: "button",
11469
- className: _dsh_plugin_shop_css_9836f849_default.restartSelfButton,
11586
+ className: _dsh_plugin_shop_css_e3675a89_default.restartSelfButton,
11470
11587
  "data-shop-restart": true,
11471
11588
  onClick: () => setSelfRestartGate(true),
11472
11589
  children: t("restart")
11473
11590
  }) : !selfPending && selfVersion.outdated && selfVersion.latest !== null && selfUpdate.view.kind === "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11474
11591
  type: "button",
11475
- className: _dsh_plugin_shop_css_9836f849_default.updateSelfButton,
11592
+ className: _dsh_plugin_shop_css_e3675a89_default.updateSelfButton,
11476
11593
  "data-shop-update-self": true,
11477
11594
  onClick: () => {
11478
11595
  if (selfVersion.latest !== null) selfUpdate.start({ version: selfVersion.latest });
@@ -11480,7 +11597,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11480
11597
  children: t("update")
11481
11598
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11482
11599
  type: "button",
11483
- className: _dsh_plugin_shop_css_9836f849_default.checkUpdateButton,
11600
+ className: _dsh_plugin_shop_css_e3675a89_default.checkUpdateButton,
11484
11601
  "data-shop-check-update": true,
11485
11602
  title: t("checkUpdateTitle"),
11486
11603
  disabled: checkState !== "idle",
@@ -11489,7 +11606,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11489
11606
  })]
11490
11607
  }),
11491
11608
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
11492
- className: _dsh_plugin_shop_css_9836f849_default.githubLink,
11609
+ className: _dsh_plugin_shop_css_e3675a89_default.githubLink,
11493
11610
  href: SHOP_REPO_URL,
11494
11611
  target: "_blank",
11495
11612
  rel: "noopener noreferrer",
@@ -11514,28 +11631,29 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11514
11631
  logFile: restartMonitor.logFile
11515
11632
  }),
11516
11633
  selfUpdate.view.kind === "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11517
- className: _dsh_plugin_shop_css_9836f849_default.selfUpdatePanel,
11634
+ className: _dsh_plugin_shop_css_e3675a89_default.selfUpdatePanel,
11518
11635
  "data-shop-self-updating": true,
11519
11636
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11520
- className: _dsh_plugin_shop_css_9836f849_default.installing,
11637
+ className: _dsh_plugin_shop_css_e3675a89_default.installing,
11521
11638
  children: t(installPhaseKey(selfUpdate.view.phase))
11522
11639
  }), selfUpdate.view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11523
- className: _dsh_plugin_shop_css_9836f849_default.log,
11640
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
11641
+ "data-shop-log": true,
11524
11642
  children: selfUpdate.view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11525
- className: _dsh_plugin_shop_css_9836f849_default.logLine,
11643
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
11526
11644
  "data-shop-log-line": true,
11527
11645
  children: line
11528
11646
  }, index))
11529
11647
  })]
11530
11648
  }),
11531
11649
  selfPending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11532
- className: _dsh_plugin_shop_css_9836f849_default.selfUpdatePanel,
11650
+ className: _dsh_plugin_shop_css_e3675a89_default.selfUpdatePanel,
11533
11651
  "data-shop-self-update-done": true,
11534
11652
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11535
- className: _dsh_plugin_shop_css_9836f849_default.installedActions,
11653
+ className: _dsh_plugin_shop_css_e3675a89_default.installedActions,
11536
11654
  children: [
11537
11655
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11538
- className: _dsh_plugin_shop_css_9836f849_default.notice,
11656
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
11539
11657
  children: t("installedRestartNotice")
11540
11658
  }),
11541
11659
  restartBlocked === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RestartPanel, {
@@ -11548,7 +11666,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11548
11666
  }
11549
11667
  }),
11550
11668
  restartBlocked !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11551
- className: _dsh_plugin_shop_css_9836f849_default.notice,
11669
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
11552
11670
  "data-shop-restart-disabled": true,
11553
11671
  children: t(restartBlockedNoticeKey(restartBlocked))
11554
11672
  })
@@ -11556,28 +11674,29 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11556
11674
  })
11557
11675
  }),
11558
11676
  selfUpdate.view.kind === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11559
- className: _dsh_plugin_shop_css_9836f849_default.selfUpdatePanel,
11677
+ className: _dsh_plugin_shop_css_e3675a89_default.selfUpdatePanel,
11560
11678
  "data-shop-self-update-failed": true,
11561
11679
  children: [
11562
11680
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11563
- className: _dsh_plugin_shop_css_9836f849_default.failedHeading,
11681
+ className: _dsh_plugin_shop_css_e3675a89_default.failedHeading,
11564
11682
  children: t("updateFailed")
11565
11683
  }),
11566
11684
  selfUpdate.view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11567
- className: _dsh_plugin_shop_css_9836f849_default.log,
11685
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
11686
+ "data-shop-log": true,
11568
11687
  children: selfUpdate.view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
11569
- className: _dsh_plugin_shop_css_9836f849_default.logLine,
11688
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
11570
11689
  "data-shop-log-line": true,
11571
11690
  children: line
11572
11691
  }, index))
11573
11692
  }),
11574
11693
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11575
- className: _dsh_plugin_shop_css_9836f849_default.failedDetail,
11694
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
11576
11695
  children: selfUpdate.view.detail === "" ? t("updateTransportFailed") : selfUpdate.view.detail
11577
11696
  }),
11578
11697
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11579
11698
  type: "button",
11580
- className: _dsh_plugin_shop_css_9836f849_default.cancelButton,
11699
+ className: _dsh_plugin_shop_css_e3675a89_default.cancelButton,
11581
11700
  "data-shop-update-self-dismiss": true,
11582
11701
  onClick: () => {
11583
11702
  selfUpdate.resetWhere((current) => current.kind === "failed");
@@ -11587,13 +11706,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11587
11706
  ]
11588
11707
  }),
11589
11708
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11590
- className: _dsh_plugin_shop_css_9836f849_default.categoryBar,
11709
+ className: _dsh_plugin_shop_css_e3675a89_default.categoryBar,
11591
11710
  role: "group",
11592
11711
  "aria-label": t("catalog"),
11593
11712
  children: [
11594
11713
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
11595
11714
  type: "button",
11596
- className: category === null ? `${_dsh_plugin_shop_css_9836f849_default.categoryButton} ${_dsh_plugin_shop_css_9836f849_default.categoryButtonOn}` : _dsh_plugin_shop_css_9836f849_default.categoryButton,
11715
+ className: category === null ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_default.categoryButton,
11597
11716
  "aria-pressed": category === null,
11598
11717
  onClick: () => {
11599
11718
  page.setView({
@@ -11610,7 +11729,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11610
11729
  }),
11611
11730
  CATEGORY_ORDER.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
11612
11731
  type: "button",
11613
- className: category === key ? `${_dsh_plugin_shop_css_9836f849_default.categoryButton} ${_dsh_plugin_shop_css_9836f849_default.categoryButtonOn}` : _dsh_plugin_shop_css_9836f849_default.categoryButton,
11732
+ className: category === key ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_default.categoryButton,
11614
11733
  "aria-pressed": category === key,
11615
11734
  "data-category": key,
11616
11735
  "data-shop-category-tab": key,
@@ -11628,7 +11747,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11628
11747
  }, key)),
11629
11748
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
11630
11749
  type: "button",
11631
- className: category === "installed" ? `${_dsh_plugin_shop_css_9836f849_default.categoryButton} ${_dsh_plugin_shop_css_9836f849_default.categoryButtonOn}` : _dsh_plugin_shop_css_9836f849_default.categoryButton,
11750
+ className: category === "installed" ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_default.categoryButton,
11632
11751
  "aria-pressed": category === "installed",
11633
11752
  "data-shop-category-installed": true,
11634
11753
  onClick: () => {
@@ -11647,7 +11766,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11647
11766
  type: "button",
11648
11767
  role: "switch",
11649
11768
  "aria-checked": hideIncompatible,
11650
- className: hideIncompatible ? `${_dsh_plugin_shop_css_9836f849_default.incompatibleFilter} ${_dsh_plugin_shop_css_9836f849_default.incompatibleFilterOn}` : _dsh_plugin_shop_css_9836f849_default.incompatibleFilter,
11769
+ className: hideIncompatible ? `${_dsh_plugin_shop_css_e3675a89_default.incompatibleFilter} ${_dsh_plugin_shop_css_e3675a89_default.incompatibleFilterOn}` : _dsh_plugin_shop_css_e3675a89_default.incompatibleFilter,
11651
11770
  title: t(hideIncompatible ? "showIncompatibleTitle" : "incompatibleFilterTitle"),
11652
11771
  "data-shop-hide-incompatible": true,
11653
11772
  onClick: () => {
@@ -11657,18 +11776,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11657
11776
  }));
11658
11777
  },
11659
11778
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11660
- className: hideIncompatible ? `${_dsh_plugin_shop_css_9836f849_default.switch} ${_dsh_plugin_shop_css_9836f849_default.switchOn}` : _dsh_plugin_shop_css_9836f849_default.switch,
11779
+ className: hideIncompatible ? `${_dsh_plugin_shop_css_e3675a89_default.switch} ${_dsh_plugin_shop_css_e3675a89_default.switchOn}` : _dsh_plugin_shop_css_e3675a89_default.switch,
11661
11780
  "aria-hidden": "true",
11662
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_9836f849_default.switchKnob })
11781
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.switchKnob })
11663
11782
  }), t("hideIncompatible", { count: incompatibleCount })]
11664
11783
  })
11665
11784
  ]
11666
11785
  }),
11667
11786
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
11668
- className: _dsh_plugin_shop_css_9836f849_default.catalogStatsRow,
11787
+ className: _dsh_plugin_shop_css_e3675a89_default.catalogStatsRow,
11669
11788
  children: [
11670
11789
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11671
- className: _dsh_plugin_shop_css_9836f849_default.catalogStats,
11790
+ className: _dsh_plugin_shop_css_e3675a89_default.catalogStats,
11672
11791
  "data-shop-catalog-stats": true,
11673
11792
  children: t("catalogStats", {
11674
11793
  count: String(browsable.length),
@@ -11677,7 +11796,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11677
11796
  }),
11678
11797
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11679
11798
  type: "button",
11680
- className: _dsh_plugin_shop_css_9836f849_default.catalogRefreshButton,
11799
+ className: _dsh_plugin_shop_css_e3675a89_default.catalogRefreshButton,
11681
11800
  "data-shop-catalog-refresh": true,
11682
11801
  disabled: reloading,
11683
11802
  onClick: () => {
@@ -11692,22 +11811,22 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11692
11811
  children: reloading ? t("refreshing") : t("refresh")
11693
11812
  }),
11694
11813
  reloadFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11695
- className: _dsh_plugin_shop_css_9836f849_default.catalogRefreshFailed,
11814
+ className: _dsh_plugin_shop_css_e3675a89_default.catalogRefreshFailed,
11696
11815
  "data-shop-catalog-refresh-failed": true,
11697
11816
  children: t("refreshFailed")
11698
11817
  }),
11699
11818
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
11700
- className: _dsh_plugin_shop_css_9836f849_default.sortGroup,
11819
+ className: _dsh_plugin_shop_css_e3675a89_default.sortGroup,
11701
11820
  role: "group",
11702
11821
  "aria-labelledby": sortLabelId,
11703
11822
  "data-shop-sort": true,
11704
11823
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
11705
- className: _dsh_plugin_shop_css_9836f849_default.sortLabel,
11824
+ className: _dsh_plugin_shop_css_e3675a89_default.sortLabel,
11706
11825
  id: sortLabelId,
11707
11826
  children: t("sortLabel")
11708
11827
  }), SHELF_ORDERS.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
11709
11828
  type: "button",
11710
- className: order === name ? `${_dsh_plugin_shop_css_9836f849_default.categoryButton} ${_dsh_plugin_shop_css_9836f849_default.categoryButtonOn}` : _dsh_plugin_shop_css_9836f849_default.categoryButton,
11829
+ className: order === name ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_default.categoryButton,
11711
11830
  "aria-pressed": order === name,
11712
11831
  "data-shop-order": name,
11713
11832
  onClick: () => {
@@ -11722,24 +11841,25 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11722
11841
  ]
11723
11842
  }),
11724
11843
  result.plugins.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11725
- className: _dsh_plugin_shop_css_9836f849_default.emptyLine,
11844
+ className: _dsh_plugin_shop_css_e3675a89_default.emptyLine,
11726
11845
  children: t("empty")
11727
11846
  }) : filtered.length === 0 ? category === "installed" && installedState.kind !== "ready" ? installedState.kind === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11728
- className: _dsh_plugin_shop_css_9836f849_default.emptyLine,
11847
+ className: _dsh_plugin_shop_css_e3675a89_default.emptyLine,
11729
11848
  "data-shop-installed-loading": true,
11730
11849
  children: t("installedLoading")
11731
11850
  }) : null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11732
- className: _dsh_plugin_shop_css_9836f849_default.emptyLine,
11851
+ className: _dsh_plugin_shop_css_e3675a89_default.emptyLine,
11733
11852
  "data-shop-empty": true,
11734
11853
  children: matched.length > 0 ? t("emptyIncompatibleFiltered") : t("emptySearch")
11735
11854
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
11736
- className: _dsh_plugin_shop_css_9836f849_default.cards,
11855
+ className: _dsh_plugin_shop_css_e3675a89_default.cards,
11737
11856
  children: [visible.map((entry) => {
11738
11857
  const key = entryKey(entry);
11739
11858
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
11740
11859
  "data-shop-identity": key,
11741
11860
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryCard, {
11742
11861
  entry,
11862
+ icon: iconOf(entry, icons),
11743
11863
  stars: starsOf(entry, stars),
11744
11864
  order,
11745
11865
  shelfDay: shelfDayOf(entry),
@@ -11760,12 +11880,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11760
11880
  }, key);
11761
11881
  }), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
11762
11882
  ref: sentinelRef,
11763
- className: _dsh_plugin_shop_css_9836f849_default.cardsSentry,
11883
+ className: _dsh_plugin_shop_css_e3675a89_default.cardsSentry,
11764
11884
  "data-shop-sentry": true,
11765
11885
  "aria-hidden": "true"
11766
11886
  })]
11767
11887
  }), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11768
- className: _dsh_plugin_shop_css_9836f849_default.showingLine,
11888
+ className: _dsh_plugin_shop_css_e3675a89_default.showingLine,
11769
11889
  "aria-live": "polite",
11770
11890
  children: t("showing", {
11771
11891
  shown: String(visibleCount),
@@ -11775,11 +11895,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11775
11895
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedSection, {
11776
11896
  state: installedState,
11777
11897
  entriesByKey,
11898
+ icons,
11778
11899
  missingByKey,
11779
11900
  harnessVerdicts,
11780
11901
  t,
11781
11902
  setEnabled,
11782
11903
  flowFor: flows.flowFor,
11904
+ uninstallFor: uninstallFlows.flowFor,
11783
11905
  restart,
11784
11906
  restartBlocked,
11785
11907
  reload,
@@ -11787,6 +11909,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11787
11909
  }),
11788
11910
  category === "installed" && installedState.kind === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShadowedSection, {
11789
11911
  entries: installedState.entries,
11912
+ icons,
11913
+ entriesByKey,
11790
11914
  t,
11791
11915
  uninstallFlows,
11792
11916
  restart,
@@ -11922,7 +12046,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11922
12046
  }, [page]);
11923
12047
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
11924
12048
  ref: scroller,
11925
- className: `${_dsh_plugin_shop_css_9836f849_default.page} notranslate`,
12049
+ className: `${_dsh_plugin_shop_css_e3675a89_default.page} notranslate`,
11926
12050
  translate: "no",
11927
12051
  "data-shop-page": true,
11928
12052
  onScroll: (event) => {
@@ -11932,15 +12056,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
11932
12056
  onTouchStart: cancelRestore,
11933
12057
  onKeyDown: cancelRestore,
11934
12058
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
11935
- className: _dsh_plugin_shop_css_9836f849_default.pageHead,
12059
+ className: _dsh_plugin_shop_css_e3675a89_default.pageHead,
11936
12060
  "data-shop-page-head": true,
11937
12061
  "data-window-drag": true,
11938
12062
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
11939
- className: _dsh_plugin_shop_css_9836f849_default.pageTitle,
12063
+ className: _dsh_plugin_shop_css_e3675a89_default.pageTitle,
11940
12064
  "data-shop-page-title": true,
11941
12065
  children: t("title")
11942
12066
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
11943
- className: _dsh_plugin_shop_css_9836f849_default.pageIntro,
12067
+ className: _dsh_plugin_shop_css_e3675a89_default.pageIntro,
11944
12068
  "data-shop-page-intro": true,
11945
12069
  children: t("intro")
11946
12070
  })]