Skip to content

feat: scaffold @inth/docs package with convert, llm, lint, components#1

Merged
KayleeWilliams merged 7 commits into
mainfrom
KayleeWilliams/init-docs-package
Apr 18, 2026
Merged

feat: scaffold @inth/docs package with convert, llm, lint, components#1
KayleeWilliams merged 7 commits into
mainfrom
KayleeWilliams/init-docs-package

Conversation

@KayleeWilliams
Copy link
Copy Markdown
Collaborator

Extracts the shared MDX→MD pipeline and React component adapters from the duplicated implementations in ~/dev/c15t/scripts/agent-docs and ~/dev/monorepo/packages/docs into a single publishable workspace package (@inth/docs) with four subpath exports — . (components + default MDX adapter map), /remark (10 plugins + defaultRemarkPlugins + libs), /convert (engine with opt-in enrichFrontmatterFromGit that injects lastModified + lastAuthor from git log), /llm (parameterized llms.txt + topic-file generators), and /lint (valibot schemas + programmatic + CLI + pretty/json/github reporters). Adds apps/docs-smoke as a consumption harness mirroring the monorepo pattern plus test:real which shallow-clones c15t and runs the full pipeline against production content (200 .mdx → 200 .md, lint clean). Wires changesets with a pure-OIDC release workflow (no NPM_TOKEN — requires configuring GitHub Actions as a Trusted Publisher on npm before first publish) and a workspace-deps resolver that correctly skips private packages to preserve local linkage.

