dsh-plugin-shop 0.8.5 → 0.8.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -4322,7 +4322,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4322
4322
  })]).optional(),
4323
4323
  "dshPeers": union([_undefined(), record(string(), string())]).optional(),
4324
4324
  "unpackedSize": union([_undefined(), number()]).optional(),
4325
- "installSize": union([_undefined(), number()]).optional()
4325
+ "installSize": union([_undefined(), number()]).optional(),
4326
+ "archived": union([
4327
+ _undefined(),
4328
+ literal(false),
4329
+ literal(true)
4330
+ ]).optional()
4326
4331
  })),
4327
4332
  "denied": array(object({
4328
4333
  "name": string(),
@@ -4354,9 +4359,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4354
4359
  "repo": union([_undefined(), string()]).optional(),
4355
4360
  "subdir": union([_undefined(), string()]).optional(),
4356
4361
  "installed": string(),
4357
- "latest": string(),
4362
+ "latest": union([literal(null), string()]),
4358
4363
  "outdated": boolean(),
4359
- "enabled": boolean()
4364
+ "enabled": boolean(),
4365
+ "shadowed": union([_undefined(), literal(true)]).optional()
4360
4366
  }));
4361
4367
  const dsh_plugin_shop_shop_installedSpecs_result$schema = union([literal(null), record(string(), string())]);
4362
4368
  const dsh_plugin_shop_shop_installStart_parameter_0$schema = object({
@@ -4427,7 +4433,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4427
4433
  });
4428
4434
  const dsh_plugin_shop_shop_restart_result$schema = union([object({
4429
4435
  "ok": literal(true),
4430
- "logFile": union([_undefined(), string()]).optional()
4436
+ "logFile": union([_undefined(), string()]).optional(),
4437
+ "bootId": union([_undefined(), string()]).optional(),
4438
+ "pid": union([_undefined(), number()]).optional()
4431
4439
  }), object({
4432
4440
  "ok": literal(false),
4433
4441
  "detail": string()
@@ -4484,7 +4492,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4484
4492
  _undefined(),
4485
4493
  literal(null),
4486
4494
  string()
4487
- ]).optional()
4495
+ ]).optional(),
4496
+ "bootId": union([_undefined(), string()]).optional()
4488
4497
  });
4489
4498
  const TYPERT_REMOTE = {
4490
4499
  package: "dsh-plugin-shop",
@@ -4515,7 +4524,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4515
4524
  },
4516
4525
  sourceLocation: {
4517
4526
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4518
- "line": 1253,
4527
+ "line": 1272,
4519
4528
  "column": 9
4520
4529
  }
4521
4530
  },
@@ -4534,7 +4543,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4534
4543
  },
4535
4544
  sourceLocation: {
4536
4545
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4537
- "line": 1720,
4546
+ "line": 1739,
4538
4547
  "column": 9
4539
4548
  }
4540
4549
  },
@@ -4553,7 +4562,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4553
4562
  },
4554
4563
  sourceLocation: {
4555
4564
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4556
- "line": 1711,
4565
+ "line": 1730,
4557
4566
  "column": 9
4558
4567
  }
4559
4568
  },
@@ -4583,7 +4592,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4583
4592
  },
4584
4593
  sourceLocation: {
4585
4594
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4586
- "line": 1379,
4595
+ "line": 1398,
4587
4596
  "column": 9
4588
4597
  }
4589
4598
  },
@@ -4612,7 +4621,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4612
4621
  },
4613
4622
  sourceLocation: {
4614
4623
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4615
- "line": 1687,
4624
+ "line": 1706,
4616
4625
  "column": 3
4617
4626
  }
4618
4627
  },
@@ -4631,7 +4640,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4631
4640
  },
4632
4641
  sourceLocation: {
4633
4642
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4634
- "line": 1916,
4643
+ "line": 1990,
4635
4644
  "column": 9
4636
4645
  }
4637
4646
  },
@@ -4660,7 +4669,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4660
4669
  },
4661
4670
  sourceLocation: {
4662
4671
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4663
- "line": 924,
4672
+ "line": 943,
4664
4673
  "column": 9
4665
4674
  }
4666
4675
  },
@@ -4690,7 +4699,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4690
4699
  },
4691
4700
  sourceLocation: {
4692
4701
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4693
- "line": 1827,
4702
+ "line": 1884,
4694
4703
  "column": 9
4695
4704
  }
4696
4705
  },
@@ -4719,7 +4728,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4719
4728
  },
4720
4729
  sourceLocation: {
4721
4730
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4722
- "line": 2020,
4731
+ "line": 2097,
4723
4732
  "column": 9
4724
4733
  }
4725
4734
  },
@@ -4738,7 +4747,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
4738
4747
  },
4739
4748
  sourceLocation: {
4740
4749
  "file": "packages/dsh-plugin-shop/src/host/index.ts",
4741
- "line": 1978,
4750
+ "line": 2054,
4742
4751
  "column": 9
4743
4752
  }
4744
4753
  }
@@ -6588,6 +6597,31 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6588
6597
  if (input.stableSinceMs === null && input.elapsedMs > 3e4) return "failed";
6589
6598
  return "wait";
6590
6599
  }
6600
+ /**
6601
+ * Whether two `version()` answers came from different dsh processes — dsh
6602
+ * restarted under the page between them (design §8, 2026-10-06 amendment).
6603
+ *
6604
+ * A process names itself by `bootId` for as long as it lives, so where either
6605
+ * answer carries one, a difference decides: a host older than the field never
6606
+ * starts naming itself, and a newer one never stops. Where neither does, only
6607
+ * the code they run can differ — one process runs one version, read once at
6608
+ * its boot — which still catches the first restart after a self-update from
6609
+ * a host older than the field, the one that matters most.
6610
+ */
6611
+ function answeredByAnotherProcess(previous, next) {
6612
+ if (previous.bootId !== void 0 || next.bootId !== void 0) return previous.bootId !== next.bootId;
6613
+ return previous.installed !== next.installed;
6614
+ }
6615
+ /**
6616
+ * Whether dsh restarting under the page settles this receipt: a change that
6617
+ * landed and was waiting for exactly that. The tab clears these once the
6618
+ * answers come from another process, and nothing else — a reload is still
6619
+ * owed by a page nobody reloaded, a failure still happened, and a flow still
6620
+ * running settles by its own poll.
6621
+ */
6622
+ function settledByRestart(view) {
6623
+ return view.kind === "done" && view.activation === "restart";
6624
+ }
6591
6625
  /** How long the check-update button reports "up to date" after a re-check
6592
6626
  * finds no newer release, before reverting to its idle label. */
6593
6627
  const CHECK_UP_TO_DATE_MS = 3e3;
@@ -6851,6 +6885,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6851
6885
  * 2026-09-26-market-borrowings §4). */
6852
6886
  tabCrashed: "插件商店在显示这一页时出错了。",
6853
6887
  search: "搜索插件",
6888
+ /** One subdued line under the search box when a Chinese query fired a
6889
+ * dictionary intent (design 2026-10-08-search-query-expansion §4).
6890
+ * `{terms}` is the comma-joined recall terms, without the `#` marker
6891
+ * (boundary-only surface detail, not a reader's fact). */
6892
+ zhExpansionNote: "同时匹配:{terms}",
6854
6893
  all: "全部",
6855
6894
  catalog: "插件目录",
6856
6895
  catalogStats: "{count} 个插件 · 构建于 {date}",
@@ -6867,6 +6906,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6867
6906
  tierVerified: "已审核",
6868
6907
  tierVerifiedStale: "审核版本过期",
6869
6908
  tierCommunity: "社区",
6909
+ /** The github repository GitHub reports as archived (design 2026-10-08 §1.3).
6910
+ * Subdued register, like `tierCommunity`: nothing is wrong with the install
6911
+ * itself; the owner has simply frozen the project. */
6912
+ archivedBadge: "已归档",
6870
6913
  capabilitiesNote: "作者自述,无强制约束",
6871
6914
  install: "安装",
6872
6915
  confirm: "确认",
@@ -6948,6 +6991,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6948
6991
  nameTakenDetail: "已安装同名的另一个插件({holder})。\n两个同名插件声明相同的加载项 id,无法共存;安装它会替换掉已装的那个。",
6949
6992
  npmSource: "npm 包",
6950
6993
  githubSource: "源码在 GitHub",
6994
+ /** The badge and the explanation for an installed github entry no catalog
6995
+ * row claims (C10 MVP). The mechanism synthesizes a row for ANY github spec
6996
+ * with no catalog row — shadowed by a same-named npm package, an unlisted
6997
+ * fork, a removed listing, or a same-named conflict with a different repo —
6998
+ * so the copy names the catalog fact and lists the causes, rather than
6999
+ * claiming the npm-twin one. */
7000
+ shadowedBadge: "目录未列出此包",
7001
+ shadowedDetail: "此包已安装,但当前的插件目录没有它(可能是被同名 npm 包遮蔽、作者已下架、或目录尚未收录);它照常运行。",
6951
7002
  updatableSection: "可更新",
6952
7003
  installedVersion: "已安装 v{version}",
6953
7004
  latestVersion: "最新 v{version}",
@@ -6973,6 +7024,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6973
7024
  * that answered and then died. */
6974
7025
  restartFailedLogNotice: "重启后服务器没有回来,或回来后没能保持运行。新进程的日志在 {log};也可以手动启动:dsh web",
6975
7026
  restartTransportFailed: "重启请求未能送达。请稍后重试。",
7027
+ notRestartedNotice: "dsh 没有重启:页面刷新后,回答的仍是被要求重启的那个进程,所以更改还没有生效。请手动重启:退出正在运行的 dsh,再运行 dsh web。",
7028
+ notRestartedPidNotice: "dsh 没有重启:页面刷新后,回答的仍是被要求重启的那个进程(pid {pid}),所以更改还没有生效。请手动重启:运行 kill {pid}(进程仍不退出时用 kill -9 {pid}),再运行 dsh web。",
7029
+ notRestartedLogNotice: "这次重启的经过记录在 {log}。",
6976
7030
  restartBlockedDesktopNotice: "此 profile 由 DeepSeek Harness 桌面版管理,商店无法重启它。请重启桌面版,以应用更改。",
6977
7031
  restartBlockedWindowsNotice: "商店暂不支持在 Windows 上重启 dsh。请退出 dsh 后重新启动,以应用更改。",
6978
7032
  restartBlockedSystemdNotice: "当前 dsh 由 systemd 服务托管,商店无法安全地重启它。请手动重启服务,或在商店行配置中设置 allowRestart: true。",
@@ -6997,6 +7051,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
6997
7051
  retry: "Retry",
6998
7052
  tabCrashed: "The plugin shop hit an error while showing this page.",
6999
7053
  search: "Search plugins",
7054
+ /** One subdued line under the search box when a Chinese query fired a
7055
+ * dictionary intent (design 2026-10-08-search-query-expansion §4). */
7056
+ zhExpansionNote: "Also matching: {terms}",
7000
7057
  all: "All",
7001
7058
  catalog: "Plugin catalog",
7002
7059
  catalogStats: "{count} packages · built {date}",
@@ -7011,6 +7068,10 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7011
7068
  empty: "No plugins in the catalog.",
7012
7069
  emptySearch: "No matching plugins.",
7013
7070
  tierVerified: "Verified",
7071
+ /** The github repository GitHub reports as archived (design 2026-10-08 §1.3).
7072
+ * Subdued register: nothing is wrong with the install; the owner has simply
7073
+ * frozen the project. */
7074
+ archivedBadge: "Archived",
7014
7075
  tierVerifiedStale: "Reviewed version stale",
7015
7076
  tierCommunity: "Community",
7016
7077
  capabilitiesNote: "self-declared by the author, not enforced",
@@ -7070,6 +7131,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7070
7131
  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.",
7071
7132
  npmSource: "npm package",
7072
7133
  githubSource: "source on GitHub",
7134
+ shadowedBadge: "Not in the current catalog",
7135
+ shadowedDetail: "This package is installed but the current catalog does not list it (it may be shadowed by a same-named npm package, removed by the author, or never listed); it runs as before.",
7073
7136
  updatableSection: "Updatable",
7074
7137
  installedVersion: "installed v{version}",
7075
7138
  latestVersion: "latest v{version}",
@@ -7090,6 +7153,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7090
7153
  restartFailedNotice: "The server did not come back after the restart. Start it manually: dsh web (see restart.log in the shop cache directory)",
7091
7154
  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",
7092
7155
  restartTransportFailed: "The restart request could not be delivered. Please retry.",
7156
+ notRestartedNotice: "dsh did not restart: after the reload, the process that was asked to restart is still the one answering, so the change has not taken effect. Restart it by hand: quit the running dsh, then run dsh web.",
7157
+ notRestartedPidNotice: "dsh did not restart: after the reload, the process that was asked to restart (pid {pid}) is still the one answering, so the change has not taken effect. Restart it by hand: run kill {pid} (kill -9 {pid} if it does not exit), then run dsh web.",
7158
+ notRestartedLogNotice: "What this restart did is logged in {log}.",
7093
7159
  restartBlockedDesktopNotice: "This profile is managed by the DeepSeek Harness desktop app, so the shop cannot restart it. Restart the app to apply the change.",
7094
7160
  restartBlockedWindowsNotice: "The shop cannot restart dsh on Windows yet. Quit dsh and start it again to apply the change.",
7095
7161
  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.",
@@ -7316,6 +7382,48 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7316
7382
  store.state = state;
7317
7383
  for (const listener of [...store.listeners]) listener();
7318
7384
  }
7385
+ /** sessionStorage key of the restart this tab last saw committed. Not the
7386
+ * page store: the verdict belongs to the page the reload produces, and a
7387
+ * reload clears `globalThis`. Not localStorage: it is this tab's restart, and
7388
+ * another tab never asked for it. */
7389
+ const RECORD_KEY = "dsh-plugin-shop.restart";
7390
+ function writeRecord(record) {
7391
+ try {
7392
+ globalThis.sessionStorage.setItem(RECORD_KEY, JSON.stringify(record));
7393
+ } catch {}
7394
+ }
7395
+ /** The record, or null when there is none this module could have written.
7396
+ * Every plugin on the origin shares the tab's sessionStorage, and the pid is
7397
+ * rendered into a command the reader is told to run, so each field is
7398
+ * checked again on the way back in. */
7399
+ function readRecord() {
7400
+ let raw;
7401
+ try {
7402
+ raw = globalThis.sessionStorage.getItem(RECORD_KEY);
7403
+ } catch {
7404
+ return null;
7405
+ }
7406
+ if (raw === null) return null;
7407
+ let parsed;
7408
+ try {
7409
+ parsed = JSON.parse(raw);
7410
+ } catch {
7411
+ return null;
7412
+ }
7413
+ if (typeof parsed !== "object" || parsed === null) return null;
7414
+ const { bootId, pid, logFile } = parsed;
7415
+ if (typeof bootId !== "string") return null;
7416
+ return {
7417
+ bootId,
7418
+ ...typeof pid === "number" && Number.isSafeInteger(pid) && pid > 0 ? { pid } : {},
7419
+ ...typeof logFile === "string" ? { logFile } : {}
7420
+ };
7421
+ }
7422
+ function forgetRecord() {
7423
+ try {
7424
+ globalThis.sessionStorage.removeItem(RECORD_KEY);
7425
+ } catch {}
7426
+ }
7319
7427
  /** The page's restart, or null when it has committed none. The same object
7320
7428
  * until the state changes, as `useSyncExternalStore` requires. */
7321
7429
  function readRestartMonitor() {
@@ -7348,7 +7456,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7348
7456
  const store = monitorStore();
7349
7457
  if (store.state?.kind === "restarting") return;
7350
7458
  store.stop?.();
7351
- const { reload, logFile } = options;
7459
+ const { reload, logFile, bootId, pid } = options;
7460
+ if (bootId !== void 0) writeRecord({
7461
+ bootId,
7462
+ ...pid === void 0 ? {} : { pid },
7463
+ ...logFile === void 0 ? {} : { logFile }
7464
+ });
7352
7465
  const started = Date.now();
7353
7466
  let stableSince = null;
7354
7467
  let stopped = false;
@@ -7474,7 +7587,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7474
7587
  finish(store, attempt, { kind: "started" });
7475
7588
  startRestartMonitor({
7476
7589
  reload: attempt.reload,
7477
- ...typeof result.logFile === "string" ? { logFile: result.logFile } : {}
7590
+ ...typeof result.logFile === "string" ? { logFile: result.logFile } : {},
7591
+ ...typeof result.bootId === "string" ? { bootId: result.bootId } : {},
7592
+ ...typeof result.pid === "number" ? { pid: result.pid } : {}
7478
7593
  });
7479
7594
  return;
7480
7595
  }
@@ -7504,6 +7619,34 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7504
7619
  store.attempt = null;
7505
7620
  for (const waiter of attempt.waiters) waiter(outcome);
7506
7621
  }
7622
+ /**
7623
+ * Judge this tab's last committed restart by the process answering now. The
7624
+ * client half hands every `version()` answer here (index.ts), so the verdict
7625
+ * is made on the first answer a reloaded page receives, before any tab opens.
7626
+ *
7627
+ * The same boot as the one that committed is a restart that never happened:
7628
+ * the page says so, naming that process, for as long as it keeps answering
7629
+ * — across reloads too, since the record stays. Any other answer, an older
7630
+ * host's included, is a process that replaced it, and the record is spent.
7631
+ * Nothing is judged while this page's own restart is under way: the process
7632
+ * that committed answers until it exits, two seconds later.
7633
+ */
7634
+ function noteBoot(bootId) {
7635
+ const store = monitorStore();
7636
+ if (store.state?.kind === "requesting" || store.state?.kind === "restarting") return;
7637
+ const record = readRecord();
7638
+ if (record === null) return;
7639
+ if (record.bootId === bootId) {
7640
+ if (store.state?.kind !== "not-restarted") settle(store, {
7641
+ kind: "not-restarted",
7642
+ ...record.pid === void 0 ? {} : { pid: record.pid },
7643
+ ...record.logFile === void 0 ? {} : { logFile: record.logFile }
7644
+ });
7645
+ return;
7646
+ }
7647
+ forgetRecord();
7648
+ if (store.state?.kind === "not-restarted") settle(store, null);
7649
+ }
7507
7650
  //#endregion
