Skip to content

feat(docs): migrate documentation from MkDocs to Docusaurus v3 - #792

Merged
murdore merged 1 commit into
releasefrom
feat/improve-documentation-styling
Jan 29, 2026
Merged

murdore merged 1 commit into
releasefrom
feat/improve-documentation-styling

Conversation

@murdore

@murdore murdore commented Jan 28, 2026 •

Copy link
Copy Markdown
Contributor

Complete migration of documentation platform from MkDocs to Docusaurus 3.7.0 with enhanced styling, automation, and developer experience improvements.

Major Changes:

  • Replace MkDocs infrastructure (mkdocs.yml, requirements.txt) with Docusaurus
  • Add docs-site/ directory with 98+ new files (React components, CSS, config)
  • Implement automated content sync pipeline (sync-docs.ts) to transform MkDocs markdown to Docusaurus MDX format with frontmatter conversion
  • Create 3 GitHub Actions workflows for deployment, PR validation, and versioning
  • Add 100+ structured documentation files organized by topic
  • Include custom UI components (search, cards, buttons, tables)
  • Establish CSS design system with tokens, utilities, and theme support
  • Add build scripts for LLM context generation and frontmatter validation
  • Reorganize static assets with proper branding and screenshots
  • Exclude generated docs-site/docs/ from version control (.gitignore)

Infrastructure:

  • Algolia search integration
  • PostHog analytics tracking
  • Automated versioning on releases
  • PR validation with TypeScript and frontmatter checks
  • GitHub Pages deployment with caching optimization

Breaking Changes:

Summary by CodeRabbit

  • New Features

    • Launched a new Docusaurus-based documentation site with Algolia search, responsive design, and automated versioning.
    • Added landing page with hero section, features, stats, and provider listings.
    • Implemented mobile search experience and interactive UI components.
    • Configured analytics and privacy-conscious tracking integration.
  • Chores

    • Added CI/CD workflows for documentation deployment, PR validation, and automatic versioning on releases.
    • Set up documentation synchronization from existing format to new site.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Jan 28, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 110 files, which is 10 over the limit of 100.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • 🔍 Trigger a full review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

github-actions Bot commented Jan 28, 2026 •

Copy link
Copy Markdown
Contributor

✅ Single Commit Policy - COMPLIANT

Status: Policy requirements met • 1 commit • Valid format • Ready for merge

📊 View validation details

📝 Commit Details

  • Hash: 496ce27da295a937a9eb49c156d71ff7ed380763
  • Message: feat(docs): migrate documentation from MkDocs to Docusaurus v3
  • Author: Sachin Sharma

✅ Validation Results

  • Single commit requirement met
  • No merge commits in branch
  • Semantic commit message format verified
  • Ready for squash merge to release branch

🤖 Automated validation by NeuroLink Single Commit Enforcement

@github-actions

github-actions Bot commented Jan 28, 2026 •

Copy link
Copy Markdown
Contributor

Documentation Validation Results

🚀 Documentation validation passed!

Check Status Result
Frontmatter Validation ✅ Passed
TypeScript Check ✅ Passed
Build ✅ Passed
Link Validation ✅ Passed

📦 Build artifact uploaded successfully. Ready for deployment preview.

Commit: c0e9483c968a8b2e4ca7245d28201c50c814d8a5 | Workflow: View logs

@github-actions

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
docs/cli-guide.md (1)

1235-1256: Consider documentation completeness for release branch.

Nine video demonstrations are marked as "(Video coming soon)" in this CLI guide. While the placeholder text is consistent and clear, shipping release documentation with multiple "coming soon" items may impact user experience.

Consider one of these approaches:

  • Option 1: Complete the video content before merging to release
  • Option 2: Remove the placeholder entries until videos are ready, then add them in a follow-up PR
  • Option 3: If incremental documentation releases are intentional, add a note at the top of the video section explaining the rollout plan

The added feature list (lines 1251-1256) looks good and provides clear expectations for the upcoming content.

docs/sdk-custom-tools.md (1)

1663-1664: Both new resource links point to the same destination.

The "MCP SDK Integration Proof Tests" and "Real AI-MCP Integration Demo" links both point to development/testing.md. These appear to be either placeholder links or incorrectly configured, as distinct resources should point to different locations or different anchors within the same page.

🔗 Suggested fix: add specific anchors or correct the links

If both resources are in the same file but different sections:

 - [MCP SDK Integration Proof Tests](development/testing.md)
-- [Real AI-MCP Integration Demo](development/testing.md)
+- [Real AI-MCP Integration Demo](development/testing.md#real-ai-mcp-integration-demo)

Or if they should point to different files, update accordingly.

docs/business-value.md (1)

1-2: Add required frontmatter to satisfy frontmatter validation

The validation job reports missing required description fields. This file has no frontmatter, so it will likely fail validation. Please add frontmatter here to unblock the docs pipeline.

✅ Proposed frontmatter
+---
+title: Business Value Guide: Analytics & Evaluation Features
+description: Real-world ROI and business impact of NeuroLink analytics and evaluation features.
+---
+
 # 💰 Business Value Guide: Analytics & Evaluation Features
docs/cookbook/structured-output.md (1)

1-3: Add required frontmatter to satisfy frontmatter validation

This file has no frontmatter, and the validation job reports missing required description fields. Add frontmatter here to avoid failing validate:frontmatter.

✅ Proposed frontmatter
+---
+title: Structured Output with JSON Schema
+description: Generate validated JSON responses using schemas and NeuroLink structured output.
+---
+
 # Structured Output with JSON Schema
neurolink-demo/README.md (1)

5-5: Broken anchor link: [object Object] instead of proper slug.

This appears to be a bug from a build/generation script that serialized a JavaScript object instead of a string. The link will be broken in the rendered markdown.

🔧 Fix the anchor
-- [🚀 Next Steps](#[object Object])
+- [🚀 Next Steps](`#-next-steps`)
docs/demos/videos.md (1)

1-3: Add frontmatter to satisfy validation and metadata requirements.

Frontmatter validation is failing; this page has no frontmatter. Add at least title and description (and sidebar_position if your schema requires it).

🛠️ Suggested frontmatter block
+---
+title: Video Demonstrations
+description: Professional demo videos showcasing NeuroLink CLI, MCP, and business workflows.
+---
+
 # Video Demonstrations
docs/visual-demos.md (1)

1-3: Add required frontmatter (pipeline is failing).

Frontmatter validation reports missing description and duplicate sidebar_position. Please add frontmatter with a description and choose a unique sidebar_position in this directory.

🧾 Example frontmatter
+---
+title: "Visual Demonstrations"
+description: "Screenshots, videos, and demo walkthroughs for NeuroLink."
+sidebar_position: 60 # ensure this is unique in the directory
+---
 # 🎬 Visual Demonstrations
🤖 Fix all issues with AI agents
In @.github/workflows/docs-pr-validation.yml:
- Around line 66-68: The "Validate frontmatter" workflow step (id: frontmatter,
name: Validate frontmatter, run: pnpm run validate:frontmatter) is failing the
entire docs PRs; add continue-on-error: true to that step to allow the job to
complete while surfacing warnings during migration, so modify the step
definition to include continue-on-error: true and remove it later once
frontmatter issues are fixed.

In @.github/workflows/docs-version.yml:
- Around line 89-124: The Create Pull Request step is using ${{
secrets.GITHUB_TOKEN }} which prevents downstream pull_request workflows from
triggering; replace the token input in the peter-evans/create-pull-request@v6
step with a repository secret containing a PAT or GitHub App token (e.g., ${{
secrets.DOCS_PR_PAT }}) that has repo:status and workflow scopes, update any
documentation about the secret creation, and ensure the workflow caller still
uses the same step name "Create Pull Request" and branch/base settings so the PR
is created with that PAT and will trigger docs-pr-validation.

In `@docs-site/.gitignore`:
- Around line 1-4: The validation failure is caused by missing frontmatter
(title, description, sidebar_position) in the markdown files under ./docs;
update the source files or enhance the sync-docs.ts script to inject required
Docusaurus frontmatter when converting MkDocs to Docusaurus format: modify
sync-docs.ts to parse the first heading or metadata to generate a title,
synthesize a short description (or pull from existing MkDocs metadata), and
compute/assign sidebar_position (e.g., from original order or a mapping), then
write the frontmatter block (title, description, sidebar_position) to each
output markdown so validate:frontmatter will pass. Ensure you target files in
./docs/ rather than editing .gitignore.

In `@docs-site/package.json`:
- Around line 24-28: The `@docusaurus/`* dependencies are mismatched
(plugin-client-redirects is ^3.9.2 while core, preset-classic, and theme-common
are ^3.7.0); update package.json so all `@docusaurus` packages use the same
version (choose either ^3.7.0 or ^3.9.2) by editing the entries for
`@docusaurus/plugin-client-redirects`, `@docusaurus/core`,
`@docusaurus/preset-classic`, and `@docusaurus/theme-common` to the chosen version,
then reinstall dependencies (npm/yarn) to regenerate the lockfile.

In `@docs-site/scripts/sync-docs.ts`:
- Around line 416-437: The current frontmatter generation causes missing
descriptions and many duplicate sidebar_position values; modify
generateSidebarPosition to return number | undefined (remove the default 50 and
only keep deterministic mappings for filenames like
index/getting-started/installation/etc.), add an
extractDescriptionFromContent(content: string): string | null helper that strips
code blocks and headings and returns the first non-empty plaintext line, and
change the caller that sets processed.sidebar_position to only assign it when
generateSidebarPosition returns a defined number (otherwise delete
processed.sidebar_position); finally, if processed.description is empty,
populate it from extractDescriptionFromContent(content).
- Around line 368-372: The convertGridCards function currently removes the
entire <div class="grid cards" ...> block (losing inner markdown); change the
replacement to unwrap the container and preserve its inner content by matching
the opening tag and its attributes, capturing the inner HTML/markdown (using a
capturing group like ([\s\S]*?)) and replacing the whole match with the captured
group, or alternatively run two replacements that strip only the opening tag and
the closing </div> for elements whose class attribute contains "grid cards";
update convertGridCards to use that capturing/strip approach so card contents
are retained.

In `@docs-site/scripts/validate-frontmatter.ts`:
- Around line 36-41: The current frontmatter validation constants
(TITLE_MIN_LENGTH, DESCRIPTION_MIN_LENGTH, etc.) are too strict for migrated
content; modify scripts/validate-frontmatter.ts to support a strict mode flag
and default to non-strict behavior: add CLI parsing (e.g., parse process.argv
for "--strict") in validate-frontmatter.ts, change validation logic that
currently throws errors when a check fails to instead issue console.warn or
accumulate warnings unless strict is true, and only treat failures as errors
(throw/exit non-zero) when strict is enabled; update package.json to add two
scripts (validate:frontmatter runs without --strict and
validate:frontmatter:strict runs with --strict) so CI can opt into strict
checking while local/initial migration uses warnings.

In `@docs-site/src/components/CopyPageButton/index.tsx`:
- Around line 11-17: The handleCopy function currently calls
navigator.clipboard.writeText without guarding for availability or failures;
update handleCopy to check for navigator.clipboard (and
navigator.clipboard.writeText) before calling, wrap the writeText call in a
try/catch to handle NotAllowedError or other rejections, and ensure setCopied is
only set true on success (and optionally provide a failure path such as logging
the error or showing a fallback UI); reference the handleCopy function and
navigator.clipboard.writeText and adjust setCopied usage accordingly.

In `@docs-site/src/components/Search/EmptySearch.tsx`:
- Around line 14-19: QUICK_LINKS contains root-relative URLs that won't resolve
with routeBasePath "/docs"; update the entries in the QUICK_LINKS constant so
each url is prefixed with "/docs" (e.g., "/docs/getting-started", "/docs/sdk",
"/docs/cli") and replace the non-existent "/features/mcp-tools-showcase" with
the correct MCP docs path (for example "/docs/mcp/overview" or the actual MCP
doc route used in the site) so all links point to valid /docs routes.

In `@docs-site/src/components/Search/SearchResultItem.tsx`:
- Around line 61-83: In SearchResultItem
(docs-site/src/components/Search/SearchResultItem.tsx) sanitize any HTML before
using dangerouslySetInnerHTML: import DOMPurify and call DOMPurify.sanitize(...)
for breadcrumbParts entries, for title, and for content, allowing only safe
tags/attributes (e.g., keep <mark> for highlights) and then pass the sanitized
strings into dangerouslySetInnerHTML; update the breadcrumb map
(breadcrumbParts), the title assignment (resultTitle), and the content block
(resultContent) to use the sanitized values instead of the raw variables.

In `@docs-site/src/hooks/useAlgoliaSearch.ts`:
- Around line 135-192: The search call currently creates an AbortController but
never passes its signal to clientRef.current.search and relies on
abortControllerRef.current later, allowing stale responses to overwrite newer
state; fix by adding a per-request token: add a requestIdRef (e.g.,
requestIdRef.current++ before the search), capture const localRequestId =
requestIdRef.current, pass abortControllerRef.current.signal into
clientRef.current.search's options, and before any state updates (setResults,
setTotalResults, setSelectedIndex, setError, setIsLoading, and caching via
searchCache.set(trimmedQuery, ...)) verify that localRequestId ===
requestIdRef.current; also make the same increment/invalidating update in the
hook cleanup function so unmounting increments requestIdRef to ignore any
in-flight callbacks.

In `@docs-site/src/theme/prism-neurolink-light.ts`:
- Around line 56-78: The styles array in prism-neurolink-light.ts contains
overlapping token definitions: earlier entries set "keyword"/"selector"/"tag" to
red/purple (the objects with types ["atrule","keyword","attr-name","selector"]
and ["function","deleted","tag"]) but a later object (types
["tag","selector","keyword"]) redefines them to green (`#22863a`) which silently
overrides the earlier rules; fix by removing the duplicate types from the final
object or moving that final object earlier in the styles array so the intended
red/purple rules (the objects above) take precedence—update the types arrays
accordingly to avoid duplicate "keyword"/"selector"/"tag" entries.

In `@docs-site/src/theme/Root.tsx`:
- Around line 10-35: Update the PostHog initialization in posthog.init so
session recording is privacy-safe: set session_recording.maskAllInputs to true
(and provide a non-null maskInputFn or sensible maskTextSelector), set
respect_dnt to true, and only enable session_recording after explicit user
consent gating logic (e.g., check a consent flag before passing
session_recording). Also remove the duplicate initial pageview by deleting or
guarding the pageview call inside the loaded callback (the one referenced as
loaded) so usePageTracking() on mount is the single source of the initial
pageview.

In `@docs-site/static/llms.txt`:
- Around line 5-7: Replace the legacy docs host in the generated output by
updating the generator so the "Full documentation:
https://neurolink.juspay.io/llms-full.txt" link (and any other occurrences such
as the ones around lines referenced in the review) points to the new docs
domain; modify the generator/template that emits the "Generated: ..." block and
the "Full documentation:" URL to use the new host and then re-run the generator
so docs-site/static/llms.txt contains the updated links.