Test plan

  • bun install && bun run build at the repo root
  • cd packages/docs && bun run check-types && bun run lint
  • cd apps/docs-smoke && bun run build — inspect public/docs/*.md for clean frontmatter + rendered components
  • cd apps/docs-smoke && bun run test:real — clones c15t, expects 200 .mdx → 200 .md, lint reports ~4 real issues in c15t + ~24 legacy lastModified warnings
  • bun x changeset status runs cleanly

🤖 Generated with Claude Code

Introduce a workspace package that extracts the shared MDX→MD pipeline
and React component adapters from c15t / inth monorepo. Subpath exports:
. (components), /remark (plugins + libs), /convert (engine with opt-in
git enrichment), /llm (llms.txt generators), /lint (valibot schema +
CLI). Adds apps/docs-smoke as a consumption harness with a real-content
test that clones c15t. Wires changesets + OIDC-only release workflow.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 17, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a new docs package (@inth/docs) with MDX components, remark plugins, MDX→Markdown conversion, lint/CLI, and LLM generators; a docs-smoke app and scripts; monorepo tooling (Changesets, Husky, Biome, Turborepo); editor hooks/settings; CI workflows; and shared TypeScript configs and helper scripts.

Changes

Cohort / File(s) Summary
Repo tooling & editor configs
/.changeset/README.md, /.changeset/config.json, /.claude/CLAUDE.md, /.claude/settings.json, /.cursor/hooks.json, /.gitignore, /.husky/pre-commit, /.vscode/settings.json, /.zed/settings.json, .github/workflows/release.yml, .github/workflows/bench.yml, turbo.json
Add Changesets docs/config, AI assistant hooks/settings, Cursor hook, editor configs, gitignore, Husky pre-commit hook, and GitHub workflows for release (OIDC npm publish) and benchmark runs.
Monorepo manifests & scripts
/package.json, /scripts/resolve-workspace-deps.ts, /AGENTS.md, biome.jsonc
Add root workspace package.json (scripts, tooling deps), workspace dep resolver script, developer guidelines, and root Biome config.
Editor / CI hooks
/.husky/pre-commit, /.claude/settings.json, /.cursor/hooks.json
Formatting/test pre-commit and tool post-write hooks that run Ultracite/Biome fixes and tests.
Docs smoke app & automation
apps/docs-smoke/package.json, apps/docs-smoke/tsconfig.json, apps/docs-smoke/content/*, apps/docs-smoke/scripts/{mdx-convert.ts,llm-generate.ts,setup-real-content.ts,test-real.ts,bench.ts}, apps/docs-smoke/.gitignore
Add smoke-site content, build/LLM/bench/test scripts, real-content setup, and app package manifest.
Docs package manifest & build
packages/docs/package.json, packages/docs/tsup.config.ts, packages/docs/biome.jsonc, packages/docs/tsconfig.json
Create @inth/docs package, tsup build config (multiple entries + shebang handling), Biome overrides, and package-ts config.
MDX UI components & barrel
packages/docs/src/components/...
callout.tsx, card.tsx, mdx-components.ts, mermaid.tsx, package-command-tabs.tsx, selector.tsx, steps.tsx, tabs.tsx, type-table.tsx, index.ts
Add multiple React MDX components (Callout, Card(s), Mermaid, Tabs/Steps, Selector, PackageCommandTabs, TypeTable, Cards) and component export barrel.
Remark libs & helpers
packages/docs/src/remark/libs/...
attributes.ts, content-processor.ts, generic-processor.ts, guards.ts, node-creators.ts, text.ts, types.ts, index.ts
Introduce shared mdast/mdx utilities: attribute parsing, content normalization, generic JSX processors, guards, node creators, text extraction, and barrel exports.
Remark plugins & pipeline
packages/docs/src/remark/plugins/*, packages/docs/src/remark/index.ts
Add many remark transformers (remove-imports, include, callout, cards, mermaid, package-command-tabs, steps, tabs, link-icon, toc-extract, type-table with AutoTypeTable TS extraction) and export defaultRemarkPlugins.
MDX → Markdown conversion
packages/docs/src/convert/convert.ts, packages/docs/src/convert/index.ts, packages/docs/src/internal/logger.ts
Implement cached remark processor, concurrency-bounded conversion, frontmatter synthesis/enrichment (git), AST post-processing (mermaid/table compaction), file writing, and logging utilities.
Docs linting & CLI
packages/docs/src/lint/*
schema.ts, runner.ts, reporters.ts, cli.ts, index.ts
Add Valibot frontmatter/changelog/meta schemas, lint runner with unknown-field checks, reporters (pretty/json/github), and a CLI entry.
LLM generation
packages/docs/src/llm/llm.ts, packages/docs/src/llm/index.ts
Add generators to build LLM-friendly summaries and full-context per-topic files from docs; routing, ingestion, and output writers implemented.
TypeScript config package
packages/typescript-config/*
base.json, react-library.json, nextjs.json, package.json
Introduce shared TS config package (@repo/typescript-config) with base/react/next presets.
Workspace helper scripts & build tooling
scripts/resolve-workspace-deps.ts, packages/docs/tsup.config.ts
Add workspace dependency resolver and tsup build config (post-build shebang + chmod).

Sequence Diagram(s)

sequenceDiagram
participant CLI as "CLI / Scripts"
participant Converter as "convertAllMdx"
participant Remark as "remark pipeline (plugins)"
participant FS as "Filesystem"
participant Git as "git (optional enrichment)"
participant LLM as "LLM generators"

CLI->>Converter: start conversion(srcDir, outDir, config)
Converter->>FS: glob .mdx files
Converter->>Remark: parse + run defaultRemarkPlugins
Remark->>Remark: transform JSX/includes/mermaid/tables/etc.
Remark->>FS: write .md outputs to outDir
Converter->>Git: git log -1 per-file (if enrichFrontmatterFromGit)
Converter->>FS: merge/write enriched frontmatter
CLI->>LLM: generate summaries/full files from outDir
LLM->>FS: read converted .md files
LLM->>FS: write llms.txt and per-topic llms-full files
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • consentdotio/c15t-docs#12 — Strong overlap: similar MDX/remark processing infrastructure and plugins.
  • consentdotio/c15t-docs#19 — Overlapping remark plugins and MDX processing components.
  • inthhq/monorepo#413 — Related: adds comparable docs tooling (convert/remark/llm) and repo config changes.

Poem

🐇 I hopped through ASTs and nodes so bright,

I nudged JSX into markdown by night.
Plugins whispered, processors spun,
Files emerged—one by one.
— a rabbit, cheerfully compiling.


Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 40

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.changeset/config.json:
- Line 2: The schema URL currently uses the floating tag "@latest"; update the
"$schema" value in .changeset/config.json to point to a specific changesets
config schema version (e.g., replace
"https://unpkg.com/@changesets/config@latest/schema.json" with a pinned version
like "...@<version>/schema.json") so IDE validation is deterministic—choose the
current compatible version from the `@changesets/config` package (or your
lockfile) and use that exact version string.

In @.github/workflows/release.yml:
- Around line 43-49: The workflow step named "Upgrade npm for provenance
publishing" includes a workaround installing promise-retry for Node.js 22's
broken bundled npm; update that step by adding an inline comment and/or a TODO
pointing to the upstream issue (or PR) that documents the missing promise-retry
problem and stating that this step can be removed once the npm fix is released,
and optionally include the issue URL for future reference so maintainers can
track when to remove the workaround.
- Around line 11-18: The workflow currently sets id-token: write in the
permissions block (OIDC trusted publishing) which will fail for a private repo;
replace the OIDC approach by removing or disabling id-token: write and update
the publish step(s) that assume OIDC (e.g., the Changesets/npm publish job) to
use a repository secret NPM_TOKEN instead: add a secrets.NPM_TOKEN input to the
publish action or set up npm auth in the publish script to use
process.env.NPM_TOKEN, and ensure the NPM_TOKEN secret is added in repo settings
so changesets can authenticate.

In @.husky/pre-commit:
- Around line 17-30: The script currently uses set -e which makes the bun x
ultracite fix failure abort the script before FORMAT_EXIT_CODE is saved, so
disable errexit around the ultracite invocation, run bun x ultracite fix,
capture its exit into FORMAT_EXIT_CODE, then re-enable set -e (if needed) so the
existing conditional that checks FORMAT_EXIT_CODE can be reached; reference the
bun x ultracite fix invocation and the FORMAT_EXIT_CODE variable (and
STAGED_FILES loop if needed) when making the change.

In `@apps/docs-smoke/content/docs/index.mdx`:
- Around line 8-10: Update the Callout content to use the correct package name:
replace the string "`@inth/optin-docs`" with "`@inth/docs`" inside the Callout
component (the Callout variant="info" titled "Welcome") so the displayed package
name matches the introduced package in this PR.

In `@apps/docs-smoke/scripts/setup-real-content.ts`:
- Around line 20-25: The script currently resets/updates the fixture to
origin/HEAD which drifts and causes flaky CI; change the logic around
FIXTURE_DIR/REPO to pin to a specific commit/tag (preferably provided via an
environment variable like FIXTURE_REF or a lockfile) and use that ref for both
clone and update paths: after cloning or fetching ensure you fetch the specific
ref (git fetch origin <REF>) and reset/checkout to that ref (git reset --hard
<REF> or git checkout <REF>) instead of origin/HEAD, updating the code
references around FIXTURE_DIR, REPO, and the git commands to use the chosen REF
and to error if REF is missing or invalid.

In `@apps/docs-smoke/scripts/test-real.ts`:
- Around line 72-78: The script currently always prints "Real-content test
passed." even when lintDocs({ srcDir: SRC_DIR }) returns errors/warnings; change
the control flow in the async block that calls lintDocs (the result variable and
its result.summary) to check result.summary.errors and result.summary.warnings
and fail the process when either is non-zero by throwing an Error or calling
process.exit(1) with a clear message that includes the summary counts; ensure
the success message is only printed when both counts are zero.
- Line 18: Remove any stale output before each run by ensuring the OUT_DIR
constant is cleaned (delete the directory or recursively remove its contents and
recreate it) at the start of the script; update the coverage assertion (the
check that currently allows 90% at the end of the run) to assert exact/100% page
output coverage and surface a non-zero exit code on failure; handle and log
filesystem errors from the clean step and fail fast if cleaning or recreating
OUT_DIR fails (reference OUT_DIR and the final coverage assertion/check in this
script).

In `@packages/docs/biome.jsonc`:
- Around line 6-21: Re-enable the globally disabled rules suspicious.useAwait
and style.useForOf in the packages/docs/biome.jsonc by removing their "off"
entries (or setting them to "error"/"warn") so the linter enforces "always await
promises" and "prefer for...of"; keep global config strict and apply narrow,
local suppressions (inline biome-ignore comments or rule-specific overrides
inside particular files) only where necessary instead of leaving
suspicious.useAwait and style.useForOf disabled project-wide.

In `@packages/docs/src/components/package-command-tabs.tsx`:
- Around line 42-63: The tab buttons (MANAGERS map) are using role="tab" and
role="tablist" without full ARIA tabs behavior; either implement a proper tabs
pattern or remove the ARIA roles. Fix by one of two options: A) implement full
keyboard support in package-command-tabs.tsx — add a tabpanel element associated
with the active tab (e.g., wrap the <pre> with role="tabpanel" and
aria-labelledby), implement roving tabindex for the buttons (manage tabindex per
button using active state), and add keydown handlers on the button group to
handle ArrowLeft/ArrowRight/Home/End to call setActive properly; or B) remove
role="tablist" and role="tab" attributes and any aria-selected/aria-related
props on the buttons so they become a plain accessible button group (keep data
attributes and onClick). Ensure resolved output is linked appropriately
depending on the chosen approach.
- Around line 7-14: The prop contract must match the remark plugin output:
change PackageCommandTabsProps so that the singular command prop is a structured
token (the same shape produced by remarkPackageCommandTabsToMarkdown) instead of
being overloaded as a full {pm} template; update the component rendering logic
in PackageCommandTabs (and any consumers) to accept command as the token object
(e.g., {package: string, mode?: string}) and preserve commands?:
Partial<Record<PackageManager,string>> for pre-rendered strings, then update
remarkPackageCommandTabsToMarkdown to emit that same token shape so both the
React component and the MDX→Markdown pipeline consume identical data (or add a
small adapter inside PackageCommandTabs to normalize the remark output to this
single canonical shape).

In `@packages/docs/src/components/tabs.tsx`:
- Around line 48-73: The tab triggers are setting aria-controls to
`${groupId}-${normalized}` but the panel component never renders an element with
that id or aria-labelledby, and triggers lack keyboard/roving-focus handlers;
update the panel (the Tab component that consumes TabsContext) to render
role="tabpanel", id={`${groupId}-${normalized}`} and
aria-labelledby={`${groupId}-trigger-${normalized}`}, and ensure it only renders
when its normalized value matches activeValue; add keyboard handling on the
trigger buttons (where normalize, setActiveValue, groupId are used) to support
ArrowLeft/ArrowRight/Home/End for roving focus and activation (handle focus
movement and setActiveValue accordingly) so ARIA relations and keyboard
navigation are implemented correctly.

In `@packages/docs/src/components/type-table.tsx`:
- Line 61: The JSX renders the default value using a truthy check which hides
empty-string defaults; change the conditional around property.default in the
<td> so it checks for undefined (e.g., property.default !== undefined) instead
of truthiness, ensuring values like "" are displayed as
<code>{property.default}</code> while undefined still renders the "—" fallback.

In `@packages/docs/src/convert/convert.ts`:
- Line 16: The FRONTMATTER_REGEX currently only matches Unix line endings and
therefore loses existing frontmatter on CRLF files; update FRONTMATTER_REGEX to
accept optional CR before newlines (i.e., use \r?\n for the boundary markers)
and apply the same change to the other regex/occurrences referenced around lines
277-284 so frontmatter detection works for both LF and CRLF; ensure the
replacement pattern matches opening `---`, captures the YAML block, matches the
closing `---`, and captures the rest of the file using \r?\n at each newline
boundary (update the constant FRONTMATTER_REGEX and the corresponding regex
usages).

In `@packages/docs/src/convert/index.ts`:
- Around line 1-7: This barrel file re-exports ConvertResult, convertAllMdx,
convertMdxFile, convertSingleMdxFile, and MdxToMarkdownConfig from "./convert"
and should be removed; delete this index.ts and instead update the package
export mapping to point the subpath directly at the concrete module
("./convert") so consumers still resolve those symbols from the same subpath,
then verify/adjust any internal imports that referenced the barrel to continue
resolving the same exports from the concrete "./convert" module.

In `@packages/docs/src/internal/logger.ts`:
- Line 1: Replace the hardcoded import-time flag const VERBOSE =
process.env.INTH_DOCS_VERBOSE === "1" with a named sentinel constant (e.g.,
VERBOSE_FLAG = "1") and a small helper function (e.g., isDocsVerbose()) that
reads process.env.INTH_DOCS_VERBOSE at call time and returns a boolean; update
any usages of VERBOSE to call isDocsVerbose() so the check is explicit,
descriptive, and reflects runtime/env changes (use the existing VERBOSE symbol
as the target to replace and add the new VERBOSE_FLAG and isDocsVerbose helper).
- Around line 5-13: The logging functions (error, summary, verbose) write raw
messages that may contain newlines, producing unprefixed continuation lines; add
a shared helper (e.g., a prefixedWrite or writeWithPrefix function) used by
error, summary and verbose to split the incoming message by newline, prefix each
line with "[inth-docs]" (and the error label for error), and write each line to
the appropriate stream (stderr for error/verbose, stdout for summary); keep the
existing VERBOSE guard inside verbose so it calls the helper only when VERBOSE
is true and reuse the helper from the same module to normalize multi-line output
consistently.

In `@packages/docs/src/lint/cli.ts`:
- Around line 51-88: The parser accepts the next argv token as a value even when
it's another flag (e.g. "--src --format json"); update each value-taking branch
(the code handling arg, argv, value and assigning args.srcDir,
args.changelogDir, args.format, args.ignore, args.maxWarnings) to validate that
the grabbed value is present and not a flag token (e.g. value === undefined ||
value.startsWith("-")), and throw the existing "--... requires a value" error
when that check fails; keep the existing format whitelist and numeric parse for
"--format" and "--max-warnings" after this additional guard so invalid flag-like
tokens no longer shift parsing.

In `@packages/docs/src/lint/index.ts`:
- Around line 3-28: The index.ts currently re-exports internal modules directly
(githubReporter, jsonReporter, lintDocs, allowedKeys, etc.), creating a barrel;
instead create a dedicated public API module (e.g.,
packages/docs/src/lint/public.ts) that explicitly re-exports only the intended
public symbols from "./reporters", "./runner", and "./schema" (include
githubReporter, jsonReporter, prettyReporter, ReporterFormat, renderReport,
DEFAULT_IGNORE_GLOBS, LintOptions, LintResult, lintDocs, allowedKeys,
DefaultFrontmatter, defaultFrontmatterSchema, etc.), then change this index.ts
to point only at that public module (e.g., export * from "./public") so the
package surface is controlled and internal files are no longer re-exported
directly.

In `@packages/docs/src/lint/reporters.ts`:
- Around line 56-68: The githubReporter function writes raw annotation values;
escape values per GitHub Actions rules before emitting: replace % with %25, \r
with %0D, \n with %0A and also escape ':' as %3A and ',' as %2C for any
annotation field. Update githubReporter to run an escape helper over
violation.file and the computed message (the message variable) prior to building
the `::${command} file=...::...` string; add a small helper like
escapeGitHubAnnotation used by githubReporter to ensure all emitted annotations
are safe.

In `@packages/docs/src/lint/runner.ts`:
- Around line 94-100: The isUnderDir check is comparing absolute file paths to a
raw subdirectory string (e.g., "changelog"), so resolve the configured
changelogDir against the repository/src base before classification; update code
that calls isUnderDir(file, changelogDir) to first compute const
resolvedChangelogDir = changelogDir ? path.resolve(srcDir, changelogDir) :
undefined (use path.resolve and the existing srcDir variable) and pass
resolvedChangelogDir into isUnderDir; apply the same change to the other
locations that perform this comparison in this file (the additional call sites
around the classify/validation logic referenced in the review). Ensure
isUnderDir itself remains unchanged and continues to use path.relative(dir,
file).

In `@packages/docs/src/lint/schema.ts`:
- Around line 3-7: The current SEMVER_PATTERN used by semver (the v.regex
validator) only allows X.Y.Z and rejects valid prerelease/build identifiers;
update SEMVER_PATTERN to a semantic‑versioning regex that permits optional
prerelease and build metadata (e.g. allow an optional "-<prerelease>" and
optional "+<build>" suffix) and adjust the v.regex error message to reflect
examples with prerelease/build (e.g. "Must be a valid semantic version (e.g.
1.2.3, 1.2.3-canary.1, 1.2.3+build.5)"). Ensure the change is applied where
SEMVER_PATTERN and the semver validator (v.pipe(..., v.regex(...))) are defined
so prerelease/build versions are accepted.

In `@packages/docs/src/llm/index.ts`:
- Around line 1-12: The file currently re-exports a whole barrel from "./llm";
replace that barrel export with direct exports from the concrete module that
actually implements the symbols (the module that defines generateLLMFullFiles
and generateLLMSummaries and the CuratedLink/CuratedSection/FullTopic types).
Change the single line "export ... from './llm'" to explicit re-exports from the
implementing module path that contains generateLLMFullFiles/generateLLMSummaries
(so CuratedLink, CuratedSection, FullTopic, LLMFullConfig, LLMSummariesConfig,
MarkdownDoc, ProductInfo, SourceDoc, generateLLMFullFiles, generateLLMSummaries
are all exported from that concrete implementation), and ensure package.json
exports point at that concrete module rather than this index barrel.

In `@packages/docs/src/llm/llm.ts`:
- Around line 356-370: renderRootFullRouter currently always emits a link to
/docs/llms.txt (and similarly the docs section in the other root at lines
~427-437), but generateLLMSummaries only creates that file when docsSections is
provided, producing a broken link for configs that skip curated docs; update
renderRootFullRouter (and the analogous renderRoot... at 427-437) to
conditionally include the Documentation Summary link only when docsSections (or
a boolean like hasDocsSections returned by generateLLMSummaries) is true, or
change the link target to a file that is always generated; reference
renderRootFullRouter and generateLLMSummaries to locate and implement the
conditional inclusion so the root router never advertises a file that might not
be written.

In `@packages/docs/src/remark/libs/content-processor.ts`:
- Around line 21-43: processContentNode currently truncates paragraphs and
blockquotes by only using content[0] from
extractParagraphContent/extractBlockquoteContent and converts blockquotes into
plain text; update processContentNode so that for is(node, "paragraph") it
assembles and returns a Paragraph node containing all extracted text fragments
(map all items to children text nodes) instead of only the first fragment, and
for is(node, "blockquote") it returns a Blockquote node preserving structure
(map all extracted fragments through processContentText or appropriate
node-wrapping and set them as the blockquote's children) rather than returning a
single text node; keep the existing is(node, "table") behavior that returns node
as Table.

In `@packages/docs/src/remark/libs/node-creators.ts`:
- Around line 35-37: The function createInlineCode currently types its return as
PhrasingContent but always returns an InlineCode node; change its return type to
InlineCode (instead of PhrasingContent) and update imports/types accordingly so
callers get the narrower type; locate createInlineCode in node-creators.ts and
replace the return type while ensuring InlineCode is imported or referenced from
your mdast type definitions.
- Around line 132-137: Add a brief inline comment in the createListItem function
explaining why casting children from RootContent[] to (BlockContent |
DefinitionContent)[] is safe: note mdast's strict union types, that listItem
children in our usage are guaranteed to be block or definition nodes, and that
this assertion is a pragmatic, intentional workaround to satisfy the TypeScript
typings; place this comment above or next to the cast in createListItem to
document the rationale for future readers and reviewers.

In `@packages/docs/src/remark/libs/text.ts`:
- Around line 72-102: The extractTableContent function currently only serializes
the header and the first data row (using headerRow and dataRow variables) which
truncates tables with 3+ rows; update extractTableContent to iterate over all
body rows (tableRows.slice(1) or similar) and map each TableRow to a
pipe-delimited line using the existing cell->extractNodeText logic, then return
an array containing the header line followed by all data lines joined with
newline characters (preserve trimming logic used in headerText/dataText); ensure
you update any related logic in the same file (the analogous block referenced
around lines 188-221) so multi-row tables are fully serialized rather than
stopped after the first body row.

In `@packages/docs/src/remark/plugins/callout.remark.ts`:
- Around line 31-55: The function variantLabelAndEmoji currently treats "note"
as the default "info"; add an explicit case for "note" inside
variantLabelAndEmoji so it returns a distinct variant (e.g., variant: "note"),
an appropriate emoji (e.g., "📝"), and label "Note:"; update any Variant
type/enum if necessary to include "note" so the new case compiles and is
recognized by downstream code.

In `@packages/docs/src/remark/plugins/include.remark.ts`:
- Around line 16-30: FRONTMATTER_REGEX and parseFrontmatter currently only
handle LF delimiters causing CRLF files to keep YAML; update parseFrontmatter to
either normalize line endings (replace CRLF with LF) before matching or adjust
FRONTMATTER_REGEX to use \r?\n for line boundaries so it matches both LF and
CRLF; ensure you keep the same return shape ({ content: string }) and handle
cases where match is null/undefined (already done) and any unexpected input
(e.g., empty string) robustly within parseFrontmatter.
- Around line 162-172: The replaceTarget function currently mutates a paragraph
node into a root causing invalid nested roots and lost siblings; instead, when
replacement.type === "root" and the include is inside a paragraph (parent is a
paragraph), locate the grandparent (the array holding parent.children), find the
index of parent in grandparent.children, and splice replacement.children into
that grandparent.children replacing the parent node so included children are
inserted alongside existing sibling phrasing; otherwise keep the existing
Object.assign behavior for non-paragraph cases. Also handle edge cases where a
grandparent/children array is missing (fail gracefully or throw a clear error).
Apply the same fix pattern to the analogous code at the other occurrence
mentioned (lines ~439-458).

In `@packages/docs/src/remark/plugins/link-icon.remark.ts`:
- Around line 6-13: The current PROTOCOL_REGEX only matches "http(s)://" causing
protocol-relative URLs like "//www.example.com" to remain unnormalized in
cleanUrlForDisplay; update PROTOCOL_REGEX to also match protocol-relative
prefixes (e.g. make it match optional "http(s):" followed by "//") and ensure
cleanUrlForDisplay uses that updated regex (and still applies WWW_REGEX) so
display text for links handled by cleanUrlForDisplay and any other callers (see
code around the logic referenced at lines ~49-52) consistently strips both
protocol-relative and explicit protocols.

In `@packages/docs/src/remark/plugins/remove-imports.remark.ts`:
- Around line 26-34: The IMPORT_STATEMENT_PATTERN includes a leading '^\s*'
which is redundant because the code already trims each line into the lines
array; update the pattern used by IMPORT_STATEMENT_PATTERN (or replace uses in
the hasOnlyImports check) to remove the leading whitespace anchor so it matches
import statements on trimmed lines, and optionally add a brief comment near the
IMPORT_STATEMENT_PATTERN or the lines mapping to clarify that lines are trimmed
before testing.

In `@packages/docs/src/remark/plugins/steps.remark.ts`:
- Around line 130-139: The code currently uses mdastToString(firstContentNode)
which strips inline markup; instead, when firstContentNode.type === "paragraph"
clone and append its children nodes into titleParagraph (preserving node types
like link, emphasis, inlineCode, mdxTextExpression) rather than converting to
plain text; if a separating space is needed, ensure you insert a single text
node " " before the cloned children, and for any text child run
normalizeWhitespace on its value; update the logic around firstContentNode,
titleParagraph, mdastToString, and normalizeWhitespace to implement this
preservation of inline nodes.

In `@packages/docs/src/remark/plugins/type-table.remark.ts`:
- Around line 419-472: The current extractTypeAliasProperties function only
processes type literals when aliasType.isClassOrInterface() is true, which skips
object-literal aliases (e.g. type Props = { ... }); change the logic inside the
ts.isTypeLiteralNode(typeNode) branch to stop gating on
aliasType.isClassOrInterface() and instead enumerate members via
checker.getPropertiesOfType(checker.getTypeAtLocation(typeAlias.type)); use
those symbols to compute propertyName, propertyType (via
checker.getTypeOfSymbolAtLocation with the appropriate node, e.g. typeAlias.type
or sourceFile), propertyTypeText (resolveTypeName), isOptional detection and
description extraction as before, and return the properties record only if
non-empty.

In `@packages/docs/tsup.config.ts`:
- Around line 21-27: The code uses hardcoded literals for the CLI path and file
mode in the post-build hook; extract those into descriptive constants (e.g.,
const CLI_PATH = "dist/lint/cli.js" and const EXEC_MODE = 0o755) and replace the
inline values referenced in the readFile/writeFile/chmod calls and the
startsWith check (variables cli, contents, readFile, writeFile, chmod) so future
layout or mode changes use named values instead of magic literals.

In `@packages/typescript-config/package.json`:
- Around line 4-8: Remove or annotate the redundant publishConfig in
package.json: the "publishConfig" object with "access": "public" is ineffective
because "private": true prevents publishing; either delete the "publishConfig"
entry or add a clarifying comment next to "publishConfig"/"access" indicating it
is intentional (e.g., a template for future public packages) so readers
understand why both "private" and "publishConfig" exist.

In `@scripts/resolve-workspace-deps.ts`:
- Around line 55-57: The catch block that currently swallows errors when
reading/parsing package.json (the catch in scripts/resolve-workspace-deps.ts
that returns null) should capture the exception (e.g., catch (err)) and write a
warning to stderr with context (include the package.json path or package name
and err.message/stack) using console.warn or console.error before returning null
so malformed/unreadable package.json files are surfaced for debugging.
- Around line 44-46: The empty catch block in scripts/resolve-workspace-deps.ts
that currently just returns [] should not silently swallow errors; change the
catch to capture the error (e.g., catch (err)) and log a debug/warn message
(using the existing logger or console.debug/console.warn) including the error
and context (e.g., when resolving workspace deps in resolveWorkspaceDeps), or
add a clear comment explaining why the error is intentionally ignored if that is
the intent, then still return [].

In `@turbo.json`:
- Around line 5-9: The "build" pipeline's outputs currently only include Next.js
artifacts (".next/**", "!.next/cache/**"), which misses other package build
outputs like dist/ or build/ and prevents proper caching; update the "build"
target's outputs to include common package artifact directories (e.g.,
"dist/**", "build/**") alongside the existing Next.js entries so CI can
restore/cache non-Next packages as well, ensuring downstream tasks find expected
artifacts and improving cache effectiveness for the "build" pipeline.
🪄 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: 4e6b0ea4-3a7c-4eba-8373-4bac0cd23902

📥 Commits

Reviewing files that changed from the base of the PR and between 0fced35 and c69d5d4.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (74)
  • .changeset/README.md
  • .changeset/config.json
  • .claude/CLAUDE.md
  • .claude/settings.json
  • .cursor/hooks.json
  • .github/workflows/release.yml
  • .gitignore
  • .husky/pre-commit
  • .npmrc
  • .vscode/settings.json
  • .zed/settings.json
  • AGENTS.md
  • apps/docs-smoke/.gitignore
  • apps/docs-smoke/content/docs/guides/quickstart.mdx
  • apps/docs-smoke/content/docs/index.mdx
  • apps/docs-smoke/content/docs/meta.json
  • apps/docs-smoke/package.json
  • apps/docs-smoke/scripts/llm-generate.ts
  • apps/docs-smoke/scripts/mdx-convert.ts
  • apps/docs-smoke/scripts/setup-real-content.ts
  • apps/docs-smoke/scripts/test-real.ts
  • apps/docs-smoke/tsconfig.json
  • biome.jsonc
  • package.json
  • packages/docs/biome.jsonc
  • packages/docs/package.json
  • packages/docs/src/components/callout.tsx
  • packages/docs/src/components/card.tsx
  • packages/docs/src/components/index.ts
  • packages/docs/src/components/mdx-components.ts
  • packages/docs/src/components/mermaid.tsx
  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/selector.tsx
  • packages/docs/src/components/steps.tsx
  • packages/docs/src/components/tabs.tsx
  • packages/docs/src/components/type-table.tsx
  • packages/docs/src/convert/convert.ts
  • packages/docs/src/convert/index.ts
  • packages/docs/src/internal/logger.ts
  • packages/docs/src/lint/cli.ts
  • packages/docs/src/lint/index.ts
  • packages/docs/src/lint/reporters.ts
  • packages/docs/src/lint/runner.ts
  • packages/docs/src/lint/schema.ts
  • packages/docs/src/llm/index.ts
  • packages/docs/src/llm/llm.ts
  • packages/docs/src/remark/index.ts
  • packages/docs/src/remark/libs/attributes.ts
  • packages/docs/src/remark/libs/content-processor.ts
  • packages/docs/src/remark/libs/generic-processor.ts
  • packages/docs/src/remark/libs/guards.ts
  • packages/docs/src/remark/libs/index.ts
  • packages/docs/src/remark/libs/node-creators.ts
  • packages/docs/src/remark/libs/text.ts
  • packages/docs/src/remark/libs/types.ts
  • packages/docs/src/remark/plugins/callout.remark.ts
  • packages/docs/src/remark/plugins/cards.remark.ts
  • packages/docs/src/remark/plugins/include.remark.ts
  • packages/docs/src/remark/plugins/link-icon.remark.ts
  • packages/docs/src/remark/plugins/mermaid.remark.ts
  • packages/docs/src/remark/plugins/package-command-tabs.remark.ts
  • packages/docs/src/remark/plugins/remove-imports.remark.ts
  • packages/docs/src/remark/plugins/steps.remark.ts
  • packages/docs/src/remark/plugins/tabs.remark.ts
  • packages/docs/src/remark/plugins/toc-extract.remark.ts
  • packages/docs/src/remark/plugins/type-table.remark.ts
  • packages/docs/tsconfig.json
  • packages/docs/tsup.config.ts
  • packages/typescript-config/base.json
  • packages/typescript-config/nextjs.json
  • packages/typescript-config/package.json
  • packages/typescript-config/react-library.json
  • scripts/resolve-workspace-deps.ts
  • turbo.json

Comment thread .changeset/config.json Outdated
Comment on lines +11 to +18
permissions:
contents: write # create releases/tags + commit version bumps
pull-requests: write # create the "Version Packages" PR
id-token: write # OIDC token — the sole auth mechanism for npm publish
# Prereq: configure GitHub Actions as a Trusted Publisher on each npm
# package ( npmjs.com → Package → Settings → Publishing access ). Once
# configured, `npm publish` uses the OIDC token automatically — no
# NPM_TOKEN secret needed.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if the repository is configured as public or private
gh api repos/{owner}/{repo} --jq '.visibility // .private | if . == true or . == "private" then "private" else "public" end'

Repository: inthhq/docs

Length of output: 62


OIDC trusted publishing cannot be used with a private repository — switch to NPM_TOKEN authentication.

This repository is configured as private, but the workflow uses id-token: write for OIDC-based npm publishing. OIDC trusted publishing only works with public repositories. The workflow will fail when changesets attempts to publish.

Either make the repository public or replace the OIDC approach with a traditional NPM_TOKEN secret stored in repository settings.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/release.yml around lines 11 - 18, The workflow currently
sets id-token: write in the permissions block (OIDC trusted publishing) which
will fail for a private repo; replace the OIDC approach by removing or disabling
id-token: write and update the publish step(s) that assume OIDC (e.g., the
Changesets/npm publish job) to use a repository secret NPM_TOKEN instead: add a
secrets.NPM_TOKEN input to the publish action or set up npm auth in the publish
script to use process.env.NPM_TOKEN, and ensure the NPM_TOKEN secret is added in
repo settings so changesets can authenticate.

Comment thread .github/workflows/release.yml Outdated
Comment thread .husky/pre-commit Outdated
Comment thread apps/docs-smoke/content/docs/index.mdx
Comment on lines +21 to +27
const cli = "dist/lint/cli.js";
const contents = await readFile(cli, "utf8");
if (!contents.startsWith("#!")) {
await writeFile(cli, `#!/usr/bin/env node\n${contents}`);
}
await chmod(cli, 0o755);
},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Extract CLI path and mode literals into named constants.

Line 21 and Line 26 use hardcoded values that are easy to mistype during future entrypoint/layout changes.

♻️ Suggested refactor
+const CLI_OUTPUT_PATH = "dist/lint/cli.js";
+const EXECUTABLE_MODE = 0o755;
+
 export default defineConfig({
   entry: {
@@
   onSuccess: async () => {
     const { chmod, readFile, writeFile } = await import("node:fs/promises");
-    const cli = "dist/lint/cli.js";
+    const cli = CLI_OUTPUT_PATH;
     const contents = await readFile(cli, "utf8");
     if (!contents.startsWith("#!")) {
       await writeFile(cli, `#!/usr/bin/env node\n${contents}`);
     }
-    await chmod(cli, 0o755);
+    await chmod(cli, EXECUTABLE_MODE);
   },

As per coding guidelines, use meaningful variable names instead of magic numbers by extracting descriptive constants.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/tsup.config.ts` around lines 21 - 27, The code uses hardcoded
literals for the CLI path and file mode in the post-build hook; extract those
into descriptive constants (e.g., const CLI_PATH = "dist/lint/cli.js" and const
EXEC_MODE = 0o755) and replace the inline values referenced in the
readFile/writeFile/chmod calls and the startsWith check (variables cli,
contents, readFile, writeFile, chmod) so future layout or mode changes use named
values instead of magic literals.

Comment thread packages/typescript-config/package.json Outdated
Comment on lines +44 to +46
} catch {
return [];
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silent error swallowing may hide issues during debugging.

The empty catch block silently ignores errors (e.g., permission issues, unexpected directory structures). Consider logging a debug message or at least documenting why errors are intentionally ignored here.

Suggested improvement
   } catch {
+    // Directory doesn't exist or isn't readable - expected for missing workspace dirs
     return [];
   }
📝 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.

Suggested change
} catch {
return [];
}
} catch {
// Directory doesn't exist or isn't readable - expected for missing workspace dirs
return [];
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/resolve-workspace-deps.ts` around lines 44 - 46, The empty catch
block in scripts/resolve-workspace-deps.ts that currently just returns [] should
not silently swallow errors; change the catch to capture the error (e.g., catch
(err)) and log a debug/warn message (using the existing logger or
console.debug/console.warn) including the error and context (e.g., when
resolving workspace deps in resolveWorkspaceDeps), or add a clear comment
explaining why the error is intentionally ignored if that is the intent, then
still return [].

Comment on lines +55 to +57
} catch {
return null;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Same concern: silent failure when reading package.json.

If a package.json exists but is malformed or unreadable, silently returning null could mask issues. A warning to stderr would help diagnose problems.

Suggested improvement
-  } catch {
+  } catch (error) {
+    process.stderr.write(`Warning: Could not read ${packageJsonPath}: ${String(error)}\n`);
     return null;
   }
📝 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.

Suggested change
} catch {
return null;
}
} catch (error) {
process.stderr.write(`Warning: Could not read ${packageJsonPath}: ${String(error)}\n`);
return null;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@scripts/resolve-workspace-deps.ts` around lines 55 - 57, The catch block that
currently swallows errors when reading/parsing package.json (the catch in
scripts/resolve-workspace-deps.ts that returns null) should capture the
exception (e.g., catch (err)) and write a warning to stderr with context
(include the package.json path or package name and err.message/stack) using
console.warn or console.error before returning null so malformed/unreadable
package.json files are surfaced for debugging.

Comment thread turbo.json
- convertAllMdx now uses bounded concurrency (default min(cpus, 16))
  via mapLimit instead of a sequential for-loop
- Pre-create output directories upfront so per-file workers skip
  repeated mkdir calls on shared parents
- Drop the redundant access() check — readFile errors on missing file
- readMarkdownDocs in LLM gen swaps for-loop for Promise.all
- Expose `concurrency` on MdxToMarkdownConfig for override
- Document that enrichFrontmatterFromGit requires fetch-depth: 0 in CI

Real-content test against c15t (200 .mdx, git enrichment on):
  before: 4031ms  after: 1452ms  (2.8x)
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

♻️ Duplicate comments (1)
packages/docs/src/llm/llm.ts (1)

356-370: ⚠️ Potential issue | 🟠 Major

Stop linking to /docs/llms.txt when it may not exist.

generateLLMSummaries only writes that file when docsSections is provided, but renderRootFullRouter always advertises it. A valid config with no curated sections will ship llms-full.txt with a guaranteed broken link.

💡 Suggested fix
 export type LLMFullConfig = {
   outDir: string;
   baseUrl?: string;
   product: Pick<ProductInfo, "name">;
   topics: FullTopic[];
+  hasDocsSummary?: boolean;
 };
 
 function renderRootFullRouter(
   product: Pick<ProductInfo, "name">,
-  baseUrl: string
+  baseUrl: string,
+  hasDocsSummary: boolean
 ): string {
-  return [
+  const lines = [
     `# ${product.name} Full Context Router`,
     "",
     "> Start with the product summary, then the curated docs summary, then one topic-specific full-context file if needed.",
     "",
     "## Recommended Flow",
     "",
     `- [Product Summary](${toAbsoluteUrl("/llms.txt", baseUrl)}): Short product-oriented overview of ${product.name}.`,
-    `- [Documentation Summary](${toAbsoluteUrl("/docs/llms.txt", baseUrl)}): Curated docs map for implementation work.`,
     `- [Documentation Full Router](${toAbsoluteUrl("/docs/llms-full.txt", baseUrl)}): Topic-specific deep-context files.`,
-  ].join("\n");
+  ];
+
+  if (hasDocsSummary) {
+    lines.splice(
+      8,
+      0,
+      `- [Documentation Summary](${toAbsoluteUrl("/docs/llms.txt", baseUrl)}): Curated docs map for implementation work.`
+    );
+  }
+
+  return lines.join("\n");
 }
 ...
   await writeFile(
     path.join(outDir, "llms-full.txt"),
-    renderRootFullRouter(config.product, baseUrl)
+    renderRootFullRouter(
+      config.product,
+      baseUrl,
+      config.hasDocsSummary ?? false
+    )
   );

Based on learnings: Focus on business logic correctness, meaningful naming, architecture decisions, edge cases, user experience, and documentation - these are areas where automated linting cannot help.

Also applies to: 427-437, 454-457

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/llm/llm.ts` around lines 356 - 370, renderRootFullRouter
currently always links to /docs/llms.txt even though generateLLMSummaries only
creates that file when docsSections is provided; update renderRootFullRouter to
conditionally include the Product Summary link (the toAbsoluteUrl("/llms.txt",
baseUrl) entry) only when the calling context indicates docsSections exist (or
accept a boolean flag like hasDocsSections) so the generated llms-full.txt won't
advertise a non-existent /llms.txt; locate renderRootFullRouter and the
generateLLMSummaries call sites to add the flag/condition and ensure the same
conditional logic is applied in the other similar renderers mentioned (around
lines referenced: 427-437, 454-457).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/docs/src/convert/convert.ts`:
- Around line 499-500: The summary only logs the number of successful
conversions (const converted = results.filter(Boolean).length) but not how many
failed; update the reporting after results are computed to calculate totalFiles
= results.length and failures = totalFiles - converted and include that in the
summary (e.g., in the log.verbose/log.warn call that follows the existing
log.verbose(`Converted ${converted} MDX files`)) so users see both successes and
failures; if failures > 0, consider logging it at warning level to draw
attention.
- Around line 257-280: The enrichFromGit function currently splits git log
output on "|" which breaks when author names contain "|" — change the git format
to use a NUL separator (e.g., "%aI%x00%an") and parse stdout by
finding/splitting on the NUL character instead of "|"; update the extraction
logic around the execFileAsync call and the parsing of stdout so iso and author
are pulled via the NUL separator (handle missing parts and trim as before) and
keep the same error/empty-return behavior.
- Around line 455-457: The early-return when the source directory is missing
silently hides config errors; update the block that checks existsSync(srcDir) to
log a warning (including the srcDir value) before returning so callers see the
misconfiguration. Use the project's logger if available (e.g., logger.warn or
processLogger.warn), otherwise use console.warn, and keep the existing return
behavior after logging.

In `@packages/docs/src/llm/llm.ts`:
- Around line 339-343: Validate and sanitize topic.slug before using it in URLs
or file paths: in the topics.map that builds titles/descriptions and calls
toAbsoluteUrl, and wherever you use topic.slug with path.join (e.g., the block
around the later path.join usage), reject or canonicalize any value containing
path separators or traversal segments (slashes, backslashes, “..”) and allow
only a safe slug pattern (e.g., alphanumerics, dashes/underscores) or URL-encode
it; ensure you perform this check in the function that processes topics so
toAbsoluteUrl and path.join receive only a validated/sanitized slug and
fail-fast on invalid input.
- Around line 182-214: The code silently overwrites documents when multiple
files normalize to the same URL (e.g., guide.mdx and guide/index.mdx); update
the entries -> for loop that inserts into the docs Map to detect duplicates by
checking docs.has(urlPath) before setting and throw a descriptive error
(including the conflicting urlPath and the offending relativePath values) so the
build fails fast; locate symbols: toUrlPath, entries, docs (Map), urlPath, and
relativePath to implement the check and error.

---

Duplicate comments:
In `@packages/docs/src/llm/llm.ts`:
- Around line 356-370: renderRootFullRouter currently always links to
/docs/llms.txt even though generateLLMSummaries only creates that file when
docsSections is provided; update renderRootFullRouter to conditionally include
the Product Summary link (the toAbsoluteUrl("/llms.txt", baseUrl) entry) only
when the calling context indicates docsSections exist (or accept a boolean flag
like hasDocsSections) so the generated llms-full.txt won't advertise a
non-existent /llms.txt; locate renderRootFullRouter and the generateLLMSummaries
call sites to add the flag/condition and ensure the same conditional logic is
applied in the other similar renderers mentioned (around lines referenced:
427-437, 454-457).
🪄 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: 5bf1714f-09f8-4b3e-a66c-547df7558234

📥 Commits

Reviewing files that changed from the base of the PR and between c69d5d4 and cd02443.

📒 Files selected for processing (2)
  • packages/docs/src/convert/convert.ts
  • packages/docs/src/llm/llm.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use explicit types for function parameters and return values when they enhance clarity

Use meaningful variable names instead of magic numbers - extract constants with descriptive names

Use arrow functions for callbacks and short functions

Prefer for...of loops over .forEach() and indexed for loops

Use optional chaining (?.) and nullish coalescing (??) for safer property access

Prefer template literals over string concatenation

Use destructuring for object and array assignments

Use const by default, let only when reassignment is needed, never var

Always await promises in async functions - don't forget to use the return value

Use async/await syntax 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

Remove console.log, debugger, and alert statements from production code

Throw Error objects with descriptive messages, not strings or other values

Use try-catch blocks 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 use eval() or assign directly to document.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

Files:

  • packages/docs/src/convert/convert.ts
  • packages/docs/src/llm/llm.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer unknown over any when 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/docs/src/convert/convert.ts
  • packages/docs/src/llm/llm.ts
🧠 Learnings (8)
📓 Common learnings
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.mdx : Include code examples where appropriate in MDX documentation pages of the docs app
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.mdx : Keep documentation organized by categories
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.mdx : Use MDX for documentation pages
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.mdx : Use MDX for documentation pages in the docs app
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx,md} : Include TypeScript examples in Elements package documentation
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.mdx : Include code examples where appropriate in documentation
Learnt from: CR
Repo: consentdotio/agent-skills PR: 0
File: tsdoc-jsdoc-authoring/AGENTS.md:0-0
Timestamp: 2026-02-19T01:12:44.177Z
Learning: Applies to tsdoc-jsdoc-authoring/**/index.{ts,tsx} : Use `packageDocumentation` for entrypoint docs in TSDoc comments
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/src/components/**/*.tsx : Place reusable components in `src/components` within the docs app
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Include TSDoc comments for components in Elements package
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.tsx : Use TypeScript for all components (.tsx) in the docs app
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-17T17:12:35.148Z
Learning: Keep functions focused and under reasonable cognitive complexity limits
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-17T17:12:35.148Z
Learning: Group related code together and separate concerns
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-17T17:12:35.148Z
Learning: Validate and sanitize user input
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-17T17:12:35.148Z
Learning: Write code that is accessible, performant, type-safe, and maintainable. Focus on clarity and explicit intent over brevity.
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-17T17:12:35.148Z
Learning: Focus on business logic correctness, meaningful naming, architecture decisions, edge cases, user experience, and documentation - these are areas where automated linting cannot help
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.mdx : Use MDX for documentation pages in the docs app

