magus-cli 7.7.0 → 7.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +1 -1
  2. package/package.json +4 -4
  3. package/src/__tests__/blackbox-parity.test.ts +45 -26
  4. package/src/__tests__/blackbox-plugin-state.test.ts +25 -6
  5. package/src/__tests__/blind/plugin-deps/doctor.test.ts +622 -0
  6. package/src/__tests__/blind/plugin-deps/helpers.ts +340 -0
  7. package/src/__tests__/blind/plugin-deps/hook.test.ts +328 -0
  8. package/src/__tests__/blind/plugin-deps/probe.test.ts +694 -0
  9. package/src/__tests__/blind/plugin-deps/read-only.test.ts +96 -0
  10. package/src/__tests__/blind/plugin-deps/schema.test.ts +390 -0
  11. package/src/__tests__/blind-780-cli-tools.test.ts +796 -0
  12. package/src/__tests__/blind-780-deprecated.test.ts +631 -0
  13. package/src/__tests__/blind-780-failure-reasons.test.ts +1004 -0
  14. package/src/__tests__/blind-780-plugin-resolution.test.ts +431 -0
  15. package/src/__tests__/blind-780-review2.test.ts +1178 -0
  16. package/src/__tests__/cli-apply-seams.test.ts +2 -67
  17. package/src/__tests__/cli-tool-commands.test.ts +131 -6
  18. package/src/__tests__/cli-tools-adapter.test.ts +52 -2
  19. package/src/__tests__/cli-tools-plugin-managed.test.tsx +83 -0
  20. package/src/__tests__/cli-update-view.test.ts +3 -3
  21. package/src/__tests__/deprecated-remove.test.ts +827 -0
  22. package/src/__tests__/deps-adapter.test.ts +191 -0
  23. package/src/__tests__/deps-check-hook.test.ts +220 -0
  24. package/src/__tests__/deps-plan.test.ts +896 -0
  25. package/src/__tests__/deps-report.test.ts +197 -0
  26. package/src/__tests__/doctor-bins.test.ts +134 -36
  27. package/src/__tests__/doctor-json.test.ts +261 -0
  28. package/src/__tests__/doctor.test.ts +159 -1
  29. package/src/__tests__/failure-popup-review2.test.tsx +896 -0
  30. package/src/__tests__/failure-popup.test.tsx +419 -0
  31. package/src/__tests__/final-fixes-780.test.tsx +740 -0
  32. package/src/__tests__/fixtures/bins.ts +55 -0
  33. package/src/__tests__/format-update-outcome.test.ts +6 -32
  34. package/src/__tests__/gitignore-profile.test.ts +1 -9
  35. package/src/__tests__/install-plan.test.ts +15 -1
  36. package/src/__tests__/marketplace-refresh.test.ts +9 -3
  37. package/src/__tests__/models-screen-state.test.ts +18 -40
  38. package/src/__tests__/no-shell-spawn.test.ts +77 -0
  39. package/src/__tests__/npm-package-name.test.ts +6 -9
  40. package/src/__tests__/plugin-requires.test.ts +138 -153
  41. package/src/__tests__/plugin-resolution.test.ts +52 -13
  42. package/src/__tests__/plugin-row-text.test.ts +30 -37
  43. package/src/__tests__/profile-dependencies.test.ts +7 -0
  44. package/src/__tests__/profile-startup-old-format.test.ts +0 -3
  45. package/src/__tests__/profile-startup.test.ts +40 -12
  46. package/src/__tests__/requires-probe.test.ts +858 -0
  47. package/src/__tests__/resolver.test.ts +129 -52
  48. package/src/__tests__/run.test.ts +108 -0
  49. package/src/__tests__/stale-load-repair.test.ts +31 -23
  50. package/src/__tests__/tool-probe.test.ts +1 -1
  51. package/src/__tests__/toolchain.test.ts +123 -52
  52. package/src/__tests__/update-apply.test.ts +13 -29
  53. package/src/__tests__/update-plan.test.ts +24 -18
  54. package/src/__tests__/useStaleLoadModal.test.ts +1 -57
  55. package/src/__tests__/version-unknown-state.test.ts +15 -17
  56. package/src/cli/ansi.ts +2 -7
  57. package/src/cli/deps-view.ts +213 -0
  58. package/src/cli/doctor.ts +320 -54
  59. package/src/cli/install.ts +55 -71
  60. package/src/cli/profile.ts +20 -2
  61. package/src/cli/router.ts +6 -2
  62. package/src/cli/update-view.ts +58 -15
  63. package/src/cli/update.ts +55 -12
  64. package/src/data/cli-tools.ts +24 -9
  65. package/src/services/active-closure.ts +3 -3
  66. package/src/services/catalog-cache-store.ts +8 -6
  67. package/src/services/claude-cli.ts +106 -19
  68. package/src/services/claude-settings.ts +88 -384
  69. package/src/services/cli-tool-commands.ts +104 -3
  70. package/src/services/deps-plan.ts +962 -0
  71. package/src/services/deps-report.ts +265 -0
  72. package/src/services/doctor-bins.ts +115 -43
  73. package/src/services/install-plan.ts +11 -1
  74. package/src/services/local-marketplace.ts +12 -206
  75. package/src/services/marketplace-refresh.ts +4 -4
  76. package/src/services/plugin-env-needs.ts +69 -0
  77. package/src/services/plugin-manager.ts +35 -37
  78. package/src/services/plugin-requires.ts +93 -162
  79. package/src/services/plugin-resolution.ts +34 -42
  80. package/src/services/profile-activate.ts +1 -2
  81. package/src/services/profile-startup.ts +18 -43
  82. package/src/services/profile-state.ts +72 -2
  83. package/src/services/resolver.ts +263 -59
  84. package/src/services/tool-probe.ts +18 -25
  85. package/src/services/toolchain.ts +117 -111
  86. package/src/services/update-engine.ts +345 -45
  87. package/src/services/update-plan.ts +296 -36
  88. package/src/shared/plugin/hooks/bun-missing.sh +39 -0
  89. package/src/shared/plugin/hooks/deps-check.ts +224 -0
  90. package/src/shared/plugin/hooks/lib/requires-probe.ts +1183 -0
  91. package/src/types/index.ts +60 -16
  92. package/src/ui/App.tsx +81 -171
  93. package/src/ui/adapters/cliToolsAdapter.ts +252 -12
  94. package/src/ui/adapters/depsAdapter.ts +129 -0
  95. package/src/ui/adapters/failureReport.ts +137 -0
  96. package/src/ui/adapters/pluginsAdapter.ts +297 -45
  97. package/src/ui/adapters/startupReports.ts +260 -0
  98. package/src/ui/components/modals/FailureListModal.tsx +231 -0
  99. package/src/ui/components/modals/MessageModal.tsx +7 -12
  100. package/src/ui/components/modals/ModalContainer.tsx +10 -5
  101. package/src/ui/components/modals/index.ts +1 -0
  102. package/src/ui/hooks/startupQueue.ts +56 -0
  103. package/src/ui/hooks/useGitignoreModal.ts +30 -52
  104. package/src/ui/hooks/useProfileStartupModal.ts +119 -142
  105. package/src/ui/hooks/useStaleLoadModal.ts +35 -65
  106. package/src/ui/keymap.ts +14 -1
  107. package/src/ui/renderers/cliToolRenderers.tsx +135 -2
  108. package/src/ui/renderers/mcpRenderers.tsx +110 -3
  109. package/src/ui/renderers/pluginRenderers.tsx +77 -6
  110. package/src/ui/renderers/pluginRowText.ts +25 -25
  111. package/src/ui/renderers/pluginStatusText.ts +18 -1
  112. package/src/ui/screens/AliasScreen.tsx +38 -45
  113. package/src/ui/screens/CliToolsScreen.tsx +199 -98
  114. package/src/ui/screens/EnvVarsScreen.tsx +17 -5
  115. package/src/ui/screens/GitignoreScreen.tsx +48 -67
  116. package/src/ui/screens/McpRegistryScreen.tsx +67 -38
  117. package/src/ui/screens/McpScreen.tsx +46 -19
  118. package/src/ui/screens/ModelsScreen.tsx +46 -86
  119. package/src/ui/screens/PluginsScreen.tsx +315 -259
  120. package/src/ui/screens/ProfilesScreen.tsx +79 -40
  121. package/src/ui/screens/SkillsScreen.tsx +83 -80
  122. package/src/ui/screens/StylesScreen.tsx +211 -275
  123. package/src/ui/state/AppContext.tsx +66 -44
  124. package/src/ui/state/DimensionsContext.tsx +3 -22
  125. package/src/ui/state/reducer.ts +12 -100
  126. package/src/ui/state/types.ts +48 -69
  127. package/src/utils/command-utils.ts +4 -5
  128. package/src/utils/run.ts +123 -52
  129. package/src/utils/string-utils.ts +9 -0
  130. package/src/utils/user-path.ts +60 -0
  131. package/src/__tests__/go-module.test.ts +0 -43
  132. package/src/__tests__/notices-rules.test.ts +0 -476
  133. package/src/__tests__/notices.test.tsx +0 -604
  134. package/src/__tests__/plugin-setup.test.ts +0 -1016
  135. package/src/__tests__/shell-script-callers.test.ts +0 -92
  136. package/src/__tests__/styles-status-line.test.ts +0 -72
  137. package/src/__tests__/version-snapshot.test.ts +0 -212
  138. package/src/services/plugin-setup.ts +0 -965
  139. package/src/services/version-snapshot.ts +0 -189
  140. package/src/utils/go-module.ts +0 -36