In `@docs/advanced/index.md`:
- Around line 243-245: The "Performance Tuning" link currently points to a
non-existent target '../deployment/configuration.md'; update the markdown link
referenced as "Performance Tuning" in docs/advanced/index.md to either point to
the correct existing documentation file (replace
'../deployment/configuration.md' with the correct path) or create the missing
target file at '../deployment/configuration.md' with the intended content so the
link resolves; ensure the link text "Performance Tuning" and the path are
consistent with your documentation structure.

In `@docs/getting-started/providers/google-vertex.md`:
- Line 957: Update the broken link that currently points to
"/reference/troubleshooting" so it goes directly to the correct troubleshooting
subsection "/troubleshooting#google-gemini-too-many-states-for-serving-error";
locate the anchor text "Troubleshooting Guide" or the URL string
"/reference/troubleshooting" in the
docs/getting-started/providers/google-vertex.md content and replace it with
"/troubleshooting#google-gemini-too-many-states-for-serving-error" so users land
on the "Too many states for serving" section.

In `@docs/guides/migration/index.md`:
- Around line 1-3: Add a YAML frontmatter block at the very top of the file
(above the "# Migration Guides" header) to satisfy validation; include required
fields such as title: "Migration Guides", description: a short summary, slug:
"migration", and any site-specific metadata like sidebar_label or
sidebar_position expected by the pipeline (wrap them between --- and place
before the existing content). Ensure the frontmatter keys match the project's
other docs frontmatter conventions so the validator accepts this page.
🟡 Minor comments (24)
docs-site/src/components/CopyPageButton/index.tsx-20-24 (1)

20-24: Set explicit button type to avoid accidental form submits.

✅ Suggested fix
     <button
       onClick={handleCopy}
       className={styles.button}
       title="Copy page content"
+      type="button"
     >
docs-site/src/components/CopyPageButton/index.tsx-25-52 (1)

25-52: Add accessible text or hide decorative SVGs.
The SVGs need a title or explicit aria metadata to satisfy the a11y lint rule.

✅ Suggested fix
         <svg
           className={styles.icon}
           viewBox="0 0 24 24"
           fill="none"
           stroke="currentColor"
           strokeWidth={2}
+          aria-hidden="true"
+          focusable="false"
         >