7508
7651
  //#region src/client/useFlows.ts
7509
7652
  /**
@@ -7577,6 +7720,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7577
7720
  return next;
7578
7721
  });
7579
7722
  }, []);
7723
+ const resetWhere = (0, react.useCallback)((predicate) => {
7724
+ setViews((current) => {
7725
+ let next = null;
7726
+ for (const [key, view] of current) {
7727
+ if (!predicate(view)) continue;
7728
+ next ??= new Map(current);
7729
+ next.delete(key);
7730
+ }
7731
+ return next ?? current;
7732
+ });
7733
+ }, []);
7580
7734
  (0, react.useEffect)(() => {
7581
7735
  const running = [];
7582
7736
  for (const [key, view] of views) if (view.kind === "running") running.push([key, view.installId]);
@@ -7635,6 +7789,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7635
7789
  reset
7636
7790
  ]),
7637
7791
  resetFlow: reset,
7792
+ resetWhere,
7638
7793
  pending
7639
7794
  };
7640
7795
  }
@@ -7777,128 +7932,1235 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7777
7932
  });
7778
7933
  }
7779
7934
  }, [updateStart]);
7935
+ const resetWhere = (0, react.useCallback)((predicate) => {
7936
+ setView((current) => predicate(current) ? { kind: "idle" } : current);
7937
+ }, []);
7780
7938
  usePollStatus(view, setView, installStatus);
7781
7939
  return {
7782
7940
  view,
7783
- start
7941
+ start,
7942
+ resetWhere
7943
+ };
7944
+ }
7945
+ //#endregion
7946
+ //#region src/shared/zh-intents.ts
7947
+ /**
7948
+ * Word-boundary (token) match: `term` appears in `text` as a standalone
7949
+ * word. A non-ASCII-alphanumeric character is a boundary, so `ai-chat` and
7950
+ * `AI聊天` hit `ai` while `email`, `main` and `pipeline` do not. The text
7951
+ * must already be lowercased; the regex is cached per term because the
7952
+ * matcher runs this for every entry on every keystroke.
7953
+ */
7954
+ const tokenRegexCache = /* @__PURE__ */ new Map();
7955
+ function tokenInText(term, text) {
7956
+ let re = tokenRegexCache.get(term);
7957
+ if (re === void 0) {
7958
+ const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7959
+ re = new RegExp(`(?:^|[^a-z0-9])${escaped}(?![a-z0-9])`, "i");
7960
+ tokenRegexCache.set(term, re);
7961
+ }
7962
+ return re.test(text);
7963
+ }
7964
+ /**
7965
+ * The curated dictionary. Ordered by design §3's measured marginal recall
7966
+ * (largest first). Rows trimmed from 2BingLing's eighty-four to eighty by
7967
+ * dropping the four whose marginal recall on the 2026-10-08 catalog was
7968
+ * single-digit: `农历节日`, `Twitter`, `微博`, `Notion`.
7969
+ *
7970
+ * To add a row: write the intent's key in Chinese, its triggers (≥ 2 Han
7971
+ * characters each, as a defensive rule, so an isolated Han inside someone
7972
+ * else's name never fires the intent), its recall terms with `#` where a
7973
+ * bare short Latin term would otherwise act like a substring, and one
7974
+ * comment line pointing at this document's measured table.
7975
+ */
7976
+ const ZH_INTENTS = [
7977
+ {
7978
+ key: "记忆",
7979
+ words: [
7980
+ "记忆",
7981
+ "记住",
7982
+ "想起"
7983
+ ],
7984
+ terms: [
7985
+ "#memory",
7986
+ "#memories",
7987
+ "#remember"
7988
+ ]
7989
+ },
7990
+ {
7991
+ key: "主题",
7992
+ words: [
7993
+ "主题",
7994
+ "皮肤",
7995
+ "外观"
7996
+ ],
7997
+ terms: [
7998
+ "#theme",
7999
+ "#skin",
8000
+ "#appearance",
8001
+ "主题",
8002
+ "皮肤",
8003
+ "外观"
8004
+ ]
8005
+ },
8006
+ {
8007
+ key: "搜索",
8008
+ words: [
8009
+ "搜索",
8010
+ "查找",
8011
+ "查找插件"
8012
+ ],
8013
+ terms: [
8014
+ "#search",
8015
+ "search-engine",
8016
+ "搜索",
8017
+ "查找"
8018
+ ]
8019
+ },
8020
+ {
8021
+ key: "浏览器",
8022
+ words: [
8023
+ "浏览器",
8024
+ "网页",
8025
+ "上网"
8026
+ ],
8027
+ terms: [
8028
+ "#browser",
8029
+ "#web",
8030
+ "浏览器",
8031
+ "网页"
8032
+ ]
8033
+ },
8034
+ {
8035
+ key: "AI大模型",
8036
+ words: [
8037
+ "大模型",
8038
+ "llm",
8039
+ "gpt",
8040
+ "智能体",
8041
+ "人工智能"
8042
+ ],
8043
+ terms: [
8044
+ "#ai",
8045
+ "#llm",
8046
+ "#gpt",
8047
+ "#agent",
8048
+ "大模型",
8049
+ "人工智能"
8050
+ ]
8051
+ },
8052
+ {
8053
+ key: "代码开发",
8054
+ words: [
8055
+ "代码",
8056
+ "编程",
8057
+ "开发"
8058
+ ],
8059
+ terms: [
8060
+ "#code",
8061
+ "#coding",
8062
+ "#dev",
8063
+ "代码",
8064
+ "编程"
8065
+ ]
8066
+ },
8067
+ {
8068
+ key: "文件管理",
8069
+ words: [
8070
+ "文件管理",
8071
+ "资源管理",
8072
+ "文件整理",
8073
+ "整理文件",
8074
+ "整理",
8075
+ "重命名"
8076
+ ],
8077
+ terms: [
8078
+ "#file",
8079
+ "#files",
8080
+ "#explorer",
8081
+ "文件",
8082
+ "#rename",
8083
+ "#renamer",
8084
+ "重命名"
8085
+ ]
8086
+ },
8087
+ {
8088
+ key: "聊天",
8089
+ words: [
8090
+ "聊天",
8091
+ "对话",
8092
+ "陪聊"
8093
+ ],
8094
+ terms: [
8095
+ "#chat",
8096
+ "#chatbot",
8097
+ "聊天",
8098
+ "对话"
8099
+ ]
8100
+ },
8101
+ {
8102
+ key: "搜索引擎",
8103
+ words: ["搜索引擎"],
8104
+ terms: [
8105
+ "#search",
8106
+ "search-engine",
8107
+ "搜索"
8108
+ ]
8109
+ },
8110
+ {
8111
+ key: "图片",
8112
+ words: [
8113
+ "图片",
8114
+ "相册",
8115
+ "壁纸",
8116
+ "水印"
8117
+ ],
8118
+ terms: [
8119
+ "#image",
8120
+ "#images",
8121
+ "#photo",
8122
+ "#wallpaper",
8123
+ "图片",
8124
+ "壁纸",
8125
+ "相册",
8126
+ "视觉理解",
8127
+ "图像识别",
8128
+ "图片问答",
8129
+ "图像处理",
8130
+ "水印"
8131
+ ]
8132
+ },
8133
+ {
8134
+ key: "待办",
8135
+ words: [
8136
+ "待办",
8137
+ "任务管理",
8138
+ "清单",
8139
+ "todo"
8140
+ ],
8141
+ terms: [
8142
+ "#todo",
8143
+ "#task",
8144
+ "#checklist",
8145
+ "待办",
8146
+ "任务",
8147
+ "清单",
8148
+ "任务看板",
8149
+ "#kanban"
8150
+ ]
8151
+ },
8152
+ {
8153
+ key: "终端",
8154
+ words: [
8155
+ "终端",
8156
+ "命令行",
8157
+ "shell"
8158
+ ],
8159
+ terms: [
8160
+ "#terminal",
8161
+ "#shell",
8162
+ "#cli",
8163
+ "终端",
8164
+ "命令行"
8165
+ ]
8166
+ },
8167
+ {
8168
+ key: "Git",
8169
+ words: [
8170
+ "git",
8171
+ "github",
8172
+ "gitee",
8173
+ "提交"
8174
+ ],
8175
+ terms: [
8176
+ "#git",
8177
+ "#github",
8178
+ "#gitee",
8179
+ "#commit",
8180
+ "版本控制"
8181
+ ]
8182
+ },
8183
+ {
8184
+ key: "系统进程",
8185
+ words: [
8186
+ "系统",
8187
+ "进程",
8188
+ "任务管理"
8189
+ ],
8190
+ terms: [
8191
+ "#system",
8192
+ "#process",
8193
+ "系统",
8194
+ "进程"
8195
+ ]
8196
+ },
8197
+ {
8198
+ key: "阅读",
8199
+ words: [
8200
+ "阅读",
8201
+ "电子书",
8202
+ "小说"
8203
+ ],
8204
+ terms: [
8205
+ "#read",
8206
+ "#reader",
8207
+ "#ebook",
8208
+ "#novel",
8209
+ "#epub",
8210
+ "阅读",
8211
+ "小说"
8212
+ ]
8213
+ },
8214
+ {
8215
+ key: "记事本",
8216
+ words: [
8217
+ "记事本",
8218
+ "便签",
8219
+ "笔记本",
8220
+ "记事",
8221
+ "笔记"
8222
+ ],
8223
+ terms: [
8224
+ "#notes",
8225
+ "#note",
8226
+ "#notepad",
8227
+ "便签",
8228
+ "笔记",
8229
+ "markdown"
8230
+ ]
8231
+ },
8232
+ {
8233
+ key: "MCP",
8234
+ words: ["mcp", "mcp服务器"],
8235
+ terms: [
8236
+ "#mcp",
8237
+ "mcp-server",
8238
+ "#mcp-manager",
8239
+ "#mcp-debug"
8240
+ ]
8241
+ },
8242
+ {
8243
+ key: "代理网络",
8244
+ words: [
8245
+ "代理",
8246
+ "梯子",
8247
+ "组网"
8248
+ ],
8249
+ terms: [
8250
+ "#proxy",
8251
+ "#network",
8252
+ "代理",
8253
+ "网络",
8254
+ "网络代理",
8255
+ "内网穿透"
8256
+ ]
8257
+ },
8258
+ {
8259
+ key: "日志",
8260
+ words: ["日志"],
8261
+ terms: [
8262
+ "#log",
8263
+ "#logs",
8264
+ "日志"
8265
+ ]
8266
+ },
8267
+ {
8268
+ key: "提醒",
8269
+ words: ["提醒", "通知"],
8270
+ terms: [
8271
+ "#remind",
8272
+ "#reminder",
8273
+ "#notification",
8274
+ "#notify",
8275
+ "提醒",
8276
+ "消息通知"
8277
+ ]
8278
+ },
8279
+ {
8280
+ key: "写作文案",
8281
+ words: [
8282
+ "写作",
8283
+ "文案",
8284
+ "改写"
8285
+ ],
8286
+ terms: [
8287
+ "#write",
8288
+ "#writing",
8289
+ "写作",
8290
+ "文案",
8291
+ "内容创作"
8292
+ ]
8293
+ },
8294
+ {
8295
+ key: "截图",
8296
+ words: [
8297
+ "截图",
8298
+ "截屏",
8299
+ "录屏",
8300
+ "屏幕录制",
8301
+ "长图"
8302
+ ],
8303
+ terms: [
8304
+ "#screenshot",
8305
+ "#capture",
8306
+ "#screen",
8307
+ "截图",
8308
+ "录屏",
8309
+ "截图工具",
8310
+ "长图"
8311
+ ]
8312
+ },
8313
+ {
8314
+ key: "提示词",
8315
+ words: ["提示词", "prompt"],
8316
+ terms: [
8317
+ "#prompt",
8318
+ "提示词管理",
8319
+ "提示词优化"
8320
+ ]
8321
+ },
8322
+ {
8323
+ key: "备份恢复",
8324
+ words: ["备份", "恢复"],
8325
+ terms: [
8326
+ "#backup",
8327
+ "#restore",
8328
+ "备份",
8329
+ "恢复"
8330
+ ]
8331
+ },
8332
+ {
8333
+ key: "压缩",
8334
+ words: ["压缩", "解压"],
8335
+ terms: [
8336
+ "#zip",
8337
+ "#compress",
8338
+ "#archive",
8339
+ "压缩",
8340
+ "解压"
8341
+ ]
8342
+ },
8343
+ {
8344
+ key: "自动化",
8345
+ words: ["自动化", "rpa"],
8346
+ terms: [
8347
+ "#automation",
8348
+ "#automate",
8349
+ "#workflow",
8350
+ "任务编排",
8351
+ "桌面自动化"
8352
+ ]
8353
+ },
8354
+ {
8355
+ key: "代码审查",
8356
+ words: [
8357
+ "审查",
8358
+ "评审",
8359
+ "review"
8360
+ ],
8361
+ terms: [
8362
+ "#review",
8363
+ "code-review",
8364
+ "审查"
8365
+ ]
8366
+ },
8367
+ {
8368
+ key: "语音合成",
8369
+ words: [
8370
+ "语音",
8371
+ "朗读",
8372
+ "tts",
8373
+ "配音",
8374
+ "声音克隆",
8375
+ "克隆声音"
8376
+ ],
8377
+ terms: [
8378
+ "#tts",
8379
+ "#speech",
8380
+ "#voice",
8381
+ "语音",
8382
+ "朗读",
8383
+ "声音克隆"
8384
+ ]
8385
+ },
8386
+ {
8387
+ key: "视频",
8388
+ words: ["视频"],
8389
+ terms: [
8390
+ "#video",
8391
+ "视频",
8392
+ "剪辑"
8393
+ ]
8394
+ },
8395
+ {
8396
+ key: "监控",
8397
+ words: ["监控"],
8398
+ terms: ["#monitor", "监控"]
8399
+ },
8400
+ {
8401
+ key: "办公文档",
8402
+ words: [
8403
+ "word",
8404
+ "excel",
8405
+ "ppt",
8406
+ "表格",
8407
+ "幻灯片",
8408
+ "演示"
8409
+ ],
8410
+ terms: [
8411
+ "#excel",
8412
+ "#word",
8413
+ "#office",
8414
+ "#xlsx",
8415
+ "表格",
8416
+ "幻灯片",
8417
+ "#ppt"
8418
+ ]
8419
+ },
8420
+ {
8421
+ key: "知识库",
8422
+ words: [
8423
+ "知识库",
8424
+ "维基",
8425
+ "wiki"
8426
+ ],
8427
+ terms: [
8428
+ "#wiki",
8429
+ "#knowledge",
8430
+ "知识库",
8431
+ "维基"
8432
+ ]
8433
+ },
8434
+ {
8435
+ key: "网盘",
8436
+ words: ["网盘", "云盘"],
8437
+ terms: [
8438
+ "#cloud",
8439
+ "#drive",
8440
+ "网盘",
8441
+ "云盘",
8442
+ "#onedrive"
8443
+ ]
8444
+ },
8445
+ {
8446
+ key: "网络诊断",
8447
+ words: [
8448
+ "ip",
8449
+ "端口",
8450
+ "ping"
8451
+ ],
8452
+ terms: [
8453
+ "#ip",
8454
+ "#port",
8455
+ "#tcp",
8456
+ "#udp",
8457
+ "#ping",
8458
+ "端口"
8459
+ ]
8460
+ },
8461
+ {
8462
+ key: "清理",
8463
+ words: [
8464
+ "清理",
8465
+ "垃圾",
8466
+ "瘦身"
8467
+ ],
8468
+ terms: [
8469
+ "#clean",
8470
+ "#cleanup",
8471
+ "清理",
8472
+ "垃圾"
8473
+ ]
8474
+ },
8475
+ {
8476
+ key: "PDF",
8477
+ words: ["pdf"],
8478
+ terms: ["#pdf"]
8479
+ },
8480
+ {
8481
+ key: "飞书钉钉",
8482
+ words: [
8483
+ "飞书",
8484
+ "钉钉",
8485
+ "lark"
8486
+ ],
8487
+ terms: [
8488
+ "#feishu",
8489
+ "#lark",
8490
+ "#dingtalk",
8491
+ "飞书",
8492
+ "钉钉"
8493
+ ]
8494
+ },
8495
+ {
8496
+ key: "数据库",
8497
+ words: ["数据库", "sql"],
8498
+ terms: [
8499
+ "#database",
8500
+ "#sql",
8501
+ "sqlite",
8502
+ "mysql",
8503
+ "数据库",
8504
+ "数据库连接"
8505
+ ]
8506
+ },
8507
+ {
8508
+ key: "日历日程",
8509
+ words: [
8510
+ "日历",
8511
+ "日程",
8512
+ "闹钟",
8513
+ "时钟"
8514
+ ],
8515
+ terms: [
8516
+ "#calendar",
8517
+ "#schedule",
8518
+ "#clock",
8519
+ "#alarm",
8520
+ "日历",
8521
+ "日程",
8522
+ "时钟"
8523
+ ]
8524
+ },
8525
+ {
8526
+ key: "音乐",
8527
+ words: [
8528
+ "音乐",
8529
+ "歌曲",
8530
+ "听歌"
8531
+ ],
8532
+ terms: [
8533
+ "#music",
8534
+ "#audio",
8535
+ "音乐"
8536
+ ]
8537
+ },
8538
+ {
8539
+ key: "机器人",
8540
+ words: ["机器人"],
8541
+ terms: [
8542
+ "#bot",
8543
+ "#robot",
8544
+ "机器人"
8545
+ ]
8546
+ },
8547
+ {
8548
+ key: "论文文献",
8549
+ words: [
8550
+ "论文",
8551
+ "文献",
8552
+ "学术"
8553
+ ],
8554
+ terms: [
8555
+ "#paper",
8556
+ "#arxiv",
8557
+ "#scholar",
8558
+ "论文",
8559
+ "文献"
8560
+ ]
8561
+ },
8562
+ {
8563
+ key: "下载",
8564
+ words: ["下载"],
8565
+ terms: [
8566
+ "#download",
8567
+ "#downloader",
8568
+ "下载"
8569
+ ]
8570
+ },
8571
+ {
8572
+ key: "健康健身",
8573
+ words: [
8574
+ "健康",
8575
+ "健身",
8576
+ "体重"
8577
+ ],
8578
+ terms: [
8579
+ "#health",
8580
+ "#fitness",
8581
+ "健康",
8582
+ "健身"
8583
+ ]
8584
+ },
8585
+ {
8586
+ key: "学习背词",
8587
+ words: [
8588
+ "学习",
8589
+ "背单词",
8590
+ "单词",
8591
+ "英语"
8592
+ ],
8593
+ terms: [
8594
+ "#learn",
8595
+ "#study",
8596
+ "#vocabulary",
8597
+ "#english",
8598
+ "学习",
8599
+ "单词",
8600
+ "英语"
8601
+ ]
8602
+ },
8603
+ {
8604
+ key: "流程图",
8605
+ words: [
8606
+ "流程图",
8607
+ "图表",
8608
+ "绘图"
8609
+ ],
8610
+ terms: [
8611
+ "#diagram",
8612
+ "#flowchart",
8613
+ "#mermaid",
8614
+ "流程图",
8615
+ "图表"
8616
+ ]
8617
+ },
8618
+ {
8619
+ key: "报告生成",
8620
+ words: [
8621
+ "报告生成",
8622
+ "周报",
8623
+ "日报"
8624
+ ],
8625
+ terms: [
8626
+ "#report",
8627
+ "报告生成",
8628
+ "周报",
8629
+ "日报"
8630
+ ]
8631
+ },
8632
+ {
8633
+ key: "桌宠",
8634
+ words: ["桌宠", "桌面宠物"],
8635
+ terms: ["#desktop-pet", "桌宠"]
8636
+ },
8637
+ {
8638
+ key: "记账",
8639
+ words: [
8640
+ "记账",
8641
+ "账单",
8642
+ "花销"
8643
+ ],
8644
+ terms: [
8645
+ "#expense",
8646
+ "#billing",
8647
+ "记账",
8648
+ "账单"
8649
+ ]
8650
+ },
8651
+ {
8652
+ key: "微信",
8653
+ words: [
8654
+ "微信",
8655
+ "公众号",
8656
+ "wechat"
8657
+ ],
8658
+ terms: [
8659
+ "wechat",
8660
+ "weixin",
8661
+ "微信",
8662
+ "公众号"
8663
+ ]
8664
+ },
8665
+ {
8666
+ key: "股票基金",
8667
+ words: [
8668
+ "股票",
8669
+ "基金",
8670
+ "a股",
8671
+ "行情",
8672
+ "炒股"
8673
+ ],
8674
+ terms: [
8675
+ "#stock",
8676
+ "#finance",
8677
+ "股票",
8678
+ "基金",
8679
+ "行情"
8680
+ ]
8681
+ },
8682
+ {
8683
+ key: "语音识别",
8684
+ words: [
8685
+ "语音识别",
8686
+ "听写",
8687
+ "whisper"
8688
+ ],
8689
+ terms: [
8690
+ "#whisper",
8691
+ "#asr",
8692
+ "#stt",
8693
+ "语音识别",
8694
+ "听写",
8695
+ "语音输入"
8696
+ ]
8697
+ },
8698
+ {
8699
+ key: "白板",
8700
+ words: ["白板", "画布"],
8701
+ terms: [
8702
+ "#whiteboard",
8703
+ "#canvas",
8704
+ "白板",
8705
+ "画布"
8706
+ ]
8707
+ },
8708
+ {
8709
+ key: "计算换算",
8710
+ words: [
8711
+ "计算器",
8712
+ "换算",
8713
+ "单位",
8714
+ "格式转换"
8715
+ ],
8716
+ terms: [
8717
+ "#calculator",
8718
+ "#convert",
8719
+ "计算器",
8720
+ "换算",
8721
+ "数学计算"
8722
+ ]
8723
+ },
8724
+ {
8725
+ key: "番茄钟",
8726
+ words: [
8727
+ "番茄钟",
8728
+ "专注",
8729
+ "计时"
8730
+ ],
8731
+ terms: [
8732
+ "#pomodoro",
8733
+ "#focus",
8734
+ "#timer",
8735
+ "番茄钟",
8736
+ "专注",
8737
+ "计时"
8738
+ ]
8739
+ },
8740
+ {
8741
+ key: "游戏",
8742
+ words: ["游戏", "摸鱼"],
8743
+ terms: [
8744
+ "#game",
8745
+ "#games",
8746
+ "游戏"
8747
+ ]
8748
+ },
8749
+ {
8750
+ key: "绘画",
8751
+ words: [
8752
+ "画图",
8753
+ "绘画",
8754
+ "生图",
8755
+ "文生图",
8756
+ "画"
8757
+ ],
8758
+ terms: [
8759
+ "#draw",
8760
+ "#drawing",
8761
+ "stable-diffusion",
8762
+ "#image-generation",
8763
+ "绘画",
8764
+ "生图"
8765
+ ]
8766
+ },
8767
+ {
8768
+ key: "RSS订阅",
8769
+ words: ["rss", "订阅"],
8770
+ terms: [
8771
+ "#rss",
8772
+ "#feed",
8773
+ "订阅"
8774
+ ]
8775
+ },
8776
+ {
8777
+ key: "多模态",
8778
+ words: ["多模态"],
8779
+ terms: [
8780
+ "#multimodal",
8781
+ "#multi-modal",
8782
+ "多模态"
8783
+ ]
8784
+ },
8785
+ {
8786
+ key: "OCR",
8787
+ words: ["ocr", "文字识别"],
8788
+ terms: ["#ocr", "文字识别"]
8789
+ },
8790
+ {
8791
+ key: "密码",
8792
+ words: ["密码"],
8793
+ terms: ["#password", "密码"]
8794
+ },
8795
+ {
8796
+ key: "翻译",
8797
+ words: ["翻译", "translate"],
8798
+ terms: [
8799
+ "#translate",
8800
+ "#translation",
8801
+ "翻译"
8802
+ ]
8803
+ },
8804
+ {
8805
+ key: "新闻资讯",
8806
+ words: [
8807
+ "新闻",
8808
+ "资讯",
8809
+ "头条",
8810
+ "热搜"
8811
+ ],
8812
+ terms: [
8813
+ "#news",
8814
+ "#feed",
8815
+ "#trending",
8816
+ "新闻",
8817
+ "资讯",
8818
+ "热搜",
8819
+ "新闻聚合"
8820
+ ]
8821
+ },
8822
+ {
8823
+ key: "简历",
8824
+ words: ["简历"],
8825
+ terms: [
8826
+ "#resume",
8827
+ "#cv",
8828
+ "简历"
8829
+ ]
8830
+ },
8831
+ {
8832
+ key: "邮件",
8833
+ words: ["邮件", "邮箱"],
8834
+ terms: [
8835
+ "#mail",
8836
+ "#email",
8837
+ "#smtp",
8838
+ "邮件"
8839
+ ]
8840
+ },
8841
+ {
8842
+ key: "随机抽签",
8843
+ words: [
8844
+ "随机",
8845
+ "抽签",
8846
+ "骰子"
8847
+ ],
8848
+ terms: [
8849
+ "#random",
8850
+ "#dice",
8851
+ "随机"
8852
+ ]
8853
+ },
8854
+ {
8855
+ key: "播放器",
8856
+ words: ["播放器"],
8857
+ terms: ["#player", "播放器"]
8858
+ },
8859
+ {
8860
+ key: "爬虫",
8861
+ words: ["爬虫", "抓取"],
8862
+ terms: [
8863
+ "#crawler",
8864
+ "#scraper",
8865
+ "#spider",
8866
+ "爬虫",
8867
+ "抓取"
8868
+ ]
8869
+ },
8870
+ {
8871
+ key: "加密",
8872
+ words: ["加密", "解密"],
8873
+ terms: [
8874
+ "#encrypt",
8875
+ "#crypto",
8876
+ "加密"
8877
+ ]
8878
+ },
8879
+ {
8880
+ key: "视频下载",
8881
+ words: [
8882
+ "视频下载",
8883
+ "youtube",
8884
+ "油管",
8885
+ "b站",
8886
+ "bilibili",
8887
+ "哔哩"
8888
+ ],
8889
+ terms: [
8890
+ "youtube",
8891
+ "bilibili",
8892
+ "yt-dlp",
8893
+ "哔站"
8894
+ ]
8895
+ },
8896
+ {
8897
+ key: "Telegram",
8898
+ words: ["telegram"],
8899
+ terms: ["telegram"]
8900
+ },
8901
+ {
8902
+ key: "数据分析",
8903
+ words: ["数据分析", "数据统计"],
8904
+ terms: [
8905
+ "#data-analysis",
8906
+ "#analytics",
8907
+ "数据分析",
8908
+ "数据统计"
8909
+ ]
8910
+ },
8911
+ {
8912
+ key: "问卷投票",
8913
+ words: [
8914
+ "问卷",
8915
+ "投票",
8916
+ "抽奖"
8917
+ ],
8918
+ terms: [
8919
+ "#survey",
8920
+ "#poll",
8921
+ "#quiz",
8922
+ "问卷",
8923
+ "投票",
8924
+ "抽奖"
8925
+ ]
8926
+ },
8927
+ {
8928
+ key: "Obsidian",
8929
+ words: ["obsidian"],
8930
+ terms: ["obsidian"]
8931
+ },
8932
+ {
8933
+ key: "剪贴板",
8934
+ words: ["剪贴板"],
8935
+ terms: ["#clipboard", "剪贴板"]
8936
+ },
8937
+ {
8938
+ key: "加密货币",
8939
+ words: [
8940
+ "比特币",
8941
+ "加密货币",
8942
+ "币圈"
8943
+ ],
8944
+ terms: [
8945
+ "#bitcoin",
8946
+ "#crypto",
8947
+ "#blockchain",
8948
+ "加密货币"
8949
+ ]
8950
+ },
8951
+ {
8952
+ key: "天气",
8953
+ words: ["天气"],
8954
+ terms: ["#weather", "天气"]
8955
+ },
8956
+ {
8957
+ key: "汇率",
8958
+ words: ["汇率", "货币"],
8959
+ terms: [
8960
+ "#exchange",
8961
+ "#currency",
8962
+ "汇率"
8963
+ ]
8964
+ },
8965
+ {
8966
+ key: "思维导图",
8967
+ words: ["思维导图"],
8968
+ terms: [
8969
+ "#mindmap",
8970
+ "#mind",
8971
+ "思维导图"
8972
+ ]
8973
+ },
8974
+ {
8975
+ key: "字幕",
8976
+ words: ["字幕"],
8977
+ terms: ["#subtitle", "字幕"]
8978
+ },
8979
+ {
8980
+ key: "娱乐",
8981
+ words: [
8982
+ "娱乐",
8983
+ "段子",
8984
+ "笑话"
8985
+ ],
8986
+ terms: [
8987
+ "#fun",
8988
+ "#joke",
8989
+ "#meme",
8990
+ "娱乐",
8991
+ "段子"
8992
+ ]
8993
+ },
8994
+ {
8995
+ key: "面试",
8996
+ words: ["面试"],
8997
+ terms: ["#interview", "面试"]
8998
+ },
8999
+ {
9000
+ key: "Discord",
9001
+ words: ["discord"],
9002
+ terms: ["discord"]
9003
+ }
9004
+ ];
9005
+ /**
9006
+ * Fire every intent whose trigger appears in the query (lowercase, substring),
9007
+ * deduplicate the recall vocabulary, and return the query itself first.
9008
+ *
9009
+ * The call site never has to know whether expansion happened: `terms` is
9010
+ * always at least `[query]`. The caller joins these with OR against each
9011
+ * entry's searchable fields, in the same `includes`/`tokenInText` grammar
9012
+ * as the raw query.
9013
+ */
9014
+ function expandZhQuery(rawQuery) {
9015
+ const query = (rawQuery ?? "").trim().toLowerCase();
9016
+ if (query === "") return {
9017
+ query: "",
9018
+ intents: [],
9019
+ expansions: [],
9020
+ terms: [""]
9021
+ };
9022
+ const intents = [];
9023
+ const expansions = [];
9024
+ const terms = /* @__PURE__ */ new Set();
9025
+ for (const it of ZH_INTENTS) {
9026
+ if (!it.words.some((w) => query.includes(w))) continue;
9027
+ intents.push(it.key);
9028
+ const added = [...new Set(it.terms)].filter((t) => {
9029
+ const lower = t.toLowerCase();
9030
+ return lower !== "" && lower !== query && !terms.has(lower) && !terms.has(t);
9031
+ });
9032
+ for (const t of added) terms.add(t);
9033
+ expansions.push({
9034
+ intent: it.key,
9035
+ terms: added
9036
+ });
9037
+ }
9038
+ return {
9039
+ query,
9040
+ intents,
9041
+ expansions,
9042
+ terms: [query, ...terms]
7784
9043
  };
7785
9044
  }
