dsh-plugin-shop 0.5.4 → 0.6.0-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 +110 -31
- package/lib/index.js +91 -6
- package/lib/typert.host.js +13 -11
- package/lib/typert.remote-client.js +11 -9
- package/lib/types/client/locales.d.ts +10 -2
- package/lib/types/client/present.d.ts +22 -0
- package/lib/types/host/catalog.d.ts +4 -2
- package/lib/types/host/index.d.ts +18 -0
- package/lib/types/host/peers.d.ts +25 -0
- package/lib/types/host/types.d.ts +2 -0
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -4310,14 +4310,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4310
4310
|
"url": string(),
|
|
4311
4311
|
"sha256": string()
|
|
4312
4312
|
})]).optional(),
|
|
4313
|
-
"added": union([_undefined(), string()]).optional()
|
|
4313
|
+
"added": union([_undefined(), string()]).optional(),
|
|
4314
|
+
"peers": union([_undefined(), array(string())]).optional()
|
|
4314
4315
|
})),
|
|
4315
4316
|
"denied": array(object({
|
|
4316
4317
|
"name": string(),
|
|
4317
4318
|
"detail": string(),
|
|
4318
4319
|
"replacement": union([_undefined(), string()]).optional()
|
|
4319
4320
|
})),
|
|
4320
|
-
"stars": record(string(), number())
|
|
4321
|
+
"stars": record(string(), number()),
|
|
4322
|
+
"incompatible": record(string(), array(string()))
|
|
4321
4323
|
});
|
|
4322
4324
|
const dsh_plugin_shop_shop_installed_result$schema = array(object({
|
|
4323
4325
|
"name": string(),
|
|
@@ -4435,7 +4437,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4435
4437
|
},
|
|
4436
4438
|
sourceLocation: {
|
|
4437
4439
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4438
|
-
"line":
|
|
4440
|
+
"line": 543,
|
|
4439
4441
|
"column": 9
|
|
4440
4442
|
}
|
|
4441
4443
|
},
|
|
@@ -4453,7 +4455,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4453
4455
|
},
|
|
4454
4456
|
sourceLocation: {
|
|
4455
4457
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4456
|
-
"line":
|
|
4458
|
+
"line": 723,
|
|
4457
4459
|
"column": 9
|
|
4458
4460
|
}
|
|
4459
4461
|
},
|
|
@@ -4481,7 +4483,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4481
4483
|
},
|
|
4482
4484
|
sourceLocation: {
|
|
4483
4485
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4484
|
-
"line":
|
|
4486
|
+
"line": 593,
|
|
4485
4487
|
"column": 9
|
|
4486
4488
|
}
|
|
4487
4489
|
},
|
|
@@ -4508,7 +4510,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4508
4510
|
},
|
|
4509
4511
|
sourceLocation: {
|
|
4510
4512
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4511
|
-
"line":
|
|
4513
|
+
"line": 712,
|
|
4512
4514
|
"column": 3
|
|
4513
4515
|
}
|
|
4514
4516
|
},
|
|
@@ -4526,7 +4528,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4526
4528
|
},
|
|
4527
4529
|
sourceLocation: {
|
|
4528
4530
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4529
|
-
"line":
|
|
4531
|
+
"line": 873,
|
|
4530
4532
|
"column": 9
|
|
4531
4533
|
}
|
|
4532
4534
|
},
|
|
@@ -4553,7 +4555,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4553
4555
|
},
|
|
4554
4556
|
sourceLocation: {
|
|
4555
4557
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4556
|
-
"line":
|
|
4558
|
+
"line": 460,
|
|
4557
4559
|
"column": 9
|
|
4558
4560
|
}
|
|
4559
4561
|
},
|
|
@@ -4581,7 +4583,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4581
4583
|
},
|
|
4582
4584
|
sourceLocation: {
|
|
4583
4585
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4584
|
-
"line":
|
|
4586
|
+
"line": 815,
|
|
4585
4587
|
"column": 9
|
|
4586
4588
|
}
|
|
4587
4589
|
},
|
|
@@ -4608,7 +4610,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4608
4610
|
},
|
|
4609
4611
|
sourceLocation: {
|
|
4610
4612
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4611
|
-
"line":
|
|
4613
|
+
"line": 933,
|
|
4612
4614
|
"column": 9
|
|
4613
4615
|
}
|
|
4614
4616
|
},
|
|
@@ -4626,7 +4628,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4626
4628
|
},
|
|
4627
4629
|
sourceLocation: {
|
|
4628
4630
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4629
|
-
"line":
|
|
4631
|
+
"line": 917,
|
|
4630
4632
|
"column": 9
|
|
4631
4633
|
}
|
|
4632
4634
|
}
|
|
@@ -4655,6 +4657,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4655
4657
|
case "community": return "tierCommunity";
|
|
4656
4658
|
}
|
|
4657
4659
|
}
|
|
4660
|
+
/** The peers the Host said this installation does not provide, or none when it
|
|
4661
|
+
* said nothing — a plugin that runs here and one the Host could not judge are
|
|
4662
|
+
* both rendered as no warning at all. */
|
|
4663
|
+
function missingPeersOf(incompatible, name) {
|
|
4664
|
+
return incompatible[name] ?? [];
|
|
4665
|
+
}
|
|
4658
4666
|
/** Spec §9.3 verbatim — the community-tier acknowledgement. The zh dictionary
|
|
4659
4667
|
* states the same facts in its own register; never soften this wording. */
|
|
4660
4668
|
const ACKNOWLEDGEMENT_EN = "Once installed, this plugin holds the same privileges as a built-in one: reading and writing your files, running shell commands, and reading and modifying the requests sent to the model. It has not been reviewed.";
|
|
@@ -4831,6 +4839,30 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4831
4839
|
* A github entry answers null: it is installed from a repo and has no npm
|
|
4832
4840
|
* package page.
|
|
4833
4841
|
*/
|
|
4842
|
+
/**
|
|
4843
|
+
* Who put this entry where it is — the fact a reader wants when two listings
|
|
4844
|
+
* carry the same text from different hands.
|
|
4845
|
+
*
|
|
4846
|
+
* The two sources answer it differently and neither is npm's `author` field:
|
|
4847
|
+
* that one is free text the publisher writes, and a clone inherits it
|
|
4848
|
+
* verbatim. An npm entry answers with the account npm recorded for the
|
|
4849
|
+
* version (`publisher`); a github entry answers with its repository owner,
|
|
4850
|
+
* which is already half of the `owner/slug` that IS its identity — so nothing
|
|
4851
|
+
* new has to be harvested or emitted for it.
|
|
4852
|
+
*
|
|
4853
|
+
* `repo` reaches the client as an unvalidated string, so the owner is taken
|
|
4854
|
+
* only from a value that really is `owner/slug` with an owner GitHub would
|
|
4855
|
+
* accept (1-39 chars of alphanumerics and hyphens). Anything else yields
|
|
4856
|
+
* null: a misleading fragment is worse than saying nothing, the same rule
|
|
4857
|
+
* npmPageUrl applies to a package name.
|
|
4858
|
+
*/
|
|
4859
|
+
function authorOf(entry) {
|
|
4860
|
+
if (entry.source === "github") {
|
|
4861
|
+
if (entry.repo === void 0) return null;
|
|
4862
|
+
return /^([A-Za-z0-9](?:[A-Za-z0-9-]{0,38}))\/[^/]+$/.exec(entry.repo)?.[1] ?? null;
|
|
4863
|
+
}
|
|
4864
|
+
return entry.publisher ?? null;
|
|
4865
|
+
}
|
|
4834
4866
|
function npmPageUrl(entry) {
|
|
4835
4867
|
if (entry.source !== "npm") return null;
|
|
4836
4868
|
const name = entry.name;
|
|
@@ -4906,6 +4938,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4906
4938
|
acknowledgementBody: ACKNOWLEDGEMENT_ZH,
|
|
4907
4939
|
installing: "正在安装…",
|
|
4908
4940
|
installedRestartNotice: "已安装;重启 dsh 后生效",
|
|
4941
|
+
incompatibleBadge: "缺少依赖",
|
|
4942
|
+
incompatibleDetail: "此插件需要 {modules},你当前的 dsh 未提供",
|
|
4943
|
+
incompatibleInstallWarning: "此插件需要 {modules},你当前的 dsh 未提供;安装后它很可能无法加载。",
|
|
4944
|
+
incompatibleUpdateDetail: "更新此插件需要 {modules},你当前的 dsh 未提供",
|
|
4909
4945
|
hotNoPatchNotice: "该插件没有可热挂载的补丁文件;重启 dsh 后生效",
|
|
4910
4946
|
hotNotSimpleNotice: "该插件的补丁包含无法热挂载的配置;重启 dsh 后生效",
|
|
4911
4947
|
hotHostUnsupportedNotice: "当前环境不支持热挂载;重启 dsh 后生效",
|
|
@@ -4919,7 +4955,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4919
4955
|
showing: "显示前 {shown} 个(共 {total})· 继续滚动加载",
|
|
4920
4956
|
repository: "仓库",
|
|
4921
4957
|
npmPage: "npm 页面",
|
|
4922
|
-
|
|
4958
|
+
authorLine: "作者:{author}",
|
|
4923
4959
|
license: "许可",
|
|
4924
4960
|
customLicense: "自定义许可",
|
|
4925
4961
|
reviewedVersionLine: "已审核 v{reviewed} / 当前 v{current} 未审核",
|
|
@@ -4990,6 +5026,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4990
5026
|
acknowledgementBody: ACKNOWLEDGEMENT_EN,
|
|
4991
5027
|
installing: "Installing…",
|
|
4992
5028
|
installedRestartNotice: "installed; restart dsh to activate",
|
|
5029
|
+
incompatibleBadge: "missing dependency",
|
|
5030
|
+
incompatibleDetail: "This plugin needs {modules}, which your dsh does not provide",
|
|
5031
|
+
incompatibleInstallWarning: "This plugin needs {modules}, which your dsh does not provide; it will most likely fail to load once installed.",
|
|
5032
|
+
incompatibleUpdateDetail: "Updating this plugin needs {modules}, which your dsh does not provide",
|
|
4993
5033
|
hotNoPatchNotice: "no patch file to hot-mount; restart dsh to activate",
|
|
4994
5034
|
hotNotSimpleNotice: "the patch has config rows that cannot be hot-mounted; restart dsh to activate",
|
|
4995
5035
|
hotHostUnsupportedNotice: "this harness cannot hot-mount; restart dsh to activate",
|
|
@@ -5003,7 +5043,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5003
5043
|
showing: "showing first {shown} of {total} — scroll to load more",
|
|
5004
5044
|
repository: "Repository",
|
|
5005
5045
|
npmPage: "npm page",
|
|
5006
|
-
|
|
5046
|
+
authorLine: "Author: {author}",
|
|
5007
5047
|
license: "License",
|
|
5008
5048
|
customLicense: "Custom license",
|
|
5009
5049
|
reviewedVersionLine: "reviewed v{reviewed} / current v{current} unreviewed",
|
|
@@ -5193,7 +5233,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5193
5233
|
}
|
|
5194
5234
|
//#endregion
|
|
5195
5235
|
//#region \0dsh-plugin-shop-css:e3675a89
|
|
5196
|
-
const css = "/* Shop tab styles — a market shelf inside the settings surface. The visual\n * language is the dsh theme's alias tokens ONLY (the four tokens the previous\n * styles referenced that do not exist — bg-layer-3, interactive-bg-hover,\n * state-error, state-success — are corrected to bg-layer-2 and the\n * state-*-primary set). The signature is the category spine: one HUE per\n * category (seven fixed hues, not the brand token — the brand resolves to\n * near-black in the light theme, so seven brand opacities read as seven\n * shades of gray), with the category name as the cover label in the same hue. Class names are mapped to content-derived names at bundle time\n * (tsdown.s8583e3_client.s942a79_config.s5f67aa_ts) and stubbed in tests; only the keys are relied\n * on.\n *\n * One token rule the theme forces: the background LAYERS do not carry\n * contrast. In the light theme bg-base, bg-layer-1, bg-layer-2 and\n * bg-layer-3 all resolve to the same white; only the dark theme spreads them\n * (950/875/850/800). So a layer-2 fill on a layer-1 ground is invisible for\n * half of all users, and anything whose fill is its ONLY affordance — the\n * loading skeleton, the borderless capability chips — derives its fill from\n * label-primary instead, which inverts with the theme by construction.\n * Elements that also carry a border or text may keep a layer fill.\n * `css-tokens.s8583e3_client.sfd9d8a_spec.s5f67aa_ts` pins this. */\n\n.s255e5a_panel {\n display: flex;\n flex-direction: column;\n gap: 14px;\n /* The shelf never scrolls sideways: a card too wide for its track wraps\n * its badges rather than widening the panel. */\n overflow-x: hidden;\n}\n\n.s44a3ec_stateLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* Visually hidden but screen-reader readable (the loading copy while the\n * skeleton stands in visually). */\n.s3a0ece_srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n overflow: hidden;\n white-space: nowrap;\n}\n\n/* The loading shelf: ghost cards with the same geometry as the real grid, so\n * the swap to content is a same-shape handoff instead of a jump. */\n.se0c3b4_skeletonGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n}\n\n.s520416_skeletonCard {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n}\n\n/* The shimmer sweep: one gradient band travels across each ghost card while\n * the bars breathe, so the loading state reads as \"the shelf is scanning\". */\n.s520416_skeletonCard::after {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 100deg,\n transparent 25%,\n color-mix(in srgb, var(--dsw-alias-label-primary) 14%, transparent) 50%,\n transparent 75%\n );\n transform: translateX(-100%);\n animation: dshShopSkeletonSweep 1.4s ease-in-out infinite;\n pointer-events: none;\n}\n\n@keyframes dshShopSkeletonSweep {\n to { transform: translateX(100%); }\n}\n\n.s8c7d6f_skeletonName {\n width: 55%;\n height: 14px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sf5179e_skeletonSummary {\n width: 92%;\n height: 12px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sbb0d25_skeletonSummaryShort {\n width: 68%;\n height: 12px;\n margin: 6px 12px 10px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n/* The ghost of the action line, right where the real buttons land. */\n.s96e910_skeletonActions {\n display: block;\n width: 84px;\n height: 24px;\n margin: 0 12px 10px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.s520416_skeletonCard span {\n animation: dshShopSkeletonPulse 1.6s ease-in-out infinite;\n}\n\n@keyframes dshShopSkeletonPulse {\n 0%, 100% { opacity: 0.5; }\n 50% { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s520416_skeletonCard span { animation: none; opacity: 0.75; }\n .s520416_skeletonCard::after { content: none; }\n}\n\n.s73242a_actionButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s73242a_actionButton:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s73242a_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s646509_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.s745e3c_toolbarRight {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n/* The shop's own version, right of the search box: `v0.4.4`, plus the\n * update button when the version check found a newer release. */\n.s4fe61f_versionBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.sb9b924_versionText {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa04455_updateSelfButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-brand-primary);\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n}\n\n.sa04455_updateSelfButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, transparent);\n}\n\n.sa04455_updateSelfButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The on-demand re-check next to the version number: a quiet pill; the\n * border and text carry it in the light theme where bg layers are white. */\n.s614a38_checkUpdateButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.s614a38_checkUpdateButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n}\n\n.s614a38_checkUpdateButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.s614a38_checkUpdateButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The project's GitHub mark, right of the version row: a quiet icon link.\n * The octocat carries itself — color shift on hover, no text, no border. */\n.sa987f7_githubLink {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 3px;\n border-radius: 6px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa987f7_githubLink:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sa987f7_githubLink:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The self-update's expanded states (running log / done + restart /\n * failure detail) sit under the toolbar, above the category bar. */\n.sb81ba5_selfUpdatePanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n}\n\n.s232ec4_searchInput {\n width: min(280px, 100%);\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n.s232ec4_searchInput:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s1555aa_staleBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.s3946f1_staleBadge {\n padding: 2px 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n color: var(--dsw-alias-state-warn-primary);\n font-size: 12px;\n}\n\n.sb2c859_categoryBar {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 6px;\n}\n\n.s26d58e_categoryButton {\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.s26d58e_categoryButton: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.s5f68c4_categoryButtonOn {\n border-color: var(--dsw-alias-brand-primary);\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-weight: 600;\n}\n\n.s26d58e_categoryButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s668774_catalogHeading {\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.sccc4d1_catalogStats {\n margin: 6px 0 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.s6de1ab_emptyLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* The shelf: a responsive grid of cards. */\n/* Single-line rows: one full-width column, one plugin per line. */\n.sf657f5_cards {\n display: grid;\n grid-template-columns: 1fr;\n max-width: 100%;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n animation: dshShopCardsIn 220ms ease;\n}\n\n@keyframes dshShopCardsIn {\n from { opacity: 0; transform: translateY(3px); }\n to { opacity: 1; transform: none; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sf657f5_cards { animation: none; }\n}\n\n.s65f383_card {\n position: relative;\n display: flex;\n flex-direction: column;\n /* A grid item's min-width is auto by default: its content would widen the\n * 1fr track past the panel and overflow horizontally. */\n min-width: 0;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;\n /* Off-screen cards skip layout and paint; the placeholder keeps scrollbar\n * estimation sane before the browser has measured the real height. */\n content-visibility: auto;\n contain-intrinsic-size: auto 140px;\n}\n\n/* The sentinel that triggers the next shelf batch: zero visual footprint. */\n.sd7ee01_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s705cc6_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s65f383_card:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l1));\n transform: translateY(-1px);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s65f383_card { transition: none; }\n .s65f383_card:hover { transform: none; }\n}\n\n/* The signature: a spine and cover in the category's own hue. The hues are\n * fixed mid-bright colors that read on both themes; `other` deliberately\n * stays a neutral gray so the fallback category does not compete. */\n.s4ce25d_cardSpine {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 3px;\n background: var(--spine-hue, #8B8E96);\n}\n\n.s65f383_card[data-category='tool'] { --spine-hue: #4C8DFF; }\n.s65f383_card[data-category='provider'] { --spine-hue: #A78BFA; }\n.s65f383_card[data-category='ui'] { --spine-hue: #2DD4BF; }\n.s65f383_card[data-category='workflow'] { --spine-hue: #F59E0B; }\n.s65f383_card[data-category='integration'] { --spine-hue: #34D399; }\n.s65f383_card[data-category='theme'] { --spine-hue: #F472B6; }\n.s65f383_card[data-category='other'] { --spine-hue: #8B8E96; }\n\n/* The category sits in the badge row as a quiet chip in the category's own\n * hue — the border carries it where the light theme's white layers cannot\n * (the same lesson as the check button). */\n.s9c8eaa_categoryBadge {\n padding: 1px 7px;\n border: 1px solid color-mix(in srgb, var(--spine-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(--spine-hue, #8B8E96);\n white-space: nowrap;\n}\n\n.sa97234_entryHeader {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 12px;\n min-width: 0;\n padding: 8px 12px 2px;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.sa97234_entryHeader:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n/* The name fills the row and truncates rather than wraps; the badges stay\n * intact beside it. */\n.sc3f149_name {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n font-weight: 600;\n flex: 1 1 auto;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--dsw-alias-label-primary);\n}\n\n.s513026_badges {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n/* The action line: buttons side by side; an active install/uninstall flow\n * (gate, log, notices) takes the full width below them. */\n.s9b5048_cardActions {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 8px 12px 10px;\n}\n\n.s9b5048_cardActions > .sc4f2ac_gate,\n.s9b5048_cardActions > .s4d374c_installPanel,\n.s9b5048_cardActions > .sf53ca0_installedActions {\n flex-basis: 100%;\n}\n\n/* The entry's version, always visible on the card (the expanded detail\n * repeats it in the version row) — the same quiet mono as the toolbar. */\n.sc6ec6e_cardVersion {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The octocat marking a github-sourced entry, beside its short commit. */\n.s71f10f_sourceBadge {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s5e82cb_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.s5e82cb_tierBadge[data-tier='verified'] {\n color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='verified-stale'] {\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='community'] {\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n\n.sff43b4_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s73f7fb_chevron, .s61393c_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.s61393c_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .s73f7fb_chevron, .s61393c_chevronOpen { transition: none; }\n}\n\n.s820ebf_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.sb8fbc8_summary {\n margin: 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.se507db_summaryZh {\n margin: -4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 1;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* Expanded cards show the full summary: the clamp is lifted, never raised —\n * a higher number would still truncate the author's text, just later. */\n.s10baf0_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.sb9dba9_summaryZhExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.s9d2029_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s3bdbd5_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s441702_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s441702_capabilities li {\n padding: 1px 6px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sde3f0c_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.se98179_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.sfb4d2f_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.sfb4d2f_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.sfb4d2f_detailRow dd {\n margin: 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n overflow-wrap: anywhere;\n text-align: right;\n color: var(--dsw-alias-label-primary);\n}\n\n.sfb4d2f_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.sfb4d2f_detailRow a:hover {\n text-decoration: underline;\n}\n\n.sfb4d2f_detailRow a:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The publishing account, beside the npm link. Secondary label colour and a\n * middot separator carry the distinction — never a background fill, which is\n * invisible in the light theme where every bg-layer token is the same white. */\n.s9e06b8_publisher {\n color: var(--dsw-alias-label-secondary);\n}\n\n.s9e06b8_publisher::before {\n content: ' · ';\n color: var(--dsw-alias-label-secondary);\n}\n\n.sd58c6d_reviewedLine {\n margin: 8px 0 0;\n padding: 6px 8px;\n border-radius: 6px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n font-size: 12px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n/* Install flow. */\n.s4d374c_installPanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 2px;\n}\n\n.se7aaed_installButton, .sb43a49_confirmButton, .s6e45d1_cancelButton {\n align-self: flex-start;\n padding: 5px 14px;\n border-radius: 6px;\n font-size: 13px;\n cursor: pointer;\n}\n\n.se7aaed_installButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.se7aaed_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.sb43a49_confirmButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.sb43a49_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s6e45d1_cancelButton {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n}\n\n.s6e45d1_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.se7aaed_installButton:focus-visible, .sb43a49_confirmButton:focus-visible, .s6e45d1_cancelButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The shelf card of a plugin that is installed and current carries this\n * non-interactive label instead of an install button. Border + text do the\n * work in the light theme, where every bg-layer token resolves to white. */\n.sb6fc74_installedLabel {\n align-self: flex-start;\n margin: 0;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n/* An installed card's controls: the installed label or the update button,\n * plus the uninstall button. The install panel's running/failed states are\n * column layouts and sit as one item of this row. */\n.sf53ca0_installedActions {\n display: flex;\n align-items: flex-start;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* Uninstall revokes privilege; the quiet outline keeps it from competing\n * with the primary action, and the error tint says \"removes\". */\n.s96967f_uninstallButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 55%, transparent);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-error-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s96967f_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.s96967f_uninstallButton:focus-visible {\n outline: 2px solid var(--dsw-alias-state-error-primary);\n outline-offset: 1px;\n}\n\n/* The restart dsh button on a done install: quiet outline, same rank as\n * the other secondary actions. */\n.s6ac237_restartButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s6ac237_restartButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s6ac237_restartButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sc4f2ac_gate {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border: 1px solid var(--dsw-alias-state-warn-primary);\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 8%, transparent);\n}\n\n.s559c38_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s3c0ace_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s4b2373_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.s52b5a5_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s64ab71_log {\n max-height: 160px;\n overflow: auto;\n margin: 0;\n padding: 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l1);\n list-style: none;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.5;\n}\n\n.s1531a8_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sf20dda_notice {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.seb9b08_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.sfd3021_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s50ba35_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.se75489_rejectedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n/* Installed section. */\n.sa276cf_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.sceae6e_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s8d2072_outdatedRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.s5c2545_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.s5c2545_outdatedInfo strong {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n word-break: break-all;\n color: var(--dsw-alias-label-primary);\n}\n\n.s877c68_outdatedVersions {\n /* Two inline labels with nothing between them in the markup ran together as\n * `installed v1.0.0latest v2.0.0`: JSX drops the whitespace-only line, and\n * the per-label class the markup asked for was never defined (singular\n * against this rule's plural). The container owns the gap, the same idiom\n * as .s513026_badges — the labels need no class of their own. */\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa1f556_outdatedActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n/* The enable/disable switch. */\n.s11c019_switch {\n position: relative;\n width: 32px;\n height: 18px;\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.s7580e8_switchOn {\n border-color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 55%, transparent);\n}\n\n.saab2b3_switchKnob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 12px;\n height: 12px;\n border-radius: 999px;\n background: var(--dsw-alias-label-primary);\n transition: transform 120ms ease, background 120ms ease;\n}\n\n.s7580e8_switchOn .saab2b3_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.s11c019_switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s11c019_switch, .saab2b3_switchKnob { transition: none; }\n}\n\n/* The hot enable/disable switch on installed rows: the knob button plus the\n * §8 hot-apply / failure notices rendered beside it. */\n.sd9f9e4_switchWrap {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n";
|
|
5236
|
+
const css = "/* Shop tab styles — a market shelf inside the settings surface. The visual\n * language is the dsh theme's alias tokens ONLY (the four tokens the previous\n * styles referenced that do not exist — bg-layer-3, interactive-bg-hover,\n * state-error, state-success — are corrected to bg-layer-2 and the\n * state-*-primary set). The signature is the category spine: one HUE per\n * category (seven fixed hues, not the brand token — the brand resolves to\n * near-black in the light theme, so seven brand opacities read as seven\n * shades of gray), with the category name as the cover label in the same hue. Class names are mapped to content-derived names at bundle time\n * (tsdown.s8583e3_client.s942a79_config.s5f67aa_ts) and stubbed in tests; only the keys are relied\n * on.\n *\n * One token rule the theme forces: the background LAYERS do not carry\n * contrast. In the light theme bg-base, bg-layer-1, bg-layer-2 and\n * bg-layer-3 all resolve to the same white; only the dark theme spreads them\n * (950/875/850/800). So a layer-2 fill on a layer-1 ground is invisible for\n * half of all users, and anything whose fill is its ONLY affordance — the\n * loading skeleton, the borderless capability chips — derives its fill from\n * label-primary instead, which inverts with the theme by construction.\n * Elements that also carry a border or text may keep a layer fill.\n * `css-tokens.s8583e3_client.sfd9d8a_spec.s5f67aa_ts` pins this. */\n\n.s255e5a_panel {\n display: flex;\n flex-direction: column;\n gap: 14px;\n /* The shelf never scrolls sideways: a card too wide for its track wraps\n * its badges rather than widening the panel. */\n overflow-x: hidden;\n}\n\n.s44a3ec_stateLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* Visually hidden but screen-reader readable (the loading copy while the\n * skeleton stands in visually). */\n.s3a0ece_srOnly {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n border: 0;\n clip: rect(0 0 0 0);\n clip-path: inset(50%);\n overflow: hidden;\n white-space: nowrap;\n}\n\n/* The loading shelf: ghost cards with the same geometry as the real grid, so\n * the swap to content is a same-shape handoff instead of a jump. */\n.se0c3b4_skeletonGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n}\n\n.s520416_skeletonCard {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n}\n\n/* The shimmer sweep: one gradient band travels across each ghost card while\n * the bars breathe, so the loading state reads as \"the shelf is scanning\". */\n.s520416_skeletonCard::after {\n content: '';\n position: absolute;\n inset: 0;\n background: linear-gradient(\n 100deg,\n transparent 25%,\n color-mix(in srgb, var(--dsw-alias-label-primary) 14%, transparent) 50%,\n transparent 75%\n );\n transform: translateX(-100%);\n animation: dshShopSkeletonSweep 1.4s ease-in-out infinite;\n pointer-events: none;\n}\n\n@keyframes dshShopSkeletonSweep {\n to { transform: translateX(100%); }\n}\n\n.s8c7d6f_skeletonName {\n width: 55%;\n height: 14px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sf5179e_skeletonSummary {\n width: 92%;\n height: 12px;\n margin: 10px 12px 0;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.sbb0d25_skeletonSummaryShort {\n width: 68%;\n height: 12px;\n margin: 6px 12px 10px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n/* The ghost of the action line, right where the real buttons land. */\n.s96e910_skeletonActions {\n display: block;\n width: 84px;\n height: 24px;\n margin: 0 12px 10px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 8%, transparent);\n}\n\n.s520416_skeletonCard span {\n animation: dshShopSkeletonPulse 1.6s ease-in-out infinite;\n}\n\n@keyframes dshShopSkeletonPulse {\n 0%, 100% { opacity: 0.5; }\n 50% { opacity: 1; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s520416_skeletonCard span { animation: none; opacity: 0.75; }\n .s520416_skeletonCard::after { content: none; }\n}\n\n.s73242a_actionButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s73242a_actionButton:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s73242a_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s646509_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.s745e3c_toolbarRight {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n/* The shop's own version, right of the search box: `v0.4.4`, plus the\n * update button when the version check found a newer release. */\n.s4fe61f_versionBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.sb9b924_versionText {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa04455_updateSelfButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-brand-primary);\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n}\n\n.sa04455_updateSelfButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, transparent);\n}\n\n.sa04455_updateSelfButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The on-demand re-check next to the version number: a quiet pill; the\n * border and text carry it in the light theme where bg layers are white. */\n.s614a38_checkUpdateButton {\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n.s614a38_checkUpdateButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n}\n\n.s614a38_checkUpdateButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.s614a38_checkUpdateButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The project's GitHub mark, right of the version row: a quiet icon link.\n * The octocat carries itself — color shift on hover, no text, no border. */\n.sa987f7_githubLink {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 3px;\n border-radius: 6px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa987f7_githubLink:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.sa987f7_githubLink:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The self-update's expanded states (running log / done + restart /\n * failure detail) sit under the toolbar, above the category bar. */\n.sb81ba5_selfUpdatePanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n}\n\n.s232ec4_searchInput {\n width: min(280px, 100%);\n padding: 6px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n.s232ec4_searchInput:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s1555aa_staleBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.s3946f1_staleBadge {\n padding: 2px 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n color: var(--dsw-alias-state-warn-primary);\n font-size: 12px;\n}\n\n.sb2c859_categoryBar {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 6px;\n}\n\n.s26d58e_categoryButton {\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.s26d58e_categoryButton: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.s5f68c4_categoryButtonOn {\n border-color: var(--dsw-alias-brand-primary);\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-weight: 600;\n}\n\n.s26d58e_categoryButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s668774_catalogHeading {\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.sccc4d1_catalogStats {\n margin: 6px 0 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.s6de1ab_emptyLine {\n margin: 0;\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n}\n\n/* The shelf: a responsive grid of cards. */\n/* Single-line rows: one full-width column, one plugin per line. */\n.sf657f5_cards {\n display: grid;\n grid-template-columns: 1fr;\n max-width: 100%;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n animation: dshShopCardsIn 220ms ease;\n}\n\n@keyframes dshShopCardsIn {\n from { opacity: 0; transform: translateY(3px); }\n to { opacity: 1; transform: none; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sf657f5_cards { animation: none; }\n}\n\n.s65f383_card {\n position: relative;\n display: flex;\n flex-direction: column;\n /* A grid item's min-width is auto by default: its content would widen the\n * 1fr track past the panel and overflow horizontally. */\n min-width: 0;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;\n /* Off-screen cards skip layout and paint; the placeholder keeps scrollbar\n * estimation sane before the browser has measured the real height. */\n content-visibility: auto;\n contain-intrinsic-size: auto 140px;\n}\n\n/* The sentinel that triggers the next shelf batch: zero visual footprint. */\n.sd7ee01_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s705cc6_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s65f383_card:hover {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l1));\n transform: translateY(-1px);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s65f383_card { transition: none; }\n .s65f383_card:hover { transform: none; }\n}\n\n/* The signature: a spine and cover in the category's own hue. The hues are\n * fixed mid-bright colors that read on both themes; `other` deliberately\n * stays a neutral gray so the fallback category does not compete. */\n.s4ce25d_cardSpine {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 3px;\n background: var(--spine-hue, #8B8E96);\n}\n\n.s65f383_card[data-category='tool'] { --spine-hue: #4C8DFF; }\n.s65f383_card[data-category='provider'] { --spine-hue: #A78BFA; }\n.s65f383_card[data-category='ui'] { --spine-hue: #2DD4BF; }\n.s65f383_card[data-category='workflow'] { --spine-hue: #F59E0B; }\n.s65f383_card[data-category='integration'] { --spine-hue: #34D399; }\n.s65f383_card[data-category='theme'] { --spine-hue: #F472B6; }\n.s65f383_card[data-category='other'] { --spine-hue: #8B8E96; }\n\n/* The category sits in the badge row as a quiet chip in the category's own\n * hue — the border carries it where the light theme's white layers cannot\n * (the same lesson as the check button). */\n.s9c8eaa_categoryBadge {\n padding: 1px 7px;\n border: 1px solid color-mix(in srgb, var(--spine-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(--spine-hue, #8B8E96);\n white-space: nowrap;\n}\n\n.sa97234_entryHeader {\n display: flex;\n flex-direction: row;\n align-items: center;\n gap: 12px;\n min-width: 0;\n padding: 8px 12px 2px;\n border: none;\n background: transparent;\n color: inherit;\n font: inherit;\n text-align: left;\n cursor: pointer;\n}\n\n.sa97234_entryHeader:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: -2px;\n}\n\n/* The name fills the row and truncates rather than wraps; the badges stay\n * intact beside it. */\n.sc3f149_name {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n font-weight: 600;\n flex: 1 1 auto;\n min-width: 0;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n color: var(--dsw-alias-label-primary);\n}\n\n.s513026_badges {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px;\n}\n\n/* The action line: buttons side by side; an active install/uninstall flow\n * (gate, log, notices) takes the full width below them. */\n.s9b5048_cardActions {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n align-items: center;\n gap: 8px;\n padding: 8px 12px 10px;\n}\n\n.s9b5048_cardActions > .sc4f2ac_gate,\n.s9b5048_cardActions > .s4d374c_installPanel,\n.s9b5048_cardActions > .sf53ca0_installedActions {\n flex-basis: 100%;\n}\n\n/* The entry's version, always visible on the card (the expanded detail\n * repeats it in the version row) — the same quiet mono as the toolbar. */\n.sc6ec6e_cardVersion {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The octocat marking a github-sourced entry, beside its short commit. */\n.s71f10f_sourceBadge {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s5e82cb_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.s5e82cb_tierBadge[data-tier='verified'] {\n color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-success-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='verified-stale'] {\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n}\n\n.s5e82cb_tierBadge[data-tier='community'] {\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n\n.s4bbe02_incompatibleBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-warn-primary) 40%, transparent);\n}\n\n.sff43b4_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s73f7fb_chevron, .s61393c_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.s61393c_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .s73f7fb_chevron, .s61393c_chevronOpen { transition: none; }\n}\n\n.s820ebf_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.sb8fbc8_summary {\n margin: 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.se507db_summaryZh {\n margin: -4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 1;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* Expanded cards show the full summary: the clamp is lifted, never raised —\n * a higher number would still truncate the author's text, just later. */\n.s10baf0_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.sb9dba9_summaryZhExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.s6eca19_incompatibleDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s9d2029_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s3bdbd5_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s441702_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s441702_capabilities li {\n padding: 1px 6px;\n border-radius: 4px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 6%, transparent);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sde3f0c_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.se98179_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.sfb4d2f_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.sfb4d2f_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.sfb4d2f_detailRow dd {\n margin: 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n overflow-wrap: anywhere;\n text-align: right;\n color: var(--dsw-alias-label-primary);\n}\n\n.sfb4d2f_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.sfb4d2f_detailRow a:hover {\n text-decoration: underline;\n}\n\n.sfb4d2f_detailRow a:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The publishing account, beside the npm link. Secondary label colour and a\n * middot separator carry the distinction — never a background fill, which is\n * invisible in the light theme where every bg-layer token is the same white. */\n/* Who published the entry, at the right edge of the action row. `margin-left:\n * auto` is what pushes it there; the row is a wrapping flex, so an active\n * install flow (which claims the full width) drops this to the next line\n * rather than squeezing it. */\n.s1ff66f_author {\n margin-left: auto;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.sd58c6d_reviewedLine {\n margin: 8px 0 0;\n padding: 6px 8px;\n border-radius: 6px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n font-size: 12px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n/* Install flow. */\n.s4d374c_installPanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 2px;\n}\n\n.se7aaed_installButton, .sb43a49_confirmButton, .s6e45d1_cancelButton {\n align-self: flex-start;\n padding: 5px 14px;\n border-radius: 6px;\n font-size: 13px;\n cursor: pointer;\n}\n\n.se7aaed_installButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.se7aaed_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.sb43a49_confirmButton {\n border: 1px solid var(--dsw-alias-brand-primary);\n background: var(--dsw-alias-brand-primary);\n color: var(--dsw-alias-bg-base);\n}\n\n.sb43a49_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s6e45d1_cancelButton {\n border: 1px solid var(--dsw-alias-border-l2);\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n}\n\n.s6e45d1_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.se7aaed_installButton:focus-visible, .sb43a49_confirmButton:focus-visible, .s6e45d1_cancelButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The shelf card of a plugin that is installed and current carries this\n * non-interactive label instead of an install button. Border + text do the\n * work in the light theme, where every bg-layer token resolves to white. */\n.sb6fc74_installedLabel {\n align-self: flex-start;\n margin: 0;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n font-size: 13px;\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n/* An installed card's controls: the installed label or the update button,\n * plus the uninstall button. The install panel's running/failed states are\n * column layouts and sit as one item of this row. */\n.sf53ca0_installedActions {\n display: flex;\n align-items: flex-start;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n/* Uninstall revokes privilege; the quiet outline keeps it from competing\n * with the primary action, and the error tint says \"removes\". */\n.s96967f_uninstallButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 55%, transparent);\n border-radius: 6px;\n background: transparent;\n color: var(--dsw-alias-state-error-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s96967f_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.s96967f_uninstallButton:focus-visible {\n outline: 2px solid var(--dsw-alias-state-error-primary);\n outline-offset: 1px;\n}\n\n/* The restart dsh button on a done install: quiet outline, same rank as\n * the other secondary actions. */\n.s6ac237_restartButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s6ac237_restartButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s6ac237_restartButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sc4f2ac_gate {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border: 1px solid var(--dsw-alias-state-warn-primary);\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 8%, transparent);\n}\n\n.s559c38_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s3c0ace_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.sc323e5_gateWarning {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s4b2373_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.s52b5a5_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s64ab71_log {\n max-height: 160px;\n overflow: auto;\n margin: 0;\n padding: 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l1);\n list-style: none;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.5;\n}\n\n.s1531a8_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sf20dda_notice {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.seb9b08_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.sfd3021_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s50ba35_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.se75489_rejectedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n/* Installed section. */\n.sa276cf_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.sceae6e_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s8d2072_outdatedRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.s5c2545_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.s5c2545_outdatedInfo strong {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n word-break: break-all;\n color: var(--dsw-alias-label-primary);\n}\n\n.s877c68_outdatedVersions {\n /* Two inline labels with nothing between them in the markup ran together as\n * `installed v1.0.0latest v2.0.0`: JSX drops the whitespace-only line, and\n * the per-label class the markup asked for was never defined (singular\n * against this rule's plural). The container owns the gap, the same idiom\n * as .s513026_badges — the labels need no class of their own. */\n display: inline-flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa1f556_outdatedActions {\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n/* The enable/disable switch. */\n.s11c019_switch {\n position: relative;\n width: 32px;\n height: 18px;\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.s7580e8_switchOn {\n border-color: var(--dsw-alias-state-success-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-success-primary) 55%, transparent);\n}\n\n.saab2b3_switchKnob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 12px;\n height: 12px;\n border-radius: 999px;\n background: var(--dsw-alias-label-primary);\n transition: transform 120ms ease, background 120ms ease;\n}\n\n.s7580e8_switchOn .saab2b3_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.s11c019_switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s11c019_switch, .saab2b3_switchKnob { transition: none; }\n}\n\n/* The hot enable/disable switch on installed rows: the knob button plus the\n * §8 hot-apply / failure notices rendered beside it. */\n.sd9f9e4_switchWrap {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n}\n";
|
|
5197
5237
|
if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"e3675a89\"]")) {
|
|
5198
5238
|
const tag = document.createElement("style");
|
|
5199
5239
|
tag.dataset.pluginCss = "e3675a89";
|
|
@@ -5248,6 +5288,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5248
5288
|
"cardVersion": "sc6ec6e_cardVersion",
|
|
5249
5289
|
"sourceBadge": "s71f10f_sourceBadge",
|
|
5250
5290
|
"tierBadge": "s5e82cb_tierBadge",
|
|
5291
|
+
"incompatibleBadge": "s4bbe02_incompatibleBadge",
|
|
5251
5292
|
"starsBadge": "sff43b4_starsBadge",
|
|
5252
5293
|
"chevron": "s73f7fb_chevron",
|
|
5253
5294
|
"chevronOpen": "s61393c_chevronOpen",
|
|
@@ -5256,13 +5297,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5256
5297
|
"summaryZh": "se507db_summaryZh",
|
|
5257
5298
|
"summaryExpanded": "s10baf0_summaryExpanded",
|
|
5258
5299
|
"summaryZhExpanded": "sb9dba9_summaryZhExpanded",
|
|
5300
|
+
"incompatibleDetail": "s6eca19_incompatibleDetail",
|
|
5259
5301
|
"capabilitiesBlock": "s9d2029_capabilitiesBlock",
|
|
5260
5302
|
"capabilitiesNote": "s3bdbd5_capabilitiesNote",
|
|
5261
5303
|
"capabilities": "s441702_capabilities",
|
|
5262
5304
|
"detail": "sde3f0c_detail",
|
|
5263
5305
|
"detailRows": "se98179_detailRows",
|
|
5264
5306
|
"detailRow": "sfb4d2f_detailRow",
|
|
5265
|
-
"
|
|
5307
|
+
"author": "s1ff66f_author",
|
|
5266
5308
|
"reviewedLine": "sd58c6d_reviewedLine",
|
|
5267
5309
|
"installButton": "se7aaed_installButton",
|
|
5268
5310
|
"confirmButton": "sb43a49_confirmButton",
|
|
@@ -5272,6 +5314,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5272
5314
|
"restartButton": "s6ac237_restartButton",
|
|
5273
5315
|
"gateTitle": "s559c38_gateTitle",
|
|
5274
5316
|
"gateBody": "s3c0ace_gateBody",
|
|
5317
|
+
"gateWarning": "sc323e5_gateWarning",
|
|
5275
5318
|
"gateActions": "s4b2373_gateActions",
|
|
5276
5319
|
"installing": "s52b5a5_installing",
|
|
5277
5320
|
"log": "s64ab71_log",
|
|
@@ -5320,11 +5363,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5320
5363
|
* install controls. An installed plugin's card carries its installed row:
|
|
5321
5364
|
* current → the non-interactive installed label, behind → the update button;
|
|
5322
5365
|
* uninstalled → the install button. */
|
|
5323
|
-
const EntryCard = (0, react.memo)(function EntryCard({ entry, stars, installed, t, install, installStatus, uninstall, restart, restartSupported, setEnabled }) {
|
|
5366
|
+
const EntryCard = (0, react.memo)(function EntryCard({ entry, stars, installed, missing, t, install, installStatus, uninstall, restart, restartSupported, setEnabled }) {
|
|
5324
5367
|
const [open, setOpen] = (0, react.useState)(false);
|
|
5325
5368
|
const detailId = (0, react.useId)();
|
|
5326
5369
|
const summary = entry.catalog?.summary;
|
|
5327
5370
|
const npmUrl = npmPageUrl(entry);
|
|
5371
|
+
const author = authorOf(entry);
|
|
5328
5372
|
const category = entry.catalog?.category ?? "other";
|
|
5329
5373
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5330
5374
|
className: _dsh_plugin_shop_css_e3675a89_default.card,
|
|
@@ -5374,6 +5418,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5374
5418
|
"data-tier": entry.tier,
|
|
5375
5419
|
children: t(tierKey(entry.tier))
|
|
5376
5420
|
}),
|
|
5421
|
+
missing.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5422
|
+
className: _dsh_plugin_shop_css_e3675a89_default.incompatibleBadge,
|
|
5423
|
+
"data-shop-incompatible": true,
|
|
5424
|
+
title: t("incompatibleDetail", { modules: missing.join(", ") }),
|
|
5425
|
+
children: t("incompatibleBadge")
|
|
5426
|
+
}),
|
|
5377
5427
|
stars !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5378
5428
|
className: _dsh_plugin_shop_css_e3675a89_default.starsBadge,
|
|
5379
5429
|
role: "img",
|
|
@@ -5395,6 +5445,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5395
5445
|
className: open ? `${_dsh_plugin_shop_css_e3675a89_default.summaryZh} ${_dsh_plugin_shop_css_e3675a89_default.summaryZhExpanded}` : _dsh_plugin_shop_css_e3675a89_default.summaryZh,
|
|
5396
5446
|
children: summary.zh
|
|
5397
5447
|
}),
|
|
5448
|
+
missing.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5449
|
+
className: _dsh_plugin_shop_css_e3675a89_default.incompatibleDetail,
|
|
5450
|
+
children: t("incompatibleDetail", { modules: missing.join(", ") })
|
|
5451
|
+
}),
|
|
5398
5452
|
open && entry.catalog !== void 0 && entry.catalog.capabilities.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5399
5453
|
className: _dsh_plugin_shop_css_e3675a89_default.capabilitiesBlock,
|
|
5400
5454
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -5414,16 +5468,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5414
5468
|
npmUrl !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5415
5469
|
className: _dsh_plugin_shop_css_e3675a89_default.detailRow,
|
|
5416
5470
|
"data-shop-npm": true,
|
|
5417
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("npmPage") }), /* @__PURE__ */ (0, react_jsx_runtime.
|
|
5471
|
+
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", {
|
|
5418
5472
|
href: npmUrl,
|
|
5419
5473
|
target: "_blank",
|
|
5420
5474
|
rel: "noopener noreferrer",
|
|
5421
5475
|
children: npmUrl
|
|
5422
|
-
})
|
|
5423
|
-
className: _dsh_plugin_shop_css_e3675a89_default.publisher,
|
|
5424
|
-
"data-shop-publisher": true,
|
|
5425
|
-
children: t("publishedBy", { publisher: entry.publisher })
|
|
5426
|
-
})] })]
|
|
5476
|
+
}) })]
|
|
5427
5477
|
}),
|
|
5428
5478
|
entry.repository !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5429
5479
|
className: _dsh_plugin_shop_css_e3675a89_default.detailRow,
|
|
@@ -5452,12 +5502,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5452
5502
|
})
|
|
5453
5503
|
]
|
|
5454
5504
|
}),
|
|
5455
|
-
/* @__PURE__ */ (0, react_jsx_runtime.
|
|
5505
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5456
5506
|
className: _dsh_plugin_shop_css_e3675a89_default.cardActions,
|
|
5457
|
-
|
|
5507
|
+
"data-shop-actions": true,
|
|
5508
|
+
children: [installed === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
5458
5509
|
name: entry.name,
|
|
5459
5510
|
version: entry.version,
|
|
5460
5511
|
tier: entry.tier,
|
|
5512
|
+
missing,
|
|
5461
5513
|
t,
|
|
5462
5514
|
install,
|
|
5463
5515
|
installStatus,
|
|
@@ -5469,6 +5521,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5469
5521
|
version: entry.version,
|
|
5470
5522
|
tier: entry.tier,
|
|
5471
5523
|
variant: "update",
|
|
5524
|
+
missing,
|
|
5472
5525
|
t,
|
|
5473
5526
|
install,
|
|
5474
5527
|
installStatus,
|
|
@@ -5492,7 +5545,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5492
5545
|
restart,
|
|
5493
5546
|
restartSupported
|
|
5494
5547
|
})
|
|
5495
|
-
] })
|
|
5548
|
+
] }), author !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5549
|
+
className: _dsh_plugin_shop_css_e3675a89_default.author,
|
|
5550
|
+
"data-shop-author": true,
|
|
5551
|
+
children: t("authorLine", { author })
|
|
5552
|
+
})]
|
|
5496
5553
|
})
|
|
5497
5554
|
]
|
|
5498
5555
|
});
|
|
@@ -5502,7 +5559,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5502
5559
|
* failure detail, rejection detail — driven by `useInstall`. Shared by the
|
|
5503
5560
|
* catalog cards (`variant: 'install'`) and the outdated rows' update button
|
|
5504
5561
|
* (`variant: 'update'`, which drives the same install flow for `name@latest`). */
|
|
5505
|
-
function InstallPanel({ name, version, tier, variant = "install", t, install, installStatus, restart, restartSupported }) {
|
|
5562
|
+
function InstallPanel({ name, version, tier, missing, variant = "install", t, install, installStatus, restart, restartSupported }) {
|
|
5506
5563
|
const [gateOpen, setGateOpen] = (0, react.useState)(false);
|
|
5507
5564
|
const { view, start } = useInstall(install, installStatus);
|
|
5508
5565
|
if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -5578,6 +5635,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5578
5635
|
className: _dsh_plugin_shop_css_e3675a89_default.gateBody,
|
|
5579
5636
|
children: t("acknowledgementBody")
|
|
5580
5637
|
}),
|
|
5638
|
+
missing.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5639
|
+
className: _dsh_plugin_shop_css_e3675a89_default.gateWarning,
|
|
5640
|
+
"data-shop-incompatible-warning": true,
|
|
5641
|
+
children: t("incompatibleInstallWarning", { modules: missing.join(", ") })
|
|
5642
|
+
}),
|
|
5581
5643
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5582
5644
|
className: _dsh_plugin_shop_css_e3675a89_default.gateActions,
|
|
5583
5645
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -5847,15 +5909,23 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5847
5909
|
/** One outdated install row (§7.3): the name, the installed and latest
|
|
5848
5910
|
* versions, the hot enable/disable switch, and the update button (the
|
|
5849
5911
|
* install flow for `name@latest`, reusing `InstallPanel`). */
|
|
5850
|
-
function OutdatedRow({ row, tier, source, t, setEnabled, install, installStatus, restart, restartSupported }) {
|
|
5912
|
+
function OutdatedRow({ row, tier, source, missing, t, setEnabled, install, installStatus, restart, restartSupported }) {
|
|
5851
5913
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5852
5914
|
className: _dsh_plugin_shop_css_e3675a89_default.outdatedRow,
|
|
5853
5915
|
"data-shop-outdated-entry": row.name,
|
|
5854
5916
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5855
5917
|
className: _dsh_plugin_shop_css_e3675a89_default.outdatedInfo,
|
|
5856
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.
|
|
5857
|
-
className: _dsh_plugin_shop_css_e3675a89_default.
|
|
5858
|
-
children:
|
|
5918
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5919
|
+
className: _dsh_plugin_shop_css_e3675a89_default.badges,
|
|
5920
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5921
|
+
className: _dsh_plugin_shop_css_e3675a89_default.name,
|
|
5922
|
+
children: row.name
|
|
5923
|
+
}), missing.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5924
|
+
className: _dsh_plugin_shop_css_e3675a89_default.incompatibleBadge,
|
|
5925
|
+
"data-shop-incompatible": true,
|
|
5926
|
+
title: t("incompatibleUpdateDetail", { modules: missing.join(", ") }),
|
|
5927
|
+
children: t("incompatibleBadge")
|
|
5928
|
+
})]
|
|
5859
5929
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5860
5930
|
className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersions,
|
|
5861
5931
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("installedVersion", { version: source === "github" ? row.installed.slice(0, 7) : row.installed }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("latestVersion", { version: row.latest }) })]
|
|
@@ -5871,6 +5941,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5871
5941
|
version: row.latest,
|
|
5872
5942
|
tier,
|
|
5873
5943
|
variant: "update",
|
|
5944
|
+
missing,
|
|
5874
5945
|
t,
|
|
5875
5946
|
install,
|
|
5876
5947
|
installStatus,
|
|
@@ -5887,7 +5958,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5887
5958
|
* tier for the update gate is looked up from the catalog by name (community →
|
|
5888
5959
|
* acknowledgement); an entry absent from the catalog defaults to the
|
|
5889
5960
|
* community gate (the safer read). */
|
|
5890
|
-
function OutdatedSection({ state, tiers, sources, t, setEnabled, install, installStatus, restart, restartSupported }) {
|
|
5961
|
+
function OutdatedSection({ state, tiers, sources, missingByName, t, setEnabled, install, installStatus, restart, restartSupported }) {
|
|
5891
5962
|
if (state.kind === "loading") return null;
|
|
5892
5963
|
if (state.kind === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5893
5964
|
className: _dsh_plugin_shop_css_e3675a89_default.stateLine,
|
|
@@ -5908,6 +5979,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5908
5979
|
row,
|
|
5909
5980
|
tier: tiers.get(row.name) ?? "community",
|
|
5910
5981
|
source: sources.get(row.name) ?? "npm",
|
|
5982
|
+
missing: missingByName.get(row.name) ?? [],
|
|
5911
5983
|
t,
|
|
5912
5984
|
setEnabled,
|
|
5913
5985
|
install,
|
|
@@ -6069,6 +6141,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6069
6141
|
if (catalogState.kind === "ready") for (const entry of catalogState.result.plugins) map.set(entry.name, entry.source);
|
|
6070
6142
|
return map;
|
|
6071
6143
|
}, [catalogState]);
|
|
6144
|
+
const missingByName = (0, react.useMemo)(() => {
|
|
6145
|
+
const map = /* @__PURE__ */ new Map();
|
|
6146
|
+
if (catalogState.kind === "ready") for (const entry of catalogState.result.plugins) map.set(entry.name, missingPeersOf(catalogState.result.incompatible, entry.name));
|
|
6147
|
+
return map;
|
|
6148
|
+
}, [catalogState]);
|
|
6072
6149
|
if (catalogState.kind === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6073
6150
|
className: _dsh_plugin_shop_css_e3675a89_default.panel,
|
|
6074
6151
|
"data-shop-tab": true,
|
|
@@ -6307,6 +6384,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6307
6384
|
entry,
|
|
6308
6385
|
stars: starsOf(entry, stars),
|
|
6309
6386
|
installed: installedByName.get(entry.name),
|
|
6387
|
+
missing: missingByName.get(entry.name) ?? [],
|
|
6310
6388
|
t,
|
|
6311
6389
|
install,
|
|
6312
6390
|
installStatus,
|
|
@@ -6332,6 +6410,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6332
6410
|
state: installedState,
|
|
6333
6411
|
tiers,
|
|
6334
6412
|
sources,
|
|
6413
|
+
missingByName,
|
|
6335
6414
|
t,
|
|
6336
6415
|
setEnabled,
|
|
6337
6416
|
install,
|
package/lib/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
1
2
|
import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
|
|
2
3
|
import { loadOptionalPatches, readProfileManifest, resolveProfileDir } from "@deepseek-ai/dsh-app-boot";
|
|
3
4
|
import { lt, minVersion, valid } from "semver";
|
|
@@ -68,7 +69,8 @@ const entrySchema = z.object({
|
|
|
68
69
|
url: z.string(),
|
|
69
70
|
sha256: z.string()
|
|
70
71
|
}).optional(),
|
|
71
|
-
publisher: z.string().optional()
|
|
72
|
+
publisher: z.string().optional(),
|
|
73
|
+
peers: z.array(z.string()).optional()
|
|
72
74
|
});
|
|
73
75
|
const dataSchema = z.object({
|
|
74
76
|
schemaVersion: z.number(),
|
|
@@ -175,13 +177,13 @@ async function loadCatalog(options) {
|
|
|
175
177
|
let data;
|
|
176
178
|
try {
|
|
177
179
|
pointer = pointerSchema.parse(JSON.parse(fsImpl.read(indexPath)));
|
|
178
|
-
if (pointer.schemaVersion >
|
|
180
|
+
if (pointer.schemaVersion > 6) throw new Error(`catalog schemaVersion ${pointer.schemaVersion} is newer than this build supports (6)`);
|
|
179
181
|
const dataPath = join(cacheDir, basename(pointer.plugins.url));
|
|
180
182
|
const dataText = fsImpl.read(dataPath);
|
|
181
183
|
const actual = createHash("sha256").update(dataText).digest("hex");
|
|
182
184
|
if (actual !== pointer.plugins.sha256) throw new Error(`cached catalog data failed integrity check: expected ${pointer.plugins.sha256}, got ${actual}`);
|
|
183
185
|
data = dataSchema.parse(JSON.parse(dataText));
|
|
184
|
-
if (data.schemaVersion >
|
|
186
|
+
if (data.schemaVersion > 6) throw new Error(`catalog schemaVersion ${data.schemaVersion} is newer than this build supports (6)`);
|
|
185
187
|
} catch {
|
|
186
188
|
return null;
|
|
187
189
|
}
|
|
@@ -223,7 +225,7 @@ async function loadCatalog(options) {
|
|
|
223
225
|
throw error;
|
|
224
226
|
}
|
|
225
227
|
const pointer = pointerSchema.parse(JSON.parse(pointerText));
|
|
226
|
-
if (pointer.schemaVersion >
|
|
228
|
+
if (pointer.schemaVersion > 6) throw new Error(`catalog schemaVersion ${pointer.schemaVersion} is newer than this build supports (6)`);
|
|
227
229
|
const dataUrl = resolveDataUrl(baseUrl, pointer.plugins.url);
|
|
228
230
|
let dataText;
|
|
229
231
|
try {
|
|
@@ -241,7 +243,7 @@ async function loadCatalog(options) {
|
|
|
241
243
|
const actual = createHash("sha256").update(dataText).digest("hex");
|
|
242
244
|
if (actual !== pointer.plugins.sha256) throw new Error(`catalog data failed integrity check: expected ${pointer.plugins.sha256}, got ${actual}`);
|
|
243
245
|
const data = dataSchema.parse(JSON.parse(dataText));
|
|
244
|
-
if (data.schemaVersion >
|
|
246
|
+
if (data.schemaVersion > 6) throw new Error(`catalog schemaVersion ${data.schemaVersion} is newer than this build supports (6)`);
|
|
245
247
|
validateEntryCoherence(data.plugins);
|
|
246
248
|
let stars = {};
|
|
247
249
|
if (pointer.stars !== void 0) try {
|
|
@@ -1016,6 +1018,64 @@ function ownsEntryId(owned, entryId) {
|
|
|
1016
1018
|
return tail.startsWith("mkt-") && owned.has(tail.slice(4));
|
|
1017
1019
|
}
|
|
1018
1020
|
//#endregion
|
|
1021
|
+
//#region src/host/peers.ts
|
|
1022
|
+
/** Harness compatibility: which declared peers the running installation does
|
|
1023
|
+
* not provide (design 2026-09-01-harness-compatibility). */
|
|
1024
|
+
/**
|
|
1025
|
+
* The production resolver: the same question the harness's own
|
|
1026
|
+
* ClientModuleRegistry asks, through a require anchored at the profile. Asking
|
|
1027
|
+
* what the loader asks is what keeps this verdict and the runtime's behaviour
|
|
1028
|
+
* from drifting apart.
|
|
1029
|
+
*/
|
|
1030
|
+
function nodeResolver(baseUrl) {
|
|
1031
|
+
const require = createRequire(baseUrl);
|
|
1032
|
+
return (spec) => {
|
|
1033
|
+
try {
|
|
1034
|
+
require.resolve(`${spec}/package.json`);
|
|
1035
|
+
return true;
|
|
1036
|
+
} catch (error) {
|
|
1037
|
+
if (error.code === "MODULE_NOT_FOUND") return false;
|
|
1038
|
+
throw error;
|
|
1039
|
+
}
|
|
1040
|
+
};
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Package name → the peer names that did not resolve. A key is present only
|
|
1044
|
+
* when at least one peer is missing, so an absent key means "runs here, or we
|
|
1045
|
+
* could not tell" — the client renders nothing for either.
|
|
1046
|
+
*
|
|
1047
|
+
* A resolver that throws yields NO verdict at all: an unavailable fact must
|
|
1048
|
+
* never read as an accusation, because one false warning teaches a reader to
|
|
1049
|
+
* ignore every warning.
|
|
1050
|
+
*/
|
|
1051
|
+
function incompatibilityMap(entries, resolve) {
|
|
1052
|
+
const known = /* @__PURE__ */ new Map();
|
|
1053
|
+
const out = {};
|
|
1054
|
+
for (const entry of entries) {
|
|
1055
|
+
if (entry.peers === void 0 || entry.peers.length === 0) continue;
|
|
1056
|
+
const missing = [];
|
|
1057
|
+
let usable = true;
|
|
1058
|
+
for (const spec of entry.peers) {
|
|
1059
|
+
let present = known.get(spec);
|
|
1060
|
+
if (present === void 0) try {
|
|
1061
|
+
present = resolve(spec);
|
|
1062
|
+
known.set(spec, present);
|
|
1063
|
+
} catch {
|
|
1064
|
+
known.set(spec, null);
|
|
1065
|
+
usable = false;
|
|
1066
|
+
break;
|
|
1067
|
+
}
|
|
1068
|
+
else if (present === null) {
|
|
1069
|
+
usable = false;
|
|
1070
|
+
break;
|
|
1071
|
+
}
|
|
1072
|
+
if (!present) missing.push(spec);
|
|
1073
|
+
}
|
|
1074
|
+
if (usable && missing.length > 0) out[entry.name] = missing;
|
|
1075
|
+
}
|
|
1076
|
+
return out;
|
|
1077
|
+
}
|
|
1078
|
+
//#endregion
|
|
1019
1079
|
//#region src/host/index.ts
|
|
1020
1080
|
/** ShopGateway: the Host half of dsh-plugin-shop (§5.1). */
|
|
1021
1081
|
var __runInitializers = function(thisArg, initializers, value) {
|
|
@@ -1274,6 +1334,16 @@ let ShopGateway = (() => {
|
|
|
1274
1334
|
/** The install gate runs against the last loaded snapshot, never a fresh
|
|
1275
1335
|
* fetch per request (§7.2: the Host's cached snapshot is the truth). */
|
|
1276
1336
|
lastSnapshot = null;
|
|
1337
|
+
/** The incompatibility map already computed for `lastSnapshot`, keyed by
|
|
1338
|
+
* that snapshot's own object identity. Design §3 asks for the verdict
|
|
1339
|
+
* once per loaded snapshot, not once per RPC call: `loadCatalog` serves
|
|
1340
|
+
* the same snapshot from its on-disk cache for minutes at a time, so
|
|
1341
|
+
* without this, reopening the tab within that window would re-walk
|
|
1342
|
+
* `node_modules` for every distinct peer name again — and Node only
|
|
1343
|
+
* caches a SUCCESSFUL resolution, so a genuinely missing peer pays a full
|
|
1344
|
+
* failed walk on every single call. Recomputed only when `catalog()`
|
|
1345
|
+
* loads a snapshot that is not this exact object. */
|
|
1346
|
+
incompatibleCache = null;
|
|
1277
1347
|
/** Install records, running and finished; a poll finds one here or reports not found. */
|
|
1278
1348
|
installs = /* @__PURE__ */ new Map();
|
|
1279
1349
|
/** Every install id in insertion order, oldest first; finished-record eviction walks this from the front. */
|
|
@@ -1466,13 +1536,28 @@ let ShopGateway = (() => {
|
|
|
1466
1536
|
refresh: args?.refresh ?? false
|
|
1467
1537
|
});
|
|
1468
1538
|
this.lastSnapshot = snapshot;
|
|
1539
|
+
let incompatible;
|
|
1540
|
+
if (this.incompatibleCache !== null && this.incompatibleCache.snapshot === snapshot) incompatible = this.incompatibleCache.map;
|
|
1541
|
+
else {
|
|
1542
|
+
try {
|
|
1543
|
+
const resolve = this.options.resolvePeer ?? nodeResolver(pathToFileURL(join(this.profileDirResolved(), "cordis.yml")).href);
|
|
1544
|
+
incompatible = incompatibilityMap(snapshot.entries, resolve);
|
|
1545
|
+
} catch {
|
|
1546
|
+
incompatible = {};
|
|
1547
|
+
}
|
|
1548
|
+
this.incompatibleCache = {
|
|
1549
|
+
snapshot,
|
|
1550
|
+
map: incompatible
|
|
1551
|
+
};
|
|
1552
|
+
}
|
|
1469
1553
|
return {
|
|
1470
1554
|
schemaVersion: snapshot.schemaVersion,
|
|
1471
1555
|
builtAt: snapshot.builtAt,
|
|
1472
1556
|
stale,
|
|
1473
1557
|
plugins: snapshot.entries,
|
|
1474
1558
|
denied: snapshot.denied,
|
|
1475
|
-
stars: snapshot.stars
|
|
1559
|
+
stars: snapshot.stars,
|
|
1560
|
+
incompatible
|
|
1476
1561
|
};
|
|
1477
1562
|
}
|
|
1478
1563
|
/**
|
package/lib/typert.host.js
CHANGED
|
@@ -42,6 +42,7 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
|
|
|
42
42
|
'sha256': z.string(),
|
|
43
43
|
})]).optional(),
|
|
44
44
|
'added': z.union([z.undefined(), z.string()]).optional(),
|
|
45
|
+
'peers': z.union([z.undefined(), z.array(z.string())]).optional(),
|
|
45
46
|
})),
|
|
46
47
|
'denied': z.array(z.object({
|
|
47
48
|
'name': z.string(),
|
|
@@ -49,6 +50,7 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
|
|
|
49
50
|
'replacement': z.union([z.undefined(), z.string()]).optional(),
|
|
50
51
|
})),
|
|
51
52
|
'stars': z.record(z.string(), z.number()),
|
|
53
|
+
'incompatible': z.record(z.string(), z.array(z.string())),
|
|
52
54
|
})
|
|
53
55
|
const dsh_plugin_shop_shop_installed_result$schema = z.array(z.object({
|
|
54
56
|
'name': z.string(),
|
|
@@ -152,7 +154,7 @@ export const TYPERT = {
|
|
|
152
154
|
typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
|
|
153
155
|
schema: dsh_plugin_shop_shop_catalog_result$schema,
|
|
154
156
|
},
|
|
155
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
157
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":543,"column":9},
|
|
156
158
|
},
|
|
157
159
|
{
|
|
158
160
|
id: 'dsh-plugin-shop#shop/installed',
|
|
@@ -167,7 +169,7 @@ export const TYPERT = {
|
|
|
167
169
|
typeSymbol: 'dsh-plugin-shop#shop/installed:result',
|
|
168
170
|
schema: dsh_plugin_shop_shop_installed_result$schema,
|
|
169
171
|
},
|
|
170
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
172
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":723,"column":9},
|
|
171
173
|
},
|
|
172
174
|
{
|
|
173
175
|
id: 'dsh-plugin-shop#shop/installStart',
|
|
@@ -193,7 +195,7 @@ export const TYPERT = {
|
|
|
193
195
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
|
|
194
196
|
schema: dsh_plugin_shop_shop_installStart_result$schema,
|
|
195
197
|
},
|
|
196
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
198
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":593,"column":9},
|
|
197
199
|
},
|
|
198
200
|
{
|
|
199
201
|
id: 'dsh-plugin-shop#shop/installStatus',
|
|
@@ -218,7 +220,7 @@ export const TYPERT = {
|
|
|
218
220
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
|
|
219
221
|
schema: dsh_plugin_shop_shop_installStatus_result$schema,
|
|
220
222
|
},
|
|
221
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
223
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":712,"column":3},
|
|
222
224
|
},
|
|
223
225
|
{
|
|
224
226
|
id: 'dsh-plugin-shop#shop/restart',
|
|
@@ -233,7 +235,7 @@ export const TYPERT = {
|
|
|
233
235
|
typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
|
|
234
236
|
schema: dsh_plugin_shop_shop_restart_result$schema,
|
|
235
237
|
},
|
|
236
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
238
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":873,"column":9},
|
|
237
239
|
},
|
|
238
240
|
{
|
|
239
241
|
id: 'dsh-plugin-shop#shop/setEnabled',
|
|
@@ -258,7 +260,7 @@ export const TYPERT = {
|
|
|
258
260
|
typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
|
|
259
261
|
schema: dsh_plugin_shop_shop_setEnabled_result$schema,
|
|
260
262
|
},
|
|
261
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
263
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":460,"column":9},
|
|
262
264
|
},
|
|
263
265
|
{
|
|
264
266
|
id: 'dsh-plugin-shop#shop/uninstallStart',
|
|
@@ -284,7 +286,7 @@ export const TYPERT = {
|
|
|
284
286
|
typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
|
|
285
287
|
schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
|
|
286
288
|
},
|
|
287
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
289
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":815,"column":9},
|
|
288
290
|
},
|
|
289
291
|
{
|
|
290
292
|
id: 'dsh-plugin-shop#shop/updateStart',
|
|
@@ -309,7 +311,7 @@ export const TYPERT = {
|
|
|
309
311
|
typeSymbol: 'dsh-plugin-shop/types#ShopUpdateResult',
|
|
310
312
|
schema: dsh_plugin_shop_shop_updateStart_result$schema,
|
|
311
313
|
},
|
|
312
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
314
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":933,"column":9},
|
|
313
315
|
},
|
|
314
316
|
{
|
|
315
317
|
id: 'dsh-plugin-shop#shop/version',
|
|
@@ -324,7 +326,7 @@ export const TYPERT = {
|
|
|
324
326
|
typeSymbol: 'dsh-plugin-shop/types#ShopVersionResult',
|
|
325
327
|
schema: dsh_plugin_shop_shop_version_result$schema,
|
|
326
328
|
},
|
|
327
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
329
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":917,"column":9},
|
|
328
330
|
},
|
|
329
331
|
],
|
|
330
332
|
model: {
|
|
@@ -410,7 +412,7 @@ export const TYPERT = {
|
|
|
410
412
|
"types": [
|
|
411
413
|
{
|
|
412
414
|
"name": "CatalogEntry",
|
|
413
|
-
"declaration": "export interface CatalogEntry {\n name: string;\n version: string;\n integrity: string | null;\n publishedAt: string | null;\n repository: string | null;\n license: string | null;\n tier: 'verified' | 'verified-stale' | 'community';\n metadata: 'declared' | 'derived';\n review?: { reviewedVersion?: string; reviewedCommit?: string; reviewedSha256?: string; reviewer: string; reviewCommit: string; notes: string; };\n catalog?: CatalogSection;\n source: 'npm' | 'github';\n publisher?: string;\n repo?: string;\n subdir?: string;\n tarball?: { url: string; sha256: string; };\n added?: string;\n}"
|
|
415
|
+
"declaration": "export interface CatalogEntry {\n name: string;\n version: string;\n integrity: string | null;\n publishedAt: string | null;\n repository: string | null;\n license: string | null;\n tier: 'verified' | 'verified-stale' | 'community';\n metadata: 'declared' | 'derived';\n review?: { reviewedVersion?: string; reviewedCommit?: string; reviewedSha256?: string; reviewer: string; reviewCommit: string; notes: string; };\n catalog?: CatalogSection;\n source: 'npm' | 'github';\n publisher?: string;\n repo?: string;\n subdir?: string;\n tarball?: { url: string; sha256: string; };\n added?: string;\n peers?: string[];\n}"
|
|
414
416
|
},
|
|
415
417
|
{
|
|
416
418
|
"name": "CatalogSection",
|
|
@@ -450,7 +452,7 @@ export const TYPERT = {
|
|
|
450
452
|
},
|
|
451
453
|
{
|
|
452
454
|
"name": "ShopCatalogResult",
|
|
453
|
-
"declaration": "export interface ShopCatalogResult {\n schemaVersion: number;\n builtAt: string;\n stale: boolean;\n plugins: CatalogEntry[];\n denied: DeniedEntry[];\n stars: Record<string, number>;\n}"
|
|
455
|
+
"declaration": "export interface ShopCatalogResult {\n schemaVersion: number;\n builtAt: string;\n stale: boolean;\n plugins: CatalogEntry[];\n denied: DeniedEntry[];\n stars: Record<string, number>;\n incompatible: Record<string, string[]>;\n}"
|
|
454
456
|
},
|
|
455
457
|
{
|
|
456
458
|
"name": "ShopInstalledEntry",
|
|
@@ -42,6 +42,7 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
|
|
|
42
42
|
'sha256': z.string(),
|
|
43
43
|
})]).optional(),
|
|
44
44
|
'added': z.union([z.undefined(), z.string()]).optional(),
|
|
45
|
+
'peers': z.union([z.undefined(), z.array(z.string())]).optional(),
|
|
45
46
|
})),
|
|
46
47
|
'denied': z.array(z.object({
|
|
47
48
|
'name': z.string(),
|
|
@@ -49,6 +50,7 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
|
|
|
49
50
|
'replacement': z.union([z.undefined(), z.string()]).optional(),
|
|
50
51
|
})),
|
|
51
52
|
'stars': z.record(z.string(), z.number()),
|
|
53
|
+
'incompatible': z.record(z.string(), z.array(z.string())),
|
|
52
54
|
})
|
|
53
55
|
const dsh_plugin_shop_shop_installed_result$schema = z.array(z.object({
|
|
54
56
|
'name': z.string(),
|
|
@@ -149,7 +151,7 @@ export const TYPERT_REMOTE = {
|
|
|
149
151
|
typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
|
|
150
152
|
schema: dsh_plugin_shop_shop_catalog_result$schema,
|
|
151
153
|
},
|
|
152
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
154
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":543,"column":9},
|
|
153
155
|
},
|
|
154
156
|
{
|
|
155
157
|
id: 'dsh-plugin-shop#shop/installed',
|
|
@@ -164,7 +166,7 @@ export const TYPERT_REMOTE = {
|
|
|
164
166
|
typeSymbol: 'dsh-plugin-shop#shop/installed:result',
|
|
165
167
|
schema: dsh_plugin_shop_shop_installed_result$schema,
|
|
166
168
|
},
|
|
167
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
169
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":723,"column":9},
|
|
168
170
|
},
|
|
169
171
|
{
|
|
170
172
|
id: 'dsh-plugin-shop#shop/installStart',
|
|
@@ -190,7 +192,7 @@ export const TYPERT_REMOTE = {
|
|
|
190
192
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
|
|
191
193
|
schema: dsh_plugin_shop_shop_installStart_result$schema,
|
|
192
194
|
},
|
|
193
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
195
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":593,"column":9},
|
|
194
196
|
},
|
|
195
197
|
{
|
|
196
198
|
id: 'dsh-plugin-shop#shop/installStatus',
|
|
@@ -215,7 +217,7 @@ export const TYPERT_REMOTE = {
|
|
|
215
217
|
typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
|
|
216
218
|
schema: dsh_plugin_shop_shop_installStatus_result$schema,
|
|
217
219
|
},
|
|
218
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
220
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":712,"column":3},
|
|
219
221
|
},
|
|
220
222
|
{
|
|
221
223
|
id: 'dsh-plugin-shop#shop/restart',
|
|
@@ -230,7 +232,7 @@ export const TYPERT_REMOTE = {
|
|
|
230
232
|
typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
|
|
231
233
|
schema: dsh_plugin_shop_shop_restart_result$schema,
|
|
232
234
|
},
|
|
233
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
235
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":873,"column":9},
|
|
234
236
|
},
|
|
235
237
|
{
|
|
236
238
|
id: 'dsh-plugin-shop#shop/setEnabled',
|
|
@@ -255,7 +257,7 @@ export const TYPERT_REMOTE = {
|
|
|
255
257
|
typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
|
|
256
258
|
schema: dsh_plugin_shop_shop_setEnabled_result$schema,
|
|
257
259
|
},
|
|
258
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
260
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":460,"column":9},
|
|
259
261
|
},
|
|
260
262
|
{
|
|
261
263
|
id: 'dsh-plugin-shop#shop/uninstallStart',
|
|
@@ -281,7 +283,7 @@ export const TYPERT_REMOTE = {
|
|
|
281
283
|
typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
|
|
282
284
|
schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
|
|
283
285
|
},
|
|
284
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
286
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":815,"column":9},
|
|
285
287
|
},
|
|
286
288
|
{
|
|
287
289
|
id: 'dsh-plugin-shop#shop/updateStart',
|
|
@@ -306,7 +308,7 @@ export const TYPERT_REMOTE = {
|
|
|
306
308
|
typeSymbol: 'dsh-plugin-shop/types#ShopUpdateResult',
|
|
307
309
|
schema: dsh_plugin_shop_shop_updateStart_result$schema,
|
|
308
310
|
},
|
|
309
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
311
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":933,"column":9},
|
|
310
312
|
},
|
|
311
313
|
{
|
|
312
314
|
id: 'dsh-plugin-shop#shop/version',
|
|
@@ -321,7 +323,7 @@ export const TYPERT_REMOTE = {
|
|
|
321
323
|
typeSymbol: 'dsh-plugin-shop/types#ShopVersionResult',
|
|
322
324
|
schema: dsh_plugin_shop_shop_version_result$schema,
|
|
323
325
|
},
|
|
324
|
-
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":
|
|
326
|
+
sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":917,"column":9},
|
|
325
327
|
},
|
|
326
328
|
],
|
|
327
329
|
}
|
|
@@ -30,6 +30,10 @@ export declare const zh: {
|
|
|
30
30
|
acknowledgementBody: string;
|
|
31
31
|
installing: string;
|
|
32
32
|
installedRestartNotice: string;
|
|
33
|
+
incompatibleBadge: string;
|
|
34
|
+
incompatibleDetail: string;
|
|
35
|
+
incompatibleInstallWarning: string;
|
|
36
|
+
incompatibleUpdateDetail: string;
|
|
33
37
|
hotNoPatchNotice: string;
|
|
34
38
|
hotNotSimpleNotice: string;
|
|
35
39
|
hotHostUnsupportedNotice: string;
|
|
@@ -43,7 +47,7 @@ export declare const zh: {
|
|
|
43
47
|
showing: string;
|
|
44
48
|
repository: string;
|
|
45
49
|
npmPage: string;
|
|
46
|
-
|
|
50
|
+
authorLine: string;
|
|
47
51
|
license: string;
|
|
48
52
|
customLicense: string;
|
|
49
53
|
reviewedVersionLine: string;
|
|
@@ -116,6 +120,10 @@ export declare const en: {
|
|
|
116
120
|
acknowledgementBody: string;
|
|
117
121
|
installing: string;
|
|
118
122
|
installedRestartNotice: string;
|
|
123
|
+
incompatibleBadge: string;
|
|
124
|
+
incompatibleDetail: string;
|
|
125
|
+
incompatibleInstallWarning: string;
|
|
126
|
+
incompatibleUpdateDetail: string;
|
|
119
127
|
hotNoPatchNotice: string;
|
|
120
128
|
hotNotSimpleNotice: string;
|
|
121
129
|
hotHostUnsupportedNotice: string;
|
|
@@ -129,7 +137,7 @@ export declare const en: {
|
|
|
129
137
|
showing: string;
|
|
130
138
|
repository: string;
|
|
131
139
|
npmPage: string;
|
|
132
|
-
|
|
140
|
+
authorLine: string;
|
|
133
141
|
license: string;
|
|
134
142
|
customLicense: string;
|
|
135
143
|
reviewedVersionLine: string;
|
|
@@ -9,6 +9,10 @@ import type { CatalogEntry, HotRestartReason, InstallRejectionCode } from '../ho
|
|
|
9
9
|
export declare function restartReasonKey(reason: HotRestartReason | undefined): ShopLocaleKey;
|
|
10
10
|
/** Tier → locale key, for the entry-card tier badge (§6.2). */
|
|
11
11
|
export declare function tierKey(tier: CatalogEntry['tier']): ShopLocaleKey;
|
|
12
|
+
/** The peers the Host said this installation does not provide, or none when it
|
|
13
|
+
* said nothing — a plugin that runs here and one the Host could not judge are
|
|
14
|
+
* both rendered as no warning at all. */
|
|
15
|
+
export declare function missingPeersOf(incompatible: Record<string, string[]>, name: string): string[];
|
|
12
16
|
/** Spec §9.3 verbatim — the community-tier acknowledgement. The zh dictionary
|
|
13
17
|
* states the same facts in its own register; never soften this wording. */
|
|
14
18
|
export declare const ACKNOWLEDGEMENT_EN = "Once installed, this plugin holds the same privileges as a built-in one: reading and writing your files, running shell commands, and reading and modifying the requests sent to the model. It has not been reviewed.";
|
|
@@ -145,6 +149,24 @@ export declare function categoryLocaleKey(category: Category): ShopLocaleKey;
|
|
|
145
149
|
* A github entry answers null: it is installed from a repo and has no npm
|
|
146
150
|
* package page.
|
|
147
151
|
*/
|
|
152
|
+
/**
|
|
153
|
+
* Who put this entry where it is — the fact a reader wants when two listings
|
|
154
|
+
* carry the same text from different hands.
|
|
155
|
+
*
|
|
156
|
+
* The two sources answer it differently and neither is npm's `author` field:
|
|
157
|
+
* that one is free text the publisher writes, and a clone inherits it
|
|
158
|
+
* verbatim. An npm entry answers with the account npm recorded for the
|
|
159
|
+
* version (`publisher`); a github entry answers with its repository owner,
|
|
160
|
+
* which is already half of the `owner/slug` that IS its identity — so nothing
|
|
161
|
+
* new has to be harvested or emitted for it.
|
|
162
|
+
*
|
|
163
|
+
* `repo` reaches the client as an unvalidated string, so the owner is taken
|
|
164
|
+
* only from a value that really is `owner/slug` with an owner GitHub would
|
|
165
|
+
* accept (1-39 chars of alphanumerics and hyphens). Anything else yields
|
|
166
|
+
* null: a misleading fragment is worse than saying nothing, the same rule
|
|
167
|
+
* npmPageUrl applies to a package name.
|
|
168
|
+
*/
|
|
169
|
+
export declare function authorOf(entry: CatalogEntry): string | null;
|
|
148
170
|
export declare function npmPageUrl(entry: CatalogEntry): string | null;
|
|
149
171
|
/**
|
|
150
172
|
* One entry's star count from the sidecar, or undefined when it has none.
|
|
@@ -4,8 +4,10 @@ import type { CatalogEntry, DeniedEntry } from './types.ts';
|
|
|
4
4
|
* 3 adds `source` and repo entries (github install channel); 4 adds `subdir`
|
|
5
5
|
* for monorepo-subpackage entries (2026-08-31 hub-borrowings A); 5 adds
|
|
6
6
|
* `added`, `tarball` (release rescue), the `theme` category, and
|
|
7
|
-
* `denied[].replacement` (2026-08-31 market borrowings)
|
|
8
|
-
|
|
7
|
+
* `denied[].replacement` (2026-08-31 market borrowings);
|
|
8
|
+
* 6 adds `peers`, the package's declared peer dependency names
|
|
9
|
+
* (2026-09-01 harness compatibility). */
|
|
10
|
+
export declare const SUPPORTED_SCHEMA_VERSION = 6;
|
|
9
11
|
export interface CatalogSnapshot {
|
|
10
12
|
schemaVersion: number;
|
|
11
13
|
builtAt: string;
|
|
@@ -8,6 +8,7 @@ import { type InstallStatus } from './executor.ts';
|
|
|
8
8
|
import { hotMount, hotUnmount } from './hot.ts';
|
|
9
9
|
import { type RestartOutcome } from './restart.ts';
|
|
10
10
|
import { type RepoPinFs } from './repo-pins.ts';
|
|
11
|
+
import { type PeerResolver } from './peers.ts';
|
|
11
12
|
export type { InstallArgs, InstallRejectionCode } from './install.ts';
|
|
12
13
|
export type { HotRestartReason } from './hot.ts';
|
|
13
14
|
export type { CatalogEntry } from './types.ts';
|
|
@@ -94,6 +95,9 @@ export interface ShopGatewayOptions {
|
|
|
94
95
|
/** Test-only injection: how the release-tarball integrity check fetches
|
|
95
96
|
* the release asset; production uses global fetch. */
|
|
96
97
|
fetchTarball?: (url: string) => Promise<Response>;
|
|
98
|
+
/** Test-only injection: answers whether a peer resolves. Production builds
|
|
99
|
+
* one from the profile anchor. */
|
|
100
|
+
resolvePeer?: PeerResolver;
|
|
97
101
|
}
|
|
98
102
|
/** `shop/installStart` result (§7.3): rejections are typed wire values with an
|
|
99
103
|
* author-readable `detail`, not thrown RPC errors. */
|
|
@@ -186,6 +190,10 @@ export interface ShopCatalogResult {
|
|
|
186
190
|
/** GitHub star counts by package name; {} when the pointer names no sidecar
|
|
187
191
|
* or the sidecar could not be fetched/verified (§5). */
|
|
188
192
|
stars: Record<string, number>;
|
|
193
|
+
/** Package name → the declared peers this installation does not provide
|
|
194
|
+
* (design 2026-09-01). A name is absent when the plugin runs here or when
|
|
195
|
+
* no verdict could be formed; the client renders nothing for both. */
|
|
196
|
+
incompatible: Record<string, string[]>;
|
|
189
197
|
}
|
|
190
198
|
/** Remote-only service exposing the shop Remote methods of §7.3.
|
|
191
199
|
*
|
|
@@ -230,6 +238,16 @@ export declare class ShopGateway extends TypertRemoteService {
|
|
|
230
238
|
/** The install gate runs against the last loaded snapshot, never a fresh
|
|
231
239
|
* fetch per request (§7.2: the Host's cached snapshot is the truth). */
|
|
232
240
|
private lastSnapshot;
|
|
241
|
+
/** The incompatibility map already computed for `lastSnapshot`, keyed by
|
|
242
|
+
* that snapshot's own object identity. Design §3 asks for the verdict
|
|
243
|
+
* once per loaded snapshot, not once per RPC call: `loadCatalog` serves
|
|
244
|
+
* the same snapshot from its on-disk cache for minutes at a time, so
|
|
245
|
+
* without this, reopening the tab within that window would re-walk
|
|
246
|
+
* `node_modules` for every distinct peer name again — and Node only
|
|
247
|
+
* caches a SUCCESSFUL resolution, so a genuinely missing peer pays a full
|
|
248
|
+
* failed walk on every single call. Recomputed only when `catalog()`
|
|
249
|
+
* loads a snapshot that is not this exact object. */
|
|
250
|
+
private incompatibleCache;
|
|
233
251
|
/** Install records, running and finished; a poll finds one here or reports not found. */
|
|
234
252
|
private readonly installs;
|
|
235
253
|
/** Every install id in insertion order, oldest first; finished-record eviction walks this from the front. */
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Harness compatibility: which declared peers the running installation does
|
|
2
|
+
* not provide (design 2026-09-01-harness-compatibility). */
|
|
3
|
+
/** Answers "can this installation provide `spec`?" — injected so fixtures
|
|
4
|
+
* drive every verdict and exactly one call site touches the filesystem. */
|
|
5
|
+
export type PeerResolver = (spec: string) => boolean;
|
|
6
|
+
/**
|
|
7
|
+
* The production resolver: the same question the harness's own
|
|
8
|
+
* ClientModuleRegistry asks, through a require anchored at the profile. Asking
|
|
9
|
+
* what the loader asks is what keeps this verdict and the runtime's behaviour
|
|
10
|
+
* from drifting apart.
|
|
11
|
+
*/
|
|
12
|
+
export declare function nodeResolver(baseUrl: string): PeerResolver;
|
|
13
|
+
/**
|
|
14
|
+
* Package name → the peer names that did not resolve. A key is present only
|
|
15
|
+
* when at least one peer is missing, so an absent key means "runs here, or we
|
|
16
|
+
* could not tell" — the client renders nothing for either.
|
|
17
|
+
*
|
|
18
|
+
* A resolver that throws yields NO verdict at all: an unavailable fact must
|
|
19
|
+
* never read as an accusation, because one false warning teaches a reader to
|
|
20
|
+
* ignore every warning.
|
|
21
|
+
*/
|
|
22
|
+
export declare function incompatibilityMap(entries: readonly {
|
|
23
|
+
name: string;
|
|
24
|
+
peers?: string[];
|
|
25
|
+
}[], resolve: PeerResolver): Record<string, string[]>;
|
|
@@ -52,6 +52,8 @@ export interface CatalogEntry {
|
|
|
52
52
|
};
|
|
53
53
|
/** The date this entry first appeared in the catalog (YYYY-MM-DD). */
|
|
54
54
|
added?: string;
|
|
55
|
+
/** The package's declared peer dependency names (schemaVersion 6). */
|
|
56
|
+
peers?: string[];
|
|
55
57
|
}
|
|
56
58
|
export interface DeniedEntry {
|
|
57
59
|
name: string;
|
package/package.json
CHANGED