+          <title>Copied</title>
           <path
             strokeLinecap="round"
             strokeLinejoin="round"
             d="M5 13l4 4L19 7"
           />
         </svg>
       ) : (
         <svg
           className={styles.icon}
           viewBox="0 0 24 24"
           fill="none"
           stroke="currentColor"
           strokeWidth={2}
+          aria-hidden="true"
+          focusable="false"
         >
+          <title>Copy</title>
           <path
             strokeLinecap="round"
             strokeLinejoin="round"
             d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"
           />
         </svg>
docs/features/structured-output.md-168-168 (1)

168-168: Restore the #generate anchor fragment in the API Reference link.

The generate() section exists in the API reference with a stable anchor ID ({#generate} at line 132), but removing the fragment from the link breaks deep-linking. Users will land at the top of the API reference page instead of jumping directly to the generate() section. Update the link to:

[API Reference](../sdk/api-reference.md#generate)
docs/sdk-custom-tools.md-1659-1659 (1)

1659-1659: Restore the anchor fragment to the API Reference link.

The link to sdk/api-reference.md has the anchor #neurolink-class removed. The NeuroLink Class section exists in the API reference (line 5), so removing the anchor causes users to land at the page top instead of the intended section, degrading navigation.

docs-site/src/components/icons/ArrowIcon.tsx-8-24 (1)

8-24: Fix rotate() values to include angle units.

rotate(-90), rotate(90), and rotate(180) require explicit angle units (deg, rad, grad, or turn) to be applied. Without units, these values are ignored by the browser. Note: rotate(0) is valid without units, but adding units is harmless.

Suggested fix
-    up: "rotate(-90)",
-    down: "rotate(90)",
-    left: "rotate(180)",
+    up: "rotate(-90deg)",
+    down: "rotate(90deg)",
+    left: "rotate(180deg)",
docs/reference/analytics.md-182-186 (1)

182-186: Update documentation examples to use correct import paths or remove analyticsUtils references.

The import path @juspay/neurolink/utils/analyticsUtils is not a valid export. The analyticsUtils module (containing formatTokenUsage, calculateTokenCost, and combineTokenUsage) is not exposed through the package's public API. The package exports only:

  • . (main entry point)
  • ./types
  • ./cli
  • ./package.json

Either remove the analyticsUtils examples from the documentation, or add a ./utils subpath export to package.json if these utilities are intended to be public.

docs/demos/index.md-174-177 (1)

174-177: Update placeholder path to match actual asset location.

The example shows path/to/cli-demo.png which is a generic placeholder. This should either be updated to the actual path (../assets/images/cli-demo.png) or removed if the image doesn't exist yet.

📝 Proposed fix
 ### Documentation Embedding

 ```markdown
-![NeuroLink CLI Demo](path/to/cli-demo.png)
+![NeuroLink CLI Demo](../assets/images/cli-help-overview.png)
 _NeuroLink CLI with provider status and text generation_
</details>

</blockquote></details>
<details>
<summary>docs/demos/screenshots.md-9-12 (1)</summary><blockquote>

`9-12`: **Create a tracking mechanism for 35 pending screenshot assets in documentation.**

The repository contains 35 placeholder comments across `docs/demos/` (27 in `screenshots.md`, 8 in `index.md`), with no dedicated issue or checklist to track their completion. While a `todos/` directory exists for code refactoring, documentation asset completion is not tracked. Establish explicit tracking (GitHub issue, checklist, or milestone) to ensure these assets are captured before the documentation is published.

</blockquote></details>
<details>
<summary>docs-site/static/llms.txt-147-152 (1)</summary><blockquote>

`147-152`: **License badge URL is truncated.**

The MIT badge link ends at `https://opensource.` which renders a broken link. Please regenerate the llms.txt output or restore the full license URL in the source.

</blockquote></details>
<details>
<summary>docs/visual-demos.md-156-160 (1)</summary><blockquote>

`156-160`: **Capitalize GitHub in MCP table.**

Use the official capitalization for the platform name.

<details>
<summary>✏️ Suggested edit</summary>

```diff
-| **Server Installation**  | ![Install Server](/docs/visual-content/screenshots/mcp-cli/02-mcp-install-2025-06-10.png)      | Installing external MCP servers (filesystem, github, etc.) |
+| **Server Installation**  | ![Install Server](/docs/visual-content/screenshots/mcp-cli/02-mcp-install-2025-06-10.png)      | Installing external MCP servers (filesystem, GitHub, etc.) |
docs-site/src/theme/Root.tsx-38-76 (1)

38-76: Initial pageview is captured twice.

The loaded callback in posthog.init() (line 38-51) captures a pageview on initialization, and the usePageTracking hook's effect runs on mount (line 59-76) with the same location, capturing a second event. Add a first-run guard to prevent the duplicate:

🧭 Suggested guard
-import React, { useEffect } from "react";
+import React, { useEffect, useRef } from "react";
@@
 function usePageTracking() {
   const location = useLocation();
+  const isFirstRef = useRef(true);

   useEffect(() => {
     if (typeof window === "undefined" || !posthog.__loaded) return;
+    if (isFirstRef.current) {
+      isFirstRef.current = false;
+      return;
+    }
@@
   }, [location.pathname, location.search, location.hash]);
 }
docs-site/src/components/GithubLink/index.tsx-18-26 (1)

18-26: Add accessible text for the SVG icon.

The SVG is missing an accessible name, which triggers the a11y lint and can be read ambiguously by assistive tech. Add a title (and optional aria-label) to the SVG.

Proposed fix
-      <svg className={styles.icon} viewBox="0 0 24 24" fill="currentColor">
+      <svg
+        className={styles.icon}
+        viewBox="0 0 24 24"
+        fill="currentColor"
+        role="img"
+        aria-label="GitHub"
+      >
+        <title>GitHub</title>
         <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
       </svg>
docs-site/src/components/Search/SearchInput.tsx-42-43 (1)

42-43: Fix invalid aria-label on <div> element.

The aria-label attribute is not supported on a plain <div> element. For the loading spinner to be accessible, add role="status" to make the ARIA attribute valid.

🛠️ Proposed fix
-        {isLoading ? (
-          <div className={styles.loadingSpinner} aria-label="Loading" />
+        {isLoading ? (
+          <div className={styles.loadingSpinner} role="status" aria-label="Loading" />
docs-site/src/css/custom.css-81-89 (1)

81-89: The CSS color property has no effect on <img> elements.

Setting color on an <img> element won't change the image appearance. If the logo is a raster image (PNG/JPG), this rule is ineffective. If it's an SVG using currentColor, the SVG must be inlined or loaded via a component that passes the color as a fill/stroke property.

Consider using Docusaurus's themed image component or providing separate light/dark logo files instead.

docs-site/src/css/fonts.css-44-49 (1)

44-49: The font-display: swap rule on html is ineffective.

font-display is a @font-face descriptor, not a CSS property that can be applied to elements. This rule has no effect. The Google Fonts URLs already include display=swap parameter, so font swapping is already enabled correctly.

🧹 Suggested removal
-/* ---- Font Loading Optimization ---- */
-
-/* Prevent layout shift during font loading */
-html {
-  font-display: swap;
-}
-
 /* ---- Font Size Scale ---- */
docs-site/src/pages/index.tsx-8-147 (1)

8-147: Add accessibility attributes to SVG icons.

All inline SVG icons are flagged by static analysis for missing accessibility attributes. Since these icons are decorative (used alongside text labels), they should be hidden from assistive technologies using aria-hidden="true".

For icons that convey meaning independently (like ArrowRightIcon in buttons), consider adding role="img" and aria-label.

♿ Example fix for decorative icons
 const ProviderIcon = () => (
   <svg
     viewBox="0 0 24 24"
     fill="none"
     stroke="currentColor"
     strokeWidth="1.5"
     strokeLinecap="round"
     strokeLinejoin="round"
+    aria-hidden="true"
   >

For the ArrowRightIcon used in CTA buttons:

 const ArrowRightIcon = () => (
   <svg
     viewBox="0 0 24 24"
     fill="none"
     stroke="currentColor"
     strokeWidth="2"
     strokeLinecap="round"
     strokeLinejoin="round"
     className={styles.arrowIcon}
+    aria-hidden="true"
   >
docs-site/src/css/custom.css-91-110 (1)

91-110: These CSS rules appear unused and target non-existent elements.

The selectors .themedComponent--light_ElNy and .themedComponent_rrg5 contain auto-generated hashes that don't match any rendered components in the codebase—no components apply these class names. The comment "Also handle themed components if Docusaurus uses them" suggests this is speculative defensive code.

Either remove these rules or, if they're truly needed for Docusaurus-generated content, use stable selectors instead:

[data-theme="dark"] [class*="themedComponent--light"] {
  display: none !important;
}
docs-site/src/components/Search/SearchModal.tsx-73-82 (1)

73-82: Guard selection updates when there are no results.

Line 75-82: results.length - 1 becomes -1 when empty, so ArrowDown can set selectedIndex to -1. Consider short-circuiting when there are no results to keep selection non-negative.

🛠️ Suggested fix
         case "ArrowDown":
+          if (results.length === 0) return;
           e.preventDefault();
           setSelectedIndex(Math.min(selectedIndex + 1, results.length - 1));
           break;
         case "ArrowUp":
+          if (results.length === 0) return;
           e.preventDefault();
           setSelectedIndex(Math.max(selectedIndex - 1, 0));
           break;
docs-site/src/components/ProviderModelsTable/index.tsx-32-34 (1)

32-34: Use a stable unique key for rows.

Line 32-34: key={model.name} can collide if different providers share model names. Use a composite key to keep React reconciliation stable.

🔧 Suggested fix
-            <tr key={model.name}>
+            <tr key={`${model.provider}:${model.name}`}>
docs-site/src/components/Search/EmptySearch.tsx-45-70 (1)

45-70: Use semantic buttons for recent-search items.

Line 45-70: div role="button" triggers a11y lint and misses native button semantics. Prefer a real <button> for the selectable item and keep the remove button as a sibling to avoid nested interactive elements.

🛠️ Suggested structure (CSS updates needed)
-              <li key={query}>
-                <div
-                  className={styles.recentSearchItem}
-                  onClick={() => onRecentSearchClick(query)}
-                  onKeyDown={(e) => {
-                    if (e.key === "Enter" || e.key === " ") {
-                      e.preventDefault();
-                      onRecentSearchClick(query);
-                    }
-                  }}
-                  role="button"
-                  tabIndex={0}
-                >
-                  <SearchIcon className={styles.recentSearchIcon} />
-                  <span className={styles.recentSearchText}>{query}</span>
-                  <button
-                    type="button"
-                    className={styles.recentSearchRemove}
-                    onClick={(e) => {
-                      e.stopPropagation();
-                      onRemoveRecentSearch(query);
-                    }}
-                    aria-label={`Remove "${query}" from recent searches`}
-                  >
-                    <CloseIcon className={styles.recentSearchRemoveIcon} />
-                  </button>
-                </div>
-              </li>
+              <li key={query} className={styles.recentSearchItem}>
+                <button
+                  type="button"
+                  className={styles.recentSearchButton}
+                  onClick={() => onRecentSearchClick(query)}
+                >
+                  <SearchIcon className={styles.recentSearchIcon} />
+                  <span className={styles.recentSearchText}>{query}</span>
+                </button>
+                <button
+                  type="button"
+                  className={styles.recentSearchRemove}
+                  onClick={(e) => {
+                    e.stopPropagation();
+                    onRemoveRecentSearch(query);
+                  }}
+                  aria-label={`Remove "${query}" from recent searches`}
+                >
+                  <CloseIcon className={styles.recentSearchRemoveIcon} />
+                </button>
+              </li>
docs-site/src/components/Search/SearchResults.tsx-139-150 (1)

139-150: A11y: static wrapper has pointer interaction.

The wrapper <div> uses onMouseEnter (line 149), which violates noStaticElementInteractions. Move the hover handler to the SearchResultItem anchor by adding an onMouseEnter prop that calls handleMouseEnter(item.flatIndex).

docs-site/src/components/Button/index.tsx-31-36 (1)

31-36: Add explicit type="button" default to prevent accidental form submissions.

HTML button elements default to type="submit", which can trigger unintended form submissions if this component is used inside forms. This risk is especially relevant for a reusable component. Other button implementations in the codebase (SearchInput, EmptySearch, SearchWrapperMobile) already explicitly set type="button", establishing a consistent pattern.

Suggested fix
-export function Button({ className, variant, size, ...props }: ButtonProps) {
+export function Button({ className, variant, size, type = "button", ...props }: ButtonProps) {
   return (
     <button
+      type={type}
       className={buttonVariants({ variant, size, className })}
       {...props}
     />
   );
 }
docs-site/src/components/SearchWrapperMobile.tsx-195-197 (1)

195-197: Add role="status" for accessible loading indicator.

A plain <div> with only aria-label lacks semantic role for assistive technology. Use role="status" to properly announce the loading state (it includes implicit aria-live="polite" and aria-atomic="true").

For best accessibility, include visible/accessible text content (e.g., "Loading results…") instead of relying solely on aria-label. The suggested fix below is a valid improvement:

✅ Suggested fix
-              {isLoading && (
-                <div className={styles.loadingSpinner} aria-label="Loading" />
-              )}
+              {isLoading && (
+                <div
+                  className={styles.loadingSpinner}
+                  role="status"
+                  aria-label="Loading"
+                />
+              )}
docs-site/src/css/utilities.css-654-698 (1)

654-698: Fix .show-tablet-only so it doesn’t render on mobile.

Right now it only hides on desktop (Line 694), so it still shows on mobile. If the intent is “tablet only,” hide it at the mobile breakpoint too.

✅ Suggested fix
@@
 `@media` (max-width: 576px) {
@@
   /* Hide on mobile */
   .hide-mobile {
     display: none;
   }
+
+  /* Hide tablet-only on mobile */
+  .show-tablet-only {
+    display: none;
+  }
 }
🧹 Nitpick comments (27)
docs-site/src/components/YouTube/index.tsx (1)

9-18: Consider privacy/perf hardening for embeds (nocookie + lazy + encode).
If docs should minimize tracking and improve load time, switch to the youtube-nocookie.com domain, encode the id, and lazy-load the iframe.

♻️ Suggested update
 export function YouTube({ id, title = "YouTube video" }: YouTubeProps) {
   return (
     <div className={styles.wrapper}>
       <iframe
         className={styles.iframe}
-        src={`https://www.youtube.com/embed/${id}`}
+        src={`https://www.youtube-nocookie.com/embed/${encodeURIComponent(id)}`}
         title={title}
         allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
         allowFullScreen
+        loading="lazy"
       />
     </div>
   );
 }
docs/cookbook/streaming-with-retry.md (1)

277-277: Consider adding an anchor to link directly to the streaming method.

The target file docs/sdk/api-reference.md exists and documents the stream(options) method under the Core Methods section. Adding an anchor like ../sdk/api-reference.md#stream would allow users to jump directly to the relevant streaming API instead of landing at the top of the page.

docs-site/src/components/PropertiesTable/PropertiesTable.module.css (2)

114-116: Hardcoded color breaks theming consistency.

Line 81 uses var(--neurolink-accent-subtle) for the .type background, but the dark mode override uses a hardcoded rgba(59, 130, 246, 0.15). This inconsistency makes future theme updates error-prone and harder to maintain.

♻️ Proposed fix to use CSS variable
 [data-theme="dark"] .type {
-  background: rgba(59, 130, 246, 0.15) !important;
+  background: var(--neurolink-accent-subtle) !important;
 }

If the dark mode value needs to differ from the variable, consider defining a dark mode variant of --neurolink-accent-subtle in tokens.css instead.


98-108: Remove redundant dark mode selectors (lines 98-108).

The [data-theme="dark"] rules for .table thead, .table tbody tr:nth-child(even), and .table tbody tr:hover reference the same CSS variables as their light mode counterparts. Since --neurolink-surface-1, --neurolink-surface-2, and --neurolink-surface-3 are already theme-aware (defined with different values under [data-theme="dark"] in tokens.css), these overrides are unnecessary. Remove them to reduce duplication.

Note: Keep the [data-theme="dark"] .table code rule (line 110–112) since it uses --neurolink-surface-4 instead of the light mode's --neurolink-surface-3.

docs-site/src/components/CopyPageButton/CopyPageButton.module.css (1)

39-44: Use a data-attribute for copied state instead of relying on SVG path detection.

The current CSS selector .button:has(.icon path[d*="M5 13"]) couples the styling to the SVG implementation and is fragile—if the SVG path changes, the CSS breaks. A data-copied attribute makes the state explicit and maintainable. The component already tracks copied state, so this is a straightforward refactor:

♻️ Suggested change

CSS:

-.button:has(.icon path[d*="M5 13"]) {
+.button[data-copied="true"] {
   color: var(--neurolink-success-text);
   border-color: var(--neurolink-success-border);
   background: var(--neurolink-success-bg);
 }

JSX:

 <button
   onClick={handleCopy}
   className={styles.button}
   title="Copy page content"
+  data-copied={copied}
 >
docs/guides/index.md (1)

38-108: Stabilize the “Migration Guides” anchor with an explicit heading ID.

#-migration-guides is fragile across slugging rules. Prefer an explicit ID to keep links stable.

Proposed fix
-## 🔄 Migration Guides
+## 🔄 Migration Guides {`#migration-guides`}
...
-- **Migrating from another framework?** See our [Migration Guides](`#-migration-guides`)
+- **Migrating from another framework?** See our [Migration Guides](`#migration-guides`)
docs-site/src/components/ui/Kbd.tsx (1)

9-11: Consider trimming className to avoid trailing whitespace.

The current template literal produces a trailing space when className is undefined. While functionally harmless, it's cleaner to avoid extra whitespace in the DOM.

♻️ Cleaner className handling
 export function Kbd({ children, className }: KbdProps) {
-  return <kbd className={`${styles.kbd} ${className || ""}`}>{children}</kbd>;
+  return <kbd className={`${styles.kbd}${className ? ` ${className}` : ""}`}>{children}</kbd>;
 }

Alternatively, consider using a utility like clsx if the project already has it as a dependency.

docs-site/src/components/icons/ReturnIcon.tsx (2)

1-1: Unused React import.

With React 17+ and the new JSX transform (which Docusaurus uses), this import is unnecessary since React isn't directly referenced in the component.

♻️ Remove unused import
-import React from "react";
-
 interface ReturnIconProps {

9-14: Consider adding default width/height for predictable sizing.

The SVG lacks explicit width and height attributes. Without them, the icon's size depends entirely on CSS, which could cause unexpected rendering if the parent doesn't constrain dimensions.

♻️ Add default dimensions
     <svg
       className={className}
+      width={24}
+      height={24}
       fill="none"
       viewBox="0 0 24 24"
       stroke="currentColor"
       aria-hidden="true"
     >
docs-site/src/theme/prism-neurolink-dark.ts (2)

38-66: Duplicate token type definitions for inserted and deleted.

The token types inserted and deleted are defined twice with different styles:

  • Lines 49 and 62: inserted → blue (#60a5fa), deleted → purple (#c084fc)
  • Lines 105-110 and 112-117: inserted → green (#4ade80), deleted → red (#f87171)

In Prism themes, later style definitions override earlier ones for the same token type. The diff highlighting section (lines 104-117) will take precedence, making the earlier definitions dead code.

Consider removing inserted from line 49 and deleted from line 62 to avoid confusion and ensure intentional styling.

Suggested fix
     {
       types: [
         "entity",
         "url",
         "symbol",
         "number",
         "boolean",
         "variable",
         "constant",
         "property",
         "regex",
-        "inserted",
       ],
       style: {
         color: "#60a5fa", // Blue (NeuroLink accent)
       },
     },
     {
       types: ["atrule", "keyword", "attr-name", "selector"],
       style: {
         color: "#f87171", // Red for keywords
       },
     },
     {
-      types: ["function", "deleted", "tag"],
+      types: ["function", "tag"],
       style: {
         color: "#c084fc", // Purple for functions
       },
     },

Also applies to: 104-117


73-78: Additional duplicate token definitions for tag, selector, keyword.

These token types are already defined at lines 56-60 with red color (#f87171), but are redefined here with purple (#c678dd). The purple styling will override the red.

If the purple color is intentional for these tokens, remove them from the earlier definition at line 56.

docs-site/src/components/Search/Search.module.css (1)

246-264: Consider adding focus styles for keyboard navigation.

The result items have hover and selected states, but keyboard users navigating through results would benefit from explicit :focus or :focus-visible styles on .resultItem for better accessibility.

Suggested addition
.resultItem:focus-visible {
  outline: 2px solid var(--neurolink-accent);
  outline-offset: -2px;
}
docs/reference/provider-selection.md (1)

8-9: Consider updating version reference.

The "NeuroLink Version: 8.26.1+" is outdated compared to the current version 8.40.1 in package.json. While the + suffix makes it technically accurate, updating to the current version would better reflect the documentation's relevance.

.github/workflows/docs-deploy.yml (1)

24-87: Consider adding a job-level timeout.

The build job lacks timeout-minutes, which could lead to hung workflows if the build process stalls (e.g., infinite loop in a script, network issues during dependency installation).

⏱️ Proposed fix to add timeout
 jobs:
   build:
     name: Build Documentation
     runs-on: ubuntu-latest
+    timeout-minutes: 15
     defaults:
       run:
         working-directory: docs-site
docs/demos/screenshots.md (1)

534-551: Validation script has a glob pattern issue.

The bash glob pattern **/*.{png,jpg,webp} won't work as expected without shopt -s globstar being enabled first in bash. Also, the regex pattern may not match all valid filenames (e.g., filenames with numbers in feature/context parts).

🔧 Proposed fix for the validation script
 #!/bin/bash
 # validate-screenshot-names.sh
 # Validates screenshot naming convention compliance
+shopt -s globstar nullglob

 for file in docs/assets/images/**/*.{png,jpg,webp}; do
   filename=$(basename "$file")

   # Check naming pattern
-  if [[ ! $filename =~ ^[a-z]+-[a-z]+-[a-z]+(-[a-z0-9]+)?\.(png|jpg|webp)$ ]]; then
+  if [[ ! $filename =~ ^[a-z]+-[a-z0-9]+-[a-z0-9]+(-[a-z0-9]+)?\.(png|jpg|webp)$ ]]; then
     echo "❌ Invalid naming: $filename"
     echo "   Expected: category-feature-context[-variant].extension"
   else
     echo "✅ Valid naming: $filename"
   fi
 done
docs-site/scripts/validate-frontmatter.ts (2)

172-194: Consider making sidebar_position optional for index files.

Category index files (like index.md) often don't need a sidebar_position as they represent the category itself. The current validation treats it as required for all files, which may cause unnecessary errors.

🔧 Proposed fix to make sidebar_position optional for index files
   // sidebar_position validation (required, number >= 0)
-  if (frontmatter.sidebar_position === undefined) {
+  const isIndexFile = relativePath.endsWith('index.md') || relativePath.endsWith('index.mdx');
+  if (frontmatter.sidebar_position === undefined && !isIndexFile) {
     issues.push({
       level: "error",
       file: relativePath,
       message: "Missing required field: sidebar_position",
       field: "sidebar_position",
     });

74-94: Simplify glob pattern matching.

The current pattern matching logic handles ** patterns manually but may not cover all edge cases. Consider using the minimatch library (already available via glob dependency) for more robust pattern matching.

.github/workflows/docs-pr-validation.yml (1)

18-29: Consider adding timeout to the validate job.

The validation job performs multiple operations including a full build. Adding a timeout prevents hung workflows.

⏱️ Proposed fix
   validate:
     name: Validate Documentation
     runs-on: ubuntu-latest
+    timeout-minutes: 20
     defaults:
docs-site/package.json (1)

11-12: Remove duplicate script entry.

Both build:llms and build:llms-txt run the identical command. Consider removing one to avoid confusion.

🧹 Proposed fix
     "sync-docs": "tsx scripts/sync-docs.ts",
-    "build:llms": "tsx scripts/build-llms-txt.ts",
     "build:llms-txt": "tsx scripts/build-llms-txt.ts",
docs-site/scripts/build-llms-txt.ts (1)

277-298: Remove unused files parameter or add dynamic extraction.

The files parameter is accepted but never used. The function returns a hardcoded provider list regardless of input.

♻️ Suggested fix

Either remove the unused parameter:

-function extractProviders(files: DocFile[]): ProviderInfo[] {
+function extractProviders(): ProviderInfo[] {

Or add a TODO if dynamic extraction is planned:

 function extractProviders(files: DocFile[]): ProviderInfo[] {
+  // TODO: Extract providers dynamically from documentation files
   const providers: ProviderInfo[] = [
docs-site/src/components/ProviderModelsTable/ProviderModelsTable.module.css (1)

112-122: Consider removing redundant dark mode overrides.

These dark mode rules apply the same CSS variable values as their light mode counterparts (lines 23-25, 48-50, 52-54). If --neurolink-surface-* variables already adapt based on theme (the standard pattern for CSS custom properties), these overrides are unnecessary duplication.

Only the code background rule (lines 124-126) specifies a different surface level (surface-4 vs surface-3).

♻️ Suggested cleanup
 /* ===========================================
    DARK MODE
    =========================================== */

-[data-theme="dark"] .table thead {
-  background: var(--neurolink-surface-2);
-}
-
-[data-theme="dark"] .table tbody tr:nth-child(even) {
-  background: var(--neurolink-surface-1);
-}
-
-[data-theme="dark"] .table tbody tr:hover {
-  background: var(--neurolink-surface-3);
-}
-
 [data-theme="dark"] .table code {
   background: var(--neurolink-surface-4);
 }
docs-site/src/pages/index.module.css (1)

118-165: Add explicit :focus-visible styling for CTA links.

The CTA links remove default link styling and rely on hover effects; without an explicit focus-visible state, keyboard users may not get a clear focus indicator if global styles don’t cover these classes. Consider adding a focused outline to match the design.

Proposed CSS
 .ctaPrimary:hover {
   background: var(--neurolink-accent-dark);
   color: white;
   text-decoration: none;
   transform: translateY(-2px);
   box-shadow: 0 8px 20px hsla(217, 91%, 60%, 0.3);
 }
+
+.ctaPrimary:focus-visible {
+  outline: 2px solid var(--neurolink-accent);
+  outline-offset: 2px;
+}
 
 .ctaSecondary {
   background: var(--neurolink-surface-2);
   color: var(--neurolink-text-primary);
   border: 1px solid var(--border);
 }
 
 .ctaSecondary:hover {
   background: var(--neurolink-surface-3);
   color: var(--neurolink-text-primary);
   text-decoration: none;
   border-color: var(--border-secondary);
 }
+
+.ctaSecondary:focus-visible {
+  outline: 2px solid var(--border-secondary);
+  outline-offset: 2px;
+}
docs-site/src/components/Search/SearchInput.tsx (1)

20-22: Consider making autoFocus default to false for better accessibility.

The autoFocus attribute defaults to true, which can disrupt screen reader users and keyboard navigation by unexpectedly moving focus. Docusaurus search modals typically manage focus programmatically when opened. Consider defaulting to false and letting the parent component explicitly opt-in when appropriate.

♻️ Suggested change
-      autoFocus = true,
+      autoFocus = false,

Also applies to: 36-36

docs-site/src/css/base.css (1)

11-14: The ::-moz-selection pseudo-element is redundant.

Modern Firefox (version 62+, released 2018) supports the standard ::selection pseudo-element. The -moz- prefixed version is no longer needed and can be removed to reduce redundancy.

🧹 Suggested removal
 ::selection {
   background-color: hsla(217, 91%, 60%, 0.3);
   color: inherit;
 }
-
-::-moz-selection {
-  background-color: hsla(217, 91%, 60%, 0.3);
-  color: inherit;
-}
docs-site/src/css/fonts.css (1)

5-9: Consider performance implications of @import for fonts.

Using CSS @import for Google Fonts can delay font loading since the browser must first fetch and parse this CSS file before discovering the font URLs. For optimal performance, fonts are typically loaded via <link rel="preconnect"> and <link> tags in the HTML <head>.

However, this is acceptable for a documentation site where initial load performance is less critical than an application.

docs-site/src/pages/index.tsx (1)

370-371: Hardcoded model version will become stale.

The code example uses "claude-sonnet-4-20250514" which includes a specific date. Consider using a more generic model identifier or adding a comment to update this periodically.

docs-site/src/css/utilities.css (1)

199-371: Respect reduced-motion preferences for animations/transitions.

Consider disabling animation/transition utilities when users prefer reduced motion.

♿ Suggested reduced-motion override
@@
 .transition-none {
   transition: none;
 }
+
+@media (prefers-reduced-motion: reduce) {
+  [data-state="open"][data-side] {
+    animation: none;
+  }
+  .animate-fade-in,
+  .animate-fade-out,
+  .animate-scale-in,
+  .animate-scale-out,
+  .animate-spin,
+  .animate-pulse {
+    animation: none;
+  }
+  .transition-fast,
+  .transition-normal,
+  .transition-slow,
+  .transition-colors {
+    transition: none;
+  }
+}

Comment on lines +66 to +68
- name: Validate frontmatter
id: frontmatter
run: pnpm run validate:frontmatter

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

Frontmatter validation is blocking PRs.

This step is causing the pipeline failure (271 files with errors). Without continue-on-error: true, no documentation PRs can be merged until all frontmatter issues are resolved. Consider either:

  1. Adding continue-on-error: true temporarily during migration
  2. Fixing all 271 files before merging this PR
  3. Adjusting the validation script to be less strict initially
🔧 Option 1: Allow validation to pass with warnings during migration
       - name: Validate frontmatter
         id: frontmatter
+        continue-on-error: true
         run: pnpm run validate:frontmatter

Note: Remove continue-on-error once all frontmatter issues are resolved.

📝 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
- name: Validate frontmatter
id: frontmatter
run: pnpm run validate:frontmatter
- name: Validate frontmatter
id: frontmatter
continue-on-error: true
run: pnpm run validate:frontmatter
🤖 Prompt for AI Agents
In @.github/workflows/docs-pr-validation.yml around lines 66 - 68, The "Validate
frontmatter" workflow step (id: frontmatter, name: Validate frontmatter, run:
pnpm run validate:frontmatter) is failing the entire docs PRs; add
continue-on-error: true to that step to allow the job to complete while
surfacing warnings during migration, so modify the step definition to include
continue-on-error: true and remove it later once frontmatter issues are fixed.

Comment on lines +89 to +124
- name: Create Pull Request
if: steps.check.outputs.exists != 'true'
uses: peter-evans/create-pull-request@v6
with:
token: ${{ secrets.GITHUB_TOKEN }}
commit-message: "docs: add documentation version ${{ steps.version.outputs.version }}"
title: "docs: add documentation version ${{ steps.version.outputs.version }}"
body: |
## Documentation Version Update

This PR adds versioned documentation for release **${{ github.event.release.tag_name }}**.

### Changes
- Created versioned docs in `versioned_docs/version-${{ steps.version.outputs.version }}/`
- Created versioned sidebar in `versioned_sidebars/version-${{ steps.version.outputs.version }}-sidebars.json`
- Updated `versions.json`

### Release Information
- **Tag**: ${{ github.event.release.tag_name }}
- **Documentation Version**: ${{ steps.version.outputs.version }}
- **Release URL**: ${{ github.event.release.html_url }}

---

*This PR was automatically created by the Documentation Versioning workflow.*
branch: docs/version-${{ steps.version.outputs.version }}
base: release
add-paths: |
docs-site/versioned_docs/
docs-site/versioned_sidebars/
docs-site/versions.json
labels: |
documentation
automated
reviewers: |
${{ github.event.release.author.login }}

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

🧩 Analysis chain

🌐 Web query:

GITHUB_TOKEN pull_request workflow trigger GitHub Actions 2024

💡 Result:

  • Why an on: pull_request workflow may not run when the PR is created/updated by a workflow using GITHUB_TOKEN: GitHub blocks most events generated with the repository’s GITHUB_TOKEN from creating new workflow runs (to prevent accidental recursion). The documented exceptions are only workflow_dispatch and repository_dispatch. So a PR opened via API/CLI in a workflow authenticated with GITHUB_TOKEN typically won’t trigger other pull_request/push-based workflows. [1]

  • Fork PRs (and Dependabot PRs) are additionally restricted: for pull_request workflows coming from forks, GITHUB_TOKEN is read-only and secrets aren’t provided (Dependabot PRs are treated like forks). [2][3]

  • Token permissions changed for newer orgs/enterprises: defaults can be read-only depending on when the org/enterprise was created, and you may need to explicitly grant contents: write, pull-requests: write, etc., via permissions: (this affects what the workflow can do, even when it does run). [4]

  • Common workaround (recommended in practice): create the PR (or do the write action that should trigger checks) using a PAT or a GitHub App installation token instead of GITHUB_TOKEN, so the resulting PR/push event can trigger workflows. [1][5]

Sources: GitHub Docs on triggering workflows and GITHUB_TOKEN recursion prevention [1]; GitHub Docs on fork PR workflow restrictions and read-only GITHUB_TOKEN [2]; GitHub Changelog on Dependabot PRs being treated like forks [3]; GitHub Docs on default GITHUB_TOKEN permissions policies [4]; peter-evans/create-pull-request docs noting that PRs created with default GITHUB_TOKEN won’t trigger on: pull_request/on: push workflows and suggesting other tokens [5].


🏁 Script executed:

find .github/workflows -name "*.yml" -o -name "*.yaml" | head -20

Repository: juspay/neurolink

Length of output: 348


🏁 Script executed:

grep -r "on:.*pull_request" .github/workflows/ | head -20

Repository: juspay/neurolink

Length of output: 42


🏁 Script executed:

grep -l "pull_request" .github/workflows/*.yml

Repository: juspay/neurolink

Length of output: 210


🏁 Script executed:

cat .github/workflows/docs-pr-validation.yml

Repository: juspay/neurolink

Length of output: 5927


🏁 Script executed:

grep -r "DOCS_BOT_TOKEN\|GH_TOKEN\|PAT" .github/ | grep -v "\.yml" | head -10

Repository: juspay/neurolink

Length of output: 114


🏁 Script executed:

grep -i "secrets\." .github/workflows/docs-version.yml | grep -v "GITHUB_TOKEN"

Repository: juspay/neurolink

Length of output: 42


Documentation validation won't run for PRs created with GITHUB_TOKEN.

The docs-pr-validation.yml workflow monitors pull_request events on the release branch but won't trigger for PRs created by this workflow using GITHUB_TOKEN. This means versioning PRs will skip documentation validation checks (frontmatter, TypeScript, build, and link validation).

Use a PAT or GitHub App token instead:

🔧 Suggested change
-          token: ${{ secrets.GITHUB_TOKEN }}
+          token: ${{ secrets.DOCS_BOT_TOKEN }}
🤖 Prompt for AI Agents
In @.github/workflows/docs-version.yml around lines 89 - 124, The Create Pull
Request step is using ${{ secrets.GITHUB_TOKEN }} which prevents downstream
pull_request workflows from triggering; replace the token input in the
peter-evans/create-pull-request@v6 step with a repository secret containing a
PAT or GitHub App token (e.g., ${{ secrets.DOCS_PR_PAT }}) that has repo:status
and workflow scopes, update any documentation about the secret creation, and
ensure the workflow caller still uses the same step name "Create Pull Request"
and branch/base settings so the PR is created with that PAT and will trigger
docs-pr-validation.

Comment thread docs-site/.gitignore
Comment on lines +1 to +4
# Docusaurus build artifacts
.docusaurus/
build/
docs/

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
# Explore the structure of docs-site
ls -la docs-site/ 2>/dev/null | head -20

# Check if .gitignore exists and read it
echo "=== docs-site/.gitignore ===" 
cat docs-site/.gitignore 2>/dev/null

# Check for package.json to find validate:frontmatter script
echo -e "\n=== Checking for validate:frontmatter in package.json ===" 
find . -name "package.json" -type f | head -5

Repository: juspay/neurolink

Length of output: 1402


🏁 Script executed:

#!/bin/bash
# Check if there's a package.json in docs-site and search for validate:frontmatter
if [ -f "docs-site/package.json" ]; then
  echo "=== docs-site/package.json scripts ===" 
  cat docs-site/package.json | grep -A 30 '"scripts"'
fi

# Also check root package.json
if [ -f "package.json" ]; then
  echo -e "\n=== Root package.json scripts ===" 
  cat package.json | grep -A 30 '"scripts"'
fi

Repository: juspay/neurolink

Length of output: 3398


🏁 Script executed:

#!/bin/bash
# Search for markdown files in docs-site
echo "=== Markdown files in docs-site ===" 
find docs-site -name "*.md" -type f | wc -l

# Look for frontmatter validation configuration
echo -e "\n=== Looking for frontmatter validation config ===" 
find . -name "*frontmatter*" -o -name "*validate*" | grep -v node_modules | grep -v ".git"

Repository: juspay/neurolink

Length of output: 229


🏁 Script executed:

#!/bin/bash
# Read the validate-frontmatter script to understand what it validates
echo "=== validate-frontmatter.ts ===" 
cat docs-site/scripts/validate-frontmatter.ts

# Also search for markdown files more broadly
echo -e "\n=== All markdown files in repo ===" 
find . -name "*.md" -type f | grep -v node_modules | grep -v ".git" | head -20

Repository: juspay/neurolink

Length of output: 19883


🏁 Script executed:

#!/bin/bash
# Check what docs/ directory contains (it's in .gitignore, so might not be tracked)
echo "=== Checking docs/ directory ===" 
ls -la docs-site/docs/ 2>/dev/null || echo "docs-site/docs/ does not exist or is empty"

# Check content directory
echo -e "\n=== Checking content/ directory ===" 
find docs-site/content -type f -name "*.md" 2>/dev/null | wc -l
find docs-site/content -type f -name "*.md" 2>/dev/null | head -10

# Check if there are any recent CI failures related to frontmatter
echo -e "\n=== Searching for CI/validation configs ===" 
find . -name ".github" -o -name ".gitlab-ci.yml" -o -name "*.yaml" -o -name "*.yml" | grep -E "(github|gitlab|ci|workflow)" | head -10

Repository: juspay/neurolink

Length of output: 544


🏁 Script executed:

#!/bin/bash
# Check the CI workflow for frontmatter validation
echo "=== docs-pr-validation.yml ===" 
cat .github/workflows/docs-pr-validation.yml

# Also check sync-docs script to understand what it does
echo -e "\n=== sync-docs.ts ===" 
head -100 docs-site/scripts/sync-docs.ts

Repository: juspay/neurolink

Length of output: 9398


🏁 Script executed:

#!/bin/bash
# Check if there are markdown files in the root docs/ that need syncing
echo "=== Markdown files in root docs/ ===" 
find ./docs -name "*.md" -type f 2>/dev/null | wc -l

# List some of them to understand structure
echo -e "\n=== Sample markdown files ===" 
find ./docs -name "*.md" -type f 2>/dev/null | head -10

Repository: juspay/neurolink

Length of output: 445


🏁 Script executed:

#!/bin/bash
# Check a sample markdown file to see what frontmatter looks like
echo "=== Sample markdown file with frontmatter ===" 
head -30 ./docs/advanced/analytics.md

# Check another one
echo -e "\n=== Another sample ===" 
head -30 ./docs/advanced/api-reference.md

# Count files that might actually be missing frontmatter
echo -e "\n=== Files without frontmatter (checking for '---' at start) ===" 
find ./docs -name "*.md" -type f -exec sh -c 'head -1 "$1" | grep -q "^---" || echo "$1"' _ {} \; 2>/dev/null | wc -l

Repository: juspay/neurolink

Length of output: 1349


🏁 Script executed:

#!/bin/bash
# Look at more of sync-docs to understand the transformation
echo "=== More of sync-docs.ts ===" 
tail -n +100 docs-site/scripts/sync-docs.ts | head -100

# Check if there are any files that might have issues
echo -e "\n=== Check for files with potential frontmatter issues ===" 
find ./docs -name "*.md" -type f -exec grep -l "sidebar_position" {} \; 2>/dev/null | wc -l

Repository: juspay/neurolink

Length of output: 3591


This comment should not be on .gitignore—it belongs on the documentation source files.

The .gitignore file contains git exclusion patterns and is unrelated to frontmatter validation. The real issue is that the markdown files in ./docs/ (approximately 269 of 320 files) are missing required frontmatter fields (title, description, sidebar_position). The sync-docs.ts script transforms MkDocs syntax to Docusaurus format but does not add frontmatter metadata.

You'll need to add proper frontmatter headers to the source markdown files before the validate:frontmatter validation will pass. Simply running the validation script will report the missing fields, but the files themselves must be updated with the required metadata.

🧰 Tools
🪛 GitHub Actions: Documentation PR Validation

[error] 1-1: Frontmatter validation failed. 271 markdown files reported errors (e.g., Missing required field: description, duplicate sidebar_position). Command 'pnpm run validate:frontmatter' exited with code 1.

🤖 Prompt for AI Agents
In `@docs-site/.gitignore` around lines 1 - 4, The validation failure is caused by
missing frontmatter (title, description, sidebar_position) in the markdown files
under ./docs; update the source files or enhance the sync-docs.ts script to
inject required Docusaurus frontmatter when converting MkDocs to Docusaurus
format: modify sync-docs.ts to parse the first heading or metadata to generate a
title, synthesize a short description (or pull from existing MkDocs metadata),
and compute/assign sidebar_position (e.g., from original order or a mapping),
then write the frontmatter block (title, description, sidebar_position) to each
output markdown so validate:frontmatter will pass. Ensure you target files in
./docs/ rather than editing .gitignore.

Comment thread docs-site/package.json Outdated
Comment thread docs-site/scripts/sync-docs.ts
Comment on lines +10 to +35
posthog.init(apiKey, {
api_host: host,
// Maximum autocapture settings
autocapture: true,
capture_pageview: false, // We handle this manually for SPA navigation
capture_pageleave: true,

// Session recording with no masking for maximum visibility
enable_recording_console_log: true,
session_recording: {
maskAllInputs: false,
maskTextSelector: "", // No text masking
maskInputFn: null, // No input masking
},

// Heatmaps and performance tracking
enable_heatmaps: true,
capture_performance: true,

// Persistence settings for maximum tracking
persistence: "localStorage+cookie",
cross_subdomain_cookie: true,

// Ignore Do Not Track header
respect_dnt: false,

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

🧩 Analysis chain

🏁 Script executed:

cd docs-site && find . -name "Root.tsx" -type f 2>/dev/null | head -5

Repository: juspay/neurolink

Length of output: 80


🏁 Script executed:

cat -n "src/theme/Root.tsx" | head -100

Repository: juspay/neurolink

Length of output: 110


🏁 Script executed:

# Search for consent-related code
rg "consent|cookie.*consent|banner" docs-site --type tsx --type ts -i -A 2 -B 2

Repository: juspay/neurolink

Length of output: 87


🏁 Script executed:

# Search for usePageTracking and page tracking initialization
rg "usePageTracking|capture_pageview|postHog|page.*track" docs-site --type tsx --type ts -A 3 -B 1

Repository: juspay/neurolink

Length of output: 87


🏁 Script executed:

find . -name "Root.tsx" -type f 2>/dev/null

Repository: juspay/neurolink

Length of output: 90


🏁 Script executed:

# Once found, read the file without file type filter
rg "posthog.init|maskAllInputs|respect_dnt|maskTextSelector" . -A 2 -B 2

Repository: juspay/neurolink

Length of output: 975


🏁 Script executed:

# Search for consent/page tracking patterns in the docs-site
rg "usePageTracking|capture_pageview|consent" . -A 3 -B 1

Repository: juspay/neurolink

Length of output: 25369


🏁 Script executed:

cat -n ./docs-site/src/theme/Root.tsx

Repository: juspay/neurolink

Length of output: 3757


Session recording captures unmasked data and ignores DNT.

maskAllInputs: false will record all form inputs (passwords, emails, credit card data) without masking. Combined with respect_dnt: false, this creates a significant GDPR/CCPA compliance and PII risk for a public docs site. Enable input masking, respect the DNT header, and gate session recording behind explicit user consent.

Additionally, the initial pageview is captured twice: once in the loaded callback (line 40) and again when usePageTracking() fires on component mount (line 63). Remove the duplicate in the loaded callback or add a guard to skip it.

🔒 Safer default baseline
-    enable_recording_console_log: true,
-    session_recording: {
-      maskAllInputs: false,
-      maskTextSelector: "", // No text masking
-      maskInputFn: null, // No input masking
-    },
+    enable_recording_console_log: false,
+    session_recording: {
+      maskAllInputs: true,
+    },
@@
-    respect_dnt: false,
+    respect_dnt: true,
📝 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
posthog.init(apiKey, {
api_host: host,
// Maximum autocapture settings
autocapture: true,
capture_pageview: false, // We handle this manually for SPA navigation
capture_pageleave: true,
// Session recording with no masking for maximum visibility
enable_recording_console_log: true,
session_recording: {
maskAllInputs: false,
maskTextSelector: "", // No text masking
maskInputFn: null, // No input masking
},
// Heatmaps and performance tracking
enable_heatmaps: true,
capture_performance: true,
// Persistence settings for maximum tracking
persistence: "localStorage+cookie",
cross_subdomain_cookie: true,
// Ignore Do Not Track header
respect_dnt: false,
posthog.init(apiKey, {
api_host: host,
// Maximum autocapture settings
autocapture: true,
capture_pageview: false, // We handle this manually for SPA navigation
capture_pageleave: true,
// Session recording with no masking for maximum visibility
enable_recording_console_log: false,
session_recording: {
maskAllInputs: true,
},
// Heatmaps and performance tracking
enable_heatmaps: true,
capture_performance: true,
// Persistence settings for maximum tracking
persistence: "localStorage+cookie",
cross_subdomain_cookie: true,
// Ignore Do Not Track header
respect_dnt: true,
🤖 Prompt for AI Agents
In `@docs-site/src/theme/Root.tsx` around lines 10 - 35, Update the PostHog
initialization in posthog.init so session recording is privacy-safe: set
session_recording.maskAllInputs to true (and provide a non-null maskInputFn or
sensible maskTextSelector), set respect_dnt to true, and only enable
session_recording after explicit user consent gating logic (e.g., check a
consent flag before passing session_recording). Also remove the duplicate
initial pageview by deleting or guarding the pageview call inside the loaded
callback (the one referenced as loaded) so usePageTracking() on mount is the
single source of the initial pageview.

Comment thread docs-site/static/llms.txt Outdated
Comment thread docs/advanced/index.md Outdated
Comment thread docs/getting-started/providers/google-vertex.md Outdated
Comment thread docs/guides/migration/index.md
@murdore
murdore force-pushed the feat/improve-documentation-styling branch from e0e15ab to 13a109f Compare January 29, 2026 04:00
@github-actions

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore
murdore force-pushed the feat/improve-documentation-styling branch from 13a109f to b4c44cd Compare January 29, 2026 07:24
@github-actions

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore

murdore commented Jan 29, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai ."Full review, make sure to thoroughly review all the changes, the feedback on the feedback, project patterns and everything. Understand the resolution of the items that you shared in the previous feedback and see what needs to be done better."

@coderabbitai

coderabbitai Bot commented Jan 29, 2026

Copy link
Copy Markdown

@murdore: I'll conduct a comprehensive full review of this PR, focusing on:

  1. All changes: The complete migration infrastructure including the 98+ new files, configuration, and automation
  2. Previous feedback resolution: How items from earlier reviews were addressed
  3. Project patterns: Architecture decisions, code organization, and consistency
  4. Implementation quality: Best practices, potential improvements, and areas that need enhancement

I'll thoroughly examine:

  • The Docusaurus setup and configuration
  • GitHub Actions workflows (deployment, validation, versioning)
  • The automated sync pipeline (sync-docs.ts)
  • Custom UI components and styling
  • Documentation structure and organization
  • Build scripts and tooling
  • Breaking changes and migration completeness

Let me perform this comprehensive review now.

✅ Actions performed

Full review triggered.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🤖 Fix all issues with AI agents
In `@docs-site/package.json`:
- Around line 50-51: The package.json has mismatched React type packages:
"@types/react" is ^19.2.10 and "@types/react-dom" is ^19.2.3 while runtime React
is ^18.3.1; update both "@types/react" and "@types/react-dom" entries to
versions compatible with React 18 (for example ^18.3.26 or newer) so types align
with the runtime, then reinstall (npm/yarn) to refresh lockfile and ensure
type-checking passes.

In `@docs-site/scripts/build-llms-txt.ts`:
- Around line 18-23: Centralize the docs base URL by adding a DOCS_BASE_URL
constant (e.g., const DOCS_BASE_URL = process.env.DOCS_BASE_URL ||
"https://docs.neurolink.ink") near the existing constants (DOCS_DIR, OUTPUT_DIR,
SUMMARY_OUTPUT, FULL_OUTPUT), then replace all hard-coded
"https://neurolink.juspay.io" occurrences in the script (the link-generation
code blocks that produce llms.txt / llms-full.txt — search for where
SUMMARY_OUTPUT/FULL_OUTPUT are written and where links are concatenated) to use
DOCS_BASE_URL so outputs remain configurable; ensure the env var fallback is the
new canonical URL and update every occurrence mentioned in the review (the other
link-generation sites) to reference DOCS_BASE_URL.
- Around line 110-121: The regex that strips <Tabs> blocks only matches plain
"<Tabs>" so props-bearing Tabs (e.g., <Tabs groupId=...>) aren't caught; update
the replace call that operates on the variable stripped (the stripped =
stripped.replace(/<Tabs>[\s\S]*?<\/Tabs>/g, ...) invocation) to match Tabs with
attributes (use a pattern like <Tabs\b[^>]*>[\s\S]*?<\/Tabs>), then continue
extracting code fences from the match as before so code blocks inside
prop-bearing Tabs are preserved.
- Around line 340-372: Normalize the globbed relative path before running
priority matching: create a normalizedFile (e.g., const normalizedFile =
file.split(path.sep).join('/')) inside getDocFiles() and pass normalizedFile
into getPriorityInfo() (and any other priority-matching calls such as getOrder()
if those rely on the same pattern), while keeping file/filePath for filesystem
operations; this ensures getPriorityInfo() and PRIORITY_RULES regexes match
consistently across Windows and POSIX.

In `@docs-site/src/components/Search/EmptySearch.tsx`:
- Around line 45-70: Replace the non-semantic interactive div that has
role="button" and nests a <button> with two sibling semantic buttons: make the
outer clickable area a real <button> (use the existing onRecentSearchClick
handler, remove the onKeyDown/tabIndex logic) styled with
styles.recentSearchItem and containing <SearchIcon> and the query text, and keep
the remove action as a separate sibling <button> using onRemoveRecentSearch
(stopPropagation still applies) with its aria-label; ensure both buttons use the
existing class names (styles.recentSearchItem, styles.recentSearchIcon,
styles.recentSearchText, styles.recentSearchRemove,
styles.recentSearchRemoveIcon) and preserve behavior of onRecentSearchClick and
onRemoveRecentSearch without nesting interactive elements.
- Around line 81-85: The map is calling the React hook useBaseUrl inside
QUICK_LINKS.map which violates the Rules of Hooks; fix by resolving all URLs at
the top of the EmptySearch component before rendering (e.g., create a
resolvedLinks array by mapping QUICK_LINKS and calling useBaseUrl once per item
at the component level to produce a resolvedPath property), then iterate over
resolvedLinks in the JSX and use link.resolvedPath in the Link to prop instead
of calling useBaseUrl inside the render loop.
🟡 Minor comments (15)
docs-site/src/components/CopyPageButton/index.tsx-4-6 (1)

4-6: Unused text prop — always copies full page HTML.

The text prop is defined in the interface but never used. handleCopy always copies document.documentElement.outerHTML regardless of any text value passed. Either use the prop or remove it.

🔧 Proposed fix to use the text prop
   const handleCopy = async () => {
     setStatus("copying");
-    const pageContent = document.documentElement.outerHTML;
+    const pageContent = text || document.documentElement.outerHTML;

Also applies to: 8-8, 42-42

docs-site/src/components/CopyPageButton/index.tsx-81-93 (1)

81-93: Add accessible titles to SVG icons.

Per the static analysis hint, SVGs should include a <title> element for screen readers. This ensures users relying on assistive technology understand the icon's purpose.

♿ Proposed fix for accessibility
       <svg
         className={styles.icon}
         viewBox="0 0 24 24"
         fill="none"
         stroke="currentColor"
         strokeWidth={2}
+        aria-hidden="true"
       >
+        <title>Copied</title>
         <path
           strokeLinecap="round"
           strokeLinejoin="round"
           d="M5 13l4 4L19 7"
         />
       </svg>
       <svg
         className={styles.icon}
         viewBox="0 0 24 24"
         fill="none"
         stroke="currentColor"
         strokeWidth={2}
+        aria-hidden="true"
       >
+        <title>Copy</title>
         <path
           strokeLinecap="round"
           strokeLinejoin="round"
           d="M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3m2 4H10m0 0l3-3m-3 3l3 3"
         />
       </svg>

Note: Using aria-hidden="true" is appropriate here since the adjacent <span> already provides the text label. Alternatively, remove aria-hidden and keep just the <title>.

Also applies to: 95-108

docs-site/src/components/PropertiesTable/index.tsx-31-33 (1)

31-33: Make the required "*" screen-reader accessible.

The asterisk indicator for required fields is not announced by screen readers without additional context. Add an aria-label to make it accessible, and optionally include a title attribute for sighted users:

-                {prop.required && <span className={styles.required}>*</span>}
+                {prop.required && (
+                  <span className={styles.required} aria-label="Required" title="Required">
+                    *
+                  </span>
+                )}
docs-site/src/components/PropertiesTable/index.tsx-37-37 (1)

37-37: Fix empty-string defaults to display as code instead of falling back to "—".

The current truthy check prop.default ? treats empty strings as falsy, causing valid empty-string defaults to be replaced with "—". Change to prop.default !== undefined to correctly distinguish between "no default specified" (undefined) and "empty string is the default" (empty string).

Proposed fix
-              <td>{prop.default ? <code>{prop.default}</code> : "—"}</td>
+              <td>{prop.default !== undefined ? <code>{prop.default}</code> : "—"}</td>
docs-site/src/components/PropertiesTable/index.tsx-21-24 (1)

21-24: Add scope="col" to header cells for accessibility.

Screen readers need explicit column scope declarations to properly announce headers when reading table data cells. This aligns with WCAG Technique H63.

🔧 Proposed fix
-            <th>Property</th>
-            <th>Type</th>
-            <th>Default</th>
-            <th>Description</th>
+            <th scope="col">Property</th>
+            <th scope="col">Type</th>
+            <th scope="col">Default</th>
+            <th scope="col">Description</th>
docs-site/src/components/ProviderModelsTable/index.tsx-41-71 (1)

41-71: Add accessible labels for feature icons.
The checkmark/dash glyphs rely on visual meaning and title only. Add aria-label (and role="img") so screen readers announce the state.

✅ Suggested fix
-                  <span className={styles.supported} title="Supported">
+                  <span
+                    className={styles.supported}
+                    title="Supported"
+                    role="img"
+                    aria-label="Supported"
+                  >
                     &#10003;
                   </span>
                 ) : (
-                  <span className={styles.notSupported} title="Not supported">
+                  <span
+                    className={styles.notSupported}
+                    title="Not supported"
+                    role="img"
+                    aria-label="Not supported"
+                  >
                     &mdash;
                   </span>
                 )}
docs-site/src/components/ProviderModelsTable/index.tsx-31-33 (1)

31-33: Use a composite key to avoid duplicate rows.

Line 32 uses model.name as the key; if the same model name appears under different providers, React can reuse the wrong row. Since both model.provider and model.name are available, use a composite key:

Suggested fix
-            <tr key={model.name}>
+            <tr key={`${model.provider}:${model.name}`}>
docs-site/src/css/utilities.css-654-684 (1)

654-684: Fix .show-tablet-only so it doesn’t appear on mobile.
Right now it shows on mobile widths (<577px). Add a mobile hide rule.

✅ Proposed fix
 `@media` (max-width: 576px) {
   .markdown h1 {
     font-size: 1.5rem;
   }
@@
   /* Hide on mobile */
   .hide-mobile {
     display: none;
   }
+
+  /* Ensure tablet-only content is hidden on mobile */
+  .show-tablet-only {
+    display: none;
+  }
 }

Also applies to: 686-697

docs-site/src/components/GithubLink/index.tsx-24-26 (1)

24-26: Add accessible title to SVG for screen readers.

The static analysis tool correctly identifies that the SVG lacks alternative text. For accessibility, add a <title> element inside the SVG or use aria-label.

🔧 Proposed fix
-      <svg className={styles.icon} viewBox="0 0 24 24" fill="currentColor">
+      <svg className={styles.icon} viewBox="0 0 24 24" fill="currentColor" aria-label="GitHub">
+        <title>GitHub</title>
         <path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
       </svg>
docs-site/src/components/Search/SearchModal.tsx-73-82 (1)

73-82: Guard ArrowDown when there are zero results.

With an empty results array, Math.min(..., -1) sets selectedIndex to -1, which can leave navigation in an invalid state. Clamp to 0 (or early‑return) when no results.

🛠️ Suggested fix
     const handleKeyDown = (e: KeyboardEvent) => {
       switch (e.key) {
         case "ArrowDown":
           e.preventDefault();
-          setSelectedIndex(Math.min(selectedIndex + 1, results.length - 1));
+          const maxIndex = Math.max(results.length - 1, 0);
+          setSelectedIndex(Math.min(selectedIndex + 1, maxIndex));
           break;
docs-site/src/components/SearchWrapperMobile.tsx-195-196 (1)

195-196: Fix ARIA on the loading spinner element.

A div without an explicit role shouldn’t carry aria-label. Give it a proper status role (or mark it decorative) to satisfy a11y rules.

🛠️ Suggested fix
-              {isLoading && (
-                <div className={styles.loadingSpinner} aria-label="Loading" />
-              )}
+              {isLoading && (
+                <div
+                  className={styles.loadingSpinner}
+                  role="status"
+                  aria-live="polite"
+                  aria-label="Loading"
+                />
+              )}
docs-site/src/components/Search/SearchInput.tsx-42-44 (1)

42-44: Add role attribute to loading spinner for valid ARIA usage.

The aria-label attribute on Line 43 is not supported on plain <div> elements without a role. Add role="status" to make the accessibility attributes valid.

♿ Proposed fix
       {isLoading ? (
-          <div className={styles.loadingSpinner} aria-label="Loading" />
+          <div className={styles.loadingSpinner} role="status" aria-label="Loading" />
       ) : value ? (
docs-site/src/components/Search/SearchResults.tsx-138-157 (1)

138-157: Add accessibility attributes to interactive container.

The div with onMouseEnter lacks proper accessibility attributes. Add role="option" (since the parent has role="listbox") and aria-selected for screen reader support:

♿ Proposed fix
               <div
                 key={item.result.objectID}
+                role="option"
+                aria-selected={isSelected}
                 style={{
                   position: "absolute",
                   top: 0,
                   left: 0,
                   width: "100%",
                   height: `${virtualRow.size}px`,
                   transform: `translateY(${virtualRow.start}px)`,
                 }}
                 onMouseEnter={() => handleMouseEnter(item.flatIndex)}
               >
docs-site/src/pages/index.tsx-8-147 (1)

8-147: Add aria-hidden="true" to decorative SVG icons.

All SVG icons are decorative (used alongside text labels in features/buttons). Instead of adding title elements, mark them as aria-hidden="true" to hide from assistive technology while keeping them visually present:

♿ Proposed fix (apply to all icon components)
 const ProviderIcon = () => (
   <svg
     viewBox="0 0 24 24"
     fill="none"
     stroke="currentColor"
     strokeWidth="1.5"
     strokeLinecap="round"
     strokeLinejoin="round"
+    aria-hidden="true"
   >

Apply the same pattern to all icon components: ToolsIcon, StreamIcon, ShieldIcon, CodeIcon, TerminalIcon, ImageIcon, MemoryIcon, ArrowRightIcon, and CheckIcon.

docs-site/src/components/Button/index.tsx-25-37 (1)

25-37: Remove or handle asChild to avoid leaking invalid DOM attributes.
Line 28 declares asChild, but it isn’t consumed; it’s forwarded via ...props to the <button> as a non-standard attribute. Either implement the behavior or drop the prop.

🛠️ Suggested fix (remove unused prop)
-interface ButtonProps
-  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
-    VariantProps<typeof buttonVariants> {
-  asChild?: boolean;
-}
+interface ButtonProps
+  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
+    VariantProps<typeof buttonVariants> {}
🧹 Nitpick comments (28)
docs-site/src/components/YouTube/index.tsx (1)

8-19: Consider using YouTube's privacy-enhanced mode.

Given the PR mentions PostHog privacy compliance, consider using youtube-nocookie.com instead of youtube.com for the embed URL. This prevents YouTube from storing visitor information unless the video is played.

Additionally, adding loading="lazy" improves page performance for below-the-fold videos.

♻️ Suggested improvements
 export function YouTube({ id, title = "YouTube video" }: YouTubeProps) {
   return (
     <div className={styles.wrapper}>
       <iframe
         className={styles.iframe}
-        src={`https://www.youtube.com/embed/${id}`}
+        src={`https://www.youtube-nocookie.com/embed/${id}`}
         title={title}
         allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
         allowFullScreen
+        loading="lazy"
       />
     </div>
   );
 }
docs-site/src/components/CopyPageButton/CopyPageButton.module.css (1)

40-44: Fragile selector: coupling CSS to SVG path data.

The :has(.icon path[d*="M5 13"]) selector depends on the exact d attribute substring of the checkmark SVG. If the icon changes or is sourced from a library, this breaks silently. Consider using a class-based approach instead.

♻️ Suggested refactor using a state class

In the CSS, replace the :has() selector with a class:

-/* Copied state styling */
-.button:has(.icon path[d*="M5 13"]) {
+/* Copied state styling */
+.buttonCopied {
   color: var(--neurolink-success-text);
   border-color: var(--neurolink-success-border);
   background: var(--neurolink-success-bg);
 }

Then in index.tsx, apply the class conditionally:

className={`${styles.button} ${status === "copied" ? styles.buttonCopied : ""} copy-button copy-button--${status}`}
docs-site/src/components/ui/Kbd.tsx (1)

1-10: Prefer clsx for class merging.
Use clsx to avoid manual string concatenation and trailing spaces.

♻️ Proposed refactor
-import type React from "react";
-import styles from "./Kbd.module.css";
+import type React from "react";
+import clsx from "clsx";
+import styles from "./Kbd.module.css";
@@
 export function Kbd({ children, className }: KbdProps) {
-  return <kbd className={`${styles.kbd} ${className || ""}`}>{children}</kbd>;
+  return <kbd className={clsx(styles.kbd, className)}>{children}</kbd>;
 }
docs-site/src/components/ProviderModelsTable/index.tsx (1)

37-39: Make number formatting deterministic for SSG hydration.
Line 38 uses toLocaleString() without an explicit locale, which can lead to SSR/CSR mismatch if user locale differs from build locale.

✅ Suggested fix
-                {model.contextWindow.toLocaleString()}
+                {model.contextWindow.toLocaleString("en-US")}
docs-site/src/theme/prism-neurolink-light.ts (1)

38-54: Note: inserted token defined twice (intentional cascade).

The inserted token appears in the blue color group (line 49) and again in the diff highlighting section (line 111). Since prism-react-renderer applies later rules over earlier ones, the green diff styling will correctly override the blue styling for inserted tokens. This appears intentional for diff code blocks, but consider adding a brief comment at line 49 noting that the diff-specific styling below takes precedence, to clarify the design intent for future maintainers.

Also applies to: 110-116

docs-site/src/theme/prism-neurolink-dark.ts (1)

38-54: Note: inserted token defined twice (intentional cascade).

Same pattern as the light theme—inserted appears in the blue color group (line 49) and the diff highlighting section (line 111). The later diff-specific styling correctly overrides for diff code blocks. Consider adding a clarifying comment at line 49 for maintainability.

Also applies to: 110-116

docs-site/scripts/create-version.ts (1)

31-45: Guard against invalid or prerelease versions to avoid unintended doc versions.
Currently invalid semver is coerced to 0.0, and prerelease tags (e.g., 1.2.0-beta.1) still pass the patch===0 gate.

🛠️ Suggested tightening
 function parseVersion(version: string): {
   major: number;
   minor: number;
   patch: number;
 } {
-  // Handle versions like "1.2.3", "1.2.3-beta.1", etc.
-  const cleanVersion = version.replace(/^v/, "").split("-")[0];
-  const [major, minor, patch] = cleanVersion.split(".").map(Number);
+  // Handle versions like "1.2.3" and detect prerelease
+  const cleanVersion = version.replace(/^v/, "");
+  const [core] = cleanVersion.split("-");
+  const [major, minor, patch] = core.split(".").map(Number);
+  if ([major, minor, patch].some((n) => Number.isNaN(n))) {
+    throw new Error(`Invalid version string: ${version}`);
+  }

   return {
-    major: major || 0,
-    minor: minor || 0,
-    patch: patch || 0,
+    major,
+    minor,
+    patch,
   };
 }
@@
-  const { major, minor, patch } = parseVersion(version);
+  const { major, minor, patch } = parseVersion(version);
+  if (version.includes("-")) {
+    console.log(`\nSkipping prerelease version: ${version}`);
+    return;
+  }

Also applies to: 82-90

docs-site/src/components/Search/SearchResultItem.tsx (1)

75-91: Array index as key is acceptable here but could be improved.

The breadcrumb parts are derived from stable hierarchy levels (lvl0, lvl1, lvl2) that don't reorder dynamically. While using index as a key is generally discouraged, it's acceptable in this case since the hierarchy structure is fixed per result.

If you want to eliminate the linter warning, consider using the hierarchy level as the key:

♻️ Optional improvement
-          {breadcrumbParts.map((part, index) => (
-            <React.Fragment key={index}>
+          {breadcrumbParts.map((part, index) => {
+            const key = `lvl${index}`;
+            return (
+            <React.Fragment key={key}>
               {index > 0 && (
docs-site/scripts/visual-verification.py (1)

68-69: Remove unnecessary f-string prefix.

Line 69 has an f-string without any placeholders. This is harmless but should be a plain string for clarity.

🔧 Suggested fix
         if has_404:
-            print(f"  ⚠ WARNING: Page may contain 404 content")
+            print("  ⚠ WARNING: Page may contain 404 content")
docs-site/src/components/icons/ArrowIcon.tsx (1)

6-12: Consider moving rotations object outside the component.

The rotations object is static and gets recreated on every render. Moving it outside the function scope avoids unnecessary object allocations.

♻️ Suggested refactor
+const ROTATIONS = {
+  up: "rotate(-90deg)",
+  down: "rotate(90deg)",
+  left: "rotate(180deg)",
+  right: "rotate(0deg)",
+} as const;
+
 export function ArrowIcon({ className, direction = "right" }: ArrowIconProps) {
-  const rotations = {
-    up: "rotate(-90deg)",
-    down: "rotate(90deg)",
-    left: "rotate(180deg)",
-    right: "rotate(0deg)",
-  };
-
   return (
     <svg
       className={className}
       fill="none"
       viewBox="0 0 24 24"
       stroke="currentColor"
       aria-hidden="true"
-      style={{ transform: rotations[direction] }}
+      style={{ transform: ROTATIONS[direction] }}
     >
docs-site/src/components/Button/Button.module.css (2)

58-62: Consider using a design token for the primary button text color.

The color: white is hardcoded while other properties use CSS custom properties. For consistency and potential future theming flexibility, consider defining a token like --neurolink-text-on-accent or similar.

♻️ Suggested improvement
 .primary {
   background: var(--neurolink-accent);
-  color: white;
+  color: var(--neurolink-text-on-accent, `#ffffff`);
   border-color: var(--neurolink-accent);
 }

141-143: Hardcoded rgba value in dark mode could use a token.

The outline hover background uses rgba(59, 130, 246, 0.15) directly, whereas light mode uses var(--neurolink-accent-subtle). Consider defining a dark mode token for consistency.

docs-site/package.json (1)

11-12: Duplicate script entries for the same task.

build:llms and build:llms-txt both execute tsx scripts/build-llms-txt.ts. Consider removing one to avoid confusion.

♻️ Remove duplicate script
     "sync-docs": "tsx scripts/sync-docs.ts",
-    "build:llms": "tsx scripts/build-llms-txt.ts",
     "build:llms-txt": "tsx scripts/build-llms-txt.ts",
docs-site/src/css/tokens.css (1)

29-34: Icon token naming starts at level 2.

The icon color tokens start at --neurolink-icons-2 rather than --neurolink-icons-1. If this is intentional (e.g., level 1 is reserved or implicit), consider adding a brief comment. Otherwise, starting at 1 would be more intuitive.

docs-site/src/components/icons/CloseIcon.tsx (1)

7-13: Consider adding default dimensions to prevent layout shifts.

The SVG lacks explicit width and height attributes. While the parent component or CSS typically controls sizing, adding default dimensions (e.g., width={24} height={24}) provides a fallback and prevents potential layout shifts if the icon renders before styles load.

♻️ Add default dimensions
     <svg
       className={className}
       fill="none"
       viewBox="0 0 24 24"
+      width={24}
+      height={24}
       stroke="currentColor"
       aria-hidden="true"
     >
docs-site/src/components/CardGrid/CardGrid.module.css (2)

5-22: Consider adding a default column configuration.

The grid relies on data-cols attribute for column definition. If data-cols is not set (or set to an unexpected value), the grid won't have explicit column rules. Adding a default ensures graceful fallback.

♻️ Add default column configuration
 .grid {
   display: grid;
   gap: var(--spacing-4);
   margin: var(--spacing-6) 0;
+  grid-template-columns: repeat(3, 1fr); /* Default fallback */
 }

117-119: Dark mode icon background uses hardcoded rgba value.

Similar to Button.module.css, the dark mode icon background uses rgba(59, 130, 246, 0.15) directly. For consistency, consider using a token or the same value defined elsewhere (this matches the outline hover in Button dark mode).

.github/workflows/docs-deploy.yml (1)

25-31: Consider adding timeout-minutes to prevent runaway builds.

The build job has no timeout configured. If the build hangs (e.g., due to a dependency issue or infinite loop), it could consume runner minutes indefinitely.

💡 Suggested improvement
   build:
     name: Build Documentation
     runs-on: ubuntu-latest
+    timeout-minutes: 15
     defaults:
       run:
         working-directory: docs-site
docs-site/sidebars.ts (1)

37-48: Minor inconsistency: collapsed property is omitted.

For consistency with other categories (e.g., MCP, Memory, Workflows), consider explicitly setting collapsed: true on the SDK category. This isn't a bug since Docusaurus defaults to collapsed, but explicit configuration improves maintainability.

💅 Suggested improvement
     {
       type: "category",
       label: "SDK",
+      collapsed: true,
       items: [
docs-site/docusaurus.config.ts (1)

22-23: Consider stricter broken link handling for production builds.

Setting onBrokenLinks and onBrokenAnchors to "warn" allows builds to succeed with broken links, which could lead to 404s in production. Consider using "throw" for production or at least in CI to catch issues before deployment.

💡 Suggested approach
-  onBrokenLinks: "warn",
-  onBrokenAnchors: "warn",
+  onBrokenLinks: process.env.NODE_ENV === "production" ? "throw" : "warn",
+  onBrokenAnchors: process.env.NODE_ENV === "production" ? "throw" : "warn",
docs-site/src/components/SearchWrapperMobile.tsx (1)

66-73: Clear the focus timer on close.

If the panel closes quickly, the pending timeout can still fire and focus the input after close. Consider cleaning it up.

♻️ Suggested refactor
   useEffect(() => {
     if (isOpen) {
-      setTimeout(() => {
+      const timer = setTimeout(() => {
         inputRef.current?.focus();
       }, 100);
+      return () => clearTimeout(timer);
     }
   }, [isOpen]);
docs-site/src/css/components.css (3)

228-238: Consider removing !important declarations.

The !important declarations on lines 231-232 and 236-237 for sidebar caret icons can lead to specificity issues and make future maintenance harder. If these are needed to override Docusaurus defaults, consider using more specific selectors instead.


16-18: Consider using CSS variables for hardcoded colors.

Several rgba values are hardcoded (e.g., rgba(10, 10, 10, 0.85) on line 17, rgba(255, 255, 255, 0.8) implied in other contexts). For consistency with the design token system, consider defining these as CSS variables in tokens.css.

Also applies to: 69-74


97-141: Inline SVG icons are duplicated between themes.

The GitHub and Discord SVG icons are defined twice (once for light, once for dark) with only the fill color differing. Consider using CSS filter or currentColor to reduce duplication and simplify theme switching.

♻️ Example approach using currentColor
 .header-github-link::before {
   content: "";
   width: 24px;
   height: 24px;
   display: flex;
-  background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23333' d='M12 .297c-6.63...
+  background: url("data:image/svg+xml,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='currentColor' d='M12 .297c-6.63...
     no-repeat;
+  /* Use CSS filter for color inversion in dark mode instead of duplicate SVG */
 }
-
-[data-theme="dark"] .header-github-link::before {
-  background: url("data:image/svg+xml,...") no-repeat;
-}
REQUIRED_ENVIRONMENT_VARIABLES.md (1)

13-14: Minor: Capitalize "GitHub" consistently.

Per standard branding, "GitHub" should be capitalized with a capital "H" in these locations:

  • Line 13: .github/workflows/docs-version.yml → The path is correct, but surrounding text should use "GitHub"
  • Line 183: .github/workflows/release.yml → Same note
  • Line 296: .github/workflows/ → Same note

This is a minor documentation polish item.

Also applies to: 183-183, 296-296

docs-site/src/components/Steps/index.tsx (1)

24-24: Consider semantic flexibility for step titles.

The hardcoded <h4> for step titles may cause heading hierarchy issues if Steps are used in different page contexts. Consider making the heading level configurable or using a semantically neutral element like <div> with appropriate styling.

♻️ Optional: Make heading level configurable
 interface StepItemProps {
   title: string;
   children: React.ReactNode;
+  as?: 'h3' | 'h4' | 'h5' | 'div';
 }

-export function StepItem({ title, children }: StepItemProps) {
+export function StepItem({ title, children, as: Component = 'h4' }: StepItemProps) {
   return (
     <div className={styles.step}>
       <div className={styles.stepIndicator}>
         <span className={styles.stepNumber} />
       </div>
       <div className={styles.stepContent}>
-        <h4 className={styles.stepTitle}>{title}</h4>
+        <Component className={styles.stepTitle}>{title}</Component>
         <div className={styles.stepDescription}>{children}</div>
       </div>
     </div>
   );
 }
docs-site/src/components/SidebarBadge.tsx (1)

46-60: Badge parsing utility works correctly but has implicit priority ordering.

The parseBadgeFromLabel function returns the first matching badge type. Since Object.entries preserves insertion order in modern JavaScript engines, the current order (new → beta → deprecated → experimental) defines implicit priority. If a label somehow contained multiple badge markers, only the first would be detected. This is likely acceptable behavior, but consider documenting it if badge priority matters.

docs-site/src/components/Search/SearchResults.tsx (1)

44-57: Memoize flatItems to prevent unnecessary re-renders.

flatItems is recreated on every render, causing rowVirtualizer to recalculate sizes and the scroll-to-index effect to re-run unnecessarily. Memoize it with useMemo:

♻️ Proposed fix
+import { useVirtualizer } from "@tanstack/react-virtual";
+import React, { useCallback, useEffect, useMemo, useRef } from "react";
 
-  // Flatten results for virtualization while keeping track of indices
-  const flatItems: Array<
-    | { type: "header"; group: string }
-    | { type: "result"; result: SearchResult; flatIndex: number }
-  > = [];
-  let flatIndex = 0;
-
-  for (const { group, items } of groupedResults) {
-    flatItems.push({ type: "header", group });
-    for (const result of items) {
-      flatItems.push({ type: "result", result, flatIndex });
-      flatIndex++;
-    }
-  }
+  // Flatten results for virtualization while keeping track of indices
+  const flatItems = useMemo(() => {
+    const items: Array<
+      | { type: "header"; group: string }
+      | { type: "result"; result: SearchResult; flatIndex: number }
+    > = [];
+    let flatIndex = 0;
+
+    for (const { group, items: groupItems } of groupedResults) {
+      items.push({ type: "header", group });
+      for (const result of groupItems) {
+        items.push({ type: "result", result, flatIndex });
+        flatIndex++;
+      }
+    }
+    return items;
+  }, [groupedResults]);

Comment thread docs-site/package.json Outdated
Comment on lines +18 to +23
// Configuration
const DOCS_DIR = path.resolve(__dirname, "../docs");
const OUTPUT_DIR = path.resolve(__dirname, "../static");
const SUMMARY_OUTPUT = path.join(OUTPUT_DIR, "llms.txt");
const FULL_OUTPUT = path.join(OUTPUT_DIR, "llms-full.txt");

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

Make the docs base URL configurable to avoid stale links.

This script hard-codes https://neurolink.juspay.io, but the PR objective says docs now live at https://docs.neurolink.ink. The generated llms*.txt would publish outdated links. Centralize a base URL (env-driven) so the output stays consistent with deployment.

🔧 Proposed fix
 const DOCS_DIR = path.resolve(__dirname, "../docs");
 const OUTPUT_DIR = path.resolve(__dirname, "../static");
 const SUMMARY_OUTPUT = path.join(OUTPUT_DIR, "llms.txt");
 const FULL_OUTPUT = path.join(OUTPUT_DIR, "llms-full.txt");
+const DOCS_BASE_URL = process.env.DOCS_BASE_URL ?? "https://docs.neurolink.ink";
+const LLMS_URL = `${DOCS_BASE_URL}/llms.txt`;
+const LLMS_FULL_URL = `${DOCS_BASE_URL}/llms-full.txt`;
@@
-  lines.push(`Full documentation: https://neurolink.juspay.io/llms-full.txt`);
+  lines.push(`Full documentation: ${LLMS_FULL_URL}`);
@@
-  lines.push("- Full text: https://neurolink.juspay.io/llms-full.txt");
-  lines.push("- Web docs: https://neurolink.juspay.io");
+  lines.push(`- Full text: ${LLMS_FULL_URL}`);
+  lines.push(`- Web docs: ${DOCS_BASE_URL}`);
   lines.push("- GitHub: https://github.com/juspay/neurolink");
@@
-  lines.push(`Summary version: https://neurolink.juspay.io/llms.txt`);
+  lines.push(`Summary version: ${LLMS_URL}`);
@@
-  lines.push("For the latest documentation, visit: https://neurolink.juspay.io");
+  lines.push(`For the latest documentation, visit: ${DOCS_BASE_URL}`);

Also applies to: 421-423, 505-508, 529-531, 581-582

🤖 Prompt for AI Agents
In `@docs-site/scripts/build-llms-txt.ts` around lines 18 - 23, Centralize the
docs base URL by adding a DOCS_BASE_URL constant (e.g., const DOCS_BASE_URL =
process.env.DOCS_BASE_URL || "https://docs.neurolink.ink") near the existing
constants (DOCS_DIR, OUTPUT_DIR, SUMMARY_OUTPUT, FULL_OUTPUT), then replace all
hard-coded "https://neurolink.juspay.io" occurrences in the script (the
link-generation code blocks that produce llms.txt / llms-full.txt — search for
where SUMMARY_OUTPUT/FULL_OUTPUT are written and where links are concatenated)
to use DOCS_BASE_URL so outputs remain configurable; ensure the env var fallback
is the new canonical URL and update every occurrence mentioned in the review
(the other link-generation sites) to reference DOCS_BASE_URL.

Comment on lines +110 to +121
// Remove JSX components but try to extract meaningful content
stripped = stripped.replace(/<Tabs>[\s\S]*?<\/Tabs>/g, (match) => {
// Extract code blocks from tabs
const codeBlocks = match.match(/```[\s\S]*?```/g) || [];
return codeBlocks.join("\n\n");
});
stripped = stripped.replace(/<TabItem[^>]*>/g, "");
stripped = stripped.replace(/<\/TabItem>/g, "");

// Remove other React components
stripped = stripped.replace(/<[A-Z][a-zA-Z]*[^>]*\/>/g, "");
stripped = stripped.replace(/<[A-Z][a-zA-Z]*[^>]*>[\s\S]*?<\/[A-Z][a-zA-Z]*>/g, "");

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

Preserve <Tabs ...> content when extracting code blocks.

Docusaurus <Tabs> almost always carries props (e.g., groupId, defaultValue). The current regex only matches <Tabs> with no attributes, so those blocks fall through to the generic component strip and lose their content (including code fences). This drops meaningful docs from llms*.txt.

🔧 Proposed fix
-  stripped = stripped.replace(/<Tabs>[\s\S]*?<\/Tabs>/g, (match) => {
+  stripped = stripped.replace(/<Tabs\b[^>]*>[\s\S]*?<\/Tabs>/g, (match) => {
     // Extract code blocks from tabs
     const codeBlocks = match.match(/```[\s\S]*?```/g) || [];
     return codeBlocks.join("\n\n");
   });
📝 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
// Remove JSX components but try to extract meaningful content
stripped = stripped.replace(/<Tabs>[\s\S]*?<\/Tabs>/g, (match) => {
// Extract code blocks from tabs
const codeBlocks = match.match(/```[\s\S]*?```/g) || [];
return codeBlocks.join("\n\n");
});
stripped = stripped.replace(/<TabItem[^>]*>/g, "");
stripped = stripped.replace(/<\/TabItem>/g, "");
// Remove other React components
stripped = stripped.replace(/<[A-Z][a-zA-Z]*[^>]*\/>/g, "");
stripped = stripped.replace(/<[A-Z][a-zA-Z]*[^>]*>[\s\S]*?<\/[A-Z][a-zA-Z]*>/g, "");
// Remove JSX components but try to extract meaningful content
stripped = stripped.replace(/<Tabs\b[^>]*>[\s\S]*?<\/Tabs>/g, (match) => {
// Extract code blocks from tabs
const codeBlocks = match.match(/
🤖 Prompt for AI Agents
In `@docs-site/scripts/build-llms-txt.ts` around lines 110 - 121, The regex that
strips <Tabs> blocks only matches plain "<Tabs>" so props-bearing Tabs (e.g.,
<Tabs groupId=...>) aren't caught; update the replace call that operates on the
variable stripped (the stripped = stripped.replace(/<Tabs>[\s\S]*?<\/Tabs>/g,
...) invocation) to match Tabs with attributes (use a pattern like
<Tabs\b[^>]*>[\s\S]*?<\/Tabs>), then continue extracting code fences from the
match as before so code blocks inside prop-bearing Tabs are preserved.

Comment on lines +340 to +372
async function getDocFiles(): Promise<DocFile[]> {
const pattern = "**/*.md";
const files = await glob(pattern, { cwd: DOCS_DIR, nodir: true });

const docFiles: DocFile[] = [];

for (const file of files) {
// Skip excluded directories
const parts = file.split(path.sep);
if (parts.some((part) => EXCLUDED_DIRS.includes(part))) {
continue;
}

const filePath = path.join(DOCS_DIR, file);
const rawContent = fs.readFileSync(filePath, "utf-8");
const { data, content } = matter(rawContent);

const section = getSection(file);
const title = extractTitle(data, content, file);
const strippedContent = stripFormatting(content);
const { priority, includeInSummary } = getPriorityInfo(file);
const order = getOrder(section, data, path.basename(file), priority);

docFiles.push({
relativePath: file,
section,
title,
content: strippedContent,
rawContent: content,
priority,
includeInSummary,
order,
});

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if .mdx files exist under docs-site/docs
echo "=== Checking for .mdx files ==="
find docs-site/docs -name "*.mdx" -type f 2>/dev/null | head -20
echo ""
echo "Total .mdx files:"
find docs-site/docs -name "*.mdx" -type f 2>/dev/null | wc -l

Repository: juspay/neurolink

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Find PRIORITY_RULES definition to understand its structure
echo "=== Searching for PRIORITY_RULES ==="
rg 'PRIORITY_RULES' docs-site/scripts/build-llms-txt.ts -B 2 -A 10

Repository: juspay/neurolink

Length of output: 1399


🏁 Script executed:

#!/bin/bash
# Examine the getSection() and getPriorityInfo() functions
echo "=== Examining getSection() function ==="
ast-grep --pattern 'function getSection($_) {
  $$$
}'

Repository: juspay/neurolink

Length of output: 99


🏁 Script executed:

#!/bin/bash
# Look at the full context around PRIORITY_RULES and getPriorityInfo
rg 'getPriorityInfo|getSection|PRIORITY_RULES' docs-site/scripts/build-llms-txt.ts -B 2 -A 5

Repository: juspay/neurolink

Length of output: 1546


🏁 Script executed:

#!/bin/bash
# Search for .mdx references and Docusaurus mentions
echo "=== Searching for .mdx references ==="
rg '\.mdx|mdx' docs-site/scripts/ --type ts -i
echo ""
echo "=== Checking package.json or config for Docusaurus/mdx ==="
rg 'docusaurus|mdx|markdown' docs-site/ --type json -i | head -20

Repository: juspay/neurolink

Length of output: 2073


🏁 Script executed:

#!/bin/bash
# Check the full context of how glob results are used
cat -n docs-site/scripts/build-llms-txt.ts | sed -n '340,380p'

Repository: juspay/neurolink

Length of output: 1450


🏁 Script executed:

#!/bin/bash
# Verify the exact behavior: check if the code runs successfully on current state
# and see what files are being processed
rg 'const files = await glob' docs-site/scripts/build-llms-txt.ts -B 2 -A 15

Repository: juspay/neurolink

Length of output: 628


Normalize paths before getPriorityInfo() matching to ensure cross-platform compatibility.

The file variable from glob() uses OS-specific path separators (\ on Windows, / on Unix), but PRIORITY_RULES patterns exclusively use / (e.g., /^getting-started\//, /^sdk\/index\.md$/). On Windows, this breaks regex matching: "getting-started\readme.md" won't match /^getting-started\//. Normalizing paths to use / ensures consistent rule matching across platforms.

🛠️ Proposed fix
     const section = getSection(file);
     const title = extractTitle(data, content, file);
     const strippedContent = stripFormatting(content);
-    const { priority, includeInSummary } = getPriorityInfo(file);
+    const normalizedPath = file.split(path.sep).join("/");
+    const { priority, includeInSummary } = getPriorityInfo(normalizedPath);
@@
-      relativePath: file,
+      relativePath: normalizedPath,
📝 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
async function getDocFiles(): Promise<DocFile[]> {
const pattern = "**/*.md";
const files = await glob(pattern, { cwd: DOCS_DIR, nodir: true });
const docFiles: DocFile[] = [];
for (const file of files) {
// Skip excluded directories
const parts = file.split(path.sep);
if (parts.some((part) => EXCLUDED_DIRS.includes(part))) {
continue;
}
const filePath = path.join(DOCS_DIR, file);
const rawContent = fs.readFileSync(filePath, "utf-8");
const { data, content } = matter(rawContent);
const section = getSection(file);
const title = extractTitle(data, content, file);
const strippedContent = stripFormatting(content);
const { priority, includeInSummary } = getPriorityInfo(file);
const order = getOrder(section, data, path.basename(file), priority);
docFiles.push({
relativePath: file,
section,
title,
content: strippedContent,
rawContent: content,
priority,
includeInSummary,
order,
});
async function getDocFiles(): Promise<DocFile[]> {
const pattern = "**/*.md";
const files = await glob(pattern, { cwd: DOCS_DIR, nodir: true });
const docFiles: DocFile[] = [];
for (const file of files) {
// Skip excluded directories
const parts = file.split(path.sep);
if (parts.some((part) => EXCLUDED_DIRS.includes(part))) {
continue;
}
const filePath = path.join(DOCS_DIR, file);
const rawContent = fs.readFileSync(filePath, "utf-8");
const { data, content } = matter(rawContent);
const section = getSection(file);
const title = extractTitle(data, content, file);
const strippedContent = stripFormatting(content);
const normalizedPath = file.split(path.sep).join("/");
const { priority, includeInSummary } = getPriorityInfo(normalizedPath);
const order = getOrder(section, data, path.basename(file), priority);
docFiles.push({
relativePath: normalizedPath,
section,
title,
content: strippedContent,
rawContent: content,
priority,
includeInSummary,
order,
});
🤖 Prompt for AI Agents
In `@docs-site/scripts/build-llms-txt.ts` around lines 340 - 372, Normalize the
globbed relative path before running priority matching: create a normalizedFile
(e.g., const normalizedFile = file.split(path.sep).join('/')) inside
getDocFiles() and pass normalizedFile into getPriorityInfo() (and any other
priority-matching calls such as getOrder() if those rely on the same pattern),
while keeping file/filePath for filesystem operations; this ensures
getPriorityInfo() and PRIORITY_RULES regexes match consistently across Windows
and POSIX.

Comment thread docs-site/src/components/Search/EmptySearch.tsx Outdated
Comment thread docs-site/src/components/Search/EmptySearch.tsx Outdated
@murdore
murdore force-pushed the feat/improve-documentation-styling branch from b4c44cd to ff7395e Compare January 29, 2026 10:51
@github-actions

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore
murdore force-pushed the feat/improve-documentation-styling branch from ff7395e to f24e6d7 Compare January 29, 2026 12:22
@github-actions

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore

murdore commented Jan 29, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai Full review, make sure to thoroughly review all the changes, the feedback on the feedback, project patterns and everything. Understand the resolution of the items that you shared in the previous feedback and see what needs to be done better. The comments where we have resolved things you need to reply to those comments with the current status after the resolution.

@coderabbitai

coderabbitai Bot commented Jan 29, 2026

Copy link
Copy Markdown
🧩 Analysis chain

@murdore: I'll conduct a comprehensive full review of this PR, examining all changes, verifying how previous feedback was addressed, and checking project patterns throughout the migration. I'll also review the comment threads to provide status updates on resolved items.

Let me first gather detailed information about the PR structure and existing review comments:

After gathering this information, I'll trigger a comprehensive review that will:

  1. Examine the entire Docusaurus migration infrastructure
  2. Validate GitHub Actions workflows for deployment, PR validation, and versioning
  3. Review the sync-docs.ts automation pipeline
  4. Check custom UI components and styling implementation
  5. Verify documentation structure and organization
  6. Assess build scripts and tooling
  7. Review how previous feedback items were resolved

🏁 Script executed:

#!/bin/bash

# Get PR details including review comments
gh pr view 792 --json title,body,additions,deletions,changedFiles,state,reviews,comments --repo juspay/neurolink > pr_details.json

# Display the PR statistics
echo "=== PR Statistics ==="
cat pr_details.json | jq -r '"Title: \(.title)", "State: \(.state)", "Files changed: \(.changedFiles)", "Additions: +\(.additions)", "Deletions: -\(.deletions)"'

# Get the list of all changed files
echo -e "\n=== Changed Files ==="
gh pr diff 792 --name-only --repo juspay/neurolink

# Get all review comments (including resolved ones)
echo -e "\n=== Review Comments Count ==="
gh api "/repos/juspay/neurolink/pulls/792/comments" --paginate | jq 'length'

# Get recent review threads
echo -e "\n=== Recent Review Comment Threads ==="
gh api "/repos/juspay/neurolink/pulls/792/comments" --paginate | jq -r '.[] | "File: \(.path) | Line: \(.line // .original_line) | User: \(.user.login) | State: \(.state // "PENDING") | Created: \(.created_at)"' | head -20

Length of output: 3001


Let me fetch the detailed review comments to understand what was previously flagged:


🏁 Script executed:

#!/bin/bash

# Get detailed review comments with their content
echo "=== Detailed Review Comments ==="
gh api "/repos/juspay/neurolink/pulls/792/comments" --paginate | jq -r '.[] | "
========================================
File: \(.path)
Line: \(.line // .original_line)
Comment ID: \(.id)
User: \(.user.login)
Created: \(.created_at)
Body: \(.body)
"' > review_comments.txt

# Display the comments
cat review_comments.txt

# Count total comments
echo -e "\n=== Total Comments: ==="
wc -l review_comments.txt

Length of output: 95074


🏁 Script executed:

#!/bin/bash

# Get the structure of key directories
echo "=== docs-site directory structure ==="
fd . docs-site --type f -d 3 | head -50

echo -e "\n=== GitHub workflows ==="
ls -la .github/workflows/

echo -e "\n=== Key script files ==="
fd . docs-site/scripts --type f

echo -e "\n=== Key component files ==="
fd . docs-site/src/components --type f -d 2 | head -20

Length of output: 3251


🏁 Script executed:

#!/bin/bash

# Examine key configuration files
echo "=== Docusaurus Config ==="
if [ -f "docs-site/docusaurus.config.ts" ]; then
  head -100 docs-site/docusaurus.config.ts
fi

echo -e "\n=== Package.json scripts ==="
if [ -f "docs-site/package.json" ]; then
  cat docs-site/package.json | jq '.scripts'
fi

echo -e "\n=== TypeScript Config ==="
if [ -f "docs-site/tsconfig.json" ]; then
  cat docs-site/tsconfig.json
fi

Length of output: 4615


Let me examine the current state of key files that had issues flagged in the previous review:


🏁 Script executed:

#!/bin/bash

echo "=== Checking sync-docs.ts frontmatter generation ==="
sed -n '410,540p' docs-site/scripts/sync-docs.ts

echo -e "\n=== Checking sync-docs.ts grid cards function ==="
sed -n '365,380p' docs-site/scripts/sync-docs.ts

echo -e "\n=== Checking package.json Docusaurus versions ==="
cat docs-site/package.json | jq '.dependencies | with_entries(select(.key | startswith("@docusaurus")))'

echo -e "\n=== Checking React/React-DOM type versions ==="
cat docs-site/package.json | jq '.dependencies.react, .dependencies["react-dom"], .devDependencies["@types/react"], .devDependencies["@types/react-dom"]'

Length of output: 4738


🏁 Script executed:

#!/bin/bash

echo "=== Checking docs-pr-validation.yml for continue-on-error ==="
sed -n '65,75p' .github/workflows/docs-pr-validation.yml

echo -e "\n=== Checking docs-version.yml for token usage ==="
sed -n '85,130p' .github/workflows/docs-version.yml

echo -e "\n=== Checking validate-frontmatter.ts for strict mode ==="
head -60 docs-site/scripts/validate-frontmatter.ts

Length of output: 4059


🏁 Script executed:

#!/bin/bash

echo "=== Checking Root.tsx PostHog configuration ==="
sed -n '1,50p' docs-site/src/theme/Root.tsx

echo -e "\n=== Checking SearchResultItem.tsx for sanitization ==="
sed -n '1,30p' docs-site/src/components/Search/SearchResultItem.tsx
sed -n '60,85p' docs-site/src/components/Search/SearchResultItem.tsx

echo -e "\n=== Checking CopyPageButton for error handling ==="
sed -n '50,70p' docs-site/src/components/CopyPageButton/index.tsx

Length of output: 4041


🏁 Script executed:

#!/bin/bash

echo "=== Checking llms.txt for outdated URLs ==="
head -20 docs-site/static/llms.txt

echo -e "\n=== Checking build-llms-txt.ts for hardcoded URLs ==="
sed -n '15,30p' docs-site/scripts/build-llms-txt.ts
sed -n '415,430p' docs-site/scripts/build-llms-txt.ts

echo -e "\n=== Checking for Tabs regex in build-llms-txt.ts ==="
sed -n '110,130p' docs-site/scripts/build-llms-txt.ts

Length of output: 2798


#!/bin/bash

echo "=== Checking EmptySearch.tsx for quick links and useBaseUrl ==="
sed -n '10,25p' docs-site/src/components/Search/EmptySearch.tsx
sed -n '75,95p' docs-site/src/components/Search/EmptySearch.tsx

echo -e "\n=== Checking useAlgoliaSearch.ts for race condition fix ==="
sed -n '130,175p' docs-site/src/hooks/useAlgoliaSearch.ts
sed -n '225,235p' docs-site/src/hooks/useAlgoliaSearch.ts
#!/bin/bash

echo "=== Checking prism-neurolink-light.ts for duplicate token types ==="
sed -n '50,85p' docs-site/src/theme/prism-neurolink-light.ts

echo -e "\n=== Checking broken link in docs/advanced/index.md ==="
sed -n '240,250p' docs/advanced/index.md

echo -e "\n=== Checking broken link in google-vertex.md ==="
sed -n '952,962p' docs/getting-started/providers/google-vertex.md
#!/bin/bash

echo "=== Checking path normalization in build-llms-txt.ts ==="
sed -n '355,395p' docs-site/scripts/build-llms-txt.ts

echo -e "\n=== Checking if docs/guides/migration/index.md has frontmatter ==="
head -15 docs/guides/migration/index.md
#!/bin/bash

echo "=== Checking accessibility issues in EmptySearch.tsx ==="
sed -n '40,72p' docs-site/src/components/Search/EmptySearch.tsx

echo -e "\n=== Summary of package.json dependency issues ==="
echo "React runtime versions:"
cat docs-site/package.json | jq '.dependencies.react, .dependencies["react-dom"]'
echo -e "\nReact type versions:"
cat docs-site/package.json | jq '.devDependencies["@types/react"], .devDependencies["@types/react-dom"]'
✅ Actions performed

Full review triggered.

Complete migration of documentation site from MkDocs to Docusaurus v3.7.0
with enhanced features, security improvements, and comprehensive fixes.

- Migrated from MkDocs to Docusaurus v3.7.0
- New documentation site structure in docs-site/
- Enhanced search with Algolia integration
- PostHog analytics with GDPR/CCPA compliance
- GitHub Actions workflows for deployment and versioning
- Support for both /docs and /llms-full.txt endpoints

- Fixed XSS vulnerability in search results (DOMPurify sanitization)
- PostHog privacy compliance (GDPR/CCPA)
- Clipboard API error handling for browser compatibility

- Fixed grid cards content loss during migration
- Fixed search race condition with request ID tracking
- Fixed GitHub token workflow (DOCS_BOT_TOKEN support)
- Fixed Docusaurus plugin version compatibility (3.7.0)
- Fixed Prism syntax highlighting duplicate tokens
- Fixed 3 broken documentation links
- Fixed quick links in search empty state
- Updated domain references (neurolink.juspay.io → docs.neurolink.ink)

- Added: 300+ files in docs-site/ (Docusaurus installation)
- Modified: 13 files across security, configuration, and documentation
- Deleted: mkdocs.yml and MkDocs configuration

- Created REQUIRED_ENVIRONMENT_VARIABLES.md (DOCS_BOT_TOKEN setup)
- Created PR792_EXECUTION_PLAN.md (implementation strategy)
- Created PR792_COMPLETION_SUMMARY.md (comprehensive fix report)

- All 888/888 tests passing
- TypeScript compilation: 0 errors
- Security scan: No critical vulnerabilities
- Build validation: All checks passed
@murdore
murdore force-pushed the feat/improve-documentation-styling branch from f24e6d7 to 496ce27 Compare January 29, 2026 13:11
@github-actions

Copy link
Copy Markdown
Contributor

🤖 AI Review & Build Compliance ✅

Status: AI analysis complete • Build rules validated • Ready for review

📊 View detailed analysis results

🛡️ Analysis Complete

  • ✅ Security scan (vulnerabilities, API keys)
  • ✅ TypeScript safety & code quality
  • ✅ Error handling & best practices
  • ✅ Build rule enforcement validated
  • ✅ Commit format & compliance checks

📋 Ready for Merge When

  • All CI checks passing
  • Manual review approved
  • Any AI-flagged issues resolved

🤖 AI analysis complete - check individual code comments for specific feedback

@murdore
murdore merged commit 2f70a32 into release Jan 29, 2026
13 checks passed
@murdore
murdore deleted the feat/improve-documentation-styling branch January 29, 2026 13:37
@github-actions

Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 8.41.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant