dsh-plugin-shop 0.4.1 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/lib/client.js +303 -87
- package/lib/index.js +144 -36
- package/lib/typert.host.js +76 -28
- package/lib/typert.remote-client.d.ts +5 -3
- package/lib/typert.remote-client.js +57 -20
- package/lib/types/client/ShopTab.d.ts +5 -2
- package/lib/types/client/index.d.ts +1 -1
- package/lib/types/client/locales.d.ts +12 -0
- package/lib/types/client/useInstall.d.ts +9 -0
- package/lib/types/client/useUninstall.d.ts +21 -0
- package/lib/types/host/executor.d.ts +18 -6
- package/lib/types/host/index.d.ts +35 -4
- package/package.json +1 -1
package/lib/client.js
CHANGED
|
@@ -4306,6 +4306,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4306
4306
|
})),
|
|
4307
4307
|
"stars": record(string(), number())
|
|
4308
4308
|
});
|
|
4309
|
+
const dsh_plugin_shop_shop_installed_result$schema = array(object({
|
|
4310
|
+
"name": string(),
|
|
4311
|
+
"installed": string(),
|
|
4312
|
+
"latest": string(),
|
|
4313
|
+
"outdated": boolean()
|
|
4314
|
+
}));
|
|
4309
4315
|
const dsh_plugin_shop_shop_installStart_parameter_0$schema = object({
|
|
4310
4316
|
"name": string(),
|
|
4311
4317
|
"version": string(),
|
|
@@ -4344,11 +4350,6 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4344
4350
|
]).optional(),
|
|
4345
4351
|
"detail": union([_undefined(), string()]).optional()
|
|
4346
4352
|
});
|
|
4347
|
-
const dsh_plugin_shop_shop_outdated_result$schema = array(object({
|
|
4348
|
-
"name": string(),
|
|
4349
|
-
"installed": string(),
|
|
4350
|
-
"latest": string()
|
|
4351
|
-
}));
|
|
4352
4353
|
const dsh_plugin_shop_shop_setEnabled_parameter_0$schema = object({
|
|
4353
4354
|
"name": string(),
|
|
4354
4355
|
"enabled": boolean()
|
|
@@ -4357,6 +4358,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4357
4358
|
"ok": boolean(),
|
|
4358
4359
|
"detail": union([_undefined(), string()]).optional()
|
|
4359
4360
|
});
|
|
4361
|
+
const dsh_plugin_shop_shop_uninstallStart_parameter_0$schema = object({ "name": string() });
|
|
4362
|
+
const dsh_plugin_shop_shop_uninstallStart_result$schema = union([object({
|
|
4363
|
+
"ok": literal(true),
|
|
4364
|
+
"installId": string()
|
|
4365
|
+
}), object({
|
|
4366
|
+
"ok": literal(false),
|
|
4367
|
+
"detail": string()
|
|
4368
|
+
})]);
|
|
4360
4369
|
const TYPERT_REMOTE = {
|
|
4361
4370
|
package: "dsh-plugin-shop",
|
|
4362
4371
|
descriptors: [
|
|
@@ -4384,7 +4393,25 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4384
4393
|
},
|
|
4385
4394
|
sourceLocation: {
|
|
4386
4395
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4387
|
-
"line":
|
|
4396
|
+
"line": 190,
|
|
4397
|
+
"column": 9
|
|
4398
|
+
}
|
|
4399
|
+
},
|
|
4400
|
+
{
|
|
4401
|
+
id: "dsh-plugin-shop#shop/installed",
|
|
4402
|
+
service: "shop",
|
|
4403
|
+
namespace: "shop",
|
|
4404
|
+
method: "installed",
|
|
4405
|
+
invocation: { kind: "direct" },
|
|
4406
|
+
parameters: [],
|
|
4407
|
+
result: {
|
|
4408
|
+
mode: "strict",
|
|
4409
|
+
typeSymbol: "dsh-plugin-shop#shop/installed:result",
|
|
4410
|
+
schema: dsh_plugin_shop_shop_installed_result$schema
|
|
4411
|
+
},
|
|
4412
|
+
sourceLocation: {
|
|
4413
|
+
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4414
|
+
"line": 265,
|
|
4388
4415
|
"column": 9
|
|
4389
4416
|
}
|
|
4390
4417
|
},
|
|
@@ -4412,7 +4439,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4412
4439
|
},
|
|
4413
4440
|
sourceLocation: {
|
|
4414
4441
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4415
|
-
"line":
|
|
4442
|
+
"line": 216,
|
|
4416
4443
|
"column": 9
|
|
4417
4444
|
}
|
|
4418
4445
|
},
|
|
@@ -4439,33 +4466,43 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4439
4466
|
},
|
|
4440
4467
|
sourceLocation: {
|
|
4441
4468
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4442
|
-
"line":
|
|
4469
|
+
"line": 254,
|
|
4443
4470
|
"column": 3
|
|
4444
4471
|
}
|
|
4445
4472
|
},
|
|
4446
4473
|
{
|
|
4447
|
-
id: "dsh-plugin-shop#shop/
|
|
4474
|
+
id: "dsh-plugin-shop#shop/setEnabled",
|
|
4448
4475
|
service: "shop",
|
|
4449
4476
|
namespace: "shop",
|
|
4450
|
-
method: "
|
|
4477
|
+
method: "setEnabled",
|
|
4451
4478
|
invocation: { kind: "direct" },
|
|
4452
|
-
parameters: [
|
|
4479
|
+
parameters: [{
|
|
4480
|
+
name: "args",
|
|
4481
|
+
wire: "args",
|
|
4482
|
+
source: "json",
|
|
4483
|
+
codec: {
|
|
4484
|
+
mode: "strict",
|
|
4485
|
+
typeSymbol: "dsh-plugin-shop#shop/setEnabled:args",
|
|
4486
|
+
schema: dsh_plugin_shop_shop_setEnabled_parameter_0$schema
|
|
4487
|
+
}
|
|
4488
|
+
}],
|
|
4453
4489
|
result: {
|
|
4454
4490
|
mode: "strict",
|
|
4455
|
-
typeSymbol: "dsh-plugin-shop#
|
|
4456
|
-
schema:
|
|
4491
|
+
typeSymbol: "dsh-plugin-shop/types#ShopSetEnabledResult",
|
|
4492
|
+
schema: dsh_plugin_shop_shop_setEnabled_result$schema
|
|
4457
4493
|
},
|
|
4458
4494
|
sourceLocation: {
|
|
4459
4495
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4460
|
-
"line":
|
|
4461
|
-
"column":
|
|
4496
|
+
"line": 159,
|
|
4497
|
+
"column": 3
|
|
4462
4498
|
}
|
|
4463
4499
|
},
|
|
4464
4500
|
{
|
|
4465
|
-
id: "dsh-plugin-shop#shop/
|
|
4501
|
+
id: "dsh-plugin-shop#shop/uninstallStart",
|
|
4466
4502
|
service: "shop",
|
|
4467
4503
|
namespace: "shop",
|
|
4468
|
-
method: "
|
|
4504
|
+
method: "uninstallStart",
|
|
4505
|
+
implementation: "uninstall",
|
|
4469
4506
|
invocation: { kind: "direct" },
|
|
4470
4507
|
parameters: [{
|
|
4471
4508
|
name: "args",
|
|
@@ -4473,19 +4510,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4473
4510
|
source: "json",
|
|
4474
4511
|
codec: {
|
|
4475
4512
|
mode: "strict",
|
|
4476
|
-
typeSymbol: "dsh-plugin-shop#shop/
|
|
4477
|
-
schema:
|
|
4513
|
+
typeSymbol: "dsh-plugin-shop#shop/uninstallStart:args",
|
|
4514
|
+
schema: dsh_plugin_shop_shop_uninstallStart_parameter_0$schema
|
|
4478
4515
|
}
|
|
4479
4516
|
}],
|
|
4480
4517
|
result: {
|
|
4481
4518
|
mode: "strict",
|
|
4482
|
-
typeSymbol: "dsh-plugin-shop/types#
|
|
4483
|
-
schema:
|
|
4519
|
+
typeSymbol: "dsh-plugin-shop/types#ShopUninstallResult",
|
|
4520
|
+
schema: dsh_plugin_shop_shop_uninstallStart_result$schema
|
|
4484
4521
|
},
|
|
4485
4522
|
sourceLocation: {
|
|
4486
4523
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4487
|
-
"line":
|
|
4488
|
-
"column":
|
|
4524
|
+
"line": 305,
|
|
4525
|
+
"column": 9
|
|
4489
4526
|
}
|
|
4490
4527
|
}
|
|
4491
4528
|
]
|
|
@@ -4691,6 +4728,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4691
4728
|
installedVersion: "已安装 v{version}",
|
|
4692
4729
|
latestVersion: "最新 v{version}",
|
|
4693
4730
|
update: "更新",
|
|
4731
|
+
installed: "已安装",
|
|
4732
|
+
uninstall: "卸载",
|
|
4733
|
+
uninstalling: "正在卸载…",
|
|
4734
|
+
uninstallFailed: "卸载失败",
|
|
4735
|
+
uninstallTransportFailed: "卸载请求未能送达。请稍后重试。",
|
|
4736
|
+
uninstalledRestartNotice: "已卸载;重启 dsh 后生效",
|
|
4694
4737
|
enabledSwitch: "启用",
|
|
4695
4738
|
toggleFailed: "设置失败,请重试。",
|
|
4696
4739
|
hotApplyNote: "无需重启,立即生效"
|
|
@@ -4744,6 +4787,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4744
4787
|
installedVersion: "installed v{version}",
|
|
4745
4788
|
latestVersion: "latest v{version}",
|
|
4746
4789
|
update: "Update",
|
|
4790
|
+
installed: "Installed",
|
|
4791
|
+
uninstall: "Uninstall",
|
|
4792
|
+
uninstalling: "Uninstalling…",
|
|
4793
|
+
uninstallFailed: "Uninstall failed",
|
|
4794
|
+
uninstallTransportFailed: "The uninstall request could not be delivered. Please retry.",
|
|
4795
|
+
uninstalledRestartNotice: "uninstalled; restart dsh to activate",
|
|
4747
4796
|
enabledSwitch: "Enable",
|
|
4748
4797
|
toggleFailed: "Failed to update. Please retry.",
|
|
4749
4798
|
hotApplyNote: "takes effect without a restart"
|
|
@@ -4751,6 +4800,29 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4751
4800
|
//#endregion
|
|
4752
4801
|
//#region src/client/useInstall.ts
|
|
4753
4802
|
/** Install driving hook for one entry: start, poll to terminal, reset. */
|
|
4803
|
+
/** The poll loop shared by the install and uninstall drivers: while the view
|
|
4804
|
+
* is `running`, poll once per second and fold each status through the
|
|
4805
|
+
* reducer. A poll failure is transient — the host retains the record, so the
|
|
4806
|
+
* next tick finds it. The rejection handler must be present: an unhandled
|
|
4807
|
+
* rejection here would escape the poll loop. */
|
|
4808
|
+
function usePollStatus(view, setView, installStatus) {
|
|
4809
|
+
(0, react.useEffect)(() => {
|
|
4810
|
+
if (view.kind !== "running") return;
|
|
4811
|
+
const timer = setInterval(() => {
|
|
4812
|
+
installStatus({ installId: view.installId }).then((status) => {
|
|
4813
|
+
setView((current) => reduceInstall(current, {
|
|
4814
|
+
type: "status",
|
|
4815
|
+
status
|
|
4816
|
+
}));
|
|
4817
|
+
}, () => {});
|
|
4818
|
+
}, INSTALL_POLL_MS);
|
|
4819
|
+
return () => clearInterval(timer);
|
|
4820
|
+
}, [
|
|
4821
|
+
view,
|
|
4822
|
+
setView,
|
|
4823
|
+
installStatus
|
|
4824
|
+
]);
|
|
4825
|
+
}
|
|
4754
4826
|
/** Drive one install: rejections, the polling loop at INSTALL_POLL_MS, and
|
|
4755
4827
|
* terminal states. The interval is cleared on unmount and whenever the view
|
|
4756
4828
|
* leaves `running`, so a done/failed/rejected install never polls again. */
|
|
@@ -4781,18 +4853,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4781
4853
|
});
|
|
4782
4854
|
}
|
|
4783
4855
|
}, [install]);
|
|
4784
|
-
(
|
|
4785
|
-
if (view.kind !== "running") return;
|
|
4786
|
-
const timer = setInterval(() => {
|
|
4787
|
-
installStatus({ installId: view.installId }).then((status) => {
|
|
4788
|
-
setView((current) => reduceInstall(current, {
|
|
4789
|
-
type: "status",
|
|
4790
|
-
status
|
|
4791
|
-
}));
|
|
4792
|
-
}, () => {});
|
|
4793
|
-
}, INSTALL_POLL_MS);
|
|
4794
|
-
return () => clearInterval(timer);
|
|
4795
|
-
}, [view, installStatus]);
|
|
4856
|
+
usePollStatus(view, setView, installStatus);
|
|
4796
4857
|
return {
|
|
4797
4858
|
view,
|
|
4798
4859
|
start,
|
|
@@ -4800,8 +4861,51 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4800
4861
|
};
|
|
4801
4862
|
}
|
|
4802
4863
|
//#endregion
|
|
4864
|
+
//#region src/client/useUninstall.ts
|
|
4865
|
+
/** Uninstall driving hook for one entry: start, poll to terminal. Shares the
|
|
4866
|
+
* install view machine and poll loop; the only difference is the start
|
|
4867
|
+
* mapping — an uninstall business failure (not in the catalog / not
|
|
4868
|
+
* installed) lands in the `failed` view with the host's published detail,
|
|
4869
|
+
* never in the install `rejected` codes, which belong to the install gate
|
|
4870
|
+
* (§7.2). */
|
|
4871
|
+
/** Drive one uninstall: the host's business union, the shared polling loop,
|
|
4872
|
+
* and terminal states. The `rejected` install state never occurs here. */
|
|
4873
|
+
function useUninstall(uninstall, installStatus) {
|
|
4874
|
+
const [view, setView] = (0, react.useState)({ kind: "idle" });
|
|
4875
|
+
const start = (0, react.useCallback)(async (args) => {
|
|
4876
|
+
setView({ kind: "idle" });
|
|
4877
|
+
try {
|
|
4878
|
+
const result = await uninstall(args);
|
|
4879
|
+
if (!result.ok) {
|
|
4880
|
+
setView({
|
|
4881
|
+
kind: "failed",
|
|
4882
|
+
detail: result.detail,
|
|
4883
|
+
log: []
|
|
4884
|
+
});
|
|
4885
|
+
return;
|
|
4886
|
+
}
|
|
4887
|
+
setView({
|
|
4888
|
+
kind: "running",
|
|
4889
|
+
installId: result.installId,
|
|
4890
|
+
log: []
|
|
4891
|
+
});
|
|
4892
|
+
} catch {
|
|
4893
|
+
setView({
|
|
4894
|
+
kind: "failed",
|
|
4895
|
+
detail: "",
|
|
4896
|
+
log: []
|
|
4897
|
+
});
|
|
4898
|
+
}
|
|
4899
|
+
}, [uninstall]);
|
|
4900
|
+
usePollStatus(view, setView, installStatus);
|
|
4901
|
+
return {
|
|
4902
|
+
view,
|
|
4903
|
+
start
|
|
4904
|
+
};
|
|
4905
|
+
}
|
|
4906
|
+
//#endregion
|
|
4803
4907
|
//#region \0dsh-plugin-shop-css:e3675a89
|
|
4804
|
-
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 (six fixed hues, not the brand token — the brand resolves to\n * near-black in the light theme, so six brand opacities read as six shades\n * 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}\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: repeat(auto-fill, minmax(260px, 1fr));\n gap: 12px;\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.s30e662_skeletonCover {\n display: block;\n height: 26px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 10%, transparent);\n}\n\n.s8c7d6f_skeletonName {\n width: 55%;\n height: 14px;\n margin: 12px 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 14px;\n border-radius: 4px;\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.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: 10px;\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: -8px 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.sf657f5_cards {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));\n gap: 12px;\n list-style: none;\n margin: 0;\n padding: 0;\n animation: dshShopCardsIn 220ms ease;\n}\n\n@keyframes dshShopCardsIn {\n from { opacity: 0; transform: translateY(3px); }\n to { opacity: 1; transform: none; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sf657f5_cards { animation: none; }\n}\n\n.s65f383_card {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;\n /* Off-screen cards skip layout and paint; the placeholder keeps scrollbar\n * estimation sane before the browser has measured the real height. */\n content-visibility: auto;\n contain-intrinsic-size: auto 240px;\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='other'] { --spine-hue: #8B8E96; }\n\n.s9e2bec_cardCover {\n display: block;\n padding: 5px 12px 5px 12px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n background: color-mix(in srgb, var(--spine-hue, #8B8E96) 12%, transparent);\n}\n\n.s1a7c5b_coverLabel {\n display: block;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--spine-hue, #8B8E96);\n}\n\n.sa97234_entryHeader {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: 8px;\n padding: 10px 12px 6px;\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 owns the full header width; badges sit on their own line below. */\n.sc3f149_name {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n font-weight: 600;\n overflow-wrap: anywhere;\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.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.s23e3e1_unclaimedBadge {\n padding: 1px 7px;\n border-radius: 999px;\n border: 1px dashed var(--dsw-alias-border-l2);\n font-size: 11px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary);\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 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.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.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 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";
|
|
4908
|
+
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 (six fixed hues, not the brand token — the brand resolves to\n * near-black in the light theme, so six brand opacities read as six shades\n * 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}\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: repeat(auto-fill, minmax(260px, 1fr));\n gap: 12px;\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.s30e662_skeletonCover {\n display: block;\n height: 26px;\n background: color-mix(in srgb, var(--dsw-alias-label-primary) 10%, transparent);\n}\n\n.s8c7d6f_skeletonName {\n width: 55%;\n height: 14px;\n margin: 12px 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 14px;\n border-radius: 4px;\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.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.sf657f5_cards {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(260px, 1fr));\n gap: 12px;\n list-style: none;\n margin: 0;\n padding: 0;\n animation: dshShopCardsIn 220ms ease;\n}\n\n@keyframes dshShopCardsIn {\n from { opacity: 0; transform: translateY(3px); }\n to { opacity: 1; transform: none; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .sf657f5_cards { animation: none; }\n}\n\n.s65f383_card {\n position: relative;\n display: flex;\n flex-direction: column;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 10px;\n background: var(--dsw-alias-bg-layer-1);\n overflow: hidden;\n transition: border-color 120ms ease, transform 120ms ease, box-shadow 120ms ease;\n /* Off-screen cards skip layout and paint; the placeholder keeps scrollbar\n * estimation sane before the browser has measured the real height. */\n content-visibility: auto;\n contain-intrinsic-size: auto 240px;\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='other'] { --spine-hue: #8B8E96; }\n\n.s9e2bec_cardCover {\n display: block;\n padding: 5px 12px 5px 12px;\n border-bottom: 1px solid var(--dsw-alias-border-l1);\n background: color-mix(in srgb, var(--spine-hue, #8B8E96) 12%, transparent);\n}\n\n.s1a7c5b_coverLabel {\n display: block;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--spine-hue, #8B8E96);\n}\n\n.sa97234_entryHeader {\n display: flex;\n flex-direction: column;\n align-items: stretch;\n gap: 8px;\n padding: 10px 12px 6px;\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 owns the full header width; badges sit on their own line below. */\n.sc3f149_name {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 13px;\n font-weight: 600;\n overflow-wrap: anywhere;\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.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.s23e3e1_unclaimedBadge {\n padding: 1px 7px;\n border-radius: 999px;\n border: 1px dashed var(--dsw-alias-border-l2);\n font-size: 11px;\n line-height: 1.6;\n color: var(--dsw-alias-label-secondary);\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 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.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.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 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";
|
|
4805
4909
|
if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"e3675a89\"]")) {
|
|
4806
4910
|
const tag = document.createElement("style");
|
|
4807
4911
|
tag.dataset.pluginCss = "e3675a89";
|
|
@@ -4864,6 +4968,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4864
4968
|
"installButton": "se7aaed_installButton",
|
|
4865
4969
|
"confirmButton": "sb43a49_confirmButton",
|
|
4866
4970
|
"cancelButton": "s6e45d1_cancelButton",
|
|
4971
|
+
"installedLabel": "sb6fc74_installedLabel",
|
|
4972
|
+
"installedActions": "sf53ca0_installedActions",
|
|
4973
|
+
"uninstallButton": "s96967f_uninstallButton",
|
|
4867
4974
|
"gate": "sc4f2ac_gate",
|
|
4868
4975
|
"gateTitle": "s559c38_gateTitle",
|
|
4869
4976
|
"gateBody": "s3c0ace_gateBody",
|
|
@@ -4909,8 +5016,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4909
5016
|
/** One entry card: the category spine and cover band, the expandable header
|
|
4910
5017
|
* (name, tier, category, unclaimed marker), the plain-text summary in both
|
|
4911
5018
|
* languages, the self-declared capabilities, the detail section, and the
|
|
4912
|
-
* install controls.
|
|
4913
|
-
|
|
5019
|
+
* install controls. An installed plugin's card carries its installed row:
|
|
5020
|
+
* current → the non-interactive installed label, behind → the update button;
|
|
5021
|
+
* uninstalled → the install button. */
|
|
5022
|
+
const EntryCard = (0, react.memo)(function EntryCard({ entry, stars, installed, t, install, installStatus, uninstall }) {
|
|
4914
5023
|
const [open, setOpen] = (0, react.useState)(false);
|
|
4915
5024
|
const detailId = (0, react.useId)();
|
|
4916
5025
|
const summary = entry.catalog?.summary;
|
|
@@ -5016,13 +5125,33 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5016
5125
|
})
|
|
5017
5126
|
})]
|
|
5018
5127
|
}),
|
|
5019
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
5128
|
+
installed === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
5020
5129
|
name: entry.name,
|
|
5021
5130
|
version: entry.version,
|
|
5022
5131
|
tier: entry.tier,
|
|
5023
5132
|
t,
|
|
5024
5133
|
install,
|
|
5025
5134
|
installStatus
|
|
5135
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5136
|
+
className: _dsh_plugin_shop_css_e3675a89_default.installedActions,
|
|
5137
|
+
children: [installed.outdated ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
5138
|
+
name: entry.name,
|
|
5139
|
+
version: entry.version,
|
|
5140
|
+
tier: entry.tier,
|
|
5141
|
+
variant: "update",
|
|
5142
|
+
t,
|
|
5143
|
+
install,
|
|
5144
|
+
installStatus
|
|
5145
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5146
|
+
className: _dsh_plugin_shop_css_e3675a89_default.installedLabel,
|
|
5147
|
+
"data-shop-installed": true,
|
|
5148
|
+
children: t("installed")
|
|
5149
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
|
|
5150
|
+
name: entry.name,
|
|
5151
|
+
t,
|
|
5152
|
+
uninstall,
|
|
5153
|
+
installStatus
|
|
5154
|
+
})]
|
|
5026
5155
|
})
|
|
5027
5156
|
]
|
|
5028
5157
|
})
|
|
@@ -5136,6 +5265,61 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5136
5265
|
children: t(update ? "update" : "install")
|
|
5137
5266
|
});
|
|
5138
5267
|
}
|
|
5268
|
+
/** One installed entry's uninstall flow: remove from the profile through the
|
|
5269
|
+
* same executor records and poll loop as installs. Uninstalling revokes
|
|
5270
|
+
* privilege rather than granting it, so there is no acknowledgement gate —
|
|
5271
|
+
* §9.3 is about granting. A business failure (not in the catalog / not
|
|
5272
|
+
* installed) lands in the failed view with the host's published detail; a
|
|
5273
|
+
* transport failure carries the empty detail and the localized fallback. */
|
|
5274
|
+
function UninstallPanel({ name, t, uninstall, installStatus }) {
|
|
5275
|
+
const { view, start } = useUninstall(uninstall, installStatus);
|
|
5276
|
+
if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5277
|
+
className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
|
|
5278
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5279
|
+
className: _dsh_plugin_shop_css_e3675a89_default.installing,
|
|
5280
|
+
children: t("uninstalling")
|
|
5281
|
+
}), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5282
|
+
className: _dsh_plugin_shop_css_e3675a89_default.log,
|
|
5283
|
+
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5284
|
+
className: _dsh_plugin_shop_css_e3675a89_default.logLine,
|
|
5285
|
+
children: line
|
|
5286
|
+
}, index))
|
|
5287
|
+
})]
|
|
5288
|
+
});
|
|
5289
|
+
if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5290
|
+
className: _dsh_plugin_shop_css_e3675a89_default.notice,
|
|
5291
|
+
"data-shop-uninstall-done": true,
|
|
5292
|
+
children: t("uninstalledRestartNotice")
|
|
5293
|
+
});
|
|
5294
|
+
if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
5295
|
+
className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
|
|
5296
|
+
children: [
|
|
5297
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5298
|
+
className: _dsh_plugin_shop_css_e3675a89_default.failedHeading,
|
|
5299
|
+
children: t("uninstallFailed")
|
|
5300
|
+
}),
|
|
5301
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5302
|
+
className: _dsh_plugin_shop_css_e3675a89_default.log,
|
|
5303
|
+
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
5304
|
+
className: _dsh_plugin_shop_css_e3675a89_default.logLine,
|
|
5305
|
+
children: line
|
|
5306
|
+
}, index))
|
|
5307
|
+
}),
|
|
5308
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5309
|
+
className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
|
|
5310
|
+
children: view.detail === "" ? t("uninstallTransportFailed") : view.detail
|
|
5311
|
+
})
|
|
5312
|
+
]
|
|
5313
|
+
});
|
|
5314
|
+
if (view.kind !== "idle") return null;
|
|
5315
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
5316
|
+
type: "button",
|
|
5317
|
+
className: _dsh_plugin_shop_css_e3675a89_default.uninstallButton,
|
|
5318
|
+
"data-shop-uninstall": true,
|
|
5319
|
+
onClick: () => void start({ name }),
|
|
5320
|
+
children: t("uninstall")
|
|
5321
|
+
});
|
|
5322
|
+
}
|
|
5139
5323
|
/** One outdated install row (§7.3): the name, the installed and latest
|
|
5140
5324
|
* versions, an optimistic enable/disable switch, and the update button (the
|
|
5141
5325
|
* install flow for `name@latest`, reusing `InstallPanel`). The switch has no
|
|
@@ -5223,10 +5407,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5223
5407
|
]
|
|
5224
5408
|
});
|
|
5225
5409
|
}
|
|
5226
|
-
/** The §7.3
|
|
5227
|
-
* both versions, a switch, and an update button. The
|
|
5228
|
-
*
|
|
5229
|
-
*
|
|
5410
|
+
/** The §7.3 installed list, rendered as the "installed" section: each row
|
|
5411
|
+
* shows both versions, a switch, and an update button. The rows are the
|
|
5412
|
+
* installed entries filtered to `outdated` — a current install is already
|
|
5413
|
+
* spoken for by its shelf card's installed label, and has no row here. The
|
|
5414
|
+
* tier for the update gate is looked up from the catalog by name (community →
|
|
5415
|
+
* acknowledgement); an entry absent from the catalog defaults to the
|
|
5416
|
+
* community gate (the safer read). */
|
|
5230
5417
|
function OutdatedSection({ state, tiers, t, setEnabled, install, installStatus }) {
|
|
5231
5418
|
if (state.kind === "loading") return null;
|
|
5232
5419
|
if (state.kind === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -5234,7 +5421,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5234
5421
|
"data-shop-outdated-error": true,
|
|
5235
5422
|
children: t("error")
|
|
5236
5423
|
});
|
|
5237
|
-
|
|
5424
|
+
const outdated = state.entries.filter((entry) => entry.outdated);
|
|
5425
|
+
if (outdated.length === 0) return null;
|
|
5238
5426
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
5239
5427
|
className: _dsh_plugin_shop_css_e3675a89_default.outdatedSection,
|
|
5240
5428
|
"data-shop-outdated": true,
|
|
@@ -5243,7 +5431,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5243
5431
|
children: t("installedSection")
|
|
5244
5432
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
5245
5433
|
className: _dsh_plugin_shop_css_e3675a89_default.outdatedList,
|
|
5246
|
-
children:
|
|
5434
|
+
children: outdated.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedRow, {
|
|
5247
5435
|
row,
|
|
5248
5436
|
tier: tiers.get(row.name) ?? "community",
|
|
5249
5437
|
t,
|
|
@@ -5257,9 +5445,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5257
5445
|
/** The shop tab root: browse, search, refresh, and render one card per
|
|
5258
5446
|
* entry. Data attributes on the e2e-relevant nodes follow the Task 3 list. */
|
|
5259
5447
|
function ShopTab(props) {
|
|
5260
|
-
const { t, catalog, install, installStatus, setEnabled,
|
|
5448
|
+
const { t, catalog, install, installStatus, setEnabled, installed, uninstall } = props;
|
|
5261
5449
|
const [catalogState, setCatalogState] = (0, react.useState)({ kind: "loading" });
|
|
5262
|
-
const [
|
|
5450
|
+
const [installedState, setInstalledState] = (0, react.useState)({ kind: "loading" });
|
|
5263
5451
|
const [request, setRequest] = (0, react.useState)({ kind: "initial" });
|
|
5264
5452
|
const [query, setQuery] = (0, react.useState)("");
|
|
5265
5453
|
const [category, setCategory] = (0, react.useState)(null);
|
|
@@ -5290,26 +5478,33 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5290
5478
|
let cancelled = false;
|
|
5291
5479
|
const load = async () => {
|
|
5292
5480
|
try {
|
|
5293
|
-
const entries = await
|
|
5294
|
-
if (!cancelled)
|
|
5481
|
+
const entries = await installed();
|
|
5482
|
+
if (!cancelled) setInstalledState({
|
|
5295
5483
|
kind: "ready",
|
|
5296
5484
|
entries
|
|
5297
5485
|
});
|
|
5298
5486
|
} catch {
|
|
5299
|
-
if (!cancelled)
|
|
5487
|
+
if (!cancelled) setInstalledState({ kind: "error" });
|
|
5300
5488
|
}
|
|
5301
5489
|
};
|
|
5302
5490
|
load();
|
|
5303
5491
|
return () => {
|
|
5304
5492
|
cancelled = true;
|
|
5305
5493
|
};
|
|
5306
|
-
}, [
|
|
5494
|
+
}, [installed, request]);
|
|
5495
|
+
const installedByName = (0, react.useMemo)(() => {
|
|
5496
|
+
const map = /* @__PURE__ */ new Map();
|
|
5497
|
+
if (installedState.kind === "ready") for (const entry of installedState.entries) map.set(entry.name, entry);
|
|
5498
|
+
return map;
|
|
5499
|
+
}, [installedState]);
|
|
5307
5500
|
const filtered = (0, react.useMemo)(() => {
|
|
5308
5501
|
if (catalogState.kind !== "ready") return [];
|
|
5309
5502
|
const q = query.trim().toLowerCase();
|
|
5310
5503
|
return catalogState.result.plugins.filter((entry) => {
|
|
5311
5504
|
if (isShopLike(entry.name)) return false;
|
|
5312
|
-
if (category
|
|
5505
|
+
if (category === "installed") {
|
|
5506
|
+
if (!installedByName.has(entry.name)) return false;
|
|
5507
|
+
} else if (category !== null && categoryKey(entry) !== categoryLocaleKey(category)) return false;
|
|
5313
5508
|
if (q === "") return true;
|
|
5314
5509
|
const summaryEn = entry.catalog?.summary.en ?? "";
|
|
5315
5510
|
const summaryZh = entry.catalog?.summary.zh ?? "";
|
|
@@ -5318,7 +5513,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5318
5513
|
}, [
|
|
5319
5514
|
catalogState,
|
|
5320
5515
|
query,
|
|
5321
|
-
category
|
|
5516
|
+
category,
|
|
5517
|
+
installedByName
|
|
5322
5518
|
]);
|
|
5323
5519
|
filteredLenRef.current = filtered.length;
|
|
5324
5520
|
const stars = catalogState.kind === "ready" ? catalogState.result.stars : {};
|
|
@@ -5348,6 +5544,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5348
5544
|
}
|
|
5349
5545
|
return counts;
|
|
5350
5546
|
}, [catalogState]);
|
|
5547
|
+
const installedCount = (0, react.useMemo)(() => {
|
|
5548
|
+
if (installedState.kind !== "ready") return 0;
|
|
5549
|
+
return installedState.entries.filter((entry) => !isShopLike(entry.name)).length;
|
|
5550
|
+
}, [installedState]);
|
|
5351
5551
|
const tiers = (0, react.useMemo)(() => {
|
|
5352
5552
|
const map = /* @__PURE__ */ new Map();
|
|
5353
5553
|
if (catalogState.kind === "ready") for (const entry of catalogState.result.plugins) map.set(entry.name, entry.tier);
|
|
@@ -5422,37 +5622,50 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5422
5622
|
className: _dsh_plugin_shop_css_e3675a89_default.categoryBar,
|
|
5423
5623
|
role: "group",
|
|
5424
5624
|
"aria-label": t("catalog"),
|
|
5425
|
-
children: [
|
|
5426
|
-
|
|
5427
|
-
|
|
5428
|
-
|
|
5429
|
-
|
|
5430
|
-
|
|
5431
|
-
|
|
5432
|
-
|
|
5433
|
-
|
|
5434
|
-
|
|
5435
|
-
|
|
5436
|
-
|
|
5437
|
-
|
|
5438
|
-
|
|
5439
|
-
|
|
5440
|
-
|
|
5441
|
-
|
|
5442
|
-
|
|
5443
|
-
|
|
5444
|
-
|
|
5445
|
-
|
|
5446
|
-
|
|
5447
|
-
|
|
5448
|
-
|
|
5449
|
-
|
|
5450
|
-
|
|
5451
|
-
|
|
5452
|
-
|
|
5453
|
-
|
|
5454
|
-
|
|
5455
|
-
|
|
5625
|
+
children: [
|
|
5626
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5627
|
+
type: "button",
|
|
5628
|
+
className: category === null ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_default.categoryButton,
|
|
5629
|
+
"aria-pressed": category === null,
|
|
5630
|
+
onClick: () => {
|
|
5631
|
+
setCategory(null);
|
|
5632
|
+
setVisibleCount(48);
|
|
5633
|
+
},
|
|
5634
|
+
children: [
|
|
5635
|
+
t("all"),
|
|
5636
|
+
" ",
|
|
5637
|
+
[...categoryCounts.values()].reduce((a, b) => a + b, 0)
|
|
5638
|
+
]
|
|
5639
|
+
}),
|
|
5640
|
+
CATEGORY_ORDER.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5641
|
+
type: "button",
|
|
5642
|
+
className: category === key ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_default.categoryButton,
|
|
5643
|
+
"aria-pressed": category === key,
|
|
5644
|
+
onClick: () => {
|
|
5645
|
+
setCategory(key);
|
|
5646
|
+
setVisibleCount(48);
|
|
5647
|
+
},
|
|
5648
|
+
children: [
|
|
5649
|
+
t(categoryLocaleKey(key)),
|
|
5650
|
+
" ",
|
|
5651
|
+
categoryCounts.get(key) ?? 0
|
|
5652
|
+
]
|
|
5653
|
+
}, key)),
|
|
5654
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
5655
|
+
type: "button",
|
|
5656
|
+
className: category === "installed" ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_default.categoryButton,
|
|
5657
|
+
"aria-pressed": category === "installed",
|
|
5658
|
+
onClick: () => {
|
|
5659
|
+
setCategory("installed");
|
|
5660
|
+
setVisibleCount(48);
|
|
5661
|
+
},
|
|
5662
|
+
children: [
|
|
5663
|
+
t("installed"),
|
|
5664
|
+
" ",
|
|
5665
|
+
installedCount
|
|
5666
|
+
]
|
|
5667
|
+
})
|
|
5668
|
+
]
|
|
5456
5669
|
}),
|
|
5457
5670
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
5458
5671
|
className: _dsh_plugin_shop_css_e3675a89_default.catalogStats,
|
|
@@ -5472,9 +5685,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5472
5685
|
children: [visible.map((entry) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryCard, {
|
|
5473
5686
|
entry,
|
|
5474
5687
|
stars: stars[entry.name],
|
|
5688
|
+
installed: installedByName.get(entry.name),
|
|
5475
5689
|
t,
|
|
5476
5690
|
install,
|
|
5477
|
-
installStatus
|
|
5691
|
+
installStatus,
|
|
5692
|
+
uninstall
|
|
5478
5693
|
}) }, entry.name)), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
5479
5694
|
ref: sentinelRef,
|
|
5480
5695
|
className: _dsh_plugin_shop_css_e3675a89_default.cardsSentry,
|
|
@@ -5490,7 +5705,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5490
5705
|
})
|
|
5491
5706
|
})] }),
|
|
5492
5707
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedSection, {
|
|
5493
|
-
state:
|
|
5708
|
+
state: installedState,
|
|
5494
5709
|
tiers,
|
|
5495
5710
|
t,
|
|
5496
5711
|
setEnabled,
|
|
@@ -5538,7 +5753,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
5538
5753
|
install: async (args) => unwrap(await ns.installStart(args)),
|
|
5539
5754
|
installStatus: async (args) => unwrap(await ns.installStatus(args)),
|
|
5540
5755
|
setEnabled: async (args) => unwrap(await ns.setEnabled(args)),
|
|
5541
|
-
|
|
5756
|
+
installed: async () => unwrap(await ns.installed()),
|
|
5757
|
+
uninstall: async (args) => unwrap(await ns.uninstallStart(args))
|
|
5542
5758
|
});
|
|
5543
5759
|
ctx.slots.inject("settings.plugins.tab", () => ctx.slots.register({
|
|
5544
5760
|
name: "settings.plugins.tab",
|