dsh-plugin-shop 0.5.4 → 0.6.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +72 -19
- 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 +8 -0
- package/lib/types/client/present.d.ts +4 -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.";
|
|
@@ -4906,6 +4914,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4906
4914
|
acknowledgementBody: ACKNOWLEDGEMENT_ZH,
|
|
4907
4915
|
installing: "正在安装…",
|
|
4908
4916
|
installedRestartNotice: "已安装;重启 dsh 后生效",
|
|
4917
|
+
incompatibleBadge: "缺少依赖",
|
|
4918
|
+
incompatibleDetail: "此插件需要 {modules},你当前的 dsh 未提供",
|
|
4919
|
+
incompatibleInstallWarning: "此插件需要 {modules},你当前的 dsh 未提供;安装后它很可能无法加载。",
|
|
4920
|
+
incompatibleUpdateDetail: "更新此插件需要 {modules},你当前的 dsh 未提供",
|
|
4909
4921
|
hotNoPatchNotice: "该插件没有可热挂载的补丁文件;重启 dsh 后生效",
|
|
4910
4922
|
hotNotSimpleNotice: "该插件的补丁包含无法热挂载的配置;重启 dsh 后生效",
|
|
4911
4923
|
hotHostUnsupportedNotice: "当前环境不支持热挂载;重启 dsh 后生效",
|
|
@@ -4990,6 +5002,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4990
5002
|
acknowledgementBody: ACKNOWLEDGEMENT_EN,
|
|
4991
5003
|
installing: "Installing…",
|
|
4992
5004
|
installedRestartNotice: "installed; restart dsh to activate",
|
|
5005
|
+
incompatibleBadge: "missing dependency",
|
|
5006
|
+
incompatibleDetail: "This plugin needs {modules}, which your dsh does not provide",
|
|
5007
|
+
incompatibleInstallWarning: "This plugin needs {modules}, which your dsh does not provide; it will most likely fail to load once installed.",
|
|
5008
|
+
incompatibleUpdateDetail: "Updating this plugin needs {modules}, which your dsh does not provide",
|
|
4993
5009
|
hotNoPatchNotice: "no patch file to hot-mount; restart dsh to activate",
|
|
4994
5010
|
hotNotSimpleNotice: "the patch has config rows that cannot be hot-mounted; restart dsh to activate",
|
|
4995
5011
|
hotHostUnsupportedNotice: "this harness cannot hot-mount; restart dsh to activate",
|
|
@@ -5193,7 +5209,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5193
5209
|
}
|
|
5194
5210
|
//#endregion
|
|
5195
5211
|
//#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";
|
|
5212
|
+
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.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.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
5213
|
if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"e3675a89\"]")) {
|
|
5198
5214
|
const tag = document.createElement("style");
|
|
5199
5215
|
tag.dataset.pluginCss = "e3675a89";
|
|
@@ -5248,6 +5264,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5248
5264
|
"cardVersion": "sc6ec6e_cardVersion",
|
|
5249
5265
|
"sourceBadge": "s71f10f_sourceBadge",
|
|
5250
5266
|
"tierBadge": "s5e82cb_tierBadge",
|
|
5267
|
+
"incompatibleBadge": "s4bbe02_incompatibleBadge",
|
|
5251
5268
|
"starsBadge": "sff43b4_starsBadge",
|
|
5252
5269
|
"chevron": "s73f7fb_chevron",
|
|
5253
5270
|
"chevronOpen": "s61393c_chevronOpen",
|
|
@@ -5256,6 +5273,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5256
5273
|
"summaryZh": "se507db_summaryZh",
|
|
5257
5274
|
"summaryExpanded": "s10baf0_summaryExpanded",
|
|
5258
5275
|
"summaryZhExpanded": "sb9dba9_summaryZhExpanded",
|
|
5276
|
+
"incompatibleDetail": "s6eca19_incompatibleDetail",
|
|
5259
5277
|
"capabilitiesBlock": "s9d2029_capabilitiesBlock",
|
|
5260
5278
|
"capabilitiesNote": "s3bdbd5_capabilitiesNote",
|
|
5261
5279
|
"capabilities": "s441702_capabilities",
|
|
@@ -5272,6 +5290,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5272
5290
|
"restartButton": "s6ac237_restartButton",
|
|
5273
5291
|
"gateTitle": "s559c38_gateTitle",
|
|
5274
5292
|
"gateBody": "s3c0ace_gateBody",
|
|
5293
|
+
"gateWarning": "sc323e5_gateWarning",
|
|
5275
5294
|
"gateActions": "s4b2373_gateActions",
|
|
5276
5295
|
"installing": "s52b5a5_installing",
|
|
5277
5296
|
"log": "s64ab71_log",
|
|
@@ -5320,7 +5339,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5320
5339
|
* install controls. An installed plugin's card carries its installed row:
|
|
5321
5340
|
* current → the non-interactive installed label, behind → the update button;
|
|
5322
5341
|
* uninstalled → the install button. */
|
|
5323
|
-
const EntryCard = (0, react.memo)(function EntryCard({ entry, stars, installed, t, install, installStatus, uninstall, restart, restartSupported, setEnabled }) {
|
|
5342
|
+
const EntryCard = (0, react.memo)(function EntryCard({ entry, stars, installed, missing, t, install, installStatus, uninstall, restart, restartSupported, setEnabled }) {
|
|
5324
5343
|
const [open, setOpen] = (0, react.useState)(false);
|
|
5325
5344
|
const detailId = (0, react.useId)();
|
|
5326
5345
|
const summary = entry.catalog?.summary;
|
|
@@ -5374,6 +5393,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5374
5393
|
"data-tier": entry.tier,
|
|
5375
5394
|
children: t(tierKey(entry.tier))
|
|
5376
5395
|
}),
|
|
5396
|
+
missing.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5397
|
+
className: _dsh_plugin_shop_css_e3675a89_default.incompatibleBadge,
|
|
5398
|
+
"data-shop-incompatible": true,
|
|
5399
|
+
title: t("incompatibleDetail", { modules: missing.join(", ") }),
|
|
5400
|
+
children: t("incompatibleBadge")
|
|
5401
|
+
}),
|
|
5377
5402
|
stars !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5378
5403
|
className: _dsh_plugin_shop_css_e3675a89_default.starsBadge,
|
|
5379
5404
|
role: "img",
|
|
@@ -5395,6 +5420,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5395
5420
|
className: open ? `${_dsh_plugin_shop_css_e3675a89_default.summaryZh} ${_dsh_plugin_shop_css_e3675a89_default.summaryZhExpanded}` : _dsh_plugin_shop_css_e3675a89_default.summaryZh,
|
|
5396
5421
|
children: summary.zh
|
|
5397
5422
|
}),
|
|
5423
|
+
missing.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5424
|
+
className: _dsh_plugin_shop_css_e3675a89_default.incompatibleDetail,
|
|
5425
|
+
children: t("incompatibleDetail", { modules: missing.join(", ") })
|
|
5426
|
+
}),
|
|
5398
5427
|
open && entry.catalog !== void 0 && entry.catalog.capabilities.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5399
5428
|
className: _dsh_plugin_shop_css_e3675a89_default.capabilitiesBlock,
|
|
5400
5429
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -5458,6 +5487,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5458
5487
|
name: entry.name,
|
|
5459
5488
|
version: entry.version,
|
|
5460
5489
|
tier: entry.tier,
|
|
5490
|
+
missing,
|
|
5461
5491
|
t,
|
|
5462
5492
|
install,
|
|
5463
5493
|
installStatus,
|
|
@@ -5469,6 +5499,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5469
5499
|
version: entry.version,
|
|
5470
5500
|
tier: entry.tier,
|
|
5471
5501
|
variant: "update",
|
|
5502
|
+
missing,
|
|
5472
5503
|
t,
|
|
5473
5504
|
install,
|
|
5474
5505
|
installStatus,
|
|
@@ -5502,7 +5533,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5502
5533
|
* failure detail, rejection detail — driven by `useInstall`. Shared by the
|
|
5503
5534
|
* catalog cards (`variant: 'install'`) and the outdated rows' update button
|
|
5504
5535
|
* (`variant: 'update'`, which drives the same install flow for `name@latest`). */
|
|
5505
|
-
function InstallPanel({ name, version, tier, variant = "install", t, install, installStatus, restart, restartSupported }) {
|
|
5536
|
+
function InstallPanel({ name, version, tier, missing, variant = "install", t, install, installStatus, restart, restartSupported }) {
|
|
5506
5537
|
const [gateOpen, setGateOpen] = (0, react.useState)(false);
|
|
5507
5538
|
const { view, start } = useInstall(install, installStatus);
|
|
5508
5539
|
if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -5578,6 +5609,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5578
5609
|
className: _dsh_plugin_shop_css_e3675a89_default.gateBody,
|
|
5579
5610
|
children: t("acknowledgementBody")
|
|
5580
5611
|
}),
|
|
5612
|
+
missing.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5613
|
+
className: _dsh_plugin_shop_css_e3675a89_default.gateWarning,
|
|
5614
|
+
"data-shop-incompatible-warning": true,
|
|
5615
|
+
children: t("incompatibleInstallWarning", { modules: missing.join(", ") })
|
|
5616
|
+
}),
|
|
5581
5617
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5582
5618
|
className: _dsh_plugin_shop_css_e3675a89_default.gateActions,
|
|
5583
5619
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -5847,15 +5883,23 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5847
5883
|
/** One outdated install row (§7.3): the name, the installed and latest
|
|
5848
5884
|
* versions, the hot enable/disable switch, and the update button (the
|
|
5849
5885
|
* install flow for `name@latest`, reusing `InstallPanel`). */
|
|
5850
|
-
function OutdatedRow({ row, tier, source, t, setEnabled, install, installStatus, restart, restartSupported }) {
|
|
5886
|
+
function OutdatedRow({ row, tier, source, missing, t, setEnabled, install, installStatus, restart, restartSupported }) {
|
|
5851
5887
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5852
5888
|
className: _dsh_plugin_shop_css_e3675a89_default.outdatedRow,
|
|
5853
5889
|
"data-shop-outdated-entry": row.name,
|
|
5854
5890
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5855
5891
|
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:
|
|
5892
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5893
|
+
className: _dsh_plugin_shop_css_e3675a89_default.badges,
|
|
5894
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5895
|
+
className: _dsh_plugin_shop_css_e3675a89_default.name,
|
|
5896
|
+
children: row.name
|
|
5897
|
+
}), missing.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
5898
|
+
className: _dsh_plugin_shop_css_e3675a89_default.incompatibleBadge,
|
|
5899
|
+
"data-shop-incompatible": true,
|
|
5900
|
+
title: t("incompatibleUpdateDetail", { modules: missing.join(", ") }),
|
|
5901
|
+
children: t("incompatibleBadge")
|
|
5902
|
+
})]
|
|
5859
5903
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
5860
5904
|
className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersions,
|
|
5861
5905
|
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 +5915,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5871
5915
|
version: row.latest,
|
|
5872
5916
|
tier,
|
|
5873
5917
|
variant: "update",
|
|
5918
|
+
missing,
|
|
5874
5919
|
t,
|
|
5875
5920
|
install,
|
|
5876
5921
|
installStatus,
|
|
@@ -5887,7 +5932,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5887
5932
|
* tier for the update gate is looked up from the catalog by name (community →
|
|
5888
5933
|
* acknowledgement); an entry absent from the catalog defaults to the
|
|
5889
5934
|
* community gate (the safer read). */
|
|
5890
|
-
function OutdatedSection({ state, tiers, sources, t, setEnabled, install, installStatus, restart, restartSupported }) {
|
|
5935
|
+
function OutdatedSection({ state, tiers, sources, missingByName, t, setEnabled, install, installStatus, restart, restartSupported }) {
|
|
5891
5936
|
if (state.kind === "loading") return null;
|
|
5892
5937
|
if (state.kind === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5893
5938
|
className: _dsh_plugin_shop_css_e3675a89_default.stateLine,
|
|
@@ -5908,6 +5953,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5908
5953
|
row,
|
|
5909
5954
|
tier: tiers.get(row.name) ?? "community",
|
|
5910
5955
|
source: sources.get(row.name) ?? "npm",
|
|
5956
|
+
missing: missingByName.get(row.name) ?? [],
|
|
5911
5957
|
t,
|
|
5912
5958
|
setEnabled,
|
|
5913
5959
|
install,
|
|
@@ -6069,6 +6115,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6069
6115
|
if (catalogState.kind === "ready") for (const entry of catalogState.result.plugins) map.set(entry.name, entry.source);
|
|
6070
6116
|
return map;
|
|
6071
6117
|
}, [catalogState]);
|
|
6118
|
+
const missingByName = (0, react.useMemo)(() => {
|
|
6119
|
+
const map = /* @__PURE__ */ new Map();
|
|
6120
|
+
if (catalogState.kind === "ready") for (const entry of catalogState.result.plugins) map.set(entry.name, missingPeersOf(catalogState.result.incompatible, entry.name));
|
|
6121
|
+
return map;
|
|
6122
|
+
}, [catalogState]);
|
|
6072
6123
|
if (catalogState.kind === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6073
6124
|
className: _dsh_plugin_shop_css_e3675a89_default.panel,
|
|
6074
6125
|
"data-shop-tab": true,
|
|
@@ -6307,6 +6358,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6307
6358
|
entry,
|
|
6308
6359
|
stars: starsOf(entry, stars),
|
|
6309
6360
|
installed: installedByName.get(entry.name),
|
|
6361
|
+
missing: missingByName.get(entry.name) ?? [],
|
|
6310
6362
|
t,
|
|
6311
6363
|
install,
|
|
6312
6364
|
installStatus,
|
|
@@ -6332,6 +6384,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6332
6384
|
state: installedState,
|
|
6333
6385
|
tiers,
|
|
6334
6386
|
sources,
|
|
6387
|
+
missingByName,
|
|
6335
6388
|
t,
|
|
6336
6389
|
setEnabled,
|
|
6337
6390
|
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;
|
|
@@ -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;
|
|
@@ -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.";
|
|
@@ -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