dsh-plugin-shop 0.9.0 → 0.9.1-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +983 -821
- package/lib/index.js +227 -86
- package/lib/typert.host.js +14 -12
- package/lib/typert.remote-client.js +12 -10
- package/lib/types/client/EntryIcon.d.ts +27 -0
- package/lib/types/client/locales.d.ts +8 -0
- package/lib/types/client/present.d.ts +8 -0
- package/lib/types/host/catalog.d.ts +5 -0
- package/lib/types/host/index.d.ts +3 -0
- package/lib/types/host/types.d.ts +7 -0
- package/lib/types/shared/npm-name.d.ts +42 -0
- package/lib/types/shared/tar.d.ts +8 -3
- package/package.json +2 -2
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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":
|
|
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,20 @@ 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} 个插件 ·
|
|
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
|
-
|
|
4803
|
+
/** The wide layout's rail headings (design 2026-11-02-shop-widescreen-layout
|
|
4804
|
+
* §4): the category group above, the hide-incompatible modifier below. */
|
|
4805
|
+
categoriesHeading: "分类",
|
|
4806
|
+
filterHeading: "筛选",
|
|
4807
|
+
sortStars: "星标最多",
|
|
4802
4808
|
sortAdded: "最新上架",
|
|
4803
4809
|
sortUpdated: "最近更新",
|
|
4804
|
-
listedOn: "
|
|
4805
|
-
updatedOn: "
|
|
4806
|
-
stars: "{count}
|
|
4810
|
+
listedOn: "{date} 上架",
|
|
4811
|
+
updatedOn: "{date} 更新",
|
|
4812
|
+
stars: "{count} 个星标",
|
|
4807
4813
|
categoryTool: "工具",
|
|
4808
4814
|
categoryProvider: "模型服务",
|
|
4809
4815
|
categoryUi: "界面",
|
|
@@ -4811,16 +4817,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4811
4817
|
categoryIntegration: "集成",
|
|
4812
4818
|
categoryTheme: "主题",
|
|
4813
4819
|
categoryOther: "其他",
|
|
4814
|
-
empty: "
|
|
4815
|
-
emptySearch: "
|
|
4820
|
+
empty: "暂无插件。",
|
|
4821
|
+
emptySearch: "没有找到匹配的插件。试试其他关键词或分类。",
|
|
4816
4822
|
tierVerified: "已审核",
|
|
4817
|
-
tierVerifiedStale: "
|
|
4823
|
+
tierVerifiedStale: "当前版本未审核",
|
|
4818
4824
|
tierCommunity: "社区",
|
|
4819
4825
|
/** The github repository GitHub reports as archived (design 2026-10-08 §1.3).
|
|
4820
4826
|
* Subdued register, like `tierCommunity`: nothing is wrong with the install
|
|
4821
4827
|
* itself; the owner has simply frozen the project. */
|
|
4822
4828
|
archivedBadge: "已归档",
|
|
4823
|
-
capabilitiesNote: "
|
|
4829
|
+
capabilitiesNote: "权限由作者声明,插件的实际权限可能超出这一范围。",
|
|
4824
4830
|
install: "安装",
|
|
4825
4831
|
cancel: "取消",
|
|
4826
4832
|
dismiss: "知道了",
|
|
@@ -4831,44 +4837,44 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4831
4837
|
lessRedLines: "收起",
|
|
4832
4838
|
installing: "正在安装…",
|
|
4833
4839
|
downloading: "正在下载…",
|
|
4834
|
-
installedRestartNotice: "
|
|
4840
|
+
installedRestartNotice: "已安装,重启 dsh 后生效。",
|
|
4835
4841
|
incompatibleBadge: "不兼容",
|
|
4836
|
-
incompatibleDetail: "
|
|
4842
|
+
incompatibleDetail: "缺少所需的组件或插件:{modules}\n此插件可能适用于其他版本的 dsh。",
|
|
4837
4843
|
/** The author's own `dsh.compatibility`, unmet on this machine (design
|
|
4838
4844
|
* 2026-09-01 §8.2). Each names BOTH sides — what the author declared and
|
|
4839
4845
|
* what this dsh runs — because the host sends both, and a bare
|
|
4840
4846
|
* "unsupported" leaves the reader guessing which side is off. */
|
|
4841
|
-
harnessRangeDetail: "
|
|
4842
|
-
harnessProfileDetail: "
|
|
4847
|
+
harnessRangeDetail: "作者声明支持 dsh {range},当前版本为 {running}。",
|
|
4848
|
+
harnessProfileDetail: "作者声明支持的配置(profile):{declared}。当前使用的是 {running}。",
|
|
4843
4849
|
/** dsh's OWN refusal (0.1.7 on), not the author's claim: the card shows the
|
|
4844
4850
|
* refused label in the install button's place (design
|
|
4845
4851
|
* 2026-09-26-dsh-017-readiness, B1, amended 2026-10-14). `{peers}`
|
|
4846
4852
|
* is each refused peer with the range the package declared. The remedy
|
|
4847
4853
|
* leads into the exemption command, rendered on its own line after it, and
|
|
4848
4854
|
* names the Refresh button by its label. */
|
|
4849
|
-
harnessPeersDetail: "
|
|
4850
|
-
harnessPeersRemedy: "
|
|
4851
|
-
hotNoPatchNotice: "
|
|
4852
|
-
hotNotSimpleNotice: "
|
|
4853
|
-
hotHostUnsupportedNotice: "
|
|
4854
|
-
hotTimeoutNotice: "
|
|
4855
|
-
hotMountFailedNotice: "
|
|
4855
|
+
harnessPeersDetail: "此插件要求 {peers},当前的 dsh {running} 不满足要求,无法安装。",
|
|
4856
|
+
harnessPeersRemedy: "继续安装可能导致崩溃或数据丢失。若接受这些风险,可在终端运行以下命令,允许安装此版本,然后点击“刷新”:",
|
|
4857
|
+
hotNoPatchNotice: "已安装,但未找到可即时加载的插件配置。请重启 dsh 使其生效。",
|
|
4858
|
+
hotNotSimpleNotice: "已安装,但插件配置需要重启 dsh 才能加载。",
|
|
4859
|
+
hotHostUnsupportedNotice: "已安装,当前环境需要重启 dsh 才能加载插件。",
|
|
4860
|
+
hotTimeoutNotice: "已安装,但加载超时。请重启 dsh 使其生效。",
|
|
4861
|
+
hotMountFailedNotice: "已安装,但加载失败。请重启 dsh 使其生效。",
|
|
4856
4862
|
/** No mount was tried: this dsh already loaded the package once, and Node
|
|
4857
4863
|
* will not load a second copy from the same place (design
|
|
4858
4864
|
* 2026-09-26-market-borrowings §1). Covers an update and a reinstall alike,
|
|
4859
4865
|
* so it names the new files rather than a "new version". */
|
|
4860
|
-
hotAlreadyLoadedNotice: "
|
|
4861
|
-
installedNoRestartNotice: "
|
|
4862
|
-
installedReloadNotice: "
|
|
4866
|
+
hotAlreadyLoadedNotice: "已安装。dsh 已加载过此插件,新安装的文件将在重启后生效。",
|
|
4867
|
+
installedNoRestartNotice: "已安装并生效,无需重启。",
|
|
4868
|
+
installedReloadNotice: "已生效,刷新页面即可看到更改。",
|
|
4863
4869
|
reload: "刷新页面",
|
|
4864
|
-
reloadNote: "
|
|
4870
|
+
reloadNote: "更改已在服务器上生效,刷新页面后即可看到。当前页面中未完成的内容会丢失。",
|
|
4865
4871
|
installFailed: "安装失败",
|
|
4866
|
-
installTransportFailed: "
|
|
4872
|
+
installTransportFailed: "安装请求失败,请稍后重试。",
|
|
4867
4873
|
refresh: "刷新",
|
|
4868
4874
|
refreshing: "刷新中…",
|
|
4869
|
-
refreshFailed: "
|
|
4870
|
-
staleLabel: "
|
|
4871
|
-
showing: "
|
|
4875
|
+
refreshFailed: "刷新失败,当前显示的是上次加载的插件列表。",
|
|
4876
|
+
staleLabel: "当前显示 {date} 的插件列表",
|
|
4877
|
+
showing: "已显示 {shown} 个,共 {total} 个 · 向下滚动加载更多",
|
|
4872
4878
|
repository: "仓库",
|
|
4873
4879
|
npmPage: "npm 页面",
|
|
4874
4880
|
authorLine: "作者:{author}",
|
|
@@ -4878,236 +4884,240 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4878
4884
|
* was npm's word: a commit-pinned github entry unpacks nothing, its figure
|
|
4879
4885
|
* being the sum of its git tree's blobs. One phrase now covers both sources
|
|
4880
4886
|
* and stays true of npm, whose number is unchanged. */
|
|
4881
|
-
sizeLabel: "
|
|
4887
|
+
sizeLabel: "磁盘占用:{size}",
|
|
4882
4888
|
/** The incompatible filter's label. FIXED — the switch beside it carries the
|
|
4883
4889
|
* state, so the words name what the switch does and never flip with it. */
|
|
4884
4890
|
hideIncompatible: "隐藏不兼容 {count}",
|
|
4885
4891
|
/** What the filter takes away is every card whose badge reads 不兼容 —
|
|
4886
4892
|
* missing components OR an author's declaration this dsh does not meet —
|
|
4887
4893
|
* so these three name the badge, not one of its reasons. */
|
|
4888
|
-
incompatibleFilterTitle: "
|
|
4889
|
-
showIncompatibleTitle: "
|
|
4890
|
-
emptyIncompatibleFiltered: "
|
|
4891
|
-
license: "
|
|
4892
|
-
customLicense: "
|
|
4893
|
-
reviewedVersionLine: "
|
|
4894
|
-
reviewedPinLine: "
|
|
4895
|
-
deniedCode: "
|
|
4894
|
+
incompatibleFilterTitle: "隐藏标为不兼容的插件",
|
|
4895
|
+
showIncompatibleTitle: "显示标为不兼容的插件",
|
|
4896
|
+
emptyIncompatibleFiltered: "匹配的插件都标为不兼容。关闭“隐藏不兼容”即可查看。",
|
|
4897
|
+
license: "许可证",
|
|
4898
|
+
customLicense: "自定义许可证",
|
|
4899
|
+
reviewedVersionLine: "已审核版本:{reviewed} · 当前版本 {current} 尚未审核",
|
|
4900
|
+
reviewedPinLine: "已审核:{pin} · 当前版本 {current} 尚未审核",
|
|
4901
|
+
deniedCode: "不允许安装",
|
|
4896
4902
|
notInCatalogCode: "不在目录中",
|
|
4897
4903
|
versionMismatchCode: "版本不匹配",
|
|
4898
|
-
needsAcknowledgementCode: "
|
|
4904
|
+
needsAcknowledgementCode: "需要确认安装",
|
|
4899
4905
|
tarballIntegrityCode: "压缩包完整性校验失败",
|
|
4900
|
-
ambiguousIdentityCode: "
|
|
4901
|
-
nameTakenCode: "
|
|
4902
|
-
desktopProfileCode: "
|
|
4903
|
-
nameTakenBadge: "
|
|
4904
|
-
nameTakenDetail: "
|
|
4906
|
+
ambiguousIdentityCode: "无法确定要安装的插件",
|
|
4907
|
+
nameTakenCode: "已安装其他同名插件",
|
|
4908
|
+
desktopProfileCode: "此配置由桌面版管理",
|
|
4909
|
+
nameTakenBadge: "名称冲突",
|
|
4910
|
+
nameTakenDetail: "已安装同名插件({holder}),安装此插件会替换它。\n同一配置中只能安装一个同名插件。若要更换,请先卸载已安装的插件。",
|
|
4905
4911
|
npmSource: "npm 包",
|
|
4906
|
-
githubSource: "
|
|
4912
|
+
githubSource: "GitHub 源码",
|
|
4907
4913
|
/** The accessible name and the tooltip of a card's version, which say which
|
|
4908
4914
|
* of three things the printed string is (market borrowings §11): the string
|
|
4909
4915
|
* alone cannot tell a release tag from a version number, and a short commit
|
|
4910
4916
|
* printed with a `v` read as one. */
|
|
4911
4917
|
npmVersionLabel: "npm 版本 {version}",
|
|
4912
4918
|
commitVersionLabel: "GitHub 提交 {version}",
|
|
4913
|
-
releaseVersionLabel: "GitHub
|
|
4919
|
+
releaseVersionLabel: "GitHub 发布版本 {version}",
|
|
4914
4920
|
/** The badge and the explanation for an installed github entry no catalog
|
|
4915
4921
|
* row claims (C10 MVP). The mechanism synthesizes a row for ANY github spec
|
|
4916
4922
|
* with no catalog row — shadowed by a same-named npm package, an unlisted
|
|
4917
4923
|
* fork, a removed listing, or a same-named conflict with a different repo —
|
|
4918
4924
|
* so the copy names the catalog fact and lists the causes, rather than
|
|
4919
4925
|
* claiming the npm-twin one. */
|
|
4920
|
-
shadowedBadge: "
|
|
4921
|
-
shadowedDetail: "
|
|
4922
|
-
updatableSection: "
|
|
4926
|
+
shadowedBadge: "未在目录中列出",
|
|
4927
|
+
shadowedDetail: "此插件已安装,但当前目录未列出它,不影响现有使用。可能是目录只展示了同名 npm 包、插件已下架,或尚未被收录。",
|
|
4928
|
+
updatableSection: "有可用更新",
|
|
4923
4929
|
/** `{version}` is the shelf's own version text (`versionLabel`), which
|
|
4924
4930
|
* carries the `v` an npm version takes and no other kind does. */
|
|
4925
4931
|
installedVersion: "已安装 {version}",
|
|
4926
|
-
latestVersion: "
|
|
4932
|
+
latestVersion: "最新版本 {version}",
|
|
4927
4933
|
update: "更新",
|
|
4928
4934
|
installed: "已安装",
|
|
4929
4935
|
uninstall: "卸载",
|
|
4930
4936
|
uninstalling: "正在卸载…",
|
|
4931
4937
|
uninstallFailed: "卸载失败",
|
|
4932
|
-
uninstallTransportFailed: "
|
|
4933
|
-
uninstalledLiveNotice: "
|
|
4934
|
-
uninstalledRestartNotice: "
|
|
4935
|
-
uninstalledReloadNotice: "
|
|
4938
|
+
uninstallTransportFailed: "卸载请求失败,请稍后重试。",
|
|
4939
|
+
uninstalledLiveNotice: "已卸载并停止运行,下次启动 dsh 时也不会再加载此插件。",
|
|
4940
|
+
uninstalledRestartNotice: "已卸载,重启 dsh 后生效。",
|
|
4941
|
+
uninstalledReloadNotice: "已卸载,刷新页面后即可移除界面中的相关内容。下次启动 dsh 时也不会再加载此插件。",
|
|
4936
4942
|
restart: "重启 dsh",
|
|
4937
|
-
restartTitle: "
|
|
4938
|
-
restartBody: "
|
|
4943
|
+
restartTitle: "重启 dsh?",
|
|
4944
|
+
restartBody: "重启会暂时断开连接,并中断正在进行的对话和任务。服务器就绪后,页面会自动重新连接。",
|
|
4939
4945
|
restartConfirm: "重启",
|
|
4940
|
-
restarting: "
|
|
4941
|
-
restartFailedNotice: "
|
|
4946
|
+
restarting: "正在重启 dsh,服务器就绪后页面会自动刷新…",
|
|
4947
|
+
restartFailedNotice: "重启后未能连接到服务器。请运行 dsh web 手动启动;启动日志位于插件商店缓存目录中的 restart.log。",
|
|
4942
4948
|
/** The same failure, when the host said where the new process writes —
|
|
4943
4949
|
* which is where a boot that died explains itself (design
|
|
4944
4950
|
* 2026-09-26-market-borrowings §3). "Or did not stay up": the page now
|
|
4945
4951
|
* waits for the new server to keep answering, so this also covers a boot
|
|
4946
4952
|
* that answered and then died. */
|
|
4947
|
-
restartFailedLogNotice: "
|
|
4948
|
-
restartTransportFailed: "
|
|
4949
|
-
notRestartedNotice: "dsh
|
|
4950
|
-
notRestartedPidNotice: "dsh
|
|
4951
|
-
notRestartedLogNotice: "
|
|
4952
|
-
restartBlockedDesktopNotice: "
|
|
4953
|
-
restartBlockedFromDefaultProfileNotice: "
|
|
4954
|
-
restartBlockedWindowsNotice: "
|
|
4955
|
-
restartBlockedSystemdNotice: "
|
|
4956
|
-
restartBlockedPortZeroNotice: "
|
|
4953
|
+
restartFailedLogNotice: "重启后未能连接到服务器,或服务器启动后再次停止。请查看启动日志 {log},也可运行 dsh web 手动启动。",
|
|
4954
|
+
restartTransportFailed: "重启请求失败,请稍后重试。",
|
|
4955
|
+
notRestartedNotice: "dsh 尚未重启,当前仍连接到原来的进程,更改尚未生效。请退出正在运行的 dsh,再运行 dsh web。",
|
|
4956
|
+
notRestartedPidNotice: "dsh 尚未重启,原来的进程(PID {pid})仍在运行,更改尚未生效。请运行 kill {pid} 结束该进程;若无法退出,可用 kill -9 {pid} 强制结束。然后运行 dsh web。",
|
|
4957
|
+
notRestartedLogNotice: "重启日志:{log}",
|
|
4958
|
+
restartBlockedDesktopNotice: "此配置由 DeepSeek Harness 桌面版管理。请重启桌面版以使更改生效。",
|
|
4959
|
+
restartBlockedFromDefaultProfileNotice: "dsh 启动时使用了 --from-default-profile 参数。从 dsh 0.2.1 起,这会在重启时按模板重建配置,并删除已安装的所有插件,包括插件商店。请去掉该参数后手动重启 dsh。",
|
|
4960
|
+
restartBlockedWindowsNotice: "Windows 上暂不支持从插件商店重启 dsh。请退出 dsh 后重新启动,使更改生效。",
|
|
4961
|
+
restartBlockedSystemdNotice: "dsh 由 systemd 服务管理。从插件商店重启可能导致服务停止后无法恢复,请手动重启服务。若要允许从商店重启,可在插件商店配置中设置 allowRestart: true。",
|
|
4962
|
+
restartBlockedPortZeroNotice: "dsh 启动时使用了 --port 0 参数,重启后端口会改变,页面无法自动重新连接。请手动重启 dsh,再打开新的地址。",
|
|
4957
4963
|
updateFailed: "更新失败",
|
|
4958
|
-
updateTransportFailed: "
|
|
4964
|
+
updateTransportFailed: "更新请求失败,请稍后重试。",
|
|
4959
4965
|
checkUpdate: "检查更新",
|
|
4960
4966
|
checkUpdateTitle: "检查是否有新版本",
|
|
4961
4967
|
upToDate: "已是最新版本",
|
|
4962
4968
|
github: "GitHub",
|
|
4963
4969
|
enabledSwitch: "启用",
|
|
4964
|
-
toggleFailed: "
|
|
4965
|
-
hotApplyNote: "
|
|
4966
|
-
toggleRestartNote: "已保存,重启 dsh
|
|
4970
|
+
toggleFailed: "未能更改启用状态,请重试。",
|
|
4971
|
+
hotApplyNote: "已生效,无需重启。",
|
|
4972
|
+
toggleRestartNote: "已保存,重启 dsh 后生效。"
|
|
4967
4973
|
};
|
|
4968
4974
|
/** English dictionary checked against the Chinese key set. */
|
|
4969
4975
|
const en = {
|
|
4970
4976
|
title: "Plugin shop",
|
|
4971
|
-
intro: "Browse, install, and update plugins
|
|
4977
|
+
intro: "Browse, install, and update plugins.",
|
|
4972
4978
|
openShop: "Open Plugin shop",
|
|
4973
4979
|
viewInShop: "View in Plugin shop",
|
|
4974
|
-
loading: "
|
|
4975
|
-
error: "
|
|
4976
|
-
installedLoading: "
|
|
4977
|
-
installedError: "
|
|
4980
|
+
loading: "Loading plugins…",
|
|
4981
|
+
error: "Could not load the plugin catalog. Please try again.",
|
|
4982
|
+
installedLoading: "Loading installed plugins…",
|
|
4983
|
+
installedError: "Could not load your installed plugins. Please try again.",
|
|
4978
4984
|
retry: "Retry",
|
|
4979
|
-
tabCrashed: "The plugin shop
|
|
4985
|
+
tabCrashed: "The plugin shop could not display this page. Please try again.",
|
|
4980
4986
|
search: "Search plugins",
|
|
4981
4987
|
/** One subdued line under the search box when a Chinese query fired a
|
|
4982
4988
|
* dictionary intent (design 2026-10-08-search-query-expansion §4). */
|
|
4983
4989
|
zhExpansionNote: "Also matching: {terms}",
|
|
4984
4990
|
all: "All",
|
|
4985
4991
|
catalog: "Plugin catalog",
|
|
4986
|
-
catalogStats: "{count}
|
|
4992
|
+
catalogStats: "{count} plugins · Catalog updated {date}",
|
|
4987
4993
|
/** The shelf's order control, and the date each card carries under the two
|
|
4988
4994
|
* date orders (design 2026-10-12-shelf-orders §4). */
|
|
4989
4995
|
sortLabel: "Sort",
|
|
4990
|
-
|
|
4996
|
+
/** The wide layout's rail headings (design 2026-11-02-shop-widescreen-layout
|
|
4997
|
+
* §4): the category group above, the hide-incompatible modifier below. */
|
|
4998
|
+
categoriesHeading: "Categories",
|
|
4999
|
+
filterHeading: "Filter",
|
|
5000
|
+
sortStars: "Most stars",
|
|
4991
5001
|
sortAdded: "Newest",
|
|
4992
5002
|
sortUpdated: "Recently updated",
|
|
4993
5003
|
listedOn: "Listed {date}",
|
|
4994
5004
|
updatedOn: "Updated {date}",
|
|
4995
5005
|
stars: "{count} stars",
|
|
4996
|
-
categoryTool: "
|
|
4997
|
-
categoryProvider: "
|
|
4998
|
-
categoryUi: "
|
|
4999
|
-
categoryWorkflow: "
|
|
5000
|
-
categoryIntegration: "
|
|
5006
|
+
categoryTool: "Tools",
|
|
5007
|
+
categoryProvider: "Model providers",
|
|
5008
|
+
categoryUi: "Interface",
|
|
5009
|
+
categoryWorkflow: "Workflows",
|
|
5010
|
+
categoryIntegration: "Integrations",
|
|
5001
5011
|
categoryTheme: "Themes",
|
|
5002
5012
|
categoryOther: "Other",
|
|
5003
|
-
empty: "No plugins
|
|
5004
|
-
emptySearch: "No matching plugins.",
|
|
5005
|
-
tierVerified: "
|
|
5013
|
+
empty: "No plugins yet.",
|
|
5014
|
+
emptySearch: "No matching plugins. Try another search or category.",
|
|
5015
|
+
tierVerified: "Reviewed",
|
|
5006
5016
|
/** The github repository GitHub reports as archived (design 2026-10-08 §1.3).
|
|
5007
5017
|
* Subdued register: nothing is wrong with the install; the owner has simply
|
|
5008
5018
|
* frozen the project. */
|
|
5009
5019
|
archivedBadge: "Archived",
|
|
5010
|
-
tierVerifiedStale: "
|
|
5020
|
+
tierVerifiedStale: "Current version unreviewed",
|
|
5011
5021
|
tierCommunity: "Community",
|
|
5012
|
-
capabilitiesNote: "
|
|
5022
|
+
capabilitiesNote: "These permissions are declared by the author. They do not limit what the plugin can access.",
|
|
5013
5023
|
install: "Install",
|
|
5014
5024
|
cancel: "Cancel",
|
|
5015
|
-
dismiss: "
|
|
5025
|
+
dismiss: "Got it",
|
|
5016
5026
|
moreRedLines: "Show more",
|
|
5017
5027
|
lessRedLines: "Show less",
|
|
5018
5028
|
installing: "Installing…",
|
|
5019
5029
|
downloading: "Downloading…",
|
|
5020
|
-
installedRestartNotice: "
|
|
5030
|
+
installedRestartNotice: "Installed. Restart dsh to apply the change.",
|
|
5021
5031
|
incompatibleBadge: "Incompatible",
|
|
5022
|
-
incompatibleDetail: "Missing components
|
|
5023
|
-
harnessRangeDetail: "The author
|
|
5024
|
-
harnessProfileDetail: "The author
|
|
5025
|
-
harnessPeersDetail: "
|
|
5026
|
-
harnessPeersRemedy: "
|
|
5027
|
-
hotNoPatchNotice: "no
|
|
5028
|
-
hotNotSimpleNotice: "
|
|
5029
|
-
hotHostUnsupportedNotice: "
|
|
5030
|
-
hotTimeoutNotice: "
|
|
5031
|
-
hotMountFailedNotice: "
|
|
5032
|
-
hotAlreadyLoadedNotice: "
|
|
5033
|
-
installedNoRestartNotice: "
|
|
5034
|
-
installedReloadNotice: "
|
|
5032
|
+
incompatibleDetail: "Missing required components or plugins: {modules}\nThis plugin may need a different version of dsh.",
|
|
5033
|
+
harnessRangeDetail: "The author lists support for dsh {range}. You are running {running}.",
|
|
5034
|
+
harnessProfileDetail: "The author lists support for these profiles: {declared}. You are using {running}.",
|
|
5035
|
+
harnessPeersDetail: "This plugin requires {peers}, which your dsh {running} does not satisfy. Installation is blocked.",
|
|
5036
|
+
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:",
|
|
5037
|
+
hotNoPatchNotice: "Installed, but no plugin configuration could be loaded immediately. Restart dsh to apply the change.",
|
|
5038
|
+
hotNotSimpleNotice: "Installed. This plugin configuration needs a dsh restart to load.",
|
|
5039
|
+
hotHostUnsupportedNotice: "Installed. Your environment needs a dsh restart to load the plugin.",
|
|
5040
|
+
hotTimeoutNotice: "Installed, but loading timed out. Restart dsh to apply the change.",
|
|
5041
|
+
hotMountFailedNotice: "Installed, but the plugin could not load. Restart dsh to apply the change.",
|
|
5042
|
+
hotAlreadyLoadedNotice: "Installed. dsh has already loaded this plugin, so the new files will take effect after a restart.",
|
|
5043
|
+
installedNoRestartNotice: "Installed and ready to use. No restart needed.",
|
|
5044
|
+
installedReloadNotice: "Applied. Reload the page to see the change.",
|
|
5035
5045
|
reload: "Reload the page",
|
|
5036
|
-
reloadNote: "The
|
|
5046
|
+
reloadNote: "The change is active on the server. Reload the page to see it. Any unfinished work on this page will be lost.",
|
|
5037
5047
|
installFailed: "Install failed",
|
|
5038
|
-
installTransportFailed: "The install request
|
|
5048
|
+
installTransportFailed: "The install request failed. Please try again.",
|
|
5039
5049
|
refresh: "Refresh",
|
|
5040
5050
|
refreshing: "Refreshing…",
|
|
5041
|
-
refreshFailed: "
|
|
5042
|
-
staleLabel: "
|
|
5043
|
-
showing: "
|
|
5051
|
+
refreshFailed: "Could not refresh. Showing the last loaded catalog.",
|
|
5052
|
+
staleLabel: "Showing the catalog from {date}",
|
|
5053
|
+
showing: "Showing {shown} of {total} · Scroll down to load more",
|
|
5044
5054
|
repository: "Repository",
|
|
5045
5055
|
npmPage: "npm page",
|
|
5046
5056
|
authorLine: "Author: {author}",
|
|
5047
5057
|
sizeLabel: "{size} on disk",
|
|
5048
5058
|
hideIncompatible: "Hide incompatible {count}",
|
|
5049
|
-
incompatibleFilterTitle: "
|
|
5050
|
-
showIncompatibleTitle: "
|
|
5051
|
-
emptyIncompatibleFiltered: "
|
|
5059
|
+
incompatibleFilterTitle: "Hide plugins marked as incompatible",
|
|
5060
|
+
showIncompatibleTitle: "Show plugins marked as incompatible",
|
|
5061
|
+
emptyIncompatibleFiltered: "All matching plugins are marked as incompatible. Turn off \"Hide incompatible\" to see them.",
|
|
5052
5062
|
license: "License",
|
|
5053
5063
|
customLicense: "Custom license",
|
|
5054
|
-
reviewedVersionLine: "
|
|
5055
|
-
reviewedPinLine: "
|
|
5056
|
-
deniedCode: "
|
|
5064
|
+
reviewedVersionLine: "Reviewed: {reviewed} · Current version {current} has not been reviewed",
|
|
5065
|
+
reviewedPinLine: "Reviewed: {pin} · Current version {current} has not been reviewed",
|
|
5066
|
+
deniedCode: "Installation blocked",
|
|
5057
5067
|
notInCatalogCode: "Not in the catalog",
|
|
5058
5068
|
versionMismatchCode: "Version mismatch",
|
|
5059
|
-
needsAcknowledgementCode: "
|
|
5060
|
-
tarballIntegrityCode: "
|
|
5061
|
-
ambiguousIdentityCode: "
|
|
5062
|
-
nameTakenCode: "Another plugin
|
|
5069
|
+
needsAcknowledgementCode: "Installation confirmation needed",
|
|
5070
|
+
tarballIntegrityCode: "Package integrity check failed",
|
|
5071
|
+
ambiguousIdentityCode: "Could not identify the plugin",
|
|
5072
|
+
nameTakenCode: "Another plugin with this name is installed",
|
|
5063
5073
|
desktopProfileCode: "Managed by the desktop app",
|
|
5064
|
-
nameTakenBadge: "Name
|
|
5065
|
-
nameTakenDetail: "
|
|
5074
|
+
nameTakenBadge: "Name conflict",
|
|
5075
|
+
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
5076
|
npmSource: "npm package",
|
|
5067
|
-
githubSource: "source
|
|
5077
|
+
githubSource: "GitHub source",
|
|
5068
5078
|
npmVersionLabel: "npm version {version}",
|
|
5069
5079
|
commitVersionLabel: "GitHub commit {version}",
|
|
5070
5080
|
releaseVersionLabel: "GitHub release {version}",
|
|
5071
5081
|
shadowedBadge: "Not in the current catalog",
|
|
5072
|
-
shadowedDetail: "This
|
|
5073
|
-
updatableSection: "
|
|
5074
|
-
installedVersion: "
|
|
5075
|
-
latestVersion: "
|
|
5082
|
+
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.",
|
|
5083
|
+
updatableSection: "Updates available",
|
|
5084
|
+
installedVersion: "Installed {version}",
|
|
5085
|
+
latestVersion: "Latest version {version}",
|
|
5076
5086
|
update: "Update",
|
|
5077
5087
|
installed: "Installed",
|
|
5078
5088
|
uninstall: "Uninstall",
|
|
5079
5089
|
uninstalling: "Uninstalling…",
|
|
5080
5090
|
uninstallFailed: "Uninstall failed",
|
|
5081
|
-
uninstallTransportFailed: "The uninstall request
|
|
5082
|
-
uninstalledLiveNotice: "
|
|
5083
|
-
uninstalledRestartNotice: "
|
|
5084
|
-
uninstalledReloadNotice: "
|
|
5091
|
+
uninstallTransportFailed: "The uninstall request failed. Please try again.",
|
|
5092
|
+
uninstalledLiveNotice: "Uninstalled and stopped. The plugin will no longer load when dsh starts.",
|
|
5093
|
+
uninstalledRestartNotice: "Uninstalled. Restart dsh to finish removing the plugin.",
|
|
5094
|
+
uninstalledReloadNotice: "Uninstalled. Reload the page to remove its interface. The plugin will no longer load when dsh starts.",
|
|
5085
5095
|
restart: "Restart dsh",
|
|
5086
5096
|
restartTitle: "Restart dsh?",
|
|
5087
|
-
restartBody: "Restarting
|
|
5097
|
+
restartBody: "Restarting will temporarily disconnect this page and interrupt ongoing conversations and tasks. The page will reconnect automatically when the server is ready.",
|
|
5088
5098
|
restartConfirm: "Restart",
|
|
5089
|
-
restarting: "Restarting
|
|
5090
|
-
restartFailedNotice: "
|
|
5091
|
-
restartFailedLogNotice: "
|
|
5092
|
-
restartTransportFailed: "The restart request
|
|
5093
|
-
notRestartedNotice: "dsh
|
|
5094
|
-
notRestartedPidNotice: "dsh
|
|
5095
|
-
notRestartedLogNotice: "
|
|
5096
|
-
restartBlockedDesktopNotice: "This profile is managed by the DeepSeek Harness desktop app
|
|
5097
|
-
restartBlockedFromDefaultProfileNotice: "
|
|
5098
|
-
restartBlockedWindowsNotice: "
|
|
5099
|
-
restartBlockedSystemdNotice: "
|
|
5100
|
-
restartBlockedPortZeroNotice: "
|
|
5099
|
+
restarting: "Restarting dsh. The page will reload when the server is ready…",
|
|
5100
|
+
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.",
|
|
5101
|
+
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.",
|
|
5102
|
+
restartTransportFailed: "The restart request failed. Please try again.",
|
|
5103
|
+
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.",
|
|
5104
|
+
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.",
|
|
5105
|
+
notRestartedLogNotice: "Restart log: {log}",
|
|
5106
|
+
restartBlockedDesktopNotice: "This profile is managed by the DeepSeek Harness desktop app. Restart the app to apply the change.",
|
|
5107
|
+
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.",
|
|
5108
|
+
restartBlockedWindowsNotice: "Restarting dsh from the plugin shop is not supported on Windows yet. Quit dsh and start it again to apply the change.",
|
|
5109
|
+
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.",
|
|
5110
|
+
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
5111
|
updateFailed: "Update failed",
|
|
5102
|
-
updateTransportFailed: "The update request
|
|
5112
|
+
updateTransportFailed: "The update request failed. Please try again.",
|
|
5103
5113
|
checkUpdate: "Check",
|
|
5104
5114
|
checkUpdateTitle: "Check for updates",
|
|
5105
5115
|
upToDate: "Up to date",
|
|
5106
5116
|
github: "GitHub",
|
|
5107
5117
|
enabledSwitch: "Enable",
|
|
5108
|
-
toggleFailed: "
|
|
5109
|
-
hotApplyNote: "
|
|
5110
|
-
toggleRestartNote: "
|
|
5118
|
+
toggleFailed: "Could not change the enabled state. Please try again.",
|
|
5119
|
+
hotApplyNote: "Applied. No restart needed.",
|
|
5120
|
+
toggleRestartNote: "Saved. Restart dsh to apply the change."
|
|
5111
5121
|
};
|
|
5112
5122
|
//#endregion
|
|
5113
5123
|
//#region src/client/module-table.ts
|
|
@@ -7306,7 +7316,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7306
7316
|
const { status } = event;
|
|
7307
7317
|
if (!status.found) return {
|
|
7308
7318
|
kind: "failed",
|
|
7309
|
-
detail: status.detail ?? "
|
|
7319
|
+
detail: status.detail ?? "The installation record is no longer available. Refresh the shop to check whether the plugin was installed.",
|
|
7310
7320
|
log: status.log
|
|
7311
7321
|
};
|
|
7312
7322
|
if (!isTerminalInstallState(status.state)) return {
|
|
@@ -7471,6 +7481,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7471
7481
|
return cleared.has(entry.name) || !isShopLike(entry.name) && (entry.repo === void 0 || !isShopLike(entry.repo));
|
|
7472
7482
|
}
|
|
7473
7483
|
/**
|
|
7484
|
+
* An entry's icon as the catalog's data URL, or undefined to draw the default:
|
|
7485
|
+
* it declares none, the catalog predates the icons file, or the sidecar
|
|
7486
|
+
* degraded and the map does not carry its sha256. The map is keyed by 64-hex
|
|
7487
|
+
* sha256, so no name from the catalog is ever used as a lookup here (design
|
|
7488
|
+
* 2026-10-23-shop-plugin-icons §7).
|
|
7489
|
+
*/
|
|
7490
|
+
function iconOf(entry, icons) {
|
|
7491
|
+
const sha = entry?.icon;
|
|
7492
|
+
if (sha === void 0) return void 0;
|
|
7493
|
+
return Object.hasOwn(icons, sha) ? icons[sha] : void 0;
|
|
7494
|
+
}
|
|
7495
|
+
/**
|
|
7474
7496
|
* One entry's star count from the sidecar, or undefined when it has none.
|
|
7475
7497
|
*
|
|
7476
7498
|
* The sidecar keys a github entry by its repo full name and an npm entry by
|
|
@@ -8417,136 +8439,147 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8417
8439
|
if (typeof held?.dispose === "function") held.dispose();
|
|
8418
8440
|
}
|
|
8419
8441
|
//#endregion
|
|
8420
|
-
//#region \0dsh-plugin-shop-css:
|
|
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=\"
|
|
8442
|
+
//#region \0dsh-plugin-shop-css:bc430a83
|
|
8443
|
+
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.s514620_client.s7e11f7_config.s418ac5_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.s514620_client.s74d5ba_spec.s418ac5_ts` pins this. */\n\n.sc99bd6_panel {\n display: flex;\n flex-direction: column;\n gap: 14px;\n /* The shop's own width decides the shelf's arrangement (design\n * 2026-11-02-shop-widescreen-layout §3). The container is this tab root,\n * not `.sd86001_shelfBody`: a container query cannot style its own container, and\n * the rule that turns the shelf body into the rail's grid is exactly that.\n * Inline size only — the panel's height still follows its content. */\n container-type: inline-size;\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 `.s76d7f5_searchInput` sits at\n * exactly (0, 0) of this box — it is the first child of `.sa59b0a_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 (960px then; 1280 since the\n * 2026-11-02 wide layout), and the head clears the desktop title bar. One declaration is the shop's own, `color`:\n * dsh's `.s92dc8e_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.s92dc8e_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/* The head and the shelf share one column, and on 2026-11-02 that column\n * widened from 960 to 1280 so the wide arrangement has room for a rail\n * beside two cards (design 2026-11-02-shop-widescreen-layout §3). A head at\n * 960 over a body at 1280 would read as misaligned. */\n.s92dc8e_page > * {\n width: 100%;\n max-width: min(1280px, 100%);\n}\n\n.se536cd_pageHead {\n box-sizing: border-box;\n padding-top: calc(28px + var(--dsh-frame-top-clearance, 0px));\n}\n\n.sb9c160_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.sa532f1_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 shelf's two arrangements (design 2026-11-02-shop-widescreen-layout\n * §4). Base: today's column — the controls, then the results — with the\n * same 14px the panel used to put between them. The container is `.sc99bd6_panel`,\n * so these rules are queried from above, not from here. */\n.sd86001_shelfBody {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\n\n/* Narrow: the category chips and the switch are one row, as they are today.\n * `nowrap` is load-bearing — flex line-breaking uses the chips' MAX-content\n * width, so a wrapping row sends the switch to a line of its own the moment\n * the chips do not fit, which is every narrow panel. The bar shrinks and\n * wraps its own chips instead. The bar is a sibling of the switch now, not\n * its parent, so `align-items: flex-end` is what keeps the switch level with\n * the bar's last line rather than floating beside the middle of a two-line\n * block. */\n.s4b680b_controls {\n display: flex;\n flex-wrap: nowrap;\n align-items: flex-end;\n gap: 6px;\n}\n\n/* The rail's headings. Hidden below the threshold, where the controls are one\n * wrapping bar and these words would label nothing; the wide rule reveals\n * them (design 2026-11-02-shop-widescreen-layout §4). */\n.s91375e_railHeading {\n display: none;\n margin: 0;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s7e9262_results {\n display: flex;\n flex-direction: column;\n gap: 14px;\n}\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.s3306eb_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.s3306eb_detailAction:hover {\n background: var(--dsw-alias-interactive-bg-hover);\n}\n\n.s3306eb_detailAction:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s14f3b1_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.s3a0a70_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.sf21dd5_skeletonGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n}\n\n.s393a26_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.s393a26_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.sdbd3bb_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.s78fd91_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.se0c7ad_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.se30d52_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.s393a26_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 .s393a26_skeletonCard span { animation: none; opacity: 0.75; }\n .s393a26_skeletonCard::after { content: none; }\n}\n\n.sc69180_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.sc69180_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.sc69180_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sa59b0a_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.sd633e3_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.se387e7_versionBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.seccb93_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.s6b83c1_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.s6b83c1_updateSelfButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, transparent);\n}\n\n.s6b83c1_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.s86fc72_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.s86fc72_checkUpdateButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n}\n\n.s86fc72_checkUpdateButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.s86fc72_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.s3b5e50_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.s3b5e50_githubLink:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.s3b5e50_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.s9200c1_selfUpdatePanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n}\n\n.s76d7f5_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.s76d7f5_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.sf4cb4b_expansionNote {\n margin: 4px 0 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sc6f44e_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.sce6c31_categoryBar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n margin-top: 6px;\n /* A flex item of `.s4b680b_controls` at narrow widths: it may shrink below its\n * content, so the chips wrap instead of pushing the switch off the line.\n * It must NOT grow — a growing bar takes the whole line and pushes the\n * switch to a line of its own, where today the two share one. The wide\n * rail restores its natural height. */\n flex: 0 1 auto;\n min-width: 0;\n}\n\n.safdbbe_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.safdbbe_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 `.safdbbe_categoryButton.sc5d852_categoryButtonOn` — TWO classes — so it\n * matches `.safdbbe_categoryButton:hover`'s specificity and, coming after it, wins.\n * As a bare `.sc5d852_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.s514620_client.s74d5ba_spec.s418ac5_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.safdbbe_categoryButton.sc5d852_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 .safdbbe_categoryButton.sc5d852_categoryButtonOn {\n outline: 2px solid ButtonText;\n outline-offset: -4px;\n }\n}\n\n.safdbbe_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 controls row at narrow\n * widths: `margin-left: auto` pushes it past the bar, which shrinks and wraps\n * its own chips (`flex: 0 1 auto`) rather than squeezing the switch. Under\n * the wide layout it is a full-width row under the rail's \"Filter\" heading.\n * It is a MODIFIER, not a ninth category — the categories select what to show\n * and this one subtracts from whatever they selected — so it takes the error\n * hue it filters on rather 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 `.safdbbe_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.sd6d2c7_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: `.sd6d2c7_incompatibleFilter:hover .sc8dc92_switch` outranks `.s12653b_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.sd6d2c7_incompatibleFilter:hover {\n color: var(--dsw-alias-label-primary);\n}\n\n.sd6d2c7_incompatibleFilter:hover .sc8dc92_switch:not(.s12653b_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 `.safdbbe_categoryButton.sc5d852_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.sd6d2c7_incompatibleFilter.s515b4e_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.sd6d2c7_incompatibleFilter .sc8dc92_switch:not(.s12653b_switchOn) .sb7170f_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.sd6d2c7_incompatibleFilter:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: 999px;\n}\n\n.s9964fe_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.sc7794b_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.s28aa3c_sortGroup {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n margin-left: auto;\n}\n\n.s7842d7_sortLabel {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.s28d92b_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 `.sc69180_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.s695d80_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.s695d80_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.s695d80_catalogRefreshButton:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.s695d80_catalogRefreshButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s7a8ca3_catalogRefreshFailed {\n font-size: 11px;\n color: var(--shop-warn);\n}\n\n.s23625a_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. `auto-fill` with a 430px minimum\n * gives the two columns the wide layout buys — two tracks and their 12px gap\n * need 872px of results column — and one column below that (design\n * 2026-11-02-shop-widescreen-layout §5). The vertical gap is today's 8px. */\n.s36d340_cards {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(min(430px, 100%), 1fr));\n max-width: 100%;\n gap: 8px 12px;\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 .s36d340_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/* The card is this item's child. Without a grid here the item stretches to\n * the row but the card keeps its content height, so the action row's\n * `margin-top: auto` has no free space to absorb and two cards in a row end\n * their buttons on different lines — measured 65px short at a 1280px tab\n * (2026-11-02). A one-row grid stretches the card to the cell. */\n.s36d340_cards > li {\n min-width: 0;\n display: grid;\n}\n\n.sf5fa2b_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.s584a60_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s9d9bba_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sf5fa2b_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 .sf5fa2b_card { transition: none; }\n .sf5fa2b_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.s28d979_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. `.safdbbe_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.sf5fa2b_card[data-category='tool'], .safdbbe_categoryButton[data-category='tool'] { --category-hue: #4C8DFF; }\n.sf5fa2b_card[data-category='provider'], .safdbbe_categoryButton[data-category='provider'] { --category-hue: #A78BFA; }\n.sf5fa2b_card[data-category='ui'], .safdbbe_categoryButton[data-category='ui'] { --category-hue: #2DD4BF; }\n.sf5fa2b_card[data-category='workflow'], .safdbbe_categoryButton[data-category='workflow'] { --category-hue: #F59E0B; }\n.sf5fa2b_card[data-category='integration'], .safdbbe_categoryButton[data-category='integration'] { --category-hue: #34D399; }\n.sf5fa2b_card[data-category='theme'], .safdbbe_categoryButton[data-category='theme'] { --category-hue: #F472B6; }\n.sf5fa2b_card[data-category='other'], .safdbbe_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.s91829d_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 .s7f7e1e_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.s9d98a4_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.sac0d07_entryIconImage {\n width: 24px;\n height: 24px;\n object-fit: contain;\n}\n\n.s6a017b_entryHeader {\n display: flex;\n flex-direction: row;\n align-items: flex-start;\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.s6a017b_entryHeader:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n/* The name and the badge row, one column inside the header button. The\n * badges used to share the name's row, which at a 430px grid track left the\n * name about 120px; under it they get the full width and the name keeps its\n * line (design 2026-11-02-shop-widescreen-layout §5). */\n.sbc36fc_entryHeading {\n display: flex;\n flex-direction: column;\n gap: 3px;\n min-width: 0;\n flex: 1 1 auto;\n}\n\n/* The name fills the row and truncates rather than wraps; the badges stay\n * intact on the row under it. */\n.s4485fd_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.s150e7a_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.sbdfc2c_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 /* Grid rows stretch every card to the tallest one, and the card is a flex\n * column: this takes the leftover height above the buttons, so two cards\n * in one row end their action rows on the same line (design\n * 2026-11-02-shop-widescreen-layout §5). */\n margin-top: auto;\n}\n\n.sbdfc2c_cardActions > .s35ae56_gate,\n.sbdfc2c_cardActions > .s75cfe7_installPanel,\n.sbdfc2c_cardActions > .s130990_installedActions,\n.sbdfc2c_cardActions > .s848d91_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.se4fd96_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.s4ebdb5_sourceBadge {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa10256_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.sa10256_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.sa10256_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.sa10256_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.sa10256_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.sa10256_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.se63699_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.scc86dc_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sd8ede4_chevron, .s879a81_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.s879a81_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .sd8ede4_chevron, .s879a81_chevronOpen { transition: none; }\n}\n\n.s2900a7_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.sa41615_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.s91610a_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.s81bc49_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.sc64654_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.sfcd9cd_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.sb6142c_remedy {\n display: block;\n}\n\n.sb6142c_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 `.s445a81_foldBlock` collapsed and `.sb6457b_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.s445a81_foldBlock {\n display: block;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n display: -webkit-box;\n overflow: hidden;\n}\n\n.sb6457b_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.sd9b244_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.sd9b244_foldToggle:hover { border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2)); }\n.sd9b244_foldToggle:focus-visible { outline: 2px solid var(--dsw-alias-brand-primary); outline-offset: 1px; }\n\n.s29ecbc_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.se34c39_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.sb88eac_refusal {\n display: block;\n flex-basis: 100%;\n}\n\n.sb88eac_refusal > * + * { margin-top: 6px; }\n\n.seda8cf_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s85ae81_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.se9f845_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.se9f845_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.sa099e4_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.s33e230_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.s3c9f5e_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.s3c9f5e_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.s3c9f5e_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.s3c9f5e_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.s3c9f5e_detailRow a:hover {\n text-decoration: underline;\n}\n\n.s3c9f5e_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 * `.sbdfc2c_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.sf02a91_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: `.sf02a91_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 * `.s968579_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.s7859cb_size,\n.s968579_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 `.sf02a91_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 `.sf02a91_cardMeta`. There is no middot:\n * `.sf02a91_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.s4801d5_author {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.sfa18da_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.s75cfe7_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.s17e180_installButton,\n.s4d480e_confirmButton,\n.s9466ff_cancelButton,\n.sb6cbde_installedLabel,\n.s6d82dc_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.s17e180_installButton, .s4d480e_confirmButton, .s9466ff_cancelButton {\n cursor: pointer;\n}\n\n.s17e180_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.s17e180_installButton:disabled {\n cursor: wait;\n}\n\n.s17e180_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s4d480e_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.s4d480e_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s9466ff_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.s9466ff_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s17e180_installButton:focus-visible, .s4d480e_confirmButton:focus-visible, .s9466ff_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.sb6cbde_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.s6d82dc_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.s130990_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.safa1f4_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.safa1f4_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.safa1f4_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.s19d2da_restartSelfButton {\n /* The version row's third state. Geometry matches .s86fc72_checkUpdateButton and\n * .s6b83c1_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.s19d2da_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.s1916c4_restartButton,\n.se4b67d_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.s1916c4_restartButton:hover,\n.se4b67d_reloadButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s1916c4_restartButton:focus-visible,\n.se4b67d_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.s848d91_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.s860bef_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 * `.s130990_installedActions` the way the restart gate is — a column of its own\n * rather than inline with the row. */\n.se6345a_reloadPanel {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 6px;\n}\n\n\n.s35ae56_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.s4b04c5_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--shop-warn-on-tint);\n}\n\n.s3552a4_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.sf344ec_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.sba54c1_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.s110c4a_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa13cb7_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.s326167_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s88fa30_notice {\n margin: 0;\n font-size: 12px;\n color: var(--shop-success);\n}\n\n.s34c383_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--shop-error);\n}\n\n.s7d7ac6_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.scf1455_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--shop-error);\n}\n\n.s604bbc_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.s81e51c_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.s6f80ac_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s7e8f68_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.s27f8b7_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.sf16e21_outdatedText {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.s27f8b7_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.sb74cf7_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 .s150e7a_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.s4da305_shadowedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.sf1d53c_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.s43bdbf_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: `.sd6d2c7_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 `.sc8dc92_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.sc8dc92_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.s12653b_switchOn {\n border-color: var(--track-hue);\n background: color-mix(in srgb, var(--track-hue) 55%, transparent);\n}\n\n.sb7170f_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.s12653b_switchOn .sb7170f_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.sc8dc92_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 .sc8dc92_switch, .sb7170f_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.see584d_switchWrap {\n display: inline-flex;\n align-items: center;\n /* Same reason as `.s43bdbf_outdatedActions`: `.se6345a_reloadPanel`'s own comment calls\n * itself \"a column of its own rather than inline with the row\", which held\n * for `.s130990_installedActions` and not here until this line. */\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* THE WIDE RULES MUST STAY LAST. Nesting in @container adds no\n * specificity, so between a container rule and a top-level rule of the same\n * selector the source order decides. Placed above `.sce6c31_categoryBar`,\n * `.safdbbe_categoryButton` and the two focus rules, every one of these lost: the\n * rail kept the pills' row, the switch's auto margin and the outside ring,\n * and the geometry-only e2e passed (measured 2026-11-02).\n * `css-tokens.s514620_client.s74d5ba_spec.s418ac5_ts` fails if this block moves back up. */\n/* The rail. Two 430px tracks and their 12px gap need 872; the rail and its\n * 24px gap add 256; 12px of margin makes the threshold 1140. Below 872 the\n * grid's own `auto-fill` leaves one column. */\n@container (min-width: 1140px) {\n .sd86001_shelfBody {\n display: grid;\n grid-template-columns: 232px minmax(0, 1fr);\n column-gap: 24px;\n row-gap: 14px;\n }\n\n /* The rail sticks under dsh's frame top rather than scrolling away with\n * the results. `.sc99bd6_panel`'s `overflow: clip` is not a scroll container, so\n * the page stays the scrollport; the e2e proves it (design\n * 2026-11-02-shop-widescreen-layout §4). */\n .s4b680b_controls {\n flex-direction: column;\n align-items: stretch;\n gap: 8px;\n position: sticky;\n top: calc(16px + var(--dsh-frame-top-clearance, 0px));\n align-self: start;\n /* The cap subtracts the same clearance the offset adds. Without it a\n * rail whose content fits the cap hangs clearance - 16px below the\n * viewport while stuck (measured: 32px at the desktop frame's 48px), and\n * its last control only comes into view at the very bottom of the page.\n * The 32px is the 16px above the rail plus 16px below it. */\n max-height: calc(100vh - 32px - var(--dsh-frame-top-clearance, 0px));\n overflow: auto;\n }\n\n .s91375e_railHeading {\n display: block;\n }\n\n .sce6c31_categoryBar {\n flex: none;\n flex-direction: column;\n align-items: stretch;\n gap: 4px;\n /* The 6px belongs to the wrapping row; in the rail the controls' 8px gap\n * is the whole rhythm above the first button. */\n margin-top: 0;\n }\n\n /* Label left, count right: the two spans the buttons carry. */\n .safdbbe_categoryButton {\n display: flex;\n justify-content: space-between;\n }\n\n /* The rail is a column with `align-items: stretch`, where an auto left\n * margin would shrink the switch to its content and push it right. */\n .sd6d2c7_incompatibleFilter {\n margin-left: 0;\n }\n\n /* The rail is a scroll container (`overflow: auto`) and its controls are\n * flush with its padding box, so a ring drawn OUTSIDE them loses both\n * vertical segments to the clip — measured: red on the top edge and\n * corners, nothing on the sides. The inset ring, the idiom\n * `.s6a017b_entryHeader:focus-visible` already uses, keeps all four sides. */\n .safdbbe_categoryButton:focus-visible {\n outline-offset: -2px;\n }\n\n .sd6d2c7_incompatibleFilter:focus-visible {\n outline-offset: -2px;\n }\n}\n";
|
|
8444
|
+
if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"bc430a83\"]")) {
|
|
8423
8445
|
const tag = document.createElement("style");
|
|
8424
|
-
tag.dataset.pluginCss = "
|
|
8446
|
+
tag.dataset.pluginCss = "bc430a83";
|
|
8425
8447
|
tag.textContent = css;
|
|
8426
8448
|
document.head.appendChild(tag);
|
|
8427
8449
|
}
|
|
8428
|
-
var
|
|
8429
|
-
"client": "
|
|
8430
|
-
"config": "
|
|
8431
|
-
"ts": "
|
|
8432
|
-
"spec": "
|
|
8433
|
-
"panel": "
|
|
8434
|
-
"
|
|
8435
|
-
"
|
|
8436
|
-
"
|
|
8437
|
-
"
|
|
8438
|
-
"
|
|
8439
|
-
"
|
|
8440
|
-
"
|
|
8441
|
-
"
|
|
8442
|
-
"
|
|
8443
|
-
"
|
|
8444
|
-
"
|
|
8445
|
-
"
|
|
8446
|
-
"
|
|
8447
|
-
"
|
|
8448
|
-
"
|
|
8449
|
-
"
|
|
8450
|
-
"
|
|
8451
|
-
"
|
|
8452
|
-
"
|
|
8453
|
-
"
|
|
8454
|
-
"
|
|
8455
|
-
"
|
|
8456
|
-
"
|
|
8457
|
-
"
|
|
8458
|
-
"
|
|
8459
|
-
"
|
|
8460
|
-
"
|
|
8461
|
-
"
|
|
8462
|
-
"
|
|
8463
|
-
"
|
|
8464
|
-
"
|
|
8465
|
-
"
|
|
8466
|
-
"
|
|
8467
|
-
"
|
|
8468
|
-
"
|
|
8469
|
-
"
|
|
8470
|
-
"
|
|
8471
|
-
"
|
|
8472
|
-
"
|
|
8473
|
-
"
|
|
8474
|
-
"
|
|
8475
|
-
"
|
|
8476
|
-
"
|
|
8477
|
-
"
|
|
8478
|
-
"
|
|
8479
|
-
"
|
|
8480
|
-
"
|
|
8481
|
-
"
|
|
8482
|
-
"
|
|
8483
|
-
"
|
|
8484
|
-
"
|
|
8485
|
-
"
|
|
8486
|
-
"
|
|
8487
|
-
"
|
|
8488
|
-
"
|
|
8489
|
-
"
|
|
8490
|
-
"
|
|
8491
|
-
"
|
|
8492
|
-
"
|
|
8493
|
-
"
|
|
8494
|
-
"
|
|
8495
|
-
"
|
|
8496
|
-
"
|
|
8497
|
-
"
|
|
8498
|
-
"
|
|
8499
|
-
"
|
|
8500
|
-
"
|
|
8501
|
-
"
|
|
8502
|
-
"
|
|
8503
|
-
"
|
|
8504
|
-
"
|
|
8505
|
-
"
|
|
8506
|
-
"
|
|
8507
|
-
"
|
|
8508
|
-
"
|
|
8509
|
-
"
|
|
8510
|
-
"
|
|
8511
|
-
"
|
|
8512
|
-
"
|
|
8513
|
-
"
|
|
8514
|
-
"
|
|
8515
|
-
"
|
|
8516
|
-
"
|
|
8517
|
-
"
|
|
8518
|
-
"
|
|
8519
|
-
"
|
|
8520
|
-
"
|
|
8521
|
-
"
|
|
8522
|
-
"
|
|
8523
|
-
"
|
|
8524
|
-
"
|
|
8525
|
-
"
|
|
8526
|
-
"
|
|
8527
|
-
"
|
|
8528
|
-
"
|
|
8529
|
-
"
|
|
8530
|
-
"
|
|
8531
|
-
"
|
|
8532
|
-
"
|
|
8533
|
-
"
|
|
8534
|
-
"
|
|
8535
|
-
"
|
|
8536
|
-
"
|
|
8537
|
-
"
|
|
8538
|
-
"
|
|
8539
|
-
"
|
|
8540
|
-
"
|
|
8541
|
-
"
|
|
8542
|
-
"
|
|
8543
|
-
"
|
|
8544
|
-
"
|
|
8545
|
-
"
|
|
8546
|
-
"
|
|
8547
|
-
"
|
|
8548
|
-
"
|
|
8549
|
-
"
|
|
8450
|
+
var _dsh_plugin_shop_css_bc430a83_default = {
|
|
8451
|
+
"client": "s514620_client",
|
|
8452
|
+
"config": "s7e11f7_config",
|
|
8453
|
+
"ts": "s418ac5_ts",
|
|
8454
|
+
"spec": "s74d5ba_spec",
|
|
8455
|
+
"panel": "sc99bd6_panel",
|
|
8456
|
+
"shelfBody": "sd86001_shelfBody",
|
|
8457
|
+
"searchInput": "s76d7f5_searchInput",
|
|
8458
|
+
"toolbar": "sa59b0a_toolbar",
|
|
8459
|
+
"page": "s92dc8e_page",
|
|
8460
|
+
"pageHead": "se536cd_pageHead",
|
|
8461
|
+
"pageTitle": "sb9c160_pageTitle",
|
|
8462
|
+
"pageIntro": "sa532f1_pageIntro",
|
|
8463
|
+
"controls": "s4b680b_controls",
|
|
8464
|
+
"railHeading": "s91375e_railHeading",
|
|
8465
|
+
"results": "s7e9262_results",
|
|
8466
|
+
"detailAction": "s3306eb_detailAction",
|
|
8467
|
+
"stateLine": "s14f3b1_stateLine",
|
|
8468
|
+
"srOnly": "s3a0a70_srOnly",
|
|
8469
|
+
"skeletonGrid": "sf21dd5_skeletonGrid",
|
|
8470
|
+
"skeletonCard": "s393a26_skeletonCard",
|
|
8471
|
+
"skeletonName": "sdbd3bb_skeletonName",
|
|
8472
|
+
"skeletonSummary": "s78fd91_skeletonSummary",
|
|
8473
|
+
"skeletonSummaryShort": "se0c7ad_skeletonSummaryShort",
|
|
8474
|
+
"skeletonActions": "se30d52_skeletonActions",
|
|
8475
|
+
"actionButton": "sc69180_actionButton",
|
|
8476
|
+
"toolbarRight": "sd633e3_toolbarRight",
|
|
8477
|
+
"versionBlock": "se387e7_versionBlock",
|
|
8478
|
+
"versionText": "seccb93_versionText",
|
|
8479
|
+
"updateSelfButton": "s6b83c1_updateSelfButton",
|
|
8480
|
+
"checkUpdateButton": "s86fc72_checkUpdateButton",
|
|
8481
|
+
"githubLink": "s3b5e50_githubLink",
|
|
8482
|
+
"selfUpdatePanel": "s9200c1_selfUpdatePanel",
|
|
8483
|
+
"expansionNote": "sf4cb4b_expansionNote",
|
|
8484
|
+
"staleBadge": "sc6f44e_staleBadge",
|
|
8485
|
+
"categoryBar": "sce6c31_categoryBar",
|
|
8486
|
+
"categoryButton": "safdbbe_categoryButton",
|
|
8487
|
+
"categoryButtonOn": "sc5d852_categoryButtonOn",
|
|
8488
|
+
"incompatibleFilter": "sd6d2c7_incompatibleFilter",
|
|
8489
|
+
"switch": "sc8dc92_switch",
|
|
8490
|
+
"switchOn": "s12653b_switchOn",
|
|
8491
|
+
"incompatibleFilterOn": "s515b4e_incompatibleFilterOn",
|
|
8492
|
+
"switchKnob": "sb7170f_switchKnob",
|
|
8493
|
+
"updatableHeading": "s9964fe_updatableHeading",
|
|
8494
|
+
"catalogStatsRow": "sc7794b_catalogStatsRow",
|
|
8495
|
+
"sortGroup": "s28aa3c_sortGroup",
|
|
8496
|
+
"sortLabel": "s7842d7_sortLabel",
|
|
8497
|
+
"catalogStats": "s28d92b_catalogStats",
|
|
8498
|
+
"catalogRefreshButton": "s695d80_catalogRefreshButton",
|
|
8499
|
+
"catalogRefreshFailed": "s7a8ca3_catalogRefreshFailed",
|
|
8500
|
+
"emptyLine": "s23625a_emptyLine",
|
|
8501
|
+
"cards": "s36d340_cards",
|
|
8502
|
+
"card": "sf5fa2b_card",
|
|
8503
|
+
"cardsSentry": "s584a60_cardsSentry",
|
|
8504
|
+
"showingLine": "s9d9bba_showingLine",
|
|
8505
|
+
"cardSpine": "s28d979_cardSpine",
|
|
8506
|
+
"categoryBadge": "s91829d_categoryBadge",
|
|
8507
|
+
"cardIcon": "s7f7e1e_cardIcon",
|
|
8508
|
+
"entryIcon": "s9d98a4_entryIcon",
|
|
8509
|
+
"entryIconImage": "sac0d07_entryIconImage",
|
|
8510
|
+
"entryHeader": "s6a017b_entryHeader",
|
|
8511
|
+
"entryHeading": "sbc36fc_entryHeading",
|
|
8512
|
+
"name": "s4485fd_name",
|
|
8513
|
+
"badges": "s150e7a_badges",
|
|
8514
|
+
"cardActions": "sbdfc2c_cardActions",
|
|
8515
|
+
"gate": "s35ae56_gate",
|
|
8516
|
+
"installPanel": "s75cfe7_installPanel",
|
|
8517
|
+
"installedActions": "s130990_installedActions",
|
|
8518
|
+
"receipt": "s848d91_receipt",
|
|
8519
|
+
"cardVersion": "se4fd96_cardVersion",
|
|
8520
|
+
"sourceBadge": "s4ebdb5_sourceBadge",
|
|
8521
|
+
"tierBadge": "sa10256_tierBadge",
|
|
8522
|
+
"incompatibleBadge": "se63699_incompatibleBadge",
|
|
8523
|
+
"starsBadge": "scc86dc_starsBadge",
|
|
8524
|
+
"chevron": "sd8ede4_chevron",
|
|
8525
|
+
"chevronOpen": "s879a81_chevronOpen",
|
|
8526
|
+
"body": "s2900a7_body",
|
|
8527
|
+
"summary": "sa41615_summary",
|
|
8528
|
+
"summaryZh": "s91610a_summaryZh",
|
|
8529
|
+
"summaryExpanded": "s81bc49_summaryExpanded",
|
|
8530
|
+
"summaryZhExpanded": "sc64654_summaryZhExpanded",
|
|
8531
|
+
"incompatibleDetail": "sfcd9cd_incompatibleDetail",
|
|
8532
|
+
"remedy": "sb6142c_remedy",
|
|
8533
|
+
"foldBlock": "s445a81_foldBlock",
|
|
8534
|
+
"foldBlockExpanded": "sb6457b_foldBlockExpanded",
|
|
8535
|
+
"foldToggle": "sd9b244_foldToggle",
|
|
8536
|
+
"remedyLead": "s29ecbc_remedyLead",
|
|
8537
|
+
"allowCommand": "se34c39_allowCommand",
|
|
8538
|
+
"refusal": "sb88eac_refusal",
|
|
8539
|
+
"capabilitiesBlock": "seda8cf_capabilitiesBlock",
|
|
8540
|
+
"capabilitiesNote": "s85ae81_capabilitiesNote",
|
|
8541
|
+
"capabilities": "se9f845_capabilities",
|
|
8542
|
+
"detail": "sa099e4_detail",
|
|
8543
|
+
"detailRows": "s33e230_detailRows",
|
|
8544
|
+
"detailRow": "s3c9f5e_detailRow",
|
|
8545
|
+
"cardMeta": "sf02a91_cardMeta",
|
|
8546
|
+
"shelfDate": "s968579_shelfDate",
|
|
8547
|
+
"size": "s7859cb_size",
|
|
8548
|
+
"author": "s4801d5_author",
|
|
8549
|
+
"reviewedLine": "sfa18da_reviewedLine",
|
|
8550
|
+
"installButton": "s17e180_installButton",
|
|
8551
|
+
"confirmButton": "s4d480e_confirmButton",
|
|
8552
|
+
"cancelButton": "s9466ff_cancelButton",
|
|
8553
|
+
"installedLabel": "sb6cbde_installedLabel",
|
|
8554
|
+
"refusedLabel": "s6d82dc_refusedLabel",
|
|
8555
|
+
"uninstallButton": "safa1f4_uninstallButton",
|
|
8556
|
+
"restartSelfButton": "s19d2da_restartSelfButton",
|
|
8557
|
+
"restartButton": "s1916c4_restartButton",
|
|
8558
|
+
"reloadButton": "se4b67d_reloadButton",
|
|
8559
|
+
"offerActions": "s860bef_offerActions",
|
|
8560
|
+
"reloadPanel": "se6345a_reloadPanel",
|
|
8561
|
+
"gateTitle": "s4b04c5_gateTitle",
|
|
8562
|
+
"gateBody": "s3552a4_gateBody",
|
|
8563
|
+
"gateWarning": "sf344ec_gateWarning",
|
|
8564
|
+
"gateActions": "sba54c1_gateActions",
|
|
8565
|
+
"installing": "s110c4a_installing",
|
|
8566
|
+
"log": "sa13cb7_log",
|
|
8567
|
+
"logLine": "s326167_logLine",
|
|
8568
|
+
"notice": "s88fa30_notice",
|
|
8569
|
+
"failedHeading": "s34c383_failedHeading",
|
|
8570
|
+
"failedDetail": "s7d7ac6_failedDetail",
|
|
8571
|
+
"rejectedCode": "scf1455_rejectedCode",
|
|
8572
|
+
"rejectedDetail": "s604bbc_rejectedDetail",
|
|
8573
|
+
"outdatedSection": "s81e51c_outdatedSection",
|
|
8574
|
+
"outdatedList": "s6f80ac_outdatedList",
|
|
8575
|
+
"outdatedRow": "s7e8f68_outdatedRow",
|
|
8576
|
+
"outdatedInfo": "s27f8b7_outdatedInfo",
|
|
8577
|
+
"outdatedText": "sf16e21_outdatedText",
|
|
8578
|
+
"outdatedVersions": "sb74cf7_outdatedVersions",
|
|
8579
|
+
"shadowedSection": "s4da305_shadowedSection",
|
|
8580
|
+
"shadowedDetail": "sf1d53c_shadowedDetail",
|
|
8581
|
+
"outdatedActions": "s43bdbf_outdatedActions",
|
|
8582
|
+
"switchWrap": "see584d_switchWrap"
|
|
8550
8583
|
};
|
|
8551
8584
|
//#endregion
|
|
8552
8585
|
//#region src/client/ShopDetailAction.tsx
|
|
@@ -8606,7 +8639,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8606
8639
|
if (name === null || !canOpen()) return null;
|
|
8607
8640
|
if (own) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8608
8641
|
type: "button",
|
|
8609
|
-
className:
|
|
8642
|
+
className: _dsh_plugin_shop_css_bc430a83_default.detailAction,
|
|
8610
8643
|
"data-shop-open-shop": true,
|
|
8611
8644
|
onClick: openShop,
|
|
8612
8645
|
children: t("openShop")
|
|
@@ -8615,7 +8648,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8615
8648
|
const only = listed.keys.length === 1 ? listed.keys[0] ?? null : null;
|
|
8616
8649
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8617
8650
|
type: "button",
|
|
8618
|
-
className:
|
|
8651
|
+
className: _dsh_plugin_shop_css_bc430a83_default.detailAction,
|
|
8619
8652
|
"data-shop-view-in-shop": true,
|
|
8620
8653
|
onClick: () => {
|
|
8621
8654
|
page.focus(name, only);
|
|
@@ -9934,13 +9967,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
9934
9967
|
const { rootRef, expandable, expanded, toggle } = useFoldableRedBlock();
|
|
9935
9968
|
const kindName = attrName.replace(/^data-shop-/, "");
|
|
9936
9969
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
9937
|
-
className: expanded ?
|
|
9970
|
+
className: expanded ? _dsh_plugin_shop_css_bc430a83_default.foldBlockExpanded : _dsh_plugin_shop_css_bc430a83_default.foldBlock,
|
|
9938
9971
|
[attrName]: true,
|
|
9939
9972
|
ref: rootRef,
|
|
9940
9973
|
children
|
|
9941
9974
|
}), expandable && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9942
9975
|
type: "button",
|
|
9943
|
-
className:
|
|
9976
|
+
className: _dsh_plugin_shop_css_bc430a83_default.foldToggle,
|
|
9944
9977
|
"data-shop-fold": kindName,
|
|
9945
9978
|
"aria-expanded": expanded,
|
|
9946
9979
|
onClick: toggle,
|
|
@@ -9948,6 +9981,49 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
9948
9981
|
})] });
|
|
9949
9982
|
}
|
|
9950
9983
|
//#endregion
|
|
9984
|
+
//#region src/client/EntryIcon.tsx
|
|
9985
|
+
/** The artwork an entry without a usable icon draws: dsh's own default,
|
|
9986
|
+
* `PluginArtworkDefault` from `dsh-client-ui-primitives` (MIT, DeepSeek — see
|
|
9987
|
+
* the dsh repository's LICENSE). Copied rather than imported because
|
|
9988
|
+
* 0.1.5-rc.3 exports nothing this build could borrow at run time.
|
|
9989
|
+
*
|
|
9990
|
+
* A constant data URL drawn through an `<img>`, not inline markup: the SVG
|
|
9991
|
+
* carries a gradient addressed by id, so thousands of inline copies would each
|
|
9992
|
+
* need one, while an image document is its own namespace and needs none
|
|
9993
|
+
* (design 2026-10-23-shop-plugin-icons §8.1). The id inside this string is
|
|
9994
|
+
* fixed for the same reason.
|
|
9995
|
+
*/
|
|
9996
|
+
const DEFAULT_ICON_DATA_URL = "data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMzYiIGhlaWdodD0iMzYiIHZpZXdCb3g9IjAgMCAzNiAzNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJNMjQuNjI5NCA4LjYzNjk2QzI2LjI4NjIgOC42MzcwNSAyNy42Mjk0IDkuOTgwMTYgMjcuNjI5NCAxMS42MzdWMTIuNzgyNUMyNy42MjkyIDE0LjQzOTEgMjYuMjg2MSAxNS43ODI0IDI0LjYyOTQgMTUuNzgyNUgyMy40ODM5QzIyLjY1MTkgMTUuNzgyNSAyMS41NDU0IDE2LjM0NTkgMjEuNTQ1NCAxNy4xNzc4VjE4LjE1NzNDMjEuNTQ1NCAxOC43NzU3IDIyLjIxNiAxOS4xOTY2IDIyLjgzNDUgMTkuMTk2NUgyNC42Mjk0QzI2LjI4NjEgMTkuMTk2NSAyNy42MjkyIDIwLjUzOTggMjcuNjI5NCAyMi4xOTY1VjIzLjk5MjRDMjcuNjI5MiAyNS42NDkxIDI2LjI4NjEgMjYuOTkyNCAyNC42Mjk0IDI2Ljk5MjRIMjIuODM0NUMyMS4xNzc4IDI2Ljk5MjQgMTkuODM0NyAyNS42NDkgMTkuODM0NSAyMy45OTI0VjIyLjE5NjVDMTkuODM0NSAyMS43MTQ4IDE5LjQ5NTcgMjEuMjMyNyAxOS4wMTQgMjEuMjMyN0gxNi40NzkyQzE1Ljc5NzUgMjEuMjMyNyAxNS4zMzc0IDIyLjAwNjEgMTUuMzM3NCAyMi42ODc3VjIzLjgzMzNDMTUuMzM3MyAyNS40OSAxMy45OTQyIDI2LjgzMzMgMTIuMzM3NCAyNi44MzMzSDExLjE5MTlDOS41MzUwOSAyNi44MzMzIDguMTkxOTggMjUuNDkgOC4xOTE4OSAyMy44MzMzVjIyLjY4NzdDOC4xOTE4OSAyMS4wMzA5IDkuNTM1MDQgMTkuNjg3NyAxMS4xOTE5IDE5LjY4NzdIMTIuMzM3NEMxMy4xOTY0IDE5LjY4NzcgMTQuMzk5OSAxOS4wOTU5IDE0LjM5OTkgMTguMjM2OVYxNi4wMTg1QzE0LjM5OTkgMTQuOTUxOCAxNS4yNjQ2IDE0LjA4NzIgMTYuMzMxMiAxNC4wODcySDE5LjQzNUMyMC4wNTk2IDE0LjA4NzIgMjAuNDg0IDEzLjQwNzEgMjAuNDgzOSAxMi43ODI1VjExLjYzN0MyMC40ODM5IDkuOTgwMTEgMjEuODI3IDguNjM2OTYgMjMuNDgzOSA4LjYzNjk2SDI0LjYyOTRaTTEzLjQxMTYgMTEuMjQ2OEMxMy40MTE2IDEyLjY4ODIgMTIuMjQzMSAxMy44NTY3IDEwLjgwMTggMTMuODU2N0M5LjM2MDM3IDEzLjg1NjcgOC4xOTE4OSAxMi42ODgyIDguMTkxODkgMTEuMjQ2OEM4LjE5MTg5IDkuODA1NDQgOS4zNjAzNyA4LjYzNjk2IDEwLjgwMTggOC42MzY5NkMxMi4yNDMxIDguNjM2OTYgMTMuNDExNiA5LjgwNTQ0IDEzLjQxMTYgMTEuMjQ2OFoiIGZpbGw9InVybCgjYSkiLz48ZGVmcz48bGluZWFyR3JhZGllbnQgaWQ9ImEiIHgxPSIxNS4xNDgxIiB5MT0iOS45NDE4OCIgeDI9IjE1LjE0ODEiIHkyPSIxMy42Mzc1IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agc3RvcC1jb2xvcj0iIzU0RUNFNyIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzY1OEVGRiIvPjwvbGluZWFyR3JhZGllbnQ+PC9kZWZzPjwvc3ZnPg==";
|
|
9997
|
+
/** One entry's icon: a 32px tile holding a 24px image (design §8.3).
|
|
9998
|
+
*
|
|
9999
|
+
* An author's icon reaches the DOM only here, as this `<img>`'s `src` — a
|
|
10000
|
+
* `data:` URL built from bytes the catalog carried. Never `innerHTML`, never
|
|
10001
|
+
* an inline `<svg>`: an image document runs no script and loads nothing
|
|
10002
|
+
* external, and that is the whole defence for an SVG (§8.5).
|
|
10003
|
+
*
|
|
10004
|
+
* `onError` swaps a source that will not decode for the default, as dsh's own
|
|
10005
|
+
* `PackageArtwork` does. The failed source, not a boolean, is remembered, so a
|
|
10006
|
+
* later entry reusing this component instance still tries its own icon.
|
|
10007
|
+
*/
|
|
10008
|
+
function EntryIcon({ icon }) {
|
|
10009
|
+
const [failed, setFailed] = (0, react.useState)(void 0);
|
|
10010
|
+
const src = icon === void 0 || icon === failed ? DEFAULT_ICON_DATA_URL : icon;
|
|
10011
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10012
|
+
className: _dsh_plugin_shop_css_bc430a83_default.entryIcon,
|
|
10013
|
+
"aria-hidden": "true",
|
|
10014
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
|
|
10015
|
+
className: _dsh_plugin_shop_css_bc430a83_default.entryIconImage,
|
|
10016
|
+
src,
|
|
10017
|
+
width: 24,
|
|
10018
|
+
height: 24,
|
|
10019
|
+
alt: "",
|
|
10020
|
+
onError: () => {
|
|
10021
|
+
setFailed(icon);
|
|
10022
|
+
}
|
|
10023
|
+
})
|
|
10024
|
+
});
|
|
10025
|
+
}
|
|
10026
|
+
//#endregion
|
|
9951
10027
|
//#region src/client/ShopTab.tsx
|
|
9952
10028
|
/** The shop tab: browse the catalog, detail an entry, install on one click,
|
|
9953
10029
|
* poll the install to its terminal state (§7.2).
|
|
@@ -9962,14 +10038,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
9962
10038
|
* those nodes, and React's next commit throws on the ones it no longer finds
|
|
9963
10039
|
* (design 2026-09-26-market-borrowings §4). The tab renders no portals, so its
|
|
9964
10040
|
* root is its whole surface. */
|
|
9965
|
-
const PANEL_ROOT = `${
|
|
10041
|
+
const PANEL_ROOT = `${_dsh_plugin_shop_css_bc430a83_default.panel} notranslate`;
|
|
9966
10042
|
function ChevronIcon({ open }) {
|
|
9967
10043
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
9968
10044
|
viewBox: "0 0 16 16",
|
|
9969
10045
|
width: "14",
|
|
9970
10046
|
height: "14",
|
|
9971
10047
|
"aria-hidden": "true",
|
|
9972
|
-
className: open ?
|
|
10048
|
+
className: open ? _dsh_plugin_shop_css_bc430a83_default.chevronOpen : _dsh_plugin_shop_css_bc430a83_default.chevron,
|
|
9973
10049
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
9974
10050
|
fill: "currentColor",
|
|
9975
10051
|
d: "M3.5 6 8 10.5 12.5 6l1 1L8 12.5 2.5 7l1-1Z"
|
|
@@ -9982,7 +10058,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
9982
10058
|
* install controls. An installed plugin's card carries its installed row:
|
|
9983
10059
|
* current → the non-interactive installed label, behind → the update button;
|
|
9984
10060
|
* 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 }) {
|
|
10061
|
+
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
10062
|
const key = entryKey(entry);
|
|
9987
10063
|
const open = (0, react.useSyncExternalStore)(page.subscribeExpanded, () => page.isExpanded(key));
|
|
9988
10064
|
const [, rerender] = (0, react.useReducer)((count) => count + 1, 0);
|
|
@@ -10002,102 +10078,110 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10002
10078
|
repo: entry.repo,
|
|
10003
10079
|
subdir: entry.subdir
|
|
10004
10080
|
};
|
|
10081
|
+
const uninstalling = uninstallFlow.view.kind === "running";
|
|
10082
|
+
const removed = uninstallFlow.view.kind === "done";
|
|
10005
10083
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10006
|
-
className:
|
|
10084
|
+
className: _dsh_plugin_shop_css_bc430a83_default.card,
|
|
10007
10085
|
"data-shop-entry": entry.name,
|
|
10008
10086
|
"data-category": category,
|
|
10009
10087
|
children: [
|
|
10010
10088
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10011
|
-
className:
|
|
10089
|
+
className: _dsh_plugin_shop_css_bc430a83_default.cardSpine,
|
|
10012
10090
|
"aria-hidden": "true"
|
|
10013
10091
|
}),
|
|
10014
10092
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
10015
10093
|
type: "button",
|
|
10016
|
-
className:
|
|
10094
|
+
className: _dsh_plugin_shop_css_bc430a83_default.entryHeader,
|
|
10017
10095
|
"aria-expanded": open,
|
|
10018
10096
|
"aria-controls": detailId,
|
|
10019
10097
|
onClick: () => {
|
|
10020
10098
|
page.toggleExpanded(key);
|
|
10021
10099
|
rerender();
|
|
10022
10100
|
},
|
|
10023
|
-
children: [
|
|
10024
|
-
|
|
10025
|
-
|
|
10026
|
-
|
|
10027
|
-
|
|
10028
|
-
|
|
10029
|
-
|
|
10030
|
-
|
|
10031
|
-
|
|
10032
|
-
|
|
10033
|
-
|
|
10034
|
-
|
|
10035
|
-
|
|
10036
|
-
|
|
10037
|
-
|
|
10038
|
-
|
|
10039
|
-
|
|
10040
|
-
|
|
10041
|
-
|
|
10042
|
-
|
|
10043
|
-
|
|
10044
|
-
|
|
10045
|
-
|
|
10046
|
-
|
|
10101
|
+
children: [
|
|
10102
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryIcon, { icon }),
|
|
10103
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
10104
|
+
className: _dsh_plugin_shop_css_bc430a83_default.entryHeading,
|
|
10105
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10106
|
+
className: _dsh_plugin_shop_css_bc430a83_default.name,
|
|
10107
|
+
children: entry.name
|
|
10108
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
10109
|
+
className: _dsh_plugin_shop_css_bc430a83_default.badges,
|
|
10110
|
+
children: [
|
|
10111
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10112
|
+
className: _dsh_plugin_shop_css_bc430a83_default.categoryBadge,
|
|
10113
|
+
children: t(categoryKey(entry))
|
|
10114
|
+
}),
|
|
10115
|
+
entry.source === "npm" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10116
|
+
className: _dsh_plugin_shop_css_bc430a83_default.sourceBadge,
|
|
10117
|
+
"data-shop-source-npm": true,
|
|
10118
|
+
role: "img",
|
|
10119
|
+
"aria-label": t("npmSource"),
|
|
10120
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
10121
|
+
viewBox: "0 0 16 16",
|
|
10122
|
+
width: "12",
|
|
10123
|
+
height: "12",
|
|
10124
|
+
"aria-hidden": "true",
|
|
10125
|
+
fill: "currentColor",
|
|
10126
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
10127
|
+
fillRule: "evenodd",
|
|
10128
|
+
d: "M0 0v16h16V0H0zm13 13h-2V6H8v7H3V3h10v10z"
|
|
10129
|
+
})
|
|
10130
|
+
})
|
|
10131
|
+
}),
|
|
10132
|
+
hasGithubHome(entry) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10133
|
+
className: _dsh_plugin_shop_css_bc430a83_default.sourceBadge,
|
|
10134
|
+
"data-shop-source-github": true,
|
|
10135
|
+
role: "img",
|
|
10136
|
+
"aria-label": t("githubSource"),
|
|
10137
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
10138
|
+
viewBox: "0 0 16 16",
|
|
10139
|
+
width: "12",
|
|
10140
|
+
height: "12",
|
|
10141
|
+
"aria-hidden": "true",
|
|
10142
|
+
fill: "currentColor",
|
|
10143
|
+
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" })
|
|
10144
|
+
})
|
|
10145
|
+
}),
|
|
10146
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10147
|
+
className: _dsh_plugin_shop_css_bc430a83_default.cardVersion,
|
|
10148
|
+
"data-card-version": true,
|
|
10149
|
+
role: "img",
|
|
10150
|
+
"aria-label": versionName,
|
|
10151
|
+
title: versionName,
|
|
10152
|
+
children: version.text
|
|
10153
|
+
}),
|
|
10154
|
+
entry.tier !== "community" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10155
|
+
className: _dsh_plugin_shop_css_bc430a83_default.tierBadge,
|
|
10156
|
+
"data-tier": entry.tier,
|
|
10157
|
+
children: t(tierKey(entry.tier))
|
|
10158
|
+
}),
|
|
10159
|
+
entry.archived === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10160
|
+
className: _dsh_plugin_shop_css_bc430a83_default.tierBadge,
|
|
10161
|
+
"data-tier": "archived",
|
|
10162
|
+
children: t("archivedBadge")
|
|
10163
|
+
}),
|
|
10164
|
+
stars !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
10165
|
+
className: _dsh_plugin_shop_css_bc430a83_default.starsBadge,
|
|
10166
|
+
role: "img",
|
|
10167
|
+
"aria-label": t("stars", { count: stars }),
|
|
10168
|
+
children: ["★ ", formatStars(stars)]
|
|
10047
10169
|
})
|
|
10048
|
-
|
|
10049
|
-
})
|
|
10050
|
-
|
|
10051
|
-
|
|
10052
|
-
|
|
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
|
-
})]
|
|
10170
|
+
]
|
|
10171
|
+
})]
|
|
10172
|
+
}),
|
|
10173
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChevronIcon, { open })
|
|
10174
|
+
]
|
|
10091
10175
|
}),
|
|
10092
10176
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10093
|
-
className:
|
|
10177
|
+
className: _dsh_plugin_shop_css_bc430a83_default.body,
|
|
10094
10178
|
children: [
|
|
10095
10179
|
summary !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10096
|
-
className: open ? `${
|
|
10180
|
+
className: open ? `${_dsh_plugin_shop_css_bc430a83_default.summary} ${_dsh_plugin_shop_css_bc430a83_default.summaryExpanded}` : _dsh_plugin_shop_css_bc430a83_default.summary,
|
|
10097
10181
|
children: summary.en
|
|
10098
10182
|
}),
|
|
10099
10183
|
summary?.zh !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10100
|
-
className: open ? `${
|
|
10184
|
+
className: open ? `${_dsh_plugin_shop_css_bc430a83_default.summaryZh} ${_dsh_plugin_shop_css_bc430a83_default.summaryZhExpanded}` : _dsh_plugin_shop_css_bc430a83_default.summaryZh,
|
|
10101
10185
|
children: summary.zh
|
|
10102
10186
|
}),
|
|
10103
10187
|
blockers.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FoldableRedBlock, {
|
|
@@ -10105,28 +10189,28 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10105
10189
|
attrName: "data-shop-remedy",
|
|
10106
10190
|
children: blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
|
|
10107
10191
|
blocker,
|
|
10108
|
-
className:
|
|
10192
|
+
className: _dsh_plugin_shop_css_bc430a83_default.incompatibleDetail,
|
|
10109
10193
|
surface: "detail"
|
|
10110
10194
|
}, blocker.kind))
|
|
10111
10195
|
}),
|
|
10112
10196
|
open && entry.catalog !== void 0 && entry.catalog.capabilities.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10113
|
-
className:
|
|
10197
|
+
className: _dsh_plugin_shop_css_bc430a83_default.capabilitiesBlock,
|
|
10114
10198
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10115
|
-
className:
|
|
10199
|
+
className: _dsh_plugin_shop_css_bc430a83_default.capabilitiesNote,
|
|
10116
10200
|
children: t("capabilitiesNote")
|
|
10117
10201
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
10118
|
-
className:
|
|
10202
|
+
className: _dsh_plugin_shop_css_bc430a83_default.capabilities,
|
|
10119
10203
|
children: entry.catalog.capabilities.map((capability) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: capability }, capability))
|
|
10120
10204
|
})]
|
|
10121
10205
|
}),
|
|
10122
10206
|
open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
10123
10207
|
id: detailId,
|
|
10124
|
-
className:
|
|
10208
|
+
className: _dsh_plugin_shop_css_bc430a83_default.detail,
|
|
10125
10209
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
|
|
10126
|
-
className:
|
|
10210
|
+
className: _dsh_plugin_shop_css_bc430a83_default.detailRows,
|
|
10127
10211
|
children: [
|
|
10128
10212
|
npmUrl !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10129
|
-
className:
|
|
10213
|
+
className: _dsh_plugin_shop_css_bc430a83_default.detailRow,
|
|
10130
10214
|
"data-shop-npm": true,
|
|
10131
10215
|
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
10216
|
href: npmUrl,
|
|
@@ -10136,7 +10220,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10136
10220
|
}) })]
|
|
10137
10221
|
}),
|
|
10138
10222
|
entry.repository !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10139
|
-
className:
|
|
10223
|
+
className: _dsh_plugin_shop_css_bc430a83_default.detailRow,
|
|
10140
10224
|
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
10225
|
href: entry.repository,
|
|
10142
10226
|
target: "_blank",
|
|
@@ -10145,12 +10229,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10145
10229
|
}) : entry.repository })]
|
|
10146
10230
|
}),
|
|
10147
10231
|
entry.license !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10148
|
-
className:
|
|
10232
|
+
className: _dsh_plugin_shop_css_bc430a83_default.detailRow,
|
|
10149
10233
|
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
10234
|
})
|
|
10151
10235
|
]
|
|
10152
10236
|
}), entry.tier === "verified-stale" && entry.review !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10153
|
-
className:
|
|
10237
|
+
className: _dsh_plugin_shop_css_bc430a83_default.reviewedLine,
|
|
10154
10238
|
children: entry.review.reviewedVersion !== void 0 ? t("reviewedVersionLine", {
|
|
10155
10239
|
reviewed: versionLabel("npm", entry.review.reviewedVersion).text,
|
|
10156
10240
|
current: version.text
|
|
@@ -10163,9 +10247,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10163
10247
|
]
|
|
10164
10248
|
}),
|
|
10165
10249
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10166
|
-
className:
|
|
10250
|
+
className: _dsh_plugin_shop_css_bc430a83_default.cardActions,
|
|
10167
10251
|
"data-shop-actions": true,
|
|
10168
|
-
children: [installed === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
10252
|
+
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, {
|
|
10253
|
+
name: entry.name,
|
|
10254
|
+
t,
|
|
10255
|
+
restart,
|
|
10256
|
+
restartBlocked,
|
|
10257
|
+
reload,
|
|
10258
|
+
flow: uninstallFlow
|
|
10259
|
+
}, "uninstall"), !inInstalledView && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
10169
10260
|
target: installTarget,
|
|
10170
10261
|
tier: entry.tier,
|
|
10171
10262
|
blockers,
|
|
@@ -10175,18 +10266,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10175
10266
|
restart,
|
|
10176
10267
|
restartBlocked,
|
|
10177
10268
|
reload
|
|
10178
|
-
}
|
|
10179
|
-
|
|
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, {
|
|
10269
|
+
}, "install")] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
10270
|
+
removed ? null : installed.outdated || flow.view.kind !== "idle" && !(uninstalling && flow.view.kind !== "running") ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
10187
10271
|
target: installTarget,
|
|
10188
10272
|
tier: entry.tier,
|
|
10189
10273
|
variant: installed.outdated ? "update" : "install",
|
|
10274
|
+
locked: uninstalling,
|
|
10190
10275
|
blockers,
|
|
10191
10276
|
blockersStated: true,
|
|
10192
10277
|
flow,
|
|
@@ -10194,15 +10279,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10194
10279
|
restart,
|
|
10195
10280
|
restartBlocked,
|
|
10196
10281
|
reload
|
|
10197
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(
|
|
10198
|
-
className:
|
|
10282
|
+
}, "install") : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10283
|
+
className: _dsh_plugin_shop_css_bc430a83_default.installedLabel,
|
|
10199
10284
|
"data-shop-installed": true,
|
|
10200
10285
|
children: t("installed")
|
|
10201
10286
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerBadge, {
|
|
10202
10287
|
blockers,
|
|
10203
10288
|
t
|
|
10204
|
-
})] }),
|
|
10205
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EnabledSwitch, {
|
|
10289
|
+
})] }, "installed"),
|
|
10290
|
+
!removed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EnabledSwitch, {
|
|
10206
10291
|
row: installed,
|
|
10207
10292
|
surface: "card",
|
|
10208
10293
|
page,
|
|
@@ -10210,8 +10295,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10210
10295
|
setEnabled,
|
|
10211
10296
|
restart,
|
|
10212
10297
|
restartBlocked,
|
|
10213
|
-
reload
|
|
10214
|
-
|
|
10298
|
+
reload,
|
|
10299
|
+
locked: uninstalling
|
|
10300
|
+
}, "switch"),
|
|
10215
10301
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
|
|
10216
10302
|
name: entry.name,
|
|
10217
10303
|
t,
|
|
@@ -10219,17 +10305,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10219
10305
|
restartBlocked,
|
|
10220
10306
|
reload,
|
|
10221
10307
|
flow: uninstallFlow
|
|
10222
|
-
})
|
|
10308
|
+
}, "uninstall")
|
|
10223
10309
|
] }), (size !== void 0 || author !== null || shelfDay !== void 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
10224
|
-
className:
|
|
10310
|
+
className: _dsh_plugin_shop_css_bc430a83_default.cardMeta,
|
|
10225
10311
|
children: [
|
|
10226
10312
|
shelfDay !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10227
|
-
className:
|
|
10313
|
+
className: _dsh_plugin_shop_css_bc430a83_default.shelfDate,
|
|
10228
10314
|
"data-shop-shelf-date": true,
|
|
10229
10315
|
children: t(order === "added" ? "listedOn" : "updatedOn", { date: shelfDay })
|
|
10230
10316
|
}),
|
|
10231
10317
|
size !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10232
|
-
className:
|
|
10318
|
+
className: _dsh_plugin_shop_css_bc430a83_default.size,
|
|
10233
10319
|
"data-shop-size": true,
|
|
10234
10320
|
role: "img",
|
|
10235
10321
|
"aria-label": t("sizeLabel", { size }),
|
|
@@ -10237,7 +10323,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10237
10323
|
children: size
|
|
10238
10324
|
}),
|
|
10239
10325
|
author !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10240
|
-
className:
|
|
10326
|
+
className: _dsh_plugin_shop_css_bc430a83_default.author,
|
|
10241
10327
|
"data-shop-author": true,
|
|
10242
10328
|
children: t("authorLine", { author })
|
|
10243
10329
|
})
|
|
@@ -10287,13 +10373,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10287
10373
|
...attribute,
|
|
10288
10374
|
children: blocker.text
|
|
10289
10375
|
}), blocker.remedy !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10290
|
-
className:
|
|
10376
|
+
className: _dsh_plugin_shop_css_bc430a83_default.remedy,
|
|
10291
10377
|
"data-shop-remedy": blocker.kind,
|
|
10292
10378
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10293
|
-
className:
|
|
10379
|
+
className: _dsh_plugin_shop_css_bc430a83_default.remedyLead,
|
|
10294
10380
|
children: blocker.remedy.lead
|
|
10295
10381
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
10296
|
-
className:
|
|
10382
|
+
className: _dsh_plugin_shop_css_bc430a83_default.allowCommand,
|
|
10297
10383
|
"data-shop-allow-command": true,
|
|
10298
10384
|
children: blocker.remedy.command
|
|
10299
10385
|
})]
|
|
@@ -10331,7 +10417,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10331
10417
|
if (lead === void 0 || word === null) return null;
|
|
10332
10418
|
const detail = blockers.map((blocker) => blocker.text).join("\n");
|
|
10333
10419
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10334
|
-
className:
|
|
10420
|
+
className: _dsh_plugin_shop_css_bc430a83_default.incompatibleBadge,
|
|
10335
10421
|
"data-shop-blocker": lead.kind,
|
|
10336
10422
|
role: "img",
|
|
10337
10423
|
"aria-label": detail,
|
|
@@ -10354,8 +10440,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10354
10440
|
* keeps a second press from becoming a second RPC. The page states the
|
|
10355
10441
|
* dialog's facts nowhere since 2026-10-15 (the shop design's §7 amendment of
|
|
10356
10442
|
* that date). */
|
|
10357
|
-
function InstallPanel({ target, tier, blockers, blockersStated = false, variant = "install", flow, t, restart, restartBlocked, reload }) {
|
|
10358
|
-
const { view, start, starting, reset } = flow;
|
|
10443
|
+
function InstallPanel({ target, tier, blockers, blockersStated = false, variant = "install", locked = false, flow, t, restart, restartBlocked, reload }) {
|
|
10444
|
+
const { view: flowView, start, starting, reset } = flow;
|
|
10445
|
+
const view = locked && flowView.kind !== "running" ? { kind: "idle" } : flowView;
|
|
10359
10446
|
const request = {
|
|
10360
10447
|
...target,
|
|
10361
10448
|
acknowledged: tier === "verified" ? void 0 : true
|
|
@@ -10363,37 +10450,39 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10363
10450
|
const refused = refusesInstall(blockers);
|
|
10364
10451
|
const word = blockerBadgeKey(blockers);
|
|
10365
10452
|
if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10366
|
-
className:
|
|
10453
|
+
className: _dsh_plugin_shop_css_bc430a83_default.installPanel,
|
|
10367
10454
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10368
|
-
className:
|
|
10455
|
+
className: _dsh_plugin_shop_css_bc430a83_default.installing,
|
|
10369
10456
|
children: t(installPhaseKey(view.phase))
|
|
10370
10457
|
}), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10371
|
-
className:
|
|
10458
|
+
className: _dsh_plugin_shop_css_bc430a83_default.log,
|
|
10459
|
+
"data-shop-log": true,
|
|
10372
10460
|
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10373
|
-
className:
|
|
10461
|
+
className: _dsh_plugin_shop_css_bc430a83_default.logLine,
|
|
10374
10462
|
"data-shop-log-line": true,
|
|
10375
10463
|
children: line
|
|
10376
10464
|
}, index))
|
|
10377
10465
|
})]
|
|
10378
10466
|
});
|
|
10379
10467
|
if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10380
|
-
className:
|
|
10468
|
+
className: _dsh_plugin_shop_css_bc430a83_default.receipt,
|
|
10381
10469
|
children: [
|
|
10382
10470
|
view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10383
|
-
className:
|
|
10471
|
+
className: _dsh_plugin_shop_css_bc430a83_default.log,
|
|
10472
|
+
"data-shop-log": true,
|
|
10384
10473
|
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10385
|
-
className:
|
|
10474
|
+
className: _dsh_plugin_shop_css_bc430a83_default.logLine,
|
|
10386
10475
|
"data-shop-log-line": true,
|
|
10387
10476
|
children: line
|
|
10388
10477
|
}, index))
|
|
10389
10478
|
}),
|
|
10390
10479
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10391
|
-
className:
|
|
10480
|
+
className: _dsh_plugin_shop_css_bc430a83_default.notice,
|
|
10392
10481
|
"data-shop-restart-notice": true,
|
|
10393
10482
|
children: t(activationNoticeKey(view.activation, view.restartReason))
|
|
10394
10483
|
}),
|
|
10395
10484
|
view.detail !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10396
|
-
className:
|
|
10485
|
+
className: _dsh_plugin_shop_css_bc430a83_default.notice,
|
|
10397
10486
|
"data-shop-done-note": true,
|
|
10398
10487
|
children: view.detail
|
|
10399
10488
|
}),
|
|
@@ -10403,68 +10492,73 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10403
10492
|
restart,
|
|
10404
10493
|
restartBlocked,
|
|
10405
10494
|
reload,
|
|
10406
|
-
where: "install"
|
|
10407
|
-
|
|
10408
|
-
|
|
10409
|
-
|
|
10410
|
-
|
|
10411
|
-
|
|
10412
|
-
|
|
10413
|
-
|
|
10495
|
+
where: "install",
|
|
10496
|
+
trailing: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10497
|
+
type: "button",
|
|
10498
|
+
className: _dsh_plugin_shop_css_bc430a83_default.cancelButton,
|
|
10499
|
+
"data-shop-install-dismiss": true,
|
|
10500
|
+
onClick: reset,
|
|
10501
|
+
children: t("dismiss")
|
|
10502
|
+
})
|
|
10414
10503
|
})
|
|
10415
10504
|
]
|
|
10416
10505
|
});
|
|
10417
10506
|
if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10418
|
-
className:
|
|
10507
|
+
className: _dsh_plugin_shop_css_bc430a83_default.installPanel,
|
|
10419
10508
|
children: [
|
|
10420
10509
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10421
|
-
className:
|
|
10510
|
+
className: _dsh_plugin_shop_css_bc430a83_default.failedHeading,
|
|
10422
10511
|
children: t("installFailed")
|
|
10423
10512
|
}),
|
|
10424
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10425
|
-
className:
|
|
10513
|
+
view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10514
|
+
className: _dsh_plugin_shop_css_bc430a83_default.log,
|
|
10515
|
+
"data-shop-log": true,
|
|
10426
10516
|
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10427
|
-
className:
|
|
10517
|
+
className: _dsh_plugin_shop_css_bc430a83_default.logLine,
|
|
10428
10518
|
"data-shop-log-line": true,
|
|
10429
10519
|
children: line
|
|
10430
10520
|
}, index))
|
|
10431
10521
|
}),
|
|
10432
10522
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10433
|
-
className:
|
|
10523
|
+
className: _dsh_plugin_shop_css_bc430a83_default.failedDetail,
|
|
10434
10524
|
children: view.detail === "" ? t("installTransportFailed") : view.detail
|
|
10435
10525
|
}),
|
|
10436
|
-
|
|
10437
|
-
|
|
10438
|
-
|
|
10439
|
-
"
|
|
10440
|
-
|
|
10441
|
-
|
|
10442
|
-
|
|
10443
|
-
|
|
10444
|
-
|
|
10445
|
-
"
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
|
|
10449
|
-
|
|
10450
|
-
|
|
10526
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10527
|
+
className: _dsh_plugin_shop_css_bc430a83_default.offerActions,
|
|
10528
|
+
"data-shop-offer": true,
|
|
10529
|
+
children: refused && word !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10530
|
+
type: "button",
|
|
10531
|
+
className: _dsh_plugin_shop_css_bc430a83_default.cancelButton,
|
|
10532
|
+
"data-shop-install-dismiss": true,
|
|
10533
|
+
onClick: reset,
|
|
10534
|
+
children: t("dismiss")
|
|
10535
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10536
|
+
type: "button",
|
|
10537
|
+
className: _dsh_plugin_shop_css_bc430a83_default.installButton,
|
|
10538
|
+
"data-shop-install-retry": true,
|
|
10539
|
+
disabled: starting,
|
|
10540
|
+
onClick: () => {
|
|
10541
|
+
start(request);
|
|
10542
|
+
},
|
|
10543
|
+
children: t("retry")
|
|
10544
|
+
})
|
|
10451
10545
|
})
|
|
10452
10546
|
]
|
|
10453
10547
|
});
|
|
10454
10548
|
if (view.kind === "rejected") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10455
|
-
className:
|
|
10549
|
+
className: _dsh_plugin_shop_css_bc430a83_default.installPanel,
|
|
10456
10550
|
children: [
|
|
10457
10551
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10458
|
-
className:
|
|
10552
|
+
className: _dsh_plugin_shop_css_bc430a83_default.rejectedCode,
|
|
10459
10553
|
children: t(rejectionCodeKey(view.code))
|
|
10460
10554
|
}),
|
|
10461
10555
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10462
|
-
className:
|
|
10556
|
+
className: _dsh_plugin_shop_css_bc430a83_default.rejectedDetail,
|
|
10463
10557
|
children: view.detail
|
|
10464
10558
|
}),
|
|
10465
10559
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10466
10560
|
type: "button",
|
|
10467
|
-
className:
|
|
10561
|
+
className: _dsh_plugin_shop_css_bc430a83_default.cancelButton,
|
|
10468
10562
|
"data-shop-install-dismiss": true,
|
|
10469
10563
|
onClick: reset,
|
|
10470
10564
|
children: t("dismiss")
|
|
@@ -10474,7 +10568,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10474
10568
|
const update = variant === "update";
|
|
10475
10569
|
const detail = blockers.map((blocker) => blocker.text).join("\n");
|
|
10476
10570
|
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:
|
|
10571
|
+
className: _dsh_plugin_shop_css_bc430a83_default.refusedLabel,
|
|
10478
10572
|
"data-shop-refused": true,
|
|
10479
10573
|
"data-shop-blocker-kind": blockers[0]?.kind,
|
|
10480
10574
|
role: "img",
|
|
@@ -10486,15 +10580,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10486
10580
|
attrName: "data-shop-refusal",
|
|
10487
10581
|
children: blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
|
|
10488
10582
|
blocker,
|
|
10489
|
-
className:
|
|
10583
|
+
className: _dsh_plugin_shop_css_bc430a83_default.gateWarning,
|
|
10490
10584
|
surface: "warning"
|
|
10491
10585
|
}, blocker.kind))
|
|
10492
10586
|
})] });
|
|
10493
10587
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
10494
10588
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10495
10589
|
type: "button",
|
|
10496
|
-
className:
|
|
10497
|
-
disabled: starting,
|
|
10590
|
+
className: _dsh_plugin_shop_css_bc430a83_default.installButton,
|
|
10591
|
+
disabled: starting || locked,
|
|
10498
10592
|
...update ? { "data-shop-update": true } : { "data-shop-install": true },
|
|
10499
10593
|
onClick: () => {
|
|
10500
10594
|
start(request);
|
|
@@ -10510,7 +10604,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10510
10604
|
attrName: "data-shop-blocker-lines",
|
|
10511
10605
|
children: blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
|
|
10512
10606
|
blocker,
|
|
10513
|
-
className:
|
|
10607
|
+
className: _dsh_plugin_shop_css_bc430a83_default.gateWarning,
|
|
10514
10608
|
surface: "warning"
|
|
10515
10609
|
}, blocker.kind))
|
|
10516
10610
|
})
|
|
@@ -10525,32 +10619,34 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10525
10619
|
function UninstallPanel({ name, t, restart, restartBlocked, reload, flow }) {
|
|
10526
10620
|
const { view, start, reset } = flow;
|
|
10527
10621
|
if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10528
|
-
className:
|
|
10622
|
+
className: _dsh_plugin_shop_css_bc430a83_default.installPanel,
|
|
10529
10623
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10530
|
-
className:
|
|
10624
|
+
className: _dsh_plugin_shop_css_bc430a83_default.installing,
|
|
10531
10625
|
children: t("uninstalling")
|
|
10532
10626
|
}), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10533
|
-
className:
|
|
10627
|
+
className: _dsh_plugin_shop_css_bc430a83_default.log,
|
|
10628
|
+
"data-shop-log": true,
|
|
10534
10629
|
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10535
|
-
className:
|
|
10630
|
+
className: _dsh_plugin_shop_css_bc430a83_default.logLine,
|
|
10536
10631
|
"data-shop-log-line": true,
|
|
10537
10632
|
children: line
|
|
10538
10633
|
}, index))
|
|
10539
10634
|
})]
|
|
10540
10635
|
});
|
|
10541
10636
|
if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10542
|
-
className:
|
|
10637
|
+
className: _dsh_plugin_shop_css_bc430a83_default.receipt,
|
|
10543
10638
|
children: [
|
|
10544
10639
|
view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10545
|
-
className:
|
|
10640
|
+
className: _dsh_plugin_shop_css_bc430a83_default.log,
|
|
10641
|
+
"data-shop-log": true,
|
|
10546
10642
|
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10547
|
-
className:
|
|
10643
|
+
className: _dsh_plugin_shop_css_bc430a83_default.logLine,
|
|
10548
10644
|
"data-shop-log-line": true,
|
|
10549
10645
|
children: line
|
|
10550
10646
|
}, index))
|
|
10551
10647
|
}),
|
|
10552
10648
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10553
|
-
className:
|
|
10649
|
+
className: _dsh_plugin_shop_css_bc430a83_default.notice,
|
|
10554
10650
|
"data-shop-uninstall-done": true,
|
|
10555
10651
|
children: t(uninstallActivationNoticeKey(view.activation))
|
|
10556
10652
|
}),
|
|
@@ -10560,56 +10656,60 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10560
10656
|
restart,
|
|
10561
10657
|
restartBlocked,
|
|
10562
10658
|
reload,
|
|
10563
|
-
where: "uninstall"
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
|
|
10659
|
+
where: "uninstall",
|
|
10660
|
+
trailing: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10661
|
+
type: "button",
|
|
10662
|
+
className: _dsh_plugin_shop_css_bc430a83_default.cancelButton,
|
|
10663
|
+
"data-shop-uninstall-dismiss": true,
|
|
10664
|
+
onClick: reset,
|
|
10665
|
+
children: t("dismiss")
|
|
10666
|
+
})
|
|
10571
10667
|
})
|
|
10572
10668
|
]
|
|
10573
10669
|
});
|
|
10574
10670
|
if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10575
|
-
className:
|
|
10671
|
+
className: _dsh_plugin_shop_css_bc430a83_default.installPanel,
|
|
10576
10672
|
children: [
|
|
10577
10673
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10578
|
-
className:
|
|
10674
|
+
className: _dsh_plugin_shop_css_bc430a83_default.failedHeading,
|
|
10579
10675
|
children: t("uninstallFailed")
|
|
10580
10676
|
}),
|
|
10581
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10582
|
-
className:
|
|
10677
|
+
view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10678
|
+
className: _dsh_plugin_shop_css_bc430a83_default.log,
|
|
10679
|
+
"data-shop-log": true,
|
|
10583
10680
|
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10584
|
-
className:
|
|
10681
|
+
className: _dsh_plugin_shop_css_bc430a83_default.logLine,
|
|
10585
10682
|
"data-shop-log-line": true,
|
|
10586
10683
|
children: line
|
|
10587
10684
|
}, index))
|
|
10588
10685
|
}),
|
|
10589
10686
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10590
|
-
className:
|
|
10687
|
+
className: _dsh_plugin_shop_css_bc430a83_default.failedDetail,
|
|
10591
10688
|
children: view.detail === "" ? t("uninstallTransportFailed") : view.detail
|
|
10592
10689
|
}),
|
|
10593
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
10594
|
-
|
|
10595
|
-
|
|
10596
|
-
"
|
|
10597
|
-
|
|
10598
|
-
|
|
10599
|
-
|
|
10600
|
-
|
|
10601
|
-
|
|
10602
|
-
|
|
10603
|
-
|
|
10604
|
-
|
|
10605
|
-
|
|
10690
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10691
|
+
className: _dsh_plugin_shop_css_bc430a83_default.offerActions,
|
|
10692
|
+
"data-shop-offer": true,
|
|
10693
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10694
|
+
type: "button",
|
|
10695
|
+
className: _dsh_plugin_shop_css_bc430a83_default.uninstallButton,
|
|
10696
|
+
"data-shop-uninstall-retry": true,
|
|
10697
|
+
onClick: () => void start({ name }),
|
|
10698
|
+
children: t("retry")
|
|
10699
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10700
|
+
type: "button",
|
|
10701
|
+
className: _dsh_plugin_shop_css_bc430a83_default.cancelButton,
|
|
10702
|
+
"data-shop-uninstall-dismiss": true,
|
|
10703
|
+
onClick: reset,
|
|
10704
|
+
children: t("dismiss")
|
|
10705
|
+
})]
|
|
10606
10706
|
})
|
|
10607
10707
|
]
|
|
10608
10708
|
});
|
|
10609
10709
|
if (view.kind !== "idle") return null;
|
|
10610
10710
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10611
10711
|
type: "button",
|
|
10612
|
-
className:
|
|
10712
|
+
className: _dsh_plugin_shop_css_bc430a83_default.uninstallButton,
|
|
10613
10713
|
"data-shop-uninstall": true,
|
|
10614
10714
|
onClick: () => void start({ name }),
|
|
10615
10715
|
children: t("uninstall")
|
|
@@ -10630,7 +10730,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10630
10730
|
* period, refreshing the page once the NEW server keeps answering. A refused
|
|
10631
10731
|
* restart renders the host's published detail above the offer, which stays;
|
|
10632
10732
|
* a server that never comes back names the manual command. */
|
|
10633
|
-
function RestartPanel({ t, restart, reload, gate }) {
|
|
10733
|
+
function RestartPanel({ t, restart, reload, gate, trailing }) {
|
|
10634
10734
|
const [ownGateOpen, setOwnGateOpen] = (0, react.useState)(false);
|
|
10635
10735
|
const gateOpen = gate === void 0 ? ownGateOpen : gate.open;
|
|
10636
10736
|
const setGateOpen = (open) => {
|
|
@@ -10649,58 +10749,67 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10649
10749
|
if (outcome.kind === "refused") setRefusal(outcome.detail);
|
|
10650
10750
|
else if (outcome.kind === "unreachable") setRefusal(t("restartTransportFailed"));
|
|
10651
10751
|
};
|
|
10652
|
-
|
|
10653
|
-
className:
|
|
10752
|
+
const tail = trailing === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10753
|
+
className: _dsh_plugin_shop_css_bc430a83_default.offerActions,
|
|
10754
|
+
"data-shop-offer": true,
|
|
10755
|
+
children: trailing
|
|
10756
|
+
});
|
|
10757
|
+
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", {
|
|
10758
|
+
className: _dsh_plugin_shop_css_bc430a83_default.notice,
|
|
10654
10759
|
"data-shop-restarting": true,
|
|
10655
10760
|
children: t("restarting")
|
|
10656
|
-
});
|
|
10657
|
-
if (monitor?.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10658
|
-
className:
|
|
10761
|
+
}), tail] });
|
|
10762
|
+
if (monitor?.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10763
|
+
className: _dsh_plugin_shop_css_bc430a83_default.failedDetail,
|
|
10659
10764
|
"data-shop-restart-error": true,
|
|
10660
10765
|
children: monitor.logFile === void 0 ? t("restartFailedNotice") : t("restartFailedLogNotice", { log: monitor.logFile })
|
|
10661
|
-
});
|
|
10662
|
-
if (monitor?.kind === "not-restarted") return
|
|
10663
|
-
if (gateOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10664
|
-
className:
|
|
10766
|
+
}), tail] });
|
|
10767
|
+
if (monitor?.kind === "not-restarted") return tail;
|
|
10768
|
+
if (gateOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10769
|
+
className: _dsh_plugin_shop_css_bc430a83_default.gate,
|
|
10665
10770
|
"data-shop-restart-gate": true,
|
|
10666
10771
|
children: [
|
|
10667
10772
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10668
|
-
className:
|
|
10773
|
+
className: _dsh_plugin_shop_css_bc430a83_default.gateTitle,
|
|
10669
10774
|
children: t("restartTitle")
|
|
10670
10775
|
}),
|
|
10671
10776
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10672
|
-
className:
|
|
10777
|
+
className: _dsh_plugin_shop_css_bc430a83_default.gateBody,
|
|
10673
10778
|
children: t("restartBody")
|
|
10674
10779
|
}),
|
|
10675
10780
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10676
|
-
className:
|
|
10781
|
+
className: _dsh_plugin_shop_css_bc430a83_default.gateActions,
|
|
10677
10782
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10678
10783
|
type: "button",
|
|
10679
|
-
className:
|
|
10784
|
+
className: _dsh_plugin_shop_css_bc430a83_default.confirmButton,
|
|
10680
10785
|
"data-shop-restart-confirm": true,
|
|
10681
10786
|
onClick: () => void onConfirm(),
|
|
10682
10787
|
children: t("restartConfirm")
|
|
10683
10788
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10684
10789
|
type: "button",
|
|
10685
|
-
className:
|
|
10790
|
+
className: _dsh_plugin_shop_css_bc430a83_default.cancelButton,
|
|
10686
10791
|
onClick: () => setGateOpen(false),
|
|
10687
10792
|
children: t("cancel")
|
|
10688
10793
|
})]
|
|
10689
10794
|
})
|
|
10690
10795
|
]
|
|
10691
|
-
});
|
|
10796
|
+
}), tail] });
|
|
10692
10797
|
const refused = refusal === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10693
|
-
className:
|
|
10798
|
+
className: _dsh_plugin_shop_css_bc430a83_default.failedDetail,
|
|
10694
10799
|
"data-shop-restart-error": true,
|
|
10695
10800
|
children: refusal
|
|
10696
10801
|
});
|
|
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.
|
|
10699
|
-
|
|
10700
|
-
|
|
10701
|
-
"
|
|
10702
|
-
|
|
10703
|
-
|
|
10802
|
+
if (gate !== void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [refused, tail] });
|
|
10803
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [refused, /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10804
|
+
className: _dsh_plugin_shop_css_bc430a83_default.offerActions,
|
|
10805
|
+
"data-shop-offer": true,
|
|
10806
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10807
|
+
type: "button",
|
|
10808
|
+
className: _dsh_plugin_shop_css_bc430a83_default.restartButton,
|
|
10809
|
+
"data-shop-restart": true,
|
|
10810
|
+
onClick: () => setGateOpen(true),
|
|
10811
|
+
children: t("restart")
|
|
10812
|
+
}), trailing]
|
|
10704
10813
|
})] });
|
|
10705
10814
|
}
|
|
10706
10815
|
/**
|
|
@@ -10715,13 +10824,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10715
10824
|
*/
|
|
10716
10825
|
function NotRestartedNotice({ t, pid, logFile }) {
|
|
10717
10826
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10718
|
-
className:
|
|
10827
|
+
className: _dsh_plugin_shop_css_bc430a83_default.selfUpdatePanel,
|
|
10719
10828
|
"data-shop-not-restarted": true,
|
|
10720
10829
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10721
|
-
className:
|
|
10830
|
+
className: _dsh_plugin_shop_css_bc430a83_default.failedDetail,
|
|
10722
10831
|
children: pid === void 0 ? t("notRestartedNotice") : t("notRestartedPidNotice", { pid: String(pid) })
|
|
10723
10832
|
}), logFile !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10724
|
-
className:
|
|
10833
|
+
className: _dsh_plugin_shop_css_bc430a83_default.failedDetail,
|
|
10725
10834
|
children: t("notRestartedLogNotice", { log: logFile })
|
|
10726
10835
|
})]
|
|
10727
10836
|
});
|
|
@@ -10739,39 +10848,51 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10739
10848
|
* the `restartBlocked` gate changes, with nothing to make the second one
|
|
10740
10849
|
* fail if it were missed.
|
|
10741
10850
|
*/
|
|
10742
|
-
function ActivationOffer({ activation, t, restart, restartBlocked, reload, where }) {
|
|
10851
|
+
function ActivationOffer({ activation, t, restart, restartBlocked, reload, where, trailing }) {
|
|
10743
10852
|
if (activation === "reload") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReloadPanel, {
|
|
10744
10853
|
t,
|
|
10745
10854
|
reload,
|
|
10746
|
-
where
|
|
10855
|
+
where,
|
|
10856
|
+
trailing
|
|
10747
10857
|
});
|
|
10748
|
-
|
|
10749
|
-
|
|
10858
|
+
const tail = trailing === void 0 ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
10859
|
+
className: _dsh_plugin_shop_css_bc430a83_default.offerActions,
|
|
10860
|
+
"data-shop-offer": true,
|
|
10861
|
+
children: trailing
|
|
10862
|
+
});
|
|
10863
|
+
if (activation !== "restart") return tail;
|
|
10864
|
+
if (restartBlocked === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RestartPanel, {
|
|
10750
10865
|
t,
|
|
10751
10866
|
restart,
|
|
10752
|
-
reload
|
|
10753
|
-
|
|
10754
|
-
|
|
10867
|
+
reload,
|
|
10868
|
+
trailing
|
|
10869
|
+
});
|
|
10870
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10871
|
+
className: _dsh_plugin_shop_css_bc430a83_default.notice,
|
|
10755
10872
|
"data-shop-restart-disabled": true,
|
|
10756
10873
|
children: t(restartBlockedNoticeKey(restartBlocked))
|
|
10757
|
-
});
|
|
10874
|
+
}), tail] });
|
|
10758
10875
|
}
|
|
10759
10876
|
/** The §4 reload offer: the server already holds the new state and this tab
|
|
10760
10877
|
* does not. Never automatic — a reload discards whatever the reader had in
|
|
10761
10878
|
* flight (a conversation, a form, an upload), and the shop knows a reload
|
|
10762
10879
|
* would help without knowing that now is a good time. */
|
|
10763
|
-
function ReloadPanel({ t, reload, where }) {
|
|
10880
|
+
function ReloadPanel({ t, reload, where, trailing }) {
|
|
10764
10881
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10765
|
-
className:
|
|
10882
|
+
className: _dsh_plugin_shop_css_bc430a83_default.reloadPanel,
|
|
10766
10883
|
"data-shop-reload": where,
|
|
10767
10884
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10768
|
-
className:
|
|
10885
|
+
className: _dsh_plugin_shop_css_bc430a83_default.notice,
|
|
10769
10886
|
children: t("reloadNote")
|
|
10770
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
10771
|
-
|
|
10772
|
-
|
|
10773
|
-
|
|
10774
|
-
|
|
10887
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10888
|
+
className: _dsh_plugin_shop_css_bc430a83_default.offerActions,
|
|
10889
|
+
"data-shop-offer": true,
|
|
10890
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
10891
|
+
type: "button",
|
|
10892
|
+
className: _dsh_plugin_shop_css_bc430a83_default.reloadButton,
|
|
10893
|
+
onClick: reload,
|
|
10894
|
+
children: t("reload")
|
|
10895
|
+
}), trailing]
|
|
10775
10896
|
})]
|
|
10776
10897
|
});
|
|
10777
10898
|
}
|
|
@@ -10782,7 +10903,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10782
10903
|
* transport throw renders the localized failure line — its private detail
|
|
10783
10904
|
* (which can name hosts and ports) never reaches the UI.
|
|
10784
10905
|
*/
|
|
10785
|
-
function EnabledSwitch({ row, surface, page, t, setEnabled, restart, restartBlocked, reload }) {
|
|
10906
|
+
function EnabledSwitch({ row, surface, page, t, setEnabled, restart, restartBlocked, reload, locked = false }) {
|
|
10786
10907
|
const [enabled, setEnabledState] = (0, react.useState)(row.enabled);
|
|
10787
10908
|
const [toggle, setToggle] = (0, react.useState)({ kind: "idle" });
|
|
10788
10909
|
const owedKey = switchKey(surface, identityKey(row));
|
|
@@ -10817,7 +10938,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10817
10938
|
}
|
|
10818
10939
|
};
|
|
10819
10940
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10820
|
-
className:
|
|
10941
|
+
className: _dsh_plugin_shop_css_bc430a83_default.switchWrap,
|
|
10821
10942
|
"data-shop-enabled-switch": row.name,
|
|
10822
10943
|
children: [
|
|
10823
10944
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -10826,18 +10947,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10826
10947
|
"aria-checked": enabled,
|
|
10827
10948
|
"aria-label": t("enabledSwitch"),
|
|
10828
10949
|
"data-shop-toggle": true,
|
|
10829
|
-
className: `${
|
|
10950
|
+
className: `${_dsh_plugin_shop_css_bc430a83_default.switch} ${enabled ? _dsh_plugin_shop_css_bc430a83_default.switchOn : ""}`,
|
|
10830
10951
|
onClick: () => void onToggle(),
|
|
10831
|
-
disabled: toggle.kind === "saving",
|
|
10832
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className:
|
|
10952
|
+
disabled: toggle.kind === "saving" || locked,
|
|
10953
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_bc430a83_default.switchKnob })
|
|
10833
10954
|
}),
|
|
10834
10955
|
toggle.kind === "saved" && toggle.live && owed === "nothing" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10835
|
-
className:
|
|
10956
|
+
className: _dsh_plugin_shop_css_bc430a83_default.notice,
|
|
10836
10957
|
"data-shop-hot-apply": true,
|
|
10837
10958
|
children: t("hotApplyNote")
|
|
10838
10959
|
}),
|
|
10839
10960
|
owed === "restart" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10840
|
-
className:
|
|
10961
|
+
className: _dsh_plugin_shop_css_bc430a83_default.notice,
|
|
10841
10962
|
"data-shop-toggle-restart": true,
|
|
10842
10963
|
children: t("toggleRestartNote")
|
|
10843
10964
|
}),
|
|
@@ -10850,7 +10971,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10850
10971
|
where: "toggle"
|
|
10851
10972
|
}),
|
|
10852
10973
|
toggle.kind === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10853
|
-
className:
|
|
10974
|
+
className: _dsh_plugin_shop_css_bc430a83_default.failedDetail,
|
|
10854
10975
|
"data-shop-toggle-error": true,
|
|
10855
10976
|
children: toggle.detail
|
|
10856
10977
|
})
|
|
@@ -10863,21 +10984,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10863
10984
|
* carry a `latest` string — a catalog truth to compare against; §C10's
|
|
10864
10985
|
* shadowed rows hold `latest: null` and never reach this control, so the
|
|
10865
10986
|
* field is narrowed at the parameter. */
|
|
10866
|
-
function OutdatedRow({ row, tier, missing, harness, t, setEnabled, flowFor, restart, restartBlocked, reload, page }) {
|
|
10987
|
+
function OutdatedRow({ row, icon, tier, missing, harness, t, setEnabled, flowFor, locked, restart, restartBlocked, reload, page }) {
|
|
10867
10988
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10868
|
-
className:
|
|
10989
|
+
className: _dsh_plugin_shop_css_bc430a83_default.outdatedRow,
|
|
10869
10990
|
"data-shop-outdated-entry": row.name,
|
|
10870
10991
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10871
|
-
className:
|
|
10872
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10873
|
-
className:
|
|
10874
|
-
children:
|
|
10875
|
-
|
|
10876
|
-
|
|
10877
|
-
|
|
10992
|
+
className: _dsh_plugin_shop_css_bc430a83_default.outdatedInfo,
|
|
10993
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryIcon, { icon }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
10994
|
+
className: _dsh_plugin_shop_css_bc430a83_default.outdatedText,
|
|
10995
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
10996
|
+
className: _dsh_plugin_shop_css_bc430a83_default.name,
|
|
10997
|
+
children: row.name
|
|
10998
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
10999
|
+
className: _dsh_plugin_shop_css_bc430a83_default.outdatedVersions,
|
|
11000
|
+
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 }) })]
|
|
11001
|
+
})]
|
|
10878
11002
|
})]
|
|
10879
11003
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10880
|
-
className:
|
|
11004
|
+
className: _dsh_plugin_shop_css_bc430a83_default.outdatedActions,
|
|
10881
11005
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EnabledSwitch, {
|
|
10882
11006
|
row,
|
|
10883
11007
|
surface: "row",
|
|
@@ -10886,7 +11010,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10886
11010
|
setEnabled,
|
|
10887
11011
|
restart,
|
|
10888
11012
|
restartBlocked,
|
|
10889
|
-
reload
|
|
11013
|
+
reload,
|
|
11014
|
+
locked
|
|
10890
11015
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
10891
11016
|
target: {
|
|
10892
11017
|
name: row.name,
|
|
@@ -10897,6 +11022,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10897
11022
|
},
|
|
10898
11023
|
tier,
|
|
10899
11024
|
variant: "update",
|
|
11025
|
+
locked,
|
|
10900
11026
|
blockers: stateBlockers(blockersOf(missing, harness, void 0), t),
|
|
10901
11027
|
flow: flowFor(identityKey(row)),
|
|
10902
11028
|
t,
|
|
@@ -10926,33 +11052,35 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10926
11052
|
* community entry's does); an entry absent from the catalog is read as
|
|
10927
11053
|
* community, so its request acknowledges, which the host accepts for any
|
|
10928
11054
|
* tier. */
|
|
10929
|
-
function OutdatedSection({ state, entriesByKey, missingByKey, harnessVerdicts, t, setEnabled, flowFor, restart, restartBlocked, reload, page }) {
|
|
11055
|
+
function OutdatedSection({ state, entriesByKey, icons, missingByKey, harnessVerdicts, t, setEnabled, flowFor, uninstallFor, restart, restartBlocked, reload, page }) {
|
|
10930
11056
|
if (state.kind === "loading") return null;
|
|
10931
11057
|
if (state.kind === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
10932
|
-
className:
|
|
11058
|
+
className: _dsh_plugin_shop_css_bc430a83_default.stateLine,
|
|
10933
11059
|
"data-shop-outdated-error": true,
|
|
10934
11060
|
children: t("installedError")
|
|
10935
11061
|
});
|
|
10936
11062
|
const outdated = state.entries.filter((entry) => entry.outdated && entry.latest !== null);
|
|
10937
11063
|
if (outdated.length === 0) return null;
|
|
10938
11064
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
10939
|
-
className:
|
|
11065
|
+
className: _dsh_plugin_shop_css_bc430a83_default.outdatedSection,
|
|
10940
11066
|
"data-shop-outdated": true,
|
|
10941
11067
|
"aria-labelledby": UPDATABLE_HEADING_ID,
|
|
10942
11068
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
10943
|
-
className:
|
|
11069
|
+
className: _dsh_plugin_shop_css_bc430a83_default.updatableHeading,
|
|
10944
11070
|
id: UPDATABLE_HEADING_ID,
|
|
10945
11071
|
children: t("updatableSection")
|
|
10946
11072
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
10947
|
-
className:
|
|
11073
|
+
className: _dsh_plugin_shop_css_bc430a83_default.outdatedList,
|
|
10948
11074
|
children: outdated.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedRow, {
|
|
10949
11075
|
row,
|
|
11076
|
+
icon: iconOf(entriesByKey.get(identityKey(row)), icons),
|
|
10950
11077
|
tier: entriesByKey.get(identityKey(row))?.tier ?? "community",
|
|
10951
11078
|
missing: missingByKey.get(identityKey(row)) ?? [],
|
|
10952
11079
|
harness: harnessVerdictOf(harnessVerdicts, identityKey(row)),
|
|
10953
11080
|
t,
|
|
10954
11081
|
setEnabled,
|
|
10955
11082
|
flowFor,
|
|
11083
|
+
locked: uninstallFor(identityKey(row)).view.kind === "running" || uninstallFor(identityKey(row)).view.kind === "done",
|
|
10956
11084
|
restart,
|
|
10957
11085
|
restartBlocked,
|
|
10958
11086
|
reload,
|
|
@@ -10968,41 +11096,44 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
10968
11096
|
* naming the harness fact, and an Uninstall button as the only offered
|
|
10969
11097
|
* action: there is no catalog truth to compare against, so no Install /
|
|
10970
11098
|
* Update / Enable / Restart control makes sense. */
|
|
10971
|
-
function ShadowedSection({ entries, t, uninstallFlows, restart, restartBlocked, reload }) {
|
|
11099
|
+
function ShadowedSection({ entries, icons, entriesByKey, t, uninstallFlows, restart, restartBlocked, reload }) {
|
|
10972
11100
|
const shadowed = entries.filter((entry) => entry.shadowed === true);
|
|
10973
11101
|
if (shadowed.length === 0) return null;
|
|
10974
11102
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
|
|
10975
|
-
className:
|
|
11103
|
+
className: _dsh_plugin_shop_css_bc430a83_default.shadowedSection,
|
|
10976
11104
|
"data-shop-shadowed": true,
|
|
10977
11105
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
10978
|
-
className:
|
|
11106
|
+
className: _dsh_plugin_shop_css_bc430a83_default.outdatedList,
|
|
10979
11107
|
children: shadowed.map((row) => {
|
|
10980
11108
|
const key = identityKey(row);
|
|
10981
11109
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10982
|
-
className:
|
|
11110
|
+
className: _dsh_plugin_shop_css_bc430a83_default.outdatedRow,
|
|
10983
11111
|
"data-shop-shadowed-entry": row.name,
|
|
10984
11112
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
10985
|
-
className:
|
|
10986
|
-
children: [
|
|
10987
|
-
|
|
10988
|
-
|
|
10989
|
-
|
|
10990
|
-
|
|
10991
|
-
|
|
10992
|
-
|
|
10993
|
-
|
|
10994
|
-
className:
|
|
10995
|
-
|
|
10996
|
-
|
|
11113
|
+
className: _dsh_plugin_shop_css_bc430a83_default.outdatedInfo,
|
|
11114
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryIcon, { icon: iconOf(entriesByKey.get(identityKey(row)), icons) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
11115
|
+
className: _dsh_plugin_shop_css_bc430a83_default.outdatedText,
|
|
11116
|
+
children: [
|
|
11117
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11118
|
+
className: _dsh_plugin_shop_css_bc430a83_default.name,
|
|
11119
|
+
children: row.name
|
|
11120
|
+
}),
|
|
11121
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11122
|
+
className: _dsh_plugin_shop_css_bc430a83_default.badges,
|
|
11123
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11124
|
+
className: _dsh_plugin_shop_css_bc430a83_default.tierBadge,
|
|
11125
|
+
"data-tier": "shadowed",
|
|
11126
|
+
children: t("shadowedBadge")
|
|
11127
|
+
})
|
|
11128
|
+
}),
|
|
11129
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11130
|
+
className: _dsh_plugin_shop_css_bc430a83_default.shadowedDetail,
|
|
11131
|
+
children: t("shadowedDetail")
|
|
10997
11132
|
})
|
|
10998
|
-
|
|
10999
|
-
|
|
11000
|
-
className: _dsh_plugin_shop_css_9836f849_default.shadowedDetail,
|
|
11001
|
-
children: t("shadowedDetail")
|
|
11002
|
-
})
|
|
11003
|
-
]
|
|
11133
|
+
]
|
|
11134
|
+
})]
|
|
11004
11135
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
11005
|
-
className:
|
|
11136
|
+
className: _dsh_plugin_shop_css_bc430a83_default.outdatedActions,
|
|
11006
11137
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
|
|
11007
11138
|
name: row.name,
|
|
11008
11139
|
t,
|
|
@@ -11052,16 +11183,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11052
11183
|
role: "alert",
|
|
11053
11184
|
children: [
|
|
11054
11185
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11055
|
-
className:
|
|
11186
|
+
className: _dsh_plugin_shop_css_bc430a83_default.stateLine,
|
|
11056
11187
|
children: say("tabCrashed", en.tabCrashed)
|
|
11057
11188
|
}),
|
|
11058
11189
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11059
|
-
className:
|
|
11190
|
+
className: _dsh_plugin_shop_css_bc430a83_default.failedDetail,
|
|
11060
11191
|
children: error.message
|
|
11061
11192
|
}),
|
|
11062
11193
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
11063
11194
|
type: "button",
|
|
11064
|
-
className:
|
|
11195
|
+
className: _dsh_plugin_shop_css_bc430a83_default.actionButton,
|
|
11065
11196
|
onClick: () => this.setState({ error: null }),
|
|
11066
11197
|
children: say("retry", en.retry)
|
|
11067
11198
|
})
|
|
@@ -11277,6 +11408,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11277
11408
|
return map;
|
|
11278
11409
|
}, [installedState]);
|
|
11279
11410
|
const stars = (0, react.useMemo)(() => catalogState.kind === "ready" ? catalogState.result.stars : {}, [catalogState]);
|
|
11411
|
+
const icons = (0, react.useMemo)(() => catalogState.kind === "ready" ? catalogState.result.icons ?? {} : {}, [catalogState]);
|
|
11280
11412
|
const starOrder = (0, react.useMemo)(() => catalogState.kind === "ready" ? sortByStars(catalogState.result.plugins, stars) : [], [catalogState, stars]);
|
|
11281
11413
|
const builtAt = catalogState.kind === "ready" ? catalogState.result.builtAt : "";
|
|
11282
11414
|
const sortedCatalog = (0, react.useMemo)(() => orderShelf(starOrder, order, builtAt), [
|
|
@@ -11387,19 +11519,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11387
11519
|
"data-shop-tab": true,
|
|
11388
11520
|
"aria-busy": "true",
|
|
11389
11521
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11390
|
-
className:
|
|
11522
|
+
className: _dsh_plugin_shop_css_bc430a83_default.srOnly,
|
|
11391
11523
|
children: t("loading")
|
|
11392
11524
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
11393
|
-
className:
|
|
11525
|
+
className: _dsh_plugin_shop_css_bc430a83_default.skeletonGrid,
|
|
11394
11526
|
"data-shop-skeleton": true,
|
|
11395
11527
|
"aria-hidden": "true",
|
|
11396
11528
|
children: Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11397
|
-
className:
|
|
11529
|
+
className: _dsh_plugin_shop_css_bc430a83_default.skeletonCard,
|
|
11398
11530
|
children: [
|
|
11399
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className:
|
|
11400
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className:
|
|
11401
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className:
|
|
11402
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className:
|
|
11531
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_bc430a83_default.skeletonName }),
|
|
11532
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_bc430a83_default.skeletonSummary }),
|
|
11533
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_bc430a83_default.skeletonSummaryShort }),
|
|
11534
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_bc430a83_default.skeletonActions })
|
|
11403
11535
|
]
|
|
11404
11536
|
}, i))
|
|
11405
11537
|
})]
|
|
@@ -11409,11 +11541,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11409
11541
|
translate: "no",
|
|
11410
11542
|
"data-shop-tab": true,
|
|
11411
11543
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11412
|
-
className:
|
|
11544
|
+
className: _dsh_plugin_shop_css_bc430a83_default.stateLine,
|
|
11413
11545
|
children: t("error")
|
|
11414
11546
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
11415
11547
|
type: "button",
|
|
11416
|
-
className:
|
|
11548
|
+
className: _dsh_plugin_shop_css_bc430a83_default.actionButton,
|
|
11417
11549
|
onClick: () => {
|
|
11418
11550
|
setRequest({
|
|
11419
11551
|
kind: "retry",
|
|
@@ -11431,11 +11563,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11431
11563
|
"data-shop-tab": true,
|
|
11432
11564
|
children: [
|
|
11433
11565
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11434
|
-
className:
|
|
11566
|
+
className: _dsh_plugin_shop_css_bc430a83_default.toolbar,
|
|
11435
11567
|
children: [
|
|
11436
11568
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
11437
11569
|
type: "search",
|
|
11438
|
-
className:
|
|
11570
|
+
className: _dsh_plugin_shop_css_bc430a83_default.searchInput,
|
|
11439
11571
|
"aria-label": t("search"),
|
|
11440
11572
|
placeholder: t("search"),
|
|
11441
11573
|
value: query,
|
|
@@ -11448,31 +11580,31 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11448
11580
|
}),
|
|
11449
11581
|
expansionMemo.intents.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11450
11582
|
"data-shop-expansion-note": true,
|
|
11451
|
-
className:
|
|
11583
|
+
className: _dsh_plugin_shop_css_bc430a83_default.expansionNote,
|
|
11452
11584
|
children: t("zhExpansionNote", { terms: expansionMemo.terms.slice(1).map((term) => term.startsWith("#") ? term.slice(1) : term).join(", ") })
|
|
11453
11585
|
}),
|
|
11454
11586
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11455
|
-
className:
|
|
11587
|
+
className: _dsh_plugin_shop_css_bc430a83_default.toolbarRight,
|
|
11456
11588
|
children: [
|
|
11457
11589
|
result.stale && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11458
|
-
className:
|
|
11590
|
+
className: _dsh_plugin_shop_css_bc430a83_default.staleBadge,
|
|
11459
11591
|
children: t("staleLabel", { date: result.builtAt.slice(0, 10) })
|
|
11460
11592
|
}),
|
|
11461
11593
|
selfVersion !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11462
|
-
className:
|
|
11594
|
+
className: _dsh_plugin_shop_css_bc430a83_default.versionBlock,
|
|
11463
11595
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
11464
|
-
className:
|
|
11596
|
+
className: _dsh_plugin_shop_css_bc430a83_default.versionText,
|
|
11465
11597
|
"data-shop-version": true,
|
|
11466
11598
|
children: ["v", selfVersion.installed]
|
|
11467
11599
|
}), restartMonitor !== null ? null : selfPending && restartBlocked === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
11468
11600
|
type: "button",
|
|
11469
|
-
className:
|
|
11601
|
+
className: _dsh_plugin_shop_css_bc430a83_default.restartSelfButton,
|
|
11470
11602
|
"data-shop-restart": true,
|
|
11471
11603
|
onClick: () => setSelfRestartGate(true),
|
|
11472
11604
|
children: t("restart")
|
|
11473
11605
|
}) : !selfPending && selfVersion.outdated && selfVersion.latest !== null && selfUpdate.view.kind === "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
11474
11606
|
type: "button",
|
|
11475
|
-
className:
|
|
11607
|
+
className: _dsh_plugin_shop_css_bc430a83_default.updateSelfButton,
|
|
11476
11608
|
"data-shop-update-self": true,
|
|
11477
11609
|
onClick: () => {
|
|
11478
11610
|
if (selfVersion.latest !== null) selfUpdate.start({ version: selfVersion.latest });
|
|
@@ -11480,7 +11612,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11480
11612
|
children: t("update")
|
|
11481
11613
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
11482
11614
|
type: "button",
|
|
11483
|
-
className:
|
|
11615
|
+
className: _dsh_plugin_shop_css_bc430a83_default.checkUpdateButton,
|
|
11484
11616
|
"data-shop-check-update": true,
|
|
11485
11617
|
title: t("checkUpdateTitle"),
|
|
11486
11618
|
disabled: checkState !== "idle",
|
|
@@ -11489,7 +11621,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11489
11621
|
})]
|
|
11490
11622
|
}),
|
|
11491
11623
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
11492
|
-
className:
|
|
11624
|
+
className: _dsh_plugin_shop_css_bc430a83_default.githubLink,
|
|
11493
11625
|
href: SHOP_REPO_URL,
|
|
11494
11626
|
target: "_blank",
|
|
11495
11627
|
rel: "noopener noreferrer",
|
|
@@ -11514,28 +11646,29 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11514
11646
|
logFile: restartMonitor.logFile
|
|
11515
11647
|
}),
|
|
11516
11648
|
selfUpdate.view.kind === "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11517
|
-
className:
|
|
11649
|
+
className: _dsh_plugin_shop_css_bc430a83_default.selfUpdatePanel,
|
|
11518
11650
|
"data-shop-self-updating": true,
|
|
11519
11651
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11520
|
-
className:
|
|
11652
|
+
className: _dsh_plugin_shop_css_bc430a83_default.installing,
|
|
11521
11653
|
children: t(installPhaseKey(selfUpdate.view.phase))
|
|
11522
11654
|
}), selfUpdate.view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
11523
|
-
className:
|
|
11655
|
+
className: _dsh_plugin_shop_css_bc430a83_default.log,
|
|
11656
|
+
"data-shop-log": true,
|
|
11524
11657
|
children: selfUpdate.view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
11525
|
-
className:
|
|
11658
|
+
className: _dsh_plugin_shop_css_bc430a83_default.logLine,
|
|
11526
11659
|
"data-shop-log-line": true,
|
|
11527
11660
|
children: line
|
|
11528
11661
|
}, index))
|
|
11529
11662
|
})]
|
|
11530
11663
|
}),
|
|
11531
11664
|
selfPending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
11532
|
-
className:
|
|
11665
|
+
className: _dsh_plugin_shop_css_bc430a83_default.selfUpdatePanel,
|
|
11533
11666
|
"data-shop-self-update-done": true,
|
|
11534
11667
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11535
|
-
className:
|
|
11668
|
+
className: _dsh_plugin_shop_css_bc430a83_default.installedActions,
|
|
11536
11669
|
children: [
|
|
11537
11670
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11538
|
-
className:
|
|
11671
|
+
className: _dsh_plugin_shop_css_bc430a83_default.notice,
|
|
11539
11672
|
children: t("installedRestartNotice")
|
|
11540
11673
|
}),
|
|
11541
11674
|
restartBlocked === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RestartPanel, {
|
|
@@ -11548,7 +11681,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11548
11681
|
}
|
|
11549
11682
|
}),
|
|
11550
11683
|
restartBlocked !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11551
|
-
className:
|
|
11684
|
+
className: _dsh_plugin_shop_css_bc430a83_default.notice,
|
|
11552
11685
|
"data-shop-restart-disabled": true,
|
|
11553
11686
|
children: t(restartBlockedNoticeKey(restartBlocked))
|
|
11554
11687
|
})
|
|
@@ -11556,28 +11689,29 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11556
11689
|
})
|
|
11557
11690
|
}),
|
|
11558
11691
|
selfUpdate.view.kind === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11559
|
-
className:
|
|
11692
|
+
className: _dsh_plugin_shop_css_bc430a83_default.selfUpdatePanel,
|
|
11560
11693
|
"data-shop-self-update-failed": true,
|
|
11561
11694
|
children: [
|
|
11562
11695
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11563
|
-
className:
|
|
11696
|
+
className: _dsh_plugin_shop_css_bc430a83_default.failedHeading,
|
|
11564
11697
|
children: t("updateFailed")
|
|
11565
11698
|
}),
|
|
11566
11699
|
selfUpdate.view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
11567
|
-
className:
|
|
11700
|
+
className: _dsh_plugin_shop_css_bc430a83_default.log,
|
|
11701
|
+
"data-shop-log": true,
|
|
11568
11702
|
children: selfUpdate.view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
11569
|
-
className:
|
|
11703
|
+
className: _dsh_plugin_shop_css_bc430a83_default.logLine,
|
|
11570
11704
|
"data-shop-log-line": true,
|
|
11571
11705
|
children: line
|
|
11572
11706
|
}, index))
|
|
11573
11707
|
}),
|
|
11574
11708
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11575
|
-
className:
|
|
11709
|
+
className: _dsh_plugin_shop_css_bc430a83_default.failedDetail,
|
|
11576
11710
|
children: selfUpdate.view.detail === "" ? t("updateTransportFailed") : selfUpdate.view.detail
|
|
11577
11711
|
}),
|
|
11578
11712
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
11579
11713
|
type: "button",
|
|
11580
|
-
className:
|
|
11714
|
+
className: _dsh_plugin_shop_css_bc430a83_default.cancelButton,
|
|
11581
11715
|
"data-shop-update-self-dismiss": true,
|
|
11582
11716
|
onClick: () => {
|
|
11583
11717
|
selfUpdate.resetWhere((current) => current.kind === "failed");
|
|
@@ -11587,211 +11721,238 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11587
11721
|
]
|
|
11588
11722
|
}),
|
|
11589
11723
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11590
|
-
className:
|
|
11591
|
-
|
|
11592
|
-
|
|
11593
|
-
|
|
11594
|
-
|
|
11595
|
-
|
|
11596
|
-
|
|
11597
|
-
|
|
11598
|
-
|
|
11599
|
-
|
|
11600
|
-
|
|
11601
|
-
|
|
11602
|
-
|
|
11603
|
-
|
|
11604
|
-
|
|
11605
|
-
|
|
11606
|
-
|
|
11607
|
-
|
|
11608
|
-
|
|
11609
|
-
|
|
11610
|
-
|
|
11611
|
-
|
|
11612
|
-
|
|
11613
|
-
|
|
11614
|
-
|
|
11615
|
-
|
|
11616
|
-
|
|
11617
|
-
|
|
11618
|
-
|
|
11619
|
-
|
|
11620
|
-
|
|
11621
|
-
|
|
11622
|
-
|
|
11623
|
-
|
|
11624
|
-
|
|
11625
|
-
|
|
11626
|
-
|
|
11627
|
-
|
|
11628
|
-
|
|
11629
|
-
|
|
11630
|
-
|
|
11631
|
-
|
|
11632
|
-
|
|
11633
|
-
|
|
11634
|
-
|
|
11635
|
-
|
|
11636
|
-
|
|
11637
|
-
|
|
11638
|
-
|
|
11639
|
-
|
|
11640
|
-
|
|
11641
|
-
|
|
11642
|
-
|
|
11643
|
-
|
|
11644
|
-
|
|
11645
|
-
|
|
11646
|
-
|
|
11647
|
-
|
|
11648
|
-
|
|
11649
|
-
|
|
11650
|
-
|
|
11651
|
-
|
|
11652
|
-
|
|
11653
|
-
|
|
11654
|
-
|
|
11655
|
-
|
|
11656
|
-
|
|
11657
|
-
|
|
11658
|
-
},
|
|
11659
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("
|
|
11660
|
-
className:
|
|
11661
|
-
|
|
11662
|
-
|
|
11663
|
-
}), t("hideIncompatible", { count: incompatibleCount })]
|
|
11664
|
-
})
|
|
11665
|
-
]
|
|
11666
|
-
}),
|
|
11667
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11668
|
-
className: _dsh_plugin_shop_css_9836f849_default.catalogStatsRow,
|
|
11669
|
-
children: [
|
|
11670
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11671
|
-
className: _dsh_plugin_shop_css_9836f849_default.catalogStats,
|
|
11672
|
-
"data-shop-catalog-stats": true,
|
|
11673
|
-
children: t("catalogStats", {
|
|
11674
|
-
count: String(browsable.length),
|
|
11675
|
-
date: result.builtAt.slice(0, 10)
|
|
11676
|
-
})
|
|
11677
|
-
}),
|
|
11678
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
11679
|
-
type: "button",
|
|
11680
|
-
className: _dsh_plugin_shop_css_9836f849_default.catalogRefreshButton,
|
|
11681
|
-
"data-shop-catalog-refresh": true,
|
|
11682
|
-
disabled: reloading,
|
|
11683
|
-
onClick: () => {
|
|
11684
|
-
setReloading(true);
|
|
11685
|
-
setReloadFailed(false);
|
|
11686
|
-
setRequest({
|
|
11687
|
-
kind: "refresh",
|
|
11688
|
-
reverdicts: pendingReverdict()
|
|
11689
|
-
});
|
|
11690
|
-
setVersionReload((current) => current + 1);
|
|
11691
|
-
},
|
|
11692
|
-
children: reloading ? t("refreshing") : t("refresh")
|
|
11693
|
-
}),
|
|
11694
|
-
reloadFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11695
|
-
className: _dsh_plugin_shop_css_9836f849_default.catalogRefreshFailed,
|
|
11696
|
-
"data-shop-catalog-refresh-failed": true,
|
|
11697
|
-
children: t("refreshFailed")
|
|
11698
|
-
}),
|
|
11699
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
11700
|
-
className: _dsh_plugin_shop_css_9836f849_default.sortGroup,
|
|
11701
|
-
role: "group",
|
|
11702
|
-
"aria-labelledby": sortLabelId,
|
|
11703
|
-
"data-shop-sort": true,
|
|
11704
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11705
|
-
className: _dsh_plugin_shop_css_9836f849_default.sortLabel,
|
|
11706
|
-
id: sortLabelId,
|
|
11707
|
-
children: t("sortLabel")
|
|
11708
|
-
}), SHELF_ORDERS.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
11724
|
+
className: _dsh_plugin_shop_css_bc430a83_default.shelfBody,
|
|
11725
|
+
"data-shop-shelf": true,
|
|
11726
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11727
|
+
className: _dsh_plugin_shop_css_bc430a83_default.controls,
|
|
11728
|
+
"data-shop-controls": true,
|
|
11729
|
+
children: [
|
|
11730
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
11731
|
+
className: _dsh_plugin_shop_css_bc430a83_default.railHeading,
|
|
11732
|
+
children: t("categoriesHeading")
|
|
11733
|
+
}),
|
|
11734
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11735
|
+
className: _dsh_plugin_shop_css_bc430a83_default.categoryBar,
|
|
11736
|
+
role: "group",
|
|
11737
|
+
"aria-label": t("catalog"),
|
|
11738
|
+
children: [
|
|
11739
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
11740
|
+
type: "button",
|
|
11741
|
+
className: category === null ? `${_dsh_plugin_shop_css_bc430a83_default.categoryButton} ${_dsh_plugin_shop_css_bc430a83_default.categoryButtonOn}` : _dsh_plugin_shop_css_bc430a83_default.categoryButton,
|
|
11742
|
+
"aria-pressed": category === null,
|
|
11743
|
+
onClick: () => {
|
|
11744
|
+
page.setView({
|
|
11745
|
+
category: null,
|
|
11746
|
+
visibleCount: 48
|
|
11747
|
+
});
|
|
11748
|
+
},
|
|
11749
|
+
"data-shop-category-all": true,
|
|
11750
|
+
children: [
|
|
11751
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("all") }),
|
|
11752
|
+
" ",
|
|
11753
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: browsable.length })
|
|
11754
|
+
]
|
|
11755
|
+
}),
|
|
11756
|
+
CATEGORY_ORDER.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
11757
|
+
type: "button",
|
|
11758
|
+
className: category === key ? `${_dsh_plugin_shop_css_bc430a83_default.categoryButton} ${_dsh_plugin_shop_css_bc430a83_default.categoryButtonOn}` : _dsh_plugin_shop_css_bc430a83_default.categoryButton,
|
|
11759
|
+
"aria-pressed": category === key,
|
|
11760
|
+
"data-category": key,
|
|
11761
|
+
"data-shop-category-tab": key,
|
|
11762
|
+
onClick: () => {
|
|
11763
|
+
page.setView({
|
|
11764
|
+
category: key,
|
|
11765
|
+
visibleCount: 48
|
|
11766
|
+
});
|
|
11767
|
+
},
|
|
11768
|
+
children: [
|
|
11769
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t(categoryLocaleKey(key)) }),
|
|
11770
|
+
" ",
|
|
11771
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: categoryCounts.get(key) ?? 0 })
|
|
11772
|
+
]
|
|
11773
|
+
}, key)),
|
|
11774
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
11775
|
+
type: "button",
|
|
11776
|
+
className: category === "installed" ? `${_dsh_plugin_shop_css_bc430a83_default.categoryButton} ${_dsh_plugin_shop_css_bc430a83_default.categoryButtonOn}` : _dsh_plugin_shop_css_bc430a83_default.categoryButton,
|
|
11777
|
+
"aria-pressed": category === "installed",
|
|
11778
|
+
"data-shop-category-installed": true,
|
|
11779
|
+
onClick: () => {
|
|
11780
|
+
page.setView({
|
|
11781
|
+
category: "installed",
|
|
11782
|
+
visibleCount: 48
|
|
11783
|
+
});
|
|
11784
|
+
},
|
|
11785
|
+
children: [
|
|
11786
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("installed") }),
|
|
11787
|
+
" ",
|
|
11788
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: installedCount })
|
|
11789
|
+
]
|
|
11790
|
+
})
|
|
11791
|
+
]
|
|
11792
|
+
}),
|
|
11793
|
+
category !== "installed" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
11794
|
+
className: _dsh_plugin_shop_css_bc430a83_default.railHeading,
|
|
11795
|
+
children: t("filterHeading")
|
|
11796
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
11709
11797
|
type: "button",
|
|
11710
|
-
|
|
11711
|
-
"aria-
|
|
11712
|
-
|
|
11798
|
+
role: "switch",
|
|
11799
|
+
"aria-checked": hideIncompatible,
|
|
11800
|
+
className: hideIncompatible ? `${_dsh_plugin_shop_css_bc430a83_default.incompatibleFilter} ${_dsh_plugin_shop_css_bc430a83_default.incompatibleFilterOn}` : _dsh_plugin_shop_css_bc430a83_default.incompatibleFilter,
|
|
11801
|
+
title: t(hideIncompatible ? "showIncompatibleTitle" : "incompatibleFilterTitle"),
|
|
11802
|
+
"data-shop-hide-incompatible": true,
|
|
11713
11803
|
onClick: () => {
|
|
11714
|
-
page.
|
|
11715
|
-
|
|
11804
|
+
page.updateView((view) => ({
|
|
11805
|
+
hideIncompatible: !view.hideIncompatible,
|
|
11716
11806
|
visibleCount: 48
|
|
11717
|
-
});
|
|
11807
|
+
}));
|
|
11718
11808
|
},
|
|
11719
|
-
children:
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
11728
|
-
|
|
11729
|
-
|
|
11730
|
-
|
|
11731
|
-
|
|
11732
|
-
|
|
11733
|
-
|
|
11734
|
-
|
|
11735
|
-
|
|
11736
|
-
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
11740
|
-
|
|
11741
|
-
|
|
11742
|
-
|
|
11743
|
-
|
|
11744
|
-
|
|
11745
|
-
|
|
11746
|
-
|
|
11747
|
-
|
|
11748
|
-
|
|
11749
|
-
|
|
11809
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11810
|
+
className: hideIncompatible ? `${_dsh_plugin_shop_css_bc430a83_default.switch} ${_dsh_plugin_shop_css_bc430a83_default.switchOn}` : _dsh_plugin_shop_css_bc430a83_default.switch,
|
|
11811
|
+
"aria-hidden": "true",
|
|
11812
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_bc430a83_default.switchKnob })
|
|
11813
|
+
}), t("hideIncompatible", { count: incompatibleCount })]
|
|
11814
|
+
})] })
|
|
11815
|
+
]
|
|
11816
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11817
|
+
className: _dsh_plugin_shop_css_bc430a83_default.results,
|
|
11818
|
+
"data-shop-results": true,
|
|
11819
|
+
children: [
|
|
11820
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
11821
|
+
className: _dsh_plugin_shop_css_bc430a83_default.catalogStatsRow,
|
|
11822
|
+
children: [
|
|
11823
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11824
|
+
className: _dsh_plugin_shop_css_bc430a83_default.catalogStats,
|
|
11825
|
+
"data-shop-catalog-stats": true,
|
|
11826
|
+
children: t("catalogStats", {
|
|
11827
|
+
count: String(browsable.length),
|
|
11828
|
+
date: result.builtAt.slice(0, 10)
|
|
11829
|
+
})
|
|
11830
|
+
}),
|
|
11831
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
11832
|
+
type: "button",
|
|
11833
|
+
className: _dsh_plugin_shop_css_bc430a83_default.catalogRefreshButton,
|
|
11834
|
+
"data-shop-catalog-refresh": true,
|
|
11835
|
+
disabled: reloading,
|
|
11836
|
+
onClick: () => {
|
|
11837
|
+
setReloading(true);
|
|
11838
|
+
setReloadFailed(false);
|
|
11839
|
+
setRequest({
|
|
11840
|
+
kind: "refresh",
|
|
11841
|
+
reverdicts: pendingReverdict()
|
|
11842
|
+
});
|
|
11843
|
+
setVersionReload((current) => current + 1);
|
|
11844
|
+
},
|
|
11845
|
+
children: reloading ? t("refreshing") : t("refresh")
|
|
11846
|
+
}),
|
|
11847
|
+
reloadFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11848
|
+
className: _dsh_plugin_shop_css_bc430a83_default.catalogRefreshFailed,
|
|
11849
|
+
"data-shop-catalog-refresh-failed": true,
|
|
11850
|
+
children: t("refreshFailed")
|
|
11851
|
+
}),
|
|
11852
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
11853
|
+
className: _dsh_plugin_shop_css_bc430a83_default.sortGroup,
|
|
11854
|
+
role: "group",
|
|
11855
|
+
"aria-labelledby": sortLabelId,
|
|
11856
|
+
"data-shop-sort": true,
|
|
11857
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
11858
|
+
className: _dsh_plugin_shop_css_bc430a83_default.sortLabel,
|
|
11859
|
+
id: sortLabelId,
|
|
11860
|
+
children: t("sortLabel")
|
|
11861
|
+
}), SHELF_ORDERS.map((name) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
11862
|
+
type: "button",
|
|
11863
|
+
className: order === name ? `${_dsh_plugin_shop_css_bc430a83_default.categoryButton} ${_dsh_plugin_shop_css_bc430a83_default.categoryButtonOn}` : _dsh_plugin_shop_css_bc430a83_default.categoryButton,
|
|
11864
|
+
"aria-pressed": order === name,
|
|
11865
|
+
"data-shop-order": name,
|
|
11866
|
+
onClick: () => {
|
|
11867
|
+
page.setView({
|
|
11868
|
+
order: name,
|
|
11869
|
+
visibleCount: 48
|
|
11870
|
+
});
|
|
11871
|
+
},
|
|
11872
|
+
children: t(shelfOrderLocaleKey(name))
|
|
11873
|
+
}, name))]
|
|
11874
|
+
})
|
|
11875
|
+
]
|
|
11876
|
+
}),
|
|
11877
|
+
result.plugins.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11878
|
+
className: _dsh_plugin_shop_css_bc430a83_default.emptyLine,
|
|
11879
|
+
children: t("empty")
|
|
11880
|
+
}) : filtered.length === 0 ? category === "installed" && installedState.kind !== "ready" ? installedState.kind === "loading" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11881
|
+
className: _dsh_plugin_shop_css_bc430a83_default.emptyLine,
|
|
11882
|
+
"data-shop-installed-loading": true,
|
|
11883
|
+
children: t("installedLoading")
|
|
11884
|
+
}) : null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11885
|
+
className: _dsh_plugin_shop_css_bc430a83_default.emptyLine,
|
|
11886
|
+
"data-shop-empty": true,
|
|
11887
|
+
children: matched.length > 0 ? t("emptyIncompatibleFiltered") : t("emptySearch")
|
|
11888
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
|
|
11889
|
+
className: _dsh_plugin_shop_css_bc430a83_default.cards,
|
|
11890
|
+
children: [visible.map((entry) => {
|
|
11891
|
+
const key = entryKey(entry);
|
|
11892
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
11893
|
+
"data-shop-identity": key,
|
|
11894
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryCard, {
|
|
11895
|
+
entry,
|
|
11896
|
+
icon: iconOf(entry, icons),
|
|
11897
|
+
stars: starsOf(entry, stars),
|
|
11898
|
+
order,
|
|
11899
|
+
shelfDay: shelfDayOf(entry),
|
|
11900
|
+
installed: installedByKey.get(key),
|
|
11901
|
+
missing: missingByKey.get(key) ?? [],
|
|
11902
|
+
harness: harnessVerdictOf(harnessVerdicts, key),
|
|
11903
|
+
nameTakenBy: nameTakenByKey.get(key),
|
|
11904
|
+
t,
|
|
11905
|
+
flow: flows.flowFor(key),
|
|
11906
|
+
uninstallFlow: uninstallFlows.flowFor(key),
|
|
11907
|
+
restart,
|
|
11908
|
+
restartBlocked,
|
|
11909
|
+
reload,
|
|
11910
|
+
setEnabled,
|
|
11911
|
+
inInstalledView: category === "installed",
|
|
11912
|
+
page
|
|
11913
|
+
})
|
|
11914
|
+
}, key);
|
|
11915
|
+
}), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
11916
|
+
ref: sentinelRef,
|
|
11917
|
+
className: _dsh_plugin_shop_css_bc430a83_default.cardsSentry,
|
|
11918
|
+
"data-shop-sentry": true,
|
|
11919
|
+
"aria-hidden": "true"
|
|
11920
|
+
})]
|
|
11921
|
+
}), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11922
|
+
className: _dsh_plugin_shop_css_bc430a83_default.showingLine,
|
|
11923
|
+
"aria-live": "polite",
|
|
11924
|
+
children: t("showing", {
|
|
11925
|
+
shown: String(visibleCount),
|
|
11926
|
+
total: String(filtered.length)
|
|
11927
|
+
})
|
|
11928
|
+
})] }),
|
|
11929
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedSection, {
|
|
11930
|
+
state: installedState,
|
|
11931
|
+
entriesByKey,
|
|
11932
|
+
icons,
|
|
11933
|
+
missingByKey,
|
|
11934
|
+
harnessVerdicts,
|
|
11750
11935
|
t,
|
|
11751
|
-
|
|
11752
|
-
|
|
11936
|
+
setEnabled,
|
|
11937
|
+
flowFor: flows.flowFor,
|
|
11938
|
+
uninstallFor: uninstallFlows.flowFor,
|
|
11753
11939
|
restart,
|
|
11754
11940
|
restartBlocked,
|
|
11755
11941
|
reload,
|
|
11756
|
-
setEnabled,
|
|
11757
|
-
inInstalledView: category === "installed",
|
|
11758
11942
|
page
|
|
11943
|
+
}),
|
|
11944
|
+
category === "installed" && installedState.kind === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShadowedSection, {
|
|
11945
|
+
entries: installedState.entries,
|
|
11946
|
+
icons,
|
|
11947
|
+
entriesByKey,
|
|
11948
|
+
t,
|
|
11949
|
+
uninstallFlows,
|
|
11950
|
+
restart,
|
|
11951
|
+
restartBlocked,
|
|
11952
|
+
reload
|
|
11759
11953
|
})
|
|
11760
|
-
|
|
11761
|
-
}), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
11762
|
-
ref: sentinelRef,
|
|
11763
|
-
className: _dsh_plugin_shop_css_9836f849_default.cardsSentry,
|
|
11764
|
-
"data-shop-sentry": true,
|
|
11765
|
-
"aria-hidden": "true"
|
|
11954
|
+
]
|
|
11766
11955
|
})]
|
|
11767
|
-
}), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11768
|
-
className: _dsh_plugin_shop_css_9836f849_default.showingLine,
|
|
11769
|
-
"aria-live": "polite",
|
|
11770
|
-
children: t("showing", {
|
|
11771
|
-
shown: String(visibleCount),
|
|
11772
|
-
total: String(filtered.length)
|
|
11773
|
-
})
|
|
11774
|
-
})] }),
|
|
11775
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedSection, {
|
|
11776
|
-
state: installedState,
|
|
11777
|
-
entriesByKey,
|
|
11778
|
-
missingByKey,
|
|
11779
|
-
harnessVerdicts,
|
|
11780
|
-
t,
|
|
11781
|
-
setEnabled,
|
|
11782
|
-
flowFor: flows.flowFor,
|
|
11783
|
-
restart,
|
|
11784
|
-
restartBlocked,
|
|
11785
|
-
reload,
|
|
11786
|
-
page
|
|
11787
|
-
}),
|
|
11788
|
-
category === "installed" && installedState.kind === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShadowedSection, {
|
|
11789
|
-
entries: installedState.entries,
|
|
11790
|
-
t,
|
|
11791
|
-
uninstallFlows,
|
|
11792
|
-
restart,
|
|
11793
|
-
restartBlocked,
|
|
11794
|
-
reload
|
|
11795
11956
|
})
|
|
11796
11957
|
]
|
|
11797
11958
|
});
|
|
@@ -11833,10 +11994,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11833
11994
|
}
|
|
11834
11995
|
/**
|
|
11835
11996
|
* Where the reader is on the shelf: the first card whose bottom lies below
|
|
11836
|
-
* the page's top edge, and its top relative to that edge. The cards
|
|
11837
|
-
*
|
|
11838
|
-
*
|
|
11839
|
-
* one per
|
|
11997
|
+
* the page's top edge, and its top relative to that edge. The cards are laid
|
|
11998
|
+
* out row-major in document order — one per row before 2026-11-02, two per
|
|
11999
|
+
* row since — so their bottoms never fall with their index, and a binary
|
|
12000
|
+
* search reads a handful of their rects on each scroll event, never one per
|
|
12001
|
+
* card.
|
|
11840
12002
|
*
|
|
11841
12003
|
* A card whose top lies below the edge counts only while it lies no further
|
|
11842
12004
|
* below than the shelf's gap between cards, as when the edge falls in that
|
|
@@ -11922,7 +12084,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11922
12084
|
}, [page]);
|
|
11923
12085
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
11924
12086
|
ref: scroller,
|
|
11925
|
-
className: `${
|
|
12087
|
+
className: `${_dsh_plugin_shop_css_bc430a83_default.page} notranslate`,
|
|
11926
12088
|
translate: "no",
|
|
11927
12089
|
"data-shop-page": true,
|
|
11928
12090
|
onScroll: (event) => {
|
|
@@ -11932,15 +12094,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
11932
12094
|
onTouchStart: cancelRestore,
|
|
11933
12095
|
onKeyDown: cancelRestore,
|
|
11934
12096
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("header", {
|
|
11935
|
-
className:
|
|
12097
|
+
className: _dsh_plugin_shop_css_bc430a83_default.pageHead,
|
|
11936
12098
|
"data-shop-page-head": true,
|
|
11937
12099
|
"data-window-drag": true,
|
|
11938
12100
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h1", {
|
|
11939
|
-
className:
|
|
12101
|
+
className: _dsh_plugin_shop_css_bc430a83_default.pageTitle,
|
|
11940
12102
|
"data-shop-page-title": true,
|
|
11941
12103
|
children: t("title")
|
|
11942
12104
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
11943
|
-
className:
|
|
12105
|
+
className: _dsh_plugin_shop_css_bc430a83_default.pageIntro,
|
|
11944
12106
|
"data-shop-page-intro": true,
|
|
11945
12107
|
children: t("intro")
|
|
11946
12108
|
})]
|