Applied to files:

  • packages/docs/src/convert/convert.ts
📚 Learning: 2026-03-19T22:57:19.913Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.mdx : Use MDX for documentation pages

Applied to files:

  • packages/docs/src/convert/convert.ts
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.mdx : Include code examples where appropriate in MDX documentation pages of the docs app

Applied to files:

  • packages/docs/src/convert/convert.ts
📚 Learning: 2026-03-19T22:57:19.913Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.mdx : Keep documentation organized by categories

Applied to files:

  • packages/docs/src/convert/convert.ts
📚 Learning: 2026-03-19T22:57:19.913Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.mdx : Include code examples where appropriate in documentation

Applied to files:

  • packages/docs/src/convert/convert.ts
📚 Learning: 2026-02-19T01:12:44.177Z
Learnt from: CR
Repo: consentdotio/agent-skills PR: 0
File: tsdoc-jsdoc-authoring/AGENTS.md:0-0
Timestamp: 2026-02-19T01:12:44.177Z
Learning: Applies to tsdoc-jsdoc-authoring/**/*.{ts,tsx,js,jsx} : Write clear summary sentences in TSDoc and JSDoc comments

Applied to files:

  • packages/docs/src/llm/llm.ts
📚 Learning: 2026-02-19T01:12:44.177Z
Learnt from: CR
Repo: consentdotio/agent-skills PR: 0
File: tsdoc-jsdoc-authoring/AGENTS.md:0-0
Timestamp: 2026-02-19T01:12:44.177Z
Learning: Applies to tsdoc-jsdoc-authoring/**/*.{js,jsx} : Write JSDoc summaries with intent in JavaScript files

Applied to files:

  • packages/docs/src/llm/llm.ts
🔇 Additional comments (8)
packages/docs/src/convert/convert.ts (8)

50-50: CRLF line endings still not handled in frontmatter detection.

The regex ^---\n won't match ---\r\n from Windows-checked-out files, causing existing frontmatter to be missed and replaced with synthesized metadata.

🔧 Suggested fix
-const FRONTMATTER_REGEX = /^---\n([\s\S]*?)\n---\n([\s\S]*)$/;
+const FRONTMATTER_REGEX = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/;

24-48: LGTM!

The bounded-concurrency mapLimit implementation is correct. The shared cursor pattern with worker pool keeps throughput high when processing times vary.


71-105: LGTM!

The processor caching with identity-based comparison is efficient. The any casts are properly justified given unified's type overload limitations.


107-167: LGTM!

The frontmatter synthesis logic correctly handles common markdown structures. The description extraction properly skips headings, blockquotes, tables, and list items to find the first paragraph.