package/README.md CHANGED
@@ -155,7 +155,7 @@ check. So does `magus install --check`.
155
155
  | MCP | Add MCP servers from a curated catalog |
156
156
  | Settings | Claude Code settings, from a catalog of known keys |
157
157
  | Profiles | Saved plugin sets (the TUI's own, older profile concept) |
158
- | CLI Tools | Install claudish, mnemex and friends via the right package manager |
158
+ | CLI Tools | Install and update CLI tools via the right package manager; a tool a plugin installs from a release shows as managed by that plugin |
159
159
  | Git State | Gitignore conventions and repo hygiene |
160
160
  | Alias | Shell alias + flag management for launching `claude` |
161
161
  | Styles | Communication style presets, composed into one native output style |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "magus-cli",
3
- "version": "7.7.0",
3
+ "version": "7.9.0",
4
4
  "description": "TUI tool for managing Claude Code plugins, MCPs, and configuration",
5
5
  "type": "module",
6
6
  "main": "src/main.tsx",
@@ -65,8 +65,8 @@
65
65
  "typescript": "^5.6.3"
66
66
  },
67
67
  "optionalDependencies": {
68
- "magus-cli-darwin-arm64": "7.7.0",
69
- "magus-cli-darwin-x64": "7.7.0",
70
- "magus-cli-linux-x64": "7.7.0"
68
+ "magus-cli-darwin-arm64": "7.9.0",
69
+ "magus-cli-darwin-x64": "7.9.0",
70
+ "magus-cli-linux-x64": "7.9.0"
71
71
  }
