Uh oh!
There was an error while loading. Please reload this page.
fix(tooling): resolve a documented package's declared dependencies in the doc-snippet gate - #6129
Conversation
… the doc-snippet gate `check-doc-snippet-types` compiles every covered snippet as its own module at the repository ROOT. Workspace packages resolve there — `paths` is built from each package's own `exports` — but a third-party specifier did not: under pnpm a workspace package's own dependency is not hoisted to the root, so a snippet importing `lucide-react` failed TS2307 even though `@object-ui/layout` and `@object-ui/components` both declare it and any reader who installs them gets it. Five correct blocks across `content/docs/layout` were red on nothing but that. The snippets were right; the resolution environment was the gap. The gate now derives `paths` for the specifiers each imported 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 that ships no types is left unresolvable rather than approximated. No manifest in this repository changed. A fourth self-control keeps that narrowness measurable on every run: a module importing `@floating-ui/react-dom` — installed here as a transitive of Radix's popper, declared by no package a covered document imports — must still produce TS2307. Widen resolution past the declarations and that control goes green, which is the only thing that can tell "the gate checks" from "the gate cannot fail". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019b5UBNMtTzKbVtZZGvFuxe
The first ablation of the control landed on the wrong message: mapping the control specifier tripped the "it is now a declared dependency" branch, because that branch read the mapped `paths` rather than the manifests. Those are two different facts with two different fixes — a control specifier that has become a declared dependency needs replacing, while one that resolves with no manifest declaring it means resolution has widened, which is the failure the control exists to name. The check now reads the declared specifier set directly. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019b5UBNMtTzKbVtZZGvFuxe
yinlianghui-tw
commented
Aug 24, 2026
PM: ACCEPT — the negative control is the deliverable, and you made it permanentYou turned my acceptance bar into a standing gate controlI asked you to prove that a snippet importing something the package does not declare still fails TS2307 — a one-off demonstration. You built it as a fourth harness control that runs on every invocation: And it asserts its own two preconditions — that the specifier is genuinely installed, and that no imported package has since come to declare it. Without those, the control would quietly become vacuous the day someone adds The ablation demonstrated the exact failure mode the bar was written forWidening resolution to reach anything in the pnpm store: Every document stayed green while the gate lost the ability to fail. That is the rubber-stamp outcome I flagged as worse than the original gap and invisible — now exhibited rather than argued. Anyone reviewing a widened version of this change would have seen nothing but green.
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#6120
The defect: the snippets were right, the resolution environment was the gap
check-doc-snippet-typescompiles every covered snippet as its own module at therepository root. Workspace packages resolve there —
pathsis built from eachpackage's own
exports— but a third-party specifier was resolved the ordinaryway, from the root
node_modules, and under pnpm a workspace package's owndependency is not hoisted there. So a snippet importing
lucide-reactfailedTS2307even though@object-ui/layout(packages/layout/package.json) and@object-ui/components(packages/components/package.json) both declare it, andany reader who installs those packages gets it in their own
node_modules.Reproduced on
origin/main133e2ea1ebefore touching anything, with a throwawayre-fence of the five affected blocks under an
EXITtrap:The change, per the ruling on #6120
The gate now derives
pathsfor the specifiers each imported package declares inits own
dependencies, resolved from inside that package's own directory — theenvironment a real consumer has. The rule and its edges are stated in the script's
own docblock, as the ruling asked. It is narrow on four axes, every one of them
failing closed:
manifests, never from a walk of
node_modules.dependenciesonly — notpeerDependencies(a requirement on the consumerthat may be unmet), not
devDependencies(reaches no consumer at all).nothing, so this map grows only as coverage grows, the same property
--build-filterhas.lucide-reactis mapped,lucide-react/dynamicis not. Apkg/*mapping would reach past the package'sown
exports, which is the reader's real boundary.A dependency that ships no types is left unresolvable rather than approximated
(measured here:
lodash, which resolves only to.js, and@types/glob).No manifest in this repository changed. Declaring
lucide-reactat the root tobuy a doc snippet its coverage would put an entry in the repo's dependency graph
that exists only to satisfy a checker — the route the ruling rejected by name.
The negative control: executable, and it runs on every invocation
A fourth harness control (
undeclared) now sits besideresolution/sentinel/positive. A synthetic module imports@floating-ui/react-dom— installed inthis workspace as a transitive of Radix's popper (under
@object-ui/components'sdeclared
@radix-ui/react-popover), declared by no package a covered documentimports, and shipping real
.d.tsfiles — and it MUST produceTS2307. Thecontrol also asserts its own two preconditions: that the specifier is installed
(otherwise "it does not resolve" measures nothing) and that no imported package has
since come to declare it (otherwise it tests the wrong thing).
Ablated to prove it can fail. Widening resolution to reach anything installed in
the pnpm store, on the committed fix, restore under an
EXITtrap (marker countback to 0 afterwards):
That second line is the point: every document stayed green while the gate lost the
ability to fail. Without this control the widening is invisible.
The first ablation pass also found a defect in the control itself — mapping the
specifier tripped the "it is now a declared dependency" branch, because that branch
read the mapped
pathsinstead of the manifests. Those are two different facts withtwo different fixes, so the check now reads the declared specifier set directly
(second commit); the ablation above is the re-run against the corrected control.
Verification
Union re-run at final HEAD
5aac1c46e, each gate quoting its own verdict line:pnpm vitest run scripts/__tests__/check-doc-snippet-types.test.ts(repo root)Test Files 1 passed (1)·Tests 30 passed (30)node scripts/check-doc-snippet-types.mjsEvery covered documentation snippet compiles against the built types.pnpm type-check:scriptspnpm lint:root✖ 28 problems (0 errors, 28 warnings)— all pre-existing; the two changed files lint at 0 errors / 0 warningsnode scripts/check-control-bytes.mjs✅ check-control-bytes: OK (scanned 5081 tracked text file(s); skipped 85 binary).node scripts/check-changeset-presence.mjs✅ No source of a released package changed in this range, so no changeset is owed.The gate reads the BUILT dist — its own resolution control line, quoted from the
final run:
The five blocks now resolve. Same throwaway re-fence probe, after the fix:
grep -c "TS2307.*lucide-react"returns 0. And the types are genuinely loadedrather than stubbed to
any: in the one block that stays red for its own separatereason (below), the reported type of
iconchanged from a bareanybefore the fixto lucide's real forward-ref component type after it — a
ForwardRefExoticComponentover
LucidePropsandRefAttributesof anSVGSVGElement. (Written in wordsbecause GitHub's body sanitizer strips short angle-bracket fragments, generics
included, even inside code spans.)
Gate counts are otherwise unmoved. This changes resolution, not population —
before and after, on the unmodified tree:
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.Out of scope, deliberately
The layout pages are not re-fenced here — that is #5867's lane, and layout is
fenced off from its batch 3 pending this. No
FRAGMENT_MARKER, noUNGATED_DOCSedit, no ledger edit.
The re-fence probe did surface one further documentation defect while measuring:
content/docs/layout/sidebar-nav.mdx's grouped-navigation example leavesnavigationItemsunannotated, sobadgeVariant: 'destructive'widens tostringand
SidebarNav'sitemsrejects the whole array (TS2322). Annotating itNavGroup[]takes that block — and all five — to zero, measured. Filed separatelyunder #5867 rather than fixed here; #5867 is not addressed by this PR.
Generated by Claude Code