7786
9045
  //#endregion
7787
- //#region \0dsh-plugin-shop-css:627ddf51
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.sdb5735_client.sa77885_config.s462e50_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.sdb5735_client.sd5d636_spec.s462e50_ts` pins this. */\n\n.s61c8a3_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 `.sdd33f6_searchInput` sits at\n * exactly (0, 0) of this box — it is the first child of `.s3684ff_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.sc09ffe_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.s5293ee_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.sbd5adb_skeletonGrid {\n display: grid;\n grid-template-columns: 1fr;\n gap: 8px;\n}\n\n.s35b119_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.s35b119_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.s4877cb_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.s74236b_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.s75d872_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.sf95048_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.s35b119_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 .s35b119_skeletonCard span { animation: none; opacity: 0.75; }\n .s35b119_skeletonCard::after { content: none; }\n}\n\n.s2c1a05_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.s2c1a05_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.s2c1a05_actionButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s3684ff_toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.sc71852_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.s5ce118_versionBlock {\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.s9345af_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.sf60373_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.sf60373_updateSelfButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 20%, transparent);\n}\n\n.sf60373_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.s85d95b_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.s85d95b_checkUpdateButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n color: var(--dsw-alias-label-primary);\n}\n\n.s85d95b_checkUpdateButton:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.s85d95b_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.s5d0778_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.s5d0778_githubLink:hover {\n color: var(--dsw-alias-label-primary);\n background: var(--dsw-alias-bg-layer-2);\n}\n\n.s5d0778_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.s82fd39_selfUpdatePanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 8px;\n}\n\n.sdd33f6_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.sdd33f6_searchInput:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s327f87_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.s5a84d4_categoryBar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 6px;\n margin-top: 6px;\n}\n\n.sb8b87d_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.sb8b87d_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 `.sb8b87d_categoryButton.s18618e_categoryButtonOn` — TWO classes — so it\n * matches `.sb8b87d_categoryButton:hover`'s specificity and, coming after it, wins.\n * As a bare `.s18618e_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.sdb5735_client.sd5d636_spec.s462e50_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.sb8b87d_categoryButton.s18618e_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 .sb8b87d_categoryButton.s18618e_categoryButtonOn {\n outline: 2px solid ButtonText;\n outline-offset: -4px;\n }\n}\n\n.sb8b87d_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 `.sb8b87d_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.sd6cb42_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: `.sd6cb42_incompatibleFilter:hover .saaea46_switch` outranks `.sd9eed4_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.sd6cb42_incompatibleFilter:hover {\n color: var(--dsw-alias-label-primary);\n}\n\n.sd6cb42_incompatibleFilter:hover .saaea46_switch:not(.sd9eed4_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 `.sb8b87d_categoryButton.s18618e_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.sd6cb42_incompatibleFilter.sad5334_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.sd6cb42_incompatibleFilter .saaea46_switch:not(.sd9eed4_switchOn) .seaa2dc_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.sd6cb42_incompatibleFilter:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 2px;\n border-radius: 999px;\n}\n\n.sde2f0d_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.sa33481_catalogStatsRow {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 6px 0 0;\n}\n\n.s4a0bd4_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 `.s2c1a05_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.sc86217_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.sc86217_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.sc86217_catalogRefreshButton:disabled {\n cursor: default;\n opacity: 0.6;\n}\n\n.sc86217_catalogRefreshButton:focus-visible {\n outline: 2px solid var(--dsw-alias-brand-primary);\n outline-offset: 1px;\n}\n\n.s7a8776_catalogRefreshFailed {\n font-size: 11px;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s1120fc_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.sef40c8_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 .sef40c8_cards { animation: none; }\n}\n\n.sda151f_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.s22a6a9_cardsSentry {\n height: 1px;\n margin: 0;\n padding: 0;\n}\n\n.sec1f7d_showingLine {\n margin: 0;\n font-size: 12px;\n letter-spacing: 0.02em;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sda151f_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 .sda151f_card { transition: none; }\n .sda151f_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.saa352e_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. `.sb8b87d_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.sda151f_card[data-category='tool'], .sb8b87d_categoryButton[data-category='tool'] { --category-hue: #4C8DFF; }\n.sda151f_card[data-category='provider'], .sb8b87d_categoryButton[data-category='provider'] { --category-hue: #A78BFA; }\n.sda151f_card[data-category='ui'], .sb8b87d_categoryButton[data-category='ui'] { --category-hue: #2DD4BF; }\n.sda151f_card[data-category='workflow'], .sb8b87d_categoryButton[data-category='workflow'] { --category-hue: #F59E0B; }\n.sda151f_card[data-category='integration'], .sb8b87d_categoryButton[data-category='integration'] { --category-hue: #34D399; }\n.sda151f_card[data-category='theme'], .sb8b87d_categoryButton[data-category='theme'] { --category-hue: #F472B6; }\n.sda151f_card[data-category='other'], .sb8b87d_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.sde8d65_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.s9a4741_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.s9a4741_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.s86793e_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.s9fd7a8_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.scb6ea7_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.scb6ea7_cardActions > .s1d9b49_gate,\n.scb6ea7_cardActions > .s55951e_installPanel,\n.scb6ea7_cardActions > .s70ba39_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.s99fcd8_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.se3f041_sourceBadge {\n display: inline-flex;\n align-items: center;\n color: var(--dsw-alias-label-secondary);\n}\n\n.scbdeae_tierBadge {\n padding: 1px 7px;\n border-radius: 999px;\n font-size: 11px;\n line-height: 1.6;\n}\n\n.scbdeae_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.scbdeae_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.scbdeae_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.s888f2a_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.s738616_starsBadge {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s4866d4_chevron, .sfa79fc_chevronOpen {\n color: var(--dsw-alias-label-secondary);\n transition: transform 120ms ease;\n}\n\n.sfa79fc_chevronOpen { transform: rotate(180deg); }\n\n@media (prefers-reduced-motion: reduce) {\n .s4866d4_chevron, .sfa79fc_chevronOpen { transition: none; }\n}\n\n.sbc4ab9_body {\n display: flex;\n flex-direction: column;\n gap: 8px;\n padding: 0 12px;\n}\n\n.s86ccc3_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.sdbbc60_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.sfd6f7e_summaryExpanded {\n -webkit-line-clamp: unset;\n display: block;\n}\n\n.sd8f966_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.sc61f12_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.s7925b1_remedy {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.sc8866b_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.s9bd892_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.s9612af_refusal {\n display: flex;\n flex-direction: column;\n gap: 6px;\n flex-basis: 100%;\n}\n\n.s9138b6_capabilitiesBlock {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.s8a9920_capabilitiesNote {\n margin: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.sb90bfe_capabilities {\n display: flex;\n flex-wrap: wrap;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.sb90bfe_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.sf0358c_detail {\n border-top: 1px solid var(--dsw-alias-border-l1);\n padding-top: 8px;\n}\n\n.secfa90_detailRows {\n display: flex;\n flex-direction: column;\n gap: 4px;\n margin: 0;\n}\n\n.s64fa44_detailRow {\n display: flex;\n justify-content: space-between;\n gap: 12px;\n font-size: 12px;\n}\n\n.s64fa44_detailRow dt {\n color: var(--dsw-alias-label-secondary);\n}\n\n.s64fa44_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.s64fa44_detailRow a {\n color: var(--dsw-alias-brand-primary);\n text-decoration: none;\n}\n\n.s64fa44_detailRow a:hover {\n text-decoration: underline;\n}\n\n.s64fa44_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 * `.scb6ea7_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.s3cb9f0_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: `.s3cb9f0_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.sd5e1c9_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 `.s3cb9f0_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 `.s3cb9f0_cardMeta`. There is no middot:\n * `.s3cb9f0_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.s1cd598_author {\n color: var(--dsw-alias-label-secondary);\n font-size: 12px;\n}\n\n.se81873_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.s55951e_installPanel {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 2px;\n}\n\n.s30c7c1_installButton, .sdf53e9_confirmButton, .sfe6376_cancelButton {\n align-self: flex-start;\n padding: 5px 14px;\n border-radius: 6px;\n font-size: 13px;\n cursor: pointer;\n}\n\n.s30c7c1_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.s30c7c1_installButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.sdf53e9_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.sdf53e9_confirmButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-brand-primary) 88%, var(--dsw-alias-label-primary));\n}\n\n.sfe6376_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.sfe6376_cancelButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.s30c7c1_installButton:focus-visible, .sdf53e9_confirmButton:focus-visible, .sfe6376_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.sc51ddc_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.s70ba39_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.s7a7545_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.s7a7545_uninstallButton:hover {\n background: color-mix(in srgb, var(--dsw-alias-state-error-primary) 10%, transparent);\n}\n\n.s7a7545_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.s888e38_restartSelfButton {\n /* The version row's third state. Geometry matches .s85d95b_checkUpdateButton and\n * .sf60373_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.s888e38_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.sb72afe_restartButton,\n.sada3ca_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.sb72afe_restartButton:hover,\n.sada3ca_reloadButton:hover {\n border-color: var(--dsw-alias-label-secondary);\n}\n\n.sb72afe_restartButton:focus-visible,\n.sada3ca_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 * `.s70ba39_installedActions` the way the restart gate is — a column of its own\n * rather than inline with the row. */\n.s5e909c_reloadPanel {\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 6px;\n}\n\n\n.s1d9b49_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.sf297e2_gateTitle {\n margin: 0;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-state-warn-primary);\n}\n\n.s355722_gateBody {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.sad7d6e_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.s4c3412_gateActions {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.s8107d5_installing {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s8459f6_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.sd2b885_logLine {\n white-space: pre-wrap;\n word-break: break-all;\n color: var(--dsw-alias-label-secondary);\n}\n\n.s838f51_notice {\n margin: 0;\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary);\n}\n\n.sf7a8b1_failedHeading {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.s13de59_failedDetail {\n margin: 0;\n font-size: 12px;\n line-height: 1.6;\n color: var(--dsw-alias-label-primary);\n}\n\n.s2b86d5_rejectedCode {\n margin: 0;\n font-size: 12px;\n font-weight: 600;\n color: var(--dsw-alias-state-error-primary);\n}\n\n.s62d32b_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.sa62a7c_outdatedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.sc2080f_outdatedList {\n display: flex;\n flex-direction: column;\n gap: 8px;\n list-style: none;\n margin: 0;\n padding: 0;\n}\n\n.s7ec61a_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.sf5f2cb_outdatedInfo {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.sf5f2cb_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.sb5d2f9_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 .s9fd7a8_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.se09e8b_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: `.sd6cb42_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 `.saaea46_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.saaea46_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.sd9eed4_switchOn {\n border-color: var(--track-hue);\n background: color-mix(in srgb, var(--track-hue) 55%, transparent);\n}\n\n.seaa2dc_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.sd9eed4_switchOn .seaa2dc_switchKnob {\n transform: translateX(14px);\n background: var(--dsw-alias-bg-base);\n}\n\n.saaea46_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 .saaea46_switch, .seaa2dc_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.s514551_switchWrap {\n display: inline-flex;\n align-items: center;\n /* Same reason as `.se09e8b_outdatedActions`: `.s5e909c_reloadPanel`'s own comment calls\n * itself \"a column of its own rather than inline with the row\", which held\n * for `.s70ba39_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=\"627ddf51\"]")) {
9046
+ //#region \0dsh-plugin-shop-css:e3675a89
9047
+ 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/* Subdued line under the search box when a dictionary intent fired (design\n * 2026-10-08-search-query-expansion §4). One fact, no badge. */\n.s76fb13_expansionNote {\n margin: 4px 0 0;\n font-size: 12px;\n color: var(--dsw-alias-label-secondary);\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/* The §C10 shadowed badge: an installed github entry the catalog refuses\n * shadowed-by-npm. Subdued/secondary rather than a warn tint — nothing is\n * wrong with the install itself; the shelf just cannot show it. */\n.s5e82cb_tierBadge[data-tier='shadowed'] {\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n\n/* The §C5 archived badge: a github repository the owner has frozen.\n * Subdued register, same as shadowed — nothing is wrong with the install;\n * the pinned commit still serves, so the mark is informational only. */\n.s5e82cb_tierBadge[data-tier='archived'] {\n color: var(--dsw-alias-label-secondary);\n background: var(--dsw-alias-bg-layer-2);\n border: 1px solid var(--dsw-alias-border-l2);\n}\n\n.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/* The way out of dsh's own refusal: the lead-in, then the exemption command\n * on its own line. */\n.s3d0044_remedy {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.sdf203f_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.sc32335_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.sc74941_refusal {\n display: flex;\n flex-direction: column;\n gap: 6px;\n flex-basis: 100%;\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/* The §C10 shadowed section. Reuses the outdated list's visual language; only\n * the section spacing and the per-row explanation paragraph are its own. */\n.s6c4649_shadowedSection {\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-top: 6px;\n}\n\n.scc2a21_shadowedDetail {\n margin: 4px 0 0;\n font-size: 12px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary);\n}\n\n.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";
9048
+ if (typeof document !== "undefined" && !document.querySelector("style[data-plugin-css=\"e3675a89\"]")) {
7790
9049
  const tag = document.createElement("style");
7791
- tag.dataset.pluginCss = "627ddf51";
9050
+ tag.dataset.pluginCss = "e3675a89";
7792
9051
  tag.textContent = css;
7793
9052
  document.head.appendChild(tag);
7794
9053
  }
7795
- var _dsh_plugin_shop_css_627ddf51_default = {
7796
- "client": "sdb5735_client",
7797
- "config": "sa77885_config",
7798
- "ts": "s462e50_ts",
7799
- "spec": "sd5d636_spec",
7800
- "panel": "s61c8a3_panel",
7801
- "searchInput": "sdd33f6_searchInput",
7802
- "toolbar": "s3684ff_toolbar",
7803
- "stateLine": "sc09ffe_stateLine",
7804
- "srOnly": "s5293ee_srOnly",
7805
- "skeletonGrid": "sbd5adb_skeletonGrid",
7806
- "skeletonCard": "s35b119_skeletonCard",
7807
- "skeletonName": "s4877cb_skeletonName",
7808
- "skeletonSummary": "s74236b_skeletonSummary",
7809
- "skeletonSummaryShort": "s75d872_skeletonSummaryShort",
7810
- "skeletonActions": "sf95048_skeletonActions",
7811
- "actionButton": "s2c1a05_actionButton",
7812
- "toolbarRight": "sc71852_toolbarRight",
7813
- "versionBlock": "s5ce118_versionBlock",
7814
- "versionText": "s9345af_versionText",
7815
- "updateSelfButton": "sf60373_updateSelfButton",
7816
- "checkUpdateButton": "s85d95b_checkUpdateButton",
7817
- "githubLink": "s5d0778_githubLink",
7818
- "selfUpdatePanel": "s82fd39_selfUpdatePanel",
7819
- "staleBadge": "s327f87_staleBadge",
7820
- "categoryBar": "s5a84d4_categoryBar",
7821
- "categoryButton": "sb8b87d_categoryButton",
7822
- "categoryButtonOn": "s18618e_categoryButtonOn",
7823
- "incompatibleFilter": "sd6cb42_incompatibleFilter",
7824
- "switch": "saaea46_switch",
7825
- "switchOn": "sd9eed4_switchOn",
7826
- "incompatibleFilterOn": "sad5334_incompatibleFilterOn",
7827
- "switchKnob": "seaa2dc_switchKnob",
7828
- "updatableHeading": "sde2f0d_updatableHeading",
7829
- "catalogStatsRow": "sa33481_catalogStatsRow",
7830
- "catalogStats": "s4a0bd4_catalogStats",
7831
- "catalogRefreshButton": "sc86217_catalogRefreshButton",
7832
- "catalogRefreshFailed": "s7a8776_catalogRefreshFailed",
7833
- "emptyLine": "s1120fc_emptyLine",
7834
- "cards": "sef40c8_cards",
7835
- "card": "sda151f_card",
7836
- "cardsSentry": "s22a6a9_cardsSentry",
7837
- "showingLine": "sec1f7d_showingLine",
7838
- "cardSpine": "saa352e_cardSpine",
7839
- "categoryBadge": "sde8d65_categoryBadge",
7840
- "entryHeader": "s9a4741_entryHeader",
7841
- "name": "s86793e_name",
7842
- "badges": "s9fd7a8_badges",
7843
- "cardActions": "scb6ea7_cardActions",
7844
- "gate": "s1d9b49_gate",
7845
- "installPanel": "s55951e_installPanel",
7846
- "installedActions": "s70ba39_installedActions",
7847
- "cardVersion": "s99fcd8_cardVersion",
7848
- "sourceBadge": "se3f041_sourceBadge",
7849
- "tierBadge": "scbdeae_tierBadge",
7850
- "incompatibleBadge": "s888f2a_incompatibleBadge",
7851
- "starsBadge": "s738616_starsBadge",
7852
- "chevron": "s4866d4_chevron",
7853
- "chevronOpen": "sfa79fc_chevronOpen",
7854
- "body": "sbc4ab9_body",
7855
- "summary": "s86ccc3_summary",
7856
- "summaryZh": "sdbbc60_summaryZh",
7857
- "summaryExpanded": "sfd6f7e_summaryExpanded",
7858
- "summaryZhExpanded": "sd8f966_summaryZhExpanded",
7859
- "incompatibleDetail": "sc61f12_incompatibleDetail",
7860
- "remedy": "s7925b1_remedy",
7861
- "remedyLead": "sc8866b_remedyLead",
7862
- "allowCommand": "s9bd892_allowCommand",
7863
- "refusal": "s9612af_refusal",
7864
- "capabilitiesBlock": "s9138b6_capabilitiesBlock",
7865
- "capabilitiesNote": "s8a9920_capabilitiesNote",
7866
- "capabilities": "sb90bfe_capabilities",
7867
- "detail": "sf0358c_detail",
7868
- "detailRows": "secfa90_detailRows",
7869
- "detailRow": "s64fa44_detailRow",
7870
- "cardMeta": "s3cb9f0_cardMeta",
7871
- "size": "sd5e1c9_size",
7872
- "author": "s1cd598_author",
7873
- "reviewedLine": "se81873_reviewedLine",
7874
- "installButton": "s30c7c1_installButton",
7875
- "confirmButton": "sdf53e9_confirmButton",
7876
- "cancelButton": "sfe6376_cancelButton",
7877
- "installedLabel": "sc51ddc_installedLabel",
7878
- "uninstallButton": "s7a7545_uninstallButton",
7879
- "restartSelfButton": "s888e38_restartSelfButton",
7880
- "restartButton": "sb72afe_restartButton",
7881
- "reloadButton": "sada3ca_reloadButton",
7882
- "reloadPanel": "s5e909c_reloadPanel",
7883
- "gateTitle": "sf297e2_gateTitle",
7884
- "gateBody": "s355722_gateBody",
7885
- "gateWarning": "sad7d6e_gateWarning",
7886
- "gateActions": "s4c3412_gateActions",
7887
- "installing": "s8107d5_installing",
7888
- "log": "s8459f6_log",
7889
- "logLine": "sd2b885_logLine",
7890
- "notice": "s838f51_notice",
7891
- "failedHeading": "sf7a8b1_failedHeading",
7892
- "failedDetail": "s13de59_failedDetail",
7893
- "rejectedCode": "s2b86d5_rejectedCode",
7894
- "rejectedDetail": "s62d32b_rejectedDetail",
7895
- "outdatedSection": "sa62a7c_outdatedSection",
7896
- "outdatedList": "sc2080f_outdatedList",
7897
- "outdatedRow": "s7ec61a_outdatedRow",
7898
- "outdatedInfo": "sf5f2cb_outdatedInfo",
7899
- "outdatedVersions": "sb5d2f9_outdatedVersions",
7900
- "outdatedActions": "se09e8b_outdatedActions",
7901
- "switchWrap": "s514551_switchWrap"
9054
+ var _dsh_plugin_shop_css_e3675a89_default = {
9055
+ "client": "s8583e3_client",
9056
+ "config": "s942a79_config",
9057
+ "ts": "s5f67aa_ts",
9058
+ "spec": "sfd9d8a_spec",
9059
+ "panel": "s255e5a_panel",
9060
+ "searchInput": "s232ec4_searchInput",
9061
+ "toolbar": "s646509_toolbar",
9062
+ "stateLine": "s44a3ec_stateLine",
9063
+ "srOnly": "s3a0ece_srOnly",
9064
+ "skeletonGrid": "se0c3b4_skeletonGrid",
9065
+ "skeletonCard": "s520416_skeletonCard",
9066
+ "skeletonName": "s8c7d6f_skeletonName",
9067
+ "skeletonSummary": "sf5179e_skeletonSummary",
9068
+ "skeletonSummaryShort": "sbb0d25_skeletonSummaryShort",
9069
+ "skeletonActions": "s96e910_skeletonActions",
9070
+ "actionButton": "s73242a_actionButton",
9071
+ "toolbarRight": "s745e3c_toolbarRight",
9072
+ "versionBlock": "s4fe61f_versionBlock",
9073
+ "versionText": "sb9b924_versionText",
9074
+ "updateSelfButton": "sa04455_updateSelfButton",
9075
+ "checkUpdateButton": "s614a38_checkUpdateButton",
9076
+ "githubLink": "sa987f7_githubLink",
9077
+ "selfUpdatePanel": "sb81ba5_selfUpdatePanel",
9078
+ "expansionNote": "s76fb13_expansionNote",
9079
+ "staleBadge": "s3946f1_staleBadge",
9080
+ "categoryBar": "sb2c859_categoryBar",
9081
+ "categoryButton": "s26d58e_categoryButton",
9082
+ "categoryButtonOn": "s5f68c4_categoryButtonOn",
9083
+ "incompatibleFilter": "sfd887b_incompatibleFilter",
9084
+ "switch": "s11c019_switch",
9085
+ "switchOn": "s7580e8_switchOn",
9086
+ "incompatibleFilterOn": "s6e4cba_incompatibleFilterOn",
9087
+ "switchKnob": "saab2b3_switchKnob",
9088
+ "updatableHeading": "sebf017_updatableHeading",
9089
+ "catalogStatsRow": "seb0a1e_catalogStatsRow",
9090
+ "catalogStats": "sccc4d1_catalogStats",
9091
+ "catalogRefreshButton": "sff243d_catalogRefreshButton",
9092
+ "catalogRefreshFailed": "s194168_catalogRefreshFailed",
9093
+ "emptyLine": "s6de1ab_emptyLine",
9094
+ "cards": "sf657f5_cards",
9095
+ "card": "s65f383_card",
9096
+ "cardsSentry": "sd7ee01_cardsSentry",
9097
+ "showingLine": "s705cc6_showingLine",
9098
+ "cardSpine": "s4ce25d_cardSpine",
9099
+ "categoryBadge": "s9c8eaa_categoryBadge",
9100
+ "entryHeader": "sa97234_entryHeader",
9101
+ "name": "sc3f149_name",
9102
+ "badges": "s513026_badges",
9103
+ "cardActions": "s9b5048_cardActions",
9104
+ "gate": "sc4f2ac_gate",
9105
+ "installPanel": "s4d374c_installPanel",
9106
+ "installedActions": "sf53ca0_installedActions",
9107
+ "cardVersion": "sc6ec6e_cardVersion",
9108
+ "sourceBadge": "s71f10f_sourceBadge",
9109
+ "tierBadge": "s5e82cb_tierBadge",
9110
+ "incompatibleBadge": "s4bbe02_incompatibleBadge",
9111
+ "starsBadge": "sff43b4_starsBadge",
9112
+ "chevron": "s73f7fb_chevron",
9113
+ "chevronOpen": "s61393c_chevronOpen",
9114
+ "body": "s820ebf_body",
9115
+ "summary": "sb8fbc8_summary",
9116
+ "summaryZh": "se507db_summaryZh",
9117
+ "summaryExpanded": "s10baf0_summaryExpanded",
9118
+ "summaryZhExpanded": "sb9dba9_summaryZhExpanded",
9119
+ "incompatibleDetail": "s6eca19_incompatibleDetail",
9120
+ "remedy": "s3d0044_remedy",
9121
+ "remedyLead": "sdf203f_remedyLead",
9122
+ "allowCommand": "sc32335_allowCommand",
9123
+ "refusal": "sc74941_refusal",
9124
+ "capabilitiesBlock": "s9d2029_capabilitiesBlock",
9125
+ "capabilitiesNote": "s3bdbd5_capabilitiesNote",
9126
+ "capabilities": "s441702_capabilities",
9127
+ "detail": "sde3f0c_detail",
9128
+ "detailRows": "se98179_detailRows",
9129
+ "detailRow": "sfb4d2f_detailRow",
9130
+ "cardMeta": "s5fbaf9_cardMeta",
9131
+ "size": "s2d3c05_size",
9132
+ "author": "s1ff66f_author",
9133
+ "reviewedLine": "sd58c6d_reviewedLine",
9134
+ "installButton": "se7aaed_installButton",
9135
+ "confirmButton": "sb43a49_confirmButton",
9136
+ "cancelButton": "s6e45d1_cancelButton",
9137
+ "installedLabel": "sb6fc74_installedLabel",
9138
+ "uninstallButton": "s96967f_uninstallButton",
9139
+ "restartSelfButton": "sb00c36_restartSelfButton",
9140
+ "restartButton": "s6ac237_restartButton",
9141
+ "reloadButton": "s64a5ff_reloadButton",
9142
+ "reloadPanel": "sf79aef_reloadPanel",
9143
+ "gateTitle": "s559c38_gateTitle",
9144
+ "gateBody": "s3c0ace_gateBody",
9145
+ "gateWarning": "sc323e5_gateWarning",
9146
+ "gateActions": "s4b2373_gateActions",
9147
+ "installing": "s52b5a5_installing",
9148
+ "log": "s64ab71_log",
9149
+ "logLine": "s1531a8_logLine",
9150
+ "notice": "sf20dda_notice",
9151
+ "failedHeading": "seb9b08_failedHeading",
9152
+ "failedDetail": "sfd3021_failedDetail",
9153
+ "rejectedCode": "s50ba35_rejectedCode",
9154
+ "rejectedDetail": "se75489_rejectedDetail",
9155
+ "outdatedSection": "sa276cf_outdatedSection",
9156
+ "outdatedList": "sceae6e_outdatedList",
9157
+ "outdatedRow": "s8d2072_outdatedRow",
9158
+ "outdatedInfo": "s5c2545_outdatedInfo",
9159
+ "outdatedVersions": "s877c68_outdatedVersions",
9160
+ "shadowedSection": "s6c4649_shadowedSection",
9161
+ "shadowedDetail": "scc2a21_shadowedDetail",
9162
+ "outdatedActions": "sa1f556_outdatedActions",
9163
+ "switchWrap": "sd9f9e4_switchWrap"
7902
9164
  };
7903
9165
  //#endregion
7904
9166
  //#region src/client/ShopTab.tsx
@@ -7915,14 +9177,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7915
9177
  * those nodes, and React's next commit throws on the ones it no longer finds
7916
9178
  * (design 2026-09-26-market-borrowings §4). The tab renders no portals, so its
7917
9179
  * root is its whole surface. */
7918
- const PANEL_ROOT = `${_dsh_plugin_shop_css_627ddf51_default.panel} notranslate`;
9180
+ const PANEL_ROOT = `${_dsh_plugin_shop_css_e3675a89_default.panel} notranslate`;
7919
9181
  function ChevronIcon({ open }) {
7920
9182
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
7921
9183
  viewBox: "0 0 16 16",
7922
9184
  width: "14",
7923
9185
  height: "14",
7924
9186
  "aria-hidden": "true",
7925
- className: open ? _dsh_plugin_shop_css_627ddf51_default.chevronOpen : _dsh_plugin_shop_css_627ddf51_default.chevron,
9187
+ className: open ? _dsh_plugin_shop_css_e3675a89_default.chevronOpen : _dsh_plugin_shop_css_e3675a89_default.chevron,
7926
9188
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
7927
9189
  fill: "currentColor",
7928
9190
  d: "M3.5 6 8 10.5 12.5 6l1 1L8 12.5 2.5 7l1-1Z"
@@ -7935,7 +9197,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7935
9197
  * install controls. An installed plugin's card carries its installed row:
7936
9198
  * current → the non-interactive installed label, behind → the update button;
7937
9199
  * uninstalled → the install button. */
7938
- const EntryCard = (0, react.memo)(function EntryCard({ entry, stars, installed, missing, harness, nameTakenBy, t, flow, uninstallFlow, restart, restartBlocked, reload, setEnabled, inInstalledView }) {
9200
+ const EntryCard = (0, react.memo)(function EntryCard({ entry, stars, installed, missing, harness, nameTakenBy, t, flow, uninstallFlow, restart, restartBlocked, reload, setEnabled, inInstalledView, restarts }) {
7939
9201
  const [open, setOpen] = (0, react.useState)(false);
7940
9202
  const blockers = stateBlockers(blockersOf(missing, harness, nameTakenBy), t);
7941
9203
  const detailId = (0, react.useId)();
@@ -7952,32 +9214,32 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7952
9214
  subdir: entry.subdir
7953
9215
  };
7954
9216
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7955
- className: _dsh_plugin_shop_css_627ddf51_default.card,
9217
+ className: _dsh_plugin_shop_css_e3675a89_default.card,
7956
9218
  "data-shop-entry": entry.name,
7957
9219
  "data-category": category,
7958
9220
  children: [
7959
9221
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7960
- className: _dsh_plugin_shop_css_627ddf51_default.cardSpine,
9222
+ className: _dsh_plugin_shop_css_e3675a89_default.cardSpine,
7961
9223
  "aria-hidden": "true"
7962
9224
  }),
7963
9225
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
7964
9226
  type: "button",
7965
- className: _dsh_plugin_shop_css_627ddf51_default.entryHeader,
9227
+ className: _dsh_plugin_shop_css_e3675a89_default.entryHeader,
7966
9228
  "aria-expanded": open,
7967
9229
  "aria-controls": detailId,
7968
9230
  onClick: () => setOpen((current) => !current),
7969
9231
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7970
- className: _dsh_plugin_shop_css_627ddf51_default.name,
9232
+ className: _dsh_plugin_shop_css_e3675a89_default.name,
7971
9233
  children: entry.name
7972
9234
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
7973
- className: _dsh_plugin_shop_css_627ddf51_default.badges,
9235
+ className: _dsh_plugin_shop_css_e3675a89_default.badges,
7974
9236
  children: [
7975
9237
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7976
- className: _dsh_plugin_shop_css_627ddf51_default.categoryBadge,
9238
+ className: _dsh_plugin_shop_css_e3675a89_default.categoryBadge,
7977
9239
  children: t(categoryKey(entry))
7978
9240
  }),
7979
9241
  entry.source === "npm" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7980
- className: _dsh_plugin_shop_css_627ddf51_default.sourceBadge,
9242
+ className: _dsh_plugin_shop_css_e3675a89_default.sourceBadge,
7981
9243
  "data-shop-source-npm": true,
7982
9244
  role: "img",
7983
9245
  "aria-label": t("npmSource"),
@@ -7994,7 +9256,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
7994
9256
  })
7995
9257
  }),
7996
9258
  hasGithubHome(entry) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
7997
- className: _dsh_plugin_shop_css_627ddf51_default.sourceBadge,
9259
+ className: _dsh_plugin_shop_css_e3675a89_default.sourceBadge,
7998
9260
  "data-shop-source-github": true,
7999
9261
  role: "img",
8000
9262
  "aria-label": t("githubSource"),
@@ -8008,17 +9270,22 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8008
9270
  })
8009
9271
  }),
8010
9272
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8011
- className: _dsh_plugin_shop_css_627ddf51_default.cardVersion,
9273
+ className: _dsh_plugin_shop_css_e3675a89_default.cardVersion,
8012
9274
  "data-card-version": true,
8013
9275
  children: ["v", displayVersion(entry)]
8014
9276
  }),
8015
9277
  entry.tier !== "community" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8016
- className: _dsh_plugin_shop_css_627ddf51_default.tierBadge,
9278
+ className: _dsh_plugin_shop_css_e3675a89_default.tierBadge,
8017
9279
  "data-tier": entry.tier,
8018
9280
  children: t(tierKey(entry.tier))
8019
9281
  }),
