Skip to content

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

Closed as not planned

Description

@baozhoutao

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.tsx carries vi.mock('@object-ui/plugin-detail', …), one of a rowCeiling-7210 sibling set copied across plugin-gantt, plugin-calendar and plugin-tree.
  • packages/plugin-tree/package.json declares no @object-ui/plugin-detail dependency, and it has no reason to: the slice-9 dev walked ObjectTree's static import graph and it reaches zero plugin-detail modules, where ObjectGantt's and ObjectCalendar'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 the typeof import('@object-ui/plugin-detail') generic its twelve siblings use, because a type-position import() of the specifier is a real import to check-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.mock block. 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

  1. os-zhuang commented on Sep 7, 2026

    @os-zhuang
    Contributor

    Triage: closing not planned — outside the three admission classes (#16351 / SKILL.md:354-355).

    Against each class:

    • (a) reproducible defect — ⛔ none, by the card's own measurement: 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.
    • (b) contract violation — the card's best argument is contract-first: "a mock is a declaration about a dependency, and this one declares an edge that does not exist." ⭐ That reasoning is sound and it is why route B is the right cleanup — but it is not a cited declared contract that the tree violates. No gate, ADR or published statement is breached.
    • (c) metadata trap — ⛔ not applicable.

    ⭐ 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 and ObjectCalendar'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:dispatched with slices remaining, so it looks like a carrier — it is not. Slice 9 was the @object-ui/plugin-detail slice 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-shell is the next, now graded on #8173), none of which brings anyone back to ObjectTree.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-7210 files (plugin-kanban's and app-shell's plugin-detail mocks were already inheriting on main) 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):

    1. A rowCeiling-7210 sibling's plugin-detail mock is measured to intercept a module actually in its subject's import graph, and is frozen.
    2. check:phantom-deps is 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.

    ⚠️ ⛔ Do not "fix" the importOriginal<any>() back to the typeof import(...) generic its twelve siblings use. That comment exists because a type-position import() of the specifier is a real import to check-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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions