…rkspace-root.mjs, so tests and the CLI generator read the same layout under the monorepo's ui/ (objectstack#25733)
Standalone, the objectui tree is the pnpm workspace root. Under the
objectstack monorepo it is ui/, and the workspace root (with
pnpm-workspace.yaml and node_modules/.pnpm) is one level up.
- scripts/workspace-root.mjs gains virtualStorePath(): the workspace
root's pnpm virtual store. check-doc-snippet-types' findInstalledCopy
reads it, so the UNDECLARED and ROOT-DECLARED controls stay live
under ui/.
- @object-ui/cli currentContext() asks isWorkspaceRoot(cwd), the
predicate dev, serve and build already use. Same expression, so a
user's project behaves as before; a fixture workspace pins the
in-workspace map and a member pins the standalone map.
- The Console vendor-chunk and client-dist tests read their standalone
and merged arms off fixture layouts; the checkout is asked only which
arm its own layout selects. The vite regexes are unchanged.
- check:ui-root-lookups gains the workspace-file-at-ui-root shape: a
join of pnpm-workspace.yaml or node_modules/.pnpm onto the objectui
tree.
Part of objectstack#25677.
Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018KHW3U8PmJue4aVvtXutRb
Requested by Tesla · project thread
Part of objectstack#25733 (child 2e of the epic). Part of objectstack#25677.
Clause-②: no
Metadata-protocol: none
Why
Standalone, the objectui tree is the pnpm workspace root. Under the objectstack monorepo it is
ui/, and the workspace root (pnpm-workspace.yaml,node_modules/.pnpm) is one level up.ui/node_modulesthen holds only the ui root package's own links. There is noui/node_modules/.pnpmand noui/pnpm-workspace.yaml.Measured on a scratch worktree of the objectstack verification head
cfa1676afc, with this tree applied underui/: 12 tests went red across 5 files, before this change.check-doc-snippet-types.test.ts, 2 reds. The UNDECLARED and ROOT-DECLARED controls looked forTREE/node_modules/.pnpm, so both read "NOT INSTALLED", andcheck:doc-snippetsexited 2.vite-objectstack-client-dist.test.ts, 5 reds. The installed client is a workspace link topackages/client, not a store symlink, and that link carries its ownnode_modules.vite-objectstack-spec-dist.test.ts, 2 reds, andvite-objectstack-workspace.test.ts, 2 reds. The checkout's own console has the framework'spackages/three levels up, so its tests are the merged arm and not the baseline literals.packages/cliapp-generator.test.ts, 1 red. The in-workspace case used the checkout root as "a workspace root", and underui/it is a member.What changes
scripts/workspace-root.mjsgainsvirtualStorePath(root = uiRoot()), which is the workspace root'snode_modules/.pnpm. It throws, likeworkspaceRoot(), when no workspace governs the tree.findInstalledCopyincheck-doc-snippet-types.mjsreads it, and returns null with no workspace. The relative path it reports reads../node_modules/.pnpm/…underui/.@object-ui/clicurrentContext()returnsisMonorepo: isWorkspaceRoot(cwd). That is the predicatedev,serveandbuildalready use (utils/workspace-vite.ts, objectui#3890), and it is the same expression the function used to spell inline,existsSync(join(cwd, 'pnpm-workspace.yaml')). See the proposal below.TMP/objectui/apps/console, nothing beside it), and the merged arm off the existing monorepo-shaped fixture. The checkout itself is asked only which arm its own layout names, withisNestedWorkspace()as the independent witness.vite-objectstack-spec-dist.test.tscompares the config's vendor test with the arm the layout selects, and pins the baseline literal separately. The regexes inscripts/vite-objectstack-workspace.tsare unchanged.node_modules/.pnpm/@objectstack+client@1.0.0/node_modules/{client,core}and a console symlink). The out-of-tree copy leaves the package's ownnode_modulesbehind.check:ui-root-lookupsgains theworkspace-file-at-ui-rootshape. It fires on ajoin/resolve(or a template led by a substitution) that namespnpm-workspace.yamlornode_modules/.pnpmand whose base resolves to the objectui tree. The base can beuiRoot(),UI_ROOT,__dirname/import.meta.dirname/fileURLToPath(import.meta.url)climbed to the tree, or aconstor parameter default bound to one of those. It fires once, where the path first names the file. It adds 14 self-test cases, 51 in total. The allowlist entry forapp-generator.tsis removed, because it would read stale.Proposal: what "runs at a workspace root" means for
currentContext()currentContext()is product code, so its meaning is written in its doc comment and proposed here. "Runs at a workspace root" means cwd itself holdspnpm-workspace.yaml, the same predicatedev,serveandbuilduse to skip install and to alias. A workspace member gets the standalone map. That covers a user's package inside their own monorepo, and objectui underui/. The generator does not walk up, because walking up would hand a member the empty in-workspace maps, and no alias table backs those maps at a member's cwd.A user's project behaves exactly as before. With the file in cwd it gets the in-workspace map, and without it the standalone map, because the expression is the same. Two tests pin it, both from fixtures: the in-workspace case runs from a fixture workspace root, and a new case runs from a fixture member and expects the standalone map.
Reverse check
The new gate shape, run on the pristine #13491 head (
f8fa141), fires at exactly the two sites this PR changes, and nowhere else outside the allowlist:scripts/check-doc-snippet-types.mjsfindInstalledCopy,workspace-file-at-ui-root,join(root, 'node_modules', '.pnpm')packages/cli/src/utils/app-generator.tscurrentContext,marker-probe,existsSync(join(cwd, 'pnpm-workspace.yaml'))Verification
objectui standalone, on this branch (main
6c3340cplus this commit, afterpnpm run setup). Every command exited 0:pnpm check:ui-root-lookups·node scripts/check-ui-root-lookups.mjs --self-test(51 cases) ·pnpm check:new-line-citations·node scripts/check-changeset-presence.mjs·node scripts/check-changeset-overwrite.mjs·pnpm check:changeset-claims·pnpm type-check:scripts·pnpm check:test-path-roots·pnpm check:pre-install-import-graph·pnpm check:control-bytes·pnpm exec eslintover the changed files (0 errors) ·pnpm --filter @object-ui/cli test(30 files, 416 tests).pnpm exec vitest run scripts/__tests__was run whole in three shards (--shard=K/3, under the verify lock): 196 files passed, 5787 tests. Two files were skipped by their ownIS_CHILDconditions.Merged layout. I used a scratch worktree of the objectstack verification head
cfa1676afc. I installed it and ran the CI build of the seven@objectstackpackages, plus the@object-uiclosurecheck:doc-snippetsbuilds. Then I applied #13491's diff and this diff underui/withgit apply --directory=ui. This diff applies to the landed main unchanged; it is byte-identical apart from blob ids.vitest run --root . packages/cli/src/__tests__/app-generator.test.ts: 48/48 passed.pnpm --dir ui run check:ui-root-lookups: OK, 6 allowlist entries live.pnpm --dir ui run check:doc-snippets: exit 0, 811 blocks compiled. Both controls are live and installed at../node_modules/.pnpm/….check:doc-snippetsexited 2 with both controls "NOT INSTALLED".Bound of the new gate shape
Two of the three classes this card fixes are not syntactic joins: the CLI test treating the checkout root as a workspace root, and the vite sibling-directory probe. They are out of the gate's reach, and so are a path assembled through
letor across files. The gate header states this bound.Out of scope
Pin scripts (objectstack#25711), spec drift (objectstack#25727), QUICK_REFERENCE (objectstack#25697), dependency versions (objectstack#25726),
.github/**.🤖 Generated with Claude Code
https://claude.ai/code/session_018KHW3U8PmJue4aVvtXutRb