9282
+ entry.archived === true && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9283
+ className: _dsh_plugin_shop_css_e3675a89_default.tierBadge,
9284
+ "data-tier": "archived",
9285
+ children: t("archivedBadge")
9286
+ }),
8020
9287
  stars !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8021
- className: _dsh_plugin_shop_css_627ddf51_default.starsBadge,
9288
+ className: _dsh_plugin_shop_css_e3675a89_default.starsBadge,
8022
9289
  role: "img",
8023
9290
  "aria-label": t("stars", { count: stars }),
8024
9291
  children: ["★ ", formatStars(stars)]
@@ -8028,39 +9295,39 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8028
9295
  })]
8029
9296
  }),
8030
9297
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8031
- className: _dsh_plugin_shop_css_627ddf51_default.body,
9298
+ className: _dsh_plugin_shop_css_e3675a89_default.body,
8032
9299
  children: [
8033
9300
  summary !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8034
- className: open ? `${_dsh_plugin_shop_css_627ddf51_default.summary} ${_dsh_plugin_shop_css_627ddf51_default.summaryExpanded}` : _dsh_plugin_shop_css_627ddf51_default.summary,
9301
+ className: open ? `${_dsh_plugin_shop_css_e3675a89_default.summary} ${_dsh_plugin_shop_css_e3675a89_default.summaryExpanded}` : _dsh_plugin_shop_css_e3675a89_default.summary,
8035
9302
  children: summary.en
8036
9303
  }),