72
72
  }
@@ -17,11 +17,11 @@
17
17
  * _synthetic exist only to reach states the real data lacks.
18
18
  */
19
19
  import { describe, expect, test } from "bun:test";
20
- import { cliTools } from "../data/cli-tools.js";
21
20
  import { selectProfileId } from "../services/active-closure.js";
22
21
  import type { PluginInfo } from "../services/plugin-manager.js";
23
22
  import { resolveProfile } from "../services/resolver.js";
24
23
  import { type ToolFacts, specForCatalogTool } from "../services/tool-probe.js";
24
+ import { managedBins, managedTool } from "../services/toolchain.js";
25
25
  import {
26
26
  type BinUpdateItem,
27
27
  type PluginUpdateItem,
@@ -31,7 +31,7 @@ import {
31
31
  planPluginUpdates,
32
32
  } from "../services/update-plan.js";
33
33
  import type {
34
- BinRequirement,
34
+ ManagedTool,
35
35
  Marketplace,
36
36
  ProfileManifest,
37
37
  ResolvedClosure,
@@ -42,6 +42,7 @@ import {
42
42
  buildPluginBrowserItems,
43
43
  } from "../ui/adapters/pluginsAdapter.js";
44
44
  import { pluginRowText } from "../ui/renderers/pluginRowText.js";
45
+ import { ownedAsDeclared, selected, testCatalogue } from "./fixtures/bins.js";
45
46
 
46
47
  // ── fixture ────────────────────────────────────────────────────────────────
47
48
 
@@ -199,7 +200,7 @@ function makeManifest(): ProfileManifest {
199
200
  };
200
201
  }
201
202
 
202
- const PLUGIN_BINS: Record<string, BinRequirement[]> = {
203
+ const PLUGIN_BINS: Record<string, ManagedTool[]> = {
203
204
  "terminal@magus": [
204
205
  { name: "tmux", via: "brew", check: "tmux -V" },
205
206
  {
@@ -215,8 +216,8 @@ const PLUGIN_BINS: Record<string, BinRequirement[]> = {
215
216
  ],
216
217
  };
217
218
 
218
- async function fixtureBins(pluginId: string): Promise<BinRequirement[]> {
219
- return (PLUGIN_BINS[pluginId] ?? []).map((b) => ({ ...b }));
219
+ async function fixtureBins(pluginId: string) {
220
+ return (PLUGIN_BINS[pluginId] ?? []).map((b) => selected(b));
220
221
  }
221
222
 
222
223
  function facts(name: string, over: Partial<ToolFacts>): ToolFacts {
@@ -330,7 +331,9 @@ async function runBothPaths(): Promise<Paths> {
330
331
  if (profileId === null) throw new Error("fixture: no profile selected");
331
332
  const closure = await resolveProfile(manifest, profileId, {
332
333
  binResolver: fixtureBins,
334
+ ownedResolver: ownedAsDeclared,
333
335
  });
336
+ const tools = managedBins(closure.bins);
334
337
 
335
338
  // CLI path
336
339
  const cliPluginList = planPluginUpdates(
@@ -359,18 +362,18 @@ async function runBothPaths(): Promise<Paths> {
359
362
  // tools
360
363
  const factList = makeFacts();
361
364
  const cliBins = new Map(
362
- planBinUpdates(closure.bins, factList).map((b) => [b.name, b]),
365
+ planBinUpdates(tools, factList).map((b) => [b.name, b]),
363
366
  );
364
367
  const factsByName = new Map(factList.map((f) => [f.name, f]));
365
- const declared = new Set(closure.bins.map((b) => b.name));
368
+ const declared = new Set(tools.map((b) => b.name));
366
369
  const tuiTools = new Map<string, ReturnType<typeof buildToolRow>>();
367
- for (const tool of cliTools) {
370
+ for (const tool of testCatalogue()) {
368
371
  if (!declared.has(tool.name)) continue;
369
372
  tuiTools.set(
370
373
  tool.name,
371
374
  buildToolRow(
372
375
  tool,
373
- specForCatalogTool(tool, closure.bins, undefined),
376
+ specForCatalogTool(tool, tools, undefined),
374
377
  factsByName.get(tool.name),
375
378
  ),
376
379
  );
@@ -430,7 +433,15 @@ describe("R7 parity: one fixture through the CLI path and the TUI path", () => {
430
433
  if (!row) throw new Error(`no TUI row for ${id}`);
431
434
  const { text } = pluginRowText(row);
432
435
  expect({ id, empty: text.trim() === "" }).toEqual({ id, empty: false });
433
- if (cli.note) {
436
+ // A deprecated row says its verdict and the key that acts on it, in the
437
+ // same words on every row, rather than the note — which the CLI does not
438
+ // print for it either (its DEPRECATED heading carries the fact).
439
+ if (cli.action === "remove") {
440
+ expect({ id, text }).toEqual({
441
+ id,
442
+ text: expect.stringContaining("deprecated — press a to remove"),
443
+ });
444
+ } else if (cli.note) {
434
445
  expect({ id, containsNote: text.includes(cli.note) }).toEqual({
435
446
  id,
436
447
  containsNote: true,
@@ -490,13 +501,13 @@ describe("R7 parity: one fixture through the CLI path and the TUI path", () => {
490
501
  reason: "catalog-unverified",
491
502
  target: null,
492
503
  },
493
- // Was `moved`, which guessed a destination from a bare name match and
494
- // could not tell a move from a dual publication. An orphan is simply
495
- // not offered.
496
- "seo@magus": { action: "unknown", reason: "not-offered", target: null },
504
+ // Its marketplace no longer lists it, but magus-marketing lists the
505
+ // same name: it MOVED. Not removed — the plugin still exists — and
506
+ // nothing is installed in its place: `unknown`, never applied.
507
+ "seo@magus": { action: "unknown", reason: "moved", target: null },
497
508
  "legacy-tool@magus": {
498
- action: "unknown",
499
- reason: "not-offered",
509
+ action: "remove",
510
+ reason: "deprecated",
500
511
  target: null,
501
512
  },
502
513
  "pinned-behind@magus": {
@@ -535,7 +546,7 @@ describe("R7 parity: one fixture through the CLI path and the TUI path", () => {
535
546
  expect(undeliverable?.note).toBeTruthy();
536
547
 
537
548
  // declared but absent from the catalog: a CLI item, never a TUI row
538
- expect(p.cliPlugins.get("gone@magus")?.action).toBe("unknown");
549
+ expect(p.cliPlugins.get("gone@magus")?.action).toBe("remove");
539
550
  expect(p.tuiRows.has("gone@magus")).toBe(false);
540
551
  });
541
552
 
@@ -584,9 +595,11 @@ describe("R7 parity: one fixture through the CLI path and the TUI path", () => {
584
595
  // − pinned-unorderable (sha install against a semver pin)
585
596
  // What is left is unknown because something is genuinely wrong or unread:
586
597
  // claudish catalog-unverified — the check itself failed
587
- // seo@magus not-offered — an orphan
588
- // legacy-tool not-offered — an orphan
589
- expect(cliCounts.unknown).toBe(3);
598
+ // seo@magus moved — magus-marketing lists the same name
599
+ // The orphan nothing else lists is deprecated:
600
+ // legacy-tool remove — no longer published
601
+ expect(cliCounts.unknown).toBe(2);
602
+ expect(cliCounts.remove).toBe(1);
590
603
  expect(cliCounts.update).toBeGreaterThanOrEqual(2); // designer, pinned-behind
591
604
  });
592
605
 
@@ -614,7 +627,8 @@ describe("R7 parity: one fixture through the CLI path and the TUI path", () => {
614
627
  target: cli?.target,
615
628
  });
616
629
  // the TUI judges a declared tool against the closure's own spec
617
- const declared = p.closure.bins.find((b) => b.name === name);
630
+ const bin = p.closure.bins.find((b) => b.name === name);
631
+ const declared = bin ? managedTool(bin) : null;
618
632
  expect({ name, via: tui?.spec.via, version: tui?.spec.version }).toEqual({
619
633
  name,
620
634
  via: declared?.via,
@@ -687,7 +701,8 @@ describe("fixture discriminates the pre-change logic", () => {
687
701
  const p = await runBothPaths();
688
702
 
689
703
  for (const name of ["tmux", "browser-use", "mcp"]) {
690
- const spec = p.closure.bins.find((b) => b.name === name);
704
+ const bin = p.closure.bins.find((b) => b.name === name);
705
+ const spec = bin ? managedTool(bin) : null;
691
706
  expect(legacyPlanBin(true, Boolean(spec?.version))).toBe("upgrade");
692
707
  expect(p.cliBins.get(name)?.action).toBe("current");
693
708
  }
@@ -716,12 +731,16 @@ describe("fixture discriminates the pre-change logic", () => {
716
731
 
717
732
  // Where they still disagree outright: an orphan carries neither
718
733
  // `hasUpdate` nor `updateCheckFailed`, so the flags call it healthy while
719
- // the classifier says no marketplace offers it at all.
720
- for (const id of ["seo@magus", "legacy-tool@magus"]) {
734
+ // the classifier says it moved, or is no longer published and removes it.
735
+ const orphans: Array<[string, string, string]> = [
736
+ ["seo@magus", "unknown", "moved"],
737
+ ["legacy-tool@magus", "remove", "deprecated"],
738
+ ];
739
+ for (const [id, action, reason] of orphans) {
721
740
  const info = p.catalog.find((c) => c.id === id) as PluginInfo;
722
741
  expect(legacyFlagState(info)).toBe("healthy");
723
- expect(p.tuiRows.get(id)?.status?.action).toBe("unknown");
724
- expect(p.tuiRows.get(id)?.status?.reason).toBe("not-offered");
742
+ expect(p.tuiRows.get(id)?.status?.action).toBe(action);
743
+ expect(p.tuiRows.get(id)?.status?.reason).toBe(reason);
725
744
  }
726
745
  });
727
746
  });
@@ -90,7 +90,7 @@ describe("indexCatalog", () => {
90
90
  });
91
91
 
92
92
  describe("classifyPlugin — one state plus a human reason", () => {
93
- test("declared, absent from the catalog, marketplace read: unknown / not-offered, no target", () => {
93
+ test("declared, absent from the catalog, marketplace read: remove / deprecated, no target", () => {
94
94
  const idx = indexCatalog([info("dev@magus", "1.0.0", "1.0.0")]);
95
95
 
96
96
  const got = classifyPlugin("gone@magus", "latest", idx);
@@ -100,8 +100,8 @@ describe("classifyPlugin — one state plus a human reason", () => {
100
100
  reason: got.reason,
101
101
  target: got.target,
102
102
  }).toEqual({
103
- action: "unknown",
104
- reason: "not-offered",
103
+ action: "remove",
104
+ reason: "deprecated",
105
105
  target: null,
106
106
  });
107
107
  expect(got.note).toBeTruthy();
@@ -219,7 +219,11 @@ describe("classifyPlugin — one state plus a human reason", () => {
219
219
  // version is `current` now, not unknown. It is the negative control
220
220
  // below — without it this loop would pass even if every state collapsed
221
221
  // into one.
222
- for (const id of ["b@magus", "c@magus", "d@magus", "e@nowhere"]) {
222
+ //
223
+ // Neither are `b@magus` (an orphan of a marketplace we read) and `d@magus`
224
+ // (declared, listed nowhere, same marketplace): both are DEPRECATED now, a
225
+ // `remove` — not a guess. They are the second control, after the loop.
226
+ for (const id of ["c@magus", "e@nowhere"]) {
223
227
  const got = classifyPlugin(id, "latest", idx);
224
228
  expect({ id, action: got.action }).toEqual({ id, action: "unknown" });
225
229
  expect({ id, target: got.target, hasNote: Boolean(got.note) }).toEqual({
@@ -234,6 +238,12 @@ describe("classifyPlugin — one state plus a human reason", () => {
234
238
  const a = classifyPlugin("a@magus", "latest", idx);
235
239
  expect(a.action).toBe("current");
236
240
  expect(a.note).toBeUndefined();
241
+ for (const id of ["b@magus", "d@magus"]) {
242
+ expect({ id, action: classifyPlugin(id, "latest", idx).action }).toEqual({
243
+ id,
244
+ action: "remove",
245
+ });
246
+ }
237
247
  });
238
248
  });
239
249
 
@@ -318,6 +328,7 @@ describe("countPluginStates", () => {
318
328
  repair: 1,
319
329
  current: 1,
320
330
  "stale-load": 0,
331
+ remove: 0,
321
332
  unknown: 1,
322
333
  });
323
334
  });
@@ -329,6 +340,7 @@ describe("countPluginStates", () => {
329
340
  repair: 0,
330
341
  current: 0,
331
342
  "stale-load": 0,
343
+ remove: 0,
332
344
  unknown: 0,
333
345
  });
334
346
  });
@@ -499,8 +511,12 @@ describe("pluginRowText", () => {
499
511
  // The fixture is an ORPHAN at 0.0.0, not a plain 0.0.0 install. A plain one
500
512
  // is `current` with no note now, which would leave nothing for this test to
501
513
  // find and let the gate come back unnoticed.
514
+ //
515
+ // Its marketplace is one we did NOT read, so it stays `unknown` with a note.
516
+ // An orphan of a marketplace we read is deprecated now, whose row says
517
+ // "deprecated — press a to remove" instead of its note.
502
518
  test("a note renders on a 0.0.0 install — no version gate", () => {
503
- const plugin = info("zero-version@magus", null, "0.0.0", {
519
+ const plugin = info("zero-version@unread-mp", null, "0.0.0", {
504
520
  isOrphaned: true,
505
521
  });
506
522
  const sibling = info("dev@magus", "1.0.0", "1.0.0");
@@ -529,8 +545,11 @@ describe("pluginRowText", () => {
529
545
  expect(out.text).toContain(pluginStatusText(status).suffix);
530
546
  });
531
547
 
548
+ // An orphan whose marketplace was not read; see the note on the 0.0.0 test.
532
549
  test("an orphan row renders its note", () => {
533
- const plugin = info("legacy@magus", null, "0.5.0", { isOrphaned: true });
550
+ const plugin = info("legacy@unread-mp", null, "0.5.0", {
551
+ isOrphaned: true,
552
+ });
534
553
  const sibling = info("dev@magus", "1.0.0", "1.0.0");
535
554
  const status = classifyPlugin(
536
555
  plugin.id,