Skip to content

fix(react-doctor): clear misc architecture warnings#346

Open
bntvllnt wants to merge 7 commits into
mainfrom
fix/277-react-doctor-misc
Open

fix(react-doctor): clear misc architecture warnings#346
bntvllnt wants to merge 7 commits into
mainfrom
fix/277-react-doctor-misc

Conversation

@bntvllnt
Copy link
Copy Markdown
Collaborator

@bntvllnt bntvllnt commented May 13, 2026

Summary

  • Preserves public API compatibility while keeping the react-doctor: misc — render-in-render, polymorphic children, giant components (~40 warnings) #277 React Doctor architecture cleanup: legacy renderIntroContent and renderContent props still work, while introContent / SectionContent remain the preferred non-render-prop APIs.
  • Restores ReactNode children compatibility for CodeBlock and CodePlayground with safe text extraction, plus additive code props for explicit string input.
  • Adds regression coverage and docs/changelog updates for the legacy compatibility paths, and keeps registry component copies in sync with source components.
  • Repairs review follow-up blockers for registry installability: the content-intro, slideshow, and completion-dialog registry copies no longer import unpublished event/mount hooks from @vllnt/ui@^0.2.1; those registry copies inline the small local hook helpers while continuing to import published primitives such as cn, Button, and component exports.
  • Current head: 700c83645da13fb44731643f0309f4ff6ff61d2d.

Validation

  • git diff --check — passed at current head before push.
  • pnpm -F @vllnt/ui-registry exec tsc --noEmit --project tsconfig.json — passed at current head before push.
  • Completion-dialog blocker grep — passed: apps/registry/registry/default/completion-dialog/completion-dialog.tsx no longer imports useDocumentEventListener from @vllnt/ui.
  • Targeted TypeScript transpile syntax probe for registry/default/completion-dialog/completion-dialog.tsx — passed.
  • pnpm -F @vllnt/ui-registry lint — still blocked before reaching the touched registry copy by the existing eslint-plugin-jsx-a11y / minimatch runtime error in apps/registry/app/report/report-bug-form.tsx:73.
  • Export-contract probe retained from prior head: npm pack @vllnt/ui@0.2.1 showed cn, Button, and CompletionDialog are present, while useDocumentEventListener and useMounted are absent; the registry copies therefore inline those hooks instead of importing them from the advertised dependency.
  • Prior PR validation retained from earlier heads: pnpm -F @vllnt/ui exec vitest run src/components/content-intro/content-intro.test.tsx src/components/slideshow/slideshow.test.tsx src/components/code-block/code-block.test.tsx src/components/code-playground/code-playground.test.tsx (4 files, 14 tests), pnpm -F @vllnt/ui lint, pnpm -F @vllnt/ui exec tsc --noEmit --project tsconfig.build.json, pnpm build, pnpm test:once (218 files, 1222 tests), pnpm dlx react-doctor . --offline --json, pnpm dlx react-doctor . --diff origin/main --annotations --fail-on error --offline, and git diff --stat origin/main...HEAD.

Closes #277

Copy link
Copy Markdown
Collaborator Author

@bntvllnt bntvllnt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — request changes recommended

I reviewed PR #346 at head 5fd2fc36968c4bf750fb8a615b10ea88f0b8e7e5 against origin/main.

BLOCKING

  • C1 — ContentIntro public API was renamed instead of preserved

    • Evidence: ContentIntroProps now requires introContent and renders {introContent}; renderIntroContent is gone from source, docs, story, and registry copy.
    • Why it matters: this is a published component API. Existing consumers using renderIntroContent will fail type-checking after upgrade even though this PR is scoped as react-doctor warning cleanup.
    • Fix: keep renderIntroContent as a deprecated fallback, or support both props while migrating examples/docs.
  • C2 — Slideshow public render prop was replaced with a component prop

    • Evidence: SlideshowProps now requires SectionContent; renderContent is gone from source, docs, story, and registry copy.
    • Why it matters: this breaks existing consumers that pass renderContent={(section) => ...}. The repo rules require legacy prop behavior not to change unintentionally.
    • Fix: keep renderContent compatibility or support both renderContent and SectionContent with a clear migration path.
  • C3 — code component children contracts were narrowed

    • Evidence: CodeBlockProps.children and CodePlaygroundProps.children changed from ReactNode to string / string?; CodeBlock also removed extractTextFromChildren.
    • Why it matters: MDX/JSX/numeric/array children that were previously accepted by the public types, and in CodeBlock previously converted to text, now break or lose behavior.
    • Fix: preserve the old ReactNode contract/extraction behavior, or add a new explicit code prop without removing existing children compatibility.

