Use native markdown dispatcher#113
Conversation
This reverts commit 2f52e21.
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds a Changesleadtype/markdown native transform pipeline
Sequence Diagram(s)sequenceDiagram
participant Caller
participant convertAllMdx
participant prepareMdxConversion
participant runMdxAstTransforms
participant stringifyMarkdown
Caller->>convertAllMdx: markdownTransforms + markdownEngine
convertAllMdx->>prepareMdxConversion: resolve engine and transforms
prepareMdxConversion-->>convertAllMdx: engine-aware conversion state
convertAllMdx->>runMdxAstTransforms: transform mdast tree
runMdxAstTransforms-->>convertAllMdx: transformed tree
convertAllMdx->>stringifyMarkdown: serialize tree
stringifyMarkdown-->>convertAllMdx: markdown output
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 19
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
docs/reference/convert.mdx (1)
63-68: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winFix the
writeMdxFileAsMarkdownexample signature.This snippet still documents a non-existent object overload.
writeMdxFileAsMarkdowntakes the MDX file path as the first argument, and its config usesoutDir, notsrcPath/outPath. Copying this example will fail against the actual API.Suggested fix
-await writeMdxFileAsMarkdown({ - srcPath: "docs/quickstart.mdx", - outPath: "public/docs/quickstart.md", - markdownTransforms: defaultMarkdownTransforms, -}); +await writeMdxFileAsMarkdown("docs/quickstart.mdx", { + outDir: "public/docs", + markdownTransforms: defaultMarkdownTransforms, +});🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/reference/convert.mdx` around lines 63 - 68, The `writeMdxFileAsMarkdown` example is using a stale object-style signature that does not match the वास्तविक API. Update the docs snippet to show `writeMdxFileAsMarkdown` being called with the MDX file path as the first argument and the config using `outDir` instead of `srcPath`/`outPath`, so the example aligns with the actual function signature and can be copied without failing.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/tanstack/scripts/bench.ts`:
- Around line 263-266: The stage lookup in the bench aggregation logic is
treating valid 0ms medians as missing because the truthy check on remark and
satteri rejects zero values. Update the guard in the stage-processing code that
uses byLabel.get for remark:${stage} and satteri:${stage} to test for undefined
explicitly instead of relying on truthiness, so zero-valued medians are still
included.
- Around line 35-50: The BENCH_ENGINES parsing in bench.ts can leave ENGINES
empty when the env var is an empty string or only contains commas/whitespace,
because the current defaulting and token filtering skip blanks. Update the
BENCH_ENGINES handling around requestedEngines/ENGINES to treat an empty
effective list as invalid, and fail fast with the same stderr/exit path before
benchmark execution continues. Use the existing validation flow in the loop that
checks rawEngine/engine and the ENGINES array to locate where to add the
empty-list guard.
In `@apps/tanstack/src/routes/playground.tsx`:
- Around line 31-35: The playground recipe copy is inconsistent with the example
because it still says “remark entry points” while the code uses
leadtype/markdown. Update the adjacent summary text in the playground card so it
matches the new entry point wording shown by the Markdown transform example in
the playground route.
In `@docs/concepts/architecture.mdx`:
- Line 16: The architecture table currently lists only leadtype/markdown, but
leadtype/remark is still a public compatibility export and should remain
documented. Update the architecture overview in the relevant markdown table to
include leadtype/remark alongside leadtype/markdown, either as its own row or a
compatibility note, so the documented surface matches the exported API. Use the
existing architecture table entry for leadtype/markdown as the place to add the
leadtype/remark reference.
In `@docs/package-docs/bundle.mdx`:
- Line 107: The convertAllMdx example currently uses only
defaultMarkdownTransforms, so copied scripts that rely on include tags will not
resolve partials. Update the example to use includeMarkdown together with
defaultMarkdownTransforms in the markdown transform list, and reference the
convertAllMdx setup so users can locate the change easily.
In `@docs/writing/components.mdx`:
- Line 33: The migration guidance still says “plugin stack” even though the code
and examples use markdownTransforms/defaultMarkdownTransforms. Update the
wording in the relevant docs section to “transform stack” so the terminology
matches the current API and the includeMarkdown placement guidance remains
consistent.
In `@packages/leadtype/src/convert/engine-parity.test.ts`:
- Around line 86-93: The legacy transform factory is inserting the TypeTable
node helper directly instead of the remark pluggable, which breaks the intended
parity override. Update createLegacyMarkdownTransforms to filter and re-add the
actual transform symbol used by the markdown pipeline (the wrapper from
packages/leadtype/src/markdown/plugins/type-table.ts, not typeTableToMarkdown),
so the legacy path still overrides TypeTable behavior through the pluggable
list.
In `@packages/leadtype/src/markdown/default-transforms.ts`:
- Around line 96-119: Add AutoTypeTable to the built-in recognized component
list in BUILTIN_FLATTENER_COMPONENT_NAMES. The lintDocs() recognizedComponents
set is derived from this array, so update the default-transforms list to include
AutoTypeTable alongside the other supported flattener components. Keep the
existing nativeMarkdownComponentsToMarkdown and typeTableToMarkdown support
aligned with this list so valid AutoTypeTable usage is no longer reported as
unflattened.
In `@packages/leadtype/src/markdown/define-flattener.ts`:
- Around line 102-114: `flattenChildren` is discarding the caller’s source path
by always passing an empty `filePath` into `runMdastTransformsSync`, which
breaks nested type resolution in `ExtractedTypeTable` and related built-ins.
Update `flattenChildren` in `define-flattener` to accept and thread through the
current VFile path (for example from `file?.path`) instead of hardcoding `""`,
so `typeTableToMarkdown` can still use its `basePath` fallback when resolving
relative types. Keep the change localized to the `flattenChildren` call path and
preserve existing markdown stringification behavior.
In `@packages/leadtype/src/markdown/markdown-output.test.ts`:
- Line 104: The TypeTable cases are using the low-level node converter instead
of the pluggable transform contract, so update the markdown-output tests to
register the actual TypeTable transform wrapper rather than
`typeTableToMarkdown` in `convertMdxToMarkdown`. Use the existing
`typeTableToMarkdown` helper only through the transform entry point, and make
the same adjustment in the other affected TypeTable test cases so they exercise
the migrated pipeline consistently.
In `@packages/leadtype/src/markdown/plugins/callout.ts`:
- Around line 105-110: Normalize string-literal expression props in
calloutToMarkdown before using them, because getAttributeValue() can return
quoted expression source for {....} attrs. Strip the wrapper for both
variant/type and title the same way audienceToMarkdown() does, so
variant={"warning"} matches variantLabelAndEmoji() correctly and title={"Heads
up"} is stored without extra quotes.
In `@packages/leadtype/src/markdown/plugins/cards.ts`:
- Around line 133-139: The Cards visitor in the markdown plugin mutates
parent.children during visit without returning the updated traversal state,
which can cause the next sibling to be skipped or the detached node to be
revisited. Update the visitor logic in cards.ts to return [SKIP, index]
immediately after each splice in the cardsToMarkdown handling path, using the
Cards visitor and its parent.children splice branch as the fix point so
traversal resumes from the adjusted index correctly.
In `@packages/leadtype/src/markdown/plugins/command-tabs.ts`:
- Around line 67-80: Normalize attribute values in CommandTabs before using
them: getAttributeValue() may return quoted expression source for {""}-style
props, so rawMode and rawCommand should unwrap string-literal expressions the
same way audienceToMarkdown() does. Update the CommandTabs parsing logic to
strip the extra quotes from command and mode before trimming, so
mode={"install"} resolves to install and command={"foo"} generates commands
without embedded quotes.
In `@packages/leadtype/src/markdown/plugins/details.ts`:
- Around line 58-60: The legacy details plugin only matches the lowercase tag,
which causes `<Details>` inputs to bypass the compatibility path even though the
native dispatcher and default transform registration already recognize both
variants. Update `remarkDetailsToMarkdown` in `details.ts` so the JSX component
processor also registers the capitalized `Details` name alongside `details`,
keeping the legacy flattener aligned with the native engine behavior.
In `@packages/leadtype/src/markdown/plugins/type-table.ts`:
- Around line 49-52: The __extractedTypeByKey cache in type-table.ts is
accumulating stale entries because the map key currently bakes in mtimeMs, so
each edit creates a new entry instead of replacing the old one. Update the
caching in the type extraction flow (around the logic that reads and stores
extracted types) so the map is keyed only by ${resolvedPath}\0${typeName}, and
move mtimeMs into the stored value or payload. Make sure the read/write path in
the type-table plugin reuses the same stable key so newer parses overwrite prior
results instead of leaving historical versions resident.
In `@packages/leadtype/src/markdown/stringify.test.ts`:
- Around line 5-73: Add regression coverage in stringifier tests for the new
native inline paths: extend stringifyMarkdown in stringify.test.ts with cases
for inlineCode values that contain backticks or leading/trailing significant
spaces, and for mdxJsxTextElement children to verify the fallback serialization
is preserved. Use the existing stringifyMarkdown test suite and Root fixtures to
locate the new assertions, and keep the expectations focused on the emitted
markdown text.
In `@packages/leadtype/src/markdown/stringify.ts`:
- Around line 95-109: The inlineCode() serializer is normalizing whitespace and
always using a single-backtick delimiter, which breaks round-tripping for mdast
code spans with repeated spaces or embedded backticks. Update inlineCode() so it
preserves the original content spacing for inline code values and chooses a safe
backtick delimiter length based on the content, keeping the existing padding
logic in stringify.ts intact.
- Around line 375-390: The MDX JSX serializer in stringify.ts treats
mdxJsxTextElement the same as mdxJsxFlowElement, which adds block-style newlines
and paragraph breaks to inline JSX. Update the stringifyNode branch for jsx
nodes to distinguish mdxJsxTextElement from mdxJsxFlowElement: keep
mdxJsxFlowElement using the current multiline wrapper behavior, but serialize
mdxJsxTextElement inline by joining children without paragraph separators and
without surrounding newlines. Use the existing helpers like stringifyNode,
hasChildren, and stringifyMdxAttribute to preserve attribute handling while
changing only the text-element formatting.
In `@packages/leadtype/src/markdown/transform.ts`:
- Around line 39-73: createMdastTransforms() currently only converts bare
attachers and misses the broader PluggableList behavior, so valid presets and
context-bound unified plugins can be handled incorrectly. Update toTransform()
and createMdastTransforms() to either explicitly restrict the API to leaf
attachers or invoke the plugins through a real unified processor so attachers
receive the proper this binding and presets are expanded before transformer
extraction. Use the existing sortRemarkPluginsByPhase(), toTransform(), and
createMdastTransforms() flow as the place to apply the fix.
---
Outside diff comments:
In `@docs/reference/convert.mdx`:
- Around line 63-68: The `writeMdxFileAsMarkdown` example is using a stale
object-style signature that does not match the वास्तविक API. Update the docs
snippet to show `writeMdxFileAsMarkdown` being called with the MDX file path as
the first argument and the config using `outDir` instead of `srcPath`/`outPath`,
so the example aligns with the actual function signature and can be copied
without failing.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: d378c208-2e95-4f4a-a2a1-e4305f1fa3f4
📒 Files selected for processing (77)
apps/c15t-example/scripts/llm-generate-real.tsapps/c15t-example/scripts/test-real.tsapps/tanstack/README.mdapps/tanstack/scripts/bench.tsapps/tanstack/scripts/convert-real.tsapps/tanstack/scripts/mdx-convert.tsapps/tanstack/scripts/search-bench.tsapps/tanstack/scripts/test-engine-parity.tsapps/tanstack/scripts/test-pipeline.tsapps/tanstack/scripts/test-real.tsapps/tanstack/src/lib/docs.tsapps/tanstack/src/routes/playground.tsxapps/tanstack/tests/e2e/smoke.e2e.tsapps/tanstack/tsconfig.jsondocs/concepts/architecture.mdxdocs/package-docs/bundle.mdxdocs/pipeline/generate-static-artifacts.mdxdocs/reference/cli.mdxdocs/reference/convert.mdxdocs/reference/llm.mdxdocs/reference/mdx.mdxdocs/reference/remark.mdxdocs/writing/components.mdxdocs/writing/write-for-agents.mdxexamples/shared-docs/flatteners.tspackages/leadtype/CHANGELOG.mdpackages/leadtype/README.mdpackages/leadtype/package.jsonpackages/leadtype/rollup.config.tspackages/leadtype/scripts/generate-docs.tspackages/leadtype/src/cli/generate.tspackages/leadtype/src/convert/convert.tspackages/leadtype/src/convert/engine-parity.test.tspackages/leadtype/src/index.tspackages/leadtype/src/internal/package-surface.test.tspackages/leadtype/src/internal/remark-phase.tspackages/leadtype/src/lint/runner.tspackages/leadtype/src/markdown/builders.tspackages/leadtype/src/markdown/component-dispatcher.tspackages/leadtype/src/markdown/default-transforms.tspackages/leadtype/src/markdown/define-flattener.test.tspackages/leadtype/src/markdown/define-flattener.tspackages/leadtype/src/markdown/index.tspackages/leadtype/src/markdown/libs/attributes.tspackages/leadtype/src/markdown/libs/content-processor.tspackages/leadtype/src/markdown/libs/generic-processor.tspackages/leadtype/src/markdown/libs/guards.tspackages/leadtype/src/markdown/libs/index.tspackages/leadtype/src/markdown/libs/node-creators.tspackages/leadtype/src/markdown/libs/text.tspackages/leadtype/src/markdown/libs/types.tspackages/leadtype/src/markdown/markdown-output.test.tspackages/leadtype/src/markdown/plugins/accordion.tspackages/leadtype/src/markdown/plugins/audience.tspackages/leadtype/src/markdown/plugins/callout.tspackages/leadtype/src/markdown/plugins/cards.tspackages/leadtype/src/markdown/plugins/command-tabs.tspackages/leadtype/src/markdown/plugins/details.tspackages/leadtype/src/markdown/plugins/example.tspackages/leadtype/src/markdown/plugins/file-tree.tspackages/leadtype/src/markdown/plugins/mermaid.tspackages/leadtype/src/markdown/plugins/prompt.tspackages/leadtype/src/markdown/plugins/section.tspackages/leadtype/src/markdown/plugins/steps.tspackages/leadtype/src/markdown/plugins/tabs.tspackages/leadtype/src/markdown/plugins/topic-switcher.tspackages/leadtype/src/markdown/plugins/type-table.tspackages/leadtype/src/markdown/stringify.test.tspackages/leadtype/src/markdown/stringify.tspackages/leadtype/src/markdown/transform.tspackages/leadtype/src/mdx/index.tspackages/leadtype/src/mdx/source-preset.tspackages/leadtype/src/remark/default-plugins.tspackages/leadtype/src/remark/index.tspackages/leadtype/src/remark/plugins/include.remark.tspackages/leadtype/src/remark/plugins/prompt.remark.tspackages/leadtype/src/remark/plugins/type-table-jsx.remark.ts
💤 Files with no reviewable changes (2)
- packages/leadtype/src/remark/default-plugins.ts
- packages/leadtype/src/remark/plugins/prompt.remark.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
Files:
packages/leadtype/src/markdown/stringify.test.tspackages/leadtype/src/index.tspackages/leadtype/src/mdx/index.tspackages/leadtype/rollup.config.tspackages/leadtype/src/internal/remark-phase.tspackages/leadtype/src/mdx/source-preset.tsapps/tanstack/src/routes/playground.tsxpackages/leadtype/src/markdown/plugins/section.tsapps/tanstack/scripts/mdx-convert.tspackages/leadtype/src/remark/plugins/type-table-jsx.remark.tsexamples/shared-docs/flatteners.tsapps/tanstack/tests/e2e/smoke.e2e.tspackages/leadtype/src/markdown/plugins/tabs.tspackages/leadtype/src/markdown/plugins/details.tsapps/c15t-example/scripts/llm-generate-real.tspackages/leadtype/src/markdown/plugins/prompt.tsapps/tanstack/scripts/convert-real.tsapps/tanstack/scripts/search-bench.tspackages/leadtype/scripts/generate-docs.tspackages/leadtype/src/markdown/transform.tsapps/tanstack/src/lib/docs.tspackages/leadtype/src/internal/package-surface.test.tsapps/c15t-example/scripts/test-real.tspackages/leadtype/src/markdown/index.tspackages/leadtype/src/markdown/plugins/accordion.tsapps/tanstack/scripts/test-pipeline.tspackages/leadtype/src/markdown/builders.tspackages/leadtype/src/markdown/plugins/steps.tsapps/tanstack/scripts/test-real.tspackages/leadtype/src/markdown/plugins/callout.tspackages/leadtype/src/markdown/plugins/audience.tsapps/tanstack/scripts/test-engine-parity.tspackages/leadtype/src/markdown/plugins/file-tree.tspackages/leadtype/src/markdown/define-flattener.tspackages/leadtype/src/markdown/plugins/mermaid.tspackages/leadtype/src/remark/plugins/include.remark.tspackages/leadtype/src/markdown/plugins/topic-switcher.tspackages/leadtype/src/lint/runner.tspackages/leadtype/src/markdown/plugins/cards.tspackages/leadtype/src/markdown/stringify.tspackages/leadtype/src/markdown/plugins/command-tabs.tspackages/leadtype/src/markdown/plugins/example.tspackages/leadtype/src/remark/index.tspackages/leadtype/src/markdown/define-flattener.test.tspackages/leadtype/src/markdown/default-transforms.tspackages/leadtype/src/markdown/component-dispatcher.tspackages/leadtype/src/markdown/plugins/type-table.tsapps/tanstack/scripts/bench.tspackages/leadtype/src/convert/engine-parity.test.tspackages/leadtype/src/markdown/markdown-output.test.tspackages/leadtype/src/convert/convert.tspackages/leadtype/src/cli/generate.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Don't useeval()or assign directly todocument.cookie
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Use descriptive names for functions, variables, and types for meaningful naming
Add comments for complex logic, but prefer self-documenting code
Files:
packages/leadtype/src/markdown/stringify.test.tspackages/leadtype/src/index.tspackages/leadtype/src/mdx/index.tspackages/leadtype/rollup.config.tspackages/leadtype/src/internal/remark-phase.tspackages/leadtype/src/mdx/source-preset.tsapps/tanstack/src/routes/playground.tsxpackages/leadtype/src/markdown/plugins/section.tsapps/tanstack/scripts/mdx-convert.tspackages/leadtype/src/remark/plugins/type-table-jsx.remark.tsexamples/shared-docs/flatteners.tsapps/tanstack/tests/e2e/smoke.e2e.tspackages/leadtype/src/markdown/plugins/tabs.tspackages/leadtype/src/markdown/plugins/details.tsapps/c15t-example/scripts/llm-generate-real.tspackages/leadtype/src/markdown/plugins/prompt.tsapps/tanstack/scripts/convert-real.tsapps/tanstack/scripts/search-bench.tspackages/leadtype/scripts/generate-docs.tspackages/leadtype/src/markdown/transform.tsapps/tanstack/src/lib/docs.tspackages/leadtype/src/internal/package-surface.test.tsapps/c15t-example/scripts/test-real.tspackages/leadtype/src/markdown/index.tspackages/leadtype/src/markdown/plugins/accordion.tsapps/tanstack/scripts/test-pipeline.tspackages/leadtype/src/markdown/builders.tspackages/leadtype/src/markdown/plugins/steps.tsapps/tanstack/scripts/test-real.tspackages/leadtype/src/markdown/plugins/callout.tspackages/leadtype/src/markdown/plugins/audience.tsapps/tanstack/scripts/test-engine-parity.tspackages/leadtype/src/markdown/plugins/file-tree.tspackages/leadtype/src/markdown/define-flattener.tspackages/leadtype/src/markdown/plugins/mermaid.tspackages/leadtype/src/remark/plugins/include.remark.tspackages/leadtype/src/markdown/plugins/topic-switcher.tspackages/leadtype/src/lint/runner.tspackages/leadtype/src/markdown/plugins/cards.tspackages/leadtype/src/markdown/stringify.tspackages/leadtype/src/markdown/plugins/command-tabs.tspackages/leadtype/src/markdown/plugins/example.tspackages/leadtype/src/remark/index.tspackages/leadtype/src/markdown/define-flattener.test.tspackages/leadtype/src/markdown/default-transforms.tspackages/leadtype/src/markdown/component-dispatcher.tspackages/leadtype/src/markdown/plugins/type-table.tsapps/tanstack/scripts/bench.tspackages/leadtype/src/convert/engine-parity.test.tspackages/leadtype/src/markdown/markdown-output.test.tspackages/leadtype/src/convert/convert.tspackages/leadtype/src/cli/generate.ts
**/*.{test,spec}.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{test,spec}.{js,ts,jsx,tsx}: Write assertions insideit()ortest()blocks
Avoid done callbacks in async tests - use async/await instead
Don't use.onlyor.skipin committed code
Keep test suites reasonably flat - avoid excessivedescribenesting
Files:
packages/leadtype/src/markdown/stringify.test.tspackages/leadtype/src/internal/package-surface.test.tspackages/leadtype/src/markdown/define-flattener.test.tspackages/leadtype/src/convert/engine-parity.test.tspackages/leadtype/src/markdown/markdown-output.test.ts
**/index.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
Avoid barrel files (index files that re-export everything)
Files:
packages/leadtype/src/index.tspackages/leadtype/src/mdx/index.tspackages/leadtype/src/markdown/index.tspackages/leadtype/src/remark/index.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{jsx,tsx}: Use function components over class components in React
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use thekeyprop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props
Don't define components inside other components
AvoiddangerouslySetInnerHTMLunless absolutely necessary
Use proper image components (e.g., Next.js<Image>) over<img>tags
Use Next.js<Image>component for images
Usenext/heador App Router metadata API for head elements in Next.js
Use Server Components for async data fetching instead of async Client Components in Next.js
Use ref as a prop instead ofReact.forwardRefin React 19+
Files:
apps/tanstack/src/routes/playground.tsx
**/*.{jsx,tsx,html}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{jsx,tsx,html}: Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, use semantic elements instead of divs with roles
Addrel="noopener"when usingtarget="_blank"on links
Files:
apps/tanstack/src/routes/playground.tsx
🧠 Learnings (3)
📚 Learning: 2026-06-07T09:16:11.158Z
Learnt from: KayleeWilliams
Repo: inthhq/leadtype PR: 87
File: apps/next-example/tests/e2e/smoke.e2e.ts:6-25
Timestamp: 2026-06-07T09:16:11.158Z
Learning: In the leadtype repo’s framework example apps under `apps/`, treat each app as an intentionally self-contained, copy-pasteable reference. During code review, avoid introducing cross-app/cross-workspace imports (e.g., importing shared test utilities from sibling example apps), since that couples the examples and can break vendoring a single app. Allow duplicated helper implementations within an app’s tests (including E2E files) when they exist to keep the app standalone—e.g., duplicating helpers like `installWebMcpMock` across E2E test files is acceptable by design.
Applied to files:
apps/tanstack/tests/e2e/smoke.e2e.ts
📚 Learning: 2026-06-07T09:16:18.811Z
Learnt from: KayleeWilliams
Repo: inthhq/leadtype PR: 87
File: apps/nuxt-example/tests/e2e/smoke.e2e.ts:5-24
Timestamp: 2026-06-07T09:16:18.811Z
Learning: In the leadtype example apps’ e2e smoke tests (e.g., apps/*/tests/e2e/smoke.e2e.ts), duplicated helper functions (like installWebMcpMock) are intentional. Keep helpers within each example app’s test file so each example remains a standalone, copy-whole reference. Avoid cross-workspace imports from shared test utilities unless there’s a strong, concrete reason to change the standalone/coupling design.
Applied to files:
apps/tanstack/tests/e2e/smoke.e2e.ts
📚 Learning: 2026-06-07T09:16:22.079Z
Learnt from: KayleeWilliams
Repo: inthhq/leadtype PR: 87
File: apps/tanstack/tests/e2e/smoke.e2e.ts:14-33
Timestamp: 2026-06-07T09:16:22.079Z
Learning: In inthhq/leadtype, for each example app’s standalone E2E tests under apps/*/tests/e2e (e.g., smoke.e2e.ts), avoid review suggestions to extract or import shared test helpers across workspaces. Helpers such as installWebMcpMock (and similar utilities) may be intentionally duplicated within each app’s test files to prevent coupling between separate example apps.
Applied to files:
apps/tanstack/tests/e2e/smoke.e2e.ts
🪛 ast-grep (0.44.0)
packages/leadtype/src/markdown/stringify.ts
[warning] 63-65: Avoid hand-rolled HTML escaping (replacing characters with HTML entities); use a vetted encoder/sanitizer such as DOMPurify or sanitize-html.
Context: value
.replace(/\/g, "\\")
.replace(/\n (?=\S)/g, "\n ")
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').
(manual-sanitization-typescript)
[warning] 350-350: Avoid hand-rolled HTML escaping (replacing characters with HTML entities); use a vetted encoder/sanitizer such as DOMPurify or sanitize-html.
Context: attribute.value.replace(/"/g, """)
Note: [CWE-79] Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting').
(manual-sanitization-typescript)
packages/leadtype/src/convert/convert.ts
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from "node:child_process";
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
(detect-child-process-typescript)
🪛 LanguageTool
docs/reference/remark.mdx
[uncategorized] ~55-~55: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...ypeTable>and→ markdown table. 14.remarkAccordionToMarkdown` ...
(MARKDOWN_NNP)
🔇 Additional comments (32)
packages/leadtype/src/convert/convert.ts (1)
25-30: LGTM!Also applies to: 181-182, 320-345, 351-360, 405-409, 431-442, 467-486, 506-554, 888-896, 938-967, 1008-1023, 1093-1128, 1162-1168, 1311-1316, 1363-1390
packages/leadtype/src/cli/generate.ts (1)
8-8: LGTM!Also applies to: 49-53, 144-179, 236-269, 364-448, 2570-2588
packages/leadtype/src/remark/index.ts (1)
3-45: LGTM!packages/leadtype/src/remark/plugins/include.remark.ts (1)
18-18: LGTM!Also applies to: 238-242, 291-291, 551-551
packages/leadtype/src/remark/plugins/type-table-jsx.remark.ts (1)
17-27: LGTM!packages/leadtype/src/internal/remark-phase.ts (1)
2-5: LGTM!Also applies to: 15-15
apps/c15t-example/scripts/llm-generate-real.ts (1)
9-9: LGTM!Also applies to: 18-18
apps/c15t-example/scripts/test-real.ts (1)
12-12: LGTM!Also applies to: 57-57
apps/tanstack/scripts/bench.ts (1)
15-23: LGTM!Also applies to: 151-175, 203-218, 301-301
apps/tanstack/scripts/test-pipeline.ts (1)
6-8: LGTM!Also applies to: 19-34
apps/tanstack/scripts/test-real.ts (1)
14-14: LGTM!Also applies to: 59-59
apps/tanstack/src/lib/docs.ts (1)
78-80: LGTM!examples/shared-docs/flatteners.ts (1)
1-1: LGTM!packages/leadtype/src/index.ts (1)
6-6: LGTM!apps/tanstack/README.md (1)
28-28: LGTM!apps/tanstack/scripts/mdx-convert.ts (1)
14-37: LGTM!Also applies to: 50-50
docs/pipeline/generate-static-artifacts.mdx (1)
174-186: LGTM!docs/reference/cli.mdx (1)
418-418: LGTM!docs/reference/convert.mdx (1)
23-29: LGTM!Also applies to: 37-50, 80-88
docs/reference/llm.mdx (1)
236-241: LGTM!docs/reference/mdx.mdx (1)
14-14: LGTM!docs/reference/remark.mdx (1)
2-13: LGTM!Also applies to: 32-32, 41-41, 55-55, 70-74, 99-99, 126-139, 181-207, 225-226
apps/tanstack/scripts/search-bench.ts (1)
26-26: LGTM!Also applies to: 127-127
apps/tanstack/scripts/test-engine-parity.ts (1)
3-3: LGTM!Also applies to: 13-17, 59-64, 91-91
apps/tanstack/tests/e2e/smoke.e2e.ts (1)
83-83: LGTM!apps/tanstack/tsconfig.json (1)
25-25: LGTM!packages/leadtype/package.json (1)
70-73: LGTM!packages/leadtype/rollup.config.ts (1)
18-18: LGTM!packages/leadtype/src/markdown/plugins/mermaid.ts (1)
1-1: LGTM!Also applies to: 76-93
packages/leadtype/src/markdown/plugins/section.ts (1)
6-10: LGTM!Also applies to: 24-24
packages/leadtype/src/markdown/plugins/tabs.ts (1)
181-192: LGTM!packages/leadtype/src/markdown/plugins/topic-switcher.ts (1)
9-9: LGTM!Also applies to: 116-151
| const requestedEngines = process.env.BENCH_ENGINES ?? "remark,satteri"; | ||
| const ENGINES: BenchMarkdownEngine[] = []; | ||
| for (const rawEngine of (process.env.BENCH_ENGINES ?? "satteri").split(",")) { | ||
| for (const rawEngine of requestedEngines.split(",")) { | ||
| const engine = rawEngine.trim(); | ||
| if (!engine) { | ||
| if (engine.length === 0) { | ||
| continue; | ||
| } | ||
| if (MARKDOWN_ENGINES.has(engine as BenchMarkdownEngine)) { | ||
| ENGINES.push(engine as BenchMarkdownEngine); | ||
| continue; | ||
| if (engine !== "remark" && engine !== "satteri") { | ||
| process.stderr.write( | ||
| `BENCH_ENGINES must contain remark,satteri values, got ${JSON.stringify(rawEngine)}\n` | ||
| ); | ||
| process.exit(2); | ||
| } | ||
| if (!ENGINES.includes(engine)) { | ||
| ENGINES.push(engine); | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Reject an empty effective BENCH_ENGINES list.
BENCH_ENGINES="" (or just commas/whitespace) leaves ENGINES empty because ?? does not treat an empty string as unset and the loop skips blank tokens. The script then produces an empty benchmark/report instead of failing fast.
Suggested fix
const requestedEngines = process.env.BENCH_ENGINES ?? "remark,satteri";
const ENGINES: BenchMarkdownEngine[] = [];
for (const rawEngine of requestedEngines.split(",")) {
const engine = rawEngine.trim();
if (engine.length === 0) {
continue;
}
if (engine !== "remark" && engine !== "satteri") {
process.stderr.write(
`BENCH_ENGINES must contain remark,satteri values, got ${JSON.stringify(rawEngine)}\n`
);
process.exit(2);
}
if (!ENGINES.includes(engine)) {
ENGINES.push(engine);
}
}
+if (ENGINES.length === 0) {
+ process.stderr.write(
+ "BENCH_ENGINES must include at least one of: remark,satteri\n"
+ );
+ process.exit(2);
+}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const requestedEngines = process.env.BENCH_ENGINES ?? "remark,satteri"; | |
| const ENGINES: BenchMarkdownEngine[] = []; | |
| for (const rawEngine of (process.env.BENCH_ENGINES ?? "satteri").split(",")) { | |
| for (const rawEngine of requestedEngines.split(",")) { | |
| const engine = rawEngine.trim(); | |
| if (!engine) { | |
| if (engine.length === 0) { | |
| continue; | |
| } | |
| if (MARKDOWN_ENGINES.has(engine as BenchMarkdownEngine)) { | |
| ENGINES.push(engine as BenchMarkdownEngine); | |
| continue; | |
| if (engine !== "remark" && engine !== "satteri") { | |
| process.stderr.write( | |
| `BENCH_ENGINES must contain remark,satteri values, got ${JSON.stringify(rawEngine)}\n` | |
| ); | |
| process.exit(2); | |
| } | |
| if (!ENGINES.includes(engine)) { | |
| ENGINES.push(engine); | |
| } | |
| const requestedEngines = process.env.BENCH_ENGINES ?? "remark,satteri"; | |
| const ENGINES: BenchMarkdownEngine[] = []; | |
| for (const rawEngine of requestedEngines.split(",")) { | |
| const engine = rawEngine.trim(); | |
| if (engine.length === 0) { | |
| continue; | |
| } | |
| if (engine !== "remark" && engine !== "satteri") { | |
| process.stderr.write( | |
| `BENCH_ENGINES must contain remark,satteri values, got ${JSON.stringify(rawEngine)}\n` | |
| ); | |
| process.exit(2); | |
| } | |
| if (!ENGINES.includes(engine)) { | |
| ENGINES.push(engine); | |
| } | |
| } | |
| if (ENGINES.length === 0) { | |
| process.stderr.write( | |
| "BENCH_ENGINES must include at least one of: remark,satteri\n" | |
| ); | |
| process.exit(2); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/tanstack/scripts/bench.ts` around lines 35 - 50, The BENCH_ENGINES
parsing in bench.ts can leave ENGINES empty when the env var is an empty string
or only contains commas/whitespace, because the current defaulting and token
filtering skip blanks. Update the BENCH_ENGINES handling around
requestedEngines/ENGINES to treat an empty effective list as invalid, and fail
fast with the same stderr/exit path before benchmark execution continues. Use
the existing validation flow in the loop that checks rawEngine/engine and the
ENGINES array to locate where to add the empty-list guard.
There was a problem hiding this comment.
Important
One e2e smoke assertion was missed during the leadtype/remark → leadtype/markdown rename and will fail in CI. The dispatcher refactor itself looks sound. Remaining items are informational.
Reviewed changes — initial review of the reapplied refactor that replaces the sequential remark flattener stack with a single native component dispatcher under a new leadtype/markdown surface, preserving output parity with the legacy stack.
- Native dispatcher —
markdown/component-dispatcher.ts(nativeMarkdownComponentsToMarkdown) walks the tree once with priority-windowed recursion (processChild/processInsertedChildren) to emulate sequential per-component flattening; mutation-during-iteration accounting traced correct. - New
leadtype/markdownsurface — package./markdownexport, rollup entry, tsconfig path, andpackage-surface.test.tsare wired consistently;leadtype/remarkretained with@deprecatedaliases. - Native stringifier —
markdown/stringify.ts(emphasis*, thematic break***); per-component plugins moved tomarkdown/plugins/*, withprompt.tsnow using nativestringifyMarkdownin place of the deletedprompt.remark.ts. - Parity tests reworked — convert-level parity (legacy stack + remark engine vs dispatcher + satteri engine) retained over fixtures and the full docs corpus; the CLI cross-engine artifact test was replaced with a single-engine satteri smoke test.
- Docs / scripts / benchmarks updated for the new transform nomenclature.
⚠️ E2e smoke test still asserts the old page heading
The reference page renamed its frontmatter title from "Remark plugins" to "Markdown transforms" in docs/reference/remark.mdx, and the smoke test was partially updated (the defaultRemarkPlugins → defaultMarkdownTransforms body assertion at smoke.e2e.ts:83). The heading assertion further down was not.
apps/tanstack/tests/e2e/smoke.e2e.ts:89 still requires a visible heading named exactly "Remark plugins", which no longer renders, so this test will fail against the built app.
Technical details
# E2e smoke test asserts a heading that no longer exists
## Affected sites
- `apps/tanstack/tests/e2e/smoke.e2e.ts:89` — `page.getByRole("heading", { name: "Remark plugins", exact: true })` — the page h1 now derives from `title: "Markdown transforms"`.
- `docs/reference/remark.mdx:2` — title changed to `"Markdown transforms"`.
## Required outcome
- The heading assertion matches the new rendered title so the smoke test passes.
## Suggested approach
- Update line 89 to `{ name: "Markdown transforms", exact: true }`.ℹ️ CLI cross-engine artifact parity is no longer asserted
The prior generates matching CLI artifacts with both engines test ran generate under both --markdown-engine remark and satteri and byte-compared the emitted .md files, llms.txt, and search-index.json. Its replacement (generates CLI artifacts with the default Satteri parser) runs only the satteri path and asserts presence/substrings.
Convert-level parity over the docs corpus is still compared across engines, so this is a narrowing rather than a total loss — but llms.txt and search-index.json are no longer verified to match across the two engines at the CLI boundary. Worth confirming the narrowing is intentional.
Technical details
# CLI-level cross-engine artifact parity dropped
## Affected sites
- `packages/leadtype/src/convert/engine-parity.test.ts` (new lines ~249-303) — single-engine satteri smoke test replaces the removed cross-engine `compareTrees` + `llms.txt` + `search-index.json` comparison.
## Required outcome
- Confirm that convert-level corpus parity is considered sufficient coverage, or restore a cross-engine assertion for `llms.txt` / `search-index.json` at the CLI boundary.
## Open questions for the human
- Is dropping CLI-level cross-engine artifact parity intentional, given the convert-level corpus test still compares `.md` output?ℹ️ Nitpicks
docs/writing/components.mdx:71links to/docs/reference/remark#remarkinclude, but that section heading was renamed to### includeMarkdown— the anchor is now#includemarkdownand the current link lands at the top of the page.docs/reference/cli.mdxlink text reads`leadtype/markdown`but points at/docs/reference/remark(no/docs/reference/markdownpage exists) — functional, but the text/URL mismatch is confusing.<Prompt>bodies now stringify with the native markers (*emphasis*,***rule) instead of the deletedprompt.remark.tssettings (_emphasis_,---); both compared pipelines share the newprompt.tsso parity tests pass, but prompt-body output changes versus pre-refactor — confirm intended.
Claude Opus | 𝕏
- Introduced a new module for markdown profiling to track performance metrics during markdown processing. - Integrated profiling functions into the component dispatcher, transform functions, and include plugin to record execution times. - Enhanced the overall performance monitoring of markdown transformations, allowing for better optimization and debugging. This change aims to improve the observability of markdown processing performance within the leadtype package.
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — re-review of the single new commit since the prior pullfrog review (6e75a4f), which adds env-gated markdown profiling and, alongside it, a substantial rewrite of the include resolver with caching and parallel candidate processing.
- Markdown profiling instrumentation — new
internal/markdown-profile.tsaggregates per-stage timings behindLEADTYPE_MARKDOWN_PROFILE=1and prints a tab-separated table to stderr onbeforeExit; hooks added tocomponent-dispatcher.ts,transform.ts, andinclude.remark.tsare zero-cost when disabled. - Per-transform profile names —
transform.tsnow tags each prepared transform with aprofileNamederived from the plugin function name, used only for profiling labels. - Include transform gating —
default-transforms.tstagsincludeMarkdownwith its component names (import/include-c15t/include) soshouldRunTransformcan skip it when no include tags are present. - Include resolver rewrite —
include.remark.tsadds module-global content/path caches plus an in-flight dedup map, collects candidates with grandparent location tracking, processes them viaPromise.all, then splices sequentially; compiler integrations are deliberately routed to the uncached path for hot-reload freshness.
ℹ️ Include caches have no invalidation or reset path
The rewrite adds three process-global maps in include.remark.ts — cached file bytes, cached path resolutions, and an in-flight dedup map. For one-shot CLI builds this is the intended win, and the dev-server/hot-reload path stays correct because compiler integrations are routed to the uncached resolver. The remaining exposure is narrow but real.
- Long-lived non-compiler processes that re-read an included file after it changes on disk will serve the first read's bytes, since
includeContentCacheis keyed by resolved path and never invalidated. - Test isolation — there is no
beforeEach/reset hook, so once a test exercises the cached path, a later test reusing the same resolved path (e.g. a recreated temp file) silently inherits the earlier content.
Technical details
# Include caches have no invalidation or reset path
## Affected sites
- `packages/leadtype/src/remark/plugins/include.remark.ts:32-34` — `includeContentCache`, `includePathCache`, `includeResolutionInflight` declared module-level; never cleared.
- `packages/leadtype/src/remark/plugins/include.remark.ts:463-477` — `readIncludeFileCached` caches `readFileSync` output by resolved path with no staleness check.
- `packages/leadtype/src/remark/plugins/include.remark.ts:449-461` — `resolveIncludePathCached` also caches `existsSync`-based negative resolutions (fallback to `basePaths[0]`), so a file created later in the same process keeps resolving to the stale path.
## Required outcome
- Decide and document whether these caches are intentionally process-lifetime (correct for one-shot builds) and, if any long-lived non-compiler caller exists, give them a way to bypass or clear the caches.
- Provide a reset affordance for tests if/when a test exercises `remarkInclude` (cached path) so state doesn't leak across cases.
## Open questions for the human
- Is any non-compiler caller (watch mode, repeated in-process conversion, the bench runner against mutable fixtures) expected to observe file edits within a single process? If not, this is mergeable as-is.Claude Opus | 𝕏
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
packages/leadtype/src/remark/plugins/include.remark.ts (1)
225-263: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winSplice
rootreplacements into the containing array.When an include under
root,listItem,blockquote, etc. expands to multiple nodes, this still falls through toObject.assign(node, { type: "root", ... }), which leaves an invalid nestedrootin mdast. Only the paragraph case needs promotion to the grandparent; every otherrootreplacement should replace the include node in its parent container.Proposed fix
function replaceTarget( tree: Root, node: Record<string, unknown>, parent: Record<string, unknown> | null, replacement: | { type: "root"; children: unknown[] } | { type: "paragraph"; children: unknown[] }, parentLocation?: Pick<IncludeCandidate, "parentContainer" | "parentIndex"> ) { + if (replacement.type === "root" && parent && !isParagraph(parent)) { + const container = parent.children as Record<string, unknown>[] | undefined; + const index = container?.indexOf(node) ?? -1; + if (index >= 0) { + container.splice( + index, + 1, + ...(replacement.children as Record<string, unknown>[]) + ); + return; + } + } + // If the include lives inside a paragraph but the replacement is a root // (multiple top-level nodes), splice the replacement children into the // grandparent's children in place of the whole paragraph. Previously we // mutated the paragraph into `{ type: "root" }`, producing invalid mdast. if (parent && isParagraph(parent) && replacement.type === "root") {
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/leadtype/src/remark/plugins/include.remark.ts`:
- Around line 32-35: The include caches are currently module-global, so stale
include contents can leak across separate conversions. Move includeContentCache,
includePathCache, and includeResolutionInflight into the transformer flow in
include.remark.ts so each transformer invocation gets its own cache state
instead of reusing it across the module lifetime. Update the logic around the
include plugin’s main transform entrypoint to create and pass these maps per
run.
- Around line 463-477: The include file cache in readIncludeFileCached should
not use readFileSync, since that turns the awaited resolveIncludeInflight path
into blocking I/O and kills concurrency. Update the include loading flow in
include.remark.ts to cache and return the async readFile() result instead,
keeping the existing includeContentCache and profiling behavior intact while
preserving parallel reads under Promise.all.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 8b315ec6-3f5f-4dee-a187-d4c87084efa3
📒 Files selected for processing (5)
packages/leadtype/src/internal/markdown-profile.tspackages/leadtype/src/markdown/component-dispatcher.tspackages/leadtype/src/markdown/default-transforms.tspackages/leadtype/src/markdown/transform.tspackages/leadtype/src/remark/plugins/include.remark.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
Files:
packages/leadtype/src/internal/markdown-profile.tspackages/leadtype/src/markdown/component-dispatcher.tspackages/leadtype/src/markdown/default-transforms.tspackages/leadtype/src/markdown/transform.tspackages/leadtype/src/remark/plugins/include.remark.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Don't useeval()or assign directly todocument.cookie
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Use descriptive names for functions, variables, and types for meaningful naming
Add comments for complex logic, but prefer self-documenting code
Files:
packages/leadtype/src/internal/markdown-profile.tspackages/leadtype/src/markdown/component-dispatcher.tspackages/leadtype/src/markdown/default-transforms.tspackages/leadtype/src/markdown/transform.tspackages/leadtype/src/remark/plugins/include.remark.ts
🔇 Additional comments (4)
packages/leadtype/src/markdown/transform.ts (1)
53-64: Still bypassing unified's fullPluggablecontract.
toTransform()still drops preset objects and invokes attachers directly, so presets and plugins that rely onprocessor.use(...)semantics remain unsupported here.In unified 11, does `Pluggable` include preset objects, and are plugin attachers executed with the processor instance bound as `this` when registered through `processor.use()`?packages/leadtype/src/internal/markdown-profile.ts (1)
1-49: LGTM!packages/leadtype/src/markdown/component-dispatcher.ts (1)
1-8: LGTM!Also applies to: 173-181
packages/leadtype/src/markdown/default-transforms.ts (1)
36-36: LGTM!
| const includeContentCache = new Map<string, string>(); | ||
| const includePathCache = new Map<string, string>(); | ||
| const includeResolutionInflight = new Map<string, Promise<IncludeResolution>>(); | ||
|
|
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Scope these include caches to a single transform run.
These maps live for the lifetime of the module, so a later conversion in the same process can reuse stale include contents after the source file changes. Keep the cache per transformer invocation instead of module-global.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/leadtype/src/remark/plugins/include.remark.ts` around lines 32 - 35,
The include caches are currently module-global, so stale include contents can
leak across separate conversions. Move includeContentCache, includePathCache,
and includeResolutionInflight into the transformer flow in include.remark.ts so
each transformer invocation gets its own cache state instead of reusing it
across the module lifetime. Update the logic around the include plugin’s main
transform entrypoint to create and pass these maps per run.
| function readIncludeFileCached(resolvedPath: string): string { | ||
| const cached = includeContentCache.get(resolvedPath); | ||
| if (cached) { | ||
| return cached; | ||
| } | ||
|
|
||
| const profileEnabled = isMarkdownProfileEnabled(); | ||
| const readStartedAt = profileEnabled ? performance.now() : 0; | ||
| const raw = readFileSync(resolvedPath, "utf8"); | ||
| if (profileEnabled) { | ||
| recordMarkdownProfile("include:read", performance.now() - readStartedAt); | ||
| } | ||
| includeContentCache.set(resolvedPath, raw); | ||
| return raw; | ||
| } |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win
Don't switch the async include path back to synchronous I/O.
resolveIncludeInflight() is awaited under Promise.all(...), but readFileSync() serializes those reads on the event loop and erases most of the concurrency benefit. Cache the async readFile() result instead.
Proposed fix
-const includeContentCache = new Map<string, string>();
+const includeContentCache = new Map<string, Promise<string>>();-function readIncludeFileCached(resolvedPath: string): string {
+async function readIncludeFileCached(resolvedPath: string): Promise<string> {
const cached = includeContentCache.get(resolvedPath);
if (cached) {
- return cached;
+ return await cached;
}
const profileEnabled = isMarkdownProfileEnabled();
const readStartedAt = profileEnabled ? performance.now() : 0;
- const raw = readFileSync(resolvedPath, "utf8");
- if (profileEnabled) {
- recordMarkdownProfile("include:read", performance.now() - readStartedAt);
- }
- includeContentCache.set(resolvedPath, raw);
- return raw;
+ const pending = readFile(resolvedPath, "utf8");
+ includeContentCache.set(resolvedPath, pending);
+ try {
+ const raw = await pending;
+ if (profileEnabled) {
+ recordMarkdownProfile("include:read", performance.now() - readStartedAt);
+ }
+ return raw;
+ } catch (error) {
+ includeContentCache.delete(resolvedPath);
+ throw error;
+ }
}- const raw = readIncludeFileCached(resolvedPath);
+ const raw = await readIncludeFileCached(resolvedPath);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| function readIncludeFileCached(resolvedPath: string): string { | |
| const cached = includeContentCache.get(resolvedPath); | |
| if (cached) { | |
| return cached; | |
| } | |
| const profileEnabled = isMarkdownProfileEnabled(); | |
| const readStartedAt = profileEnabled ? performance.now() : 0; | |
| const raw = readFileSync(resolvedPath, "utf8"); | |
| if (profileEnabled) { | |
| recordMarkdownProfile("include:read", performance.now() - readStartedAt); | |
| } | |
| includeContentCache.set(resolvedPath, raw); | |
| return raw; | |
| } | |
| async function readIncludeFileCached(resolvedPath: string): Promise<string> { | |
| const cached = includeContentCache.get(resolvedPath); | |
| if (cached) { | |
| return await cached; | |
| } | |
| const profileEnabled = isMarkdownProfileEnabled(); | |
| const readStartedAt = profileEnabled ? performance.now() : 0; | |
| const pending = readFile(resolvedPath, "utf8"); | |
| includeContentCache.set(resolvedPath, pending); | |
| try { | |
| const raw = await pending; | |
| if (profileEnabled) { | |
| recordMarkdownProfile("include:read", performance.now() - readStartedAt); | |
| } | |
| return raw; | |
| } catch (error) { | |
| includeContentCache.delete(resolvedPath); | |
| throw error; | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/leadtype/src/remark/plugins/include.remark.ts` around lines 463 -
477, The include file cache in readIncludeFileCached should not use
readFileSync, since that turns the awaited resolveIncludeInflight path into
blocking I/O and kills concurrency. Update the include loading flow in
include.remark.ts to cache and return the async readFile() result instead,
keeping the existing includeContentCache and profiling behavior intact while
preserving parallel reads under Promise.all.
- Updated various scripts and documentation to replace `leadtype/remark` with `leadtype/markdown`, aligning with the new markdown transforms architecture. - Adjusted conversion functions to utilize `markdownTransforms` instead of `remarkPlugins`. - Improved documentation to reflect the transition and clarify usage of markdown transforms. This change aims to standardize the markdown processing approach across the codebase.
There was a problem hiding this comment.
ℹ️ No new issues — the changes since the last review are correctness-focused and sound. One minor duplication nit inline.
Reviewed changes — incremental review of commit 99fafd3 ("Refactor markdown processing to enhance clarity and maintainability"), a contained pass over the leadtype/markdown subsystem plus docs wording, landing several correctness fixes on top of the dispatcher refactor.
- Native stringifier hardening —
stringify.tsinlineCodenow picks the backtick delimiter from the longest internal backtick run and preserves internal whitespace (CommonMark-correct);mdxJsxTextElementnow serializes inline (<Badge ...>New</Badge>) rather than as a block. Covered by newstringify.test.tscases. - Transform scheduling —
transform.tsaddsexpandPluggablesto flatten nested unified preset ({ plugins: [...] }) entries, and invokes plugin factories aspluginFactory.call(unified(), …)so plugins that readthisget a processor. - File-path propagation —
define-flattener.tsthreads the sourcefilePathintoflattenChildrenand the sub-transform context (was hardcoded""), fixing nested base-path resolution. - String-literal expression props —
callout.tsandcommand-tabs.tsunwrap quoted expression attributes (variant={"warning"},command={"leadtype"}); new test asserts the cleaned output. - Visitor + cache fixes —
cards.tsreturns[SKIP, index]after splicing;type-table.tsswitches its TS-program and extracted-type caches to mtime-keyed invalidation. - Details parity aligned — legacy
remarkDetailsToMarkdownnow matches["Details", "details"](with a new test), aligning it with the dispatcher and resolving the prior parity thread. - Misc —
AutoTypeTableadded to the legacy flattener-name/lint contract;generate-docs.tsprependsincludeMarkdown;bench.tsuses an explicit=== undefinedcheck.
ℹ️ Nitpicks
unwrapStringLiteralExpressionis now duplicated verbatim inpackages/leadtype/src/markdown/plugins/callout.ts,command-tabs.ts, andaudience.ts. Themarkdown/libsmodule already hosts shared helpers (getAttributeValue,extractNodeText); hoisting it there would keep the three call sites in sync.
Claude Opus | 𝕏
| } | ||
| } | ||
|
|
||
| function unwrapStringLiteralExpression(value: string): string { |
There was a problem hiding this comment.
ℹ️ Nit: unwrapStringLiteralExpression is duplicated verbatim here, in command-tabs.ts, and in audience.ts. Since markdown/libs already holds the shared attribute helpers, hoisting this one there would avoid three copies drifting apart.
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
packages/leadtype/src/markdown/plugins/type-table.ts (1)
131-155: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy liftInvalidate the TypeScript cache when imported files change.
getTypeScriptProgramForFile()andextractTypeFromFile()only key off the entry file’smtimeMs, so edits in transitive imports can keep reusing a stalets.Programand stale extracted table in long-lived processes. Scope this cache to a single conversion pass or include dependency changes in the invalidation key.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/leadtype/src/markdown/plugins/type-table.ts` around lines 131 - 155, The TypeScript program cache in getTypeScriptProgramForFile() is only invalidated by the root file’s mtimeMs, so changes in imported dependencies can leave extractTypeFromFile() using a stale ts.Program. Update the caching strategy in getTypeScriptProgramForFile() / __tsProgramByRootFile so it is scoped to a single conversion pass or also invalidates when transitive imports change, ensuring extracted table types are recomputed after dependency edits.docs/reference/remark.mdx (1)
18-60: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winThe documented
defaultMarkdownTransformsstack is stale.This chart and ordered list still describe the legacy per-plugin flattener chain, but the new default export is the resolve-phase plugins followed by
nativeMarkdownComponentsToMarkdown. As written, readers will think the deprecated individual remark flatteners still run by default. Please either rewrite this section around the native dispatcher or relabel the detailed order aslegacyDefaultMarkdownTransforms.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/reference/remark.mdx` around lines 18 - 60, The `defaultMarkdownTransforms` documentation is outdated and still describes the old per-plugin flattening pipeline. Update the `remark.mdx` section to match the current default export behavior by documenting the resolve-phase plugins followed by `nativeMarkdownComponentsToMarkdown`, or rename the detailed sequence to `legacyDefaultMarkdownTransforms` so it is not presented as the default. Use the `defaultMarkdownTransforms` heading and the Mermaid flowchart/list in this file to locate and revise the stale ordering.apps/tanstack/scripts/bench.ts (1)
268-271: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winGuard zero-median stages before formatting a speedup.
These medians are rounded to milliseconds, so either side can be
0.remark / satterithen rendersInfinityxorNaNx, which makes the benchmark summary misleading for fast stages. Skip those rows or rendern/awhen the denominator is zero.Suggested fix
const speedup = remark / satteri; const delta = remark - satteri; + const speedupLabel = + satteri === 0 ? "n/a" : `${speedup.toFixed(2)}x`; lines.push( - `- \`${stage}\`: ${speedup.toFixed(2)}x (${delta >= 0 ? "-" : "+"}${Math.abs(delta)}ms)` + `- \`${stage}\`: ${speedupLabel} (${delta >= 0 ? "-" : "+"}${Math.abs(delta)}ms)` );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/tanstack/scripts/bench.ts` around lines 268 - 271, The benchmark summary formatting in the stage result block can emit Infinityx or NaNx when either median rounds to 0, so update the logic around the speedup calculation to guard zero-denominator cases before formatting. In the code that computes `speedup` and appends to `lines`, add a zero check for `satteri` (and any other zero median inputs) and either skip that row or render a safe `n/a` value instead of dividing and calling `toFixed` on an invalid result.
♻️ Duplicate comments (2)
packages/leadtype/src/markdown/stringify.ts (1)
95-108: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winPreserve spaces around inline-code line breaks.
/\s*\n\s*/gstill eats the spaces on both sides of a newline, so a value likea \n bserializes asa b. That means the native stringifier still cannot round-trip inline code with significant spacing across line breaks.Suggested fix
- const content = value.replace(/\s*\n\s*/g, " "); + const content = value.replace(/\r?\n|\r/g, " ");🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/leadtype/src/markdown/stringify.ts` around lines 95 - 108, The inlineCode helper in stringify.ts is collapsing spaces around line breaks because the current newline replacement removes surrounding whitespace, so significant spacing inside inline code is lost. Update inlineCode to preserve spaces adjacent to newlines when normalizing content, while still converting the line break itself into a single separator, and keep the existing delimiter/padding logic unchanged. Use the inlineCode function and its content normalization step as the place to fix the round-trip behavior for inline code with spaces across line breaks.packages/leadtype/src/markdown/transform.ts (1)
39-62: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
PluggableListentries still lose preset settings and shared processor state.
expandPluggables()strips presets down to.plugins, andtoTransform()attaches each entry against a freshunified()instance. That still diverges fromprocessor.use(pluggables): presetsettingsnever flow through, and attachers cannot observe processor data established by earlier plugins. If this API is meant to keep accepting genericPluggableList, it needs a single configured processor for the whole list or a narrower input type.#!/bin/bash set -euo pipefail printf '== attachers using processor state ==\n' rg -n -C2 '\bthis\.(data|use|parser|compiler)\b' packages/leadtype/src printf '\n== preset settings in repo ==\n' rg -n -C2 '\bsettings\s*:' packages/leadtype/src apps docsAlso applies to: 87-110
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/leadtype/src/markdown/transform.ts` around lines 39 - 62, `expandPluggables()` and `toTransform()` still break `PluggableList` semantics by flattening presets to only `.plugins` and then applying each item to a fresh `unified()` instance. Update the transform flow so the whole list is applied on a single configured processor, preserving preset `settings` and shared processor state across `use()` calls, or otherwise narrow the accepted input type to exclude preset behavior. Use `expandPluggables`, `isPluggablePreset`, and `toTransform` as the key symbols when refactoring.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@apps/tanstack/scripts/bench.ts`:
- Around line 268-271: The benchmark summary formatting in the stage result
block can emit Infinityx or NaNx when either median rounds to 0, so update the
logic around the speedup calculation to guard zero-denominator cases before
formatting. In the code that computes `speedup` and appends to `lines`, add a
zero check for `satteri` (and any other zero median inputs) and either skip that
row or render a safe `n/a` value instead of dividing and calling `toFixed` on an
invalid result.
In `@docs/reference/remark.mdx`:
- Around line 18-60: The `defaultMarkdownTransforms` documentation is outdated
and still describes the old per-plugin flattening pipeline. Update the
`remark.mdx` section to match the current default export behavior by documenting
the resolve-phase plugins followed by `nativeMarkdownComponentsToMarkdown`, or
rename the detailed sequence to `legacyDefaultMarkdownTransforms` so it is not
presented as the default. Use the `defaultMarkdownTransforms` heading and the
Mermaid flowchart/list in this file to locate and revise the stale ordering.
In `@packages/leadtype/src/markdown/plugins/type-table.ts`:
- Around line 131-155: The TypeScript program cache in
getTypeScriptProgramForFile() is only invalidated by the root file’s mtimeMs, so
changes in imported dependencies can leave extractTypeFromFile() using a stale
ts.Program. Update the caching strategy in getTypeScriptProgramForFile() /
__tsProgramByRootFile so it is scoped to a single conversion pass or also
invalidates when transitive imports change, ensuring extracted table types are
recomputed after dependency edits.
---
Duplicate comments:
In `@packages/leadtype/src/markdown/stringify.ts`:
- Around line 95-108: The inlineCode helper in stringify.ts is collapsing spaces
around line breaks because the current newline replacement removes surrounding
whitespace, so significant spacing inside inline code is lost. Update inlineCode
to preserve spaces adjacent to newlines when normalizing content, while still
converting the line break itself into a single separator, and keep the existing
delimiter/padding logic unchanged. Use the inlineCode function and its content
normalization step as the place to fix the round-trip behavior for inline code
with spaces across line breaks.
In `@packages/leadtype/src/markdown/transform.ts`:
- Around line 39-62: `expandPluggables()` and `toTransform()` still break
`PluggableList` semantics by flattening presets to only `.plugins` and then
applying each item to a fresh `unified()` instance. Update the transform flow so
the whole list is applied on a single configured processor, preserving preset
`settings` and shared processor state across `use()` calls, or otherwise narrow
the accepted input type to exclude preset behavior. Use `expandPluggables`,
`isPluggablePreset`, and `toTransform` as the key symbols when refactoring.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 2102a012-6ee6-4ce4-ba86-645a124bd47e
📒 Files selected for processing (22)
apps/tanstack/scripts/bench.tsapps/tanstack/src/routes/playground.tsxdocs/concepts/architecture.mdxdocs/package-docs/bundle.mdxdocs/reference/convert.mdxdocs/reference/nlweb.mdxdocs/reference/remark.mdxdocs/search/agent-tools.mdxdocs/search/ai-answers.mdxdocs/writing/components.mdxpackages/leadtype/src/convert/engine-parity.test.tspackages/leadtype/src/markdown/default-transforms.tspackages/leadtype/src/markdown/define-flattener.tspackages/leadtype/src/markdown/markdown-output.test.tspackages/leadtype/src/markdown/plugins/callout.tspackages/leadtype/src/markdown/plugins/cards.tspackages/leadtype/src/markdown/plugins/command-tabs.tspackages/leadtype/src/markdown/plugins/details.tspackages/leadtype/src/markdown/plugins/type-table.tspackages/leadtype/src/markdown/stringify.test.tspackages/leadtype/src/markdown/stringify.tspackages/leadtype/src/markdown/transform.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
Files:
apps/tanstack/src/routes/playground.tsxpackages/leadtype/src/markdown/plugins/details.tspackages/leadtype/src/markdown/stringify.test.tspackages/leadtype/src/markdown/define-flattener.tspackages/leadtype/src/markdown/plugins/callout.tspackages/leadtype/src/markdown/plugins/command-tabs.tspackages/leadtype/src/markdown/plugins/cards.tspackages/leadtype/src/markdown/default-transforms.tspackages/leadtype/src/markdown/transform.tsapps/tanstack/scripts/bench.tspackages/leadtype/src/markdown/plugins/type-table.tspackages/leadtype/src/convert/engine-parity.test.tspackages/leadtype/src/markdown/stringify.tspackages/leadtype/src/markdown/markdown-output.test.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Don't useeval()or assign directly todocument.cookie
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Use descriptive names for functions, variables, and types for meaningful naming
Add comments for complex logic, but prefer self-documenting code
Files:
apps/tanstack/src/routes/playground.tsxpackages/leadtype/src/markdown/plugins/details.tspackages/leadtype/src/markdown/stringify.test.tspackages/leadtype/src/markdown/define-flattener.tspackages/leadtype/src/markdown/plugins/callout.tspackages/leadtype/src/markdown/plugins/command-tabs.tspackages/leadtype/src/markdown/plugins/cards.tspackages/leadtype/src/markdown/default-transforms.tspackages/leadtype/src/markdown/transform.tsapps/tanstack/scripts/bench.tspackages/leadtype/src/markdown/plugins/type-table.tspackages/leadtype/src/convert/engine-parity.test.tspackages/leadtype/src/markdown/stringify.tspackages/leadtype/src/markdown/markdown-output.test.ts
**/*.{jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{jsx,tsx}: Use function components over class components in React
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use thekeyprop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props
Don't define components inside other components
AvoiddangerouslySetInnerHTMLunless absolutely necessary
Use proper image components (e.g., Next.js<Image>) over<img>tags
Use Next.js<Image>component for images
Usenext/heador App Router metadata API for head elements in Next.js
Use Server Components for async data fetching instead of async Client Components in Next.js
Use ref as a prop instead ofReact.forwardRefin React 19+
Files:
apps/tanstack/src/routes/playground.tsx
**/*.{jsx,tsx,html}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{jsx,tsx,html}: Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, use semantic elements instead of divs with roles
Addrel="noopener"when usingtarget="_blank"on links
Files:
apps/tanstack/src/routes/playground.tsx
**/*.{test,spec}.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{test,spec}.{js,ts,jsx,tsx}: Write assertions insideit()ortest()blocks
Avoid done callbacks in async tests - use async/await instead
Don't use.onlyor.skipin committed code
Keep test suites reasonably flat - avoid excessivedescribenesting
Files:
packages/leadtype/src/markdown/stringify.test.tspackages/leadtype/src/convert/engine-parity.test.tspackages/leadtype/src/markdown/markdown-output.test.ts
🪛 LanguageTool
docs/reference/remark.mdx
[uncategorized] ~52-~52: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...mandTabsToMarkdown—→ markdown table. 11.remarkStepsToMarkdown—<...
(MARKDOWN_NNP)
[uncategorized] ~55-~55: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...ypeTable>and→ markdown table. 14.remarkAccordionToMarkdown` ...
(MARKDOWN_NNP)
🔇 Additional comments (12)
docs/concepts/architecture.mdx (1)
16-16: LGTM!docs/reference/convert.mdx (1)
64-67: LGTM!Also applies to: 77-87
packages/leadtype/src/markdown/plugins/callout.ts (1)
61-74: LGTM!Also applies to: 120-166
packages/leadtype/src/markdown/plugins/command-tabs.ts (1)
53-66: LGTM!Also applies to: 76-103
packages/leadtype/src/markdown/plugins/cards.ts (1)
11-11: LGTM!Also applies to: 133-139
packages/leadtype/src/markdown/default-transforms.ts (1)
97-121: LGTM!packages/leadtype/src/markdown/stringify.test.ts (1)
74-96: LGTM!Also applies to: 98-123
docs/package-docs/bundle.mdx (1)
107-107: LGTM!Also applies to: 119-119
docs/writing/components.mdx (1)
15-18: LGTM!Also applies to: 21-23, 33-38, 322-322
packages/leadtype/src/markdown/define-flattener.ts (1)
102-118: LGTM!Also applies to: 206-214
packages/leadtype/src/convert/engine-parity.test.ts (1)
22-22: LGTM!Also applies to: 86-94
packages/leadtype/src/markdown/stringify.ts (1)
382-390: LGTM!
- Updated the `renderSpeedups` function to handle division by zero gracefully, displaying "n/a" when applicable. - Refactored markdown transformation functions to improve clarity and maintainability, including the introduction of a legacy preset for compatibility. - Added new utility functions for managing TypeScript program dependencies, enhancing caching mechanisms for improved performance. These changes aim to standardize markdown processing and improve the overall performance and reliability of the leadtype package.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/leadtype/src/markdown/plugins/type-table.ts`:
- Around line 144-160: The freshness check in collectProgramDependencies is
skipping all declaration files, which can leave extracted tables stale when
project .d.ts dependencies change. Update this helper so project-owned
declaration files are included in the dependency list while still excluding
default lib declarations, and ensure the collected entries continue to feed the
__tsProgramByRootFile and __extractedTypeByKey invalidation logic.
In `@packages/leadtype/src/markdown/transform.ts`:
- Around line 116-137: The flattening logic in addTransforms() is reordering
sibling plugins because it recurses into preset.plugins immediately instead of
preserving the original list order. Update addTransforms() in transform.ts so it
first expands presets while keeping siblings in sequence, then runs
sortRemarkPluginsByPhase() once over the fully collected leaf plugins before
calling toTransform(). Use addTransforms(), isPluggablePreset(), and
sortRemarkPluginsByPhase() as the key points to adjust.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: d9d9e954-a00c-432f-b1e4-9e3ca7301ead
📒 Files selected for processing (6)
apps/tanstack/scripts/bench.tsdocs/reference/remark.mdxpackages/leadtype/src/markdown/plugins/type-table.tspackages/leadtype/src/markdown/stringify.test.tspackages/leadtype/src/markdown/stringify.tspackages/leadtype/src/markdown/transform.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Preferunknownoveranywhen the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions
Files:
packages/leadtype/src/markdown/stringify.test.tspackages/leadtype/src/markdown/stringify.tspackages/leadtype/src/markdown/transform.tsapps/tanstack/scripts/bench.tspackages/leadtype/src/markdown/plugins/type-table.ts
**/*.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Preferfor...ofloops over.forEach()and indexedforloops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Useconstby default,letonly when reassignment is needed, nevervar
Alwaysawaitpromises in async functions - don't forget to use the return value
Useasync/awaitsyntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Removeconsole.log,debugger, andalertstatements from production code
ThrowErrorobjects with descriptive messages, not strings or other values
Usetry-catchblocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Don't useeval()or assign directly todocument.cookie
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Use descriptive names for functions, variables, and types for meaningful naming
Add comments for complex logic, but prefer self-documenting code
Files:
packages/leadtype/src/markdown/stringify.test.tspackages/leadtype/src/markdown/stringify.tspackages/leadtype/src/markdown/transform.tsapps/tanstack/scripts/bench.tspackages/leadtype/src/markdown/plugins/type-table.ts
**/*.{test,spec}.{js,ts,jsx,tsx}
📄 CodeRabbit inference engine (AGENTS.md)
**/*.{test,spec}.{js,ts,jsx,tsx}: Write assertions insideit()ortest()blocks
Avoid done callbacks in async tests - use async/await instead
Don't use.onlyor.skipin committed code
Keep test suites reasonably flat - avoid excessivedescribenesting
Files:
packages/leadtype/src/markdown/stringify.test.ts
🔇 Additional comments (5)
packages/leadtype/src/markdown/stringify.test.ts (1)
5-31: LGTM!Also applies to: 33-72, 74-99, 100-125
docs/reference/remark.mdx (1)
12-35: LGTM!Also applies to: 107-185
packages/leadtype/src/markdown/stringify.ts (1)
95-109: LGTM!packages/leadtype/src/markdown/transform.ts (1)
3-108: LGTM!apps/tanstack/scripts/bench.ts (1)
248-274: LGTM!
| function collectProgramDependencies( | ||
| program: ts.Program | ||
| ): Array<{ filePath: string; mtimeMs: number }> { | ||
| const dependencies: Array<{ filePath: string; mtimeMs: number }> = []; | ||
| for (const sourceFile of program.getSourceFiles()) { | ||
| if (sourceFile.isDeclarationFile) { | ||
| continue; | ||
| } | ||
| try { | ||
| dependencies.push({ | ||
| filePath: sourceFile.fileName, | ||
| mtimeMs: statSync(sourceFile.fileName).mtimeMs, | ||
| }); | ||
| } catch { | ||
| // Virtual files cannot be used to validate freshness. | ||
| } | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
TypeScript Program isSourceFileDefaultLibrary sourceFile declaration dependency API
💡 Result:
In the TypeScript Compiler API, isSourceFileDefaultLibrary is a method available on the Program interface [1][2]. It accepts a SourceFile object and returns a boolean value indicating whether that source file is part of the default library (such as lib.d.ts) [1][2]. This method is useful for tools and plugins that need to distinguish between user-defined source files and the built-in library files provided by TypeScript [1]. Key technical points include: - Usage: It is typically used while iterating over all source files in a program to identify or exclude default library files [1]. - Implementation context: The method is defined within the TypeScript compiler's Program implementation [3][4]. - Limitations: Users have noted that it may return false for files loaded from external packages, even when those files are intended to serve as the default library in specific project configurations [5]. - Behavior and Bugs: As an API, it relies on the internal state of the program. Issues have occasionally been reported where the method returns false negatives due to changes in how program structure is managed or reused [6]. Example usage: for (const sourceFile of program.getSourceFiles) { if (program.isSourceFileDefaultLibrary(sourceFile)) { console.log(Default lib: ${sourceFile.fileName}); } }
Citations:
- 1: https://microsoft-typescript.mintlify.app/api/program
- 2: https://typestrong.org/typedoc-auto-docs/typedoc/interfaces/TypeScript.Program.html
- 3: https://github.com/microsoft/TypeScript/blob/eac073894b172ec719ca7f28b0b94fc6e6e7d4cf/src/compiler/program.ts
- 4: https://github.com/microsoft/TypeScript/blob/main/src/compiler/program.ts
- 5: isSourceFileDefaultLibrary does not return true for files loaded from
@typescript/lib-xxxmicrosoft/TypeScript#52480 - 6: TypeScript 6 API Bug -
isSourceFileDefaultLibraryfalse negative on program structure reuse microsoft/TypeScript#63228
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the relevant file and the cache usage around it.
git ls-files | rg '^packages/leadtype/src/markdown/plugins/type-table\.ts$|^packages/leadtype/src/markdown/plugins/'
echo '--- outline ---'
ast-grep outline packages/leadtype/src/markdown/plugins/type-table.ts --view expanded || true
echo '--- key references ---'
rg -n "__tsProgramByRootFile|__extractedTypeByKey|collectProgramDependencies|isDeclarationFile|isSourceFileDefaultLibrary|mtimeMs|dependencies" packages/leadtype/src/markdown/plugins/type-table.tsRepository: inthhq/leadtype
Length of output: 4958
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Read the surrounding implementation in manageable chunks.
wc -l packages/leadtype/src/markdown/plugins/type-table.ts
sed -n '1,260p' packages/leadtype/src/markdown/plugins/type-table.tsRepository: inthhq/leadtype
Length of output: 7661
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the surrounding implementation in the target file.
sed -n '1,260p' packages/leadtype/src/markdown/plugins/type-table.tsRepository: inthhq/leadtype
Length of output: 7603
Include project .d.ts files in freshness checks. Skipping every declaration file means edits to imported declaration dependencies won’t invalidate __tsProgramByRootFile or __extractedTypeByKey, so extracted tables can stay stale until restart. Keep default libs out, but let project declarations participate.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/leadtype/src/markdown/plugins/type-table.ts` around lines 144 - 160,
The freshness check in collectProgramDependencies is skipping all declaration
files, which can leave extracted tables stale when project .d.ts dependencies
change. Update this helper so project-owned declaration files are included in
the dependency list while still excluding default lib declarations, and ensure
the collected entries continue to feed the __tsProgramByRootFile and
__extractedTypeByKey invalidation logic.
| function addTransforms(entries: PluggableList): void { | ||
| const leafEntries: Pluggable[] = []; | ||
| for (const entry of entries) { | ||
| if (isPluggablePreset(entry)) { | ||
| applyPresetSettings(processor, entry); | ||
| addTransforms(entry.plugins ?? []); | ||
| continue; | ||
| } | ||
| leafEntries.push(entry); | ||
| } | ||
|
|
||
| for (const entry of sortRemarkPluginsByPhase(leafEntries)) { | ||
| const transform = toTransform(entry, processor); | ||
| if (transform) { | ||
| transforms.push(transform); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| addTransforms(plugins); | ||
| return transforms; | ||
| } |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🌐 Web query:
unified PluggableList preset plugin order Processor.use preset plugins settings unified 11
💡 Result:
In the unified ecosystem, the order of plugin application is crucial and strictly maintained [1][2]. When you configure a processor using the processor.use method, plugins, presets, and lists are applied in the exact sequence in which they are called or defined [1][2]. Key points regarding plugin order and presets in unified: 1. Sequential Execution: The order of.use calls determines the execution order of plugins [1][2]. Plugins added earlier run before plugins added later [2][3]. 2. Preset Structure: A Preset is an object that can contain a plugins property (a PluggableList) and/or a settings property [4][5]. The plugins defined within the plugins array of a preset are applied in the order they appear in that array [1][2]. 3. Configuration and Merging: If you pass the same plugin to.use multiple times, unified does not add it twice; instead, it merges the new options with the existing configuration [1][6][7]. 4. Presets and Options: Presets themselves cannot receive options [1]. If you need to configure a specific plugin contained within a preset, you must apply that plugin again after the preset using the desired options [1]. 5. Frozen State: Plugins are not executed when.use is called [6]. They are executed when the processor is "frozen" (i.e., when a processing method like.process or.run is invoked) [6][3]. At this stage, the transformer functions returned by the plugins are collected and executed sequentially in the order they were defined [3]. In summary, unified treats the pipeline as an ordered list where each subsequent call to.use appends new plugins (or updates existing ones) to the end of the existing chain [1][6].
Citations:
- 1: https://unifiedjs.com/learn/guide/using-plugins/
- 2: https://unifiedjs.com/learn/guide/using-unified/
- 3: https://readmex.com/en-US/unifiedjs/unified/page-3.48d4ed9b3-e0d8-4a5b-aa01-90ba6a11e3c1
- 4: https://github.com/unifiedjs/unified/blob/c3ba2cd7fce33c1133b95bc81d9b0a22dba5c409/index.d.ts
- 5: https://github.com/unifiedjs/unified
- 6: https://github.com/unifiedjs/unified/
- 7: https://context7.com/unifiedjs/unified/llms.txt
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the target file and nearby references.
git ls-files 'packages/leadtype/src/markdown/transform.ts'
printf '\n--- outline ---\n'
ast-grep outline packages/leadtype/src/markdown/transform.ts --view expanded
printf '\n--- relevant symbols/searches ---\n'
rg -n "sortRemarkPluginsByPhase|isPluggablePreset|applyPresetSettings|toTransform|PluggableList|plugins\\?\\?" packages/leadtype/src/markdown/transform.ts packages/leadtype/src -g '!**/node_modules/**'
printf '\n--- file excerpt ---\n'
sed -n '1,240p' packages/leadtype/src/markdown/transform.tsRepository: inthhq/leadtype
Length of output: 13862
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n--- remark-phase outline ---\n'
ast-grep outline packages/leadtype/src/internal/remark-phase.ts --view expanded
printf '\n--- remark-phase excerpt ---\n'
sed -n '1,260p' packages/leadtype/src/internal/remark-phase.ts
printf '\n--- usages of createMdastTransforms ---\n'
rg -n "createMdastTransforms\\(" packages/leadtype/src -g '!**/node_modules/**'Repository: inthhq/leadtype
Length of output: 5433
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n--- createMdastTransforms call sites ---\n'
sed -n '880,980p' packages/leadtype/src/convert/convert.ts
printf '\n--- define-flattener excerpt ---\n'
sed -n '1,180p' packages/leadtype/src/markdown/define-flattener.ts
printf '\n--- tests / mentions of preset order ---\n'
rg -n "preset|createMdastTransforms|sortRemarkPluginsByPhase|markdownTransforms" packages/leadtype/src -g '*test.*' -g '*.ts' -g '*.tsx'Repository: inthhq/leadtype
Length of output: 14646
Preserve sibling order when expanding presets. addTransforms() recurses into preset.plugins immediately, so [pluginA, presetB] is flattened as [presetB.plugins..., pluginA]. Expand the whole list first, then phase-sort once.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@packages/leadtype/src/markdown/transform.ts` around lines 116 - 137, The
flattening logic in addTransforms() is reordering sibling plugins because it
recurses into preset.plugins immediately instead of preserving the original list
order. Update addTransforms() in transform.ts so it first expands presets while
keeping siblings in sequence, then runs sortRemarkPluginsByPhase() once over the
fully collected leaf plugins before calling toTransform(). Use addTransforms(),
isPluggablePreset(), and sortRemarkPluginsByPhase() as the key points to adjust.
There was a problem hiding this comment.
ℹ️ No new issues in this commit — the changes are correctness-focused and sound. Not approving only because a previously-flagged CI-breaking smoke assertion remains unaddressed (see below).
Reviewed changes — incremental review of commit 05c7e53 ("Enhance markdown processing and performance metrics"), a contained pass over the leadtype/markdown subsystem, docs, and the benchmark script.
- Inline-code whitespace fix —
stringify.tsinlineCodenow usesLINE_BREAK_REGEX(/\r?\n/g) to convert only line breaks to a single space, preserving surrounding internal whitespace (was collapsing\s*\n\s*to one space). CommonMark-aligned; covered by a newstringify.test.tscase ("a \n b"→`a b`). - Type-table cache invalidation —
type-table.tsswitches the TS-program and extracted-type caches from single-root-mtimekeying to dependency-mtimevalidation (programDependenciesAreFresh/collectProgramDependenciesre-stat every non-declaration program source), so the cache now invalidates when any imported type file changes, not just the root. - Transform scheduling —
transform.tsreplacesexpandPluggableswith a nestedaddTransformsthat threads presetsettingsinto a single sharedunified()processor and invokes all plugin factories against that one processor. - Benchmark + docs —
bench.tsguardssatteri === 0("n/a"instead of dividing);docs/reference/remark.mdxrewrites the pipeline diagram and ordered list to reflect the single native dispatcher and documentslegacyDefaultMarkdownTransforms.
⚠️ E2e smoke test still asserts the old page heading (carried over, not new)
This was flagged at the initial review and remains unaddressed by every commit since. The reference page title is "Markdown transforms", but the smoke test still requires a visible heading named exactly "Remark plugins", so it will fail against the built app. It is the only reason this review is not an approval — nothing in 05c7e53 introduced it or touched the file.
Technical details
# E2e smoke test asserts a heading that no longer exists
## Affected sites
- `apps/tanstack/tests/e2e/smoke.e2e.ts:89` — `page.getByRole("heading", { name: "Remark plugins", exact: true })`. The page h1 now derives from `title: "Markdown transforms"`.
- `docs/reference/remark.mdx:2` — `title: "Markdown transforms"`.
## Required outcome
- The heading assertion matches the rendered title so the smoke test passes.
## Suggested approach
- Update line 89 to `{ name: "Markdown transforms", exact: true }`.Claude Opus | 𝕏

Summary
leadtype/markdownand keepleadtype/remarkas compatibility/oracle surface.Test plan
bun x ultracite fixbun run --filter leadtype check-typesbun run --filter leadtype testbun run --filter leadtype buildbun run --filter tanstack test:engine-parityBENCH_RUNS=5 BENCH_ENGINES=remark,satteri bun run --filter tanstack benchMade with Cursor