169-223: LGTM!

Table and Mermaid compaction functions correctly handle fence boundaries and preserve table alignment markers.


282-300: LGTM!

The enrichment merge correctly preserves existing frontmatter while adding git metadata.


311-353: LGTM!

The conversion pipeline is well-structured: parse frontmatter → process MDX → compact output → synthesize/enrich frontmatter → reassemble.


376-414: LGTM!

The internal processMdxFile helper correctly handles both stdout streaming and file writing modes with proper error handling.

Comment thread packages/docs/src/convert/convert.ts
Comment on lines +455 to +457
if (!existsSync(srcDir)) {
return;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Silent return when source directory is missing could mask configuration errors.

If srcDir doesn't exist (e.g., typo in config), the function silently returns without any indication. Consider logging a warning.

🔧 Suggested fix
   if (!existsSync(srcDir)) {
+    log.verbose(`Source directory does not exist: ${srcDir}`);
     return;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/convert/convert.ts` around lines 455 - 457, The
early-return when the source directory is missing silently hides config errors;
update the block that checks existsSync(srcDir) to log a warning (including the
srcDir value) before returning so callers see the misconfiguration. Use the
project's logger if available (e.g., logger.warn or processLogger.warn),
otherwise use console.warn, and keep the existing return behavior after logging.

Comment thread packages/docs/src/convert/convert.ts Outdated
Comment thread packages/docs/src/llm/llm.ts
Comment thread packages/docs/src/llm/llm.ts
- apps/docs-smoke/scripts/bench.ts runs convert + LLM gen 3x against
  the c15t clone and reports median/min/max as a markdown table
- .github/workflows/bench.yml runs on PRs touching packages/docs or
  apps/docs-smoke and writes the table to $GITHUB_STEP_SUMMARY
- continue-on-error: true — no threshold gating because GH Actions
  shared runners are too noisy (20-30% variance) for reliable
  fail-on-regression

Local numbers (200 .mdx files, git enrichment on, 3 runs):
  convert      median 1889ms
  llm          median   9ms
  convert+llm  median 1897ms
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/bench.yml:
- Around line 18-24: The CI job "bench" in the workflow should have a timeout to
avoid runaway runner usage; inside the bench job block (the job keyed as bench
with name "Convert + LLM pipeline") add a timeout-minutes property (for example
timeout-minutes: 60) at the same indentation level as runs-on and
continue-on-error so the job will be terminated after the specified minutes.

In `@apps/docs-smoke/scripts/bench.ts`:
- Line 155: The header string in bench.ts currently embeds a hard-coded "200"
file count; replace that magic number by computing the actual fixture count and
interpolating it into the header (e.g., derive a const like fixtureCount from
the fixtures array or directory reader you already use for the benchmark and use
`${fixtureCount}` in the template), updating the `header` definition so it
references the computed value alongside `RUNS` (keep the rest of the message
unchanged).
- Line 17: Validate and sanitize the BENCH_RUNS env var before using it: when
constructing RUNS from process.env.BENCH_RUNS ensure you parse to an integer,
check for NaN or non-positive values (<=0), and coerce to a safe default (e.g.,
3) or clamp to a minimum of 1; update the RUNS constant initialization and any
places that compute averages/ratios (references: RUNS and where averages/min/max
are computed) so you never divide by zero or produce Infinity/-Infinity and
optionally log a warning when BENCH_RUNS is invalid.
🪄 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: 0f7fae82-c76e-4e75-bf86-9c3691a75938

📥 Commits

Reviewing files that changed from the base of the PR and between cd02443 and 2cf181d.

📒 Files selected for processing (3)
  • .github/workflows/bench.yml
  • apps/docs-smoke/package.json
  • apps/docs-smoke/scripts/bench.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use explicit types for function parameters and return values when they enhance clarity

Use meaningful variable names instead of magic numbers - extract constants with descriptive names

Use arrow functions for callbacks and short functions

Prefer for...of loops over .forEach() and indexed for loops

Use optional chaining (?.) and nullish coalescing (??) for safer property access

Prefer template literals over string concatenation

Use destructuring for object and array assignments

Use const by default, let only when reassignment is needed, never var

Always await promises in async functions - don't forget to use the return value

Use async/await syntax 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

Remove console.log, debugger, and alert statements from production code

Throw Error objects with descriptive messages, not strings or other values

Use try-catch blocks meaningfully - don't catch errors just to rethrow them

Prefer early returns over nested conditionals for error cases

Keep functions focused and under reasonable cognitive complexity limits

Extract complex conditions into well-named boolean variables

Use early returns to reduce nesting

Prefer simple conditionals over nested ternary operators

Don't use eval() or assign directly to document.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

Add comments for complex logic, but prefer self-documenting code

Files:

  • apps/docs-smoke/scripts/bench.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer unknown over any when 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/docs-smoke/scripts/bench.ts
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.mdx : Keep documentation organized by categories
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx,md} : Include TypeScript examples in Elements package documentation
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.mdx : Use MDX for documentation pages in the docs app
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.mdx : Use MDX for documentation pages
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.tsx : Use TypeScript for all components (.tsx) in the docs app
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/src/components/**/*.tsx : Place reusable components in `src/components` within the docs app
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.mdx : Include code examples where appropriate in MDX documentation pages of the docs app
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Include TSDoc comments for components in Elements package
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Include TSDoc comments for components
Learnt from: CR
Repo: consentdotio/agent-skills PR: 0
File: tsdoc-jsdoc-authoring/AGENTS.md:0-0
Timestamp: 2026-02-19T01:12:44.177Z
Learning: Applies to tsdoc-jsdoc-authoring/**/index.{ts,tsx} : Use `packageDocumentation` for entrypoint docs in TSDoc comments
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-18T10:57:22.390Z
Learning: Group related code together and separate concerns
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-18T10:57:22.390Z
Learning: Validate and sanitize user input
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-18T10:57:22.390Z
Learning: Use `class` and `for` attributes (not `className` or `htmlFor`) in Solid/Svelte/Vue/Qwik
📚 Learning: 2026-02-19T01:12:44.177Z
Learnt from: CR
Repo: consentdotio/agent-skills PR: 0
File: tsdoc-jsdoc-authoring/AGENTS.md:0-0
Timestamp: 2026-02-19T01:12:44.177Z
Learning: Applies to tsdoc-jsdoc-authoring/**/*.{js,jsx} : Use `module` for module-level docs in JSDoc comments

Applied to files:

  • apps/docs-smoke/package.json
📚 Learning: 2026-02-19T01:12:44.177Z
Learnt from: CR
Repo: consentdotio/agent-skills PR: 0
File: tsdoc-jsdoc-authoring/AGENTS.md:0-0
Timestamp: 2026-02-19T01:12:44.177Z
Learning: Applies to tsdoc-jsdoc-authoring/**/index.{ts,tsx} : Use `packageDocumentation` for entrypoint docs in TSDoc comments

Applied to files:

  • apps/docs-smoke/package.json
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.tsx : Use TypeScript for all components (.tsx) in the docs app

Applied to files:

  • apps/docs-smoke/package.json
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx,md} : Include TypeScript examples in Elements package documentation

Applied to files:

  • apps/docs-smoke/package.json
🔍 Remote MCP Context7

Perfect! I've gathered the key documentation. Now let me compile the relevant context for PR review:

PR Review Context Summary

Based on the PR objectives and the technologies employed, here's the critical context for reviewing this pull request:

1. Remark Plugin Architecture

The PR implements 10+ custom remark plugins following the established remark pattern:

  • Plugin Structure: Each plugin is a function that returns a transformer function operating on the mdast tree
  • AST Transformation: Plugins use unist-util-visit to traverse and modify nodes (as evidenced by usage in callout.remark.ts, cards.remark.ts, steps.remark.ts, etc.)
  • Node Processing: The PR includes utilities like createJsxComponentProcessor and createSimpleJsxComponentProcessor that wrap the standard visitor pattern for MDX JSX elements

Key Review Point: The plugins follow established remark conventions—they return transformer functions that operate on Root (mdast tree) and use the visitor pattern for AST traversal. The custom library functions (node-creators, guards, attributes) are well-designed wrappers around mdast node creation.


2. Valibot Schema Validation

The linting module (packages/docs/src/lint/) uses Valibot schemas with these patterns:

  • Object Schemas: The PR defines defaultFrontmatterSchema, defaultChangelogFrontmatterSchema, and defaultMetaSchema following Valibot's v.object() pattern
  • Optional Fields: Uses v.optional() to mark fields like availableIn, description, and navigation configs as optional
  • Unknown Field Handling: The allowedKeys() function extracts permitted keys from schemas to support unknown-field violation detection

Key Review Point: The schema approach is sound—it validates against defined structures and distinguishes between validation errors (hard error) and unknown fields (configurable severity via unknownFieldSeverity). The inferred types (DefaultFrontmatter, DefaultChangelogFrontmatter, DefaultMeta) ensure type safety.


3. Changesets Release Workflow Configuration

The PR includes:

  • Config File (.changeset/config.json): Follows standard Changesets structure with changelog, commit: false, access: public, baseBranch: main, updateInternalDependencies: patch, and snapshot.useCalculatedVersion: true
  • Release Workflow (.github/workflows/release.yml): Uses changesets/action to automate versioning and publishing
  • OIDC Trust Model: Replaces NPM_TOKEN secret with OIDC (id-token: write) for provenance-based trusted publishing

Key Review Point: The configuration is aligned with Changesets best practices. The OIDC setup is modern and secure—requires GitHub Actions as a Trusted Publisher on npm (documented in the PR summary). The snapshot.useCalculatedVersion: true enables snapshot releases for testing.

⚠️ Critical Setup Requirement: GitHub Actions must be configured as a Trusted Publisher on npm registry before the first publish attempt, or releases will fail with OIDC auth errors.


4. Monorepo Dependency Resolution

The PR includes scripts/resolve-workspace-deps.ts that:

  • Scans packages/ and apps/ for workspace packages
  • Rewrites workspace:* → exact version, workspace:^^version, etc.
  • Skips private packages to preserve local workspace:* linkage (important for internal tools like shared configs)

Key Review Point: This pattern keeps local dependencies fluid while external packages use resolved versions—preventing version mismatches during publishing.


5. Test Harness & Real-Content Validation

The apps/docs-smoke app serves as both:

  • Consumption harness: Demonstrates how monorepo apps use @inth/docs APIs
  • Integration test: test:real task clones c15t repo, runs the full conversion + linting pipeline, and validates ~200 mdx → md conversions with ≥90% success threshold

Key Review Point: The 90% threshold handles legitimate legacy warnings (PR notes ~24 expected); the benchmark workflow (non-blocking, continue-on-error: true) captures performance without gating merges.


Summary of Key Points for Review

Strengths:

  • Modular plugin architecture follows remark conventions
  • Schema validation is well-typed with Valibot
  • OIDC-based release workflow is secure and modern
  • Real-content integration test prevents regressions
  • Workspace dependency resolution preserves local linkage

⚠️ Critical Notes:

  1. Changesets OIDC Setup: GitHub Actions must be trusted publisher before first release
  2. Test Expectations: Real-content test expects ~24 legacy warnings; lint violations are legitimate and documented
  3. Concurrency: Release workflow uses concurrency to prevent overlapping runs
🔇 Additional comments (1)
apps/docs-smoke/package.json (1)

6-20: Script and dependency wiring looks good for the smoke harness.

The convert/llm/build and real-content scripts are clear, and @inth/docs as a workspace dependency matches the PR’s package-consumption objective.

Comment thread .github/workflows/bench.yml
Comment thread apps/docs-smoke/scripts/bench.ts Outdated
Comment thread apps/docs-smoke/scripts/bench.ts Outdated
Bugs / correctness
- FRONTMATTER_REGEX now accepts CRLF in both convert.ts and include.remark.ts
  so Windows-checked-out docs don't lose their source frontmatter (comment #15, #31)
- enrichFromGit uses NUL separator so author names containing `|` survive
  round-trip (comment #42)
- content-processor joins multi-fragment paragraphs / blockquotes instead of
  keeping only the first (comment #26)
- text.ts extractTableContent renders every row, not just header + first row
  (comment #29)
- steps.remark preserves inline markup (code, links, strong) when folding the
  leading paragraph into the step title instead of flattening via mdastToString
  (comment #35)
- LLM readSourceDocs throws on duplicate route collisions rather than silently
  overwriting (comment #45)
- renderRootFullRouter only advertises /docs/llms.txt when that file was
  actually written (comment #25)

Security
- GitHub Actions reporter now escapes %/CR/LF/:/, in workflow-command
  properties and %/CR/LF in message bodies (comment #21)
- lint CLI rejects flag-like tokens where an option value is required so
  `--src --format json` surfaces a usage error (comment #19)
- generateLLMFullFiles validates topic.slug against a URL-safe pattern before
  using it in paths/URLs (comment #46)

Schema / config
- changelog semver pattern now accepts prerelease + build metadata per
  SemVer 2.0.0 (comment #23)
- changelogDir is resolved against srcDir so relative subdir configs work from
  any cwd (comment #22)
- changesets config $schema pinned to 2.30.0 (comment #2)

Accessibility
- Tab panels now render the id + aria-labelledby matching their triggers'
  aria-controls/id (comment #13)
- PackageCommandTabs drops role=tablist/tab (we don't implement the roving-
  focus / arrow-key pattern) and uses aria-pressed on a plain button group
  (comment #12)
- type-table treats `""` as a real default value instead of rendering "—"
  (comment #14)

Infra
- bench.yml gets timeout-minutes: 20 (comment #47)
- BENCH_RUNS is validated as a positive integer (comment #48)
- turbo.json build outputs include dist/** and build/** for packages
  (comment #41)
- convert logs when srcDir is missing and reports failed file count
  (comments #43, #44)
- setup-real-content supports C15T_REF for reproducible bench/test numbers
  (comment #7)
- test-real cleans OUT_DIR before each run and asserts exact mdCount === mdxCount
  (comment #8)
- husky pre-commit no longer masks unfixable ultracite errors behind `set -e`
  (comment #5)
- release.yml documents the npm promise-retry workaround with an issue link
  (comment #4)
- typescript-config drops the redundant publishConfig block (comment #38)
- smoke doc fixture says @inth/docs, not @inth/optin-docs (comment #6)

Deferred
- #3 (OIDC on private repo): blocker, needs user decision to either make the
  repo public or switch back to NPM_TOKEN
- #10, #16, #20, #24, #11: disagree with rationale (see PR thread)
- #17/#18/#27/#28/#30/#33/#34/#37/#39/#40: stylistic nitpicks, skipping
- #32, #36: complex ported logic, tracked as known limitations
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

♻️ Duplicate comments (8)
packages/docs/src/components/package-command-tabs.tsx (1)

7-14: ⚠️ Potential issue | 🟠 Major

Unify the PackageCommandTabs prop contract with the remark transformer output.

This still looks at risk of runtime/convert drift: the component resolves a {pm} template or commands map, while the transformer flow is described as command + mode. A single MDX node can diverge between app render and MDX→Markdown conversion unless one canonical shape is shared.

#!/bin/bash
# Verify whether component props and remark plugin parse the same prop shape.
fd -i 'package-command-tabs' .

rg -nP -C3 'type\s+PackageCommandTabsProps|command\?:|commands\?:|mode\??:' packages/docs/src/components/package-command-tabs.tsx

rg -nP -C3 '\b(command|commands|mode)\b' packages/docs/src/remark/plugins/package-command-tabs.remark.ts

rg -nP --type=mdx -C2 '<PackageCommandTabs[^>]*(mode=|command=|commands=)' apps/docs-smoke

Based on learnings, Make architecture decisions thoughtfully - consider component structure, data flow, and API design.

Also applies to: 18-31

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/components/package-command-tabs.tsx` around lines 7 - 14,
The component props and the remark transformer disagree: the component type
PackageCommandTabsProps (and PackageCommandTabs rendering logic) accepts
command?: string and commands?: Partial<Record<PackageManager,string>>, while
the remark plugin (package-command-tabs.remark.ts) emits/consumes a different
shape (command + mode). Pick one canonical shape and make both sides match:
either (A) expand PackageCommandTabsProps to include mode?: 'template'|'map' and
update the component's resolve logic (where it replaces {pm} or reads commands)
to follow the same mode semantics the remark transformer emits, or (B) change
the remark transformer to emit the commands map that PackageCommandTabs
currently expects. Update the unique symbols PackageCommandTabsProps,
PackageCommandTabs (the resolve/render function), and the transformer in
package-command-tabs.remark.ts so the MDX node emitted matches the component
props exactly and remove duplicated conversion logic to avoid runtime/convert
drift.
packages/docs/src/components/tabs.tsx (2)

57-67: ⚠️ Potential issue | 🟠 Major

Add keyboard navigation for tab triggers (Arrow/Home/End).

Line [64] handles mouse only. For role="tab" controls, roving keyboard support is expected; currently this remains an accessibility gap.

⌨️ Suggested fix
               <button
@@
                 onClick={() => setActiveValue(normalized)}
+                onKeyDown={(event) => {
+                  const currentIndex = items.findIndex(
+                    (value) => normalize(value) === normalized
+                  );
+                  if (currentIndex < 0) return;
+
+                  let nextIndex = currentIndex;
+                  if (event.key === "ArrowRight") {
+                    nextIndex = (currentIndex + 1) % items.length;
+                  } else if (event.key === "ArrowLeft") {
+                    nextIndex = (currentIndex - 1 + items.length) % items.length;
+                  } else if (event.key === "Home") {
+                    nextIndex = 0;
+                  } else if (event.key === "End") {
+                    nextIndex = items.length - 1;
+                  } else {
+                    return;
+                  }
+
+                  event.preventDefault();
+                  const nextValue = normalize(items[nextIndex] ?? "");
+                  setActiveValue(nextValue);
+                  document.getElementById(`${groupId}-trigger-${nextValue}`)?.focus();
+                }}
                 role="tab"
+                tabIndex={isActive ? 0 : -1}
                 type="button"
               >

As per coding guidelines, "include keyboard event handlers alongside mouse events" and "Use semantic HTML and ARIA attributes for accessibility".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/components/tabs.tsx` around lines 57 - 67, The tab trigger
buttons lack keyboard navigation; add an onKeyDown handler to the element with
role="tab" (the button using id `${groupId}-trigger-${normalized}`,
data-inth-tabs-trigger and calling setActiveValue) that implements roving
keyboard support: on ArrowRight/ArrowDown move focus/activate the next tab, on
ArrowLeft/ArrowUp move to the previous tab, on Home activate the first tab and
on End activate the last tab (use preventDefault() for handled keys); determine
next/previous/first/last from the component's tab list/normalized values and
call setActiveValue(...) accordingly so keyboard users can navigate tabs.

84-99: ⚠️ Potential issue | 🟠 Major

Keep tab panels mounted so aria-controls always has a target.

Because Line [87] returns null for inactive tabs, most triggers’ aria-controls (Line [58]) point to elements that are not in the DOM until activated.

🧩 Suggested fix
 export function Tab({ value, children }: TabProps) {
   const { activeValue, groupId } = useTabsContext();
   const normalized = normalize(value);
-  if (normalized !== activeValue) {
-    return null;
-  }
+  const isActive = normalized === activeValue;
   return (
     <div
       aria-labelledby={`${groupId}-trigger-${normalized}`}
+      hidden={!isActive}
       data-inth-tab-panel=""
       data-value={normalized}
       id={`${groupId}-${normalized}`}
       role="tabpanel"
+      tabIndex={0}
     >
-      {children}
+      {isActive ? children : null}
     </div>
   );
 }

As per coding guidelines, "Use semantic HTML and ARIA attributes for accessibility".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/components/tabs.tsx` around lines 84 - 99, The Tab
component currently returns null for inactive panels (in Tab function using
useTabsContext, activeValue, and normalize), which removes their DOM nodes and
breaks triggers' aria-controls; instead always render the <div
id={`${groupId}-${normalized}`} ... role="tabpanel"> and keep all attributes
(data-inth-tab-panel, data-value, aria-labelledby) but mark inactive panels as
hidden (e.g. add hidden and aria-hidden="true" or a visually-hidden style) when
normalized !== activeValue so the panel stays in the DOM while remaining
inaccessible/hidden to users until active.
apps/docs-smoke/scripts/setup-real-content.ts (1)

13-17: ⚠️ Potential issue | 🟠 Major

Pin the default fixture ref to an immutable revision.

Falling back to "main" means test:real and bench still drift whenever c15t changes, so failures and timing deltas stop being reproducible even when this repo has not changed.

🔧 Proposed fix
 const REPO = "https://github.com/c15t/c15t.git";
-// Pin to a specific ref so bench/test numbers are reproducible. Override with
-// C15T_REF=<sha|tag|branch> to test against upstream changes.
-const FIXTURE_REF = process.env.C15T_REF ?? "main";
+// Pin to a specific ref so bench/test numbers are reproducible. Override with
+// C15T_REF=<sha|tag|branch> only when intentionally testing upstream changes.
+const DEFAULT_C15T_REF = "<pinned-commit-or-tag>";
+const FIXTURE_REF = process.env.C15T_REF ?? DEFAULT_C15T_REF;

Based on learnings, Handle boundary conditions and error states - consider edge cases.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/docs-smoke/scripts/setup-real-content.ts` around lines 13 - 17, The
fallback for FIXTURE_REF currently uses the mutable string "main", causing
drifting test/bench results; update the FIXTURE_REF initialization (the
FIXTURE_REF constant that reads process.env.C15T_REF) to use an immutable
default (for example a release tag or a specific commit SHA) or require C15T_REF
to be set and throw a clear error if absent; update any related messaging to
reference REPO and FIXTURE_DIR so callers know which repo/ref to pin.
apps/docs-smoke/scripts/bench.ts (1)

164-167: 🧹 Nitpick | 🔵 Trivial

Remove the stale fixture-count literal from the report header.

200 will drift as soon as the fixture changes, so the benchmark summary can report the wrong dataset. Compute the .mdx count from SRC_DIR once and interpolate that value here instead.

As per coding guidelines, "Use meaningful variable names instead of magic numbers - extract constants with descriptive names".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/docs-smoke/scripts/bench.ts` around lines 164 - 167, The report header
currently embeds a stale literal `200`; change it to compute the actual .mdx
file count from SRC_DIR and interpolate that value into the header instead. Add
a small helper or inline logic (e.g., countMdxFilesInDir or similar) before
creating `header` to read SRC_DIR, count files matching /\.mdx$/ (or use
existing utilities), store the result in a variable like mdxCount, and replace
the `200` literal in the `header` construction so the header uses `${mdxCount}`
along with the existing `RUNS`, keeping calls to `bench()` and
`renderTable(stats)` unchanged.
packages/docs/src/remark/plugins/include.remark.ts (1)

162-172: ⚠️ Potential issue | 🔴 Critical

Promoting a paragraph include by assigning { type: "root" } still corrupts the AST.

When an include inside a paragraph resolves to multiple top-level nodes, replaceTarget() rewrites the paragraph itself to type: "root" instead of splicing the replacement into the grandparent array. That produces invalid nested roots and can drop any surrounding phrasing that shared the paragraph with the include.

Based on learnings, Handle boundary conditions and error states - consider edge cases.

Also applies to: 439-458

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/remark/plugins/include.remark.ts` around lines 162 - 172,
The replaceTarget function currently promotes a paragraph to a root by assigning
{ type: "root" } to the paragraph itself, which corrupts the AST; change
replaceTarget so that when parent is a paragraph (isParagraph(parent)) and
replacement.type === "root" you do not mutate the paragraph node but instead
splice replacement.children into the grandparent's children array in place of
the paragraph node (i.e., locate the grandparent and replace the single parent
entry with the replacement.children) and fall back to Object.assign(node,
replacement) for all other cases; update the signature or call sites as needed
to obtain the grandparent reference and ensure you preserve surrounding phrasing
when the include expands to multiple top-level nodes.
packages/docs/src/remark/libs/content-processor.ts (1)

42-61: ⚠️ Potential issue | 🟠 Major

Blockquotes are still collapsed into a single paragraph.

extractBlockquoteContent(...).join(" ") removes paragraph boundaries and quote structure, so plugins using processContentNode() still lose multi-paragraph blockquotes. Build one paragraph per extracted fragment under the blockquote instead of merging everything into one text node.

🔧 Proposed fix
   if (is(node, "blockquote")) {
     const content = extractBlockquoteContent(node as Blockquote);
-    if (content.length === 0) {
-      return null;
-    }
-    // Preserve blockquote wrapper (previous implementation flattened to
-    // whatever processContentText returned, losing quote structure).
-    const text = normalizeWhitespace(content.join(" "), true);
-    if (!text) {
+    const paragraphs = content
+      .map((fragment) => normalizeWhitespace(fragment.replace(/^>\s?/, ""), true))
+      .filter(Boolean)
+      .map((value) => ({
+        type: "paragraph",
+        children: [{ type: "text", value }],
+      }));
+    if (paragraphs.length === 0) {
       return null;
     }
     return {
       type: "blockquote",
-      children: [
-        {
-          type: "paragraph",
-          children: [{ type: "text", value: text.replace(/^>\s?/, "") }],
-        },
-      ],
+      children: paragraphs,
     } as Blockquote;
   }

Based on learnings, Focus on business logic correctness - Biome can't validate your algorithms.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/remark/libs/content-processor.ts` around lines 42 - 61, The
current blockquote handler flattens all fragments via
extractBlockquoteContent(...).join(" ") into a single paragraph; instead,
iterate the fragments returned by extractBlockquoteContent and for each
non-empty fragment create a separate paragraph child under the blockquote so
multi-paragraph quotes are preserved. Use normalizeWhitespace(fragment, true)
(and strip any leading "> " like the current text.replace(/^>\s?/, "") logic)
for each fragment, filter out empty results, and set the blockquote return value
to have children = [ { type: "paragraph", children: [{ type: "text", value: ...
}] }, ... ] rather than a single merged text node. Ensure this change is applied
in the same block where is(node, "blockquote") is handled and replace the join("
") logic currently producing variable text.
apps/docs-smoke/scripts/test-real.ts (1)

75-90: ⚠️ Potential issue | 🟠 Major

Make the lint stage affect the smoke-test result.

This still prints Real-content test passed. after lint findings, so regressions in lintDocs() stay green and test:real effectively becomes a conversion-only check.

🔧 Proposed fix
 process.stdout.write("\nLinting real c15t docs\n");
 const result = await lintDocs({ srcDir: SRC_DIR });
 process.stdout.write(
   `  ${result.summary.filesScanned} files scanned — ${result.summary.errors} error(s), ${result.summary.warnings} warning(s)\n`
 );
 
-// Lint findings reflect the fixture repo's content, not our pipeline — so
-// they're informational. The hard pass/fail signal above (mdCount === mdxCount)
-// is what gates CI.
-if (result.summary.errors > 0) {
-  process.stdout.write(
-    "  (lint errors above are issues in c15t's content, not `@inth/docs`)\n"
-  );
+if (result.summary.errors > 0 || result.summary.warnings > 0) {
+  process.stderr.write(
+    `FAIL: lint reported ${result.summary.errors} error(s) and ${result.summary.warnings} warning(s).\n`
+  );
+  process.exit(1);
 }
 
 process.stdout.write("\nReal-content test passed.\n");

Based on learnings, Focus on business logic correctness - Biome can't validate your algorithms.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/docs-smoke/scripts/test-real.ts` around lines 75 - 90, The lint stage
currently only prints findings and always reports "Real-content test passed.";
update the post-lint logic in test-real.ts to fail the smoke test when linting
finds errors by checking lintDocs()'s result.summary.errors and exiting non-zero
or throwing an error instead of unconditionally printing success; specifically,
in the block after calling lintDocs() (where result and result.summary are
used), if result.summary.errors > 0 call process.exit(1) or throw a descriptive
Error (and keep the informational message), otherwise continue to print the
success message so the test truly reflects lint regressions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/release.yml:
- Around line 48-51: The workflow step "Upgrade npm for provenance publishing"
currently installs npm@latest causing nondeterministic releases; replace the
unpinned install command (npm install -g npm@latest) with a pinned, tested CLI
version such as npm@11.12.1 (or at minimum npm@11.5.1+) to ensure deterministic
publishing and provenance attestation, keeping the existing promise-retry
install line intact; update the run block so the step explicitly installs the
pinned npm version instead of latest.

In @.husky/pre-commit:
- Around line 4-5: Pre-commit runs tests before running "bun x ultracite fix" so
fixes can be applied and staged after tests, allowing a different snapshot to be
committed; move the "bun test --pass-with-no-tests || exit $?" invocation to
after the "bun x ultracite fix" and the subsequent "git add" (or alternatively
rerun "bun test" once fixes are applied), and apply the same change for the
other occurrences around lines 13-29 so the tests validate the post-fix,
restaged tree rather than the pre-fix snapshot.

In `@packages/docs/src/components/package-command-tabs.tsx`:
- Around line 5-6: MANAGERS and the PackageManager union are duplicated; make
MANAGERS the source of truth by declaring MANAGERS as a const tuple/array with a
const assertion (e.g., const MANAGERS = ["npm","pnpm","yarn","bun"] as const)
and then derive the exported type via export type PackageManager = typeof
MANAGERS[number]; remove the hard-coded union string type and ensure any uses
import/consume the PackageManager type and MANAGERS constant.
- Around line 47-60: The button cluster lacks an accessible group label; replace
the wrapper div with a semantic grouping (e.g., a <fieldset> with a <legend> or
a native grouping element) or add an explicit aria-label to the group so screen
readers announce the context for the MANAGERS buttons; update the container
around the map (currently data-inth-package-command-tabs-list) to include the
legend text (like "Package manager") or an aria-label and ensure individual
buttons (which use active and setActive) remain unchanged.
- Around line 23-29: The code currently treats commands?.[manager] with a truthy
check so an explicit empty string override is ignored; update the logic in
resolveCommand (the block using const explicit = commands?.[manager]) to detect
presence rather than truthiness (e.g., check explicit !== undefined or use
Object.prototype.hasOwnProperty.call(commands, manager)) and return that
explicit value when provided (including empty string), otherwise fall back to
doing command.replaceAll("{pm}", manager).

In `@packages/docs/src/components/tabs.tsx`:
- Around line 53-64: The code currently uses normalize(item) alone to build
React keys and ARIA ids in the items.map callback, which can collide when
different items normalize to the same string; update the key/id generation in
the map (the block that uses normalize, groupId, setActiveValue, activeValue) to
append a disambiguator such as the map index or a stable unique token derived
from the original item (e.g., `${groupId}-trigger-${normalized}-${index}`) and
apply the same disambiguator to aria-controls and id so keys/IDs remain unique
and tab targeting remains unambiguous.

In `@packages/docs/src/components/type-table.tsx`:
- Around line 87-95: The figure can render empty when neither caption nor rows
exist; change the rendering in the component that returns the <figure> (the
TypeTable usage) to: compute captionParts from available metadata (push name and
path as present and join with " from " so partial captions work), only render
<figcaption> when captionParts.length > 0, and return null early when
captionParts is empty and the TypeTable has no rows (e.g., check
type?.rows?.length === 0 or equivalent) so the component never outputs an empty
<figure>.

In `@packages/docs/src/convert/convert.ts`:
- Around line 50-59: MERMAID block regex MERMAID_FENCE_REGEX only matches Unix
newlines so it fails on CRLF files; update MERMAID_FENCE_REGEX to accept
optional CR before newlines (use \r?\n like FRONTMATTER_REGEX does) so it
matches both LF and CRLF boundaries around the mermaid fence and capture group;
ensure the global flag remains (/g) and keep the same capture group structure to
preserve downstream behavior.

In `@packages/docs/src/lint/cli.ts`:
- Around line 116-118: The changelog path is being resolved relative to CWD
instead of the resolved source root; when calling lintDocs set changelogDir to
be resolved against the same root used for srcDir (i.e. use the resolved src
root from resolve(args.srcDir) as the base and combine it with args.changelogDir
when present) so that lintDocs receives a changelogDir inside the source tree;
update the changelogDir expression near the lintDocs call (referencing lintDocs,
args.changelogDir and resolve(args.srcDir)) to resolve the subdirectory against
the resolved src root.

In `@packages/docs/src/lint/runner.ts`:
- Around line 187-205: Compute the changelog classification before attempting to
parse so the catch branch can preserve it: move the isChangelog =
isUnderDir(file, resolvedChangelogDir) (and any derived schemaToUse if needed)
above the try block that reads and parses the file, then in the catch path use
that precomputed isChangelog to set the violations entry's metadata (e.g., kind
should be "changelog" when isChangelog is true, otherwise "frontmatter"); update
the loop over mdxFiles to reference the precomputed isChangelog in both the try
and catch branches.

In `@packages/docs/src/llm/llm.ts`:
- Around line 339-347: The template always references the full docs and curated
summary regardless of what was actually generated; update the rendering logic in
llm.ts to consult the availability flags (e.g., hasDocsSummary and whatever flag
indicates full files from generateLLMFullFiles()) or centralize generation
behind one coordinator (instead of calling generateLLMSummaries() and
generateLLMFullFiles() independently) so the returned text only links to
/docs/llms-full.txt and recommends the curated summary when those artifacts
actually exist; locate the template that builds sections.join("\n\n") and the
code paths that call generateLLMSummaries() / generateLLMFullFiles() and add
conditionals or a single entrypoint that sets and uses a shared state describing
which files were written.
- Around line 468-505: In generateLLMFullFiles, detect when
readMarkdownDocs(outDir, baseUrl) returns an empty array and fail fast instead
of writing routers and per-topic files; after calling readMarkdownDocs assign to
markdownDocs and if markdownDocs.length === 0 throw an Error (or reject) with a
clear message indicating the docs directory (DOCS_DIRNAME) is missing or convert
hasn't run, so callers can surface the pipeline misconfiguration; reference
generateLLMFullFiles, readMarkdownDocs, markdownDocs, topics and DOCS_DIRNAME
when adding this guard.

In `@packages/docs/src/remark/libs/text.ts`:
- Around line 170-194: The parser currently splits each line on "|" which
creates empty cells for leading/trailing pipes; update createTableFromContent so
each line is first trimmed of leading/trailing pipe characters (or remove only
the first/last empty token) before splitting, then proceed to trim cell text as
before when building rows and children; modify the rows mapping (the place that
does line.split("|").map(...)) to trim or strip outer '|' to avoid empty
boundary cells while preserving intentional empty cells between pipes.

---

Duplicate comments:
In `@apps/docs-smoke/scripts/bench.ts`:
- Around line 164-167: The report header currently embeds a stale literal `200`;
change it to compute the actual .mdx file count from SRC_DIR and interpolate
that value into the header instead. Add a small helper or inline logic (e.g.,
countMdxFilesInDir or similar) before creating `header` to read SRC_DIR, count
files matching /\.mdx$/ (or use existing utilities), store the result in a
variable like mdxCount, and replace the `200` literal in the `header`
construction so the header uses `${mdxCount}` along with the existing `RUNS`,
keeping calls to `bench()` and `renderTable(stats)` unchanged.

In `@apps/docs-smoke/scripts/setup-real-content.ts`:
- Around line 13-17: The fallback for FIXTURE_REF currently uses the mutable
string "main", causing drifting test/bench results; update the FIXTURE_REF
initialization (the FIXTURE_REF constant that reads process.env.C15T_REF) to use
an immutable default (for example a release tag or a specific commit SHA) or
require C15T_REF to be set and throw a clear error if absent; update any related
messaging to reference REPO and FIXTURE_DIR so callers know which repo/ref to
pin.

In `@apps/docs-smoke/scripts/test-real.ts`:
- Around line 75-90: The lint stage currently only prints findings and always
reports "Real-content test passed."; update the post-lint logic in test-real.ts
to fail the smoke test when linting finds errors by checking lintDocs()'s
result.summary.errors and exiting non-zero or throwing an error instead of
unconditionally printing success; specifically, in the block after calling
lintDocs() (where result and result.summary are used), if result.summary.errors
> 0 call process.exit(1) or throw a descriptive Error (and keep the
informational message), otherwise continue to print the success message so the
test truly reflects lint regressions.

In `@packages/docs/src/components/package-command-tabs.tsx`:
- Around line 7-14: The component props and the remark transformer disagree: the
component type PackageCommandTabsProps (and PackageCommandTabs rendering logic)
accepts command?: string and commands?: Partial<Record<PackageManager,string>>,
while the remark plugin (package-command-tabs.remark.ts) emits/consumes a
different shape (command + mode). Pick one canonical shape and make both sides
match: either (A) expand PackageCommandTabsProps to include mode?:
'template'|'map' and update the component's resolve logic (where it replaces
{pm} or reads commands) to follow the same mode semantics the remark transformer
emits, or (B) change the remark transformer to emit the commands map that
PackageCommandTabs currently expects. Update the unique symbols
PackageCommandTabsProps, PackageCommandTabs (the resolve/render function), and
the transformer in package-command-tabs.remark.ts so the MDX node emitted
matches the component props exactly and remove duplicated conversion logic to
avoid runtime/convert drift.

In `@packages/docs/src/components/tabs.tsx`:
- Around line 57-67: The tab trigger buttons lack keyboard navigation; add an
onKeyDown handler to the element with role="tab" (the button using id
`${groupId}-trigger-${normalized}`, data-inth-tabs-trigger and calling
setActiveValue) that implements roving keyboard support: on ArrowRight/ArrowDown
move focus/activate the next tab, on ArrowLeft/ArrowUp move to the previous tab,
on Home activate the first tab and on End activate the last tab (use
preventDefault() for handled keys); determine next/previous/first/last from the
component's tab list/normalized values and call setActiveValue(...) accordingly
so keyboard users can navigate tabs.
- Around line 84-99: The Tab component currently returns null for inactive
panels (in Tab function using useTabsContext, activeValue, and normalize), which
removes their DOM nodes and breaks triggers' aria-controls; instead always
render the <div id={`${groupId}-${normalized}`} ... role="tabpanel"> and keep
all attributes (data-inth-tab-panel, data-value, aria-labelledby) but mark
inactive panels as hidden (e.g. add hidden and aria-hidden="true" or a
visually-hidden style) when normalized !== activeValue so the panel stays in the
DOM while remaining inaccessible/hidden to users until active.

In `@packages/docs/src/remark/libs/content-processor.ts`:
- Around line 42-61: The current blockquote handler flattens all fragments via
extractBlockquoteContent(...).join(" ") into a single paragraph; instead,
iterate the fragments returned by extractBlockquoteContent and for each
non-empty fragment create a separate paragraph child under the blockquote so
multi-paragraph quotes are preserved. Use normalizeWhitespace(fragment, true)
(and strip any leading "> " like the current text.replace(/^>\s?/, "") logic)
for each fragment, filter out empty results, and set the blockquote return value
to have children = [ { type: "paragraph", children: [{ type: "text", value: ...
}] }, ... ] rather than a single merged text node. Ensure this change is applied
in the same block where is(node, "blockquote") is handled and replace the join("
") logic currently producing variable text.

In `@packages/docs/src/remark/plugins/include.remark.ts`:
- Around line 162-172: The replaceTarget function currently promotes a paragraph
to a root by assigning { type: "root" } to the paragraph itself, which corrupts
the AST; change replaceTarget so that when parent is a paragraph
(isParagraph(parent)) and replacement.type === "root" you do not mutate the
paragraph node but instead splice replacement.children into the grandparent's
children array in place of the paragraph node (i.e., locate the grandparent and
replace the single parent entry with the replacement.children) and fall back to
Object.assign(node, replacement) for all other cases; update the signature or
call sites as needed to obtain the grandparent reference and ensure you preserve
surrounding phrasing when the include expands to multiple top-level nodes.
🪄 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: bdc7b696-a1f9-4553-85e0-c4a2962ae086

📥 Commits

Reviewing files that changed from the base of the PR and between 2cf181d and 8492e53.

📒 Files selected for processing (23)
  • .changeset/config.json
  • .github/workflows/bench.yml
  • .github/workflows/release.yml
  • .husky/pre-commit
  • apps/docs-smoke/content/docs/index.mdx
  • apps/docs-smoke/scripts/bench.ts
  • apps/docs-smoke/scripts/setup-real-content.ts
  • apps/docs-smoke/scripts/test-real.ts
  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/tabs.tsx
  • packages/docs/src/components/type-table.tsx
  • packages/docs/src/convert/convert.ts
  • packages/docs/src/lint/cli.ts
  • packages/docs/src/lint/reporters.ts
  • packages/docs/src/lint/runner.ts
  • packages/docs/src/lint/schema.ts
  • packages/docs/src/llm/llm.ts
  • packages/docs/src/remark/libs/content-processor.ts
  • packages/docs/src/remark/libs/text.ts
  • packages/docs/src/remark/plugins/include.remark.ts
  • packages/docs/src/remark/plugins/steps.remark.ts
  • packages/typescript-config/package.json
  • turbo.json

Comment thread .github/workflows/release.yml Outdated
Comment thread .husky/pre-commit Outdated
Comment thread packages/docs/src/components/package-command-tabs.tsx Outdated
Comment thread packages/docs/src/components/package-command-tabs.tsx
Comment thread packages/docs/src/components/package-command-tabs.tsx Outdated
Comment thread packages/docs/src/lint/cli.ts Outdated
Comment thread packages/docs/src/lint/runner.ts Outdated
Comment on lines +339 to +347
return `# ${product.name} Documentation

> Curated documentation map for developers and coding agents working with ${product.name}.

## How To Use This File

Read the summary links first. If the summary is not enough, choose the smallest relevant topic file from \`/docs/llms-full.txt\`.

${sections.join("\n\n")}`;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Generated guidance still assumes optional artifacts always exist.

Line 345 always sends readers to /docs/llms-full.txt, and Line 380 still says to go through the curated docs summary even when hasDocsSummary is false. Because generateLLMSummaries() and generateLLMFullFiles() are separate APIs, valid configs can emit self-contradictory instructions. Thread availability flags into both renderers, or expose a single coordinated entry point that knows which files were actually written. Based on learnings, handle boundary conditions and error states and consider user experience.

Also applies to: 372-390

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/llm/llm.ts` around lines 339 - 347, The template always
references the full docs and curated summary regardless of what was actually
generated; update the rendering logic in llm.ts to consult the availability
flags (e.g., hasDocsSummary and whatever flag indicates full files from
generateLLMFullFiles()) or centralize generation behind one coordinator (instead
of calling generateLLMSummaries() and generateLLMFullFiles() independently) so
the returned text only links to /docs/llms-full.txt and recommends the curated
summary when those artifacts actually exist; locate the template that builds
sections.join("\n\n") and the code paths that call generateLLMSummaries() /
generateLLMFullFiles() and add conditionals or a single entrypoint that sets and
uses a shared state describing which files were written.

Comment thread packages/docs/src/llm/llm.ts
Comment thread packages/docs/src/remark/libs/text.ts
Bugs / correctness
- include.remark replaceTarget no longer mutates a paragraph into a root
  (invalid mdast). Walks the tree to find the grandparent and splices the
  replacement children in place (comment #32)
- convert.ts MERMAID_FENCE_REGEX accepts CRLF boundaries
- text.ts createTableFromContent strips leading/trailing pipes so
  `| a | b |` doesn't produce empty boundary cells
- content-processor preserves multi-paragraph blockquotes by returning
  one paragraph per fragment instead of joining them
- generateLLMFullFiles fails fast when readMarkdownDocs returns empty

Lint / classification
- lint CLI resolves --changelog against the resolved srcDir, not cwd
- lint runner classifies isChangelog before parsing so parse-error
  violations carry the correct kind

Accessibility / UX
- Tab panels stay in the DOM (hidden=true when inactive) so the
  trigger's aria-controls never points at a missing element
- Tabs: full keyboard navigation (Arrow keys, Home, End) with roving
  tabindex
- Tab trigger/panel ids include the item index so two items that
  normalize to the same string still get unique ids
- PackageCommandTabs: MANAGERS is a const tuple (PackageManager type
  derived); resolveCommand uses presence check so empty-string override
  wins; button cluster wrapped in <fieldset>/<legend>
- AutoTypeTable returns null when there's no caption and no rows

Infra
- release.yml pins npm@11.12.1 for deterministic publishing
- pre-commit runs ultracite fix → restage → tests so tests validate
  the post-fix tree
- bench.ts counts .mdx files dynamically instead of hard-coding "200"
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

♻️ Duplicate comments (1)
packages/docs/src/components/package-command-tabs.tsx (1)

9-14: ⚠️ Potential issue | 🟠 Major

Unify PackageCommandTabs command contract with the remark transformer.

PackageCommandTabsProps still models command as a template string, while the markdown conversion path uses structured command/mode data. That keeps React rendering and MDX→Markdown conversion on different input contracts for the same component node.

Proposed direction (single canonical input shape + adapter)
 export type PackageCommandTabsProps = {
-  /** Command template — `{pm}` is replaced with the active package manager. E.g. "{pm} install `@inth/docs`" */
-  command?: string;
+  /**
+   * Canonical command token used across runtime rendering + remark conversion.
+   * Keep string support temporarily for backward compatibility.
+   */
+  command?:
+    | string
+    | {
+        package: string;
+        mode?: string;
+      };
   /** Or pass pre-rendered commands per manager */
   commands?: Partial<Record<PackageManager, string>>;
   defaultManager?: PackageManager;
   children?: ReactNode;
 };
+function toTemplate(
+  command: PackageCommandTabsProps["command"],
+  manager: PackageManager
+): string {
+  if (!command) return "";
+  if (typeof command === "string") return command.replaceAll("{pm}", manager);
+  const mode = command.mode ?? "install";
+  return `${manager} ${mode} ${command.package}`;
+}

Also applies to: 34-42

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/components/package-command-tabs.tsx` around lines 9 - 14,
PackageCommandTabsProps currently mixes a template string and per-manager map
which diverges from the MDX remark transformer’s structured {command, mode}
payload; unify to a single canonical shape (e.g. define a CommandDescriptor
union type like {mode: 'template', command: string} | {mode: 'perManager',
commands: Partial<Record<PackageManager, string>>}) and replace the existing
command?: string and commands?: Partial<Record<PackageManager,string>> fields in
PackageCommandTabsProps with a single prop (e.g. commandDescriptor?:
CommandDescriptor); update the PackageCommandTabs component to render from
CommandDescriptor and add a small compatibility adapter that converts legacy
props (template string or commands map) into the new descriptor so both the
component and the MDX remark transformer consume the same input shape.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/release.yml:
- Around line 32-35: The workflow step named "Setup Bun" is using bun-version:
latest which makes releases non-deterministic; change the bun-version value in
that step to a specific pinned version (e.g., the currently tested Bun release
like "1.0.0" or whatever you validated) so builds are reproducible—update the
bun-version field under the "Setup Bun" step to the chosen fixed version string
and commit the change.

In `@apps/docs-smoke/scripts/bench.ts`:
- Around line 48-55: The median(values: number[]) function should explicitly
handle the empty-array case instead of relying on nullish coalescing; add an
early check at the top of median that returns 0 (and/or a short comment
documenting that behavior) when values.length === 0 so intent is clear and
callers (and future readers) know empty input returns 0; keep the rest of the
sorting/mid logic unchanged and reference the median function and the RUNS
context in a comment if helpful.

In `@packages/docs/src/components/type-table.tsx`:
- Around line 26-29: The table header THs in the TypeTable component are missing
column scope attributes; update the JSX in
packages/docs/src/components/type-table.tsx (the <th> elements that render
"Prop", "Type", "Default", "Description") to include scope="col" on each header
to improve accessibility for screen readers and keyboard navigation.
- Around line 48-51: The JSX is injecting property.typeDescriptionLink directly
into <a href>, which can allow unsafe schemes (e.g. javascript:); update the
conditional rendering around property.typeDescriptionLink to first
validate/sanitize the URL: parse it (e.g. using the URL constructor) or test its
scheme and only allow safe protocols like "http:" and "https:" (optionally
"mailto:"); if the value is invalid, fall back to rendering
<code>{property.type}</code> without an <a>, and when rendering a validated
external link add rel="noopener noreferrer" (and target="_blank" if desired) to
the anchor to prevent exploitation. Ensure the changes are applied where
property.typeDescriptionLink is referenced in type-table.tsx.

In `@packages/docs/src/convert/convert.ts`:
- Around line 365-370: The current startsWith check uses case-insensitive
comparison for all platforms; update the condition so it only does a
case-insensitive compare on Windows (process.platform === 'win32') and uses a
strict case-sensitive compare on other platforms—locate the variables
normalizedInput and normalizedSrcDir in the block that returns join(outDir,
relative(srcDir, normalizedInput).replace(MDX_EXTENSION_REGEX, ".md")) and
change the startsWith logic accordingly so Windows uses toLowerCase() comparison
while non-win32 platforms use the exact startsWith check.

In `@packages/docs/src/lint/cli.ts`:
- Around line 129-137: Consolidate the repetitive format conditionals around
args.format by treating "github" and "json" the same: check if args.format is in
a small Set/array (e.g., ["github","json"]) and call
process.stdout.write(output) for those cases, otherwise call
process.stderr.write(output); update the conditional that currently uses
separate if/else if/else branches (referencing args.format,
process.stdout.write, process.stderr.write, and output) to a single clearer
conditional using includes/has.

In `@packages/docs/src/lint/runner.ts`:
- Around line 249-255: The summary construction does two separate filters over
violations; refactor to a single-pass counter loop to compute errors and
warnings. Replace the dual .filter calls used when building the summary object
(the LintSummary const named summary that uses violations.filter(...).length
twice) with a single for...of or for loop over violations that increments
errorCount and warningCount, then set errors and warnings from those counters
while keeping filesScanned unchanged.

In `@packages/docs/src/remark/libs/content-processor.ts`:
- Around line 75-86: The double type assertion "as unknown as { value: string }"
for text nodes should be simplified by importing the mdast Text type and using
it directly; in the node handling branch where you check `if (node.type ===
"text")` cast `node` to `Text` (e.g., `const textNode = node as Text`) and then
use `textNode.value` to build the normalized paragraph (functions/symbols to
locate: `normalizeWhitespace`, the `paragraph` return block, and the `Paragraph`
type). Ensure you add the Text import from the mdast types at the top of the
file and remove the unnecessary double assertion.

In `@packages/docs/src/remark/libs/text.ts`:
- Around line 24-35: The normalizeWhitespace function performs both a
.replace(TRIM_WHITESPACE, "") and .trim() when preserveNewlines is true, which
is redundant; update normalizeWhitespace to use only one trimming approach
(either keep the TRIM_WHITESPACE replacement and remove the trailing .trim(), or
remove the TRIM_WHITESPACE call and keep .trim()) so the result is trimmed once;
adjust the preserveNewlines branch in normalizeWhitespace and ensure references
to TRIM_WHITESPACE and the .trim() call are updated consistently.
- Around line 87-96: extractBlockquoteContent currently concatenates all
children into one string which flattens multi-paragraph blockquotes; update
extractBlockquoteContent to iterate over node.children and produce one array
element per paragraph-like child (e.g., for each child node call
extractNodeText(child.children || []) or otherwise extract text for that child),
trim and filter out empty entries, and return each as a separate string prefixed
with "> " so callers (like content-processor.ts) receive distinct fragments for
each blockquote paragraph.

In `@packages/docs/src/remark/plugins/include.remark.ts`:
- Around line 362-366: The code currently returns silently when specifier (from
flattenNode(node) or params.src) is empty; modify the early-exit to emit a
warning instead of silently returning: detect the empty specifier (the same
check around const specifier = flattenNode(node).trim() || (params.src ??
"").trim()), and call the appropriate project logger (or console.warn if no
logger is available) with a clear message identifying the empty <include>
specifier and the node/params context so misconfigured include tags are visible
during processing; then continue to return to keep the current control flow.
- Around line 459-522: Multiple sibling include nodes processed concurrently can
race when each .then() calls replaceTarget() (which uses findContainer()),
causing one include to remove the shared parent before another's replacement
runs and leading to invalid mdast (a root node left inside a paragraph). Change
the pass-level concurrency: instead of collecting promises and awaiting
Promise.all(tasks), collect async tasks as functions (or otherwise enqueue work)
and then process them sequentially (await each processIncludeNode result and run
replaceTarget immediately) so replaceTarget/findContainer mutations happen in a
deterministic order; update the logic around visit, processIncludeNode,
replaceTarget and findContainer to ensure each include’s replacement is applied
before the next sibling is processed within the same pass (alternatively
implement a per-parent lock), preserving tree integrity when MAX_PASSES loops.

---

Duplicate comments:
In `@packages/docs/src/components/package-command-tabs.tsx`:
- Around line 9-14: PackageCommandTabsProps currently mixes a template string
and per-manager map which diverges from the MDX remark transformer’s structured
{command, mode} payload; unify to a single canonical shape (e.g. define a
CommandDescriptor union type like {mode: 'template', command: string} | {mode:
'perManager', commands: Partial<Record<PackageManager, string>>}) and replace
the existing command?: string and commands?:
Partial<Record<PackageManager,string>> fields in PackageCommandTabsProps with a
single prop (e.g. commandDescriptor?: CommandDescriptor); update the
PackageCommandTabs component to render from CommandDescriptor and add a small
compatibility adapter that converts legacy props (template string or commands
map) into the new descriptor so both the component and the MDX remark
transformer consume the same input shape.
🪄 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: f7cf0950-8300-40ab-b1f8-6783d5d8f790

📥 Commits

Reviewing files that changed from the base of the PR and between 8492e53 and 381896a.

📒 Files selected for processing (13)
  • .github/workflows/release.yml
  • .husky/pre-commit
  • apps/docs-smoke/scripts/bench.ts
  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/tabs.tsx
  • packages/docs/src/components/type-table.tsx
  • packages/docs/src/convert/convert.ts
  • packages/docs/src/lint/cli.ts
  • packages/docs/src/lint/runner.ts
  • packages/docs/src/llm/llm.ts
  • packages/docs/src/remark/libs/content-processor.ts
  • packages/docs/src/remark/libs/text.ts
  • packages/docs/src/remark/plugins/include.remark.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use explicit types for function parameters and return values when they enhance clarity

Prefer unknown over any when 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

Use next/head or App Router metadata API for head elements

Files:

  • packages/docs/src/lint/cli.ts
  • packages/docs/src/remark/libs/content-processor.ts
  • packages/docs/src/convert/convert.ts
  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/type-table.tsx
  • packages/docs/src/remark/plugins/include.remark.ts
  • packages/docs/src/remark/libs/text.ts
  • packages/docs/src/components/tabs.tsx
  • packages/docs/src/lint/runner.ts
  • apps/docs-smoke/scripts/bench.ts
  • packages/docs/src/llm/llm.ts
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use meaningful variable names instead of magic numbers - extract constants with descriptive names

Use arrow functions for callbacks and short functions

Prefer for...of loops over .forEach() and indexed for loops

Use optional chaining (?.) and nullish coalescing (??) for safer property access

Prefer template literals over string concatenation

Use destructuring for object and array assignments

Use const by default, let only when reassignment is needed, never var

Always await promises in async functions - don't forget to use the return value

Use async/await syntax 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

Remove console.log, debugger, and alert statements from production code

Throw Error objects with descriptive messages, not strings or other values

Use try-catch blocks 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 use eval() or assign directly to document.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

Files:

  • packages/docs/src/lint/cli.ts
  • packages/docs/src/remark/libs/content-processor.ts
  • packages/docs/src/convert/convert.ts
  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/type-table.tsx
  • packages/docs/src/remark/plugins/include.remark.ts
  • packages/docs/src/remark/libs/text.ts
  • packages/docs/src/components/tabs.tsx
  • packages/docs/src/lint/runner.ts
  • apps/docs-smoke/scripts/bench.ts
  • packages/docs/src/llm/llm.ts
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Use function components over class components

Call hooks at the top level only, never conditionally

Specify all dependencies in hook dependency arrays correctly

Use the key prop 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

Provide meaningful alt text for images

Use proper heading hierarchy in semantic HTML

Add labels for form inputs

Include keyboard event handlers alongside mouse events

Use semantic elements (<button>, <nav>, etc.) instead of divs with roles

Add rel="noopener" when using target="_blank" on links

Avoid dangerouslySetInnerHTML unless absolutely necessary

Use proper image components (e.g., Next.js <Image>) over <img> tags

Use Next.js <Image> component for images

Use ref as a prop instead of React.forwardRef

Files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/type-table.tsx
  • packages/docs/src/components/tabs.tsx
**/*.tsx

📄 CodeRabbit inference engine (AGENTS.md)

Use Server Components for async data fetching instead of async Client Components

Files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/type-table.tsx
  • packages/docs/src/components/tabs.tsx
🧠 Learnings (96)
📓 Common learnings
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.mdx : Keep documentation organized by categories
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.mdx : Use MDX for documentation pages
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.mdx : Use MDX for documentation pages in the docs app
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.mdx : Include code examples where appropriate in MDX documentation pages of the docs app
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx,md} : Include TypeScript examples in Elements package documentation
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.tsx : Use TypeScript for all components (.tsx) in the docs app
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Include TSDoc comments for components
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Include TSDoc comments for components in Elements package
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/src/components/**/*.tsx : Place reusable components in `src/components` within the docs app
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.mdx : Include code examples where appropriate in documentation
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-18T12:07:13.409Z
Learning: Keep functions focused and under reasonable cognitive complexity limits
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-18T12:07:13.409Z
Learning: Group related code together and separate concerns
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-18T12:07:13.409Z
Learning: Validate and sanitize user input
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-18T12:07:13.409Z
Learning: Use `class` and `for` attributes (not `className` or `htmlFor`) in Solid/Svelte/Vue/Qwik
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-18T12:07:13.409Z
Learning: Focus code review on business logic correctness - Biome cannot validate algorithms
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-18T12:07:13.409Z
Learning: Use descriptive names for functions, variables, and types
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-18T12:07:13.409Z
Learning: Make thoughtful architecture decisions regarding component structure, data flow, and API design
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-18T12:07:13.409Z
Learning: Handle boundary conditions and error states for edge cases
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-18T12:07:13.409Z
Learning: Prioritize accessibility, performance, and usability in user experience design
Learnt from: CR
Repo: inthhq/docs

Timestamp: 2026-04-18T12:07:13.409Z
Learning: Add comments for complex logic, but prefer self-documenting code
📚 Learning: 2026-02-19T01:12:44.177Z
Learnt from: CR
Repo: consentdotio/agent-skills PR: 0
File: tsdoc-jsdoc-authoring/AGENTS.md:0-0
Timestamp: 2026-02-19T01:12:44.177Z
Learning: Applies to tsdoc-jsdoc-authoring/**/index.{ts,tsx} : Use `packageDocumentation` for entrypoint docs in TSDoc comments

Applied to files:

  • packages/docs/src/lint/cli.ts
  • packages/docs/src/lint/runner.ts
📚 Learning: 2026-02-19T01:12:44.177Z
Learnt from: CR
Repo: consentdotio/agent-skills PR: 0
File: tsdoc-jsdoc-authoring/AGENTS.md:0-0
Timestamp: 2026-02-19T01:12:44.177Z
Learning: Applies to tsdoc-jsdoc-authoring/**/*.{js,jsx} : Use optional and default parameter strings correctly in JSDoc comments

Applied to files:

  • packages/docs/src/lint/cli.ts
  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/type-table.tsx
📚 Learning: 2026-03-19T22:58:00.031Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc:0-0
Timestamp: 2026-03-19T22:58:00.031Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Handle errors explicitly and avoid silent failures or unhandled promise rejections

Applied to files:

  • packages/docs/src/lint/cli.ts
  • packages/docs/src/llm/llm.ts
📚 Learning: 2025-12-01T15:20:41.963Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/posthog-integration.mdc:0-0
Timestamp: 2025-12-01T15:20:41.963Z
Learning: Applies to **/*.{js,ts,tsx,jsx} : If using TypeScript, use an enum to store flag names. If using JavaScript, store flag names as strings to an object declared as a constant, to simulate an enum. Use a consistent naming convention for this storage. enum/const object members should be written UPPERCASE_WITH_UNDERSCORE.

Applied to files:

  • packages/docs/src/lint/cli.ts
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't use optional chaining where undefined values aren't allowed

Applied to files:

  • packages/docs/src/lint/cli.ts
  • packages/docs/src/components/type-table.tsx
📚 Learning: 2026-03-19T22:57:45.114Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:45.114Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Provide clear error messages for development, implement runtime checks for required context/props, and use descriptive error messages that guide developers

Applied to files:

  • packages/docs/src/lint/cli.ts
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{tsx,jsx} : Provide clear error messages when components are used outside of context, missing required props, have invalid prop combinations, or runtime validation failures

Applied to files:

  • packages/docs/src/lint/cli.ts
📚 Learning: 2026-03-19T22:57:45.114Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:45.114Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Implement runtime validation checks for prop combinations and provide clear error messages for validation failures

Applied to files:

  • packages/docs/src/lint/cli.ts
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't throw non-Error values

Applied to files:

  • packages/docs/src/lint/cli.ts
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Always use the radix argument when using `parseInt()`

Applied to files:

  • packages/docs/src/lint/cli.ts
📚 Learning: 2025-12-01T15:20:25.102Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/open-graph.mdc:0-0
Timestamp: 2025-12-01T15:20:25.102Z
Learning: Applies to src/pkgs/open-graph/**/*.{ts,tsx} : Use `lineClamp` property to prevent text overflow; test with real content and edge cases

Applied to files:

  • packages/docs/src/remark/libs/content-processor.ts
  • packages/docs/src/remark/libs/text.ts
📚 Learning: 2026-02-19T01:12:44.177Z
Learnt from: CR
Repo: consentdotio/agent-skills PR: 0
File: tsdoc-jsdoc-authoring/AGENTS.md:0-0
Timestamp: 2026-02-19T01:12:44.177Z
Learning: Applies to tsdoc-jsdoc-authoring/**/*.{js,jsx} : Document generator output with `yields` in JSDoc comments

Applied to files:

  • packages/docs/src/remark/libs/content-processor.ts
  • packages/docs/src/remark/libs/text.ts
  • packages/docs/src/llm/llm.ts
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.mdx : Use MDX for documentation pages in the docs app

Applied to files:

  • packages/docs/src/convert/convert.ts
📚 Learning: 2026-03-19T22:57:19.913Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.mdx : Use MDX for documentation pages

Applied to files:

  • packages/docs/src/convert/convert.ts
  • packages/docs/src/remark/libs/text.ts
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.mdx : Include code examples where appropriate in MDX documentation pages of the docs app

Applied to files:

  • packages/docs/src/convert/convert.ts
  • packages/docs/src/remark/libs/text.ts
📚 Learning: 2026-03-19T22:57:19.913Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.mdx : Keep documentation organized by categories

Applied to files:

  • packages/docs/src/convert/convert.ts
  • packages/docs/src/lint/runner.ts
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Document props and usage examples

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/type-table.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/index.{ts,tsx} : Export type definitions for public API in Elements package

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/type-table.tsx
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Document all props and types in Elements package TypeScript code

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/type-table.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Export type definitions for public API

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/type-table.tsx
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Document all props and types

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/type-table.tsx
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Document props and usage examples in Elements package components

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/type-table.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.{tsx,jsx} : Handle focus management

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Use compound components pattern in Elements package (e.g., `Component.Root`, `Component.Child`)

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Handle focus management in Elements package components

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Support keyboard navigation in Elements package components

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{tsx,jsx} : Each component should follow the specified structure using `forwardRef`, proper prop typing, and support for `asChild`, `className`, and HTML attributes

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
📚 Learning: 2026-03-19T22:57:45.114Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:45.114Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Document components with purpose and use case, required and optional props, context requirements, example usage, and common pitfalls

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/type-table.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.{tsx,jsx} : Use compound components pattern (e.g., `Component.Root`, `Component.Child`)

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{tsx,jsx} : For each component, documentation should include: purpose and use case, required and optional props, context requirements, example usage, and common pitfalls

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{tsx,jsx} : Required props should be listed first in component prop definitions

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.{tsx,jsx} : Follow the Radix UI primitives pattern

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use valid, non-abstract ARIA roles for elements with ARIA roles

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't assign non-interactive ARIA roles to interactive HTML elements

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Make sure ARIA properties are valid for the element's supported roles

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2026-02-20T14:21:40.180Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T14:21:40.180Z
Learning: Applies to **/*.{tsx,jsx} : 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 (`<button>`, `<nav>`, etc.) instead of divs with roles

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/type-table.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2026-02-20T14:21:19.208Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-02-20T14:21:19.208Z
Learning: Applies to **/*.{tsx,jsx,html} : Use semantic HTML and ARIA attributes for accessibility - use semantic elements (`<button>`, `<nav>`, etc.) instead of divs with roles

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't assign interactive ARIA roles to non-interactive HTML elements

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Assign `tabIndex` to non-interactive HTML elements with `aria-activedescendant`

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Follow WAI-ARIA guidelines in Elements package components

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2026-03-19T22:57:45.114Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:45.114Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Support keyboard navigation, include proper ARIA attributes, and maintain semantic markup for screen reader support and focus management

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2026-04-16T03:27:04.353Z
Learnt from: CR
Repo: inthhq/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-16T03:27:04.353Z
Learning: Applies to **/*.{jsx,tsx} : 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

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2026-03-19T22:57:45.114Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:45.114Z
Learning: Applies to **/*.{ts,tsx} : Export type definitions for public API and avoid TypeScript enums; use const objects or 'as const' assertions instead due to runtime overhead and tree-shaking limitations

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/type-table.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `as const` instead of literal types and type annotations

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Implement ARIA attributes in Elements package components for accessibility

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Give heading elements content that's accessible to screen readers (not hidden with `aria-hidden`)

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.{tsx,jsx} : Implement ARIA attributes

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{tsx,jsx} : Support screen readers through semantic markup in components

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.{tsx,jsx} : Implement React Context for state management

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2026-03-19T22:57:45.114Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:45.114Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Implement compound components pattern with components namespaced under a single parent (e.g., FormWizard.Root, FormWizard.Step), using React Context for state management

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Implement React Context for state management in Elements package components

Applied to files:

  • packages/docs/src/components/package-command-tabs.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{ts,tsx} : Export type definitions for public API in TypeScript libraries

Applied to files:

  • packages/docs/src/components/type-table.tsx
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx,md} : Include TypeScript examples in Elements package documentation

Applied to files:

  • packages/docs/src/components/type-table.tsx
  • packages/docs/src/lint/runner.ts
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `export type` for types

Applied to files:

  • packages/docs/src/components/type-table.tsx
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{ts,tsx} : Use proper type definitions for all props and refs in TypeScript components

Applied to files:

  • packages/docs/src/components/type-table.tsx
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{tsx,jsx} : Optional props should have clear defaults in component definitions

Applied to files:

  • packages/docs/src/components/type-table.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't use explicit role property that's the same as the implicit/default role

Applied to files:

  • packages/docs/src/components/type-table.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use valid ARIA state and property values

Applied to files:

  • packages/docs/src/components/type-table.tsx
  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't use useless undefined

Applied to files:

  • packages/docs/src/components/type-table.tsx
📚 Learning: 2026-02-19T01:12:44.177Z
Learnt from: CR
Repo: consentdotio/agent-skills PR: 0
File: tsdoc-jsdoc-authoring/AGENTS.md:0-0
Timestamp: 2026-02-19T01:12:44.177Z
Learning: Applies to tsdoc-jsdoc-authoring/**/*.{ts,tsx,js,jsx} : Document defaults with `defaultValue` in TSDoc and JSDoc comments

Applied to files:

  • packages/docs/src/components/type-table.tsx
📚 Learning: 2026-02-20T14:21:40.180Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T14:21:40.180Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use optional chaining (`?.`) and nullish coalescing (`??`) for safer property access

Applied to files:

  • packages/docs/src/components/type-table.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Include a `default` clause in switch statements

Applied to files:

  • packages/docs/src/components/type-table.tsx
📚 Learning: 2026-02-20T14:21:19.208Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-02-20T14:21:19.208Z
Learning: Applies to **/*.{tsx,jsx} : Provide meaningful alt text for images

Applied to files:

  • packages/docs/src/components/type-table.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `import type` for types

Applied to files:

  • packages/docs/src/components/type-table.tsx
📚 Learning: 2026-02-19T01:12:44.177Z
Learnt from: CR
Repo: consentdotio/agent-skills PR: 0
File: tsdoc-jsdoc-authoring/AGENTS.md:0-0
Timestamp: 2026-02-19T01:12:44.177Z
Learning: Applies to tsdoc-jsdoc-authoring/**/*.{js,jsx} : Define reusable shapes with `typedef` and `property` in JSDoc comments

Applied to files:

  • packages/docs/src/components/type-table.tsx
📚 Learning: 2026-03-19T22:57:54.730Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/typography.mdc:0-0
Timestamp: 2026-03-19T22:57:54.730Z
Learning: Applies to **/*.{ts,tsx,css} : Use `text-paragraph-xs` through `text-paragraph-xl` tokens for body text content

Applied to files:

  • packages/docs/src/remark/libs/text.ts
📚 Learning: 2026-02-01T08:57:09.557Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/typography.mdc:0-0
Timestamp: 2026-02-01T08:57:09.557Z
Learning: Applies to **/*.{ts,tsx} : Use `text-paragraph-xs` through `text-paragraph-xl` tokens for body text, `text-label-xs` through `text-label-xl` for UI labels in buttons and form fields, and `text-subheading-*` for uppercase subheadings with wide tracking

Applied to files:

  • packages/docs/src/remark/libs/text.ts
📚 Learning: 2026-02-19T01:12:44.177Z
Learnt from: CR
Repo: consentdotio/agent-skills PR: 0
File: tsdoc-jsdoc-authoring/AGENTS.md:0-0
Timestamp: 2026-02-19T01:12:44.177Z
Learning: Applies to tsdoc-jsdoc-authoring/**/*.{ts,tsx,js,jsx} : Write clear summary sentences in TSDoc and JSDoc comments

Applied to files:

  • packages/docs/src/remark/libs/text.ts
  • packages/docs/src/llm/llm.ts
📚 Learning: 2026-02-19T01:12:44.177Z
Learnt from: CR
Repo: consentdotio/agent-skills PR: 0
File: tsdoc-jsdoc-authoring/AGENTS.md:0-0
Timestamp: 2026-02-19T01:12:44.177Z
Learning: Applies to tsdoc-jsdoc-authoring/**/*.{js,jsx} : Use `module` for module-level docs in JSDoc comments

Applied to files:

  • packages/docs/src/remark/libs/text.ts
  • packages/docs/src/lint/runner.ts
📚 Learning: 2026-03-19T22:57:19.913Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.mdx : Include code examples where appropriate in documentation

Applied to files:

  • packages/docs/src/remark/libs/text.ts
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{tsx,jsx} : Include proper ARIA attributes in components

Applied to files:

  • packages/docs/src/components/tabs.tsx
📚 Learning: 2026-02-20T14:21:19.208Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-02-20T14:21:19.208Z
Learning: Applies to **/*.{tsx,jsx} : Use the `key` prop for elements in iterables (prefer unique IDs over array indices)

Applied to files:

  • packages/docs/src/components/tabs.tsx
📚 Learning: 2026-04-16T03:27:04.353Z
Learnt from: CR
Repo: inthhq/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-16T03:27:04.353Z
Learning: Applies to **/*.{jsx,tsx} : Use the `key` prop for elements in iterables (prefer unique IDs over array indices)

Applied to files:

  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't use positive integers for `tabIndex` property

Applied to files:

  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't assign `tabIndex` to non-interactive HTML elements

Applied to files:

  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't use Array index in keys

Applied to files:

  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Ensure all ARIA properties (`aria-*`) are valid

Applied to files:

  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't use two keys with the same name inside objects

Applied to files:

  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{tsx,jsx} : Use React Context for global state management in components

Applied to files:

  • packages/docs/src/components/tabs.tsx
📚 Learning: 2026-03-19T22:57:45.114Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:45.114Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use React Context for global state management, implement proper state updates, and handle side effects appropriately

Applied to files:

  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/use*.{ts,tsx,js,jsx} : Create context using `createContext` and access it with custom hooks, throwing errors if context is missing

Applied to files:

  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{tsx,jsx} : Maintain focus management in interactive components

Applied to files:

  • packages/docs/src/components/tabs.tsx
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{tsx,jsx} : State should be managed through React Context in compound components

Applied to files:

  • packages/docs/src/components/tabs.tsx
📚 Learning: 2026-02-19T01:12:44.177Z
Learnt from: CR
Repo: consentdotio/agent-skills PR: 0
File: tsdoc-jsdoc-authoring/AGENTS.md:0-0
Timestamp: 2026-02-19T01:12:44.177Z
Learning: Applies to tsdoc-jsdoc-authoring/**/*.{js,jsx} : Write JSDoc summaries with intent in JavaScript files

Applied to files:

  • packages/docs/src/lint/runner.ts
  • packages/docs/src/llm/llm.ts
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Include TSDoc comments for components

Applied to files:

  • packages/docs/src/lint/runner.ts
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Include TSDoc comments for components in Elements package

Applied to files:

  • packages/docs/src/lint/runner.ts
📚 Learning: 2026-02-19T01:12:44.177Z
Learnt from: CR
Repo: consentdotio/agent-skills PR: 0
File: tsdoc-jsdoc-authoring/AGENTS.md:0-0
Timestamp: 2026-02-19T01:12:44.177Z
Learning: Applies to tsdoc-jsdoc-authoring/**/*.{ts,tsx} : Apply release tags consistently in TSDoc comments

Applied to files:

  • packages/docs/src/lint/runner.ts
📚 Learning: 2026-04-16T03:27:04.353Z
Learnt from: CR
Repo: inthhq/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-16T03:27:04.353Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use `try-catch` blocks meaningfully - don't catch errors just to rethrow them

Applied to files:

  • packages/docs/src/lint/runner.ts
📚 Learning: 2026-02-20T14:21:19.208Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-02-20T14:21:19.208Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `try-catch` blocks meaningfully - don't catch errors just to rethrow them

Applied to files:

  • packages/docs/src/lint/runner.ts
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use isNaN() when checking for NaN

Applied to files:

  • apps/docs-smoke/scripts/bench.ts
📚 Learning: 2026-04-16T03:27:04.353Z
Learnt from: CR
Repo: inthhq/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-16T03:27:04.353Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Use meaningful variable names instead of magic numbers - extract constants with descriptive names

Applied to files:

  • apps/docs-smoke/scripts/bench.ts
📚 Learning: 2026-02-20T14:21:19.208Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-02-20T14:21:19.208Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use meaningful variable names instead of magic numbers - extract constants with descriptive names

Applied to files:

  • apps/docs-smoke/scripts/bench.ts
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/app/**/*.{ts,tsx} : Use Next.js App Router conventions in the docs app

Applied to files:

  • packages/docs/src/llm/llm.ts
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Make sure all anchors are valid and navigable

Applied to files:

  • packages/docs/src/llm/llm.ts
📚 Learning: 2026-02-20T14:21:19.208Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-02-20T14:21:19.208Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Validate and sanitize user input

Applied to files:

  • packages/docs/src/llm/llm.ts

Comment thread .github/workflows/release.yml Outdated
Comment thread apps/docs-smoke/scripts/bench.ts
Comment thread packages/docs/src/components/type-table.tsx Outdated
Comment thread packages/docs/src/components/type-table.tsx Outdated
Comment thread packages/docs/src/convert/convert.ts Outdated
Comment thread packages/docs/src/remark/libs/content-processor.ts
Comment thread packages/docs/src/remark/libs/text.ts
Comment thread packages/docs/src/remark/libs/text.ts
Comment thread packages/docs/src/remark/plugins/include.remark.ts
Comment thread packages/docs/src/remark/plugins/include.remark.ts
Real bugs
- include.remark no longer races on replaceTarget: sibling includes in
  the same pass are now processed sequentially, so one .then() can't
  splice its parent out of the tree before another .then() runs
  findContainer (comment on 459-522)
- type-table typeDescriptionLink is validated against a URL allowlist
  (http/https/mailto + root-/path-relative) before being set as href,
  and external links get rel="noopener noreferrer" target="_blank"
- extractBlockquoteContent now emits one "> ..." fragment per
  paragraph-like child so multi-paragraph blockquotes survive

Correctness
- convert.ts deriveOutputPath: startsWith is case-insensitive on win32
  and case-sensitive elsewhere
- include.remark warns to stderr when <include> has no specifier instead
  of silently returning

A11y + cleanups
- type-table header cells get scope="col"
- bench median(): explicit empty-array handler returning 0
- content-processor: use mdast Text type instead of double unknown cast
- normalizeWhitespace: drop redundant TRIM_WHITESPACE replace; trim()
  handles it
- lint/cli.ts: collapse github/json branches via a STDOUT_FORMATS set
- lint/runner.ts: single-pass summary counter instead of two filters

Infra
- pin Bun to 1.3.11 in release.yml and bench.yml
@KayleeWilliams KayleeWilliams merged commit b2a778d into main Apr 18, 2026
2 checks passed
@KayleeWilliams KayleeWilliams deleted the KayleeWilliams/init-docs-package branch April 18, 2026 15:01
KayleeWilliams added a commit that referenced this pull request May 11, 2026
Review surfaced four real bugs in the TOC slug contract; this addresses
the ones that affect users today.

- mdx-components.tsx: textFromChildren returned "" for ReactElement
  children, so headings like `### `meta.json`` got no id while the TOC
  extractor still emitted #meta-json — anchor links silently missed.
  Now recurses through isValidElement(children).props.children.
- mdx-components.tsx: copyHeadingUrl called document.querySelector(hash),
  which throws SyntaxError on digit-leading ids ("## 1. Generate the
  artifacts" → "#1-..."). The throw was swallowed by the async catch so
  scrollIntoView never ran. Switched to document.getElementById.
- styles.css: the new .docs-prose [data-docs-heading-anchor] no-underline
  rule (specificity 0,2,0) was overridden by the existing prose link rule
  (0,4,1), so heading anchors still rendered underlined. Added
  :not([data-docs-heading-anchor]) to the underline selector.
- table-of-contents.tsx: decodeURIComponent on a malformed % sequence in
  the URL hash throws URIError; wrapped in try/catch with a raw-hash
  fallback.
- Updated the documented textFromChildren snippet in
  docs/authoring/components.mdx to match the corrected version (so
  copy-paste users don't inherit the bug).
- Added an e2e smoke test that asserts the inline-code heading id is
  attached AND that clicking a digit-leading heading anchor scrolls
  the heading into view.

Skipped two findings:
- Per-page dedup of duplicate heading slugs: no current docs page has
  duplicate headings (scan returned only matches inside a fenced code
  block in llm.mdx). Fix requires coordinated state in extractor and
  every renderer, materially out of scope here.
- Replacing `as unknown as AgentReadabilityManifest` with `satisfies`:
  the JSON import widens TS literal types (level: number vs 1|2|...|6,
  version: number vs 1), so satisfies fails. The double-cast is the
  pragmatic workaround for that gap; a proper fix needs a typed JSON
  declaration.
KayleeWilliams added a commit that referenced this pull request May 11, 2026
* Add Table of Contents extraction, sidebar, and authoring docs

Ships table-of-contents support so docs sites built on leadtype can
render an "On this page" sidebar. Leadtype owns the contract — slug-
stable heading IDs plus a `toc` array on every page in the navigation
manifest — while the user's app owns the React rendering, matching the
framework-neutral stance documented in docs/authoring/components.mdx.

- leadtype/llm: resolveDocsTableOfContents, extractDocsTableOfContents,
  DocsTableOfContentsItem, plus a `toc: { minLevel, maxLevel }` option
  on resolveDocsNavigation (defaults h2-h3).
- leadtype/llm/readability: slugifyDocsHeading helper (Unicode-safe,
  fs-free) so renderers can match extracted slugs.
- apps/example: TableOfContents reference component, MDX heading wiring
  via slugifyDocsHeading, smoke test covering the slug contract.
- docs/authoring/components.mdx: new "Headings and table of contents"
  section explaining the slug contract and pointing at the reference
  component; docs/reference/llm.mdx trimmed to API-only with a cross-
  link.

* Fix heading-anchor mismatches with inline markup and digit slugs

Review surfaced four real bugs in the TOC slug contract; this addresses
the ones that affect users today.

- mdx-components.tsx: textFromChildren returned "" for ReactElement
  children, so headings like `### `meta.json`` got no id while the TOC
  extractor still emitted #meta-json — anchor links silently missed.
  Now recurses through isValidElement(children).props.children.
- mdx-components.tsx: copyHeadingUrl called document.querySelector(hash),
  which throws SyntaxError on digit-leading ids ("## 1. Generate the
  artifacts" → "#1-..."). The throw was swallowed by the async catch so
  scrollIntoView never ran. Switched to document.getElementById.
- styles.css: the new .docs-prose [data-docs-heading-anchor] no-underline
  rule (specificity 0,2,0) was overridden by the existing prose link rule
  (0,4,1), so heading anchors still rendered underlined. Added
  :not([data-docs-heading-anchor]) to the underline selector.
- table-of-contents.tsx: decodeURIComponent on a malformed % sequence in
  the URL hash throws URIError; wrapped in try/catch with a raw-hash
  fallback.
- Updated the documented textFromChildren snippet in
  docs/authoring/components.mdx to match the corrected version (so
  copy-paste users don't inherit the bug).
- Added an e2e smoke test that asserts the inline-code heading id is
  attached AND that clicking a digit-leading heading anchor scrolls
  the heading into view.

Skipped two findings:
- Per-page dedup of duplicate heading slugs: no current docs page has
  duplicate headings (scan returned only matches inside a fenced code
  block in llm.mdx). Fix requires coordinated state in extractor and
  every renderer, materially out of scope here.
- Replacing `as unknown as AgentReadabilityManifest` with `satisfies`:
  the JSON import widens TS literal types (level: number vs 1|2|...|6,
  version: number vs 1), so satisfies fails. The double-cast is the
  pragmatic workaround for that gap; a proper fix needs a typed JSON
  declaration.

* Narrow root exports and preserve colon/hash in heading titles

Two valid review findings on the TOC PR; three skipped (stale or wrong).

- packages/leadtype/src/index.ts: drop the seven TOC-related exports
  this PR added to the root barrel (DocsTableOfContents{Item,Options,Page},
  ResolveDocsTableOfContentsConfig, extractDocsTableOfContents,
  resolveDocsTableOfContents, slugifyDocsHeading). They remain on the
  dedicated `leadtype/llm` subpath. Keeps the top-level surface minimal
  and consistent with the rest of the package layout. All existing
  consumers (apps/example, tests) already import from `leadtype/llm`
  or `leadtype/llm/readability`, so no internal updates needed.
- packages/leadtype/src/llm/llm.ts: drop `:` and `#` from
  MARKDOWN_INLINE_PATTERN so titles like "API: Quick Start" and "C#"
  survive `cleanHeadingText` for TOC display. Slugs are unchanged
  because `slugifyDocsHeading` strips this punctuation when forming
  the id. Updated the matching test expectation; no real docs page
  currently has `:` or `#` in a heading, so generated artifacts are
  effectively unchanged.

Skipped:
- copyHeadingUrl querySelector→getElementById: already fixed in 541d0fd.
- DocsNavigationPage.toc → optional: the field is always emitted by
  the generators (no internal site constructs the type without it).
  Package is pre-release (0.0.0); making it optional would force
  defensive `?? []` at every consumer with no gain.
- textFromChildren `.join(" ")` → `.join("")`: would break slug parity.
  The extractor's `cleanHeadingText` replaces inline markers with " ",
  so `### Hello*world*` extracts to "Hello world". Joining renderer
  children with "" would produce "Helloworld" and mismatch the slug.

* Use empty separator when flattening heading children

Switch `textFromChildren` array branch from `.join(" ")` to `.join("")`
in both the example renderer and the documented snippet so adjacent
inline elements don't get a synthetic space inserted between them.

Verified slug parity with a probe against `extractDocsTableOfContents`:
for `## Hello *world*` the extractor produces "Hello world" (single
space, after WHITESPACE_PATTERN normalization). With either join
character the renderer's text is normalized by `slugifyDocsHeading`
to the same `hello-world` slug, so realistic headings are unaffected.
Empty-string join is the more honest semantic — MDX text nodes already
carry their own source spacing, the renderer shouldn't add more.

null/undefined/boolean already fall through to the catch-all `return ""`
branch, so no separate handling needed.

E2e smoke test still passes (`heading anchors handle inline markup and
digit-leading slugs`).

* Fix TOC anchor extraction
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant