dsh-plugin-shop 0.8.3 → 0.8.4-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -8
- package/lib/client.js +1282 -443
- package/lib/index.js +2296 -250
- package/lib/typert.host.js +69 -22
- package/lib/typert.remote-client.js +52 -13
- package/lib/types/client/ShopTab.d.ts +19 -2
- package/lib/types/client/locales.d.ts +44 -2
- package/lib/types/client/module-table.d.ts +131 -0
- package/lib/types/client/present.d.ts +127 -2
- package/lib/types/client/restart-monitor.d.ts +114 -0
- package/lib/types/host/activation.d.ts +12 -9
- package/lib/types/host/bundle-patch.d.ts +35 -0
- package/lib/types/host/catalog.d.ts +9 -3
- package/lib/types/host/compatibility.d.ts +223 -0
- package/lib/types/host/dsh-cli.d.ts +8 -0
- package/lib/types/host/executor.d.ts +50 -3
- package/lib/types/host/harness.d.ts +109 -0
- package/lib/types/host/hot.d.ts +8 -8
- package/lib/types/host/index.d.ts +259 -29
- package/lib/types/host/install.d.ts +1 -1
- package/lib/types/host/manager-runner.d.ts +51 -0
- package/lib/types/host/peers.d.ts +181 -10
- package/lib/types/host/plugin-manager.d.ts +100 -0
- package/lib/types/host/profile.d.ts +61 -4
- package/lib/types/host/restart.d.ts +5 -1
- package/lib/types/host/types.d.ts +27 -1
- package/lib/types/own-version.d.ts +18 -2
- package/package.json +2 -1
package/lib/client.js
CHANGED
|
@@ -4316,6 +4316,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4316
4316
|
})]).optional(),
|
|
4317
4317
|
"added": union([_undefined(), string()]).optional(),
|
|
4318
4318
|
"peers": union([_undefined(), array(string())]).optional(),
|
|
4319
|
+
"compatibility": union([_undefined(), object({
|
|
4320
|
+
"dsh": union([_undefined(), string()]).optional(),
|
|
4321
|
+
"profiles": union([_undefined(), array(string())]).optional()
|
|
4322
|
+
})]).optional(),
|
|
4323
|
+
"dshPeers": union([_undefined(), record(string(), string())]).optional(),
|
|
4319
4324
|
"unpackedSize": union([_undefined(), number()]).optional(),
|
|
4320
4325
|
"installSize": union([_undefined(), number()]).optional()
|
|
4321
4326
|
})),
|
|
@@ -4326,7 +4331,22 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4326
4331
|
})),
|
|
4327
4332
|
"notAShop": union([_undefined(), array(string())]).optional(),
|
|
4328
4333
|
"stars": record(string(), number()),
|
|
4329
|
-
"incompatible": record(string(), array(string()))
|
|
4334
|
+
"incompatible": record(string(), array(string())),
|
|
4335
|
+
"incompatibleHarness": record(string(), object({
|
|
4336
|
+
"dsh": union([_undefined(), object({
|
|
4337
|
+
"range": string(),
|
|
4338
|
+
"running": string()
|
|
4339
|
+
})]).optional(),
|
|
4340
|
+
"profile": union([_undefined(), object({
|
|
4341
|
+
"declared": array(string()),
|
|
4342
|
+
"running": string()
|
|
4343
|
+
})]).optional(),
|
|
4344
|
+
"peers": union([_undefined(), object({
|
|
4345
|
+
"refused": record(string(), string()),
|
|
4346
|
+
"running": string(),
|
|
4347
|
+
"allowCommand": union([literal(null), string()])
|
|
4348
|
+
})]).optional()
|
|
4349
|
+
}))
|
|
4330
4350
|
});
|
|
4331
4351
|
const dsh_plugin_shop_shop_installed_result$schema = array(object({
|
|
4332
4352
|
"name": string(),
|
|
@@ -4373,7 +4393,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4373
4393
|
literal("needs-acknowledgement"),
|
|
4374
4394
|
literal("tarball-integrity"),
|
|
4375
4395
|
literal("ambiguous-identity"),
|
|
4376
|
-
literal("name-taken")
|
|
4396
|
+
literal("name-taken"),
|
|
4397
|
+
literal("desktop-profile")
|
|
4377
4398
|
]),
|
|
4378
4399
|
"detail": string()
|
|
4379
4400
|
})]);
|
|
@@ -4400,11 +4421,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4400
4421
|
literal("host-unsupported"),
|
|
4401
4422
|
literal("timeout"),
|
|
4402
4423
|
literal("mount-failed"),
|
|
4403
|
-
literal("
|
|
4424
|
+
literal("already-loaded")
|
|
4404
4425
|
]).optional(),
|
|
4405
4426
|
"detail": union([_undefined(), string()]).optional()
|
|
4406
4427
|
});
|
|
4407
|
-
const dsh_plugin_shop_shop_restart_result$schema = union([object({
|
|
4428
|
+
const dsh_plugin_shop_shop_restart_result$schema = union([object({
|
|
4429
|
+
"ok": literal(true),
|
|
4430
|
+
"logFile": union([_undefined(), string()]).optional()
|
|
4431
|
+
}), object({
|
|
4408
4432
|
"ok": literal(false),
|
|
4409
4433
|
"detail": string()
|
|
4410
4434
|
})]);
|
|
@@ -4451,10 +4475,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4451
4475
|
"outdated": boolean(),
|
|
4452
4476
|
"restartBlocked": union([
|
|
4453
4477
|
literal(null),
|
|
4478
|
+
literal("desktop"),
|
|
4454
4479
|
literal("windows"),
|
|
4455
4480
|
literal("systemd"),
|
|
4456
4481
|
literal("port-zero")
|
|
4457
|
-
])
|
|
4482
|
+
]),
|
|
4483
|
+
"pendingVersion": union([
|
|
4484
|
+
_undefined(),
|
|
4485
|
+
literal(null),
|
|
4486
|
+
string()
|
|
4487
|
+
]).optional()
|
|
4458
4488
|
});
|
|
4459
4489
|
const TYPERT_REMOTE = {
|
|
4460
4490
|
package: "dsh-plugin-shop",
|
|
@@ -4473,17 +4503,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4473
4503
|
codec: {
|
|
4474
4504
|
mode: "strict",
|
|
4475
4505
|
typeSymbol: "dsh-plugin-shop#shop/catalog:args",
|
|
4476
|
-
schema: dsh_plugin_shop_shop_catalog_parameter_0$schema
|
|
4506
|
+
schema: dsh_plugin_shop_shop_catalog_parameter_0$schema,
|
|
4507
|
+
create: () => dsh_plugin_shop_shop_catalog_parameter_0$schema
|
|
4477
4508
|
}
|
|
4478
4509
|
}],
|
|
4479
4510
|
result: {
|
|
4480
4511
|
mode: "strict",
|
|
4481
4512
|
typeSymbol: "dsh-plugin-shop/types#ShopCatalogResult",
|
|
4482
|
-
schema: dsh_plugin_shop_shop_catalog_result$schema
|
|
4513
|
+
schema: dsh_plugin_shop_shop_catalog_result$schema,
|
|
4514
|
+
create: () => dsh_plugin_shop_shop_catalog_result$schema
|
|
4483
4515
|
},
|
|
4484
4516
|
sourceLocation: {
|
|
4485
4517
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4486
|
-
"line":
|
|
4518
|
+
"line": 1253,
|
|
4487
4519
|
"column": 9
|
|
4488
4520
|
}
|
|
4489
4521
|
},
|
|
@@ -4497,11 +4529,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4497
4529
|
result: {
|
|
4498
4530
|
mode: "strict",
|
|
4499
4531
|
typeSymbol: "dsh-plugin-shop#shop/installed:result",
|
|
4500
|
-
schema: dsh_plugin_shop_shop_installed_result$schema
|
|
4532
|
+
schema: dsh_plugin_shop_shop_installed_result$schema,
|
|
4533
|
+
create: () => dsh_plugin_shop_shop_installed_result$schema
|
|
4501
4534
|
},
|
|
4502
4535
|
sourceLocation: {
|
|
4503
4536
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4504
|
-
"line":
|
|
4537
|
+
"line": 1720,
|
|
4505
4538
|
"column": 9
|
|
4506
4539
|
}
|
|
4507
4540
|
},
|
|
@@ -4515,11 +4548,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4515
4548
|
result: {
|
|
4516
4549
|
mode: "strict",
|
|
4517
4550
|
typeSymbol: "dsh-plugin-shop#shop/installedSpecs:result",
|
|
4518
|
-
schema: dsh_plugin_shop_shop_installedSpecs_result$schema
|
|
4551
|
+
schema: dsh_plugin_shop_shop_installedSpecs_result$schema,
|
|
4552
|
+
create: () => dsh_plugin_shop_shop_installedSpecs_result$schema
|
|
4519
4553
|
},
|
|
4520
4554
|
sourceLocation: {
|
|
4521
4555
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4522
|
-
"line":
|
|
4556
|
+
"line": 1711,
|
|
4523
4557
|
"column": 9
|
|
4524
4558
|
}
|
|
4525
4559
|
},
|
|
@@ -4537,17 +4571,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4537
4571
|
codec: {
|
|
4538
4572
|
mode: "strict",
|
|
4539
4573
|
typeSymbol: "dsh-plugin-shop/types#InstallArgs",
|
|
4540
|
-
schema: dsh_plugin_shop_shop_installStart_parameter_0$schema
|
|
4574
|
+
schema: dsh_plugin_shop_shop_installStart_parameter_0$schema,
|
|
4575
|
+
create: () => dsh_plugin_shop_shop_installStart_parameter_0$schema
|
|
4541
4576
|
}
|
|
4542
4577
|
}],
|
|
4543
4578
|
result: {
|
|
4544
4579
|
mode: "strict",
|
|
4545
4580
|
typeSymbol: "dsh-plugin-shop/types#ShopInstallResult",
|
|
4546
|
-
schema: dsh_plugin_shop_shop_installStart_result$schema
|
|
4581
|
+
schema: dsh_plugin_shop_shop_installStart_result$schema,
|
|
4582
|
+
create: () => dsh_plugin_shop_shop_installStart_result$schema
|
|
4547
4583
|
},
|
|
4548
4584
|
sourceLocation: {
|
|
4549
4585
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4550
|
-
"line":
|
|
4586
|
+
"line": 1379,
|
|
4551
4587
|
"column": 9
|
|
4552
4588
|
}
|
|
4553
4589
|
},
|
|
@@ -4564,17 +4600,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4564
4600
|
codec: {
|
|
4565
4601
|
mode: "strict",
|
|
4566
4602
|
typeSymbol: "dsh-plugin-shop#shop/installStatus:args",
|
|
4567
|
-
schema: dsh_plugin_shop_shop_installStatus_parameter_0$schema
|
|
4603
|
+
schema: dsh_plugin_shop_shop_installStatus_parameter_0$schema,
|
|
4604
|
+
create: () => dsh_plugin_shop_shop_installStatus_parameter_0$schema
|
|
4568
4605
|
}
|
|
4569
4606
|
}],
|
|
4570
4607
|
result: {
|
|
4571
4608
|
mode: "strict",
|
|
4572
4609
|
typeSymbol: "dsh-plugin-shop/types#ShopInstallStatusResult",
|
|
4573
|
-
schema: dsh_plugin_shop_shop_installStatus_result$schema
|
|
4610
|
+
schema: dsh_plugin_shop_shop_installStatus_result$schema,
|
|
4611
|
+
create: () => dsh_plugin_shop_shop_installStatus_result$schema
|
|
4574
4612
|
},
|
|
4575
4613
|
sourceLocation: {
|
|
4576
4614
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4577
|
-
"line":
|
|
4615
|
+
"line": 1687,
|
|
4578
4616
|
"column": 3
|
|
4579
4617
|
}
|
|
4580
4618
|
},
|
|
@@ -4588,11 +4626,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4588
4626
|
result: {
|
|
4589
4627
|
mode: "strict",
|
|
4590
4628
|
typeSymbol: "dsh-plugin-shop/types#ShopRestartResult",
|
|
4591
|
-
schema: dsh_plugin_shop_shop_restart_result$schema
|
|
4629
|
+
schema: dsh_plugin_shop_shop_restart_result$schema,
|
|
4630
|
+
create: () => dsh_plugin_shop_shop_restart_result$schema
|
|
4592
4631
|
},
|
|
4593
4632
|
sourceLocation: {
|
|
4594
4633
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4595
|
-
"line":
|
|
4634
|
+
"line": 1916,
|
|
4596
4635
|
"column": 9
|
|
4597
4636
|
}
|
|
4598
4637
|
},
|
|
@@ -4609,17 +4648,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4609
4648
|
codec: {
|
|
4610
4649
|
mode: "strict",
|
|
4611
4650
|
typeSymbol: "dsh-plugin-shop#shop/setEnabled:args",
|
|
4612
|
-
schema: dsh_plugin_shop_shop_setEnabled_parameter_0$schema
|
|
4651
|
+
schema: dsh_plugin_shop_shop_setEnabled_parameter_0$schema,
|
|
4652
|
+
create: () => dsh_plugin_shop_shop_setEnabled_parameter_0$schema
|
|
4613
4653
|
}
|
|
4614
4654
|
}],
|
|
4615
4655
|
result: {
|
|
4616
4656
|
mode: "strict",
|
|
4617
4657
|
typeSymbol: "dsh-plugin-shop/types#ShopSetEnabledResult",
|
|
4618
|
-
schema: dsh_plugin_shop_shop_setEnabled_result$schema
|
|
4658
|
+
schema: dsh_plugin_shop_shop_setEnabled_result$schema,
|
|
4659
|
+
create: () => dsh_plugin_shop_shop_setEnabled_result$schema
|
|
4619
4660
|
},
|
|
4620
4661
|
sourceLocation: {
|
|
4621
4662
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4622
|
-
"line":
|
|
4663
|
+
"line": 924,
|
|
4623
4664
|
"column": 9
|
|
4624
4665
|
}
|
|
4625
4666
|
},
|
|
@@ -4637,17 +4678,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4637
4678
|
codec: {
|
|
4638
4679
|
mode: "strict",
|
|
4639
4680
|
typeSymbol: "dsh-plugin-shop#shop/uninstallStart:args",
|
|
4640
|
-
schema: dsh_plugin_shop_shop_uninstallStart_parameter_0$schema
|
|
4681
|
+
schema: dsh_plugin_shop_shop_uninstallStart_parameter_0$schema,
|
|
4682
|
+
create: () => dsh_plugin_shop_shop_uninstallStart_parameter_0$schema
|
|
4641
4683
|
}
|
|
4642
4684
|
}],
|
|
4643
4685
|
result: {
|
|
4644
4686
|
mode: "strict",
|
|
4645
4687
|
typeSymbol: "dsh-plugin-shop/types#ShopUninstallResult",
|
|
4646
|
-
schema: dsh_plugin_shop_shop_uninstallStart_result$schema
|
|
4688
|
+
schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
|
|
4689
|
+
create: () => dsh_plugin_shop_shop_uninstallStart_result$schema
|
|
4647
4690
|
},
|
|
4648
4691
|
sourceLocation: {
|
|
4649
4692
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4650
|
-
"line":
|
|
4693
|
+
"line": 1827,
|
|
4651
4694
|
"column": 9
|
|
4652
4695
|
}
|
|
4653
4696
|
},
|
|
@@ -4664,17 +4707,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4664
4707
|
codec: {
|
|
4665
4708
|
mode: "strict",
|
|
4666
4709
|
typeSymbol: "dsh-plugin-shop#shop/updateStart:args",
|
|
4667
|
-
schema: dsh_plugin_shop_shop_updateStart_parameter_0$schema
|
|
4710
|
+
schema: dsh_plugin_shop_shop_updateStart_parameter_0$schema,
|
|
4711
|
+
create: () => dsh_plugin_shop_shop_updateStart_parameter_0$schema
|
|
4668
4712
|
}
|
|
4669
4713
|
}],
|
|
4670
4714
|
result: {
|
|
4671
4715
|
mode: "strict",
|
|
4672
4716
|
typeSymbol: "dsh-plugin-shop/types#ShopUpdateResult",
|
|
4673
|
-
schema: dsh_plugin_shop_shop_updateStart_result$schema
|
|
4717
|
+
schema: dsh_plugin_shop_shop_updateStart_result$schema,
|
|
4718
|
+
create: () => dsh_plugin_shop_shop_updateStart_result$schema
|
|
4674
4719
|
},
|
|
4675
4720
|
sourceLocation: {
|
|
4676
4721
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4677
|
-
"line":
|
|
4722
|
+
"line": 2020,
|
|
4678
4723
|
"column": 9
|
|
4679
4724
|
}
|
|
4680
4725
|
},
|
|
@@ -4688,11 +4733,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
4688
4733
|
result: {
|
|
4689
4734
|
mode: "strict",
|
|
4690
4735
|
typeSymbol: "dsh-plugin-shop/types#ShopVersionResult",
|
|
4691
|
-
schema: dsh_plugin_shop_shop_version_result$schema
|
|
4736
|
+
schema: dsh_plugin_shop_shop_version_result$schema,
|
|
4737
|
+
create: () => dsh_plugin_shop_shop_version_result$schema
|
|
4692
4738
|
},
|
|
4693
4739
|
sourceLocation: {
|
|
4694
4740
|
"file": "packages/dsh-plugin-shop/src/host/index.ts",
|
|
4695
|
-
"line":
|
|
4741
|
+
"line": 1978,
|
|
4696
4742
|
"column": 9
|
|
4697
4743
|
}
|
|
4698
4744
|
}
|
|
@@ -6290,7 +6336,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6290
6336
|
case "host-unsupported": return "hotHostUnsupportedNotice";
|
|
6291
6337
|
case "timeout": return "hotTimeoutNotice";
|
|
6292
6338
|
case "mount-failed": return "hotMountFailedNotice";
|
|
6293
|
-
case "
|
|
6339
|
+
case "already-loaded": return "hotAlreadyLoadedNotice";
|
|
6294
6340
|
default: return "installedRestartNotice";
|
|
6295
6341
|
}
|
|
6296
6342
|
}
|
|
@@ -6326,6 +6372,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6326
6372
|
* could not have helped. */
|
|
6327
6373
|
function restartBlockedNoticeKey(reason) {
|
|
6328
6374
|
switch (reason) {
|
|
6375
|
+
case "desktop": return "restartBlockedDesktopNotice";
|
|
6329
6376
|
case "windows": return "restartBlockedWindowsNotice";
|
|
6330
6377
|
case "systemd": return "restartBlockedSystemdNotice";
|
|
6331
6378
|
case "port-zero": return "restartBlockedPortZeroNotice";
|
|
@@ -6346,6 +6393,105 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6346
6393
|
function missingPeersOf(incompatible, key) {
|
|
6347
6394
|
return incompatible[key] ?? [];
|
|
6348
6395
|
}
|
|
6396
|
+
/** The author's own `dsh.compatibility` that this installation does not meet
|
|
6397
|
+
* (design 2026-09-01 §8.2), or undefined when the host gave no verdict. Keyed
|
|
6398
|
+
* by install identity, never by name, for the reason `missingPeersOf` is.
|
|
6399
|
+
*
|
|
6400
|
+
* The map itself may be absent, whatever the result type says: a tab can
|
|
6401
|
+
* outlive a shop self-update by one reload and then be talking to the OLD
|
|
6402
|
+
* in-process host until dsh restarts, which answers the shape from before the
|
|
6403
|
+
* key existed. That is "no verdict", never a crash. */
|
|
6404
|
+
function harnessVerdictOf(verdicts, key) {
|
|
6405
|
+
return verdicts !== void 0 && Object.hasOwn(verdicts, key) ? verdicts[key] : void 0;
|
|
6406
|
+
}
|
|
6407
|
+
/**
|
|
6408
|
+
* Everything standing in one entry's way, in the order every surface renders
|
|
6409
|
+
* it — the card's detail lines, the badge's accessible name, the outdated
|
|
6410
|
+
* row's refusal — and the one list the incompatible filter reads, through
|
|
6411
|
+
* `readsIncompatible`, and the install button, through `refusesInstall`.
|
|
6412
|
+
*
|
|
6413
|
+
* The refusals read first. The name conflict leads: it is about a plugin the
|
|
6414
|
+
* reader chose and would lose. Then dsh's own refusal, then what this machine
|
|
6415
|
+
* is measured to lack, then what the author declared, range before profiles —
|
|
6416
|
+
* the order the host's verdict carries its halves.
|
|
6417
|
+
*/
|
|
6418
|
+
function blockersOf(missing, harness, nameTakenBy) {
|
|
6419
|
+
const blockers = [];
|
|
6420
|
+
if (nameTakenBy !== void 0) blockers.push({
|
|
6421
|
+
kind: "name-taken",
|
|
6422
|
+
holder: nameTakenBy
|
|
6423
|
+
});
|
|
6424
|
+
if (harness?.peers !== void 0) {
|
|
6425
|
+
const { refused, running, allowCommand } = harness.peers;
|
|
6426
|
+
blockers.push({
|
|
6427
|
+
kind: "harness-peers",
|
|
6428
|
+
refused,
|
|
6429
|
+
running,
|
|
6430
|
+
allowCommand
|
|
6431
|
+
});
|
|
6432
|
+
}
|
|
6433
|
+
if (missing.length > 0) blockers.push({
|
|
6434
|
+
kind: "missing-peers",
|
|
6435
|
+
modules: missing
|
|
6436
|
+
});
|
|
6437
|
+
if (harness?.dsh !== void 0) blockers.push({
|
|
6438
|
+
kind: "harness-range",
|
|
6439
|
+
range: harness.dsh.range,
|
|
6440
|
+
running: harness.dsh.running
|
|
6441
|
+
});
|
|
6442
|
+
if (harness?.profile !== void 0) blockers.push({
|
|
6443
|
+
kind: "harness-profile",
|
|
6444
|
+
declared: harness.profile.declared,
|
|
6445
|
+
running: harness.profile.running
|
|
6446
|
+
});
|
|
6447
|
+
return blockers;
|
|
6448
|
+
}
|
|
6449
|
+
/**
|
|
6450
|
+
* The badge's visible word for these blockers, or null when there are none.
|
|
6451
|
+
*
|
|
6452
|
+
* "Incompatible" for anything about this harness, whichever reason put it
|
|
6453
|
+
* there. A taken name is the more serious condition and names a different
|
|
6454
|
+
* remedy, so it decides the word whenever it holds — the card is perfectly
|
|
6455
|
+
* compatible, and a different plugin is in the way.
|
|
6456
|
+
*/
|
|
6457
|
+
function blockerBadgeKey(blockers) {
|
|
6458
|
+
if (blockers.length === 0) return null;
|
|
6459
|
+
return blockers.some((blocker) => blocker.kind === "name-taken") ? "nameTakenBadge" : "incompatibleBadge";
|
|
6460
|
+
}
|
|
6461
|
+
/** Whether the badge reads "Incompatible" — exactly the set the incompatible
|
|
6462
|
+
* filter counts and takes away. Derived from the badge's own rule, never
|
|
6463
|
+
* restated beside it: the filter used to test the peer list by hand, which
|
|
6464
|
+
* held only while missing peers were the one reason for the word — a card
|
|
6465
|
+
* badged for its declared range would have read "Incompatible" and survived
|
|
6466
|
+
* the filter that names it. */
|
|
6467
|
+
function readsIncompatible(blockers) {
|
|
6468
|
+
return blockerBadgeKey(blockers) === "incompatibleBadge";
|
|
6469
|
+
}
|
|
6470
|
+
/**
|
|
6471
|
+
* Whether this install is refused before it starts, so its button is
|
|
6472
|
+
* disabled rather than opening the acknowledgement gate for an install that
|
|
6473
|
+
* cannot proceed: by the host, for a taken name, or by the running dsh, for
|
|
6474
|
+
* its harness peers.
|
|
6475
|
+
*
|
|
6476
|
+
* The second is the one harness reason that blocks (design
|
|
6477
|
+
* 2026-09-26-dsh-017-readiness, B1, amending "warn, never block"): dsh 0.1.7
|
|
6478
|
+
* refuses the install itself, before pnpm runs, and the shop cannot install
|
|
6479
|
+
* past it. The verdict is dsh's own rule on the profile's own exemptions, so
|
|
6480
|
+
* a disabled button is dsh's answer given early, never the shop's opinion —
|
|
6481
|
+
* and recording the exemption the card shows is what enables it again.
|
|
6482
|
+
*/
|
|
6483
|
+
function refusesInstall(blockers) {
|
|
6484
|
+
return blockers.some((blocker) => blocker.kind === "name-taken" || blocker.kind === "harness-peers");
|
|
6485
|
+
}
|
|
6486
|
+
/**
|
|
6487
|
+
* The refused peers as the refusal's copy names them: each peer and the range
|
|
6488
|
+
* the package declared, in the order dsh reported them. A blank range — which
|
|
6489
|
+
* dsh refuses — is quoted, or the sentence would name the peer and nothing
|
|
6490
|
+
* it asked for.
|
|
6491
|
+
*/
|
|
6492
|
+
function refusedPeersText(refused) {
|
|
6493
|
+
return Object.entries(refused).map(([name, range]) => `${name} ${range.trim() === "" ? JSON.stringify(range) : range}`).join(", ");
|
|
6494
|
+
}
|
|
6349
6495
|
/**
|
|
6350
6496
|
* The plugin already holding this entry's bundle name, if a DIFFERENT one is,
|
|
6351
6497
|
* named the way the host's refusal names it.
|
|
@@ -6384,6 +6530,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6384
6530
|
case "tarball-integrity": return "tarballIntegrityCode";
|
|
6385
6531
|
case "ambiguous-identity": return "ambiguousIdentityCode";
|
|
6386
6532
|
case "name-taken": return "nameTakenCode";
|
|
6533
|
+
case "desktop-profile": return "desktopProfileCode";
|
|
6387
6534
|
}
|
|
6388
6535
|
}
|
|
6389
6536
|
/** The version a card displays: full for npm, the short commit for github. */
|
|
@@ -6423,6 +6570,24 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6423
6570
|
}
|
|
6424
6571
|
/** §7.2 once-per-second poll cadence, as a named constant. */
|
|
6425
6572
|
const INSTALL_POLL_MS = 1e3;
|
|
6573
|
+
/**
|
|
6574
|
+
* The restart monitor's decision after a probe of the origin.
|
|
6575
|
+
*
|
|
6576
|
+
* `stableSinceMs` is when the current unbroken run of good answers began —
|
|
6577
|
+
* null when the last probe failed, or none has succeeded yet — and both times
|
|
6578
|
+
* count from the moment the restart was committed. One answer is not enough:
|
|
6579
|
+
* reloading on it is what landed a reader on a boot that died a moment later,
|
|
6580
|
+
* a blank page in place of the notice naming the log. So the page reloads
|
|
6581
|
+
* only once the run has lasted `RESTART_STABLE_MS`, and the monitor gives up
|
|
6582
|
+
* only when no run is under way past `RESTART_WAIT_MS` — which bounds the
|
|
6583
|
+
* whole wait at the sum of the two, since a run that began in time either
|
|
6584
|
+
* completes or breaks after the deadline.
|
|
6585
|
+
*/
|
|
6586
|
+
function restartMonitorVerdict(input) {
|
|
6587
|
+
if (input.stableSinceMs !== null && input.elapsedMs - input.stableSinceMs >= 8e3) return "reload";
|
|
6588
|
+
if (input.stableSinceMs === null && input.elapsedMs > 3e4) return "failed";
|
|
6589
|
+
return "wait";
|
|
6590
|
+
}
|
|
6426
6591
|
/** How long the check-update button reports "up to date" after a re-check
|
|
6427
6592
|
* finds no newer release, before reverting to its idle label. */
|
|
6428
6593
|
const CHECK_UP_TO_DATE_MS = 3e3;
|
|
@@ -6465,7 +6630,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6465
6630
|
kind: "done",
|
|
6466
6631
|
activation: status.activation ?? "restart",
|
|
6467
6632
|
log: status.log,
|
|
6468
|
-
...status.restartReason !== void 0 ? { restartReason: status.restartReason } : {}
|
|
6633
|
+
...status.restartReason !== void 0 ? { restartReason: status.restartReason } : {},
|
|
6634
|
+
...status.detail !== void 0 ? { detail: status.detail } : {}
|
|
6469
6635
|
};
|
|
6470
6636
|
return {
|
|
6471
6637
|
kind: "failed",
|
|
@@ -6680,6 +6846,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6680
6846
|
error: "暂时无法读取目录。",
|
|
6681
6847
|
installedError: "暂时无法读取已安装列表。",
|
|
6682
6848
|
retry: "重试",
|
|
6849
|
+
/** The shop's own error boundary caught a render error; the error's message
|
|
6850
|
+
* follows on its own line, and Retry remounts the tab (design
|
|
6851
|
+
* 2026-09-26-market-borrowings §4). */
|
|
6852
|
+
tabCrashed: "插件商店在显示这一页时出错了。",
|
|
6683
6853
|
search: "搜索插件",
|
|
6684
6854
|
all: "全部",
|
|
6685
6855
|
catalog: "插件目录",
|
|
@@ -6709,12 +6879,29 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6709
6879
|
installedRestartNotice: "已安装;重启 dsh 后生效",
|
|
6710
6880
|
incompatibleBadge: "不兼容",
|
|
6711
6881
|
incompatibleDetail: "缺少组件/插件:{modules}\n此插件可能是为其他版本的 DSH 设计的。",
|
|
6882
|
+
/** The author's own `dsh.compatibility`, unmet on this machine (design
|
|
6883
|
+
* 2026-09-01 §8.2). Each names BOTH sides — what the author declared and
|
|
6884
|
+
* what this dsh runs — because the host sends both, and a bare
|
|
6885
|
+
* "unsupported" leaves the reader guessing which side is off. */
|
|
6886
|
+
harnessRangeDetail: "作者声明支持的 dsh 版本:{range};本机运行的是 {running}。",
|
|
6887
|
+
harnessProfileDetail: "作者声明支持的 profile:{declared};此 dsh 以 {running} profile 启动。",
|
|
6888
|
+
/** dsh's OWN refusal (0.1.7 on), not the author's claim: the install button
|
|
6889
|
+
* is disabled beside it (design 2026-09-26-dsh-017-readiness, B1). `{peers}`
|
|
6890
|
+
* is each refused peer with the range the package declared. The remedy
|
|
6891
|
+
* leads into the exemption command, rendered on its own line after it, and
|
|
6892
|
+
* names the Refresh button by its label. */
|
|
6893
|
+
harnessPeersDetail: "本机的 dsh {running} 会拒绝安装此插件:它要求 {peers}。",
|
|
6894
|
+
harnessPeersRemedy: "若愿意承担崩溃或数据丢失的风险,可在终端运行以下命令,为这个确切版本授予豁免,然后点击“刷新”:",
|
|
6712
6895
|
hotNoPatchNotice: "该插件没有可热挂载的补丁文件;重启 dsh 后生效",
|
|
6713
6896
|
hotNotSimpleNotice: "该插件的补丁包含无法热挂载的配置;重启 dsh 后生效",
|
|
6714
6897
|
hotHostUnsupportedNotice: "当前环境不支持热挂载;重启 dsh 后生效",
|
|
6715
6898
|
hotTimeoutNotice: "热挂载超时;重启 dsh 后生效",
|
|
6716
6899
|
hotMountFailedNotice: "热挂载失败;重启 dsh 后生效",
|
|
6717
|
-
|
|
6900
|
+
/** No mount was tried: this dsh already loaded the package once, and Node
|
|
6901
|
+
* will not load a second copy from the same place (design
|
|
6902
|
+
* 2026-09-26-market-borrowings §1). Covers an update and a reinstall alike,
|
|
6903
|
+
* so it names the new files rather than a "new version". */
|
|
6904
|
+
hotAlreadyLoadedNotice: "已安装;这次运行的 dsh 已经加载过这个插件,新装的文件要重启 dsh 后才会生效",
|
|
6718
6905
|
installedNoRestartNotice: "已安装并热挂载;现在即可使用,无需重启",
|
|
6719
6906
|
installedReloadNotice: "已生效;刷新页面即可看到",
|
|
6720
6907
|
reload: "刷新页面",
|
|
@@ -6739,9 +6926,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6739
6926
|
/** The incompatible filter's label. FIXED — the switch beside it carries the
|
|
6740
6927
|
* state, so the words name what the switch does and never flip with it. */
|
|
6741
6928
|
hideIncompatible: "隐藏不兼容 {count}",
|
|
6742
|
-
|
|
6743
|
-
|
|
6744
|
-
|
|
6929
|
+
/** What the filter takes away is every card whose badge reads 不兼容 —
|
|
6930
|
+
* missing components OR an author's declaration this dsh does not meet —
|
|
6931
|
+
* so these three name the badge, not one of its reasons. */
|
|
6932
|
+
incompatibleFilterTitle: "不显示标为不兼容的插件",
|
|
6933
|
+
showIncompatibleTitle: "把标为不兼容的插件放回列表",
|
|
6934
|
+
emptyIncompatibleFiltered: "匹配的插件都标为不兼容,已被「隐藏不兼容」筛掉。",
|
|
6745
6935
|
license: "许可",
|
|
6746
6936
|
customLicense: "自定义许可",
|
|
6747
6937
|
reviewedVersionLine: "已审核 v{reviewed} / 当前 v{current} 未审核",
|
|
@@ -6753,6 +6943,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6753
6943
|
tarballIntegrityCode: "压缩包完整性校验失败",
|
|
6754
6944
|
ambiguousIdentityCode: "无法确定是哪一个条目",
|
|
6755
6945
|
nameTakenCode: "同名的另一个插件已安装",
|
|
6946
|
+
desktopProfileCode: "由桌面版管理的 profile",
|
|
6756
6947
|
nameTakenBadge: "名称被占用",
|
|
6757
6948
|
nameTakenDetail: "已安装同名的另一个插件({holder})。\n两个同名插件声明相同的加载项 id,无法共存;安装它会替换掉已装的那个。",
|
|
6758
6949
|
npmSource: "npm 包",
|
|
@@ -6775,7 +6966,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6775
6966
|
restartConfirm: "重启",
|
|
6776
6967
|
restarting: "正在重启,服务器就绪后页面会自动刷新…",
|
|
6777
6968
|
restartFailedNotice: "重启后服务器没有回来。请手动启动:dsh web(新进程的日志在商店缓存目录的 restart.log)",
|
|
6969
|
+
/** The same failure, when the host said where the new process writes —
|
|
6970
|
+
* which is where a boot that died explains itself (design
|
|
6971
|
+
* 2026-09-26-market-borrowings §3). "Or did not stay up": the page now
|
|
6972
|
+
* waits for the new server to keep answering, so this also covers a boot
|
|
6973
|
+
* that answered and then died. */
|
|
6974
|
+
restartFailedLogNotice: "重启后服务器没有回来,或回来后没能保持运行。新进程的日志在 {log};也可以手动启动:dsh web",
|
|
6778
6975
|
restartTransportFailed: "重启请求未能送达。请稍后重试。",
|
|
6976
|
+
restartBlockedDesktopNotice: "此 profile 由 DeepSeek Harness 桌面版管理,商店无法重启它。请重启桌面版,以应用更改。",
|
|
6779
6977
|
restartBlockedWindowsNotice: "商店暂不支持在 Windows 上重启 dsh。请退出 dsh 后重新启动,以应用更改。",
|
|
6780
6978
|
restartBlockedSystemdNotice: "当前 dsh 由 systemd 服务托管,商店无法安全地重启它。请手动重启服务,或在商店行配置中设置 allowRestart: true。",
|
|
6781
6979
|
restartBlockedPortZeroNotice: "此 dsh 以 --port 0 启动,重启后会绑定到另一个端口,本页面无法跟上。请手动重启 dsh,以应用更改。",
|
|
@@ -6787,7 +6985,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6787
6985
|
github: "GitHub",
|
|
6788
6986
|
enabledSwitch: "启用",
|
|
6789
6987
|
toggleFailed: "设置失败,请重试。",
|
|
6790
|
-
hotApplyNote: "已生效,无需重启"
|
|
6988
|
+
hotApplyNote: "已生效,无需重启",
|
|
6989
|
+
toggleRestartNote: "已保存,重启 dsh 后生效"
|
|
6791
6990
|
};
|
|
6792
6991
|
/** English dictionary checked against the Chinese key set. */
|
|
6793
6992
|
const en = {
|
|
@@ -6796,6 +6995,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6796
6995
|
error: "The catalog is temporarily unavailable.",
|
|
6797
6996
|
installedError: "The installed list is temporarily unavailable.",
|
|
6798
6997
|
retry: "Retry",
|
|
6998
|
+
tabCrashed: "The plugin shop hit an error while showing this page.",
|
|
6799
6999
|
search: "Search plugins",
|
|
6800
7000
|
all: "All",
|
|
6801
7001
|
catalog: "Plugin catalog",
|
|
@@ -6825,12 +7025,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6825
7025
|
installedRestartNotice: "installed; restart dsh to activate",
|
|
6826
7026
|
incompatibleBadge: "Incompatible",
|
|
6827
7027
|
incompatibleDetail: "Missing components/plugins: {modules}\nThis plugin may be designed for another DSH version.",
|
|
7028
|
+
harnessRangeDetail: "The author declares support for dsh {range}; this machine runs {running}.",
|
|
7029
|
+
harnessProfileDetail: "The author declares support for the {declared} profile(s); this dsh was launched with the {running} profile.",
|
|
7030
|
+
harnessPeersDetail: "dsh {running} on this machine refuses to install this plugin: it requires {peers}.",
|
|
7031
|
+
harnessPeersRemedy: "To accept the risk of crashes or data loss for this exact version, run this in a terminal, then press Refresh:",
|
|
6828
7032
|
hotNoPatchNotice: "no patch file to hot-mount; restart dsh to activate",
|
|
6829
7033
|
hotNotSimpleNotice: "the patch has config rows that cannot be hot-mounted; restart dsh to activate",
|
|
6830
7034
|
hotHostUnsupportedNotice: "this harness cannot hot-mount; restart dsh to activate",
|
|
6831
7035
|
hotTimeoutNotice: "hot-mount timed out; restart dsh to activate",
|
|
6832
7036
|
hotMountFailedNotice: "hot-mount failed; restart dsh to activate",
|
|
6833
|
-
|
|
7037
|
+
hotAlreadyLoadedNotice: "installed; this dsh has already loaded the plugin once, so the files just installed take effect after a restart of dsh",
|
|
6834
7038
|
installedNoRestartNotice: "installed and hot-mounted; running now, no restart needed",
|
|
6835
7039
|
installedReloadNotice: "already applied; reload the page to see it",
|
|
6836
7040
|
reload: "Reload the page",
|
|
@@ -6847,9 +7051,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6847
7051
|
authorLine: "Author: {author}",
|
|
6848
7052
|
sizeLabel: "{size} on disk",
|
|
6849
7053
|
hideIncompatible: "Hide incompatible {count}",
|
|
6850
|
-
incompatibleFilterTitle: "Leave out plugins
|
|
6851
|
-
showIncompatibleTitle: "Put plugins
|
|
6852
|
-
emptyIncompatibleFiltered: "Every matching plugin is
|
|
7054
|
+
incompatibleFilterTitle: "Leave out plugins marked incompatible with this dsh",
|
|
7055
|
+
showIncompatibleTitle: "Put plugins marked incompatible back on the shelf",
|
|
7056
|
+
emptyIncompatibleFiltered: "Every matching plugin is marked incompatible, and the incompatible filter is leaving them out.",
|
|
6853
7057
|
license: "License",
|
|
6854
7058
|
customLicense: "Custom license",
|
|
6855
7059
|
reviewedVersionLine: "reviewed v{reviewed} / current v{current} unreviewed",
|
|
@@ -6861,6 +7065,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6861
7065
|
tarballIntegrityCode: "tarball integrity check failed",
|
|
6862
7066
|
ambiguousIdentityCode: "Ambiguous entry",
|
|
6863
7067
|
nameTakenCode: "Another plugin holds this name",
|
|
7068
|
+
desktopProfileCode: "Managed by the desktop app",
|
|
6864
7069
|
nameTakenBadge: "Name taken",
|
|
6865
7070
|
nameTakenDetail: "A different plugin of this name is installed ({holder}).\nTwo plugins of one name declare the same loader entry id and cannot both load; installing this one would replace it.",
|
|
6866
7071
|
npmSource: "npm package",
|
|
@@ -6883,7 +7088,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6883
7088
|
restartConfirm: "Restart",
|
|
6884
7089
|
restarting: "Restarting; the page will refresh once the server is back…",
|
|
6885
7090
|
restartFailedNotice: "The server did not come back after the restart. Start it manually: dsh web (see restart.log in the shop cache directory)",
|
|
7091
|
+
restartFailedLogNotice: "The server did not come back after the restart, or did not stay up. The new process logs to {log}; you can also start it manually: dsh web",
|
|
6886
7092
|
restartTransportFailed: "The restart request could not be delivered. Please retry.",
|
|
7093
|
+
restartBlockedDesktopNotice: "This profile is managed by the DeepSeek Harness desktop app, so the shop cannot restart it. Restart the app to apply the change.",
|
|
6887
7094
|
restartBlockedWindowsNotice: "The shop cannot restart dsh on Windows yet. Quit dsh and start it again to apply the change.",
|
|
6888
7095
|
restartBlockedSystemdNotice: "This dsh process runs as a systemd service, so the shop cannot restart it safely. Restart the service manually, or set allowRestart: true in the shop row config.",
|
|
6889
7096
|
restartBlockedPortZeroNotice: "This dsh was launched with --port 0, so a restart would bind a different port and this page could not follow it. Restart dsh manually to apply the change.",
|
|
@@ -6895,9 +7102,409 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
6895
7102
|
github: "GitHub",
|
|
6896
7103
|
enabledSwitch: "Enable",
|
|
6897
7104
|
toggleFailed: "Failed to update. Please retry.",
|
|
6898
|
-
hotApplyNote: "applied without a restart"
|
|
7105
|
+
hotApplyNote: "applied without a restart",
|
|
7106
|
+
toggleRestartNote: "saved; it takes effect after dsh restarts"
|
|
6899
7107
|
};
|
|
6900
7108
|
//#endregion
|
|
7109
|
+
//#region src/client/module-table.ts
|
|
7110
|
+
/**
|
|
7111
|
+
* How long one probe may take before its answer counts as unknown.
|
|
7112
|
+
*
|
|
7113
|
+
* The work a probe can actually reach is a microtask: a seed-word lookup, or a
|
|
7114
|
+
* factory this page already registered. So this bounds a harness that never
|
|
7115
|
+
* answers, not work — and it exists because the cost of the alternative is the
|
|
7116
|
+
* whole shelf. `refineAgainstModuleTable` promises that a table in any state
|
|
7117
|
+
* cannot cost the reader the catalog, and an `import()` that never settles
|
|
7118
|
+
* would have made that promise false by leaving the tab with nothing to render
|
|
7119
|
+
* (§9's degradation rule: an unavailable fact is silence, never an accusation).
|
|
7120
|
+
* A probe per name, not one deadline for the whole refinement: every other
|
|
7121
|
+
* name is still judged, and the tab waits at most this long however many names
|
|
7122
|
+
* there are.
|
|
7123
|
+
*/
|
|
7124
|
+
const MODULE_PROBE_TIMEOUT_MS = 2e3;
|
|
7125
|
+
/** `outcome`, or null once `ms` has passed — whichever settles first. The
|
|
7126
|
+
* timer is cleared either way, so a settled probe leaves nothing pending. */
|
|
7127
|
+
function withDeadline(outcome, ms) {
|
|
7128
|
+
let timer;
|
|
7129
|
+
const deadline = new Promise((resolve) => {
|
|
7130
|
+
timer = setTimeout(() => resolve(null), ms);
|
|
7131
|
+
});
|
|
7132
|
+
return Promise.race([outcome, deadline]).finally(() => {
|
|
7133
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
7134
|
+
});
|
|
7135
|
+
}
|
|
7136
|
+
/**
|
|
7137
|
+
* The host's verdict less every name the oracle provides.
|
|
7138
|
+
*
|
|
7139
|
+
* An entry whose list empties is dropped, and an entry ANY of whose names
|
|
7140
|
+
* comes back unknown gets no verdict at all — the rule `incompatibilityMap`
|
|
7141
|
+
* follows on the host, for the host's reason: an unavailable fact must never
|
|
7142
|
+
* read as an accusation, and keeping the rest of a list whose one unknown name
|
|
7143
|
+
* was dropped would publish a partial answer as the whole one. Each distinct
|
|
7144
|
+
* name is asked once, however many entries declare it. Never rejects: any
|
|
7145
|
+
* failure of the oracle is an unknown.
|
|
7146
|
+
*/
|
|
7147
|
+
async function refineIncompatible(incompatible, provides, timeoutMs = MODULE_PROBE_TIMEOUT_MS) {
|
|
7148
|
+
const answers = /* @__PURE__ */ new Map();
|
|
7149
|
+
const ask = (spec) => {
|
|
7150
|
+
const cached = answers.get(spec);
|
|
7151
|
+
if (cached !== void 0) return cached;
|
|
7152
|
+
const answer = withDeadline(new Promise((resolve) => {
|
|
7153
|
+
resolve(provides(spec));
|
|
7154
|
+
}).catch(() => null), timeoutMs);
|
|
7155
|
+
answers.set(spec, answer);
|
|
7156
|
+
return answer;
|
|
7157
|
+
};
|
|
7158
|
+
const judged = await Promise.all(Object.entries(incompatible).map(async ([key, names]) => {
|
|
7159
|
+
const verdicts = await Promise.all(names.map(ask));
|
|
7160
|
+
if (verdicts.some((verdict) => verdict !== true && verdict !== false)) return null;
|
|
7161
|
+
const missing = names.filter((_, index) => verdicts[index] === false);
|
|
7162
|
+
return missing.length > 0 ? {
|
|
7163
|
+
key,
|
|
7164
|
+
missing
|
|
7165
|
+
} : null;
|
|
7166
|
+
}));
|
|
7167
|
+
const refined = {};
|
|
7168
|
+
for (const row of judged) if (row !== null) refined[row.key] = row.missing;
|
|
7169
|
+
return refined;
|
|
7170
|
+
}
|
|
7171
|
+
/** The harness's own normalization (`stripClientSuffix`): a plugin bundle IS
|
|
7172
|
+
* its package's client half, so `x/client` and `x` name one row and one
|
|
7173
|
+
* record. Mirrored rather than imported — this package does not depend on
|
|
7174
|
+
* `@deepseek-ai/dsh-client-modules`. */
|
|
7175
|
+
function stripClientSuffix(spec) {
|
|
7176
|
+
return spec.endsWith("/client") ? spec.slice(0, -7) : spec;
|
|
7177
|
+
}
|
|
7178
|
+
/** The prefix of the one rejection that means "absent":
|
|
7179
|
+
* `ClientModuleSystem.import`'s own `client-modules: cannot resolve "<spec>" —
|
|
7180
|
+
* not a seed word, not a materialized module, and not a row in the boot
|
|
7181
|
+
* graph`. A harness that rewords it makes every probe unknown, which renders
|
|
7182
|
+
* as silence — the safe direction, never a false accusation. */
|
|
7183
|
+
const CANNOT_RESOLVE = "client-modules: cannot resolve";
|
|
7184
|
+
/**
|
|
7185
|
+
* An oracle over the live `modules` service, or null when the page offers no
|
|
7186
|
+
* usable table.
|
|
7187
|
+
*
|
|
7188
|
+
* The service is the harness's `ClientModuleLoader`, published to client
|
|
7189
|
+
* plugins as `ctx.reflect.provide('modules', …)`. It arrives as unknown-typed
|
|
7190
|
+
* foreign state, so only its PUBLIC contract is read, and only after
|
|
7191
|
+
* narrowing: `manifest.modules` must be an array, `loadCache` a Map and
|
|
7192
|
+
* `import` a function. `seed`, `factories` and `graphRows` exist on the
|
|
7193
|
+
* instance too, but they are TypeScript-`private` in the harness's own
|
|
7194
|
+
* declarations, and nothing here names them.
|
|
7195
|
+
*
|
|
7196
|
+
* Per spec, in the table's own order but with every side effect kept out:
|
|
7197
|
+
* 1. A graph row provides it, and `import` is NOT called — for a row it
|
|
7198
|
+
* fetches the bundle and runs the module body, and a probe must never be
|
|
7199
|
+
* what loads a plugin. `manifest.modules` is the row index, but it is not
|
|
7200
|
+
* fixed for the page's whole life on every harness build. On 0.1.5-rc.3
|
|
7201
|
+
* `this.manifest = options.manifest` is the constructor's only write, so a
|
|
7202
|
+
* row placed at boot stands unchanged for as long as the page lives. On
|
|
7203
|
+
* 0.1.7-rc.2 (`next`) `updateManifest` REPLACES `this.manifest` outright
|
|
7204
|
+
* with a new manifest that no longer lists an uninstalled package, and
|
|
7205
|
+
* `reconcile` removes the plugin from the loader — so UNTIL this page's own
|
|
7206
|
+
* uninstall reconciles, the OLD row still stands in `manifest.modules` and
|
|
7207
|
+
* this step clears the entry falsely; AFTER it reconciles, this step can no
|
|
7208
|
+
* longer be the false clear, because `manifest.modules` has already dropped
|
|
7209
|
+
* the package. The false clear then comes from `prune`, which keeps the
|
|
7210
|
+
* removed id only in the PRIVATE `graphRows` and in `loadCache`, and only
|
|
7211
|
+
* while some retained module still references it — that is step 2 below,
|
|
7212
|
+
* or the side effect of `import` in step 3 (a private graph row this
|
|
7213
|
+
* function cannot see).
|
|
7214
|
+
* 2. A `loadCache` record provides it, again without `import`.
|
|
7215
|
+
* 3. Otherwise `import(spec, '', {})`, with the spec as written — the seed
|
|
7216
|
+
* holds `react-dom/client` itself, not its stripped form. Resolving means a
|
|
7217
|
+
* seed word, whose lookup has no side effect; rejecting with the
|
|
7218
|
+
* "cannot resolve" message means absent; any other rejection is unknown.
|
|
7219
|
+
*
|
|
7220
|
+
* A kept-but-unmaterialized row is this function's own remaining blind spot:
|
|
7221
|
+
* asked about here, it still reaches step 3's `import` probe, which fetches
|
|
7222
|
+
* and runs it — the one side effect this module promises never to cause on
|
|
7223
|
+
* its own account. `refineAgainstModuleTable`'s page-removed set is the fix
|
|
7224
|
+
* for the one case a page can know about on its own — a name uninstalled
|
|
7225
|
+
* through THIS shop, on THIS page, since it loaded — by bypassing this oracle
|
|
7226
|
+
* for that name entirely rather than trusting a row that may only be kept. An
|
|
7227
|
+
* uninstall made from another tab, another window, or the CLI is not in that
|
|
7228
|
+
* set and stays unknown to this page until it reloads.
|
|
7229
|
+
*
|
|
7230
|
+
* One side-effect path remains: a registered page-local factory that is
|
|
7231
|
+
* neither a graph row nor yet materialized, which `import` materializes. That
|
|
7232
|
+
* is reachable only for a name the host could not resolve AND the page
|
|
7233
|
+
* registered without a row — the shell's own page-local modules are
|
|
7234
|
+
* node-resolvable, so the host never lists them. And what it does is what the
|
|
7235
|
+
* first real `require` of that name does anyway: run a factory this page
|
|
7236
|
+
* registered precisely so it would be materialized, once, memoized in
|
|
7237
|
+
* `loadCache`, fetching nothing. It moves that moment earlier; it runs nothing
|
|
7238
|
+
* the page did not already hold.
|
|
7239
|
+
*/
|
|
7240
|
+
function moduleTableOracle(service) {
|
|
7241
|
+
if (typeof service !== "object" || service === null) return null;
|
|
7242
|
+
let rows;
|
|
7243
|
+
let loadCache;
|
|
7244
|
+
let probe;
|
|
7245
|
+
try {
|
|
7246
|
+
const { manifest, loadCache: cache, import: load } = service;
|
|
7247
|
+
const modules = typeof manifest === "object" && manifest !== null ? manifest.modules : void 0;
|
|
7248
|
+
if (!Array.isArray(modules) || !(cache instanceof Map) || typeof load !== "function") return null;
|
|
7249
|
+
rows = /* @__PURE__ */ new Set();
|
|
7250
|
+
for (const row of modules) {
|
|
7251
|
+
const id = typeof row === "object" && row !== null ? row.id : void 0;
|
|
7252
|
+
if (typeof id === "string") rows.add(id);
|
|
7253
|
+
}
|
|
7254
|
+
loadCache = cache;
|
|
7255
|
+
probe = (spec) => Promise.resolve(load.call(service, spec, "", {}));
|
|
7256
|
+
} catch {
|
|
7257
|
+
return null;
|
|
7258
|
+
}
|
|
7259
|
+
return async (spec) => {
|
|
7260
|
+
const id = stripClientSuffix(spec);
|
|
7261
|
+
if (rows.has(id) || loadCache.has(id)) return true;
|
|
7262
|
+
try {
|
|
7263
|
+
await probe(spec);
|
|
7264
|
+
return true;
|
|
7265
|
+
} catch (error) {
|
|
7266
|
+
return error instanceof Error && error.message.startsWith(CANNOT_RESOLVE) ? false : null;
|
|
7267
|
+
}
|
|
7268
|
+
};
|
|
7269
|
+
}
|
|
7270
|
+
/**
|
|
7271
|
+
* The host's verdict as this page can stand behind it: refined against the
|
|
7272
|
+
* live table, or `{}` — no verdicts at all — when there is no usable table.
|
|
7273
|
+
*
|
|
7274
|
+
* Never the host's list unrefined. Measured, that list is majority-false on
|
|
7275
|
+
* this harness line (see the header), so showing it whenever the table cannot
|
|
7276
|
+
* be read would hand the reader a verdict already known to be unreliable;
|
|
7277
|
+
* silence is this project's documented degradation — an unavailable fact must
|
|
7278
|
+
* never read as an accusation. Never rejects, so a table in any state cannot
|
|
7279
|
+
* cost the reader the catalog.
|
|
7280
|
+
*
|
|
7281
|
+
* `removed` is the page-removed set (design 2026-09-01-harness-compatibility
|
|
7282
|
+
* section 9.1): package names this page has uninstalled since it loaded.
|
|
7283
|
+
* Every name in it is reported missing exactly
|
|
7284
|
+
* as the host said, WITHOUT ever reaching the table — not the rows check, not
|
|
7285
|
+
* `loadCache`, not `import` — because a row or a cache record can outlive a
|
|
7286
|
+
* restart-free uninstall (`moduleTableOracle`'s header), and the table's own
|
|
7287
|
+
* "provided" would be precisely the false clear this set exists to prevent.
|
|
7288
|
+
*/
|
|
7289
|
+
async function refineAgainstModuleTable(incompatible, service, removed = /* @__PURE__ */ new Set(), timeoutMs = MODULE_PROBE_TIMEOUT_MS) {
|
|
7290
|
+
const oracle = moduleTableOracle(service);
|
|
7291
|
+
if (oracle === null) return {};
|
|
7292
|
+
const withRemoved = async (spec) => removed.has(stripClientSuffix(spec)) ? false : oracle(spec);
|
|
7293
|
+
return refineIncompatible(incompatible, withRemoved, timeoutMs);
|
|
7294
|
+
}
|
|
7295
|
+
//#endregion
|
|
7296
|
+
//#region src/client/restart-monitor.ts
|
|
7297
|
+
/** Registry symbol, so every module instance on the page finds the same store. */
|
|
7298
|
+
const STORE_KEY = Symbol.for("dsh-plugin-shop.restart-monitor");
|
|
7299
|
+
function monitorStore() {
|
|
7300
|
+
const page = globalThis;
|
|
7301
|
+
let stored = page[STORE_KEY];
|
|
7302
|
+
if (stored === void 0) {
|
|
7303
|
+
stored = {
|
|
7304
|
+
state: null,
|
|
7305
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
7306
|
+
stop: null
|
|
7307
|
+
};
|
|
7308
|
+
page[STORE_KEY] = stored;
|
|
7309
|
+
}
|
|
7310
|
+
return Object.assign(stored, {
|
|
7311
|
+
carriers: stored.carriers ?? [],
|
|
7312
|
+
attempt: stored.attempt ?? null
|
|
7313
|
+
});
|
|
7314
|
+
}
|
|
7315
|
+
function settle(store, state) {
|
|
7316
|
+
store.state = state;
|
|
7317
|
+
for (const listener of [...store.listeners]) listener();
|
|
7318
|
+
}
|
|
7319
|
+
/** The page's restart, or null when it has committed none. The same object
|
|
7320
|
+
* until the state changes, as `useSyncExternalStore` requires. */
|
|
7321
|
+
function readRestartMonitor() {
|
|
7322
|
+
return monitorStore().state;
|
|
7323
|
+
}
|
|
7324
|
+
/** Be told when the page's restart changes state. */
|
|
7325
|
+
function subscribeRestartMonitor(listener) {
|
|
7326
|
+
const store = monitorStore();
|
|
7327
|
+
store.listeners.add(listener);
|
|
7328
|
+
return () => {
|
|
7329
|
+
store.listeners.delete(listener);
|
|
7330
|
+
};
|
|
7331
|
+
}
|
|
7332
|
+
/**
|
|
7333
|
+
* Watch the origin for the restarted server and reload into it: probe once
|
|
7334
|
+
* the grace period is over (the host exits within it, so an answer is the NEW
|
|
7335
|
+
* server), and reload only after it has KEPT answering for the stable window.
|
|
7336
|
+
* A boot that is about to fail can bind the port and answer before its plugin
|
|
7337
|
+
* tree is audited and it exits; reloading on that one answer left the reader
|
|
7338
|
+
* on a blank page instead of the notice naming the log (design
|
|
7339
|
+
* 2026-09-26-market-borrowings §3). `restartMonitorVerdict` decides; this
|
|
7340
|
+
* loop only probes. Probes never overlap: each is scheduled one poll interval
|
|
7341
|
+
* after the previous one settled.
|
|
7342
|
+
*
|
|
7343
|
+
* A restart already being watched is left alone — every offer on the page
|
|
7344
|
+
* renders this state, so a second press can only come from a panel that has
|
|
7345
|
+
* not re-rendered yet, and a second loop would probe the same origin twice.
|
|
7346
|
+
*/
|
|
7347
|
+
function startRestartMonitor(options) {
|
|
7348
|
+
const store = monitorStore();
|
|
7349
|
+
if (store.state?.kind === "restarting") return;
|
|
7350
|
+
store.stop?.();
|
|
7351
|
+
const { reload, logFile } = options;
|
|
7352
|
+
const started = Date.now();
|
|
7353
|
+
let stableSince = null;
|
|
7354
|
+
let stopped = false;
|
|
7355
|
+
let timer;
|
|
7356
|
+
const probe = async () => {
|
|
7357
|
+
if (Date.now() - started >= 3e3) {
|
|
7358
|
+
let up = false;
|
|
7359
|
+
try {
|
|
7360
|
+
up = (await fetch(globalThis.location.href, { cache: "no-store" })).ok;
|
|
7361
|
+
} catch {}
|
|
7362
|
+
if (stopped) return;
|
|
7363
|
+
const elapsed = Date.now() - started;
|
|
7364
|
+
stableSince = up ? stableSince ?? elapsed : null;
|
|
7365
|
+
const verdict = restartMonitorVerdict({
|
|
7366
|
+
elapsedMs: elapsed,
|
|
7367
|
+
stableSinceMs: stableSince
|
|
7368
|
+
});
|
|
7369
|
+
if (verdict === "reload") {
|
|
7370
|
+
store.stop = null;
|
|
7371
|
+
reload();
|
|
7372
|
+
return;
|
|
7373
|
+
}
|
|
7374
|
+
if (verdict === "failed") {
|
|
7375
|
+
store.stop = null;
|
|
7376
|
+
settle(store, logFile === void 0 ? { kind: "failed" } : {
|
|
7377
|
+
kind: "failed",
|
|
7378
|
+
logFile
|
|
7379
|
+
});
|
|
7380
|
+
return;
|
|
7381
|
+
}
|
|
7382
|
+
}
|
|
7383
|
+
timer = setTimeout(() => {
|
|
7384
|
+
probe();
|
|
7385
|
+
}, INSTALL_POLL_MS);
|
|
7386
|
+
};
|
|
7387
|
+
store.stop = () => {
|
|
7388
|
+
stopped = true;
|
|
7389
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
7390
|
+
};
|
|
7391
|
+
timer = setTimeout(() => {
|
|
7392
|
+
probe();
|
|
7393
|
+
}, INSTALL_POLL_MS);
|
|
7394
|
+
settle(store, logFile === void 0 ? { kind: "restarting" } : {
|
|
7395
|
+
kind: "restarting",
|
|
7396
|
+
logFile
|
|
7397
|
+
});
|
|
7398
|
+
}
|
|
7399
|
+
/**
|
|
7400
|
+
* Register one module instance's path to the host; the client half's apply
|
|
7401
|
+
* does, with the `restart` it hands the tab. The returned function withdraws
|
|
7402
|
+
* it, and has to run BEFORE the mount it rides is disposed: a request still in
|
|
7403
|
+
* flight on it is then asked again through the newest live path — at once if
|
|
7404
|
+
* one is registered, or as soon as one is — rather than waiting on a call the
|
|
7405
|
+
* dispose is about to abort.
|
|
7406
|
+
*/
|
|
7407
|
+
function registerRestartCarrier(restart) {
|
|
7408
|
+
const store = monitorStore();
|
|
7409
|
+
const carrier = {
|
|
7410
|
+
restart,
|
|
7411
|
+
live: true
|
|
7412
|
+
};
|
|
7413
|
+
store.carriers.push(carrier);
|
|
7414
|
+
const attempt = store.attempt;
|
|
7415
|
+
if (attempt !== null && !attempt.carrier.live) ask(store, attempt, carrier);
|
|
7416
|
+
return () => {
|
|
7417
|
+
if (!carrier.live) return;
|
|
7418
|
+
carrier.live = false;
|
|
7419
|
+
const current = monitorStore();
|
|
7420
|
+
current.carriers = current.carriers.filter((each) => each !== carrier);
|
|
7421
|
+
const orphaned = current.attempt;
|
|
7422
|
+
const next = current.carriers.at(-1);
|
|
7423
|
+
if (orphaned !== null && orphaned.carrier === carrier && next !== void 0) ask(current, orphaned, next);
|
|
7424
|
+
};
|
|
7425
|
+
}
|
|
7426
|
+
/**
|
|
7427
|
+
* Ask the host to restart, for the page (design §8). The request is the
|
|
7428
|
+
* page's from the confirm on, not from the answer: every offer renders it as
|
|
7429
|
+
* under way, and a second press joins it rather than asking twice. It rides
|
|
7430
|
+
* the newest path a module instance registered, or — where no client half
|
|
7431
|
+
* registered one — the pressing tab's own `restart`.
|
|
7432
|
+
*
|
|
7433
|
+
* Resolves, never rejects, with what the press should show. A commit starts
|
|
7434
|
+
* the monitor and is `started`. A refusal and an unreachable host leave
|
|
7435
|
+
* nothing under way. A path withdrawn mid-call decides nothing but a commit;
|
|
7436
|
+
* the ask on the path that replaced it decides the rest, and that ask
|
|
7437
|
+
* finding no host is followed as a restart, since the first ask may have
|
|
7438
|
+
* committed and taken its host down with it.
|
|
7439
|
+
*/
|
|
7440
|
+
function requestRestart(options) {
|
|
7441
|
+
const store = monitorStore();
|
|
7442
|
+
if (store.state?.kind === "restarting") return Promise.resolve({ kind: "started" });
|
|
7443
|
+
return new Promise((resolve) => {
|
|
7444
|
+
const pending = store.attempt;
|
|
7445
|
+
if (pending !== null) {
|
|
7446
|
+
pending.waiters.push(resolve);
|
|
7447
|
+
return;
|
|
7448
|
+
}
|
|
7449
|
+
const carrier = store.carriers.at(-1) ?? {
|
|
7450
|
+
restart: options.restart,
|
|
7451
|
+
live: true
|
|
7452
|
+
};
|
|
7453
|
+
const attempt = {
|
|
7454
|
+
carrier,
|
|
7455
|
+
reload: options.reload,
|
|
7456
|
+
reissued: false,
|
|
7457
|
+
waiters: [resolve]
|
|
7458
|
+
};
|
|
7459
|
+
store.attempt = attempt;
|
|
7460
|
+
settle(store, { kind: "requesting" });
|
|
7461
|
+
ask(store, attempt, carrier);
|
|
7462
|
+
});
|
|
7463
|
+
}
|
|
7464
|
+
/** Carry the request on one path. Its answer decides only while the request
|
|
7465
|
+
* is still the page's and that path still carries it — a commit excepted. */
|
|
7466
|
+
function ask(store, attempt, carrier) {
|
|
7467
|
+
if (attempt.carrier !== carrier) {
|
|
7468
|
+
attempt.carrier = carrier;
|
|
7469
|
+
attempt.reissued = true;
|
|
7470
|
+
}
|
|
7471
|
+
(async () => carrier.restart())().then((result) => {
|
|
7472
|
+
if (store.attempt !== attempt) return;
|
|
7473
|
+
if (result.ok) {
|
|
7474
|
+
finish(store, attempt, { kind: "started" });
|
|
7475
|
+
startRestartMonitor({
|
|
7476
|
+
reload: attempt.reload,
|
|
7477
|
+
...typeof result.logFile === "string" ? { logFile: result.logFile } : {}
|
|
7478
|
+
});
|
|
7479
|
+
return;
|
|
7480
|
+
}
|
|
7481
|
+
if (superseded(attempt, carrier)) return;
|
|
7482
|
+
finish(store, attempt, {
|
|
7483
|
+
kind: "refused",
|
|
7484
|
+
detail: result.detail
|
|
7485
|
+
});
|
|
7486
|
+
settle(store, null);
|
|
7487
|
+
}, () => {
|
|
7488
|
+
if (store.attempt !== attempt || superseded(attempt, carrier)) return;
|
|
7489
|
+
if (attempt.reissued) {
|
|
7490
|
+
finish(store, attempt, { kind: "started" });
|
|
7491
|
+
startRestartMonitor({ reload: attempt.reload });
|
|
7492
|
+
return;
|
|
7493
|
+
}
|
|
7494
|
+
finish(store, attempt, { kind: "unreachable" });
|
|
7495
|
+
settle(store, null);
|
|
7496
|
+
});
|
|
7497
|
+
}
|
|
7498
|
+
/** Whether a path's answer is one the request has moved past: the path was
|
|
7499
|
+
* withdrawn, so the dispose aborted it or the ask that replaced it decides. */
|
|
7500
|
+
function superseded(attempt, carrier) {
|
|
7501
|
+
return !carrier.live || attempt.carrier !== carrier;
|
|
7502
|
+
}
|
|
7503
|
+
function finish(store, attempt, outcome) {
|
|
7504
|
+
store.attempt = null;
|
|
7505
|
+
for (const waiter of attempt.waiters) waiter(outcome);
|
|
7506
|
+
}
|
|
7507
|
+
//#endregion
|
|
6901
7508
|
//#region src/client/useFlows.ts
|
|
6902
7509
|
/**
|
|
6903
7510
|
* One keyed registry of install-shaped flows: start, poll to terminal, reset.
|
|
@@ -7177,117 +7784,121 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7177
7784
|
};
|
|
7178
7785
|
}
|
|
7179
7786
|
//#endregion
|
|
7180
|
-
//#region \0dsh-plugin-shop-css:
|
|
7181
|
-
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 *\n * Clipped on BOTH axes, with a margin, because `.s232ec4_searchInput` sits at\n * exactly (0, 0) of this box — it is the first child of `.s646509_toolbar`, itself\n * the first child here, and this rule carries no padding — so its focus\n * ring paints ENTIRELY outside the panel.\n *\n * Both axes is the load-bearing part: `overflow-clip-margin` is inert\n * unless the element clips on both. Clipping x alone left the margin doing\n * nothing — measured against the live app on 0.7.1, the ring reached 0 of\n * its 3px on the left at every device pixel ratio (1, 1.25, 1.5, 2), while\n * keeping the full 3px on the right, where the 280px input stops short of\n * the panel and never meets the clip edge. Raising the margin to 6px\n * changed nothing; clipping both axes with the SAME 3px restored the whole\n * ring. A test that reads declarations cannot see that difference, which is\n * why the first attempt at this shipped broken.\n *\n * `clip` is not a scroll container, so this does not bring back the\n * accidental scrolling `hidden` caused by coercing the other axis to\n * `auto`. Nothing is lost vertically: the panel's scrollHeight equals its\n * height and no descendant paints outside the clip rect (measured).\n * `overflow-x: hidden` stays as the fallback for engines without `clip`,\n * where the ring is clipped as it was before, never worse. */\n overflow-x: hidden;\n overflow: clip;\n overflow-clip-margin: 3px;\n}\n\n.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.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 align-items: center;\n gap: 6px;\n margin-top: 6px;\n}\n\n.s26d58e_categoryButton {\n /* The hue this button carries when selected. `All` and `Installed` select by\n * something other than a category and keep the brand token; the six category\n * buttons override it from the shared hue table below, so a pressed Tool tab\n * turns the same blue as a tool card's spine. */\n --category-hue: var(--dsw-alias-brand-primary);\n padding: 3px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n/* Blend text with the theme foreground: darker on light backgrounds, lighter\n * on dark ones. The unblended category hue belongs on the border and fill. */\n.s26d58e_categoryButton:hover {\n border-color: color-mix(in srgb, var(--category-hue) 45%, var(--dsw-alias-border-l2));\n color: color-mix(in srgb, var(--category-hue) 45%, var(--dsw-alias-label-primary) 55%);\n}\n\n/* Selected. Written `.s26d58e_categoryButton.s5f68c4_categoryButtonOn` — TWO classes — so it\n * matches `.s26d58e_categoryButton:hover`'s specificity and, coming after it, wins.\n * As a bare `.s5f68c4_categoryButtonOn` it lost both `border-color` and `color` to the\n * hover rule for as long as the pointer stayed on the tab that had just been\n * clicked, which is precisely when a reader looks for confirmation.\n *\n * EVERY declaration here is a colour or a shadow: a pressed tab must occupy\n * exactly the same box as an unpressed one, so the bar does not reflow — and\n * with ~9 pills on a wrapping row, one that grows can push the rest onto\n * another line and move the tab out from under the pointer that just clicked\n * it. That is what `font-weight: 600` did (bold metrics are wider than\n * regular), and it is why nothing may be added here that resolves to a length:\n * no padding, no border-width, no font-size, no letter-spacing.\n * `css-tokens.s8583e3_client.sfd9d8a_spec.s5f67aa_ts` fails the build on one.\n *\n * `box-shadow` is the exception that earns its place: it paints outside the\n * layout box, so an inset ring doubles the border's weight at zero cost to the\n * metrics. It is what `font-weight: 600` used to be — a second, thickness-\n * based signal that hover does not also produce — and the `other` tab needs it\n * most: its hue IS the neutral gray this file uses for \"no category\", so on\n * the light theme hue alone barely separates pressed from unpressed. */\n.s26d58e_categoryButton.s5f68c4_categoryButtonOn {\n border-color: var(--category-hue);\n background: color-mix(in srgb, var(--category-hue) 14%, transparent);\n color: color-mix(in srgb, var(--category-hue) 45%, var(--dsw-alias-label-primary) 55%);\n box-shadow: inset 0 0 0 1px var(--category-hue);\n}\n\n/* Forced colours replace every colour above with the system palette, which\n * would leave the three colour declarations saying nothing and the pressed tab\n * indistinguishable from its neighbours. `box-shadow` is dropped in this mode\n * too, so the ring has to come back as an outline. */\n@media (forced-colors: active) {\n .s26d58e_categoryButton.s5f68c4_categoryButtonOn {\n outline: 2px solid ButtonText;\n outline-offset: -4px;\n }\n}\n\n.s26d58e_categoryButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The incompatible filter, at the right edge of the category bar: `margin-left:\n * auto` pushes it past the pills, and the bar wraps, so on a narrow panel it\n * drops to its own line rather than squeezing them. It is a MODIFIER, not a\n * ninth category — the categories select what to show and this one subtracts\n * from whatever they selected — so it takes the error hue it filters on rather\n * than joining the hue table.\n *\n * And it is a SWITCH, which is the difference the shape now carries. It used\n * to wear `.s26d58e_categoryButton`: same pill, same row, same size as the eight tabs,\n * differing only in being red. That reads as a ninth tab, and it is not one —\n * the tabs are choose-one and this is on-or-off. So it drops the pill chrome\n * entirely (no border, no fill, nothing to compete with the row it sits\n * beside) and puts the state in a track, the same one every installed card\n * carries. Nothing here restates the switch geometry; `--switch-hue` is the\n * whole of what this control says about it, and it reaches the label too, so\n * one declaration keeps the word and the track the same colour. */\n.sfd887b_incompatibleFilter {\n --switch-hue: var(--dsw-alias-state-error-primary);\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n /* Matches the pills' vertical padding so the bar keeps one line box whether\n * this control wraps onto a row of its own or shares theirs. */\n padding: 3px 0;\n border: 0;\n background: none;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n/* Hover lifts the word to full contrast and warms the track's border. Scoped\n * to the OFF track: `.sfd887b_incompatibleFilter:hover .s11c019_switch` outranks `.s7580e8_switchOn`\n * by two classes, so unscoped it would strip the hue from the border at\n * exactly the moment a reader has the pointer on it to check the state. */\n.sfd887b_incompatibleFilter:hover {\n color: var(--dsw-alias-label-primary);\n}\n\n.sfd887b_incompatibleFilter:hover .s11c019_switch:not(.s7580e8_switchOn) {\n border-color: color-mix(in srgb, var(--switch-hue) 45%, var(--dsw-alias-border-l2));\n}\n\n/* On. Written with both classes for the reason `.s26d58e_categoryButton.s5f68c4_categoryButtonOn`\n * is: it has to match `:hover`'s specificity and come after it, or the colour\n * reverts under a pointer that has not moved since the click. */\n.sfd887b_incompatibleFilter.s6e4cba_incompatibleFilterOn {\n color: color-mix(in srgb, var(--switch-hue) 45%, var(--dsw-alias-label-primary) 55%);\n}\n\n/* Off here means \"nothing is being taken away\" — the resting state — so the\n * knob steps back to the secondary label. The enable switch keeps the full\n * strength dot, where off means \"this plugin is disabled\" and is worth\n * noticing. In this bar it would be the heaviest mark in a row of hairline\n * pills, and on the light theme it is the WHOLE control: bg-layer-2 is the\n * same white as the panel there, so an off track is a hairline outline with a\n * near-black dot sitting in it. */\n.sfd887b_incompatibleFilter .s11c019_switch:not(.s7580e8_switchOn) .saab2b3_switchKnob {\n background: var(--dsw-alias-label-secondary);\n}\n\n/* The ring has no border to trace, so it takes an offset and the pill radius\n * of the tabs it sits beside rather than hugging the text. */\n.sfd887b_incompatibleFilter:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: 999px;\n}\n\n.sebf017_updatableHeading {\n margin: 2px 0 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-primary);\n}\n\n.seb0a1e_catalogStatsRow {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 6px 0 0;\n}\n\n.sccc4d1_catalogStats {\n margin: 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* Sits beside the 12px stats caption, so it is quieter than `.s73242a_actionButton`:\n * this is an affordance, not a call to action — the catalog reloads on its\n * own at mount and the shelf is already usable without it. Named\n * `catalogRefresh*` (not `reload*`) so it cannot collide with the §4 page\n * reload offer below, which reloads the whole tab rather than refetching\n * the catalog data. */\n.sff243d_catalogRefreshButton {\n padding: 1px 9px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n line-height: 17px;\n cursor: pointer;\n}\n\n.sff243d_catalogRefreshButton:hover:not(:disabled) {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.sff243d_catalogRefreshButton:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.sff243d_catalogRefreshButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s194168_catalogRefreshFailed {\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary);\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(--category-hue, #8B8E96);\n}\n\n/* The hue table, keyed by `data-category` and read by BOTH surfaces that\n * speak in category colour: a card's spine and badge, and the category bar's\n * own tab. One table, so a tab can never disagree with the cards it filters to\n * — which is the whole point of colouring the tab. `.s26d58e_categoryButton` sets the\n * brand fallback for `All`/`Installed`; these attribute selectors are one\n * specificity step above it and win wherever the attribute is present. */\n.s65f383_card[data-category='tool'], .s26d58e_categoryButton[data-category='tool'] { --category-hue: #4C8DFF; }\n.s65f383_card[data-category='provider'], .s26d58e_categoryButton[data-category='provider'] { --category-hue: #A78BFA; }\n.s65f383_card[data-category='ui'], .s26d58e_categoryButton[data-category='ui'] { --category-hue: #2DD4BF; }\n.s65f383_card[data-category='workflow'], .s26d58e_categoryButton[data-category='workflow'] { --category-hue: #F59E0B; }\n.s65f383_card[data-category='integration'], .s26d58e_categoryButton[data-category='integration'] { --category-hue: #34D399; }\n.s65f383_card[data-category='theme'], .s26d58e_categoryButton[data-category='theme'] { --category-hue: #F472B6; }\n.s65f383_card[data-category='other'], .s26d58e_categoryButton[data-category='other'] { --category-hue: #8B8E96; }\n\n/* The category sits in the badge row as a quiet chip in the category's own\n * hue — the border carries it where the light theme's white layers cannot\n * (the same lesson as the check button). */\n.s9c8eaa_categoryBadge {\n padding: 1px 7px;\n border: 1px solid color-mix(in srgb, var(--category-hue, #8B8E96) 45%, transparent);\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--category-hue, #8B8E96);\n white-space: nowrap;\n}\n\n.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 flex-shrink: 0;\n padding: 3px 9px;\n border-radius: 999px;\n font-size: 12px;\n white-space: nowrap;\n color: var(--dsw-alias-state-error-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 40%, transparent);\n}\n\n.sff43b4_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s73f7fb_chevron, .s61393c_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.s61393c_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .s73f7fb_chevron, .s61393c_chevronOpen { transition: none; }\n}\n\n.s820ebf_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.sb8fbc8_summary {\n margin: 0;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n display: -webkit-box;\n -webkit-line-clamp: 2;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n.se507db_summaryZh {\n margin: -4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n display: -webkit-box;\n -webkit-line-clamp: 1;\n -webkit-box-orient: vertical;\n overflow: hidden;\n}\n\n/* Expanded cards show the full summary: the clamp is lifted, never raised —\n * a higher number would still truncate the author's text, just later. */\n.s10baf0_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.sb9dba9_summaryZhExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n/* `white-space: pre-line` so the newline in the copy is the copy's own: the\n dictionary decides where the line breaks, and a translator changes it\n without touching the component. Collapses runs of spaces like `normal`, so\n only the explicit \\n survives. */\n.s6eca19_incompatibleDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n white-space: pre-line;\n color: var(--dsw-alias-state-error-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/* What the entry IS rather than what you can do with it — how big it is and\n * who published it — at the right edge of the action row. `margin-left: auto`\n * is what pushes it there, and it lives on this wrapper rather than on either\n * label so the group stays flush right when only one of the two has a value —\n * a packument that named no account has no author, and an entry the registry\n * could not measure honestly has no size. Rarer than it was (every live entry\n * now carries a size, github included), but the wrapper is not what makes it\n * rare: without one, an EMPTY meta group still costs 8px, because\n * `.s9b5048_cardActions` is a flex row with `gap: 8px` and a zero-width item is still\n * an item. The row is a wrapping flex, so an active install flow (which claims\n * the full width) drops the whole group to the next line rather than squeezing\n * it. */\n.s5fbaf9_cardMeta {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n margin-left: auto;\n min-width: 0;\n}\n\n/* The on-disk size, left of the author. Mono and tabular so the digits of one\n * figure line up with the digits of another WHEREVER the two land: `.s5fbaf9_cardMeta`\n * is flush right and the author sits outside the size, so a size's x position\n * moves with the author's width and is different again on the many entries\n * that carry no author at all. There is no column to align to, which is why\n * the reason given here is per-figure legibility rather than the column the\n * first version of this comment claimed. Secondary colour, because it\n * qualifies the entry rather than announcing it.\n *\n * `font-variant-numeric` is safe HERE and would not be on a pill: this element\n * has no state rules, so nothing it does can reflow a wrapping row. */\n.s2d3c05_size {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n letter-spacing: 0.02em;\n white-space: nowrap;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The publishing account, at the outer end of `.s5fbaf9_cardMeta` on the action row —\n * NOT beside the npm link in the expanded detail, which is where it used to\n * live and what this comment described until the accurate half was deleted\n * with the `margin-left: auto` that moved to `.s5fbaf9_cardMeta`. There is no middot:\n * `.s5fbaf9_cardMeta`'s 8px gap separates it from the size.\n *\n * Secondary label colour carries the distinction — never a background fill,\n * which is invisible in the light theme where every bg-layer token is the\n * same white. */\n.s1ff66f_author {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.sd58c6d_reviewedLine {\n margin: 8px 0 0;\n padding: 6px 8px;\n border-radius: 6px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 12%, transparent);\n font-size: 12px;\n color: var(--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.sb00c36_restartSelfButton {\n /* The version row's third state. Geometry matches .s614a38_checkUpdateButton and\n * .sa04455_updateSelfButton exactly — the row is one evolving control and a\n * different pill would read as a different kind of thing. It takes the\n * emphasised fill rather than the neutral one because at that moment the\n * restart is the only thing left to do, the same reason Update has it. */\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-brand-primary);\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n}\n\n.sb00c36_restartSelfButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The two activation offers share one recipe: a border in an alias token,\n * never a `bg-layer` fill alone — all four bg-layer tokens collapse to the\n * same white under `body` in the light theme, and a fill-only button would\n * vanish there. Grouped rather than copied, because a copy is a rule that\n * can be corrected on one button and left wrong on the other, silently. */\n.s6ac237_restartButton,\n.s64a5ff_reloadButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s6ac237_restartButton:hover,\n.s64a5ff_reloadButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s6ac237_restartButton:focus-visible,\n.s64a5ff_reloadButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The §4 reload offer: a notice plus its button, one item of\n * `.sf53ca0_installedActions` the way the restart gate is — a column of its own\n * rather than inline with the row. */\n.sf79aef_reloadPanel {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 6px;\n}\n\n\n.sc4f2ac_gate {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 10px;\n border: 1px solid var(--dsw-alias-state-warn-primary);\n border-radius: 8px;\n background: color-mix(in srgb, var(--dsw-alias-state-warn-primary) 8%, transparent);\n}\n\n.s559c38_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--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 white-space: pre-line;\n color: var(--dsw-alias-state-error-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/* The Updatable section. The class keeps the Host's field name; only the\n heading the reader sees was renamed. */\n.sa276cf_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.sceae6e_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s8d2072_outdatedRow {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n padding: 10px 12px;\n border: 1px solid var(--dsw-alias-border-l1);\n border-radius: 8px;\n background: var(--dsw-alias-bg-layer-1);\n}\n\n.s5c2545_outdatedInfo {\n display: flex;\n flex-direction: 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 /* The reload offer (§4) mounts inside the switch beside the Update button\n * and is a two-line block, not a control: without wrapping, a row reading\n * [switch][notice + Reload][Update] overflows the settings dialog. */\n flex-wrap: wrap;\n gap: 10px;\n}\n\n/* The shop's one switch. It is worn by two controls that mean different\n * things — the enable/disable toggle on an installed card, and the shelf's\n * incompatible filter — so the geometry lives here once and each caller states\n * only its hue.\n *\n * `--switch-hue` is that knob, and it is read from the ANCESTOR rather than\n * declared per state: `.sfd887b_incompatibleFilter` sets it once and it reaches the\n * track and the label together, which is what keeps a red switch from sitting\n * under a neutral word. A declaration on `.s11c019_switch` itself would shadow the\n * inherited value (an element's own rule beats what it inherits), so the\n * default rides in the `var()` fallback and is resolved once into\n * `--track-hue` instead of being spelled out at each use.\n *\n * `box-sizing` and `padding` are stated because the two callers are different\n * elements — a `<button>`, which the UA gives padding, and a `<span>`, which\n * it does not. Without them the same class renders two sizes, and the knob\n * sits 2px above centre in the taller one. */\n.s11c019_switch {\n --track-hue: var(--switch-hue, var(--dsw-alias-state-success-primary));\n box-sizing: border-box;\n display: inline-block;\n position: relative;\n width: 32px;\n height: 18px;\n padding: 0;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n\n/* The knob's POSITION is the state, which is the reason this control needs no\n * `forced-colors` fallback the way the category pills do: a system palette\n * replaces every colour below and moves nothing. */\n.s7580e8_switchOn {\n border-color: var(--track-hue);\n background: color-mix(in srgb, var(--track-hue) 55%, transparent);\n}\n\n.saab2b3_switchKnob {\n position: absolute;\n top: 2px;\n left: 2px;\n width: 12px;\n height: 12px;\n border-radius: 999px;\n background: var(--dsw-alias-label-primary);\n transition: transform 120ms ease, background 120ms ease;\n}\n\n.s7580e8_switchOn .saab2b3_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.s11c019_switch:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .s11c019_switch, .saab2b3_switchKnob { transition: none; }\n}\n\n/* The hot enable/disable switch on installed rows: the knob button plus the\n * §8 hot-apply / failure notices rendered beside it. */\n.sd9f9e4_switchWrap {\n display: inline-flex;\n align-items: center;\n /* Same reason as `.sa1f556_outdatedActions`: `.sf79aef_reloadPanel`'s own comment calls\n * itself \"a column of its own rather than inline with the row\", which held\n * for `.sf53ca0_installedActions` and not here until this line. */\n flex-wrap: wrap;\n gap: 8px;\n}\n";
|
|
7182
|
-
if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"
|
|
7787
|
+
//#region \0dsh-plugin-shop-css:201c343a
|
|
7788
|
+
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.se24648_client.s4c8568_config.s64bf36_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.se24648_client.scd01a1_spec.s64bf36_ts` pins this. */\n\n.sa18b47_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 *\n * Clipped on BOTH axes, with a margin, because `.seba6a3_searchInput` sits at\n * exactly (0, 0) of this box — it is the first child of `.s7df13e_toolbar`, itself\n * the first child here, and this rule carries no padding — so its focus\n * ring paints ENTIRELY outside the panel.\n *\n * Both axes is the load-bearing part: `overflow-clip-margin` is inert\n * unless the element clips on both. Clipping x alone left the margin doing\n * nothing — measured against the live app on 0.7.1, the ring reached 0 of\n * its 3px on the left at every device pixel ratio (1, 1.25, 1.5, 2), while\n * keeping the full 3px on the right, where the 280px input stops short of\n * the panel and never meets the clip edge. Raising the margin to 6px\n * changed nothing; clipping both axes with the SAME 3px restored the whole\n * ring. A test that reads declarations cannot see that difference, which is\n * why the first attempt at this shipped broken.\n *\n * `clip` is not a scroll container, so this does not bring back the\n * accidental scrolling `hidden` caused by coercing the other axis to\n * `auto`. Nothing is lost vertically: the panel's scrollHeight equals its\n * height and no descendant paints outside the clip rect (measured).\n * `overflow-x: hidden` stays as the fallback for engines without `clip`,\n * where the ring is clipped as it was before, never worse. */\n overflow-x: hidden;\n overflow: clip;\n overflow-clip-margin: 3px;\n}\n\n.sd85001_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.se60107_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.sba4a38_skeletonGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n}\n\n.s5aa954_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.s5aa954_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.s7a0318_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.s7a5df4_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.s8d4020_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.sbc0e1a_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.s5aa954_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 .s5aa954_skeletonCard span { animation: none; opacity: 0.75; }\n .s5aa954_skeletonCard::after { content: none; }\n}\n\n.s4a2332_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.s4a2332_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.s4a2332_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s7df13e_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.s20709b_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.s98c547_versionBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.s2b9f1d_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.s394a1f_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.s394a1f_updateSelfButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, transparent);\n}\n\n.s394a1f_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.s511e9b_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.s511e9b_checkUpdateButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n}\n\n.s511e9b_checkUpdateButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.s511e9b_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.se3bcc2_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.se3bcc2_githubLink:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.se3bcc2_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.s8c6b17_selfUpdatePanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n}\n\n.seba6a3_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.seba6a3_searchInput:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.sc9d2d3_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.sb4e3b8_categoryBar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n margin-top: 6px;\n}\n\n.s85e01e_categoryButton {\n /* The hue this button carries when selected. `All` and `Installed` select by\n * something other than a category and keep the brand token; the six category\n * buttons override it from the shared hue table below, so a pressed Tool tab\n * turns the same blue as a tool card's spine. */\n --category-hue: var(--dsw-alias-brand-primary);\n padding: 3px 10px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-1);\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n/* Blend text with the theme foreground: darker on light backgrounds, lighter\n * on dark ones. The unblended category hue belongs on the border and fill. */\n.s85e01e_categoryButton:hover {\n border-color: color-mix(in srgb, var(--category-hue) 45%, var(--dsw-alias-border-l2));\n color: color-mix(in srgb, var(--category-hue) 45%, var(--dsw-alias-label-primary) 55%);\n}\n\n/* Selected. Written `.s85e01e_categoryButton.s833fec_categoryButtonOn` — TWO classes — so it\n * matches `.s85e01e_categoryButton:hover`'s specificity and, coming after it, wins.\n * As a bare `.s833fec_categoryButtonOn` it lost both `border-color` and `color` to the\n * hover rule for as long as the pointer stayed on the tab that had just been\n * clicked, which is precisely when a reader looks for confirmation.\n *\n * EVERY declaration here is a colour or a shadow: a pressed tab must occupy\n * exactly the same box as an unpressed one, so the bar does not reflow — and\n * with ~9 pills on a wrapping row, one that grows can push the rest onto\n * another line and move the tab out from under the pointer that just clicked\n * it. That is what `font-weight: 600` did (bold metrics are wider than\n * regular), and it is why nothing may be added here that resolves to a length:\n * no padding, no border-width, no font-size, no letter-spacing.\n * `css-tokens.se24648_client.scd01a1_spec.s64bf36_ts` fails the build on one.\n *\n * `box-shadow` is the exception that earns its place: it paints outside the\n * layout box, so an inset ring doubles the border's weight at zero cost to the\n * metrics. It is what `font-weight: 600` used to be — a second, thickness-\n * based signal that hover does not also produce — and the `other` tab needs it\n * most: its hue IS the neutral gray this file uses for \"no category\", so on\n * the light theme hue alone barely separates pressed from unpressed. */\n.s85e01e_categoryButton.s833fec_categoryButtonOn {\n border-color: var(--category-hue);\n background: color-mix(in srgb, var(--category-hue) 14%, transparent);\n color: color-mix(in srgb, var(--category-hue) 45%, var(--dsw-alias-label-primary) 55%);\n box-shadow: inset 0 0 0 1px var(--category-hue);\n}\n\n/* Forced colours replace every colour above with the system palette, which\n * would leave the three colour declarations saying nothing and the pressed tab\n * indistinguishable from its neighbours. `box-shadow` is dropped in this mode\n * too, so the ring has to come back as an outline. */\n@media (forced-colors: active) {\n .s85e01e_categoryButton.s833fec_categoryButtonOn {\n outline: 2px solid ButtonText;\n outline-offset: -4px;\n }\n}\n\n.s85e01e_categoryButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The incompatible filter, at the right edge of the category bar: `margin-left:\n * auto` pushes it past the pills, and the bar wraps, so on a narrow panel it\n * drops to its own line rather than squeezing them. It is a MODIFIER, not a\n * ninth category — the categories select what to show and this one subtracts\n * from whatever they selected — so it takes the error hue it filters on rather\n * than joining the hue table.\n *\n * And it is a SWITCH, which is the difference the shape now carries. It used\n * to wear `.s85e01e_categoryButton`: same pill, same row, same size as the eight tabs,\n * differing only in being red. That reads as a ninth tab, and it is not one —\n * the tabs are choose-one and this is on-or-off. So it drops the pill chrome\n * entirely (no border, no fill, nothing to compete with the row it sits\n * beside) and puts the state in a track, the same one every installed card\n * carries. Nothing here restates the switch geometry; `--switch-hue` is the\n * whole of what this control says about it, and it reaches the label too, so\n * one declaration keeps the word and the track the same colour. */\n.sab8b1c_incompatibleFilter {\n --switch-hue: var(--dsw-alias-state-error-primary);\n margin-left: auto;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n /* Matches the pills' vertical padding so the bar keeps one line box whether\n * this control wraps onto a row of its own or shares theirs. */\n padding: 3px 0;\n border: 0;\n background: none;\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n cursor: pointer;\n}\n\n/* Hover lifts the word to full contrast and warms the track's border. Scoped\n * to the OFF track: `.sab8b1c_incompatibleFilter:hover .sc4c0fe_switch` outranks `.s383bcc_switchOn`\n * by two classes, so unscoped it would strip the hue from the border at\n * exactly the moment a reader has the pointer on it to check the state. */\n.sab8b1c_incompatibleFilter:hover {\n color: var(--dsw-alias-label-primary);\n}\n\n.sab8b1c_incompatibleFilter:hover .sc4c0fe_switch:not(.s383bcc_switchOn) {\n border-color: color-mix(in srgb, var(--switch-hue) 45%, var(--dsw-alias-border-l2));\n}\n\n/* On. Written with both classes for the reason `.s85e01e_categoryButton.s833fec_categoryButtonOn`\n * is: it has to match `:hover`'s specificity and come after it, or the colour\n * reverts under a pointer that has not moved since the click. */\n.sab8b1c_incompatibleFilter.s6dac80_incompatibleFilterOn {\n color: color-mix(in srgb, var(--switch-hue) 45%, var(--dsw-alias-label-primary) 55%);\n}\n\n/* Off here means \"nothing is being taken away\" — the resting state — so the\n * knob steps back to the secondary label. The enable switch keeps the full\n * strength dot, where off means \"this plugin is disabled\" and is worth\n * noticing. In this bar it would be the heaviest mark in a row of hairline\n * pills, and on the light theme it is the WHOLE control: bg-layer-2 is the\n * same white as the panel there, so an off track is a hairline outline with a\n * near-black dot sitting in it. */\n.sab8b1c_incompatibleFilter .sc4c0fe_switch:not(.s383bcc_switchOn) .s43a686_switchKnob {\n background: var(--dsw-alias-label-secondary);\n}\n\n/* The ring has no border to trace, so it takes an offset and the pill radius\n * of the tabs it sits beside rather than hugging the text. */\n.sab8b1c_incompatibleFilter:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: 999px;\n}\n\n.sefee3c_updatableHeading {\n margin: 2px 0 0;\n font-size: 16px;\n font-weight: 600;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-primary);\n}\n\n.sff4c1e_catalogStatsRow {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 6px 0 0;\n}\n\n.s927a53_catalogStats {\n margin: 0;\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* Sits beside the 12px stats caption, so it is quieter than `.s4a2332_actionButton`:\n * this is an affordance, not a call to action — the catalog reloads on its\n * own at mount and the shelf is already usable without it. Named\n * `catalogRefresh*` (not `reload*`) so it cannot collide with the §4 page\n * reload offer below, which reloads the whole tab rather than refetching\n * the catalog data. */\n.s68cab8_catalogRefreshButton {\n padding: 1px 9px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: transparent;\n color: var(--dsw-alias-label-secondary);\n font-size: 11px;\n line-height: 17px;\n cursor: pointer;\n}\n\n.s68cab8_catalogRefreshButton:hover:not(:disabled) {\n border-color: color-mix(in srgb, var(--dsw-alias-brand-primary) 45%, var(--dsw-alias-border-l2));\n color: var(--dsw-alias-brand-primary);\n}\n\n.s68cab8_catalogRefreshButton:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.s68cab8_catalogRefreshButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s6dceed_catalogRefreshFailed {\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s5748b0_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.sb2eae9_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 .sb2eae9_cards { animation: none; }\n}\n\n.sa1c2f7_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.s203f20_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.s795be6_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sa1c2f7_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 .sa1c2f7_card { transition: none; }\n .sa1c2f7_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.sb00bbe_cardSpine {\n position: absolute;\n top: 0;\n bottom: 0;\n left: 0;\n width: 3px;\n background: var(--category-hue, #8B8E96);\n}\n\n/* The hue table, keyed by `data-category` and read by BOTH surfaces that\n * speak in category colour: a card's spine and badge, and the category bar's\n * own tab. One table, so a tab can never disagree with the cards it filters to\n * — which is the whole point of colouring the tab. `.s85e01e_categoryButton` sets the\n * brand fallback for `All`/`Installed`; these attribute selectors are one\n * specificity step above it and win wherever the attribute is present. */\n.sa1c2f7_card[data-category='tool'], .s85e01e_categoryButton[data-category='tool'] { --category-hue: #4C8DFF; }\n.sa1c2f7_card[data-category='provider'], .s85e01e_categoryButton[data-category='provider'] { --category-hue: #A78BFA; }\n.sa1c2f7_card[data-category='ui'], .s85e01e_categoryButton[data-category='ui'] { --category-hue: #2DD4BF; }\n.sa1c2f7_card[data-category='workflow'], .s85e01e_categoryButton[data-category='workflow'] { --category-hue: #F59E0B; }\n.sa1c2f7_card[data-category='integration'], .s85e01e_categoryButton[data-category='integration'] { --category-hue: #34D399; }\n.sa1c2f7_card[data-category='theme'], .s85e01e_categoryButton[data-category='theme'] { --category-hue: #F472B6; }\n.sa1c2f7_card[data-category='other'], .s85e01e_categoryButton[data-category='other'] { --category-hue: #8B8E96; }\n\n/* The category sits in the badge row as a quiet chip in the category's own\n * hue — the border carries it where the light theme's white layers cannot\n * (the same lesson as the check button). */\n.see02fd_categoryBadge {\n padding: 1px 7px;\n border: 1px solid color-mix(in srgb, var(--category-hue, #8B8E96) 45%, transparent);\n border-radius: 999px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--category-hue, #8B8E96);\n white-space: nowrap;\n}\n\n.sd53924_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.sd53924_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.sc2b129_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.scd0548_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.s4f6385_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.s4f6385_cardActions > .sc1afc6_gate,\n.s4f6385_cardActions > .s65b9eb_installPanel,\n.s4f6385_cardActions > .s4fbfe0_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.s4b6469_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.s5a7549_sourceBadge {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s23bf0e_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.s23bf0e_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.s23bf0e_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.s23bf0e_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.sadc37f_incompatibleBadge {\n flex-shrink: 0;\n padding: 3px 9px;\n border-radius: 999px;\n font-size: 12px;\n white-space: nowrap;\n color: var(--dsw-alias-state-error-primary);\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 14%, transparent);\n border: 1px solid color-mix(in srgb, var(--dsw-alias-state-error-primary) 40%, transparent);\n}\n\n.s6ba7cc_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s80dda2_chevron, .sde820b_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.sde820b_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .s80dda2_chevron, .sde820b_chevronOpen { transition: none; }\n}\n\n.sa4ad4d_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.s7a8821_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.seab456_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.s5ab512_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.seca366_summaryZhExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n/* `white-space: pre-line` so the newline in the copy is the copy's own: the\n dictionary decides where the line breaks, and a translator changes it\n without touching the component. Collapses runs of spaces like `normal`, so\n only the explicit \\n survives. */\n.sa6eb0b_incompatibleDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n white-space: pre-line;\n color: var(--dsw-alias-state-error-primary);\n}\n\n/* The way out of dsh's own refusal: the lead-in, then the exemption command\n * on its own line. */\n.s92e71f_remedy {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.sb320b4_remedyLead {\n margin: 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-primary);\n}\n\n/* The exemption command, bound for a terminal unchanged: one click selects\n * all of it, and it wraps anywhere rather than widening the card. A border,\n * not a fill, marks the box: the bg layers are one white in the light theme. */\n.s2c3062_allowCommand {\n display: block;\n padding: 6px 8px;\n border-radius: 6px;\n background: var(--dsw-alias-bg-base);\n border: 1px solid var(--dsw-alias-border-l1);\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px;\n line-height: 1.5;\n white-space: pre-wrap;\n word-break: break-all;\n user-select: all;\n color: var(--dsw-alias-label-primary);\n}\n\n/* A refused install on a surface that does not state its blockers — the\n * outdated row — writes them out on their own full-width line under the\n * disabled button, the command that clears dsh's refusal included. */\n.s5fe511_refusal {\n display: flex;\n flex-direction: column;\n gap: 6px;\n flex-basis: 100%;\n}\n\n.scaa43b_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s121391_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s57f233_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s57f233_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.sbcd943_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.s59866d_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.s4662cb_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.s4662cb_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.s4662cb_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.s4662cb_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.s4662cb_detailRow a:hover {\n text-decoration: underline;\n}\n\n.s4662cb_detailRow a:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* What the entry IS rather than what you can do with it — how big it is and\n * who published it — at the right edge of the action row. `margin-left: auto`\n * is what pushes it there, and it lives on this wrapper rather than on either\n * label so the group stays flush right when only one of the two has a value —\n * a packument that named no account has no author, and an entry the registry\n * could not measure honestly has no size. Rarer than it was (every live entry\n * now carries a size, github included), but the wrapper is not what makes it\n * rare: without one, an EMPTY meta group still costs 8px, because\n * `.s4f6385_cardActions` is a flex row with `gap: 8px` and a zero-width item is still\n * an item. The row is a wrapping flex, so an active install flow (which claims\n * the full width) drops the whole group to the next line rather than squeezing\n * it. */\n.s6e38b0_cardMeta {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n margin-left: auto;\n min-width: 0;\n}\n\n/* The on-disk size, left of the author. Mono and tabular so the digits of one\n * figure line up with the digits of another WHEREVER the two land: `.s6e38b0_cardMeta`\n * is flush right and the author sits outside the size, so a size's x position\n * moves with the author's width and is different again on the many entries\n * that carry no author at all. There is no column to align to, which is why\n * the reason given here is per-figure legibility rather than the column the\n * first version of this comment claimed. Secondary colour, because it\n * qualifies the entry rather than announcing it.\n *\n * `font-variant-numeric` is safe HERE and would not be on a pill: this element\n * has no state rules, so nothing it does can reflow a wrapping row. */\n.s51bec8_size {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-variant-numeric: tabular-nums;\n font-size: 12px;\n letter-spacing: 0.02em;\n white-space: nowrap;\n color: var(--dsw-alias-label-secondary);\n}\n\n/* The publishing account, at the outer end of `.s6e38b0_cardMeta` on the action row —\n * NOT beside the npm link in the expanded detail, which is where it used to\n * live and what this comment described until the accurate half was deleted\n * with the `margin-left: auto` that moved to `.s6e38b0_cardMeta`. There is no middot:\n * `.s6e38b0_cardMeta`'s 8px gap separates it from the size.\n *\n * Secondary label colour carries the distinction — never a background fill,\n * which is invisible in the light theme where every bg-layer token is the\n * same white. */\n.s7503fc_author {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.sf73a51_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.s65b9eb_installPanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 2px;\n}\n\n.sd07a79_installButton, .s1e8df4_confirmButton, .saa2e75_cancelButton {\n align-self: flex-start;\n padding: 5px 14px;\n border-radius: 6px;\n font-size: 13px;\n cursor: pointer;\n}\n\n.sd07a79_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.sd07a79_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.s1e8df4_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.s1e8df4_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.saa2e75_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.saa2e75_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.sd07a79_installButton:focus-visible, .s1e8df4_confirmButton:focus-visible, .saa2e75_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.s849adb_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.s4fbfe0_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.sd93778_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.sd93778_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.sd93778_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.s3d7804_restartSelfButton {\n /* The version row's third state. Geometry matches .s511e9b_checkUpdateButton and\n * .s394a1f_updateSelfButton exactly — the row is one evolving control and a\n * different pill would read as a different kind of thing. It takes the\n * emphasised fill rather than the neutral one because at that moment the\n * restart is the only thing left to do, the same reason Update has it. */\n padding: 2px 10px;\n border: 1px solid var(--dsw-alias-brand-primary);\n border-radius: 999px;\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 12%, transparent);\n color: var(--dsw-alias-brand-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n}\n\n.s3d7804_restartSelfButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The two activation offers share one recipe: a border in an alias token,\n * never a `bg-layer` fill alone — all four bg-layer tokens collapse to the\n * same white under `body` in the light theme, and a fill-only button would\n * vanish there. Grouped rather than copied, because a copy is a rule that\n * can be corrected on one button and left wrong on the other, silently. */\n.s66e9a9_restartButton,\n.s4ca1f6_reloadButton {\n align-self: flex-start;\n padding: 5px 14px;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 6px;\n background: var(--dsw-alias-bg-layer-2);\n color: var(--dsw-alias-label-primary);\n font-size: 13px;\n cursor: pointer;\n}\n\n.s66e9a9_restartButton:hover,\n.s4ca1f6_reloadButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s66e9a9_restartButton:focus-visible,\n.s4ca1f6_reloadButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n/* The §4 reload offer: a notice plus its button, one item of\n * `.s4fbfe0_installedActions` the way the restart gate is — a column of its own\n * rather than inline with the row. */\n.s42eadb_reloadPanel {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 6px;\n}\n\n\n.sc1afc6_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.sbbe486_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s119e47_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.sadd68a_gateWarning {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n white-space: pre-line;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.s7ac0e2_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.scee95e_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s7bfdc8_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.s4bc9ba_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s8692d7_notice {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.s537eee_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.scdb145_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.sdaa6f0_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.s1c1027_rejectedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n/* The Updatable section. The class keeps the Host's field name; only the\n heading the reader sees was renamed. */\n.sc8d6dc_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.se27588_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s46601e_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.s54c287_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.s54c287_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.sa02632_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 .scd0548_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.s59c2df_outdatedActions {\n display: flex;\n align-items: center;\n /* The reload offer (§4) mounts inside the switch beside the Update button\n * and is a two-line block, not a control: without wrapping, a row reading\n * [switch][notice + Reload][Update] overflows the settings dialog. */\n flex-wrap: wrap;\n gap: 10px;\n}\n\n/* The shop's one switch. It is worn by two controls that mean different\n * things — the enable/disable toggle on an installed card, and the shelf's\n * incompatible filter — so the geometry lives here once and each caller states\n * only its hue.\n *\n * `--switch-hue` is that knob, and it is read from the ANCESTOR rather than\n * declared per state: `.sab8b1c_incompatibleFilter` sets it once and it reaches the\n * track and the label together, which is what keeps a red switch from sitting\n * under a neutral word. A declaration on `.sc4c0fe_switch` itself would shadow the\n * inherited value (an element's own rule beats what it inherits), so the\n * default rides in the `var()` fallback and is resolved once into\n * `--track-hue` instead of being spelled out at each use.\n *\n * `box-sizing` and `padding` are stated because the two callers are different\n * elements — a `<button>`, which the UA gives padding, and a `<span>`, which\n * it does not. Without them the same class renders two sizes, and the knob\n * sits 2px above centre in the taller one. */\n.sc4c0fe_switch {\n --track-hue: var(--switch-hue, var(--dsw-alias-state-success-primary));\n box-sizing: border-box;\n display: inline-block;\n position: relative;\n width: 32px;\n height: 18px;\n padding: 0;\n border: 1px solid var(--dsw-alias-border-l2);\n border-radius: 999px;\n background: var(--dsw-alias-bg-layer-2);\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease;\n}\n\n/* The knob's POSITION is the state, which is the reason this control needs no\n * `forced-colors` fallback the way the category pills do: a system palette\n * replaces every colour below and moves nothing. */\n.s383bcc_switchOn {\n border-color: var(--track-hue);\n background: color-mix(in srgb, var(--track-hue) 55%, transparent);\n}\n\n.s43a686_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.s383bcc_switchOn .s43a686_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.sc4c0fe_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 .sc4c0fe_switch, .s43a686_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.sd9a4e2_switchWrap {\n display: inline-flex;\n align-items: center;\n /* Same reason as `.s59c2df_outdatedActions`: `.s42eadb_reloadPanel`'s own comment calls\n * itself \"a column of its own rather than inline with the row\", which held\n * for `.s4fbfe0_installedActions` and not here until this line. */\n flex-wrap: wrap;\n gap: 8px;\n}\n";
|
|
7789
|
+
if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"201c343a\"]")) {
|
|
7183
7790
|
const tag = document.createElement("style");
|
|
7184
|
-
tag.dataset.pluginCss = "
|
|
7791
|
+
tag.dataset.pluginCss = "201c343a";
|
|
7185
7792
|
tag.textContent = css;
|
|
7186
7793
|
document.head.appendChild(tag);
|
|
7187
7794
|
}
|
|
7188
|
-
var
|
|
7189
|
-
"client": "
|
|
7190
|
-
"config": "
|
|
7191
|
-
"ts": "
|
|
7192
|
-
"spec": "
|
|
7193
|
-
"panel": "
|
|
7194
|
-
"searchInput": "
|
|
7195
|
-
"toolbar": "
|
|
7196
|
-
"stateLine": "
|
|
7197
|
-
"srOnly": "
|
|
7198
|
-
"skeletonGrid": "
|
|
7199
|
-
"skeletonCard": "
|
|
7200
|
-
"skeletonName": "
|
|
7201
|
-
"skeletonSummary": "
|
|
7202
|
-
"skeletonSummaryShort": "
|
|
7203
|
-
"skeletonActions": "
|
|
7204
|
-
"actionButton": "
|
|
7205
|
-
"toolbarRight": "
|
|
7206
|
-
"versionBlock": "
|
|
7207
|
-
"versionText": "
|
|
7208
|
-
"updateSelfButton": "
|
|
7209
|
-
"checkUpdateButton": "
|
|
7210
|
-
"githubLink": "
|
|
7211
|
-
"selfUpdatePanel": "
|
|
7212
|
-
"staleBadge": "
|
|
7213
|
-
"categoryBar": "
|
|
7214
|
-
"categoryButton": "
|
|
7215
|
-
"categoryButtonOn": "
|
|
7216
|
-
"incompatibleFilter": "
|
|
7217
|
-
"switch": "
|
|
7218
|
-
"switchOn": "
|
|
7219
|
-
"incompatibleFilterOn": "
|
|
7220
|
-
"switchKnob": "
|
|
7221
|
-
"updatableHeading": "
|
|
7222
|
-
"catalogStatsRow": "
|
|
7223
|
-
"catalogStats": "
|
|
7224
|
-
"catalogRefreshButton": "
|
|
7225
|
-
"catalogRefreshFailed": "
|
|
7226
|
-
"emptyLine": "
|
|
7227
|
-
"cards": "
|
|
7228
|
-
"card": "
|
|
7229
|
-
"cardsSentry": "
|
|
7230
|
-
"showingLine": "
|
|
7231
|
-
"cardSpine": "
|
|
7232
|
-
"categoryBadge": "
|
|
7233
|
-
"entryHeader": "
|
|
7234
|
-
"name": "
|
|
7235
|
-
"badges": "
|
|
7236
|
-
"cardActions": "
|
|
7237
|
-
"gate": "
|
|
7238
|
-
"installPanel": "
|
|
7239
|
-
"installedActions": "
|
|
7240
|
-
"cardVersion": "
|
|
7241
|
-
"sourceBadge": "
|
|
7242
|
-
"tierBadge": "
|
|
7243
|
-
"incompatibleBadge": "
|
|
7244
|
-
"starsBadge": "
|
|
7245
|
-
"chevron": "
|
|
7246
|
-
"chevronOpen": "
|
|
7247
|
-
"body": "
|
|
7248
|
-
"summary": "
|
|
7249
|
-
"summaryZh": "
|
|
7250
|
-
"summaryExpanded": "
|
|
7251
|
-
"summaryZhExpanded": "
|
|
7252
|
-
"incompatibleDetail": "
|
|
7253
|
-
"
|
|
7254
|
-
"
|
|
7255
|
-
"
|
|
7256
|
-
"
|
|
7257
|
-
"
|
|
7258
|
-
"
|
|
7259
|
-
"
|
|
7260
|
-
"
|
|
7261
|
-
"
|
|
7262
|
-
"
|
|
7263
|
-
"
|
|
7264
|
-
"
|
|
7265
|
-
"
|
|
7266
|
-
"
|
|
7267
|
-
"
|
|
7268
|
-
"
|
|
7269
|
-
"
|
|
7270
|
-
"
|
|
7271
|
-
"
|
|
7272
|
-
"
|
|
7273
|
-
"
|
|
7274
|
-
"
|
|
7275
|
-
"
|
|
7276
|
-
"
|
|
7277
|
-
"
|
|
7278
|
-
"
|
|
7279
|
-
"
|
|
7280
|
-
"
|
|
7281
|
-
"
|
|
7282
|
-
"
|
|
7283
|
-
"
|
|
7284
|
-
"
|
|
7285
|
-
"
|
|
7286
|
-
"
|
|
7287
|
-
"
|
|
7288
|
-
"
|
|
7289
|
-
"
|
|
7290
|
-
"
|
|
7795
|
+
var _dsh_plugin_shop_css_201c343a_default = {
|
|
7796
|
+
"client": "se24648_client",
|
|
7797
|
+
"config": "s4c8568_config",
|
|
7798
|
+
"ts": "s64bf36_ts",
|
|
7799
|
+
"spec": "scd01a1_spec",
|
|
7800
|
+
"panel": "sa18b47_panel",
|
|
7801
|
+
"searchInput": "seba6a3_searchInput",
|
|
7802
|
+
"toolbar": "s7df13e_toolbar",
|
|
7803
|
+
"stateLine": "sd85001_stateLine",
|
|
7804
|
+
"srOnly": "se60107_srOnly",
|
|
7805
|
+
"skeletonGrid": "sba4a38_skeletonGrid",
|
|
7806
|
+
"skeletonCard": "s5aa954_skeletonCard",
|
|
7807
|
+
"skeletonName": "s7a0318_skeletonName",
|
|
7808
|
+
"skeletonSummary": "s7a5df4_skeletonSummary",
|
|
7809
|
+
"skeletonSummaryShort": "s8d4020_skeletonSummaryShort",
|
|
7810
|
+
"skeletonActions": "sbc0e1a_skeletonActions",
|
|
7811
|
+
"actionButton": "s4a2332_actionButton",
|
|
7812
|
+
"toolbarRight": "s20709b_toolbarRight",
|
|
7813
|
+
"versionBlock": "s98c547_versionBlock",
|
|
7814
|
+
"versionText": "s2b9f1d_versionText",
|
|
7815
|
+
"updateSelfButton": "s394a1f_updateSelfButton",
|
|
7816
|
+
"checkUpdateButton": "s511e9b_checkUpdateButton",
|
|
7817
|
+
"githubLink": "se3bcc2_githubLink",
|
|
7818
|
+
"selfUpdatePanel": "s8c6b17_selfUpdatePanel",
|
|
7819
|
+
"staleBadge": "sc9d2d3_staleBadge",
|
|
7820
|
+
"categoryBar": "sb4e3b8_categoryBar",
|
|
7821
|
+
"categoryButton": "s85e01e_categoryButton",
|
|
7822
|
+
"categoryButtonOn": "s833fec_categoryButtonOn",
|
|
7823
|
+
"incompatibleFilter": "sab8b1c_incompatibleFilter",
|
|
7824
|
+
"switch": "sc4c0fe_switch",
|
|
7825
|
+
"switchOn": "s383bcc_switchOn",
|
|
7826
|
+
"incompatibleFilterOn": "s6dac80_incompatibleFilterOn",
|
|
7827
|
+
"switchKnob": "s43a686_switchKnob",
|
|
7828
|
+
"updatableHeading": "sefee3c_updatableHeading",
|
|
7829
|
+
"catalogStatsRow": "sff4c1e_catalogStatsRow",
|
|
7830
|
+
"catalogStats": "s927a53_catalogStats",
|
|
7831
|
+
"catalogRefreshButton": "s68cab8_catalogRefreshButton",
|
|
7832
|
+
"catalogRefreshFailed": "s6dceed_catalogRefreshFailed",
|
|
7833
|
+
"emptyLine": "s5748b0_emptyLine",
|
|
7834
|
+
"cards": "sb2eae9_cards",
|
|
7835
|
+
"card": "sa1c2f7_card",
|
|
7836
|
+
"cardsSentry": "s203f20_cardsSentry",
|
|
7837
|
+
"showingLine": "s795be6_showingLine",
|
|
7838
|
+
"cardSpine": "sb00bbe_cardSpine",
|
|
7839
|
+
"categoryBadge": "see02fd_categoryBadge",
|
|
7840
|
+
"entryHeader": "sd53924_entryHeader",
|
|
7841
|
+
"name": "sc2b129_name",
|
|
7842
|
+
"badges": "scd0548_badges",
|
|
7843
|
+
"cardActions": "s4f6385_cardActions",
|
|
7844
|
+
"gate": "sc1afc6_gate",
|
|
7845
|
+
"installPanel": "s65b9eb_installPanel",
|
|
7846
|
+
"installedActions": "s4fbfe0_installedActions",
|
|
7847
|
+
"cardVersion": "s4b6469_cardVersion",
|
|
7848
|
+
"sourceBadge": "s5a7549_sourceBadge",
|
|
7849
|
+
"tierBadge": "s23bf0e_tierBadge",
|
|
7850
|
+
"incompatibleBadge": "sadc37f_incompatibleBadge",
|
|
7851
|
+
"starsBadge": "s6ba7cc_starsBadge",
|
|
7852
|
+
"chevron": "s80dda2_chevron",
|
|
7853
|
+
"chevronOpen": "sde820b_chevronOpen",
|
|
7854
|
+
"body": "sa4ad4d_body",
|
|
7855
|
+
"summary": "s7a8821_summary",
|
|
7856
|
+
"summaryZh": "seab456_summaryZh",
|
|
7857
|
+
"summaryExpanded": "s5ab512_summaryExpanded",
|
|
7858
|
+
"summaryZhExpanded": "seca366_summaryZhExpanded",
|
|
7859
|
+
"incompatibleDetail": "sa6eb0b_incompatibleDetail",
|
|
7860
|
+
"remedy": "s92e71f_remedy",
|
|
7861
|
+
"remedyLead": "sb320b4_remedyLead",
|
|
7862
|
+
"allowCommand": "s2c3062_allowCommand",
|
|
7863
|
+
"refusal": "s5fe511_refusal",
|
|
7864
|
+
"capabilitiesBlock": "scaa43b_capabilitiesBlock",
|
|
7865
|
+
"capabilitiesNote": "s121391_capabilitiesNote",
|
|
7866
|
+
"capabilities": "s57f233_capabilities",
|
|
7867
|
+
"detail": "sbcd943_detail",
|
|
7868
|
+
"detailRows": "s59866d_detailRows",
|
|
7869
|
+
"detailRow": "s4662cb_detailRow",
|
|
7870
|
+
"cardMeta": "s6e38b0_cardMeta",
|
|
7871
|
+
"size": "s51bec8_size",
|
|
7872
|
+
"author": "s7503fc_author",
|
|
7873
|
+
"reviewedLine": "sf73a51_reviewedLine",
|
|
7874
|
+
"installButton": "sd07a79_installButton",
|
|
7875
|
+
"confirmButton": "s1e8df4_confirmButton",
|
|
7876
|
+
"cancelButton": "saa2e75_cancelButton",
|
|
7877
|
+
"installedLabel": "s849adb_installedLabel",
|
|
7878
|
+
"uninstallButton": "sd93778_uninstallButton",
|
|
7879
|
+
"restartSelfButton": "s3d7804_restartSelfButton",
|
|
7880
|
+
"restartButton": "s66e9a9_restartButton",
|
|
7881
|
+
"reloadButton": "s4ca1f6_reloadButton",
|
|
7882
|
+
"reloadPanel": "s42eadb_reloadPanel",
|
|
7883
|
+
"gateTitle": "sbbe486_gateTitle",
|
|
7884
|
+
"gateBody": "s119e47_gateBody",
|
|
7885
|
+
"gateWarning": "sadd68a_gateWarning",
|
|
7886
|
+
"gateActions": "s7ac0e2_gateActions",
|
|
7887
|
+
"installing": "scee95e_installing",
|
|
7888
|
+
"log": "s7bfdc8_log",
|
|
7889
|
+
"logLine": "s4bc9ba_logLine",
|
|
7890
|
+
"notice": "s8692d7_notice",
|
|
7891
|
+
"failedHeading": "s537eee_failedHeading",
|
|
7892
|
+
"failedDetail": "scdb145_failedDetail",
|
|
7893
|
+
"rejectedCode": "sdaa6f0_rejectedCode",
|
|
7894
|
+
"rejectedDetail": "s1c1027_rejectedDetail",
|
|
7895
|
+
"outdatedSection": "sc8d6dc_outdatedSection",
|
|
7896
|
+
"outdatedList": "se27588_outdatedList",
|
|
7897
|
+
"outdatedRow": "s46601e_outdatedRow",
|
|
7898
|
+
"outdatedInfo": "s54c287_outdatedInfo",
|
|
7899
|
+
"outdatedVersions": "sa02632_outdatedVersions",
|
|
7900
|
+
"outdatedActions": "s59c2df_outdatedActions",
|
|
7901
|
+
"switchWrap": "sd9a4e2_switchWrap"
|
|
7291
7902
|
};
|
|
7292
7903
|
//#endregion
|
|
7293
7904
|
//#region src/client/ShopTab.tsx
|
|
@@ -7298,13 +7909,20 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7298
7909
|
* §11.3.4): no render path here may ever use dangerouslySetInnerHTML. */
|
|
7299
7910
|
/** The project's home on GitHub, linked from the toolbar. */
|
|
7300
7911
|
const SHOP_REPO_URL = "https://github.com/LivXue/dsh-plugin-shop";
|
|
7912
|
+
/** The class of every root the tab renders, the crash fallback's included:
|
|
7913
|
+
* the panel, plus `notranslate`, which with `translate="no"` keeps a browser's
|
|
7914
|
+
* page translation out of the text nodes React owns. Translation rewrites
|
|
7915
|
+
* those nodes, and React's next commit throws on the ones it no longer finds
|
|
7916
|
+
* (design 2026-09-26-market-borrowings §4). The tab renders no portals, so its
|
|
7917
|
+
* root is its whole surface. */
|
|
7918
|
+
const PANEL_ROOT = `${_dsh_plugin_shop_css_201c343a_default.panel} notranslate`;
|
|
7301
7919
|
function ChevronIcon({ open }) {
|
|
7302
7920
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
|
|
7303
7921
|
viewBox: "0 0 16 16",
|
|
7304
7922
|
width: "14",
|
|
7305
7923
|
height: "14",
|
|
7306
7924
|
"aria-hidden": "true",
|
|
7307
|
-
className: open ?
|
|
7925
|
+
className: open ? _dsh_plugin_shop_css_201c343a_default.chevronOpen : _dsh_plugin_shop_css_201c343a_default.chevron,
|
|
7308
7926
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
|
|
7309
7927
|
fill: "currentColor",
|
|
7310
7928
|
d: "M3.5 6 8 10.5 12.5 6l1 1L8 12.5 2.5 7l1-1Z"
|
|
@@ -7317,9 +7935,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7317
7935
|
* install controls. An installed plugin's card carries its installed row:
|
|
7318
7936
|
* current → the non-interactive installed label, behind → the update button;
|
|
7319
7937
|
* uninstalled → the install button. */
|
|
7320
|
-
const EntryCard = (0, react.memo)(function EntryCard({ entry, stars, installed, missing, nameTakenBy, t, flow, uninstallFlow, restart, restartBlocked, reload, setEnabled, inInstalledView }) {
|
|
7938
|
+
const EntryCard = (0, react.memo)(function EntryCard({ entry, stars, installed, missing, harness, nameTakenBy, t, flow, uninstallFlow, restart, restartBlocked, reload, setEnabled, inInstalledView }) {
|
|
7321
7939
|
const [open, setOpen] = (0, react.useState)(false);
|
|
7322
|
-
const blockers = blockersOf(missing, nameTakenBy, t);
|
|
7940
|
+
const blockers = stateBlockers(blockersOf(missing, harness, nameTakenBy), t);
|
|
7323
7941
|
const detailId = (0, react.useId)();
|
|
7324
7942
|
const summary = entry.catalog?.summary;
|
|
7325
7943
|
const npmUrl = npmPageUrl(entry);
|
|
@@ -7334,32 +7952,32 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7334
7952
|
subdir: entry.subdir
|
|
7335
7953
|
};
|
|
7336
7954
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7337
|
-
className:
|
|
7955
|
+
className: _dsh_plugin_shop_css_201c343a_default.card,
|
|
7338
7956
|
"data-shop-entry": entry.name,
|
|
7339
7957
|
"data-category": category,
|
|
7340
7958
|
children: [
|
|
7341
7959
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7342
|
-
className:
|
|
7960
|
+
className: _dsh_plugin_shop_css_201c343a_default.cardSpine,
|
|
7343
7961
|
"aria-hidden": "true"
|
|
7344
7962
|
}),
|
|
7345
7963
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
7346
7964
|
type: "button",
|
|
7347
|
-
className:
|
|
7965
|
+
className: _dsh_plugin_shop_css_201c343a_default.entryHeader,
|
|
7348
7966
|
"aria-expanded": open,
|
|
7349
7967
|
"aria-controls": detailId,
|
|
7350
7968
|
onClick: () => setOpen((current) => !current),
|
|
7351
7969
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7352
|
-
className:
|
|
7970
|
+
className: _dsh_plugin_shop_css_201c343a_default.name,
|
|
7353
7971
|
children: entry.name
|
|
7354
7972
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7355
|
-
className:
|
|
7973
|
+
className: _dsh_plugin_shop_css_201c343a_default.badges,
|
|
7356
7974
|
children: [
|
|
7357
7975
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7358
|
-
className:
|
|
7976
|
+
className: _dsh_plugin_shop_css_201c343a_default.categoryBadge,
|
|
7359
7977
|
children: t(categoryKey(entry))
|
|
7360
7978
|
}),
|
|
7361
7979
|
entry.source === "npm" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7362
|
-
className:
|
|
7980
|
+
className: _dsh_plugin_shop_css_201c343a_default.sourceBadge,
|
|
7363
7981
|
"data-shop-source-npm": true,
|
|
7364
7982
|
role: "img",
|
|
7365
7983
|
"aria-label": t("npmSource"),
|
|
@@ -7376,7 +7994,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7376
7994
|
})
|
|
7377
7995
|
}),
|
|
7378
7996
|
hasGithubHome(entry) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7379
|
-
className:
|
|
7997
|
+
className: _dsh_plugin_shop_css_201c343a_default.sourceBadge,
|
|
7380
7998
|
"data-shop-source-github": true,
|
|
7381
7999
|
role: "img",
|
|
7382
8000
|
"aria-label": t("githubSource"),
|
|
@@ -7390,17 +8008,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7390
8008
|
})
|
|
7391
8009
|
}),
|
|
7392
8010
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7393
|
-
className:
|
|
8011
|
+
className: _dsh_plugin_shop_css_201c343a_default.cardVersion,
|
|
7394
8012
|
"data-card-version": true,
|
|
7395
8013
|
children: ["v", displayVersion(entry)]
|
|
7396
8014
|
}),
|
|
7397
8015
|
entry.tier !== "community" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7398
|
-
className:
|
|
8016
|
+
className: _dsh_plugin_shop_css_201c343a_default.tierBadge,
|
|
7399
8017
|
"data-tier": entry.tier,
|
|
7400
8018
|
children: t(tierKey(entry.tier))
|
|
7401
8019
|
}),
|
|
7402
8020
|
stars !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7403
|
-
className:
|
|
8021
|
+
className: _dsh_plugin_shop_css_201c343a_default.starsBadge,
|
|
7404
8022
|
role: "img",
|
|
7405
8023
|
"aria-label": t("stars", { count: stars }),
|
|
7406
8024
|
children: ["★ ", formatStars(stars)]
|
|
@@ -7410,39 +8028,39 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7410
8028
|
})]
|
|
7411
8029
|
}),
|
|
7412
8030
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7413
|
-
className:
|
|
8031
|
+
className: _dsh_plugin_shop_css_201c343a_default.body,
|
|
7414
8032
|
children: [
|
|
7415
8033
|
summary !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7416
|
-
className: open ? `${
|
|
8034
|
+
className: open ? `${_dsh_plugin_shop_css_201c343a_default.summary} ${_dsh_plugin_shop_css_201c343a_default.summaryExpanded}` : _dsh_plugin_shop_css_201c343a_default.summary,
|
|
7417
8035
|
children: summary.en
|
|
7418
8036
|
}),
|
|
7419
8037
|
summary?.zh !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7420
|
-
className: open ? `${
|
|
8038
|
+
className: open ? `${_dsh_plugin_shop_css_201c343a_default.summaryZh} ${_dsh_plugin_shop_css_201c343a_default.summaryZhExpanded}` : _dsh_plugin_shop_css_201c343a_default.summaryZh,
|
|
7421
8039
|
children: summary.zh
|
|
7422
8040
|
}),
|
|
7423
|
-
blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
7424
|
-
|
|
7425
|
-
|
|
7426
|
-
|
|
8041
|
+
blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
|
|
8042
|
+
blocker,
|
|
8043
|
+
className: _dsh_plugin_shop_css_201c343a_default.incompatibleDetail,
|
|
8044
|
+
surface: "detail"
|
|
7427
8045
|
}, blocker.kind)),
|
|
7428
8046
|
open && entry.catalog !== void 0 && entry.catalog.capabilities.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7429
|
-
className:
|
|
8047
|
+
className: _dsh_plugin_shop_css_201c343a_default.capabilitiesBlock,
|
|
7430
8048
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7431
|
-
className:
|
|
8049
|
+
className: _dsh_plugin_shop_css_201c343a_default.capabilitiesNote,
|
|
7432
8050
|
children: t("capabilitiesNote")
|
|
7433
8051
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
7434
|
-
className:
|
|
8052
|
+
className: _dsh_plugin_shop_css_201c343a_default.capabilities,
|
|
7435
8053
|
children: entry.catalog.capabilities.map((capability) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: capability }, capability))
|
|
7436
8054
|
})]
|
|
7437
8055
|
}),
|
|
7438
8056
|
open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
7439
8057
|
id: detailId,
|
|
7440
|
-
className:
|
|
8058
|
+
className: _dsh_plugin_shop_css_201c343a_default.detail,
|
|
7441
8059
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
|
|
7442
|
-
className:
|
|
8060
|
+
className: _dsh_plugin_shop_css_201c343a_default.detailRows,
|
|
7443
8061
|
children: [
|
|
7444
8062
|
npmUrl !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7445
|
-
className:
|
|
8063
|
+
className: _dsh_plugin_shop_css_201c343a_default.detailRow,
|
|
7446
8064
|
"data-shop-npm": true,
|
|
7447
8065
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("npmPage") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
7448
8066
|
href: npmUrl,
|
|
@@ -7452,7 +8070,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7452
8070
|
}) })]
|
|
7453
8071
|
}),
|
|
7454
8072
|
entry.repository !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7455
|
-
className:
|
|
8073
|
+
className: _dsh_plugin_shop_css_201c343a_default.detailRow,
|
|
7456
8074
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("repository") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: /^https?:\/\//.test(entry.repository) ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
7457
8075
|
href: entry.repository,
|
|
7458
8076
|
target: "_blank",
|
|
@@ -7461,12 +8079,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7461
8079
|
}) : entry.repository })]
|
|
7462
8080
|
}),
|
|
7463
8081
|
entry.license !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7464
|
-
className:
|
|
8082
|
+
className: _dsh_plugin_shop_css_201c343a_default.detailRow,
|
|
7465
8083
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("dt", { children: t("license") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("dd", { children: isCustomLicense(entry.license) ? t("customLicense") : entry.license })]
|
|
7466
8084
|
})
|
|
7467
8085
|
]
|
|
7468
8086
|
}), entry.tier === "verified-stale" && entry.review !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7469
|
-
className:
|
|
8087
|
+
className: _dsh_plugin_shop_css_201c343a_default.reviewedLine,
|
|
7470
8088
|
children: entry.review.reviewedVersion !== void 0 ? t("reviewedVersionLine", {
|
|
7471
8089
|
reviewed: entry.review.reviewedVersion,
|
|
7472
8090
|
current: entry.version
|
|
@@ -7479,14 +8097,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7479
8097
|
]
|
|
7480
8098
|
}),
|
|
7481
8099
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7482
|
-
className:
|
|
8100
|
+
className: _dsh_plugin_shop_css_201c343a_default.cardActions,
|
|
7483
8101
|
"data-shop-actions": true,
|
|
7484
8102
|
children: [installed === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [!inInstalledView && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
7485
8103
|
target: installTarget,
|
|
7486
8104
|
tier: entry.tier,
|
|
7487
|
-
missing,
|
|
7488
8105
|
blockers,
|
|
7489
|
-
|
|
8106
|
+
blockersStated: true,
|
|
7490
8107
|
flow,
|
|
7491
8108
|
t,
|
|
7492
8109
|
restart,
|
|
@@ -7504,16 +8121,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7504
8121
|
target: installTarget,
|
|
7505
8122
|
tier: entry.tier,
|
|
7506
8123
|
variant: installed.outdated ? "update" : "install",
|
|
7507
|
-
missing,
|
|
7508
8124
|
blockers,
|
|
7509
|
-
|
|
8125
|
+
blockersStated: true,
|
|
7510
8126
|
flow,
|
|
7511
8127
|
t,
|
|
7512
8128
|
restart,
|
|
7513
8129
|
restartBlocked,
|
|
7514
8130
|
reload
|
|
7515
8131
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7516
|
-
className:
|
|
8132
|
+
className: _dsh_plugin_shop_css_201c343a_default.installedLabel,
|
|
7517
8133
|
"data-shop-installed": true,
|
|
7518
8134
|
children: t("installed")
|
|
7519
8135
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerBadge, {
|
|
@@ -7524,6 +8140,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7524
8140
|
row: installed,
|
|
7525
8141
|
t,
|
|
7526
8142
|
setEnabled,
|
|
8143
|
+
restart,
|
|
8144
|
+
restartBlocked,
|
|
7527
8145
|
reload
|
|
7528
8146
|
}),
|
|
7529
8147
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
|
|
@@ -7535,16 +8153,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7535
8153
|
flow: uninstallFlow
|
|
7536
8154
|
})
|
|
7537
8155
|
] }), (size !== void 0 || author !== null) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
7538
|
-
className:
|
|
8156
|
+
className: _dsh_plugin_shop_css_201c343a_default.cardMeta,
|
|
7539
8157
|
children: [size !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7540
|
-
className:
|
|
8158
|
+
className: _dsh_plugin_shop_css_201c343a_default.size,
|
|
7541
8159
|
"data-shop-size": true,
|
|
7542
8160
|
role: "img",
|
|
7543
8161
|
"aria-label": t("sizeLabel", { size }),
|
|
7544
8162
|
title: t("sizeLabel", { size }),
|
|
7545
8163
|
children: size
|
|
7546
8164
|
}), author !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7547
|
-
className:
|
|
8165
|
+
className: _dsh_plugin_shop_css_201c343a_default.author,
|
|
7548
8166
|
"data-shop-author": true,
|
|
7549
8167
|
children: t("authorLine", { author })
|
|
7550
8168
|
})]
|
|
@@ -7554,41 +8172,95 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7554
8172
|
});
|
|
7555
8173
|
});
|
|
7556
8174
|
/**
|
|
7557
|
-
* Everything standing in this entry's way,
|
|
8175
|
+
* Everything standing in this entry's way, localized ONCE per surface: the
|
|
8176
|
+
* detail paragraphs and the badge summarizing them read this one list, so
|
|
8177
|
+
* they can never be built from different inputs.
|
|
7558
8178
|
*
|
|
7559
|
-
* The
|
|
7560
|
-
*
|
|
7561
|
-
*
|
|
8179
|
+
* The host sent facts — module names, the declared range and the running
|
|
8180
|
+
* version, the declared and the running profile — and the sentences are this
|
|
8181
|
+
* dictionary's, in the reader's own language (§4: no copy crosses the RPC).
|
|
7562
8182
|
*/
|
|
7563
|
-
function
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
kind
|
|
7567
|
-
|
|
7568
|
-
|
|
7569
|
-
|
|
7570
|
-
|
|
7571
|
-
|
|
8183
|
+
function stateBlockers(blockers, t) {
|
|
8184
|
+
return blockers.map((blocker) => {
|
|
8185
|
+
const text = blockerText(blocker, t);
|
|
8186
|
+
return blocker.kind === "harness-peers" && blocker.allowCommand !== null ? {
|
|
8187
|
+
kind: blocker.kind,
|
|
8188
|
+
text,
|
|
8189
|
+
remedy: {
|
|
8190
|
+
lead: t("harnessPeersRemedy"),
|
|
8191
|
+
command: blocker.allowCommand
|
|
8192
|
+
}
|
|
8193
|
+
} : {
|
|
8194
|
+
kind: blocker.kind,
|
|
8195
|
+
text
|
|
8196
|
+
};
|
|
7572
8197
|
});
|
|
7573
|
-
|
|
8198
|
+
}
|
|
8199
|
+
/**
|
|
8200
|
+
* One blocker as a surface prints it: its sentence in the surface's own
|
|
8201
|
+
* paragraph style, and after it, for a refusal the reader can clear, the
|
|
8202
|
+
* lead-in and the command on a line of its own — selectable as a whole, since
|
|
8203
|
+
* it goes to a terminal unchanged. `surface` names the attribute a locator
|
|
8204
|
+
* reads: the card's detail line, or the warning the gate and the outdated
|
|
8205
|
+
* row's refusal print.
|
|
8206
|
+
*/
|
|
8207
|
+
function BlockerLine({ blocker, className, surface }) {
|
|
8208
|
+
const attribute = surface === "detail" ? { "data-shop-incompatible-detail": blocker.kind } : { "data-shop-incompatible-warning": blocker.kind };
|
|
8209
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8210
|
+
className,
|
|
8211
|
+
...attribute,
|
|
8212
|
+
children: blocker.text
|
|
8213
|
+
}), blocker.remedy !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8214
|
+
className: _dsh_plugin_shop_css_201c343a_default.remedy,
|
|
8215
|
+
"data-shop-remedy": blocker.kind,
|
|
8216
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8217
|
+
className: _dsh_plugin_shop_css_201c343a_default.remedyLead,
|
|
8218
|
+
children: blocker.remedy.lead
|
|
8219
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
|
|
8220
|
+
className: _dsh_plugin_shop_css_201c343a_default.allowCommand,
|
|
8221
|
+
"data-shop-allow-command": true,
|
|
8222
|
+
children: blocker.remedy.command
|
|
8223
|
+
})]
|
|
8224
|
+
})] });
|
|
8225
|
+
}
|
|
8226
|
+
/** One blocker's sentence. Exhaustive with no default, so a kind added
|
|
8227
|
+
* to `Blocker` is a type error here rather than a line that renders nothing. */
|
|
8228
|
+
function blockerText(blocker, t) {
|
|
8229
|
+
switch (blocker.kind) {
|
|
8230
|
+
case "name-taken": return t("nameTakenDetail", { holder: blocker.holder });
|
|
8231
|
+
case "harness-peers": return t("harnessPeersDetail", {
|
|
8232
|
+
running: blocker.running,
|
|
8233
|
+
peers: refusedPeersText(blocker.refused)
|
|
8234
|
+
});
|
|
8235
|
+
case "missing-peers": return t("incompatibleDetail", { modules: blocker.modules.join(", ") });
|
|
8236
|
+
case "harness-range": return t("harnessRangeDetail", {
|
|
8237
|
+
range: blocker.range,
|
|
8238
|
+
running: blocker.running
|
|
8239
|
+
});
|
|
8240
|
+
case "harness-profile": return t("harnessProfileDetail", {
|
|
8241
|
+
declared: blocker.declared.join(", "),
|
|
8242
|
+
running: blocker.running
|
|
8243
|
+
});
|
|
8244
|
+
}
|
|
7574
8245
|
}
|
|
7575
8246
|
/** The harness-compatibility verdict, rendered beside the control it
|
|
7576
8247
|
* qualifies. It answers "what happens if I press this", not "what is this",
|
|
7577
8248
|
* so it belongs to the action row and not among the identity badges in the
|
|
7578
8249
|
* header — where it also sat inside a <button>, competing with that button's
|
|
7579
|
-
* own hit area for the tooltip. Renders nothing when
|
|
7580
|
-
*
|
|
8250
|
+
* own hit area for the tooltip. Renders nothing when nothing stands in the
|
|
8251
|
+
* way. */
|
|
7581
8252
|
function BlockerBadge({ blockers, t }) {
|
|
7582
|
-
|
|
8253
|
+
const lead = blockers[0];
|
|
8254
|
+
const word = blockerBadgeKey(blockers);
|
|
8255
|
+
if (lead === void 0 || word === null) return null;
|
|
7583
8256
|
const detail = blockers.map((blocker) => blocker.text).join("\n");
|
|
7584
|
-
const taken = blockers.some((blocker) => blocker.kind === "name-taken");
|
|
7585
8257
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
7586
|
-
className:
|
|
7587
|
-
"data-shop-blocker":
|
|
8258
|
+
className: _dsh_plugin_shop_css_201c343a_default.incompatibleBadge,
|
|
8259
|
+
"data-shop-blocker": lead.kind,
|
|
7588
8260
|
role: "img",
|
|
7589
8261
|
"aria-label": detail,
|
|
7590
8262
|
title: detail,
|
|
7591
|
-
children: t(
|
|
8263
|
+
children: t(word)
|
|
7592
8264
|
});
|
|
7593
8265
|
}
|
|
7594
8266
|
/** One entry's install flow: the button, the §9.3 acknowledgement gate for
|
|
@@ -7596,37 +8268,44 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7596
8268
|
* failure detail, rejection detail — driven by `useInstallFlows`. Shared by the
|
|
7597
8269
|
* catalog cards (`variant: 'install'`) and the outdated rows' update button
|
|
7598
8270
|
* (`variant: 'update'`, which drives the same install flow for `name@latest`). */
|
|
7599
|
-
function InstallPanel({ target, tier,
|
|
8271
|
+
function InstallPanel({ target, tier, blockers, blockersStated = false, variant = "install", flow, t, restart, restartBlocked, reload }) {
|
|
7600
8272
|
const [gateOpen, setGateOpen] = (0, react.useState)(false);
|
|
7601
8273
|
const { view, start } = flow;
|
|
7602
8274
|
if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7603
|
-
className:
|
|
8275
|
+
className: _dsh_plugin_shop_css_201c343a_default.installPanel,
|
|
7604
8276
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7605
|
-
className:
|
|
8277
|
+
className: _dsh_plugin_shop_css_201c343a_default.installing,
|
|
7606
8278
|
children: t(installPhaseKey(view.phase))
|
|
7607
8279
|
}), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7608
|
-
className:
|
|
8280
|
+
className: _dsh_plugin_shop_css_201c343a_default.log,
|
|
7609
8281
|
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7610
|
-
className:
|
|
8282
|
+
className: _dsh_plugin_shop_css_201c343a_default.logLine,
|
|
8283
|
+
"data-shop-log-line": true,
|
|
7611
8284
|
children: line
|
|
7612
8285
|
}, index))
|
|
7613
8286
|
})]
|
|
7614
8287
|
});
|
|
7615
8288
|
if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7616
|
-
className:
|
|
8289
|
+
className: _dsh_plugin_shop_css_201c343a_default.installedActions,
|
|
7617
8290
|
children: [
|
|
7618
8291
|
view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7619
|
-
className:
|
|
8292
|
+
className: _dsh_plugin_shop_css_201c343a_default.log,
|
|
7620
8293
|
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7621
|
-
className:
|
|
8294
|
+
className: _dsh_plugin_shop_css_201c343a_default.logLine,
|
|
8295
|
+
"data-shop-log-line": true,
|
|
7622
8296
|
children: line
|
|
7623
8297
|
}, index))
|
|
7624
8298
|
}),
|
|
7625
8299
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7626
|
-
className:
|
|
8300
|
+
className: _dsh_plugin_shop_css_201c343a_default.notice,
|
|
7627
8301
|
"data-shop-restart-notice": true,
|
|
7628
8302
|
children: t(activationNoticeKey(view.activation, view.restartReason))
|
|
7629
8303
|
}),
|
|
8304
|
+
view.detail !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8305
|
+
className: _dsh_plugin_shop_css_201c343a_default.notice,
|
|
8306
|
+
"data-shop-done-note": true,
|
|
8307
|
+
children: view.detail
|
|
8308
|
+
}),
|
|
7630
8309
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActivationOffer, {
|
|
7631
8310
|
activation: view.activation,
|
|
7632
8311
|
t,
|
|
@@ -7638,56 +8317,57 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7638
8317
|
]
|
|
7639
8318
|
});
|
|
7640
8319
|
if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7641
|
-
className:
|
|
8320
|
+
className: _dsh_plugin_shop_css_201c343a_default.installPanel,
|
|
7642
8321
|
children: [
|
|
7643
8322
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7644
|
-
className:
|
|
8323
|
+
className: _dsh_plugin_shop_css_201c343a_default.failedHeading,
|
|
7645
8324
|
children: t("installFailed")
|
|
7646
8325
|
}),
|
|
7647
8326
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7648
|
-
className:
|
|
8327
|
+
className: _dsh_plugin_shop_css_201c343a_default.log,
|
|
7649
8328
|
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7650
|
-
className:
|
|
8329
|
+
className: _dsh_plugin_shop_css_201c343a_default.logLine,
|
|
8330
|
+
"data-shop-log-line": true,
|
|
7651
8331
|
children: line
|
|
7652
8332
|
}, index))
|
|
7653
8333
|
}),
|
|
7654
8334
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7655
|
-
className:
|
|
8335
|
+
className: _dsh_plugin_shop_css_201c343a_default.failedDetail,
|
|
7656
8336
|
children: view.detail === "" ? t("installTransportFailed") : view.detail
|
|
7657
8337
|
})
|
|
7658
8338
|
]
|
|
7659
8339
|
});
|
|
7660
8340
|
if (view.kind === "rejected") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7661
|
-
className:
|
|
8341
|
+
className: _dsh_plugin_shop_css_201c343a_default.installPanel,
|
|
7662
8342
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7663
|
-
className:
|
|
8343
|
+
className: _dsh_plugin_shop_css_201c343a_default.rejectedCode,
|
|
7664
8344
|
children: t(rejectionCodeKey(view.code))
|
|
7665
8345
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7666
|
-
className:
|
|
8346
|
+
className: _dsh_plugin_shop_css_201c343a_default.rejectedDetail,
|
|
7667
8347
|
children: view.detail
|
|
7668
8348
|
})]
|
|
7669
8349
|
});
|
|
7670
8350
|
if (gateOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7671
|
-
className:
|
|
8351
|
+
className: _dsh_plugin_shop_css_201c343a_default.gate,
|
|
7672
8352
|
children: [
|
|
7673
8353
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7674
|
-
className:
|
|
8354
|
+
className: _dsh_plugin_shop_css_201c343a_default.gateTitle,
|
|
7675
8355
|
children: t("acknowledgementTitle")
|
|
7676
8356
|
}),
|
|
7677
8357
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7678
|
-
className:
|
|
8358
|
+
className: _dsh_plugin_shop_css_201c343a_default.gateBody,
|
|
7679
8359
|
children: t("acknowledgementBody")
|
|
7680
8360
|
}),
|
|
7681
|
-
!
|
|
7682
|
-
|
|
7683
|
-
|
|
7684
|
-
|
|
8361
|
+
!blockersStated && blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
|
|
8362
|
+
blocker,
|
|
8363
|
+
className: _dsh_plugin_shop_css_201c343a_default.gateWarning,
|
|
8364
|
+
surface: "warning"
|
|
7685
8365
|
}, blocker.kind)),
|
|
7686
8366
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7687
|
-
className:
|
|
8367
|
+
className: _dsh_plugin_shop_css_201c343a_default.gateActions,
|
|
7688
8368
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7689
8369
|
type: "button",
|
|
7690
|
-
className:
|
|
8370
|
+
className: _dsh_plugin_shop_css_201c343a_default.confirmButton,
|
|
7691
8371
|
"data-shop-confirm": true,
|
|
7692
8372
|
onClick: () => {
|
|
7693
8373
|
setGateOpen(false);
|
|
@@ -7699,7 +8379,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7699
8379
|
children: t("confirm")
|
|
7700
8380
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7701
8381
|
type: "button",
|
|
7702
|
-
className:
|
|
8382
|
+
className: _dsh_plugin_shop_css_201c343a_default.cancelButton,
|
|
7703
8383
|
onClick: () => setGateOpen(false),
|
|
7704
8384
|
children: t("cancel")
|
|
7705
8385
|
})]
|
|
@@ -7707,25 +8387,37 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7707
8387
|
]
|
|
7708
8388
|
});
|
|
7709
8389
|
const update = variant === "update";
|
|
7710
|
-
const refused = blockers
|
|
7711
|
-
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
7715
|
-
|
|
7716
|
-
|
|
7717
|
-
|
|
7718
|
-
|
|
7719
|
-
|
|
7720
|
-
|
|
7721
|
-
|
|
7722
|
-
|
|
7723
|
-
|
|
7724
|
-
|
|
7725
|
-
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
8390
|
+
const refused = refusesInstall(blockers);
|
|
8391
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
|
|
8392
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8393
|
+
type: "button",
|
|
8394
|
+
className: _dsh_plugin_shop_css_201c343a_default.installButton,
|
|
8395
|
+
disabled: refused,
|
|
8396
|
+
...refused ? { "data-shop-refused": true } : {},
|
|
8397
|
+
...update ? { "data-shop-update": true } : { "data-shop-install": true },
|
|
8398
|
+
onClick: () => {
|
|
8399
|
+
if (tier === "verified") start({
|
|
8400
|
+
...target,
|
|
8401
|
+
acknowledged: void 0
|
|
8402
|
+
});
|
|
8403
|
+
else setGateOpen(true);
|
|
8404
|
+
},
|
|
8405
|
+
children: t(update ? "update" : "install")
|
|
8406
|
+
}),
|
|
8407
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerBadge, {
|
|
8408
|
+
blockers,
|
|
8409
|
+
t
|
|
8410
|
+
}),
|
|
8411
|
+
refused && !blockersStated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8412
|
+
className: _dsh_plugin_shop_css_201c343a_default.refusal,
|
|
8413
|
+
"data-shop-refusal": true,
|
|
8414
|
+
children: blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
|
|
8415
|
+
blocker,
|
|
8416
|
+
className: _dsh_plugin_shop_css_201c343a_default.gateWarning,
|
|
8417
|
+
surface: "warning"
|
|
8418
|
+
}, blocker.kind))
|
|
8419
|
+
})
|
|
8420
|
+
] });
|
|
7729
8421
|
}
|
|
7730
8422
|
/** One installed entry's uninstall flow: remove from the profile through the
|
|
7731
8423
|
* same executor records and poll loop as installs. Uninstalling revokes
|
|
@@ -7736,23 +8428,32 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7736
8428
|
function UninstallPanel({ name, t, restart, restartBlocked, reload, flow }) {
|
|
7737
8429
|
const { view, start, reset } = flow;
|
|
7738
8430
|
if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7739
|
-
className:
|
|
8431
|
+
className: _dsh_plugin_shop_css_201c343a_default.installPanel,
|
|
7740
8432
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7741
|
-
className:
|
|
8433
|
+
className: _dsh_plugin_shop_css_201c343a_default.installing,
|
|
7742
8434
|
children: t("uninstalling")
|
|
7743
8435
|
}), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7744
|
-
className:
|
|
8436
|
+
className: _dsh_plugin_shop_css_201c343a_default.log,
|
|
7745
8437
|
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7746
|
-
className:
|
|
8438
|
+
className: _dsh_plugin_shop_css_201c343a_default.logLine,
|
|
8439
|
+
"data-shop-log-line": true,
|
|
7747
8440
|
children: line
|
|
7748
8441
|
}, index))
|
|
7749
8442
|
})]
|
|
7750
8443
|
});
|
|
7751
8444
|
if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7752
|
-
className:
|
|
8445
|
+
className: _dsh_plugin_shop_css_201c343a_default.installedActions,
|
|
7753
8446
|
children: [
|
|
8447
|
+
view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8448
|
+
className: _dsh_plugin_shop_css_201c343a_default.log,
|
|
8449
|
+
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8450
|
+
className: _dsh_plugin_shop_css_201c343a_default.logLine,
|
|
8451
|
+
"data-shop-log-line": true,
|
|
8452
|
+
children: line
|
|
8453
|
+
}, index))
|
|
8454
|
+
}),
|
|
7754
8455
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7755
|
-
className:
|
|
8456
|
+
className: _dsh_plugin_shop_css_201c343a_default.notice,
|
|
7756
8457
|
"data-shop-uninstall-done": true,
|
|
7757
8458
|
children: t(uninstallActivationNoticeKey(view.activation))
|
|
7758
8459
|
}),
|
|
@@ -7766,7 +8467,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7766
8467
|
}),
|
|
7767
8468
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7768
8469
|
type: "button",
|
|
7769
|
-
className:
|
|
8470
|
+
className: _dsh_plugin_shop_css_201c343a_default.cancelButton,
|
|
7770
8471
|
"data-shop-uninstall-dismiss": true,
|
|
7771
8472
|
onClick: reset,
|
|
7772
8473
|
children: t("dismiss")
|
|
@@ -7774,26 +8475,27 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7774
8475
|
]
|
|
7775
8476
|
});
|
|
7776
8477
|
if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7777
|
-
className:
|
|
8478
|
+
className: _dsh_plugin_shop_css_201c343a_default.installPanel,
|
|
7778
8479
|
children: [
|
|
7779
8480
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7780
|
-
className:
|
|
8481
|
+
className: _dsh_plugin_shop_css_201c343a_default.failedHeading,
|
|
7781
8482
|
children: t("uninstallFailed")
|
|
7782
8483
|
}),
|
|
7783
8484
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7784
|
-
className:
|
|
8485
|
+
className: _dsh_plugin_shop_css_201c343a_default.log,
|
|
7785
8486
|
children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
7786
|
-
className:
|
|
8487
|
+
className: _dsh_plugin_shop_css_201c343a_default.logLine,
|
|
8488
|
+
"data-shop-log-line": true,
|
|
7787
8489
|
children: line
|
|
7788
8490
|
}, index))
|
|
7789
8491
|
}),
|
|
7790
8492
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7791
|
-
className:
|
|
8493
|
+
className: _dsh_plugin_shop_css_201c343a_default.failedDetail,
|
|
7792
8494
|
children: view.detail === "" ? t("uninstallTransportFailed") : view.detail
|
|
7793
8495
|
}),
|
|
7794
8496
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7795
8497
|
type: "button",
|
|
7796
|
-
className:
|
|
8498
|
+
className: _dsh_plugin_shop_css_201c343a_default.uninstallButton,
|
|
7797
8499
|
"data-shop-uninstall-retry": true,
|
|
7798
8500
|
onClick: () => void start({ name }),
|
|
7799
8501
|
children: t("retry")
|
|
@@ -7803,113 +8505,97 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7803
8505
|
if (view.kind !== "idle") return null;
|
|
7804
8506
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7805
8507
|
type: "button",
|
|
7806
|
-
className:
|
|
8508
|
+
className: _dsh_plugin_shop_css_201c343a_default.uninstallButton,
|
|
7807
8509
|
"data-shop-uninstall": true,
|
|
7808
8510
|
onClick: () => void start({ name }),
|
|
7809
8511
|
children: t("uninstall")
|
|
7810
8512
|
});
|
|
7811
8513
|
}
|
|
8514
|
+
/** The page's restart, re-rendering whenever it changes state. Shared by
|
|
8515
|
+
* every restart offer and by the version row, in this tab and in whatever
|
|
8516
|
+
* tab an HMR swap mounts in its place. */
|
|
8517
|
+
function useRestartMonitor() {
|
|
8518
|
+
return (0, react.useSyncExternalStore)(subscribeRestartMonitor, readRestartMonitor);
|
|
8519
|
+
}
|
|
7812
8520
|
/** The §8 restart flow (amendment 2026-08-27): after an install, update, or
|
|
7813
8521
|
* uninstall reports done, this panel offers a restart of dsh. The
|
|
7814
8522
|
* confirmation gate states the cost — the page disconnects and in-flight
|
|
7815
|
-
* conversations/tasks are interrupted — and on confirm the
|
|
7816
|
-
* commits the two-phase handoff: the host exits,
|
|
7817
|
-
*
|
|
7818
|
-
* once the NEW server
|
|
7819
|
-
*
|
|
7820
|
-
* command. */
|
|
7821
|
-
function RestartPanel({ t, restart, gate }) {
|
|
8523
|
+
* conversations/tasks are interrupted — and on confirm the page asks the host
|
|
8524
|
+
* (restart-monitor.ts), which commits the two-phase handoff: the host exits,
|
|
8525
|
+
* a helper re-runs dsh, and the page's monitor polls the origin after a grace
|
|
8526
|
+
* period, refreshing the page once the NEW server keeps answering. A refused
|
|
8527
|
+
* restart renders the host's published detail above the offer, which stays;
|
|
8528
|
+
* a server that never comes back names the manual command. */
|
|
8529
|
+
function RestartPanel({ t, restart, reload, gate }) {
|
|
7822
8530
|
const [ownGateOpen, setOwnGateOpen] = (0, react.useState)(false);
|
|
7823
8531
|
const gateOpen = gate === void 0 ? ownGateOpen : gate.open;
|
|
7824
8532
|
const setGateOpen = (open) => {
|
|
7825
8533
|
if (gate === void 0) setOwnGateOpen(open);
|
|
7826
8534
|
else if (!open) gate.close();
|
|
7827
8535
|
};
|
|
7828
|
-
const [
|
|
8536
|
+
const [refusal, setRefusal] = (0, react.useState)(null);
|
|
8537
|
+
const monitor = useRestartMonitor();
|
|
7829
8538
|
const onConfirm = async () => {
|
|
7830
8539
|
setGateOpen(false);
|
|
7831
|
-
|
|
7832
|
-
|
|
7833
|
-
|
|
7834
|
-
|
|
7835
|
-
|
|
7836
|
-
|
|
7837
|
-
|
|
7838
|
-
return;
|
|
7839
|
-
}
|
|
7840
|
-
setState({ kind: "restarting" });
|
|
7841
|
-
} catch {
|
|
7842
|
-
setState({
|
|
7843
|
-
kind: "failed",
|
|
7844
|
-
detail: t("restartTransportFailed")
|
|
7845
|
-
});
|
|
7846
|
-
}
|
|
8540
|
+
setRefusal(null);
|
|
8541
|
+
const outcome = await requestRestart({
|
|
8542
|
+
reload,
|
|
8543
|
+
restart
|
|
8544
|
+
});
|
|
8545
|
+
if (outcome.kind === "refused") setRefusal(outcome.detail);
|
|
8546
|
+
else if (outcome.kind === "unreachable") setRefusal(t("restartTransportFailed"));
|
|
7847
8547
|
};
|
|
7848
|
-
(0,
|
|
7849
|
-
|
|
7850
|
-
const started = Date.now();
|
|
7851
|
-
const timer = setInterval(() => {
|
|
7852
|
-
const elapsed = Date.now() - started;
|
|
7853
|
-
if (elapsed < 3e3) return;
|
|
7854
|
-
fetch(window.location.href, { cache: "no-store" }).then(() => {
|
|
7855
|
-
window.location.reload();
|
|
7856
|
-
}, () => {});
|
|
7857
|
-
if (elapsed > 3e4) {
|
|
7858
|
-
clearInterval(timer);
|
|
7859
|
-
setState({
|
|
7860
|
-
kind: "failed",
|
|
7861
|
-
detail: t("restartFailedNotice")
|
|
7862
|
-
});
|
|
7863
|
-
}
|
|
7864
|
-
}, INSTALL_POLL_MS);
|
|
7865
|
-
return () => clearInterval(timer);
|
|
7866
|
-
}, [state, t]);
|
|
7867
|
-
if (state.kind === "restarting") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7868
|
-
className: _dsh_plugin_shop_css_e3675a89_default.notice,
|
|
8548
|
+
if (monitor?.kind === "requesting" || monitor?.kind === "restarting") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8549
|
+
className: _dsh_plugin_shop_css_201c343a_default.notice,
|
|
7869
8550
|
"data-shop-restarting": true,
|
|
7870
8551
|
children: t("restarting")
|
|
7871
8552
|
});
|
|
7872
|
-
if (
|
|
7873
|
-
className:
|
|
8553
|
+
if (monitor?.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8554
|
+
className: _dsh_plugin_shop_css_201c343a_default.failedDetail,
|
|
7874
8555
|
"data-shop-restart-error": true,
|
|
7875
|
-
children:
|
|
8556
|
+
children: monitor.logFile === void 0 ? t("restartFailedNotice") : t("restartFailedLogNotice", { log: monitor.logFile })
|
|
7876
8557
|
});
|
|
7877
8558
|
if (gateOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7878
|
-
className:
|
|
8559
|
+
className: _dsh_plugin_shop_css_201c343a_default.gate,
|
|
7879
8560
|
children: [
|
|
7880
8561
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7881
|
-
className:
|
|
8562
|
+
className: _dsh_plugin_shop_css_201c343a_default.gateTitle,
|
|
7882
8563
|
children: t("restartTitle")
|
|
7883
8564
|
}),
|
|
7884
8565
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7885
|
-
className:
|
|
8566
|
+
className: _dsh_plugin_shop_css_201c343a_default.gateBody,
|
|
7886
8567
|
children: t("restartBody")
|
|
7887
8568
|
}),
|
|
7888
8569
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7889
|
-
className:
|
|
8570
|
+
className: _dsh_plugin_shop_css_201c343a_default.gateActions,
|
|
7890
8571
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7891
8572
|
type: "button",
|
|
7892
|
-
className:
|
|
8573
|
+
className: _dsh_plugin_shop_css_201c343a_default.confirmButton,
|
|
7893
8574
|
"data-shop-restart-confirm": true,
|
|
7894
8575
|
onClick: () => void onConfirm(),
|
|
7895
8576
|
children: t("restartConfirm")
|
|
7896
8577
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7897
8578
|
type: "button",
|
|
7898
|
-
className:
|
|
8579
|
+
className: _dsh_plugin_shop_css_201c343a_default.cancelButton,
|
|
7899
8580
|
onClick: () => setGateOpen(false),
|
|
7900
8581
|
children: t("cancel")
|
|
7901
8582
|
})]
|
|
7902
8583
|
})
|
|
7903
8584
|
]
|
|
7904
8585
|
});
|
|
7905
|
-
|
|
7906
|
-
|
|
8586
|
+
const refused = refusal === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8587
|
+
className: _dsh_plugin_shop_css_201c343a_default.failedDetail,
|
|
8588
|
+
"data-shop-restart-error": true,
|
|
8589
|
+
children: refusal
|
|
8590
|
+
});
|
|
8591
|
+
if (gate !== void 0) return refused;
|
|
8592
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [refused, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7907
8593
|
type: "button",
|
|
7908
|
-
className:
|
|
8594
|
+
className: _dsh_plugin_shop_css_201c343a_default.restartButton,
|
|
7909
8595
|
"data-shop-restart": true,
|
|
7910
8596
|
onClick: () => setGateOpen(true),
|
|
7911
8597
|
children: t("restart")
|
|
7912
|
-
});
|
|
8598
|
+
})] });
|
|
7913
8599
|
}
|
|
7914
8600
|
/**
|
|
7915
8601
|
* The §4/§8 offer under a settled install, update or uninstall: a reload
|
|
@@ -7933,9 +8619,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7933
8619
|
if (activation !== "restart") return null;
|
|
7934
8620
|
return restartBlocked === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RestartPanel, {
|
|
7935
8621
|
t,
|
|
7936
|
-
restart
|
|
8622
|
+
restart,
|
|
8623
|
+
reload
|
|
7937
8624
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7938
|
-
className:
|
|
8625
|
+
className: _dsh_plugin_shop_css_201c343a_default.notice,
|
|
7939
8626
|
"data-shop-restart-disabled": true,
|
|
7940
8627
|
children: t(restartBlockedNoticeKey(restartBlocked))
|
|
7941
8628
|
});
|
|
@@ -7946,14 +8633,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7946
8633
|
* would help without knowing that now is a good time. */
|
|
7947
8634
|
function ReloadPanel({ t, reload, where }) {
|
|
7948
8635
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7949
|
-
className:
|
|
8636
|
+
className: _dsh_plugin_shop_css_201c343a_default.reloadPanel,
|
|
7950
8637
|
"data-shop-reload": where,
|
|
7951
8638
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
7952
|
-
className:
|
|
8639
|
+
className: _dsh_plugin_shop_css_201c343a_default.notice,
|
|
7953
8640
|
children: t("reloadNote")
|
|
7954
8641
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
7955
8642
|
type: "button",
|
|
7956
|
-
className:
|
|
8643
|
+
className: _dsh_plugin_shop_css_201c343a_default.reloadButton,
|
|
7957
8644
|
onClick: reload,
|
|
7958
8645
|
children: t("reload")
|
|
7959
8646
|
})]
|
|
@@ -7966,10 +8653,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7966
8653
|
* transport throw renders the localized failure line — its private detail
|
|
7967
8654
|
* (which can name hosts and ports) never reaches the UI.
|
|
7968
8655
|
*/
|
|
7969
|
-
function EnabledSwitch({ row, t, setEnabled, reload }) {
|
|
8656
|
+
function EnabledSwitch({ row, t, setEnabled, restart, restartBlocked, reload }) {
|
|
7970
8657
|
const [enabled, setEnabledState] = (0, react.useState)(row.enabled);
|
|
7971
8658
|
const [toggle, setToggle] = (0, react.useState)({ kind: "idle" });
|
|
7972
|
-
const [
|
|
8659
|
+
const [owed, setOwed] = (0, react.useState)("nothing");
|
|
7973
8660
|
const onToggle = async () => {
|
|
7974
8661
|
if (toggle.kind === "saving") return;
|
|
7975
8662
|
const next = !enabled;
|
|
@@ -7982,7 +8669,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7982
8669
|
if (result.ok) {
|
|
7983
8670
|
setEnabledState(next);
|
|
7984
8671
|
setToggle({ kind: "saved" });
|
|
7985
|
-
if (result.activation
|
|
8672
|
+
if (result.activation === "restart") setOwed("restart");
|
|
8673
|
+
else if (result.activation !== "live") setOwed((previous) => previous === "restart" ? previous : "reload");
|
|
7986
8674
|
} else setToggle({
|
|
7987
8675
|
kind: "error",
|
|
7988
8676
|
detail: result.detail || t("toggleFailed")
|
|
@@ -7995,7 +8683,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
7995
8683
|
}
|
|
7996
8684
|
};
|
|
7997
8685
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
7998
|
-
className:
|
|
8686
|
+
className: _dsh_plugin_shop_css_201c343a_default.switchWrap,
|
|
7999
8687
|
"data-shop-enabled-switch": row.name,
|
|
8000
8688
|
children: [
|
|
8001
8689
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
@@ -8004,23 +8692,31 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8004
8692
|
"aria-checked": enabled,
|
|
8005
8693
|
"aria-label": t("enabledSwitch"),
|
|
8006
8694
|
"data-shop-toggle": true,
|
|
8007
|
-
className: `${
|
|
8695
|
+
className: `${_dsh_plugin_shop_css_201c343a_default.switch} ${enabled ? _dsh_plugin_shop_css_201c343a_default.switchOn : ""}`,
|
|
8008
8696
|
onClick: () => void onToggle(),
|
|
8009
8697
|
disabled: toggle.kind === "saving",
|
|
8010
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className:
|
|
8698
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_201c343a_default.switchKnob })
|
|
8011
8699
|
}),
|
|
8012
|
-
toggle.kind === "saved" &&
|
|
8013
|
-
className:
|
|
8700
|
+
toggle.kind === "saved" && owed === "nothing" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8701
|
+
className: _dsh_plugin_shop_css_201c343a_default.notice,
|
|
8014
8702
|
"data-shop-hot-apply": true,
|
|
8015
8703
|
children: t("hotApplyNote")
|
|
8016
8704
|
}),
|
|
8017
|
-
|
|
8705
|
+
owed === "restart" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8706
|
+
className: _dsh_plugin_shop_css_201c343a_default.notice,
|
|
8707
|
+
"data-shop-toggle-restart": true,
|
|
8708
|
+
children: t("toggleRestartNote")
|
|
8709
|
+
}),
|
|
8710
|
+
owed !== "nothing" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ActivationOffer, {
|
|
8711
|
+
activation: owed,
|
|
8018
8712
|
t,
|
|
8713
|
+
restart,
|
|
8714
|
+
restartBlocked,
|
|
8019
8715
|
reload,
|
|
8020
8716
|
where: "toggle"
|
|
8021
8717
|
}),
|
|
8022
8718
|
toggle.kind === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8023
|
-
className:
|
|
8719
|
+
className: _dsh_plugin_shop_css_201c343a_default.failedDetail,
|
|
8024
8720
|
"data-shop-toggle-error": true,
|
|
8025
8721
|
children: toggle.detail
|
|
8026
8722
|
})
|
|
@@ -8030,25 +8726,27 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8030
8726
|
/** One outdated install row (§7.3): the name, the installed and latest
|
|
8031
8727
|
* versions, the hot enable/disable switch, and the update button (the
|
|
8032
8728
|
* install flow for `name@latest`, reusing `InstallPanel`). */
|
|
8033
|
-
function OutdatedRow({ row, tier, missing, t, setEnabled, flowFor, restart, restartBlocked, reload }) {
|
|
8729
|
+
function OutdatedRow({ row, tier, missing, harness, t, setEnabled, flowFor, restart, restartBlocked, reload }) {
|
|
8034
8730
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8035
|
-
className:
|
|
8731
|
+
className: _dsh_plugin_shop_css_201c343a_default.outdatedRow,
|
|
8036
8732
|
"data-shop-outdated-entry": row.name,
|
|
8037
8733
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8038
|
-
className:
|
|
8734
|
+
className: _dsh_plugin_shop_css_201c343a_default.outdatedInfo,
|
|
8039
8735
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8040
|
-
className:
|
|
8736
|
+
className: _dsh_plugin_shop_css_201c343a_default.name,
|
|
8041
8737
|
children: row.name
|
|
8042
8738
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
8043
|
-
className:
|
|
8739
|
+
className: _dsh_plugin_shop_css_201c343a_default.outdatedVersions,
|
|
8044
8740
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("installedVersion", { version: row.source === "github" ? row.installed.slice(0, 7) : row.installed }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("latestVersion", { version: row.latest }) })]
|
|
8045
8741
|
})]
|
|
8046
8742
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8047
|
-
className:
|
|
8743
|
+
className: _dsh_plugin_shop_css_201c343a_default.outdatedActions,
|
|
8048
8744
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EnabledSwitch, {
|
|
8049
8745
|
row,
|
|
8050
8746
|
t,
|
|
8051
8747
|
setEnabled,
|
|
8748
|
+
restart,
|
|
8749
|
+
restartBlocked,
|
|
8052
8750
|
reload
|
|
8053
8751
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
|
|
8054
8752
|
target: {
|
|
@@ -8060,8 +8758,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8060
8758
|
},
|
|
8061
8759
|
tier,
|
|
8062
8760
|
variant: "update",
|
|
8063
|
-
missing,
|
|
8064
|
-
blockers: blockersOf(missing, void 0, t),
|
|
8761
|
+
blockers: stateBlockers(blockersOf(missing, harness, void 0), t),
|
|
8065
8762
|
flow: flowFor(identityKey(row)),
|
|
8066
8763
|
t,
|
|
8067
8764
|
restart,
|
|
@@ -8088,29 +8785,30 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8088
8785
|
* tier for the update gate is looked up from the catalog by install IDENTITY,
|
|
8089
8786
|
* never by name (community → acknowledgement); an entry absent from the
|
|
8090
8787
|
* catalog defaults to the community gate (the safer read). */
|
|
8091
|
-
function OutdatedSection({ state, entriesByKey, missingByKey, t, setEnabled, flowFor, restart, restartBlocked, reload }) {
|
|
8788
|
+
function OutdatedSection({ state, entriesByKey, missingByKey, harnessVerdicts, t, setEnabled, flowFor, restart, restartBlocked, reload }) {
|
|
8092
8789
|
if (state.kind === "loading") return null;
|
|
8093
8790
|
if (state.kind === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8094
|
-
className:
|
|
8791
|
+
className: _dsh_plugin_shop_css_201c343a_default.stateLine,
|
|
8095
8792
|
"data-shop-outdated-error": true,
|
|
8096
8793
|
children: t("installedError")
|
|
8097
8794
|
});
|
|
8098
8795
|
const outdated = state.entries.filter((entry) => entry.outdated);
|
|
8099
8796
|
if (outdated.length === 0) return null;
|
|
8100
8797
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
|
|
8101
|
-
className:
|
|
8798
|
+
className: _dsh_plugin_shop_css_201c343a_default.outdatedSection,
|
|
8102
8799
|
"data-shop-outdated": true,
|
|
8103
8800
|
"aria-labelledby": UPDATABLE_HEADING_ID,
|
|
8104
8801
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
|
|
8105
|
-
className:
|
|
8802
|
+
className: _dsh_plugin_shop_css_201c343a_default.updatableHeading,
|
|
8106
8803
|
id: UPDATABLE_HEADING_ID,
|
|
8107
8804
|
children: t("updatableSection")
|
|
8108
8805
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
8109
|
-
className:
|
|
8806
|
+
className: _dsh_plugin_shop_css_201c343a_default.outdatedList,
|
|
8110
8807
|
children: outdated.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedRow, {
|
|
8111
8808
|
row,
|
|
8112
8809
|
tier: entriesByKey.get(identityKey(row))?.tier ?? "community",
|
|
8113
8810
|
missing: missingByKey.get(identityKey(row)) ?? [],
|
|
8811
|
+
harness: harnessVerdictOf(harnessVerdicts, identityKey(row)),
|
|
8114
8812
|
t,
|
|
8115
8813
|
setEnabled,
|
|
8116
8814
|
flowFor,
|
|
@@ -8121,10 +8819,71 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8121
8819
|
})]
|
|
8122
8820
|
});
|
|
8123
8821
|
}
|
|
8124
|
-
/**
|
|
8125
|
-
*
|
|
8822
|
+
/**
|
|
8823
|
+
* The shop's own error boundary (design 2026-09-26-market-borrowings §4).
|
|
8824
|
+
*
|
|
8825
|
+
* The harness catches a crashing slot entry as well, but renders an empty
|
|
8826
|
+
* `<div data-slot-error>` that stays until the page reloads: a blank tab, with
|
|
8827
|
+
* no message and no way back. This one says what happened and offers a Retry
|
|
8828
|
+
* that remounts the tab from scratch. Its fallback carries no `data-shop-tab`,
|
|
8829
|
+
* so a crashed tab cannot satisfy anything waiting for a working one. A class,
|
|
8830
|
+
* because React offers error boundaries in no other form.
|
|
8831
|
+
*/
|
|
8832
|
+
var ShopErrorBoundary = class extends react.Component {
|
|
8833
|
+
state = { error: null };
|
|
8834
|
+
static getDerivedStateFromError(error) {
|
|
8835
|
+
return { error: error instanceof Error ? error : new Error(String(error)) };
|
|
8836
|
+
}
|
|
8837
|
+
componentDidCatch(error, info) {
|
|
8838
|
+
console.error("dsh-plugin-shop: the shop tab crashed", error, info.componentStack);
|
|
8839
|
+
}
|
|
8840
|
+
render() {
|
|
8841
|
+
const { error } = this.state;
|
|
8842
|
+
if (error === null) return this.props.children;
|
|
8843
|
+
const say = (key, fallback) => {
|
|
8844
|
+
try {
|
|
8845
|
+
return this.props.t(key);
|
|
8846
|
+
} catch {
|
|
8847
|
+
return fallback;
|
|
8848
|
+
}
|
|
8849
|
+
};
|
|
8850
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8851
|
+
className: PANEL_ROOT,
|
|
8852
|
+
translate: "no",
|
|
8853
|
+
"data-shop-crashed": true,
|
|
8854
|
+
role: "alert",
|
|
8855
|
+
children: [
|
|
8856
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8857
|
+
className: _dsh_plugin_shop_css_201c343a_default.stateLine,
|
|
8858
|
+
children: say("tabCrashed", en.tabCrashed)
|
|
8859
|
+
}),
|
|
8860
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8861
|
+
className: _dsh_plugin_shop_css_201c343a_default.failedDetail,
|
|
8862
|
+
children: error.message
|
|
8863
|
+
}),
|
|
8864
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8865
|
+
type: "button",
|
|
8866
|
+
className: _dsh_plugin_shop_css_201c343a_default.actionButton,
|
|
8867
|
+
onClick: () => this.setState({ error: null }),
|
|
8868
|
+
children: say("retry", en.retry)
|
|
8869
|
+
})
|
|
8870
|
+
]
|
|
8871
|
+
});
|
|
8872
|
+
}
|
|
8873
|
+
};
|
|
8874
|
+
/** The tab as the settings slot registers it (`index.ts`): the body inside the
|
|
8875
|
+
* shop's own error boundary. Exported under the name the body used to carry,
|
|
8876
|
+
* so nothing can register the tab without the boundary. */
|
|
8126
8877
|
function ShopTab(props) {
|
|
8127
|
-
|
|
8878
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShopErrorBoundary, {
|
|
8879
|
+
t: props.t,
|
|
8880
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShopTabBody, { ...props })
|
|
8881
|
+
});
|
|
8882
|
+
}
|
|
8883
|
+
/** The shop tab body: browse, search, refresh, and render one card per
|
|
8884
|
+
* entry. Data attributes on the e2e-relevant nodes follow the Task 3 list. */
|
|
8885
|
+
function ShopTabBody(props) {
|
|
8886
|
+
const { t, catalog, install, installStatus, setEnabled, installed, installedSpecs, uninstall, noteUninstalled, restart, version, updateStart, reload: injectedReload } = props;
|
|
8128
8887
|
const [catalogState, setCatalogState] = (0, react.useState)({ kind: "loading" });
|
|
8129
8888
|
const [installedState, setInstalledState] = (0, react.useState)({ kind: "loading" });
|
|
8130
8889
|
/** The install gate's own input, or undefined while it is unknown — loading,
|
|
@@ -8139,7 +8898,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8139
8898
|
}), [injectedReload]);
|
|
8140
8899
|
const [selfRestartGate, setSelfRestartGate] = (0, react.useState)(false);
|
|
8141
8900
|
const selfUpdate = useUpdateSelf(updateStart, installStatus);
|
|
8901
|
+
const restartMonitor = useRestartMonitor();
|
|
8902
|
+
const hostPending = selfVersion !== null && selfVersion.pendingVersion !== null;
|
|
8903
|
+
const selfPending = selfUpdate.view.kind === "done" || selfUpdate.view.kind === "idle" && hostPending;
|
|
8142
8904
|
const [request, setRequest] = (0, react.useState)({ kind: "initial" });
|
|
8905
|
+
const [versionReload, setVersionReload] = (0, react.useState)(0);
|
|
8143
8906
|
const [mutations, setMutations] = (0, react.useState)(0);
|
|
8144
8907
|
const noteMutation = (0, react.useCallback)(() => {
|
|
8145
8908
|
setMutations((current) => current + 1);
|
|
@@ -8148,12 +8911,21 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8148
8911
|
const flows = useInstallFlows(install, installStatus, (0, react.useCallback)((key, outcome) => {
|
|
8149
8912
|
if (outcome === "done") uninstallResetRef.current(key);
|
|
8150
8913
|
noteMutation();
|
|
8914
|
+
if (outcome === "done") setRequest({ kind: "reverdict" });
|
|
8151
8915
|
}, [noteMutation]));
|
|
8152
8916
|
const uninstallFlows = useUninstallFlows(uninstall, installStatus, (0, react.useCallback)((key, outcome) => {
|
|
8153
8917
|
if (outcome !== "done") return;
|
|
8154
8918
|
flows.flowFor(key).reset();
|
|
8155
8919
|
noteMutation();
|
|
8156
|
-
|
|
8920
|
+
const name = catalogState.kind === "ready" ? catalogState.result.plugins.find((entry) => entryKey(entry) === key)?.name : void 0;
|
|
8921
|
+
if (name !== void 0) noteUninstalled?.(name);
|
|
8922
|
+
setRequest({ kind: "reverdict" });
|
|
8923
|
+
}, [
|
|
8924
|
+
flows,
|
|
8925
|
+
noteMutation,
|
|
8926
|
+
catalogState,
|
|
8927
|
+
noteUninstalled
|
|
8928
|
+
]));
|
|
8157
8929
|
uninstallResetRef.current = uninstallFlows.resetFlow;
|
|
8158
8930
|
const [reloading, setReloading] = (0, react.useState)(false);
|
|
8159
8931
|
const [reloadFailed, setReloadFailed] = (0, react.useState)(false);
|
|
@@ -8164,21 +8936,27 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8164
8936
|
const [visibleCount, setVisibleCount] = (0, react.useState)(48);
|
|
8165
8937
|
const sentinelRef = (0, react.useRef)(null);
|
|
8166
8938
|
const filteredLenRef = (0, react.useRef)(0);
|
|
8939
|
+
const pendingRefreshRef = (0, react.useRef)(null);
|
|
8167
8940
|
(0, react.useEffect)(() => {
|
|
8168
8941
|
let cancelled = false;
|
|
8169
|
-
if (request.kind !== "refresh") setCatalogState((current) => current.kind === "ready" ? current : { kind: "loading" });
|
|
8942
|
+
if (request.kind !== "refresh" && request.kind !== "reverdict") setCatalogState((current) => current.kind === "ready" ? current : { kind: "loading" });
|
|
8943
|
+
if (request.kind === "refresh") pendingRefreshRef.current = request;
|
|
8170
8944
|
const load = async () => {
|
|
8171
8945
|
try {
|
|
8172
|
-
const result = await catalog(request.kind === "refresh" ? { refresh: true } : void 0);
|
|
8946
|
+
const result = await catalog(request.kind === "refresh" ? { refresh: true } : request.kind === "reverdict" ? { reverdict: true } : void 0);
|
|
8173
8947
|
if (!cancelled) setCatalogState({
|
|
8174
8948
|
kind: "ready",
|
|
8175
8949
|
result
|
|
8176
8950
|
});
|
|
8177
8951
|
} catch {
|
|
8178
|
-
if (cancelled)
|
|
8952
|
+
if (cancelled) {
|
|
8953
|
+
if (request.kind === "refresh" && pendingRefreshRef.current === request) setReloadFailed(true);
|
|
8954
|
+
return;
|
|
8955
|
+
}
|
|
8179
8956
|
setCatalogState((current) => current.kind === "ready" ? current : { kind: "error" });
|
|
8180
|
-
setReloadFailed(true);
|
|
8957
|
+
if (request.kind !== "reverdict") setReloadFailed(true);
|
|
8181
8958
|
} finally {
|
|
8959
|
+
if (pendingRefreshRef.current === request) pendingRefreshRef.current = null;
|
|
8182
8960
|
if (!cancelled) setReloading(false);
|
|
8183
8961
|
}
|
|
8184
8962
|
};
|
|
@@ -8199,7 +8977,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8199
8977
|
return () => {
|
|
8200
8978
|
cancelled = true;
|
|
8201
8979
|
};
|
|
8202
|
-
}, [version,
|
|
8980
|
+
}, [version, versionReload]);
|
|
8203
8981
|
const [checkState, setCheckState] = (0, react.useState)("idle");
|
|
8204
8982
|
const upToDateTimer = (0, react.useRef)(null);
|
|
8205
8983
|
(0, react.useEffect)(() => () => {
|
|
@@ -8272,7 +9050,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8272
9050
|
}
|
|
8273
9051
|
return map;
|
|
8274
9052
|
}, [catalogState, specs]);
|
|
8275
|
-
const
|
|
9053
|
+
const harnessVerdicts = (0, react.useMemo)(() => catalogState.kind === "ready" ? catalogState.result.incompatibleHarness : void 0, [catalogState]);
|
|
9054
|
+
const badgedIncompatible = (0, react.useCallback)((key) => readsIncompatible(blockersOf(missingByKey.get(key) ?? [], harnessVerdictOf(harnessVerdicts, key), nameTakenByKey.get(key))), [
|
|
9055
|
+
missingByKey,
|
|
9056
|
+
harnessVerdicts,
|
|
9057
|
+
nameTakenByKey
|
|
9058
|
+
]);
|
|
8276
9059
|
const matched = (0, react.useMemo)(() => {
|
|
8277
9060
|
const q = query.trim().toLowerCase();
|
|
8278
9061
|
return (category === "installed" ? sortedCatalog : browsable).filter((entry) => {
|
|
@@ -8332,50 +9115,56 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8332
9115
|
return map;
|
|
8333
9116
|
}, [catalogState]);
|
|
8334
9117
|
if (catalogState.kind === "loading") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8335
|
-
className:
|
|
9118
|
+
className: PANEL_ROOT,
|
|
9119
|
+
translate: "no",
|
|
8336
9120
|
"data-shop-tab": true,
|
|
8337
9121
|
"aria-busy": "true",
|
|
8338
9122
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8339
|
-
className:
|
|
9123
|
+
className: _dsh_plugin_shop_css_201c343a_default.srOnly,
|
|
8340
9124
|
children: t("loading")
|
|
8341
9125
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8342
|
-
className:
|
|
9126
|
+
className: _dsh_plugin_shop_css_201c343a_default.skeletonGrid,
|
|
8343
9127
|
"data-shop-skeleton": true,
|
|
8344
9128
|
"aria-hidden": "true",
|
|
8345
9129
|
children: Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8346
|
-
className:
|
|
9130
|
+
className: _dsh_plugin_shop_css_201c343a_default.skeletonCard,
|
|
8347
9131
|
children: [
|
|
8348
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className:
|
|
8349
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className:
|
|
8350
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className:
|
|
8351
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className:
|
|
9132
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_201c343a_default.skeletonName }),
|
|
9133
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_201c343a_default.skeletonSummary }),
|
|
9134
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_201c343a_default.skeletonSummaryShort }),
|
|
9135
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_201c343a_default.skeletonActions })
|
|
8352
9136
|
]
|
|
8353
9137
|
}, i))
|
|
8354
9138
|
})]
|
|
8355
9139
|
});
|
|
8356
9140
|
if (catalogState.kind === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8357
|
-
className:
|
|
9141
|
+
className: PANEL_ROOT,
|
|
9142
|
+
translate: "no",
|
|
8358
9143
|
"data-shop-tab": true,
|
|
8359
9144
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8360
|
-
className:
|
|
9145
|
+
className: _dsh_plugin_shop_css_201c343a_default.stateLine,
|
|
8361
9146
|
children: t("error")
|
|
8362
9147
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8363
9148
|
type: "button",
|
|
8364
|
-
className:
|
|
8365
|
-
onClick: () =>
|
|
9149
|
+
className: _dsh_plugin_shop_css_201c343a_default.actionButton,
|
|
9150
|
+
onClick: () => {
|
|
9151
|
+
setRequest({ kind: "retry" });
|
|
9152
|
+
setVersionReload((current) => current + 1);
|
|
9153
|
+
},
|
|
8366
9154
|
children: t("retry")
|
|
8367
9155
|
})]
|
|
8368
9156
|
});
|
|
8369
9157
|
const { result } = catalogState;
|
|
8370
9158
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8371
|
-
className:
|
|
9159
|
+
className: PANEL_ROOT,
|
|
9160
|
+
translate: "no",
|
|
8372
9161
|
"data-shop-tab": true,
|
|
8373
9162
|
children: [
|
|
8374
9163
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8375
|
-
className:
|
|
9164
|
+
className: _dsh_plugin_shop_css_201c343a_default.toolbar,
|
|
8376
9165
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
|
|
8377
9166
|
type: "search",
|
|
8378
|
-
className:
|
|
9167
|
+
className: _dsh_plugin_shop_css_201c343a_default.searchInput,
|
|
8379
9168
|
"aria-label": t("search"),
|
|
8380
9169
|
placeholder: t("search"),
|
|
8381
9170
|
value: query,
|
|
@@ -8384,27 +9173,27 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8384
9173
|
setVisibleCount(48);
|
|
8385
9174
|
}
|
|
8386
9175
|
}), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8387
|
-
className:
|
|
9176
|
+
className: _dsh_plugin_shop_css_201c343a_default.toolbarRight,
|
|
8388
9177
|
children: [
|
|
8389
9178
|
result.stale && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8390
|
-
className:
|
|
9179
|
+
className: _dsh_plugin_shop_css_201c343a_default.staleBadge,
|
|
8391
9180
|
children: t("staleLabel", { date: result.builtAt.slice(0, 10) })
|
|
8392
9181
|
}),
|
|
8393
9182
|
selfVersion !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8394
|
-
className:
|
|
9183
|
+
className: _dsh_plugin_shop_css_201c343a_default.versionBlock,
|
|
8395
9184
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
8396
|
-
className:
|
|
9185
|
+
className: _dsh_plugin_shop_css_201c343a_default.versionText,
|
|
8397
9186
|
"data-shop-version": true,
|
|
8398
9187
|
children: ["v", selfVersion.installed]
|
|
8399
|
-
}),
|
|
9188
|
+
}), restartMonitor !== null ? null : selfPending && restartBlocked === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8400
9189
|
type: "button",
|
|
8401
|
-
className:
|
|
9190
|
+
className: _dsh_plugin_shop_css_201c343a_default.restartSelfButton,
|
|
8402
9191
|
"data-shop-restart": true,
|
|
8403
9192
|
onClick: () => setSelfRestartGate(true),
|
|
8404
9193
|
children: t("restart")
|
|
8405
|
-
}) : selfVersion.outdated && selfVersion.latest !== null && selfUpdate.view.kind === "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
9194
|
+
}) : !selfPending && selfVersion.outdated && selfVersion.latest !== null && selfUpdate.view.kind === "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8406
9195
|
type: "button",
|
|
8407
|
-
className:
|
|
9196
|
+
className: _dsh_plugin_shop_css_201c343a_default.updateSelfButton,
|
|
8408
9197
|
"data-shop-update-self": true,
|
|
8409
9198
|
onClick: () => {
|
|
8410
9199
|
if (selfVersion.latest !== null) selfUpdate.start({ version: selfVersion.latest });
|
|
@@ -8412,7 +9201,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8412
9201
|
children: t("update")
|
|
8413
9202
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8414
9203
|
type: "button",
|
|
8415
|
-
className:
|
|
9204
|
+
className: _dsh_plugin_shop_css_201c343a_default.checkUpdateButton,
|
|
8416
9205
|
"data-shop-check-update": true,
|
|
8417
9206
|
title: t("checkUpdateTitle"),
|
|
8418
9207
|
disabled: checkState !== "idle",
|
|
@@ -8421,7 +9210,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8421
9210
|
})]
|
|
8422
9211
|
}),
|
|
8423
9212
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
|
|
8424
|
-
className:
|
|
9213
|
+
className: _dsh_plugin_shop_css_201c343a_default.githubLink,
|
|
8425
9214
|
href: SHOP_REPO_URL,
|
|
8426
9215
|
target: "_blank",
|
|
8427
9216
|
rel: "noopener noreferrer",
|
|
@@ -8440,39 +9229,41 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8440
9229
|
})]
|
|
8441
9230
|
}),
|
|
8442
9231
|
selfUpdate.view.kind === "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8443
|
-
className:
|
|
9232
|
+
className: _dsh_plugin_shop_css_201c343a_default.selfUpdatePanel,
|
|
8444
9233
|
"data-shop-self-updating": true,
|
|
8445
9234
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8446
|
-
className:
|
|
9235
|
+
className: _dsh_plugin_shop_css_201c343a_default.installing,
|
|
8447
9236
|
children: t(installPhaseKey(selfUpdate.view.phase))
|
|
8448
9237
|
}), selfUpdate.view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8449
|
-
className:
|
|
9238
|
+
className: _dsh_plugin_shop_css_201c343a_default.log,
|
|
8450
9239
|
children: selfUpdate.view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8451
|
-
className:
|
|
9240
|
+
className: _dsh_plugin_shop_css_201c343a_default.logLine,
|
|
9241
|
+
"data-shop-log-line": true,
|
|
8452
9242
|
children: line
|
|
8453
9243
|
}, index))
|
|
8454
9244
|
})]
|
|
8455
9245
|
}),
|
|
8456
|
-
|
|
8457
|
-
className:
|
|
9246
|
+
selfPending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
9247
|
+
className: _dsh_plugin_shop_css_201c343a_default.selfUpdatePanel,
|
|
8458
9248
|
"data-shop-self-update-done": true,
|
|
8459
9249
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8460
|
-
className:
|
|
9250
|
+
className: _dsh_plugin_shop_css_201c343a_default.installedActions,
|
|
8461
9251
|
children: [
|
|
8462
9252
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8463
|
-
className:
|
|
9253
|
+
className: _dsh_plugin_shop_css_201c343a_default.notice,
|
|
8464
9254
|
children: t("installedRestartNotice")
|
|
8465
9255
|
}),
|
|
8466
9256
|
restartBlocked === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RestartPanel, {
|
|
8467
9257
|
t,
|
|
8468
9258
|
restart,
|
|
9259
|
+
reload,
|
|
8469
9260
|
gate: {
|
|
8470
9261
|
open: selfRestartGate,
|
|
8471
9262
|
close: () => setSelfRestartGate(false)
|
|
8472
9263
|
}
|
|
8473
9264
|
}),
|
|
8474
9265
|
restartBlocked !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8475
|
-
className:
|
|
9266
|
+
className: _dsh_plugin_shop_css_201c343a_default.notice,
|
|
8476
9267
|
"data-shop-restart-disabled": true,
|
|
8477
9268
|
children: t(restartBlockedNoticeKey(restartBlocked))
|
|
8478
9269
|
})
|
|
@@ -8480,34 +9271,35 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8480
9271
|
})
|
|
8481
9272
|
}),
|
|
8482
9273
|
selfUpdate.view.kind === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8483
|
-
className:
|
|
9274
|
+
className: _dsh_plugin_shop_css_201c343a_default.selfUpdatePanel,
|
|
8484
9275
|
"data-shop-self-update-failed": true,
|
|
8485
9276
|
children: [
|
|
8486
9277
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8487
|
-
className:
|
|
9278
|
+
className: _dsh_plugin_shop_css_201c343a_default.failedHeading,
|
|
8488
9279
|
children: t("updateFailed")
|
|
8489
9280
|
}),
|
|
8490
9281
|
selfUpdate.view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8491
|
-
className:
|
|
9282
|
+
className: _dsh_plugin_shop_css_201c343a_default.log,
|
|
8492
9283
|
children: selfUpdate.view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8493
|
-
className:
|
|
9284
|
+
className: _dsh_plugin_shop_css_201c343a_default.logLine,
|
|
9285
|
+
"data-shop-log-line": true,
|
|
8494
9286
|
children: line
|
|
8495
9287
|
}, index))
|
|
8496
9288
|
}),
|
|
8497
9289
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8498
|
-
className:
|
|
9290
|
+
className: _dsh_plugin_shop_css_201c343a_default.failedDetail,
|
|
8499
9291
|
children: selfUpdate.view.detail === "" ? t("updateTransportFailed") : selfUpdate.view.detail
|
|
8500
9292
|
})
|
|
8501
9293
|
]
|
|
8502
9294
|
}),
|
|
8503
9295
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8504
|
-
className:
|
|
9296
|
+
className: _dsh_plugin_shop_css_201c343a_default.categoryBar,
|
|
8505
9297
|
role: "group",
|
|
8506
9298
|
"aria-label": t("catalog"),
|
|
8507
9299
|
children: [
|
|
8508
9300
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
8509
9301
|
type: "button",
|
|
8510
|
-
className: category === null ? `${
|
|
9302
|
+
className: category === null ? `${_dsh_plugin_shop_css_201c343a_default.categoryButton} ${_dsh_plugin_shop_css_201c343a_default.categoryButtonOn}` : _dsh_plugin_shop_css_201c343a_default.categoryButton,
|
|
8511
9303
|
"aria-pressed": category === null,
|
|
8512
9304
|
onClick: () => {
|
|
8513
9305
|
setCategory(null);
|
|
@@ -8522,7 +9314,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8522
9314
|
}),
|
|
8523
9315
|
CATEGORY_ORDER.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
8524
9316
|
type: "button",
|
|
8525
|
-
className: category === key ? `${
|
|
9317
|
+
className: category === key ? `${_dsh_plugin_shop_css_201c343a_default.categoryButton} ${_dsh_plugin_shop_css_201c343a_default.categoryButtonOn}` : _dsh_plugin_shop_css_201c343a_default.categoryButton,
|
|
8526
9318
|
"aria-pressed": category === key,
|
|
8527
9319
|
"data-category": key,
|
|
8528
9320
|
"data-shop-category-tab": key,
|
|
@@ -8538,7 +9330,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8538
9330
|
}, key)),
|
|
8539
9331
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
8540
9332
|
type: "button",
|
|
8541
|
-
className: category === "installed" ? `${
|
|
9333
|
+
className: category === "installed" ? `${_dsh_plugin_shop_css_201c343a_default.categoryButton} ${_dsh_plugin_shop_css_201c343a_default.categoryButtonOn}` : _dsh_plugin_shop_css_201c343a_default.categoryButton,
|
|
8542
9334
|
"aria-pressed": category === "installed",
|
|
8543
9335
|
"data-shop-category-installed": true,
|
|
8544
9336
|
onClick: () => {
|
|
@@ -8555,7 +9347,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8555
9347
|
type: "button",
|
|
8556
9348
|
role: "switch",
|
|
8557
9349
|
"aria-checked": hideIncompatible,
|
|
8558
|
-
className: hideIncompatible ? `${
|
|
9350
|
+
className: hideIncompatible ? `${_dsh_plugin_shop_css_201c343a_default.incompatibleFilter} ${_dsh_plugin_shop_css_201c343a_default.incompatibleFilterOn}` : _dsh_plugin_shop_css_201c343a_default.incompatibleFilter,
|
|
8559
9351
|
title: t(hideIncompatible ? "showIncompatibleTitle" : "incompatibleFilterTitle"),
|
|
8560
9352
|
"data-shop-hide-incompatible": true,
|
|
8561
9353
|
onClick: () => {
|
|
@@ -8563,18 +9355,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8563
9355
|
setVisibleCount(48);
|
|
8564
9356
|
},
|
|
8565
9357
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8566
|
-
className: hideIncompatible ? `${
|
|
9358
|
+
className: hideIncompatible ? `${_dsh_plugin_shop_css_201c343a_default.switch} ${_dsh_plugin_shop_css_201c343a_default.switchOn}` : _dsh_plugin_shop_css_201c343a_default.switch,
|
|
8567
9359
|
"aria-hidden": "true",
|
|
8568
|
-
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className:
|
|
9360
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_201c343a_default.switchKnob })
|
|
8569
9361
|
}), t("hideIncompatible", { count: incompatibleCount })]
|
|
8570
9362
|
})
|
|
8571
9363
|
]
|
|
8572
9364
|
}),
|
|
8573
9365
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8574
|
-
className:
|
|
9366
|
+
className: _dsh_plugin_shop_css_201c343a_default.catalogStatsRow,
|
|
8575
9367
|
children: [
|
|
8576
9368
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8577
|
-
className:
|
|
9369
|
+
className: _dsh_plugin_shop_css_201c343a_default.catalogStats,
|
|
8578
9370
|
"data-shop-catalog-stats": true,
|
|
8579
9371
|
children: t("catalogStats", {
|
|
8580
9372
|
count: String(browsable.length),
|
|
@@ -8583,32 +9375,33 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8583
9375
|
}),
|
|
8584
9376
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
8585
9377
|
type: "button",
|
|
8586
|
-
className:
|
|
9378
|
+
className: _dsh_plugin_shop_css_201c343a_default.catalogRefreshButton,
|
|
8587
9379
|
"data-shop-catalog-refresh": true,
|
|
8588
9380
|
disabled: reloading,
|
|
8589
9381
|
onClick: () => {
|
|
8590
9382
|
setReloading(true);
|
|
8591
9383
|
setReloadFailed(false);
|
|
8592
9384
|
setRequest({ kind: "refresh" });
|
|
9385
|
+
setVersionReload((current) => current + 1);
|
|
8593
9386
|
},
|
|
8594
9387
|
children: reloading ? t("refreshing") : t("refresh")
|
|
8595
9388
|
}),
|
|
8596
9389
|
reloadFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
8597
|
-
className:
|
|
9390
|
+
className: _dsh_plugin_shop_css_201c343a_default.catalogRefreshFailed,
|
|
8598
9391
|
"data-shop-catalog-refresh-failed": true,
|
|
8599
9392
|
children: t("refreshFailed")
|
|
8600
9393
|
})
|
|
8601
9394
|
]
|
|
8602
9395
|
}),
|
|
8603
9396
|
result.plugins.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8604
|
-
className:
|
|
9397
|
+
className: _dsh_plugin_shop_css_201c343a_default.emptyLine,
|
|
8605
9398
|
children: t("empty")
|
|
8606
9399
|
}) : filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8607
|
-
className:
|
|
9400
|
+
className: _dsh_plugin_shop_css_201c343a_default.emptyLine,
|
|
8608
9401
|
"data-shop-empty": true,
|
|
8609
9402
|
children: matched.length > 0 ? t("emptyIncompatibleFiltered") : t("emptySearch")
|
|
8610
9403
|
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
|
|
8611
|
-
className:
|
|
9404
|
+
className: _dsh_plugin_shop_css_201c343a_default.cards,
|
|
8612
9405
|
children: [visible.map((entry) => {
|
|
8613
9406
|
const key = entryKey(entry);
|
|
8614
9407
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryCard, {
|
|
@@ -8616,6 +9409,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8616
9409
|
stars: starsOf(entry, stars),
|
|
8617
9410
|
installed: installedByKey.get(key),
|
|
8618
9411
|
missing: missingByKey.get(key) ?? [],
|
|
9412
|
+
harness: harnessVerdictOf(harnessVerdicts, key),
|
|
8619
9413
|
nameTakenBy: nameTakenByKey.get(key),
|
|
8620
9414
|
t,
|
|
8621
9415
|
flow: flows.flowFor(key),
|
|
@@ -8628,12 +9422,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8628
9422
|
}) }, key);
|
|
8629
9423
|
}), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
|
|
8630
9424
|
ref: sentinelRef,
|
|
8631
|
-
className:
|
|
9425
|
+
className: _dsh_plugin_shop_css_201c343a_default.cardsSentry,
|
|
8632
9426
|
"data-shop-sentry": true,
|
|
8633
9427
|
"aria-hidden": "true"
|
|
8634
9428
|
})]
|
|
8635
9429
|
}), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8636
|
-
className:
|
|
9430
|
+
className: _dsh_plugin_shop_css_201c343a_default.showingLine,
|
|
8637
9431
|
"aria-live": "polite",
|
|
8638
9432
|
children: t("showing", {
|
|
8639
9433
|
shown: String(visibleCount),
|
|
@@ -8644,6 +9438,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8644
9438
|
state: installedState,
|
|
8645
9439
|
entriesByKey,
|
|
8646
9440
|
missingByKey,
|
|
9441
|
+
harnessVerdicts,
|
|
8647
9442
|
t,
|
|
8648
9443
|
setEnabled,
|
|
8649
9444
|
flowFor: flows.flowFor,
|
|
@@ -8670,6 +9465,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8670
9465
|
* fire-and-forget from surfacing as an unhandled rejection. Every later
|
|
8671
9466
|
* result, including an explicit refresh, replaces this timestamped stash. */
|
|
8672
9467
|
let warmCatalog = null;
|
|
9468
|
+
/** Package names THIS PAGE has uninstalled since it loaded (design
|
|
9469
|
+
* 2026-09-01-harness-compatibility section 9.1): the module table can still
|
|
9470
|
+
* hold a graph row or a `loadCache` record for one after a restart-free
|
|
9471
|
+
* uninstall (module-table.ts's header explains why), so
|
|
9472
|
+
* the refinement must never clear a name here on the table's say-so — the
|
|
9473
|
+
* host's verdict stands unconditionally. Module-level so it survives the tab
|
|
9474
|
+
* closing and reopening; reset in `apply` beside `warmCatalog`, because a
|
|
9475
|
+
* re-applied bundle is a new page that has uninstalled nothing yet. An
|
|
9476
|
+
* uninstall made from another tab, another window, or the CLI is never added
|
|
9477
|
+
* here and stays unknown to this page until it reloads. */
|
|
9478
|
+
let pageRemoved = /* @__PURE__ */ new Set();
|
|
8673
9479
|
/** Services required by the tab registration and the Remote mount.
|
|
8674
9480
|
*
|
|
8675
9481
|
* `remote.shop` is deliberately ABSENT: this package both mounts its own
|
|
@@ -8692,7 +9498,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8692
9498
|
en
|
|
8693
9499
|
}), "dsh-plugin-shop: dictionaries");
|
|
8694
9500
|
const dispose = await ctx.remote.$mount(TYPERT_REMOTE);
|
|
8695
|
-
|
|
9501
|
+
let withdrawRestart = () => {};
|
|
9502
|
+
ctx.effect(() => () => {
|
|
9503
|
+
withdrawRestart();
|
|
9504
|
+
return dispose();
|
|
9505
|
+
}, "dsh-plugin-shop: shop remote mount");
|
|
8696
9506
|
const ns = ctx.get("remote.shop");
|
|
8697
9507
|
if (ns === void 0) throw new Error("dsh-plugin-shop: the shop remote did not register (service \"remote.shop\" missing after $mount)");
|
|
8698
9508
|
const unwrap = (result) => {
|
|
@@ -8700,6 +9510,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8700
9510
|
return result.value;
|
|
8701
9511
|
};
|
|
8702
9512
|
warmCatalog = null;
|
|
9513
|
+
pageRemoved = /* @__PURE__ */ new Set();
|
|
8703
9514
|
const warmed = Promise.resolve(ns.catalog(void 0)).then((result) => unwrap(result));
|
|
8704
9515
|
warmCatalog = {
|
|
8705
9516
|
at: Date.now(),
|
|
@@ -8708,36 +9519,64 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
|
|
|
8708
9519
|
warmed.catch(() => {});
|
|
8709
9520
|
Promise.resolve(ns.installed()).then((result) => unwrap(result)).catch(() => {});
|
|
8710
9521
|
Promise.resolve(ns.version()).then((result) => unwrap(result)).catch(() => {});
|
|
8711
|
-
const
|
|
8712
|
-
|
|
8713
|
-
|
|
8714
|
-
const refreshed = unwrap(await ns.catalog(args));
|
|
8715
|
-
warmCatalog = {
|
|
8716
|
-
at: Date.now(),
|
|
8717
|
-
result: Promise.resolve(refreshed)
|
|
8718
|
-
};
|
|
8719
|
-
return refreshed;
|
|
8720
|
-
}
|
|
8721
|
-
const warm = warmCatalog;
|
|
8722
|
-
if (warm !== null && Date.now() - warm.at < 3e5) try {
|
|
8723
|
-
return await warm.result;
|
|
8724
|
-
} catch {}
|
|
8725
|
-
const fresh = unwrap(await ns.catalog(args));
|
|
9522
|
+
const hostCatalog = async (args) => {
|
|
9523
|
+
if (args?.refresh === true) {
|
|
9524
|
+
const refreshed = unwrap(await ns.catalog({ refresh: true }));
|
|
8726
9525
|
warmCatalog = {
|
|
8727
9526
|
at: Date.now(),
|
|
8728
|
-
result: Promise.resolve(
|
|
9527
|
+
result: Promise.resolve(refreshed)
|
|
8729
9528
|
};
|
|
8730
|
-
return
|
|
9529
|
+
return refreshed;
|
|
9530
|
+
}
|
|
9531
|
+
if (args?.reverdict === true) {
|
|
9532
|
+
warmCatalog = null;
|
|
9533
|
+
const judged = unwrap(await ns.catalog(void 0));
|
|
9534
|
+
warmCatalog = {
|
|
9535
|
+
at: Date.now(),
|
|
9536
|
+
result: Promise.resolve(judged)
|
|
9537
|
+
};
|
|
9538
|
+
return judged;
|
|
9539
|
+
}
|
|
9540
|
+
const warm = warmCatalog;
|
|
9541
|
+
if (warm !== null && Date.now() - warm.at < 3e5) try {
|
|
9542
|
+
return await warm.result;
|
|
9543
|
+
} catch {}
|
|
9544
|
+
const fresh = unwrap(await ns.catalog(void 0));
|
|
9545
|
+
warmCatalog = {
|
|
9546
|
+
at: Date.now(),
|
|
9547
|
+
result: Promise.resolve(fresh)
|
|
9548
|
+
};
|
|
9549
|
+
return fresh;
|
|
9550
|
+
};
|
|
9551
|
+
const handOver = async (result) => ({
|
|
9552
|
+
...result,
|
|
9553
|
+
incompatible: await refineAgainstModuleTable(result.incompatible ?? {}, ctx.get("modules"), pageRemoved)
|
|
9554
|
+
});
|
|
9555
|
+
const restart = async () => unwrap(await ns.restart());
|
|
9556
|
+
withdrawRestart = registerRestartCarrier(restart);
|
|
9557
|
+
const injected = () => ({
|
|
9558
|
+
catalog: async (args) => handOver(await hostCatalog(args)),
|
|
9559
|
+
install: async (args) => {
|
|
9560
|
+
warmCatalog = null;
|
|
9561
|
+
return unwrap(await ns.installStart(args));
|
|
8731
9562
|
},
|
|
8732
|
-
install: async (args) => unwrap(await ns.installStart(args)),
|
|
8733
9563
|
installStatus: async (args) => unwrap(await ns.installStatus(args)),
|
|
8734
9564
|
setEnabled: async (args) => unwrap(await ns.setEnabled(args)),
|
|
8735
9565
|
installed: async () => unwrap(await ns.installed()),
|
|
8736
9566
|
installedSpecs: async () => unwrap(await ns.installedSpecs()),
|
|
8737
|
-
uninstall: async (args) =>
|
|
8738
|
-
|
|
9567
|
+
uninstall: async (args) => {
|
|
9568
|
+
warmCatalog = null;
|
|
9569
|
+
return unwrap(await ns.uninstallStart(args));
|
|
9570
|
+
},
|
|
9571
|
+
noteUninstalled: (name) => {
|
|
9572
|
+
pageRemoved.add(name);
|
|
9573
|
+
},
|
|
9574
|
+
restart,
|
|
8739
9575
|
version: async () => unwrap(await ns.version()),
|
|
8740
|
-
updateStart: async (args) =>
|
|
9576
|
+
updateStart: async (args) => {
|
|
9577
|
+
warmCatalog = null;
|
|
9578
|
+
return unwrap(await ns.updateStart(args));
|
|
9579
|
+
}
|
|
8741
9580
|
});
|
|
8742
9581
|
ctx.slots.inject("settings.plugins.tab", () => ctx.slots.register({
|
|
8743
9582
|
name: "settings.plugins.tab",
|