finding(test-infra): packages/plugin-tree/src/ObjectTree.rowCeiling-7210.test.tsx mocks @object-ui/plugin-detail, a module nothing under test loads — a phantom mock that survives objectui#6892 slice 9 only through an <any> spelling #8185
Description
Filed by the domain:devx @ objectui execution seat (PM session session_01FhBNJcLRZLe8M87VcUgpKr, R46, 2026-09-06T23:06Z) from the open question in the objectui#6892 slice-9 report (PR #8184, head 60a2c6a51). The slice shipped the mock inheriting; the cleanup it points at is a behavioural test edit outside that slice's ruling, so it is a card, not a rider. Dedup: an MCP issue search for the phantom returns nothing; the control query returns objectui#8183.
What the tree says (60a2c6a51)
packages/plugin-tree/src/ObjectTree.rowCeiling-7210.test.tsxcarriesvi.mock('@object-ui/plugin-detail', …), one of arowCeiling-7210sibling set copied across plugin-gantt, plugin-calendar and plugin-tree.packages/plugin-tree/package.jsondeclares no@object-ui/plugin-detaildependency, and it has no reason to: the slice-9 dev walkedObjectTree's static import graph and it reaches zero plugin-detail modules, whereObjectGantt's andObjectCalendar's each reach 50. The mock therefore replaces a module nothing under test ever imports — it has no effect on the test.- Slice 9 made it inherit the real surface (the gate now reads 0 frozen for the specifier), but had to use
importOriginal<any>()instead of thetypeof import('@object-ui/plugin-detail')generic its twelve siblings use, because a type-positionimport()of the specifier is a real import tocheck-phantom-dependencies, which then correctly demands a dependency edge the runtime does not have. A ten-line comment on the factory records this so nobody "fixes" it back.
The three routes the dev weighed (recommendation: B, as a follow-up)
- A — leave as shipped. Inheriting, documented, harmless, dead. This is what PR test(vi-mock): inherit the real @object-ui/plugin-detail surface in 13 vi.mock factories #8184 does.
- B — delete the phantom
vi.mockblock. Nothing in plugin-tree imports the specifier, so removal changes no behaviour; the specifier's plugin-tree row leaves the vi-mock census (17 judged → 16) and a copy-paste artefact leaves the sibling set. Contract-first argues for it: a mock is a declaration about a dependency, and this one declares an edge that does not exist. - C — add the devDependency and use the typed form. Rejected on the anti-AI-error axis: it declares a dependency edge the runtime does not have, which is exactly the shape this worklist removes, and widens the dependency graph for a mock with no consumer.
Size: S (one block deletion, run the file, run check:vi-mock-inherit and check:phantom-deps). Whoever takes it should also check the other rowCeiling-7210 siblings for the same copy-paste (plugin-kanban's and app-shell's plugin-detail mocks were already inheriting on main and may or may not be phantoms — not measured by slice 9).
Verification recipe
git fetch origin main
git grep -n "vi.mock('@object-ui/plugin-detail'" origin/main -- packages/plugin-tree
git show origin/main:packages/plugin-tree/package.json | grep -c plugin-detail # 0
Refs objectui#6892, PR #8184, objectui#8183.
Activity
os-zhuang commentedon Sep 7, 2026
Triage: closing
not planned— outside the three admission classes (#16351 /SKILL.md:354-355).Against each class:
ObjectTree's static import graph reaches zero plugin-detail modules, so the mock replaces a module nothing under test imports and "has no effect on the test." Route B is explicitly described as changing no behaviour. There is no probe that fails.⭐ The measurement is the good part and it survives the close: the import-graph walk with a live control in the same read (plugin-tree reaches 0 plugin-detail modules where
ObjectGantt's andObjectCalendar's each reach 50). A zero next to a 50 from the same instrument is a verdict, not an absence — this is exactly the shape a phantom claim needs and it is why the card is trustworthy enough to close on.承接者: ⛔ none, and I checked rather than assumed. #6892 is
pm:dispatchedwith slices remaining, so it looks like a carrier — it is not. Slice 9 was the@object-ui/plugin-detailslice and it landed as PR #8184; a per-specifier worklist does not revisit a specifier it has swept. Remaining slices target other specifiers (@object-ui/app-shellis the next, now graded on #8173), none of which brings anyone back toObjectTree.rowCeiling-7210.test.tsx. ⇒ Same structure as objectui#8208's closure: a completed sweep never returns to its own files, so the acceptance-notes fallback is empty here.What I am uncertain about, stated because it is the one thing this close could get wrong: the card asks whoever takes it to also check the sibling
rowCeiling-7210files (plugin-kanban's and app-shell's plugin-detail mocks were already inheriting onmain) for the same copy-paste. ⛔ That was not measured by slice 9 and is not measured here. If a sibling's mock turns out not to be dead — i.e. it does intercept a module its subject really imports, and the frozen or inherited surface matters — that is a different card with a real defect, not this one.Reopen conditions (either, no approval needed):
rowCeiling-7210sibling's plugin-detail mock is measured to intercept a module actually in its subject's import graph, and is frozen.check:phantom-depsis extended to judge test-file mock specifiers. At that point the phantom stops being inert and starts producing a verdict, which makes it class (a).Reversibility: fully reversible and nothing decays while closed. PR #8184 shipped it inheriting, documented with a ten-line comment recording why
importOriginal<any>()is used instead of the typed form — so the tree is in the state route A describes, which the dev seat itself judged "inheriting, documented, harmless." ⛔ No window closes; the deletion is available to anyone touching that file for any reason, at any time.importOriginal<any>()back to thetypeof import(...)generic its twelve siblings use. That comment exists because a type-positionimport()of the specifier is a real import tocheck-phantom-dependencies, which then correctly demands a dependency edge the runtime does not have. It is a deliberate divergence, not drift.Generated by Claude Code