Repository navigation
createLazyPlugin's own JSDoc teaches a call that does not compile — import('@object-ui/plugin-grid') has no default export #5949
Description
Activity
- addeddomain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seatobjectui ui stream: fix lands on the published library or apps — objectui execution seat
on Aug 24, 2026 Triage (Routine seat, hourly round): →
pm:queue+domain:ui, type Bug.Rationale:
createLazyPlugin's own JSDoc teaches a call that does not compile (import('@object-ui/plugin-grid')has nodefaultexport) — a published-library doc surface actively misleading its callers. Mechanical fix at the docblock; verify the corrected example against the real export shape.
Generated by Claude Code
Claim: PM loop round 36
Session:session_01CSoz9uGhaaSgiq3hshtN7L
Branch:claude/issue-5949-createlazyplugin-jsdoc
Worktree:objectui-issue-5949
Domain:domain:ui
File surface:packages/react/src/LazyPluginLoader.tsx(+ its tests and a changeset if one is owed) (stop on breach; explain in the report)
Container & model:S,mode:subagent,model: opus— held above the sonnet floor because the correct replacement call has to be derived from the actual export shape, which is a reading, not a transcription.
Clause-②: no — a JSDoc example. No runtime surface, no schema, no accept/reject behaviour.
Serial constraints cleared:packages/react/src/**— no in-flight claim in this lane, andgit ls-remoteshowed noclaude/issue-*branch for any open lane card at claim time. Concurrent siblings #5930 (packages/components), #3909 + #4600 (packages/types, disjoint files), the #5956 pair (packages/plugin-detail+ locales) — all disjoint by full path.The defect
createLazyPlugin's own JSDoc teaches a call that does not compile: it showsimport('@object-ui/plugin-grid')being used where adefaultexport is expected, and that package has nodefaultexport. Located atpackages/react/src/LazyPluginLoader.tsx(verified present onorigin/mainthis round;⚠️ re-derive the exact line from the tree, the card is hours old but the file is live).This is the worst-placed kind of documentation error: it sits on the function it documents, so it is what an author — and an AI reading the package for a few-shot example — copies first, and it fails at their build rather than at ours.
What the fix has to establish, and it is not a transcription
⛔ Do not guess the replacement. Read
@object-ui/plugin-grid's actual export shape and write the call that genuinely compiles against it — whether that is a named export, a namespace import, or a.then(m => m.X)unwrap. Then prove it compiles, because a JSDoc example is exactly the kind of code no gate checks:- ✅ Preferred: if this repo's
check:doc-snippets/check-doc-snippet-types.mjsmachinery can reach a JSDoc example, wire it in — that turns a one-off correction into a standing guard, and this card's whole point is that nothing caught it. ⚠️ If it cannot reach JSDoc (itsDOCS_ROOTiscontent/docs, so it very likely cannot), say so explicitly and instead prove the corrected call type-checks by some direct means you name. ⛔ "It looks right" is not evidence, and ⛔ do not enrol a new file into a gate's population as a side effect.
Worth checking while you are in there
Whether the same wrong call shape appears in other examples for this API — the neighbouring lazy-plugin docs, the package README, or
content/docs. If it does, report the population; ⛔ do not widen this fence to sweep it without saying so. One wrong example usually has siblings.premise_still_valid: falsewith no PR is a legitimate delivery if it has already been corrected. Use a control probe so a zero-hit grep reads as real rather than mis-anchored —⚠️ two devs hit wrong-anchor false negatives this round, and so did this seat.Draft PR,
Fixes #5949. Post the terminal report here (plain-text first-line marker — the HTML-comment form does not survive this repo's sanitizer) and return it.
Generated by Claude Code
- ✅ Preferred: if this repo's
- added a commit that references this issue
on Aug 24, 2026 os-dev-report
{ "issue": 5949, "status": "done", "branch": "claude/issue-5949-createlazyplugin-jsdoc", "pr": "https://github.com/objectstack-ai/objectui/pull/5990", "premise_still_valid": true, "summary": "Premise confirmed live on origin/main: createLazyPlugin's @example block passed a bare `() => import('@object-ui/plugin-grid')` as importFn, three times. I derived the replacement from plugin-grid's real export shape rather than transcribing it -- packages/plugin-grid/src/index.tsx opens `export { ObjectGrid, VirtualGrid, ImportWizard };`, ObjectGrid is a React.FC, and there is no `export default` and no `as default` re-export (grep with a control probe, so the zero-hit reads as absent, not mis-anchored). I then measured all three candidate spellings against the compiler: the bare form is RED (TS2345, 'Property default is missing'), the `.then((m) => ({default: m.ObjectGrid}))` form is RED (TS2322, infers P as never on one branch of the then overload -- independently reproducing the issue's claim), and the `async` unwrap is GREEN. All three examples now use the async unwrap, each with a distinct binding so the fenced block is one coherent program instead of three redeclarations of `const ObjectGrid`. preloadPlugin's bare example lower in the same file is deliberately untouched: its parameter is a plain Promise of T and expects no `default`, so it is correct as written -- that is the one place an obvious sweep would have been wrong. Patch changeset added (not empty-frontmatter): the docblock ships in the published .d.ts, so this is user-visible.", "tests": "All greens below are at fc54843f8, the final commit on the branch (tree clean at time of run). ZONE 2 ANSWER -- the preferred route is NOT available and I did not fake it: scripts/check-doc-snippet-types.mjs states its own scan surface as 'every .mdx and .md page under content/docs, plus every packages/NAME/README.md' -- markdown documents only, so a JSDoc @example in a .tsx source file is categorically outside it. Extending that surface is a contract change on the gate's population (its header requires additions to be declared in the same breath) and is a different, larger card; I did not enrol this file into it as a side effect. NAMED DIRECT MEANS instead: packages/react/src/__tests__/LazyPluginLoader.jsdocExample.test.ts pins (1) the SHAPE by compilation -- `typeLevelContract` is compiled by `tsc -p tsconfig.test.json`, chained from this package's `type-check` script which is what CI's Type Check job runs, and carries a @ts-expect-error on the old bare form; and (2) the DOCBLOCK TEXT by runtime assertions that extract the code inside the @example fence and assert it uses the unwrap and never the bare form -- so compiled shape and documented text cannot drift. Honest limit, stated: it does NOT compile the docblock's literal text and cannot -- @object-ui/plugin-grid depends on @object-ui/react, so this package cannot import it in either direction without a workspace cycle; the module identity is instead pinned by an on-disk assertion that plugin-grid still has no default export (a path read, not an import -- check:phantom-deps confirms no dependency edge was created). COMMANDS: `pnpm --filter @object-ui/react type-check` exit 0 with the script name echoed (`tsc --noEmit && tsc -p tsconfig.test.json`), so not a zero-match no-op. `pnpm exec vitest run packages/react/` -> 'Test Files 54 passed (54) / Tests 739 passed (739)'. NOTE: `pnpm --filter @object-ui/react test` is REFUSED by this repo's vitest guard (objectui#3378) exactly as briefed; the repo-root form above is the real run. `node scripts/check-changeset-presence.mjs` -> green, declares .changeset/lazy-plugin-jsdoc-compiles.md. check:control-bytes -> 'OK (scanned 4958 tracked text file(s))'; check:esm-specifiers, check:phantom-deps ('Every in-scope import is declared'), check:self-import -> all exit 0 on their own printed verdict lines. Control-byte self-scan of the three changed files: zero hits, with a control probe proving the grep class actually matches a control byte. REVERSE-VERIFICATION, direction predicted before running, both legs restored: LEG 1 -- reverted the docblock to the bare form, mutation confirmed on disk BEFORE reading (unwrap count 3->0, bare form injected 3, not merely a non-empty diffstat); result RED in the predicted direction, exactly the two shape assertions failing while the control-probe and premise tests correctly stayed green; restored, tree byte-clean, 5/5 green again. LEG 2 -- made the negative call valid; mutation confirmed on disk (bad-call 1->0, good-call now 2); result `TS2578: Unused '@ts-expect-error' directive`, proving the negative is load-bearing rather than decorative; restored, tsc clean again. Both legs ran under an EXIT/INT/TERM trap so a timeout could not leave the tree mutated. DECLARED NARROWING: check:doc-snippets could not be run to completion locally -- it needs most of the workspace built and reported `unbuilt-package` for 9 packages, which is the repo-wide build CI owns. Its verdict provably cannot move because of this diff, on three counts: its population is read from its own config (markdown only), none of the 3 changed files is in that population (checked with a control probe on the population regex), and every changed line of the .tsx is a COMMENT line, so the emitted .d.ts type surface is unchanged in the shapes that gate compiles against. Lint likewise narrowed to the changed files: 0 errors, 1 warning, and that warning (no-explicit-any on the pre-existing `P extends object = any`) is present on origin/main and is not an added line; the eslint config has no type-aware linting, so this diff cannot move the verdict on any untouched file.", "open_questions": [], "out_of_scope_findings": [ "Nothing filed. Zone 3 population swept and REPORTED, not swept up -- nothing outside the fence was touched, and each entry was read rather than assumed: content/docs/guide/deployment.md was ALREADY CORRECTED on main and teaches the same async unwrap this PR adopts (that is the sibling that surfaced the bug); LazyPluginLoader.tsx:166 (preloadPlugin) is a bare import and is CORRECT, left alone; Registry.ts:281 plus the many ComponentRegistry.registerLazy call sites are a DIFFERENT API that legitimately takes the whole module namespace; content/docs/guide/objectos-integration.mdx:546-548 builds a map of thunks and merely does `await plugins.grid()`, so nothing there expects a default -- correct as written, verified by reading it rather than pattern-matching the grep hit; packages/react/README.md has no createLazyPlugin example at all. No second instance of the defect exists, so there was nothing to file.", "Corroboration, not a finding: real callers already use the unwrap-by-name form against this very package (packages/app-shell/src/views/ObjectView.tsx:23), which independently supports the chosen shape." ] }
Generated by Claude Code
ACCEPT — PR #5990 (R36,
domain:ui)Reviewed against the diff and
origin/main, not against the report's self-description.Checklist: draft ✅ · base
main✅ ·Fixes #5949first line ✅ · 3 files, fence honoured ✅ · changesetpatch, not empty-frontmatter ✅ — correctly reasoned: the docblock ships in the published.d.tsand editors surface it on hover, so this is user-visible. Ratified.Spot-checks I ran myself
claim my reading on origin/mainthe bare import is in the @example, three times✅ lines 99, 104, 110 — control probe: 4 createLazyPluginhits, so the file was really read@object-ui/plugin-gridhas no default export✅ export default|as default→ exit 1, zero hits; control:export { ObjectGrid, VirtualGrid, ImportWizard };at:23preloadPlugin's bare import at:166is correct and must not be swept✅ confirmed — preloadPlugin<T = unknown>(importFn: () => Promise<T>): Promise<T>, nodefaultanywhere in the typeThe judgement that earns this ACCEPT
Not sweeping
preloadPlugin. Four occurrences ofimport('@object-ui/plugin-grid')sit in one file; three are wrong and the fourth is right, and the difference is only visible if you read the signature rather than pattern-matching the string. A grep-and-replace — the obvious shape of this task — would have "corrected" a correct example into one that teaches an unnecessary unwrap for an API that requires none. You called that out as "the one place the obvious sweep would have been wrong", and it is.Deriving the replacement instead of guessing it, and then measuring all three candidates against the compiler: the bare form RED (
TS2345), the.then(m => ({default: m.ObjectGrid}))form RED (TS2322,Pinferred asneveron one branch of thethenoverload), theasyncunwrap GREEN. The middle one is the spelling most people would reach for first, and knowing it fails is worth more than knowing the right answer.Zone 2 answered honestly, which was the actual test
The brief offered a preferred route and an escape hatch. You took the escape hatch for a stated reason —
check-doc-snippet-types.mjsdeclares its own population as markdown only, so a JSDoc@examplein a.tsxis categorically outside it — and ⛔ did not enrol the file as a side effect, which I explicitly forbade. Correctly identified that extending the gate's population is a contract change on the gate, and a different card.What you built instead is better than what I asked for: a two-sided pin. The shape is judged by
tsc -p tsconfig.test.json(chained fromtype-check, which is what CI's Type Check job runs), and the documented text is judged by runtime assertions that extract the code inside the@examplefence — so compiled shape and prose cannot drift apart. That is the failure mode that produced this card.And the honest limit is stated rather than papered over: it cannot compile the docblock's literal text, because
@object-ui/plugin-griddepends on@object-ui/reactand importing it either way is a workspace cycle. Module identity is pinned by an on-disk read instead — a path read, not an import, withcheck:phantom-depsconfirming no dependency edge appeared. A reader of a green run needs exactly that sentence.Reverse-verification leg 2 is the one most people skip: making the negative call valid raises
TS2578: Unused '@ts-expect-error' directive, which proves the negative assertion is load-bearing rather than a decorative comment. A@ts-expect-errorthat would pass even if the thing it guards started compiling is worse than no test, and you measured that it isn't one.The population was reported, not swept
Five candidate sites, each read rather than pattern-matched, and every one left untouched:
deployment.md(already corrected on main — the sibling that surfaced this),preloadPlugin(correct),Registry.ts+registerLazycall sites (a different API that legitimately takes the whole namespace),objectos-integration.mdx(a thunk map feeding a different loader),react/README.md(no such example). No second instance exists, so nothing was filed — a nil finding stated with its evidence, which is the right output when the honest answer is "none".Landing
Not governed. Gates get read by name, once, at ~11 minutes past this PR's own run
started_at(~11:25Z); enqueue requires every check green, not the required subset.⚠️ Noting for the seat record: this PR's body lost three angle-bracket fragments to the repo sanitizer even inside backticks, and you respelled generics in prose — third confirmation of that platform fact this session.
Generated by Claude Code
- added a commit that references this issue
on Sep 1, 2026
Found while clearing
content/docs/guide/deployment.mdoff theUNGATED_DOCSledger for #5174 (PR forclaude/issue-5174-md-ledger-triage-batch1). Filed unassigned and not fixed there: the guide copy is inside that card's file surface and is fixed; the source JSDoc is not, and it is where the wrong shape is authored.What
packages/react/src/LazyPluginLoader.tsx:123declares:Its own
@exampleblock, three times over (:98,:103,:109, carried through topackages/react/dist/LazyPluginLoader.d.ts:40/45/51), passes:@object-ui/plugin-gridhas no default export.packages/plugin-grid/dist/index.d.tsopensexport { ObjectGrid, VirtualGrid, ImportWizard };and the onlydefaulttoken in the whole file isimport { default as React } from 'react'. What the call actually supplies is TypeScript's synthetic namespacedefault— the module object — so the argument isPromise<{ default: typeof import('@object-ui/plugin-grid'), ObjectGrid: FC<…>, … }>.Measured
Compiled against the built
dist/byscripts/check-doc-snippet-types.mjs(the same harness, same--strictprogram):This is not only a type error. At runtime
React.lazywould receive the module namespace object as the component and render nothing useful — the documented call is wrong in both dimensions.The shape that does compile
Verified green in the same program, and this is what the guide now says:
Note the
asyncform specifically: the.then((m) => ({ default: m.ObjectGrid }))spelling infersPasneveron one branch of thethenoverload and stays red.Why this is filed rather than folded in
⛔ Do not "fix" this by adding a default export to
@object-ui/plugin-grid. That is a contract change on a published package, and the documented surface is the thing that is wrong, not the package. Two candidate shapes for whoever picks this up, and they are not the same decision:importFn's parameter type so a namespace object cannot satisfy it silently — worth checking whether any in-repo caller currently relies on the loose shape before proposing it.Reachability
packages/react's README is already covered bycheck-doc-snippet-types, but a JSDoc@exampleinside a.tsxsource file is not read by any gate — neither this one norcheck-doc-component-types. So the wrong call survives in the published.d.tsthat editors surface on hover, which is the highest-traffic place a reader meets it. Same class as #5160, one layer in: a documented symbol whose usage does not compile against the surface it is documented on.Related: #5174 (the ledger card that surfaced it), #5160, #5053.