VERIFIED CLEAN

  • PR head/worktree matched: 5fd2fc36968c4bf750fb8a615b10ea88f0b8e7e5.
  • All 30 changed files were covered through direct inspection and focused subreview; source and generated registry copies are internally in parity for the new names.
  • Remote GitHub checks were green when captured: Storybook, Quality Gates, issue-linked PR gate, codebase health scan, and Vercel previews.

VALIDATION

  • Ran locally: git diff --check origin/main...HEAD — clean.
  • Ran targeted grep/evidence capture for renamed public props and source/registry parity.
  • Did not rerun full pnpm gates locally; relied on captured green remote checks plus diff/evidence inspection.

Note: the authenticated reviewer is also the PR author, so GitHub may reject a formal REQUEST_CHANGES review. If that happens, treat this same payload as the authoritative fallback review for the current head.

/** Estimated time to complete */
estimatedTime: string;
/** Rendered introduction content */
introContent: ReactNode;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: this renames the published ContentIntro API from renderIntroContent to required introContent instead of preserving compatibility. Existing consumers using the old render prop will fail type-checking on upgrade. Please keep renderIntroContent as a deprecated fallback or support both props while migrating docs/examples.

/** Render function for section content */
renderContent: (section: SlideshowSection) => ReactNode;
/** Component used to render the current section content */
SectionContent: ComponentType<SlideshowSectionContentProps>;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: this replaces the published renderContent render prop with required SectionContent. That breaks existing Slideshow consumers even though the PR is scoped as react-doctor warning cleanup. Please preserve renderContent compatibility or support both APIs with a migration path.