8037
9304
  summary?.zh !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8038
- className: open ? `${_dsh_plugin_shop_css_627ddf51_default.summaryZh} ${_dsh_plugin_shop_css_627ddf51_default.summaryZhExpanded}` : _dsh_plugin_shop_css_627ddf51_default.summaryZh,
9305
+ className: open ? `${_dsh_plugin_shop_css_e3675a89_default.summaryZh} ${_dsh_plugin_shop_css_e3675a89_default.summaryZhExpanded}` : _dsh_plugin_shop_css_e3675a89_default.summaryZh,
8039
9306
  children: summary.zh
8040
9307
  }),
8041
9308
  blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
8042
9309
  blocker,
8043
- className: _dsh_plugin_shop_css_627ddf51_default.incompatibleDetail,
9310
+ className: _dsh_plugin_shop_css_e3675a89_default.incompatibleDetail,
8044
9311
  surface: "detail"
8045
9312
  }, blocker.kind)),
8046
9313
  open && entry.catalog !== void 0 && entry.catalog.capabilities.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8047
- className: _dsh_plugin_shop_css_627ddf51_default.capabilitiesBlock,
9314
+ className: _dsh_plugin_shop_css_e3675a89_default.capabilitiesBlock,
8048
9315
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8049
- className: _dsh_plugin_shop_css_627ddf51_default.capabilitiesNote,
9316
+ className: _dsh_plugin_shop_css_e3675a89_default.capabilitiesNote,
8050
9317
  children: t("capabilitiesNote")
8051
9318
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
8052
- className: _dsh_plugin_shop_css_627ddf51_default.capabilities,
9319
+ className: _dsh_plugin_shop_css_e3675a89_default.capabilities,
8053
9320
  children: entry.catalog.capabilities.map((capability) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: capability }, capability))
8054
9321
  })]
8055
9322
  }),
8056
9323
  open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
8057
9324
  id: detailId,
8058
- className: _dsh_plugin_shop_css_627ddf51_default.detail,
9325
+ className: _dsh_plugin_shop_css_e3675a89_default.detail,
8059
9326
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("dl", {
8060
- className: _dsh_plugin_shop_css_627ddf51_default.detailRows,
9327
+ className: _dsh_plugin_shop_css_e3675a89_default.detailRows,
8061
9328
  children: [
8062
9329
  npmUrl !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8063
- className: _dsh_plugin_shop_css_627ddf51_default.detailRow,
9330
+ className: _dsh_plugin_shop_css_e3675a89_default.detailRow,
8064
9331
  "data-shop-npm": true,
8065
9332
  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", {
8066
9333
  href: npmUrl,
@@ -8070,7 +9337,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8070
9337
  }) })]
8071
9338
  }),
8072
9339
  entry.repository !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8073
- className: _dsh_plugin_shop_css_627ddf51_default.detailRow,
9340
+ className: _dsh_plugin_shop_css_e3675a89_default.detailRow,
8074
9341
  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", {
8075
9342
  href: entry.repository,
8076
9343
  target: "_blank",
@@ -8079,12 +9346,12 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8079
9346
  }) : entry.repository })]
8080
9347
  }),
8081
9348
  entry.license !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8082
- className: _dsh_plugin_shop_css_627ddf51_default.detailRow,
9349
+ className: _dsh_plugin_shop_css_e3675a89_default.detailRow,
8083
9350
  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 })]
8084
9351
  })
8085
9352
  ]
8086
9353
  }), entry.tier === "verified-stale" && entry.review !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8087
- className: _dsh_plugin_shop_css_627ddf51_default.reviewedLine,
9354
+ className: _dsh_plugin_shop_css_e3675a89_default.reviewedLine,
8088
9355
  children: entry.review.reviewedVersion !== void 0 ? t("reviewedVersionLine", {
8089
9356
  reviewed: entry.review.reviewedVersion,
8090
9357
  current: entry.version
@@ -8097,7 +9364,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8097
9364
  ]
8098
9365
  }),
8099
9366
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8100
- className: _dsh_plugin_shop_css_627ddf51_default.cardActions,
9367
+ className: _dsh_plugin_shop_css_e3675a89_default.cardActions,
8101
9368
  "data-shop-actions": true,
8102
9369
  children: [installed === void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [!inInstalledView && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
8103
9370
  target: installTarget,
@@ -8129,7 +9396,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8129
9396
  restartBlocked,
8130
9397
  reload
8131
9398
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8132
- className: _dsh_plugin_shop_css_627ddf51_default.installedLabel,
9399
+ className: _dsh_plugin_shop_css_e3675a89_default.installedLabel,
8133
9400
  "data-shop-installed": true,
8134
9401
  children: t("installed")
8135
9402
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerBadge, {
@@ -8142,7 +9409,8 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8142
9409
  setEnabled,
8143
9410
  restart,
8144
9411
  restartBlocked,
8145
- reload
9412
+ reload,
9413
+ restarts
8146
9414
  }),
8147
9415
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
8148
9416
  name: entry.name,
@@ -8153,16 +9421,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8153
9421
  flow: uninstallFlow
8154
9422
  })
8155
9423
  ] }), (size !== void 0 || author !== null) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8156
- className: _dsh_plugin_shop_css_627ddf51_default.cardMeta,
9424
+ className: _dsh_plugin_shop_css_e3675a89_default.cardMeta,
8157
9425
  children: [size !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8158
- className: _dsh_plugin_shop_css_627ddf51_default.size,
9426
+ className: _dsh_plugin_shop_css_e3675a89_default.size,
8159
9427
  "data-shop-size": true,
8160
9428
  role: "img",
8161
9429
  "aria-label": t("sizeLabel", { size }),
8162
9430
  title: t("sizeLabel", { size }),
8163
9431
  children: size
8164
9432
  }), author !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8165
- className: _dsh_plugin_shop_css_627ddf51_default.author,
9433
+ className: _dsh_plugin_shop_css_e3675a89_default.author,
8166
9434
  "data-shop-author": true,
8167
9435
  children: t("authorLine", { author })
8168
9436
  })]
@@ -8211,13 +9479,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8211
9479
  ...attribute,
8212
9480
  children: blocker.text
8213
9481
  }), blocker.remedy !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8214
- className: _dsh_plugin_shop_css_627ddf51_default.remedy,
9482
+ className: _dsh_plugin_shop_css_e3675a89_default.remedy,
8215
9483
  "data-shop-remedy": blocker.kind,
8216
9484
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8217
- className: _dsh_plugin_shop_css_627ddf51_default.remedyLead,
9485
+ className: _dsh_plugin_shop_css_e3675a89_default.remedyLead,
8218
9486
  children: blocker.remedy.lead
8219
9487
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
8220
- className: _dsh_plugin_shop_css_627ddf51_default.allowCommand,
9488
+ className: _dsh_plugin_shop_css_e3675a89_default.allowCommand,
8221
9489
  "data-shop-allow-command": true,
8222
9490
  children: blocker.remedy.command
8223
9491
  })]
@@ -8255,7 +9523,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8255
9523
  if (lead === void 0 || word === null) return null;
8256
9524
  const detail = blockers.map((blocker) => blocker.text).join("\n");
8257
9525
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8258
- className: _dsh_plugin_shop_css_627ddf51_default.incompatibleBadge,
9526
+ className: _dsh_plugin_shop_css_e3675a89_default.incompatibleBadge,
8259
9527
  "data-shop-blocker": lead.kind,
8260
9528
  role: "img",
8261
9529
  "aria-label": detail,
@@ -8272,37 +9540,37 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8272
9540
  const [gateOpen, setGateOpen] = (0, react.useState)(false);
8273
9541
  const { view, start } = flow;
8274
9542
  if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8275
- className: _dsh_plugin_shop_css_627ddf51_default.installPanel,
9543
+ className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
8276
9544
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8277
- className: _dsh_plugin_shop_css_627ddf51_default.installing,
9545
+ className: _dsh_plugin_shop_css_e3675a89_default.installing,
8278
9546
  children: t(installPhaseKey(view.phase))
8279
9547
  }), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8280
- className: _dsh_plugin_shop_css_627ddf51_default.log,
9548
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
8281
9549
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8282
- className: _dsh_plugin_shop_css_627ddf51_default.logLine,
9550
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
8283
9551
  "data-shop-log-line": true,
8284
9552
  children: line
8285
9553
  }, index))
8286
9554
  })]
8287
9555
  });
8288
9556
  if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8289
- className: _dsh_plugin_shop_css_627ddf51_default.installedActions,
9557
+ className: _dsh_plugin_shop_css_e3675a89_default.installedActions,
8290
9558
  children: [
8291
9559
  view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8292
- className: _dsh_plugin_shop_css_627ddf51_default.log,
9560
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
8293
9561
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8294
- className: _dsh_plugin_shop_css_627ddf51_default.logLine,
9562
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
8295
9563
  "data-shop-log-line": true,
8296
9564
  children: line
8297
9565
  }, index))
8298
9566
  }),
8299
9567
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8300
- className: _dsh_plugin_shop_css_627ddf51_default.notice,
9568
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
8301
9569
  "data-shop-restart-notice": true,
8302
9570
  children: t(activationNoticeKey(view.activation, view.restartReason))
8303
9571
  }),
8304
9572
  view.detail !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8305
- className: _dsh_plugin_shop_css_627ddf51_default.notice,
9573
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
8306
9574
  "data-shop-done-note": true,
8307
9575
  children: view.detail
8308
9576
  }),
@@ -8317,57 +9585,57 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8317
9585
  ]
8318
9586
  });
8319
9587
  if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8320
- className: _dsh_plugin_shop_css_627ddf51_default.installPanel,
9588
+ className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
8321
9589
  children: [
8322
9590
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8323
- className: _dsh_plugin_shop_css_627ddf51_default.failedHeading,
9591
+ className: _dsh_plugin_shop_css_e3675a89_default.failedHeading,
8324
9592
  children: t("installFailed")
8325
9593
  }),
8326
9594
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8327
- className: _dsh_plugin_shop_css_627ddf51_default.log,
9595
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
8328
9596
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8329
- className: _dsh_plugin_shop_css_627ddf51_default.logLine,
9597
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
8330
9598
  "data-shop-log-line": true,
8331
9599
  children: line
8332
9600
  }, index))
8333
9601
  }),
8334
9602
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8335
- className: _dsh_plugin_shop_css_627ddf51_default.failedDetail,
9603
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
8336
9604
  children: view.detail === "" ? t("installTransportFailed") : view.detail
8337
9605
  })
8338
9606
  ]
8339
9607
  });
8340
9608
  if (view.kind === "rejected") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8341
- className: _dsh_plugin_shop_css_627ddf51_default.installPanel,
9609
+ className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
8342
9610
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8343
- className: _dsh_plugin_shop_css_627ddf51_default.rejectedCode,
9611
+ className: _dsh_plugin_shop_css_e3675a89_default.rejectedCode,
8344
9612
  children: t(rejectionCodeKey(view.code))
8345
9613
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8346
- className: _dsh_plugin_shop_css_627ddf51_default.rejectedDetail,
9614
+ className: _dsh_plugin_shop_css_e3675a89_default.rejectedDetail,
8347
9615
  children: view.detail
8348
9616
  })]
8349
9617
  });
8350
9618
  if (gateOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8351
- className: _dsh_plugin_shop_css_627ddf51_default.gate,
9619
+ className: _dsh_plugin_shop_css_e3675a89_default.gate,
8352
9620
  children: [
8353
9621
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8354
- className: _dsh_plugin_shop_css_627ddf51_default.gateTitle,
9622
+ className: _dsh_plugin_shop_css_e3675a89_default.gateTitle,
8355
9623
  children: t("acknowledgementTitle")
8356
9624
  }),
8357
9625
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8358
- className: _dsh_plugin_shop_css_627ddf51_default.gateBody,
9626
+ className: _dsh_plugin_shop_css_e3675a89_default.gateBody,
8359
9627
  children: t("acknowledgementBody")
8360
9628
  }),
8361
9629
  !blockersStated && blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
8362
9630
  blocker,
8363
- className: _dsh_plugin_shop_css_627ddf51_default.gateWarning,
9631
+ className: _dsh_plugin_shop_css_e3675a89_default.gateWarning,
8364
9632
  surface: "warning"
8365
9633
  }, blocker.kind)),
8366
9634
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8367
- className: _dsh_plugin_shop_css_627ddf51_default.gateActions,
9635
+ className: _dsh_plugin_shop_css_e3675a89_default.gateActions,
8368
9636
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8369
9637
  type: "button",
8370
- className: _dsh_plugin_shop_css_627ddf51_default.confirmButton,
9638
+ className: _dsh_plugin_shop_css_e3675a89_default.confirmButton,
8371
9639
  "data-shop-confirm": true,
8372
9640
  onClick: () => {
8373
9641
  setGateOpen(false);
@@ -8379,7 +9647,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8379
9647
  children: t("confirm")
8380
9648
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8381
9649
  type: "button",
8382
- className: _dsh_plugin_shop_css_627ddf51_default.cancelButton,
9650
+ className: _dsh_plugin_shop_css_e3675a89_default.cancelButton,
8383
9651
  onClick: () => setGateOpen(false),
8384
9652
  children: t("cancel")
8385
9653
  })]
@@ -8391,7 +9659,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8391
9659
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8392
9660
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8393
9661
  type: "button",
8394
- className: _dsh_plugin_shop_css_627ddf51_default.installButton,
9662
+ className: _dsh_plugin_shop_css_e3675a89_default.installButton,
8395
9663
  disabled: refused,
8396
9664
  ...refused ? { "data-shop-refused": true } : {},
8397
9665
  ...update ? { "data-shop-update": true } : { "data-shop-install": true },
@@ -8409,11 +9677,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8409
9677
  t
8410
9678
  }),
8411
9679
  refused && !blockersStated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8412
- className: _dsh_plugin_shop_css_627ddf51_default.refusal,
9680
+ className: _dsh_plugin_shop_css_e3675a89_default.refusal,
8413
9681
  "data-shop-refusal": true,
8414
9682
  children: blockers.map((blocker) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BlockerLine, {
8415
9683
  blocker,
8416
- className: _dsh_plugin_shop_css_627ddf51_default.gateWarning,
9684
+ className: _dsh_plugin_shop_css_e3675a89_default.gateWarning,
8417
9685
  surface: "warning"
8418
9686
  }, blocker.kind))
8419
9687
  })
@@ -8428,32 +9696,32 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8428
9696
  function UninstallPanel({ name, t, restart, restartBlocked, reload, flow }) {
8429
9697
  const { view, start, reset } = flow;
8430
9698
  if (view.kind === "running") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8431
- className: _dsh_plugin_shop_css_627ddf51_default.installPanel,
9699
+ className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
8432
9700
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8433
- className: _dsh_plugin_shop_css_627ddf51_default.installing,
9701
+ className: _dsh_plugin_shop_css_e3675a89_default.installing,
8434
9702
  children: t("uninstalling")
8435
9703
  }), view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8436
- className: _dsh_plugin_shop_css_627ddf51_default.log,
9704
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
8437
9705
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8438
- className: _dsh_plugin_shop_css_627ddf51_default.logLine,
9706
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
8439
9707
  "data-shop-log-line": true,
8440
9708
  children: line
8441
9709
  }, index))
8442
9710
  })]
8443
9711
  });
8444
9712
  if (view.kind === "done") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8445
- className: _dsh_plugin_shop_css_627ddf51_default.installedActions,
9713
+ className: _dsh_plugin_shop_css_e3675a89_default.installedActions,
8446
9714
  children: [
8447
9715
  view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8448
- className: _dsh_plugin_shop_css_627ddf51_default.log,
9716
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
8449
9717
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8450
- className: _dsh_plugin_shop_css_627ddf51_default.logLine,
9718
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
8451
9719
  "data-shop-log-line": true,
8452
9720
  children: line
8453
9721
  }, index))
8454
9722
  }),
8455
9723
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8456
- className: _dsh_plugin_shop_css_627ddf51_default.notice,
9724
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
8457
9725
  "data-shop-uninstall-done": true,
8458
9726
  children: t(uninstallActivationNoticeKey(view.activation))
8459
9727
  }),
@@ -8467,7 +9735,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8467
9735
  }),
8468
9736
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8469
9737
  type: "button",
8470
- className: _dsh_plugin_shop_css_627ddf51_default.cancelButton,
9738
+ className: _dsh_plugin_shop_css_e3675a89_default.cancelButton,
8471
9739
  "data-shop-uninstall-dismiss": true,
8472
9740
  onClick: reset,
8473
9741
  children: t("dismiss")
@@ -8475,27 +9743,27 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8475
9743
  ]
8476
9744
  });
8477
9745
  if (view.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8478
- className: _dsh_plugin_shop_css_627ddf51_default.installPanel,
9746
+ className: _dsh_plugin_shop_css_e3675a89_default.installPanel,
8479
9747
  children: [
8480
9748
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8481
- className: _dsh_plugin_shop_css_627ddf51_default.failedHeading,
9749
+ className: _dsh_plugin_shop_css_e3675a89_default.failedHeading,
8482
9750
  children: t("uninstallFailed")
8483
9751
  }),
8484
9752
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8485
- className: _dsh_plugin_shop_css_627ddf51_default.log,
9753
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
8486
9754
  children: view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8487
- className: _dsh_plugin_shop_css_627ddf51_default.logLine,
9755
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
8488
9756
  "data-shop-log-line": true,
8489
9757
  children: line
8490
9758
  }, index))
8491
9759
  }),
8492
9760
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8493
- className: _dsh_plugin_shop_css_627ddf51_default.failedDetail,
9761
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
8494
9762
  children: view.detail === "" ? t("uninstallTransportFailed") : view.detail
8495
9763
  }),
8496
9764
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8497
9765
  type: "button",
8498
- className: _dsh_plugin_shop_css_627ddf51_default.uninstallButton,
9766
+ className: _dsh_plugin_shop_css_e3675a89_default.uninstallButton,
8499
9767
  "data-shop-uninstall-retry": true,
8500
9768
  onClick: () => void start({ name }),
8501
9769
  children: t("retry")
@@ -8505,7 +9773,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8505
9773
  if (view.kind !== "idle") return null;
8506
9774
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8507
9775
  type: "button",
8508
- className: _dsh_plugin_shop_css_627ddf51_default.uninstallButton,
9776
+ className: _dsh_plugin_shop_css_e3675a89_default.uninstallButton,
8509
9777
  "data-shop-uninstall": true,
8510
9778
  onClick: () => void start({ name }),
8511
9779
  children: t("uninstall")
@@ -8546,37 +9814,38 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8546
9814
  else if (outcome.kind === "unreachable") setRefusal(t("restartTransportFailed"));
8547
9815
  };
8548
9816
  if (monitor?.kind === "requesting" || monitor?.kind === "restarting") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8549
- className: _dsh_plugin_shop_css_627ddf51_default.notice,
9817
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
8550
9818
  "data-shop-restarting": true,
8551
9819
  children: t("restarting")
8552
9820
  });
8553
9821
  if (monitor?.kind === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8554
- className: _dsh_plugin_shop_css_627ddf51_default.failedDetail,
9822
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
8555
9823
  "data-shop-restart-error": true,
8556
9824
  children: monitor.logFile === void 0 ? t("restartFailedNotice") : t("restartFailedLogNotice", { log: monitor.logFile })
8557
9825
  });
9826
+ if (monitor?.kind === "not-restarted") return null;
8558
9827
  if (gateOpen) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8559
- className: _dsh_plugin_shop_css_627ddf51_default.gate,
9828
+ className: _dsh_plugin_shop_css_e3675a89_default.gate,
8560
9829
  children: [
8561
9830
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8562
- className: _dsh_plugin_shop_css_627ddf51_default.gateTitle,
9831
+ className: _dsh_plugin_shop_css_e3675a89_default.gateTitle,
8563
9832
  children: t("restartTitle")
8564
9833
  }),
8565
9834
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8566
- className: _dsh_plugin_shop_css_627ddf51_default.gateBody,
9835
+ className: _dsh_plugin_shop_css_e3675a89_default.gateBody,
8567
9836
  children: t("restartBody")
8568
9837
  }),
8569
9838
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8570
- className: _dsh_plugin_shop_css_627ddf51_default.gateActions,
9839
+ className: _dsh_plugin_shop_css_e3675a89_default.gateActions,
8571
9840
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8572
9841
  type: "button",
8573
- className: _dsh_plugin_shop_css_627ddf51_default.confirmButton,
9842
+ className: _dsh_plugin_shop_css_e3675a89_default.confirmButton,
8574
9843
  "data-shop-restart-confirm": true,
8575
9844
  onClick: () => void onConfirm(),
8576
9845
  children: t("restartConfirm")
8577
9846
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8578
9847
  type: "button",
8579
- className: _dsh_plugin_shop_css_627ddf51_default.cancelButton,
9848
+ className: _dsh_plugin_shop_css_e3675a89_default.cancelButton,
8580
9849
  onClick: () => setGateOpen(false),
8581
9850
  children: t("cancel")
8582
9851
  })]
@@ -8584,20 +9853,43 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8584
9853
  ]
8585
9854
  });
8586
9855
  const refused = refusal === null ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8587
- className: _dsh_plugin_shop_css_627ddf51_default.failedDetail,
9856
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
8588
9857
  "data-shop-restart-error": true,
8589
9858
  children: refusal
8590
9859
  });
8591
9860
  if (gate !== void 0) return refused;
8592
9861
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [refused, /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8593
9862
  type: "button",
8594
- className: _dsh_plugin_shop_css_627ddf51_default.restartButton,
9863
+ className: _dsh_plugin_shop_css_e3675a89_default.restartButton,
8595
9864
  "data-shop-restart": true,
8596
9865
  onClick: () => setGateOpen(true),
8597
9866
  children: t("restart")
8598
9867
  })] });
8599
9868
  }
8600
9869
  /**
9870
+ * The page reloaded into the very process it asked to restart
9871
+ * (restart-monitor.ts `noteBoot`): said once, above everything, because
9872
+ * whatever asked for the restart — a self-update, a card, a switch — has
9873
+ * nothing left on screen after the reload to say it under. It names the
9874
+ * process to stop, when the host named it, and the command that starts dsh
9875
+ * again; the log holds what the handoff did, which tells an old process that
9876
+ * never exited from a new one that died in boot (design §8, 2026-10-06
9877
+ * amendment).
9878
+ */
9879
+ function NotRestartedNotice({ t, pid, logFile }) {
9880
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9881
+ className: _dsh_plugin_shop_css_e3675a89_default.selfUpdatePanel,
9882
+ "data-shop-not-restarted": true,
9883
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9884
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
9885
+ children: pid === void 0 ? t("notRestartedNotice") : t("notRestartedPidNotice", { pid: String(pid) })
9886
+ }), logFile !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9887
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
9888
+ children: t("notRestartedLogNotice", { log: logFile })
9889
+ })]
9890
+ });
9891
+ }
9892
+ /**
8601
9893
  * The §4/§8 offer under a settled install, update or uninstall: a reload
8602
9894
  * when the change is already live on the server and only this page is
8603
9895
  * stale, a restart when the host actually needs one — and can restart at
@@ -8622,7 +9914,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8622
9914
  restart,
8623
9915
  reload
8624
9916
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8625
- className: _dsh_plugin_shop_css_627ddf51_default.notice,
9917
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
8626
9918
  "data-shop-restart-disabled": true,
8627
9919
  children: t(restartBlockedNoticeKey(restartBlocked))
8628
9920
  });
@@ -8633,14 +9925,14 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8633
9925
  * would help without knowing that now is a good time. */
8634
9926
  function ReloadPanel({ t, reload, where }) {
8635
9927
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8636
- className: _dsh_plugin_shop_css_627ddf51_default.reloadPanel,
9928
+ className: _dsh_plugin_shop_css_e3675a89_default.reloadPanel,
8637
9929
  "data-shop-reload": where,
8638
9930
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8639
- className: _dsh_plugin_shop_css_627ddf51_default.notice,
9931
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
8640
9932
  children: t("reloadNote")
8641
9933
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8642
9934
  type: "button",
8643
- className: _dsh_plugin_shop_css_627ddf51_default.reloadButton,
9935
+ className: _dsh_plugin_shop_css_e3675a89_default.reloadButton,
8644
9936
  onClick: reload,
8645
9937
  children: t("reload")
8646
9938
  })]
@@ -8653,10 +9945,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8653
9945
  * transport throw renders the localized failure line — its private detail
8654
9946
  * (which can name hosts and ports) never reaches the UI.
8655
9947
  */
