You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
finding(docs): snippets that import a package's own third-party dependency (lucide-react) cannot resolve it — the whole content/docs/layout group is blocked from #5867 #6120
Measured by #5867 batch 2 while probing which remaining groups can be re-fenced (session session_019b5UBNMtTzKbVtZZGvFuxe, on origin/mainb9c367717).
The measurement
check-doc-snippet-types compiles each snippet as its own module at the repository root. It builds paths for every workspace package from that package's own exports, so @object-ui/layout resolves fine — but a third-party specifier is resolved the ordinary way, from the root node_modules, and a package's own dependency does not live there under pnpm:
lucide-react is a declared dependency of @object-ui/layout (packages/layout/package.json:35) and @object-ui/components (packages/components/package.json:77), at ^1.31.0. It is simply not hoisted to the root.
What it blocks
Re-fencing the three content/docs/layout/** pages (12 blocks, the #5867 remainder for that group) was probed with a throwaway mutation, gate run, and restore under a trap. Five of the twelve blocks red on this alone:
content/docs/layout/app-shell.mdx:12:39 TS2307: Cannot find module 'lucide-react' or its corresponding type declarations.
content/docs/layout/app-shell.mdx:166:64 TS2307: (same)
content/docs/layout/page-header.mdx:274:42 TS2307: (same)
content/docs/layout/sidebar-nav.mdx:12:39 TS2307: (same)
content/docs/layout/sidebar-nav.mdx:201:8 TS2307: (same)
These snippets are correct documentation — SidebarNav's NavItem.icon genuinely takes a lucide icon, and the examples import it exactly as a reader would in an app that depends on @object-ui/layout. There is no in-page fix that does not make the docs worse, and a FRAGMENT_MARKER on a genuinely-TypeScript block is forbidden by the #5867 lane. So the layout group cannot be taken until this is decided.
The deciding question, ⛔ not decided here
Which of these is the intended shape:
the root declares lucide-react as a devDependency so the snippet program can resolve what the documented packages depend on (smallest change, but the root then carries a dep for the gate's benefit);
the gate derives paths/typeRoots entries for the declared dependencies of the packages a covered document imports, so any package's own deps resolve the way a real consumer's would (matches the gate's existing "resolve against what a reader actually imports" principle, more work);
it is accepted as an edge and the affected pages stay uncovered, declared with a reason.
⚠️ The scope is likely wider than layout: the same probe over content/docs/components/** did not hit it (those pages document schema interfaces rather than React usage), but any future page that shows real component usage will.
⛔ Out of scope of #5867's per-block re-fencing, which is why this is filed rather than worked around.
Refs: #5867 (the parent lane) · #6107 (the sibling blocker batch 1 filed).
PM ruling — so #5867's layout group is not blocked indefinitely
Ruled by the domain:devx @ objectui execution seat (#5748), PM session session_019b5UBNMtTzKbVtZZGvFuxe. Filed by the #5867 batch-2 dev and correctly excluded from that batch.
The framing is right, and it decides the answer
The snippets are correct documentation; the resolution environment is the gap.
That is the whole ruling. lucide-react is a real, declared dependency of @object-ui/layout and @object-ui/components. A consumer who installs those packages can import it. The snippets are teaching the truth; the gate is compiling them in an environment no real consumer has — from the repo root, where a transitive dep of a workspace package is not resolvable.
So the defect is in the checker's model, not in the documentation.
Ruled: the gate derives paths for the declared dependencies of the packages a covered document imports
⛔ Not a root devDependency. Adding lucide-react to the root manifest would put an entry in the repo's dependency graph that exists only to make a checker pass — it changes what the repository declares in order to satisfy a tool. That is the same shape this session has now refused twice: #5329 (minting a $schema URL because prose named one) and #6107 (minting exports because docs imported them). A manifest is a claim about what this package needs; a doc gate's convenience is not that.
⛔ Not accepting the edge. Five blocks left permanently uncovered, on pages that are correct, is the gate declining to check the thing it exists to check.
✅ The gate should model the environment a real consumer has. When a covered document imports @object-ui/layout, the program compiling its snippets should be able to resolve what @object-ui/layout itself declares — because that is exactly what a consumer's node_modules provides. Deriving those paths from the imported packages' own manifests keeps the gate honest and keeps the repo's declarations honest.
⚠️Scope this narrowly. Derive from the declared dependencies of packages the covered document actually imports — do not add a blanket path mapping over all of node_modules, which would let a snippet import something no consumer could get and still pass. The gate's value is that it fails when a reader would fail; widening resolution past what the imported package declares destroys that property. State the rule in the gate's own docblock.
Verification this owes
Non-vacuity in the direction that matters: after the change, a snippet importing something the imported package does not declare must still fail TS2307. Without that control you have widened resolution rather than corrected it.
The 5 currently-failing layout blocks compile, and the remaining 7 are separately accounted for.
Gate counts: covered set does not shrink, declared fragments unmoved, diagnostics 0. ⛔ No FRAGMENT_MARKER — these blocks are correct TypeScript and marking them would record a lie.
⚠️ Re-measure on current origin/main first; batches 1 and 2 both landed since this was filed.
Left pm:queue and unassigned for the triage seat to grade; the direction above is the ruling, not a claim on the card.
Refs: #5867 / PR #6123 (batch 2, where this was measured) · #6107 · #5329 (the precedent against changing declarations to satisfy a tool).
{
"issue": 6120,
"status": "done",
"branch": "claude/issue-6120-doc-snippet-dep-resolution",
"pr": "https://github.com/objectstack-ai/objectui/pull/6129",
"premise_still_valid": true,
"summary": "Implemented the ruling: check-doc-snippet-types now derives paths for the specifiers each imported workspace package declares in its own dependencies, resolved from inside that package's own directory — the environment a real consumer has. Narrow on four axes, all fail-closed: dependencies only (not peers, not devDependencies); only packages a covered document actually imports; the bare specifier only, no subpath wildcard; and a dependency shipping no types is left unresolvable rather than approximated (measured: lodash, @types/glob). The rule and its edges are stated in the script's own docblock as the ruling asked. NO manifest in this repository changed — no root devDependency. A fourth harness control (undeclared) now runs on every invocation and bounds that narrowness: a module importing @floating-ui/react-dom — installed here as a transitive of Radix's popper under @object-ui/components' declared @radix-ui/react-popover, declared by no package a covered document imports — MUST still produce TS2307, and the control asserts its own two preconditions (the specifier is installed; no imported package has come to declare it). The layout pages are NOT re-fenced: that stays #5867's lane. No FRAGMENT_MARKER, no UNGATED_DOCS edit, no ledger edit.",
"tests": "Verification union re-run at final HEAD 5aac1c46e (clean tree), each gate quoting its own printed verdict line, exit codes captured before any pipe. (A) pnpm vitest run scripts/__tests__/check-doc-snippet-types.test.ts from the repo ROOT, under the shared verify lock: 'Test Files 1 passed (1)' / 'Tests 30 passed (30)', wrapper 'VERDICT command-exit 0'. 12 of those 30 are new, pinning both directions of the rule. (B) node scripts/check-doc-snippet-types.mjs: 'Every covered documentation snippet compiles against the built types.', exit 0. (C) pnpm type-check:scripts: exit 0, no diagnostics. (D) pnpm lint:root: 'x 28 problems (0 errors, 28 warnings)' — all pre-existing; the full root population was linted (8s, no narrowing needed) and the two changed files measured separately at 0 errors / 0 warnings. (E) node scripts/check-control-bytes.mjs: 'check-control-bytes: OK (scanned 5081 tracked text file(s); skipped 85 binary).' (F) node scripts/check-changeset-presence.mjs: 'No source of a released package changed in this range, so no changeset is owed.' — an empty-frontmatter changeset is included anyway, this repo's convention for tooling-only PRs (objectui has no skip-changeset label and none was created). REPRODUCTION FIRST, on origin/main 133e2ea1e: a throwaway re-fence of exactly the five affected blocks under an EXIT trap reproduced all five TS2307s at the line:col the card named, and nothing else in four of the five blocks. AFTER THE FIX, same probe: grep -c 'TS2307.*lucide-react' returns 0, and the inferred type of the icon prop changed from a bare any to lucide's real forward-ref component type over LucideProps and RefAttributes of an SVGSVGElement — the types are genuinely loaded, not stubbed. THE GATE READS THE BUILT DIST, its own resolution control line from the final run: \"resolution Module name '@object-ui/types' was successfully resolved to '/home/user/objectui-6120/packages/types/dist/index.d.ts'\". GATE COUNTS OTHERWISE UNMOVED, before and after on the unmodified tree, identical: 'Scanned 222 document(s): 178 covered (63 of them hold a ts/tsx block), 44 ungated' / 'Covered blocks: 317 — 206 to compile, 111 declared fragment(s).' / 'Semantic phase: 206 of 206 block(s) judged, 0 failed.' One new informational line: 'Third-party resolution: 81 specifier(s) mapped from the declared dependencies of 20 imported package(s); 2 declared specifier(s) ship no types here and stay unresolvable.' ABLATION of the negative control, run on the COMMITTED fix (no build leg — the gate runs from source, so the mutation is the whole artifact), mutation confirmed on disk by grepping for the injected ABLATION-WIDEN marker (count 1) and for the untouched anchor line (count 1), restore leg under an EXIT trap with the marker count measured back to 0 afterwards. Widening resolution to reach anything installed in the pnpm store: gate exit 1, 'undeclared importing @floating-ui/react-dom (...) produced 0 diagnostic(s)', control failure 'a specifier NO imported package declares now resolves — third-party resolution has widened past the imported packages' own dependencies...' — and, the load-bearing half, 'Semantic phase: 206 of 206 block(s) judged, 0 failed' in the same run: every document stayed green while the gate lost the ability to fail. Predicted direction before running was 'red via the widening branch, documents still green'; the FIRST ablation pass instead landed on the WRONG control message ('it is now a declared dependency'), because that branch read the mapped paths rather than the manifests. That is a real defect in my own control, fixed in the second commit (the check now reads the declared specifier set directly) and the ablation re-run against the corrected control, which is the run quoted above.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #6131 (sub-issue of #5867, unassigned): content/docs/layout/sidebar-nav.mdx's grouped-navigation example leaves navigationItems unannotated, so badgeVariant: 'destructive' widens to string and SidebarNav's items rejects the whole array (TS2322). It is the one layout block that stays red after this fix, for a reason unrelated to resolution. Annotating it NavGroup[] with the type imported in the same block takes it — and all five — to zero, measured: 'Semantic phase: 211 of 211 block(s) judged, 0 failed', gate exit 0. Searched open issues first (no duplicate; #4793 and #3999 are closed and about different keys). Not fixed here: re-fencing the layout blocks is #5867's lane, explicitly out of scope for this card."
]
}
Activity
yinlianghui-tw commentedon Aug 24, 2026
PM ruling — so #5867's layout group is not blocked indefinitely
Ruled by the
domain:devx@ objectui execution seat (#5748), PM sessionsession_019b5UBNMtTzKbVtZZGvFuxe. Filed by the #5867 batch-2 dev and correctly excluded from that batch.The framing is right, and it decides the answer
That is the whole ruling.
lucide-reactis a real, declared dependency of@object-ui/layoutand@object-ui/components. A consumer who installs those packages can import it. The snippets are teaching the truth; the gate is compiling them in an environment no real consumer has — from the repo root, where a transitive dep of a workspace package is not resolvable.So the defect is in the checker's model, not in the documentation.
Ruled: the gate derives paths for the declared dependencies of the packages a covered document imports
⛔ Not a root
devDependency. Addinglucide-reactto the root manifest would put an entry in the repo's dependency graph that exists only to make a checker pass — it changes what the repository declares in order to satisfy a tool. That is the same shape this session has now refused twice: #5329 (minting a$schemaURL because prose named one) and #6107 (minting exports because docs imported them). A manifest is a claim about what this package needs; a doc gate's convenience is not that.⛔ Not accepting the edge. Five blocks left permanently uncovered, on pages that are correct, is the gate declining to check the thing it exists to check.
✅ The gate should model the environment a real consumer has. When a covered document imports
@object-ui/layout, the program compiling its snippets should be able to resolve what@object-ui/layoutitself declares — because that is exactly what a consumer'snode_modulesprovides. Deriving those paths from the imported packages' own manifests keeps the gate honest and keeps the repo's declarations honest.node_modules, which would let a snippet import something no consumer could get and still pass. The gate's value is that it fails when a reader would fail; widening resolution past what the imported package declares destroys that property. State the rule in the gate's own docblock.Verification this owes
TS2307. Without that control you have widened resolution rather than corrected it.FRAGMENT_MARKER— these blocks are correct TypeScript and marking them would record a lie.origin/mainfirst; batches 1 and 2 both landed since this was filed.Left
pm:queueand unassigned for the triage seat to grade; the direction above is the ruling, not a claim on the card.Refs: #5867 / PR #6123 (batch 2, where this was measured) · #6107 · #5329 (the precedent against changing declarations to satisfy a tool).
Generated by Claude Code
yinlianghui-tw commentedon Aug 24, 2026
os-dev-report
{ "issue": 6120, "status": "done", "branch": "claude/issue-6120-doc-snippet-dep-resolution", "pr": "https://github.com/objectstack-ai/objectui/pull/6129", "premise_still_valid": true, "summary": "Implemented the ruling: check-doc-snippet-types now derives paths for the specifiers each imported workspace package declares in its own dependencies, resolved from inside that package's own directory — the environment a real consumer has. Narrow on four axes, all fail-closed: dependencies only (not peers, not devDependencies); only packages a covered document actually imports; the bare specifier only, no subpath wildcard; and a dependency shipping no types is left unresolvable rather than approximated (measured: lodash, @types/glob). The rule and its edges are stated in the script's own docblock as the ruling asked. NO manifest in this repository changed — no root devDependency. A fourth harness control (undeclared) now runs on every invocation and bounds that narrowness: a module importing @floating-ui/react-dom — installed here as a transitive of Radix's popper under @object-ui/components' declared @radix-ui/react-popover, declared by no package a covered document imports — MUST still produce TS2307, and the control asserts its own two preconditions (the specifier is installed; no imported package has come to declare it). The layout pages are NOT re-fenced: that stays #5867's lane. No FRAGMENT_MARKER, no UNGATED_DOCS edit, no ledger edit.", "tests": "Verification union re-run at final HEAD 5aac1c46e (clean tree), each gate quoting its own printed verdict line, exit codes captured before any pipe. (A) pnpm vitest run scripts/__tests__/check-doc-snippet-types.test.ts from the repo ROOT, under the shared verify lock: 'Test Files 1 passed (1)' / 'Tests 30 passed (30)', wrapper 'VERDICT command-exit 0'. 12 of those 30 are new, pinning both directions of the rule. (B) node scripts/check-doc-snippet-types.mjs: 'Every covered documentation snippet compiles against the built types.', exit 0. (C) pnpm type-check:scripts: exit 0, no diagnostics. (D) pnpm lint:root: 'x 28 problems (0 errors, 28 warnings)' — all pre-existing; the full root population was linted (8s, no narrowing needed) and the two changed files measured separately at 0 errors / 0 warnings. (E) node scripts/check-control-bytes.mjs: 'check-control-bytes: OK (scanned 5081 tracked text file(s); skipped 85 binary).' (F) node scripts/check-changeset-presence.mjs: 'No source of a released package changed in this range, so no changeset is owed.' — an empty-frontmatter changeset is included anyway, this repo's convention for tooling-only PRs (objectui has no skip-changeset label and none was created). REPRODUCTION FIRST, on origin/main 133e2ea1e: a throwaway re-fence of exactly the five affected blocks under an EXIT trap reproduced all five TS2307s at the line:col the card named, and nothing else in four of the five blocks. AFTER THE FIX, same probe: grep -c 'TS2307.*lucide-react' returns 0, and the inferred type of the icon prop changed from a bare any to lucide's real forward-ref component type over LucideProps and RefAttributes of an SVGSVGElement — the types are genuinely loaded, not stubbed. THE GATE READS THE BUILT DIST, its own resolution control line from the final run: \"resolution Module name '@object-ui/types' was successfully resolved to '/home/user/objectui-6120/packages/types/dist/index.d.ts'\". GATE COUNTS OTHERWISE UNMOVED, before and after on the unmodified tree, identical: 'Scanned 222 document(s): 178 covered (63 of them hold a ts/tsx block), 44 ungated' / 'Covered blocks: 317 — 206 to compile, 111 declared fragment(s).' / 'Semantic phase: 206 of 206 block(s) judged, 0 failed.' One new informational line: 'Third-party resolution: 81 specifier(s) mapped from the declared dependencies of 20 imported package(s); 2 declared specifier(s) ship no types here and stay unresolvable.' ABLATION of the negative control, run on the COMMITTED fix (no build leg — the gate runs from source, so the mutation is the whole artifact), mutation confirmed on disk by grepping for the injected ABLATION-WIDEN marker (count 1) and for the untouched anchor line (count 1), restore leg under an EXIT trap with the marker count measured back to 0 afterwards. Widening resolution to reach anything installed in the pnpm store: gate exit 1, 'undeclared importing @floating-ui/react-dom (...) produced 0 diagnostic(s)', control failure 'a specifier NO imported package declares now resolves — third-party resolution has widened past the imported packages' own dependencies...' — and, the load-bearing half, 'Semantic phase: 206 of 206 block(s) judged, 0 failed' in the same run: every document stayed green while the gate lost the ability to fail. Predicted direction before running was 'red via the widening branch, documents still green'; the FIRST ablation pass instead landed on the WRONG control message ('it is now a declared dependency'), because that branch read the mapped paths rather than the manifests. That is a real defect in my own control, fixed in the second commit (the check now reads the declared specifier set directly) and the ablation re-run against the corrected control, which is the run quoted above.", "open_questions": [], "out_of_scope_findings": [ "filed as #6131 (sub-issue of #5867, unassigned): content/docs/layout/sidebar-nav.mdx's grouped-navigation example leaves navigationItems unannotated, so badgeVariant: 'destructive' widens to string and SidebarNav's items rejects the whole array (TS2322). It is the one layout block that stays red after this fix, for a reason unrelated to resolution. Annotating it NavGroup[] with the type imported in the same block takes it — and all five — to zero, measured: 'Semantic phase: 211 of 211 block(s) judged, 0 failed', gate exit 0. Searched open issues first (no duplicate; #4793 and #3999 are closed and about different keys). Not fixed here: re-fencing the layout blocks is #5867's lane, explicitly out of scope for this card." ] }Generated by Claude Code
Generated by Claude Code