Observation-class finding, measured while doing objectui#4389's dependency-direction check. Nothing a user hits today — no source file currently takes the bad import, so nothing is broken. Filing so the trap is recorded rather than re-discovered by the next agent that measures this boundary.
What was measured
@object-ui/core is React-free by construction and by declaration:
packages/core/package.json — dependencies are @object-ui/types, @objectstack/formula, @objectstack/spec, lodash, zod. No react, and peerDependencies is empty.
packages/core/src — 0 files with a .tsx extension, 0 from 'react' imports, 0 exported hooks.
packages/core/node_modules/ — @object-ui, @objectstack, lodash, typescript, vitest, zod. No react.
AGENTS.md section 3 pins the constraint in words too: core is "No UI-lib deps. Logic only."
And yet resolution succeeds:
$ node -e "console.log(require.resolve('react', { paths: ['packages/core/src'] }))"
/home/user/objectui-4389/node_modules/.pnpm/react@19.2.8/node_modules/react/index.js
Root cause is benign and permanent: the root package.json carries devDependencies.react (19.2.8, needed by the DOM vitest projects), so a node_modules/react symlink exists at the workspace root:
node_modules/react -> .pnpm/react@19.2.8/node_modules/react
Node's — and TypeScript's — upward directory walk reaches that symlink from any package directory. pnpm's isolated node_modules prevents phantom deps between workspace packages, but not against the root's own dev dependencies.
Why it matters
A dependency-direction check performed by resolution ("can core import react? let me resolve it — yes") returns the wrong answer. The authoritative check is the package's own declared dependencies / peerDependencies, not whether the specifier resolves in a hoisted workspace. The two disagree here, and the disagreement is silent: a react import added to packages/core would typecheck, build, and pass every local suite, then break the published @object-ui/core for any consumer that does not already have React installed — the classic phantom-dependency failure, discovered at install time by someone else.
This is not hypothetical as a method error: objectui#4389's brief asks the implementer to "measure the actual import graph", and the resolution probe is the obvious way to do that.
Not proposing a fix here
For triage. The shape a fix could take, cheapest first:
- Nothing — record the trap in AGENTS.md beside the topology table, so "measure the import graph" is understood as "read the declared deps".
- A
check:* script asserting that every bare-specifier import under packages/*/src resolves to a dependency that package declares (dep, peerDep, or devDep for test files). This is the mechanical version, and it would also catch the reverse direction.
- An ESLint
no-restricted-imports / import/no-extraneous-dependencies rule per package. Heavier config surface than 2 for the same property.
Related in family, both closed: objectui#3367 (toPredicateInput duplicated core-side and react-side, resolved by having react re-export core — i.e. the layering direction this finding is about) and objectui#3741 (an unbounded peerDependencies.react range).
Generated by Claude Code
Observation-class finding, measured while doing objectui#4389's dependency-direction check. Nothing a user hits today — no source file currently takes the bad import, so nothing is broken. Filing so the trap is recorded rather than re-discovered by the next agent that measures this boundary.
What was measured
@object-ui/coreis React-free by construction and by declaration:packages/core/package.json—dependenciesare@object-ui/types,@objectstack/formula,@objectstack/spec,lodash,zod. Noreact, andpeerDependenciesis empty.packages/core/src— 0 files with a.tsxextension, 0from 'react'imports, 0 exported hooks.packages/core/node_modules/—@object-ui,@objectstack,lodash,typescript,vitest,zod. Noreact.AGENTS.md section 3 pins the constraint in words too: core is "No UI-lib deps. Logic only."
And yet resolution succeeds:
Root cause is benign and permanent: the root
package.jsoncarriesdevDependencies.react(19.2.8, needed by the DOM vitest projects), so anode_modules/reactsymlink exists at the workspace root:Node's — and TypeScript's — upward directory walk reaches that symlink from any package directory. pnpm's isolated
node_modulesprevents phantom deps between workspace packages, but not against the root's own dev dependencies.Why it matters
A dependency-direction check performed by resolution ("can core import react? let me resolve it — yes") returns the wrong answer. The authoritative check is the package's own declared
dependencies/peerDependencies, not whether the specifier resolves in a hoisted workspace. The two disagree here, and the disagreement is silent: areactimport added topackages/corewould typecheck, build, and pass every local suite, then break the published@object-ui/corefor any consumer that does not already have React installed — the classic phantom-dependency failure, discovered at install time by someone else.This is not hypothetical as a method error: objectui#4389's brief asks the implementer to "measure the actual import graph", and the resolution probe is the obvious way to do that.
Not proposing a fix here
For triage. The shape a fix could take, cheapest first:
check:*script asserting that every bare-specifierimportunderpackages/*/srcresolves to a dependency that package declares (dep, peerDep, or devDep for test files). This is the mechanical version, and it would also catch the reverse direction.no-restricted-imports/import/no-extraneous-dependenciesrule per package. Heavier config surface than 2 for the same property.Related in family, both closed: objectui#3367 (
toPredicateInputduplicated core-side and react-side, resolved by having react re-export core — i.e. the layering direction this finding is about) and objectui#3741 (an unboundedpeerDependencies.reactrange).Generated by Claude Code