8656
- function EnabledSwitch({ row, t, setEnabled, restart, restartBlocked, reload }) {
9948
+ function EnabledSwitch({ row, t, setEnabled, restart, restartBlocked, reload, restarts }) {
8657
9949
  const [enabled, setEnabledState] = (0, react.useState)(row.enabled);
8658
9950
  const [toggle, setToggle] = (0, react.useState)({ kind: "idle" });
8659
9951
  const [owed, setOwed] = (0, react.useState)("nothing");
9952
+ const restartsSeen = (0, react.useRef)(restarts);
9953
+ (0, react.useEffect)(() => {
9954
+ if (restartsSeen.current === restarts) return;
9955
+ restartsSeen.current = restarts;
9956
+ if (owed !== "restart") return;
9957
+ setOwed("nothing");
9958
+ setToggle((current) => current.kind === "saved" ? { kind: "idle" } : current);
9959
+ }, [restarts, owed]);
8660
9960
  const onToggle = async () => {
8661
9961
  if (toggle.kind === "saving") return;
8662
9962
  const next = !enabled;
@@ -8683,7 +9983,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8683
9983
  }
8684
9984
  };
8685
9985
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8686
- className: _dsh_plugin_shop_css_627ddf51_default.switchWrap,
9986
+ className: _dsh_plugin_shop_css_e3675a89_default.switchWrap,
8687
9987
  "data-shop-enabled-switch": row.name,
8688
9988
  children: [
8689
9989
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -8692,18 +9992,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8692
9992
  "aria-checked": enabled,
8693
9993
  "aria-label": t("enabledSwitch"),
8694
9994
  "data-shop-toggle": true,
8695
- className: `${_dsh_plugin_shop_css_627ddf51_default.switch} ${enabled ? _dsh_plugin_shop_css_627ddf51_default.switchOn : ""}`,
9995
+ className: `${_dsh_plugin_shop_css_e3675a89_default.switch} ${enabled ? _dsh_plugin_shop_css_e3675a89_default.switchOn : ""}`,
8696
9996
  onClick: () => void onToggle(),
8697
9997
  disabled: toggle.kind === "saving",
8698
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_627ddf51_default.switchKnob })
9998
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.switchKnob })
8699
9999
  }),
8700
10000
  toggle.kind === "saved" && owed === "nothing" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8701
- className: _dsh_plugin_shop_css_627ddf51_default.notice,
10001
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
8702
10002
  "data-shop-hot-apply": true,
8703
10003
  children: t("hotApplyNote")
8704
10004
  }),
8705
10005
  owed === "restart" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8706
- className: _dsh_plugin_shop_css_627ddf51_default.notice,
10006
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
8707
10007
  "data-shop-toggle-restart": true,
8708
10008
  children: t("toggleRestartNote")
8709
10009
  }),
@@ -8716,7 +10016,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8716
10016
  where: "toggle"
8717
10017
  }),
8718
10018
  toggle.kind === "error" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8719
- className: _dsh_plugin_shop_css_627ddf51_default.failedDetail,
10019
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
8720
10020
  "data-shop-toggle-error": true,
8721
10021
  children: toggle.detail
8722
10022
  })
@@ -8725,29 +10025,33 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8725
10025
  }
8726
10026
  /** One outdated install row (§7.3): the name, the installed and latest
8727
10027
  * versions, the hot enable/disable switch, and the update button (the
8728
- * install flow for `name@latest`, reusing `InstallPanel`). */
8729
- function OutdatedRow({ row, tier, missing, harness, t, setEnabled, flowFor, restart, restartBlocked, reload }) {
10028
+ * install flow for `name@latest`, reusing `InstallPanel`). The row MUST
10029
+ * carry a `latest` string — a catalog truth to compare against; §C10's
10030
+ * shadowed rows hold `latest: null` and never reach this control, so the
10031
+ * field is narrowed at the parameter. */
10032
+ function OutdatedRow({ row, tier, missing, harness, t, setEnabled, flowFor, restart, restartBlocked, reload, restarts }) {
8730
10033
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8731
- className: _dsh_plugin_shop_css_627ddf51_default.outdatedRow,
10034
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedRow,
8732
10035
  "data-shop-outdated-entry": row.name,
8733
10036
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8734
- className: _dsh_plugin_shop_css_627ddf51_default.outdatedInfo,
10037
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedInfo,
8735
10038
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8736
- className: _dsh_plugin_shop_css_627ddf51_default.name,
10039
+ className: _dsh_plugin_shop_css_e3675a89_default.name,
8737
10040
  children: row.name
8738
10041
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8739
- className: _dsh_plugin_shop_css_627ddf51_default.outdatedVersions,
10042
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedVersions,
8740
10043
  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 }) })]
8741
10044
  })]
8742
10045
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8743
- className: _dsh_plugin_shop_css_627ddf51_default.outdatedActions,
10046
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedActions,
8744
10047
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(EnabledSwitch, {
8745
10048
  row,
8746
10049
  t,
8747
10050
  setEnabled,
8748
10051
  restart,
8749
10052
  restartBlocked,
8750
- reload
10053
+ reload,
10054
+ restarts
8751
10055
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InstallPanel, {
8752
10056
  target: {
8753
10057
  name: row.name,
@@ -8785,25 +10089,25 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8785
10089
  * tier for the update gate is looked up from the catalog by install IDENTITY,
8786
10090
  * never by name (community → acknowledgement); an entry absent from the
8787
10091
  * catalog defaults to the community gate (the safer read). */
8788
- function OutdatedSection({ state, entriesByKey, missingByKey, harnessVerdicts, t, setEnabled, flowFor, restart, restartBlocked, reload }) {
10092
+ function OutdatedSection({ state, entriesByKey, missingByKey, harnessVerdicts, t, setEnabled, flowFor, restart, restartBlocked, reload, restarts }) {
8789
10093
  if (state.kind === "loading") return null;
8790
10094
  if (state.kind === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8791
- className: _dsh_plugin_shop_css_627ddf51_default.stateLine,
10095
+ className: _dsh_plugin_shop_css_e3675a89_default.stateLine,
8792
10096
  "data-shop-outdated-error": true,
8793
10097
  children: t("installedError")
8794
10098
  });
8795
- const outdated = state.entries.filter((entry) => entry.outdated);
10099
+ const outdated = state.entries.filter((entry) => entry.outdated && entry.latest !== null);
8796
10100
  if (outdated.length === 0) return null;
8797
10101
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("section", {
8798
- className: _dsh_plugin_shop_css_627ddf51_default.outdatedSection,
10102
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedSection,
8799
10103
  "data-shop-outdated": true,
8800
10104
  "aria-labelledby": UPDATABLE_HEADING_ID,
8801
10105
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h2", {
8802
- className: _dsh_plugin_shop_css_627ddf51_default.updatableHeading,
10106
+ className: _dsh_plugin_shop_css_e3675a89_default.updatableHeading,
8803
10107
  id: UPDATABLE_HEADING_ID,
8804
10108
  children: t("updatableSection")
8805
10109
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
8806
- className: _dsh_plugin_shop_css_627ddf51_default.outdatedList,
10110
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedList,
8807
10111
  children: outdated.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OutdatedRow, {
8808
10112
  row,
8809
10113
  tier: entriesByKey.get(identityKey(row))?.tier ?? "community",
@@ -8814,11 +10118,68 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8814
10118
  flowFor,
8815
10119
  restart,
8816
10120
  restartBlocked,
8817
- reload
10121
+ reload,
10122
+ restarts
8818
10123
  }) }, identityKey(row)))
8819
10124
  })]
8820
10125
  });
8821
10126
  }
10127
+ /** The §C10 shadowed rows: installed github dependencies whose name the
10128
+ * catalog now holds as an npm package — the pipeline refused the github
10129
+ * entry `shadowed-by-npm`, so it has no shelf card. Rendered as a single
10130
+ * strip inside the Installed view, with the badge and the explanation
10131
+ * naming the harness fact, and an Uninstall button as the only offered
10132
+ * action: there is no catalog truth to compare against, so no Install /
10133
+ * Update / Enable / Restart control makes sense. */
10134
+ function ShadowedSection({ entries, t, uninstallFlows, restart, restartBlocked, reload }) {
10135
+ const shadowed = entries.filter((entry) => entry.shadowed === true);
10136
+ if (shadowed.length === 0) return null;
10137
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("section", {
10138
+ className: _dsh_plugin_shop_css_e3675a89_default.shadowedSection,
10139
+ "data-shop-shadowed": true,
10140
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
10141
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedList,
10142
+ children: shadowed.map((row) => {
10143
+ const key = identityKey(row);
10144
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10145
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedRow,
10146
+ "data-shop-shadowed-entry": row.name,
10147
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10148
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedInfo,
10149
+ children: [
10150
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10151
+ className: _dsh_plugin_shop_css_e3675a89_default.name,
10152
+ children: row.name
10153
+ }),
10154
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10155
+ className: _dsh_plugin_shop_css_e3675a89_default.badges,
10156
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10157
+ className: _dsh_plugin_shop_css_e3675a89_default.tierBadge,
10158
+ "data-tier": "shadowed",
10159
+ children: t("shadowedBadge")
10160
+ })
10161
+ }),
10162
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10163
+ className: _dsh_plugin_shop_css_e3675a89_default.shadowedDetail,
10164
+ children: t("shadowedDetail")
10165
+ })
10166
+ ]
10167
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10168
+ className: _dsh_plugin_shop_css_e3675a89_default.outdatedActions,
10169
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UninstallPanel, {
10170
+ name: row.name,
10171
+ t,
10172
+ restart,
10173
+ restartBlocked,
10174
+ reload,
10175
+ flow: uninstallFlows.flowFor(key)
10176
+ })
10177
+ })]
10178
+ }) }, key);
10179
+ })
10180
+ })
10181
+ });
10182
+ }
8822
10183
  /**
8823
10184
  * The shop's own error boundary (design 2026-09-26-market-borrowings §4).
8824
10185
  *
@@ -8854,16 +10215,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8854
10215
  role: "alert",
8855
10216
  children: [
8856
10217
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8857
- className: _dsh_plugin_shop_css_627ddf51_default.stateLine,
10218
+ className: _dsh_plugin_shop_css_e3675a89_default.stateLine,
8858
10219
  children: say("tabCrashed", en.tabCrashed)
8859
10220
  }),
8860
10221
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
8861
- className: _dsh_plugin_shop_css_627ddf51_default.failedDetail,
10222
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
8862
10223
  children: error.message
8863
10224
  }),
8864
10225
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8865
10226
  type: "button",
8866
- className: _dsh_plugin_shop_css_627ddf51_default.actionButton,
10227
+ className: _dsh_plugin_shop_css_e3675a89_default.actionButton,
8867
10228
  onClick: () => this.setState({ error: null }),
8868
10229
  children: say("retry", en.retry)
8869
10230
  })
@@ -8883,7 +10244,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8883
10244
  /** The shop tab body: browse, search, refresh, and render one card per
8884
10245
  * entry. Data attributes on the e2e-relevant nodes follow the Task 3 list. */