type CodeBlockProps = {
children: ReactNode;
children?: string;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: CodeBlock used to accept ReactNode children and extract text from strings, numbers, arrays, and nested elements. Narrowing this to string and rendering children ?? "" breaks existing MDX/JSX-shaped children. Please preserve the ReactNode contract/extraction behavior or add a separate explicit code prop without removing the old behavior.


export type CodePlaygroundProps = {
children: ReactNode;
children?: string;
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: this narrows the public CodePlaygroundProps.children type from ReactNode to string?. Even though the runtime only displayed string children before, this is still a public type break for existing consumers. Please preserve the old type contract or introduce a compatible migration path.


import type { ReactNode } from "react";

import { useDocumentEventListener } from "@vllnt/ui";
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking: this registry copy now imports useDocumentEventListener from @vllnt/ui, but the registry item still declares @vllnt/ui@^0.2.1 and packages/ui/package.json is still 0.2.1. The currently published @vllnt/ui@0.2.1 does not export this hook, so shadcn add .../content-intro.json can install a component that immediately fails to compile for consumers until a future package version exists and is resolved. Keep this helper local in the registry copy, or bump/publish and point the registry dependency at a version that actually contains the export.

import type { ComponentType, ReactNode } from "react";
import { createPortal } from "react-dom";

import { useDocumentEventListener } from "@vllnt/ui";
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same registry compatibility blocker here: the copied Slideshow component imports the new useDocumentEventListener export from @vllnt/ui, while registry.json still advertises @vllnt/ui@^0.2.1; published 0.2.1 has no such export. This replaces the fixed render-prop API break with a registry-install break. Make the registry copy self-contained for the hook or update the dependency/versioning so the installed package definitely exports it.

@@ -33,9 +33,9 @@ const sections = [

<ContentIntro
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking docs/API issue: the public usage snippet omits required completedSections, even though the API table below correctly marks it required. This gives consumers a copy-paste example that does not satisfy ContentIntroProps. Add completedSections={new Set()} (or equivalent state) so the docs match the current public contract.

return <p>{section.title}</p>
}

<Slideshow
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking docs/API issue: this public usage snippet omits required completedSections and passes currentIndex="0" as a string while the prop is typed as number. Since this PR is specifically about public API compatibility and docs/MDX freshness, the example needs to be copy-paste valid against SlideshowProps (completedSections={new Set()}, currentIndex={0}, etc.).

@bntvllnt
Copy link
Copy Markdown
Collaborator Author

Formal REQUEST_CHANGES could not be submitted because GitHub rejects review verdicts from the PR author identity (Review Can not request changes on your own pull request). Publishing the blocking review as a comment fallback; inline file comments were also posted on the affected lines.

Review — 4 blocking findings

BLOCKING

  • C1 — Registry copies depend on an unpublished @vllnt/ui export

    • Evidence: apps/registry/registry/default/content-intro/content-intro.tsx:7, apps/registry/registry/default/slideshow/slideshow.tsx:8-9, apps/registry/registry.json:1177-1179, apps/registry/registry.json:3494-3496.
    • I verified npm view @vllnt/ui version is still 0.2.1, packages/ui/package.json is still 0.2.1, and a packed copy of published @vllnt/ui@0.2.1 contains no useDocumentEventListener export. Consumers installing these registry items can therefore get copied components that import a package export which does not exist in the advertised dependency range.
    • Fix: keep the hook local/self-contained in registry copies, or bump/publish the package and pin the registry dependency to a version that actually contains the export.
  • C2 — Public docs snippets are not valid against required props

    • Evidence: packages/ui/src/components/content-intro/content-intro.mdx:34-41 omits required completedSections; packages/ui/src/components/slideshow/slideshow.mdx:38-47 omits required completedSections and passes currentIndex="0" even though the prop is typed as number.
    • Why it matters: this PR is specifically a public API compatibility remediation; MDX/docs must not advertise copy-paste usage that violates the public prop contract.
    • Fix: update the snippets to include completedSections={new Set()} or equivalent state and currentIndex={0}.

WARN

  • None beyond the blockers above.

VERIFIED CLEAN

  • The four prior blocker groups are resolved in source: ContentIntro keeps renderIntroContent while adding introContent; Slideshow keeps renderContent while adding SectionContent; CodeBlock restores ReactNode children extraction and adds explicit code; CodePlayground restores ReactNode children extraction and adds explicit code.
  • Source/registry copies were compared for the four target components; the remaining issue is the registry dependency/installability boundary above, not source API removal.
  • Current-head GitHub checks are green except the known waived/canceled Vercel – ui.vllnt.ai app-preview status; Storybook, visual regression, quality gates, CodeQL, issue-link, and build/story checks are passing on head 15adb082e80f24d56991cede602b4af61cef88f9.

VALIDATION

  • Reviewed all 38 changed files in the PR diff at current head 15adb082e80f24d56991cede602b4af61cef88f9.
  • Ran/read: gh pr view 346 --json ..., gh pr checks 346, git diff --name-only origin/main...HEAD, git diff --stat origin/main...HEAD, targeted source/registry/docs diffs, and npm pack @vllnt/ui@0.2.1 grep for useDocumentEventListener.
  • Did not rerun the full R6 suite locally; used current-head CI plus parent validation evidence for those gates.

Copy link
Copy Markdown
Collaborator Author

@bntvllnt bntvllnt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — 0 findings (current head)

BLOCKING

  • None found in the remediation delta.

WARN

  • W1 — full registry lint remains blocked by unrelated tooling debt
    • Evidence: pnpm -F @vllnt/ui-registry lint still exits 2 before the touched registry copies, at apps/registry/app/report/report-bug-form.tsx:73, with eslint-plugin-jsx-a11y / minimatch runtime TypeError: (0 , _minimatch.default) is not a function.
    • Why it matters: this is still a validation gap for the registry app as a whole, but it is not evidence of a regression in this remediation because the targeted registry typecheck/lint and all current GitHub checks are green.

VERIFIED CLEAN

  • Reviewed current head e84c03866daf9f43df9aa9866814926098bd37bd against previous reviewed head 15adb082e80f24d56991cede602b4af61cef88f9; the new remediation delta is limited to the two registry copies plus the two MDX docs.
  • apps/registry/registry/default/content-intro/content-intro.tsx no longer imports useDocumentEventListener from @vllnt/ui; it defines the document listener helper locally and imports only cn / Button from @vllnt/ui.
  • apps/registry/registry/default/slideshow/slideshow.tsx no longer imports useDocumentEventListener or useMounted from @vllnt/ui; both helpers are local and the only @vllnt/ui imports are cn / CompletionDialog.
  • Public MDX examples now include the required props: ContentIntro includes completedSections={new Set()}, and Slideshow includes both completedSections={new Set()} and currentIndex={0}.
  • PR metadata is current enough for this head: body calls out the registry export-contract remediation, the MDX required-prop fix, validation evidence, and Closes #277.
  • Current GitHub checks are all passing: Quality Gates, PR Issue Link, Verify Stories, Scan codebase health, CodeQL, Build Storybook, Storybook Tests, and Visual Regression.

VALIDATION

  • Ran: git diff --check — PASS.
  • Ran: pnpm -F @vllnt/ui lint — PASS.
  • Ran: pnpm -F @vllnt/ui exec tsc --noEmit --project tsconfig.build.json — PASS.
  • Ran: pnpm -F @vllnt/ui-registry exec tsc --noEmit --project tsconfig.json — PASS.
  • Ran: pnpm -F @vllnt/ui-registry exec eslint registry/default/content-intro/content-intro.tsx registry/default/slideshow/slideshow.tsx — PASS with package-config ignore warnings only.
  • Ran: npm pack @vllnt/ui@0.2.1 plus export-file probe — PASS for the relevant contract: root exports cn and re-exports components including Button / CompletionDialog; no root export for useDocumentEventListener / useMounted was found.
  • Ran: pnpm -F @vllnt/ui-registry lint — blocked by the unrelated existing eslint-plugin-jsx-a11y / minimatch runtime error noted above.

Manual approval is recommended, but final approval remains reserved for bntvllnt.

@vllnt-pilot vllnt-pilot Bot had a problem deploying to Preview · pr-346-storybook May 18, 2026 17:14 Failure
@vllnt-pilot
Copy link
Copy Markdown

vllnt-pilot Bot commented May 18, 2026

Preview ready · Updated 2026-05-20T16:10:16Z

Service Status Preview
ui-registry Ready https://pr-346-ui-registry.preview.vllnt.ai
Inspect
  • Tailnet-only by default (not reachable from public internet)
  • Cert: real Let's Encrypt wildcard
  • Reply with /clean to destroy this preview now

@vllnt vllnt deleted a comment from vllnt-pilot Bot May 18, 2026
@vllnt vllnt deleted a comment from vercel Bot May 18, 2026
…rect

React 17+ registers synthetic onWheel handlers as passive, making
preventDefault() a no-op and emitting a console warning. Restore the
native addEventListener({ passive: false }) pattern using a ref on the
scroll container so preventDefault() actually cancels the scroll event.

Also add a TSDoc note to useDocumentEventListener warning callers that
the options parameter must be a stable reference to avoid re-registering
the listener on every render.

import type { ReactNode } from "react";

import { useDocumentEventListener } from "@vllnt/ui";
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This registry copy now imports useDocumentEventListener from @vllnt/ui, but the registry entry still advertises @vllnt/ui@^0.2.1; I verified the currently published 0.2.1 package does not expose this root export. Consumers installing this registry item can therefore get a copied component that fails to compile/import. Please either keep the hook local to the registry copy or bump/publish/pin the dependency to a version that actually contains the export.

@bntvllnt
Copy link
Copy Markdown
Collaborator Author

Review — REQUEST_CHANGES

I reviewed PR #346 at current head 52ef7f970f85cc520b7cba36febcd86c4cc564ff against origin/main.

BLOCKING

  • C1 — completion-dialog registry copy imports an export that the declared package dependency does not provide
    • Evidence: apps/registry/registry/default/completion-dialog/completion-dialog.tsx:7 imports useDocumentEventListener from @vllnt/ui, while the registry item still declares @vllnt/ui@^0.2.1 in apps/registry/registry.json:1140-1142 and package version remains 0.2.1.
    • I verified the currently published @vllnt/ui@0.2.1 with npm pack @vllnt/ui@0.2.1; its root package exports do not expose useDocumentEventListener / useEventCallback / useWindowEventListener. A consumer installing this registry component against the advertised dependency range can therefore copy code that fails to compile/import.
    • This also makes the PR body stale/inaccurate for the current head: it says registry copies no longer import unpublished event hooks from @vllnt/ui@^0.2.1, but completion-dialog still does.
    • Fix: keep this hook local/self-contained in the registry copy, or bump/publish the package and pin the registry dependency to a version that actually contains the export.

WARN

  • W1 — pre-existing event-interception patterns remain worth hardening
    • CodeBlock, Carousel, and CompletionDialog still contain capture/wheel keyboard handling that could benefit from explicit nested-interactive / nested-scroll-container guards. I did not classify these as current-head blockers because the most concerning patterns predate this remediation or are narrowly scoped, and the concrete installability regression above is sufficient to block.

VERIFIED CLEAN

  • Current PR metadata: non-draft, mergeable, mergeStateStatus=CLEAN; issue link is present (Closes #277).
  • Current GitHub checks are green on this head, including Quality Gates, PR Issue Link, CodeQL, Storybook, react-doctor, ui-registry deploy, storybook deploy, Storybook Tests, Visual Regression, and preview deploy.
  • Local full gate run at head passed: pnpm -F @vllnt/ui lint; pnpm -F @vllnt/ui exec tsc --noEmit --project tsconfig.build.json; pnpm -F @vllnt/ui-registry exec tsc --noEmit --project tsconfig.json; pnpm build; pnpm test:once (218 files, 1222 tests).
  • git diff --check origin/main...HEAD passed.
  • Reviewed the changed-file ledger for all 38 changed files and focused the blocking finding on the registry dependency/installability boundary.

Because GitHub may reject a formal REQUEST_CHANGES review from the PR author identity, treat this payload as the request-changes-equivalent review if the API refuses the verdict.

Copy link
Copy Markdown
Collaborator Author

@bntvllnt bntvllnt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review — completion-dialog registry remediation at 700c836

BLOCKING

  • None in the bounded completion-dialog remediation. The original registry installability blocker is resolved.

WARN

  • W1 — vllnt-pilot status artifact still leaves the PR UNSTABLE
    • Evidence: the current head has one nameless vllnt-pilot check run failing with BuildKit reported error: exec: "buildctl": executable file not found in $PATH (https://github.com/vllnt/ui/runs/76871625654), while the later named vllnt-pilot / preview deploy check succeeded and produced https://pr-346-ui-registry.preview.vllnt.ai.
    • Interpretation: this is an infrastructure/deployer check artifact, not a completion-dialog code regression, but it is still visible in GitHub's status rollup and keeps mergeStateStatus at UNSTABLE until rerun/cleared/routed.

VERIFIED CLEAN

  • apps/registry/registry/default/completion-dialog/completion-dialog.tsx no longer imports useDocumentEventListener from @vllnt/ui; it imports only published primitives cn and Button from @vllnt/ui and defines the small document event helper locally.
  • apps/registry/registry.json still advertises completion-dialog with dependencies: ["@vllnt/ui@^0.2.1"] and no registryDependencies, matching the intended install contract.
  • npm pack @vllnt/ui@0.2.1 confirms Button, cn, and CompletionDialog are exported, while useDocumentEventListener and useMounted are not exported. The registry copy no longer depends on those unpublished hooks.
  • The PR body now names current head 700c83645da13fb44731643f0309f4ff6ff61d2d, states the hook-inlining remediation, includes the current registry typecheck/diff-check evidence, and accurately calls out the known @vllnt/ui-registry lint blocker.

VALIDATION

  • Reviewed live PR #346 at head 700c83645da13fb44731643f0309f4ff6ff61d2d.
  • Ran git diff --check origin/main...HEAD — passed.
  • Ran pnpm -F @vllnt/ui-registry exec tsc --noEmit --project tsconfig.json — passed.
  • Ran pnpm -F @vllnt/ui-registry lint — reproduced the PR-body-described existing eslint-plugin-jsx-a11y / minimatch runtime error before reaching the touched registry copy.
  • Re-fetched GitHub checks: all named Actions/deploy checks are passing; only the nameless vllnt-pilot check run is failing as described above.

Verdict: no code changes requested for the completion-dialog remediation. Manual approval/merge should wait for the visible nameless vllnt-pilot failure to be cleared or explicitly accepted as an infra artifact.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

react-doctor Reported by react-doctor (codebase health) tech-debt Refactoring or cleanup

Projects

None yet

Development

Successfully merging this pull request may close these issues.

react-doctor: misc — render-in-render, polymorphic children, giant components (~40 warnings)

1 participant