8885
10246
  function ShopTabBody(props) {
8886
- const { t, catalog, install, installStatus, setEnabled, installed, installedSpecs, uninstall, noteUninstalled, restart, version, updateStart, reload: injectedReload } = props;
10247
+ const { t, catalog, install, installStatus, setEnabled, installed, installedSpecs, uninstall, noteUninstalled, restart, version, onRestarted, updateStart, reload: injectedReload } = props;
8887
10248
  const [catalogState, setCatalogState] = (0, react.useState)({ kind: "loading" });
8888
10249
  const [installedState, setInstalledState] = (0, react.useState)({ kind: "loading" });
8889
10250
  /** The install gate's own input, or undefined while it is unknown — loading,
@@ -8927,6 +10288,25 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
8927
10288
  noteUninstalled
8928
10289
  ]));
8929
10290
  uninstallResetRef.current = uninstallFlows.resetFlow;
10291
+ const [restarts, setRestarts] = (0, react.useState)(0);
10292
+ const resetSelfUpdate = selfUpdate.resetWhere;
10293
+ const resetInstalls = flows.resetWhere;
10294
+ const resetUninstalls = uninstallFlows.resetWhere;
10295
+ (0, react.useEffect)(() => onRestarted?.((fresh) => {
10296
+ setSelfVersion(fresh);
10297
+ resetSelfUpdate(settledByRestart);
10298
+ resetInstalls(settledByRestart);
10299
+ resetUninstalls(settledByRestart);
10300
+ setRestarts((current) => current + 1);
10301
+ noteMutation();
10302
+ setRequest({ kind: "reverdict" });
10303
+ }), [
10304
+ onRestarted,
10305
+ resetSelfUpdate,
10306
+ resetInstalls,
10307
+ resetUninstalls,
10308
+ noteMutation
10309
+ ]);
8930
10310
  const [reloading, setReloading] = (0, react.useState)(false);
8931
10311
  const [reloadFailed, setReloadFailed] = (0, react.useState)(false);
8932
10312
  const [query, setQuery] = (0, react.useState)("");
@@ -9056,8 +10436,9 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9056
10436
  harnessVerdicts,
9057
10437
  nameTakenByKey
9058
10438
  ]);
10439
+ const expansionMemo = (0, react.useMemo)(() => expandZhQuery(query), [query]);
9059
10440
  const matched = (0, react.useMemo)(() => {
9060
- const q = query.trim().toLowerCase();
10441
+ const q = expansionMemo.query;
9061
10442
  return (category === "installed" ? sortedCatalog : browsable).filter((entry) => {
9062
10443
  if (category === "installed") {
9063
10444
  const key = entryKey(entry);
@@ -9066,12 +10447,16 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9066
10447
  if (q === "") return true;
9067
10448
  const summaryEn = entry.catalog?.summary.en ?? "";
9068
10449
  const summaryZh = entry.catalog?.summary.zh ?? "";
9069
- return entry.name.toLowerCase().includes(q) || summaryEn.toLowerCase().includes(q) || summaryZh.toLowerCase().includes(q);
10450
+ const expansion = expansionMemo;
10451
+ const name = entry.name.toLowerCase();
10452
+ const en = summaryEn.toLowerCase();
10453
+ const zh = summaryZh.toLowerCase();
10454
+ return expansion.terms.some((term) => term.startsWith("#") ? tokenInText(term.slice(1), name + " " + en + " " + zh) : name.includes(term) || en.includes(term) || zh.includes(term));
9070
10455
  });
9071
10456
  }, [
9072
10457
  sortedCatalog,
9073
10458
  browsable,
9074
- query,
10459
+ expansionMemo,
9075
10460
  category,
9076
10461
  installedByKey,
9077
10462
  uninstallFlows.pending
@@ -9120,19 +10505,19 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9120
10505
  "data-shop-tab": true,
9121
10506
  "aria-busy": "true",
9122
10507
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9123
- className: _dsh_plugin_shop_css_627ddf51_default.srOnly,
10508
+ className: _dsh_plugin_shop_css_e3675a89_default.srOnly,
9124
10509
  children: t("loading")
9125
10510
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9126
- className: _dsh_plugin_shop_css_627ddf51_default.skeletonGrid,
10511
+ className: _dsh_plugin_shop_css_e3675a89_default.skeletonGrid,
9127
10512
  "data-shop-skeleton": true,
9128
10513
  "aria-hidden": "true",
9129
10514
  children: Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9130
- className: _dsh_plugin_shop_css_627ddf51_default.skeletonCard,
10515
+ className: _dsh_plugin_shop_css_e3675a89_default.skeletonCard,
9131
10516
  children: [
9132
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_627ddf51_default.skeletonName }),
9133
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_627ddf51_default.skeletonSummary }),
9134
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_627ddf51_default.skeletonSummaryShort }),
9135
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_627ddf51_default.skeletonActions })
10517
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonName }),
10518
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonSummary }),
10519
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonSummaryShort }),
10520
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.skeletonActions })
9136
10521
  ]
9137
10522
  }, i))
9138
10523
  })]
@@ -9142,11 +10527,11 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9142
10527
  translate: "no",
9143
10528
  "data-shop-tab": true,
9144
10529
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9145
- className: _dsh_plugin_shop_css_627ddf51_default.stateLine,
10530
+ className: _dsh_plugin_shop_css_e3675a89_default.stateLine,
9146
10531
  children: t("error")
9147
10532
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9148
10533
  type: "button",
9149
- className: _dsh_plugin_shop_css_627ddf51_default.actionButton,
10534
+ className: _dsh_plugin_shop_css_e3675a89_default.actionButton,
9150
10535
  onClick: () => {
9151
10536
  setRequest({ kind: "retry" });
9152
10537
  setVersionReload((current) => current + 1);
@@ -9161,96 +10546,109 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9161
10546
  "data-shop-tab": true,
9162
10547
  children: [
9163
10548
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9164
- className: _dsh_plugin_shop_css_627ddf51_default.toolbar,
9165
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
9166
- type: "search",
9167
- className: _dsh_plugin_shop_css_627ddf51_default.searchInput,
9168
- "aria-label": t("search"),
9169
- placeholder: t("search"),
9170
- value: query,
9171
- onChange: (event) => {
9172
- setQuery(event.target.value);
9173
- setVisibleCount(48);
9174
- }
9175
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9176
- className: _dsh_plugin_shop_css_627ddf51_default.toolbarRight,
9177
- children: [
9178
- result.stale && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9179
- className: _dsh_plugin_shop_css_627ddf51_default.staleBadge,
9180
- children: t("staleLabel", { date: result.builtAt.slice(0, 10) })
9181
- }),
9182
- selfVersion !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9183
- className: _dsh_plugin_shop_css_627ddf51_default.versionBlock,
9184
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9185
- className: _dsh_plugin_shop_css_627ddf51_default.versionText,
9186
- "data-shop-version": true,
9187
- children: ["v", selfVersion.installed]
9188
- }), restartMonitor !== null ? null : selfPending && restartBlocked === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9189
- type: "button",
9190
- className: _dsh_plugin_shop_css_627ddf51_default.restartSelfButton,
9191
- "data-shop-restart": true,
9192
- onClick: () => setSelfRestartGate(true),
9193
- children: t("restart")
9194
- }) : !selfPending && selfVersion.outdated && selfVersion.latest !== null && selfUpdate.view.kind === "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9195
- type: "button",
9196
- className: _dsh_plugin_shop_css_627ddf51_default.updateSelfButton,
9197
- "data-shop-update-self": true,
9198
- onClick: () => {
9199
- if (selfVersion.latest !== null) selfUpdate.start({ version: selfVersion.latest });
9200
- },
9201
- children: t("update")
9202
- }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9203
- type: "button",
9204
- className: _dsh_plugin_shop_css_627ddf51_default.checkUpdateButton,
9205
- "data-shop-check-update": true,
9206
- title: t("checkUpdateTitle"),
9207
- disabled: checkState !== "idle",
9208
- onClick: () => void checkVersion(),
9209
- children: checkState === "up-to-date" ? t("upToDate") : t("checkUpdate")
9210
- })]
9211
- }),
9212
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
9213
- className: _dsh_plugin_shop_css_627ddf51_default.githubLink,
9214
- href: SHOP_REPO_URL,
9215
- target: "_blank",
9216
- rel: "noopener noreferrer",
9217
- "aria-label": t("github"),
9218
- "data-shop-github": true,
9219
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
9220
- viewBox: "0 0 16 16",
9221
- width: "16",
9222
- height: "16",
9223
- "aria-hidden": "true",
9224
- fill: "currentColor",
9225
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" })
10549
+ className: _dsh_plugin_shop_css_e3675a89_default.toolbar,
10550
+ children: [
10551
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
10552
+ type: "search",
10553
+ className: _dsh_plugin_shop_css_e3675a89_default.searchInput,
10554
+ "aria-label": t("search"),
10555
+ placeholder: t("search"),
10556
+ value: query,
10557
+ onChange: (event) => {
10558
+ setQuery(event.target.value);
10559
+ setVisibleCount(48);
10560
+ }
10561
+ }),
10562
+ expansionMemo.intents.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
10563
+ "data-shop-expansion-note": true,
10564
+ className: _dsh_plugin_shop_css_e3675a89_default.expansionNote,
10565
+ children: t("zhExpansionNote", { terms: expansionMemo.terms.slice(1).map((term) => term.startsWith("#") ? term.slice(1) : term).join(", ") })
10566
+ }),
10567
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10568
+ className: _dsh_plugin_shop_css_e3675a89_default.toolbarRight,
10569
+ children: [
10570
+ result.stale && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10571
+ className: _dsh_plugin_shop_css_e3675a89_default.staleBadge,
10572
+ children: t("staleLabel", { date: result.builtAt.slice(0, 10) })
10573
+ }),
10574
+ selfVersion !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10575
+ className: _dsh_plugin_shop_css_e3675a89_default.versionBlock,
10576
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
10577
+ className: _dsh_plugin_shop_css_e3675a89_default.versionText,
10578
+ "data-shop-version": true,
10579
+ children: ["v", selfVersion.installed]
10580
+ }), restartMonitor !== null ? null : selfPending && restartBlocked === null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10581
+ type: "button",
10582
+ className: _dsh_plugin_shop_css_e3675a89_default.restartSelfButton,
10583
+ "data-shop-restart": true,
10584
+ onClick: () => setSelfRestartGate(true),
10585
+ children: t("restart")
10586
+ }) : !selfPending && selfVersion.outdated && selfVersion.latest !== null && selfUpdate.view.kind === "idle" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10587
+ type: "button",
10588
+ className: _dsh_plugin_shop_css_e3675a89_default.updateSelfButton,
10589
+ "data-shop-update-self": true,
10590
+ onClick: () => {
10591
+ if (selfVersion.latest !== null) selfUpdate.start({ version: selfVersion.latest });
10592
+ },
10593
+ children: t("update")
10594
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
10595
+ type: "button",
10596
+ className: _dsh_plugin_shop_css_e3675a89_default.checkUpdateButton,
10597
+ "data-shop-check-update": true,
10598
+ title: t("checkUpdateTitle"),
10599
+ disabled: checkState !== "idle",
10600
+ onClick: () => void checkVersion(),
10601
+ children: checkState === "up-to-date" ? t("upToDate") : t("checkUpdate")
10602
+ })]
10603
+ }),
10604
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
10605
+ className: _dsh_plugin_shop_css_e3675a89_default.githubLink,
10606
+ href: SHOP_REPO_URL,
10607
+ target: "_blank",
10608
+ rel: "noopener noreferrer",
10609
+ "aria-label": t("github"),
10610
+ "data-shop-github": true,
10611
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
10612
+ viewBox: "0 0 16 16",
10613
+ width: "16",
10614
+ height: "16",
10615
+ "aria-hidden": "true",
10616
+ fill: "currentColor",
10617
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: "M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0 0 16 8c0-4.42-3.58-8-8-8Z" })
10618
+ })
9226
10619
  })
9227
- })
9228
- ]
9229
- })]
10620
+ ]
10621
+ })
10622
+ ]
10623
+ }),
10624
+ restartMonitor?.kind === "not-restarted" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotRestartedNotice, {
10625
+ t,
10626
+ pid: restartMonitor.pid,
10627
+ logFile: restartMonitor.logFile
9230
10628
  }),
9231
10629
  selfUpdate.view.kind === "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9232
- className: _dsh_plugin_shop_css_627ddf51_default.selfUpdatePanel,
10630
+ className: _dsh_plugin_shop_css_e3675a89_default.selfUpdatePanel,
9233
10631
  "data-shop-self-updating": true,
9234
10632
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9235
- className: _dsh_plugin_shop_css_627ddf51_default.installing,
10633
+ className: _dsh_plugin_shop_css_e3675a89_default.installing,
9236
10634
  children: t(installPhaseKey(selfUpdate.view.phase))
9237
10635
  }), selfUpdate.view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9238
- className: _dsh_plugin_shop_css_627ddf51_default.log,
10636
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
9239
10637
  children: selfUpdate.view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9240
- className: _dsh_plugin_shop_css_627ddf51_default.logLine,
10638
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
9241
10639
  "data-shop-log-line": true,
9242
10640
  children: line
9243
10641
  }, index))
9244
10642
  })]
9245
10643
  }),
9246
10644
  selfPending && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9247
- className: _dsh_plugin_shop_css_627ddf51_default.selfUpdatePanel,
10645
+ className: _dsh_plugin_shop_css_e3675a89_default.selfUpdatePanel,
9248
10646
  "data-shop-self-update-done": true,
9249
10647
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9250
- className: _dsh_plugin_shop_css_627ddf51_default.installedActions,
10648
+ className: _dsh_plugin_shop_css_e3675a89_default.installedActions,
9251
10649
  children: [
9252
10650
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9253
- className: _dsh_plugin_shop_css_627ddf51_default.notice,
10651
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
9254
10652
  children: t("installedRestartNotice")
9255
10653
  }),
9256
10654
  restartBlocked === null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RestartPanel, {
@@ -9263,7 +10661,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9263
10661
  }
9264
10662
  }),
9265
10663
  restartBlocked !== null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9266
- className: _dsh_plugin_shop_css_627ddf51_default.notice,
10664
+ className: _dsh_plugin_shop_css_e3675a89_default.notice,
9267
10665
  "data-shop-restart-disabled": true,
9268
10666
  children: t(restartBlockedNoticeKey(restartBlocked))
9269
10667
  })
@@ -9271,35 +10669,35 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9271
10669
  })
9272
10670
  }),
9273
10671
  selfUpdate.view.kind === "failed" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9274
- className: _dsh_plugin_shop_css_627ddf51_default.selfUpdatePanel,
10672
+ className: _dsh_plugin_shop_css_e3675a89_default.selfUpdatePanel,
9275
10673
  "data-shop-self-update-failed": true,
9276
10674
  children: [
9277
10675
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9278
- className: _dsh_plugin_shop_css_627ddf51_default.failedHeading,
10676
+ className: _dsh_plugin_shop_css_e3675a89_default.failedHeading,
9279
10677
  children: t("updateFailed")
9280
10678
  }),
9281
10679
  selfUpdate.view.log.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9282
- className: _dsh_plugin_shop_css_627ddf51_default.log,
10680
+ className: _dsh_plugin_shop_css_e3675a89_default.log,
9283
10681
  children: selfUpdate.view.log.map((line, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9284
- className: _dsh_plugin_shop_css_627ddf51_default.logLine,
10682
+ className: _dsh_plugin_shop_css_e3675a89_default.logLine,
9285
10683
  "data-shop-log-line": true,
9286
10684
  children: line
9287
10685
  }, index))
9288
10686
  }),
9289
10687
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9290
- className: _dsh_plugin_shop_css_627ddf51_default.failedDetail,
10688
+ className: _dsh_plugin_shop_css_e3675a89_default.failedDetail,
9291
10689
  children: selfUpdate.view.detail === "" ? t("updateTransportFailed") : selfUpdate.view.detail
9292
10690
  })
9293
10691
  ]
9294
10692
  }),
9295
10693
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9296
- className: _dsh_plugin_shop_css_627ddf51_default.categoryBar,
10694
+ className: _dsh_plugin_shop_css_e3675a89_default.categoryBar,
9297
10695
  role: "group",
9298
10696
  "aria-label": t("catalog"),
9299
10697
  children: [
9300
10698
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
9301
10699
  type: "button",
9302
- className: category === null ? `${_dsh_plugin_shop_css_627ddf51_default.categoryButton} ${_dsh_plugin_shop_css_627ddf51_default.categoryButtonOn}` : _dsh_plugin_shop_css_627ddf51_default.categoryButton,
10700
+ className: category === null ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_default.categoryButton,
9303
10701
  "aria-pressed": category === null,
9304
10702
  onClick: () => {
9305
10703
  setCategory(null);
@@ -9314,7 +10712,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9314
10712
  }),
9315
10713
  CATEGORY_ORDER.map((key) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
9316
10714
  type: "button",
9317
- className: category === key ? `${_dsh_plugin_shop_css_627ddf51_default.categoryButton} ${_dsh_plugin_shop_css_627ddf51_default.categoryButtonOn}` : _dsh_plugin_shop_css_627ddf51_default.categoryButton,
10715
+ className: category === key ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_default.categoryButton,
9318
10716
  "aria-pressed": category === key,
9319
10717
  "data-category": key,
9320
10718
  "data-shop-category-tab": key,
@@ -9330,7 +10728,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9330
10728
  }, key)),
9331
10729
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
9332
10730
  type: "button",
9333
- className: category === "installed" ? `${_dsh_plugin_shop_css_627ddf51_default.categoryButton} ${_dsh_plugin_shop_css_627ddf51_default.categoryButtonOn}` : _dsh_plugin_shop_css_627ddf51_default.categoryButton,
10731
+ className: category === "installed" ? `${_dsh_plugin_shop_css_e3675a89_default.categoryButton} ${_dsh_plugin_shop_css_e3675a89_default.categoryButtonOn}` : _dsh_plugin_shop_css_e3675a89_default.categoryButton,
9334
10732
  "aria-pressed": category === "installed",
9335
10733
  "data-shop-category-installed": true,
9336
10734
  onClick: () => {
@@ -9347,7 +10745,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9347
10745
  type: "button",
9348
10746
  role: "switch",
9349
10747
  "aria-checked": hideIncompatible,
9350
- className: hideIncompatible ? `${_dsh_plugin_shop_css_627ddf51_default.incompatibleFilter} ${_dsh_plugin_shop_css_627ddf51_default.incompatibleFilterOn}` : _dsh_plugin_shop_css_627ddf51_default.incompatibleFilter,
10748
+ className: hideIncompatible ? `${_dsh_plugin_shop_css_e3675a89_default.incompatibleFilter} ${_dsh_plugin_shop_css_e3675a89_default.incompatibleFilterOn}` : _dsh_plugin_shop_css_e3675a89_default.incompatibleFilter,
9351
10749
  title: t(hideIncompatible ? "showIncompatibleTitle" : "incompatibleFilterTitle"),
9352
10750
  "data-shop-hide-incompatible": true,
9353
10751
  onClick: () => {
@@ -9355,18 +10753,18 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9355
10753
  setVisibleCount(48);
9356
10754
  },
9357
10755
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9358
- className: hideIncompatible ? `${_dsh_plugin_shop_css_627ddf51_default.switch} ${_dsh_plugin_shop_css_627ddf51_default.switchOn}` : _dsh_plugin_shop_css_627ddf51_default.switch,
10756
+ className: hideIncompatible ? `${_dsh_plugin_shop_css_e3675a89_default.switch} ${_dsh_plugin_shop_css_e3675a89_default.switchOn}` : _dsh_plugin_shop_css_e3675a89_default.switch,
9359
10757
  "aria-hidden": "true",
9360
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_627ddf51_default.switchKnob })
10758
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: _dsh_plugin_shop_css_e3675a89_default.switchKnob })
9361
10759
  }), t("hideIncompatible", { count: incompatibleCount })]
9362
10760
  })
9363
10761
  ]
9364
10762
  }),
9365
10763
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9366
- className: _dsh_plugin_shop_css_627ddf51_default.catalogStatsRow,
10764
+ className: _dsh_plugin_shop_css_e3675a89_default.catalogStatsRow,
9367
10765
  children: [
9368
10766
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9369
- className: _dsh_plugin_shop_css_627ddf51_default.catalogStats,
10767
+ className: _dsh_plugin_shop_css_e3675a89_default.catalogStats,
9370
10768
  "data-shop-catalog-stats": true,
9371
10769
  children: t("catalogStats", {
9372
10770
  count: String(browsable.length),
@@ -9375,7 +10773,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9375
10773
  }),
9376
10774
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9377
10775
  type: "button",
9378
- className: _dsh_plugin_shop_css_627ddf51_default.catalogRefreshButton,
10776
+ className: _dsh_plugin_shop_css_e3675a89_default.catalogRefreshButton,
9379
10777
  "data-shop-catalog-refresh": true,
9380
10778
  disabled: reloading,
9381
10779
  onClick: () => {
@@ -9387,21 +10785,21 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9387
10785
  children: reloading ? t("refreshing") : t("refresh")
9388
10786
  }),
9389
10787
  reloadFailed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
9390
- className: _dsh_plugin_shop_css_627ddf51_default.catalogRefreshFailed,
10788
+ className: _dsh_plugin_shop_css_e3675a89_default.catalogRefreshFailed,
9391
10789
  "data-shop-catalog-refresh-failed": true,
9392
10790
  children: t("refreshFailed")
9393
10791
  })
9394
10792
  ]
9395
10793
  }),
9396
10794
  result.plugins.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9397
- className: _dsh_plugin_shop_css_627ddf51_default.emptyLine,
10795
+ className: _dsh_plugin_shop_css_e3675a89_default.emptyLine,
9398
10796
  children: t("empty")
9399
10797
  }) : filtered.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9400
- className: _dsh_plugin_shop_css_627ddf51_default.emptyLine,
10798
+ className: _dsh_plugin_shop_css_e3675a89_default.emptyLine,
9401
10799
  "data-shop-empty": true,
9402
10800
  children: matched.length > 0 ? t("emptyIncompatibleFiltered") : t("emptySearch")
9403
10801
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("ul", {
9404
- className: _dsh_plugin_shop_css_627ddf51_default.cards,
10802
+ className: _dsh_plugin_shop_css_e3675a89_default.cards,
9405
10803
  children: [visible.map((entry) => {
9406
10804
  const key = entryKey(entry);
9407
10805
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EntryCard, {
@@ -9418,16 +10816,17 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9418
10816
  restartBlocked,
9419
10817
  reload,
9420
10818
  setEnabled,
9421
- inInstalledView: category === "installed"
10819
+ inInstalledView: category === "installed",
10820
+ restarts
9422
10821
  }) }, key);
9423
10822
  }), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", {
9424
10823
  ref: sentinelRef,
9425
- className: _dsh_plugin_shop_css_627ddf51_default.cardsSentry,
10824
+ className: _dsh_plugin_shop_css_e3675a89_default.cardsSentry,
9426
10825
  "data-shop-sentry": true,
9427
10826
  "aria-hidden": "true"
9428
10827
  })]
9429
10828
  }), incremental && visibleCount < filtered.length && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
9430
- className: _dsh_plugin_shop_css_627ddf51_default.showingLine,
10829
+ className: _dsh_plugin_shop_css_e3675a89_default.showingLine,
9431
10830
  "aria-live": "polite",
9432
10831
  children: t("showing", {
9433
10832
  shown: String(visibleCount),
@@ -9444,6 +10843,15 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9444
10843
  flowFor: flows.flowFor,
9445
10844
  restart,
9446
10845
  restartBlocked,
10846
+ reload,
10847
+ restarts
10848
+ }),
10849
+ category === "installed" && installedState.kind === "ready" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShadowedSection, {
10850
+ entries: installedState.entries,
10851
+ t,
10852
+ uninstallFlows,
10853
+ restart,
10854
+ restartBlocked,
9447
10855
  reload
9448
10856
  })
9449
10857
  ]
@@ -9518,7 +10926,23 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9518
10926
  };
9519
10927
  warmed.catch(() => {});
9520
10928
  Promise.resolve(ns.installed()).then((result) => unwrap(result)).catch(() => {});
9521
- Promise.resolve(ns.version()).then((result) => unwrap(result)).catch(() => {});
10929
+ let lastVersion = null;
10930
+ const restartListeners = /* @__PURE__ */ new Set();
10931
+ const version = async () => {
10932
+ const result = unwrap(await ns.version());
10933
+ noteBoot(result.bootId);
10934
+ const previous = lastVersion;
10935
+ lastVersion = result;
10936
+ if (previous !== null && answeredByAnotherProcess(previous, result)) {
10937
+ warmCatalog = null;
10938
+ for (const listener of [...restartListeners]) listener(result);
10939
+ }
10940
+ return result;
10941
+ };
10942
+ version().catch(() => {});
10943
+ ctx.on("connection/reset", () => {
10944
+ version().catch(() => {});
10945
+ });
9522
10946
  const hostCatalog = async (args) => {
9523
10947
  if (args?.refresh === true) {
9524
10948
  const refreshed = unwrap(await ns.catalog({ refresh: true }));
@@ -9572,7 +10996,13 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
9572
10996
  pageRemoved.add(name);
9573
10997
  },
9574
10998
  restart,
9575
- version: async () => unwrap(await ns.version()),
10999
+ version,
11000
+ onRestarted: (listener) => {
11001
+ restartListeners.add(listener);
11002
+ return () => {
11003
+ restartListeners.delete(listener);
11004
+ };
11005
+ },
9576
11006
  updateStart: async (args) => {
9577
11007
  warmCatalog = null;
9578
11008
  return unwrap(await ns.updateStart(args));