Skip to content

Add compact docs search and AI answers#5

Merged
KayleeWilliams merged 10 commits into
mainfrom
KayleeWilliams/compact-docs-search
Apr 22, 2026
Merged

Add compact docs search and AI answers#5
KayleeWilliams merged 10 commits into
mainfrom
KayleeWilliams/compact-docs-search

Conversation

@KayleeWilliams
Copy link
Copy Markdown
Collaborator

Adds the headless @inth/docs search surfaces, including compact v2 lexical indexes, split answer content, request guards, rate limiting, and AI SDK streaming helpers. Wires the docs-smoke demo with realtime debounced search, explicit Ask streaming, generated search artifacts, and the moonshotai/kimi-k2.6 default model. Adds optional @inth/docs/search/bash support backed by just-bash and bash-tool for read-only docs inspection via ls, cat, find, grep, and rg. Documents the search/AI approach across package docs, generated agent docs, and the demo docs, with tests for core search, generation, AI helpers, bash VFS behavior, and demo coverage.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 21, 2026

Note

Reviews paused

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

Use the following commands to manage reviews:

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

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a docs search subsystem and UI: build-time index generation, runtime lexical search and content APIs, AI answer streaming, read-only bash adapter, app routes/UI + tests, associated docs, package exports, and build/script updates.

Changes

Cohort / File(s) Summary
Search Engine & APIs
packages/docs/src/search/*.ts, packages/docs/src/search/index.ts, packages/docs/src/search/node.ts, packages/docs/src/search/node-index.ts
New in-memory search implementation, content APIs, answer-context assembly, request guards, node generator (generateDocsSearchFiles) and public re-exports.
AI Answering
packages/docs/src/search/ai.ts, packages/docs/src/search/ai-index.ts, packages/docs/src/search/ai.test.ts
streamDocsAnswer: builds prompt/context, invokes streaming provider, wraps/normalizes stream response, exposes sources; includes tests for normal and error flows.
Bash Adapter & Tools
packages/docs/src/search/bash.ts, packages/docs/src/search/bash-index.ts, packages/docs/src/search/bash.test.ts
Read-only virtual /docs filesystem, unsafe-command interception, bash-tool wrapper and tests validating read-only enforcement and tool surface.
App integration & routes
apps/docs-smoke/src/routes/search.tsx, apps/docs-smoke/src/routes/docs/search.tsx, apps/docs-smoke/src/routes/api/docs/{search.ts,ask.ts}, apps/docs-smoke/src/lib/search.ts, apps/docs-smoke/src/lib/docs.ts, apps/docs-smoke/src/routeTree.gen.ts
Client search page with debounced queries/Ask streaming; server handlers with validation and per-client rate limiting; limiter setup, JSON helper, and routing/typegen updates.
App content, scripts & pipeline
apps/docs-smoke/content/docs/*.mdx, apps/docs-smoke/content/docs/meta.json, apps/docs-smoke/scripts/search-generate.ts, apps/docs-smoke/scripts/*.ts, apps/docs-smoke/package.json, apps/docs-smoke/.gitignore
Added docs/search pages, meta entry, new search pipeline script, mdx/convert script path tweaks, pipeline build scripts updated to include search, new deps (ai, streamdown), and ignored generated artifacts.
UI/MDX/Components renames
packages/docs/src/components/*, packages/docs/src/components/mdx-components.ts, packages/docs/src/components/type-table.tsx, packages/docs/src/components/command-tabs.tsx, apps/docs-smoke/src/mdx-components.tsx, apps/docs-smoke/src/components/*
Renamed/split components and props: PackageCommandTabs → CommandTabs (+mode), AutoTypeTable → ExtractedTypeTable (properties prop), MDX heading id behavior, and related attribute/selector/class renames across app and styles.
Tests & Playwright
packages/docs/src/search/*.test.ts, packages/docs/src/components/components.test.tsx, apps/docs-smoke/tests/e2e/smoke.e2e.ts, apps/docs-smoke/playwright.config.ts, biome.jsonc
New unit/integration tests for search/ai/node/bash, updated component tests, Playwright config dynamic baseURL, and e2e smoke tests expanded to cover search flows; biome excludes updated for generated JSON.
Package exports & build
packages/docs/package.json, packages/docs/tsup.config.ts
Added new package exports/entrypoints for ./search, ./search/node, ./search/ai, ./search/bash; tsup entries and externals updated; dev/peer deps for optional integrations added.
Docs & READMEs
packages/docs/README.md, packages/docs/agent-docs/**, apps/docs-smoke/content/docs/index.mdx, apps/docs-smoke/content/docs/search.mdx
Extensive documentation additions/updates describing generation, runtime querying, AI streaming, bash adapter, and wiring guidance; updated examples to new API names.
Styling
apps/docs-smoke/src/styles.css
Theme and prose adjustments, new .docs-answer class, Streamdown source include, and selector renames to match component renames.

Sequence Diagram(s)

sequenceDiagram
    participant User as User Browser
    participant UI as Search Page
    participant API as Server (/api/docs/search, /api/docs/ask)
    participant Index as Docs Search Index (server)
    participant AI as AI Provider

    User->>UI: Type query
    activate UI
    Note over UI: Debounce 250ms
    UI->>API: GET /api/docs/search?q=...
    activate API
    API->>API: validate query & rate limit
    API->>Index: run lexical search against index/content
    Index-->>API: results JSON
    API-->>UI: JSON { results[] }
    deactivate API
    UI->>UI: render results
    deactivate UI

    User->>UI: Click "Ask"
    activate UI
    UI->>API: POST /api/docs/ask { query }
    activate API
    API->>API: check feature flag & rate limit
    API->>Index: build answer context (search + content)
    API->>AI: streamText(system,prompt,model,opts)
    activate AI
    AI-->>API: streamed chunks
    API-->>UI: forward readable streamed response
    deactivate AI
    UI->>UI: consume stream, render answer + sources
    deactivate API
    deactivate UI
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Poem

🐰 I hopped through markdown, indexed each bright page,
Chunks snug in baskets, context set on the stage,
Streams of answers that cite where they peep,
A read-only bash where curious agents creep,
Hooray—search blooms, rabbit-approved delight.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d33236f90b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +12 to +15
if (Array.isArray(children)) {
return children.map(textFromChildren).join(" ");
}
return "";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve text from nested heading children when slugifying

The heading ID fallback drops any non-string child content (return ""), so MDX headings with inline elements (for example inline code/emphasis) can produce an empty or incomplete slug. In those cases the rendered heading gets no matching id, while search results still emit hash links derived from the markdown heading text, so clicking a result/citation does not jump to the intended section.

Useful? React with 👍 / 👎.

Comment thread apps/docs-smoke/src/routes/search.tsx Outdated
Comment on lines +159 to +161
while (true) {
const chunk = await reader.read();
if (chunk.done) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Handle stream read failures in ask flow

If the answer stream is interrupted after the request succeeds (provider timeout, connection reset, etc.), reader.read() rejects and handleAsk exits without setting an error or restoring answerStatus, leaving the UI stuck in the "Answering" state. Wrapping the streaming loop in try/catch/finally is needed so transient backend/network failures recover cleanly.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (1)
packages/docs/scripts/generate-agent-docs.ts (1)

118-139: ⚠️ Potential issue | 🟡 Minor

Update parent Generation description to reflect Search coverage.

Line 120 still says “MDX conversion and llms.txt generation” even though Search is now a first-class Generation topic. This causes a small docs consistency mismatch.

✏️ Suggested text fix
-      description: "MDX conversion and llms.txt generation.",
+      description: "MDX conversion, llms.txt generation, and search.",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/scripts/generate-agent-docs.ts` around lines 118 - 139, Update
the parent "generation" object's description property (the object with slug
"generation") so it reflects that Search is a first-class topic; change the
current description string ("MDX conversion and llms.txt generation") to
something inclusive such as mentioning MDX conversion, llms.txt generation, and
Search/static index generation to keep docs consistent with the topics list.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/docs-smoke/content/docs/index.mdx`:
- Around line 31-34: Update the intro sentence that states the number of package
surfaces to reflect the addition of "@inth/docs/search" (i.e., change "five
package surfaces" to "six package surfaces"); locate the sentence in the docs
index where the phrase "package surfaces" appears and increment the numeric
count so the written total matches the list which now includes the
"@inth/docs/search" entry.

In `@apps/docs-smoke/scripts/search-generate.ts`:
- Around line 18-21: The generateSearchIndex invocation hardcodes baseUrl to
"https://docs.example.com", which produces incorrect result/source URLs; update
the call in search-generate.ts (the generateSearchIndex({ outDir, baseUrl })
call) to derive baseUrl dynamically (e.g., from an environment variable, build
config, or package/site metadata) and pass that value instead of the literal
string so generated artifacts contain the actual site base URL.

In `@apps/docs-smoke/src/mdx-components.tsx`:
- Around line 8-16: The textFromChildren function currently only handles
strings, numbers, and arrays, so it returns empty for React elements like <code>
and other inline components; update textFromChildren (and its handling of
HeadingProps["children"]) to recursively handle React elements/objects by
checking for an object with a props property and extracting/recursing into
props.children (also handle null/boolean/undefined by returning ""), so inline
elements (e.g., <code>) contribute their textual content to generated heading
IDs and match the markdown-derived IDs.

In `@apps/docs-smoke/src/routes/search.tsx`:
- Around line 36-54: Silent swallowing of errors in loadAnswerConfig's
configPromise.catch hides useful debug info; update the catch on the promise
returned by loadAnswerConfig (the configPromise created inside the useEffect) to
log the error in non-production/dev environments (e.g., using console.error or
the app's logger) while preserving the current silent behavior in production, so
replace catch(() => undefined) with a handler that checks the environment and
logs the caught error with context about fetching /api/docs/ask.

In `@packages/docs/README.md`:
- Around line 11-14: The package list in the README omits the package entry
`@inth/docs/search/bash`; update the list that currently contains
`@inth/docs/search`, `@inth/docs/search/node`, and `@inth/docs/search/ai` to
include `@inth/docs/search/bash` with a short description consistent with the
later documentation so the exported surface matches the rest of the file.

In `@packages/docs/src/search/ai.ts`:
- Line 71: The current onError: () => undefined in
packages/docs/src/search/ai.ts silently swallows streaming errors; change the
implementation of the onError callback used by StreamDocsAnswer (and any
internal streaming handler) to at minimum log the error via the existing logger
(or console.error) and include the error object, and extend
StreamDocsAnswerOptions to accept an optional onError handler so callers can
override default behavior (use the provided onError if present, otherwise log
and optionally rethrow/propagate). Ensure references to the onError hook in
StreamDocsAnswerOptions and any streaming function (e.g., StreamDocsAnswer / the
streaming callback registration) are updated to call the option-provided handler
with the error.

In `@packages/docs/src/search/node.ts`:
- Around line 127-149: The current sequential for...of over files performs
readFile and parsing one at a time (using files, docsDir, readFile, matter,
titleFromRelativePath, toUrlPath, toAbsoluteUrl, docs.push), which is slow for
large doc sets; refactor to process files in parallel by mapping files to an
array of promises that read and parse each file (preserving relativePath, title,
description, content, urlPath, absoluteUrl using the same helper calls) and then
await Promise.all to get results, finally push or assign them into docs in the
original files order (e.g., build an array of items from Promise.all and set
docs = items or splice into docs) so you keep deterministic ordering while
gaining concurrency; consider using a concurrency limiter (p-limit) if
file-handle exhaustion is a concern.

In `@packages/docs/src/search/search.ts`:
- Around line 960-999: The entries Map in createMemoryRateLimiter grows without
eviction; modify createMemoryRateLimiter to remove expired entries periodically
and/or on access: inside createMemoryRateLimiter (function name) add a cleanup
mechanism that deletes entries whose resetAt <= now() — either run a setInterval
cleanup using options.cleanupIntervalMs (default e.g., 60_000) and clear it on
process exit, and additionally purge expired entry for the specific identifier
at the start of check(identifier) before reading/incrementing; optionally add an
options.maxEntries cap to reject or evict the oldest entries when entries.size
exceeds the cap to prevent unbounded growth.

---

Outside diff comments:
In `@packages/docs/scripts/generate-agent-docs.ts`:
- Around line 118-139: Update the parent "generation" object's description
property (the object with slug "generation") so it reflects that Search is a
first-class topic; change the current description string ("MDX conversion and
llms.txt generation") to something inclusive such as mentioning MDX conversion,
llms.txt generation, and Search/static index generation to keep docs consistent
with the topics list.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ed274634-66e8-4b18-b984-eea850acd5e7

📥 Commits

Reviewing files that changed from the base of the PR and between b922d77 and d33236f.

⛔ Files ignored due to path filters (3)
  • apps/docs-smoke/src/generated/docs-search-content.json is excluded by !**/generated/**
  • apps/docs-smoke/src/generated/docs-search-index.json is excluded by !**/generated/**
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (40)
  • apps/docs-smoke/.gitignore
  • apps/docs-smoke/content/docs/index.mdx
  • apps/docs-smoke/content/docs/meta.json
  • apps/docs-smoke/content/docs/search.mdx
  • apps/docs-smoke/package.json
  • apps/docs-smoke/scripts/mdx-convert.ts
  • apps/docs-smoke/scripts/search-generate.ts
  • apps/docs-smoke/src/lib/docs.ts
  • apps/docs-smoke/src/lib/search.ts
  • apps/docs-smoke/src/mdx-components.tsx
  • apps/docs-smoke/src/routeTree.gen.ts
  • apps/docs-smoke/src/routes/api/docs/ask.ts
  • apps/docs-smoke/src/routes/api/docs/search.ts
  • apps/docs-smoke/src/routes/docs/search.tsx
  • apps/docs-smoke/src/routes/search.tsx
  • apps/docs-smoke/tests/e2e/smoke.e2e.ts
  • biome.jsonc
  • packages/docs/README.md
  • packages/docs/agent-docs-src/docs/search.mdx
  • packages/docs/agent-docs/docs/llms-full.txt
  • packages/docs/agent-docs/docs/llms-full/generation.txt
  • packages/docs/agent-docs/docs/llms-full/generation/search.txt
  • packages/docs/agent-docs/docs/llms.txt
  • packages/docs/agent-docs/docs/search.md
  • packages/docs/agent-docs/llms.txt
  • packages/docs/package.json
  • packages/docs/scripts/generate-agent-docs.ts
  • packages/docs/src/search/ai-index.ts
  • packages/docs/src/search/ai.test.ts
  • packages/docs/src/search/ai.ts
  • packages/docs/src/search/bash-index.ts
  • packages/docs/src/search/bash.test.ts
  • packages/docs/src/search/bash.ts
  • packages/docs/src/search/index.ts
  • packages/docs/src/search/node-index.ts
  • packages/docs/src/search/node.test.ts
  • packages/docs/src/search/node.ts
  • packages/docs/src/search/search.test.ts
  • packages/docs/src/search/search.ts
  • packages/docs/tsup.config.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Prefer unknown over any when the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions

Files:

  • packages/docs/src/search/node-index.ts
  • apps/docs-smoke/scripts/mdx-convert.ts
  • packages/docs/scripts/generate-agent-docs.ts
  • apps/docs-smoke/src/routes/docs/search.tsx
  • apps/docs-smoke/scripts/search-generate.ts
  • packages/docs/src/search/node.test.ts
  • apps/docs-smoke/src/routes/api/docs/search.ts
  • packages/docs/src/search/bash-index.ts
  • packages/docs/src/search/bash.test.ts
  • apps/docs-smoke/src/lib/docs.ts
  • apps/docs-smoke/tests/e2e/smoke.e2e.ts
  • packages/docs/src/search/search.test.ts
  • packages/docs/tsup.config.ts
  • packages/docs/src/search/index.ts
  • packages/docs/src/search/ai.test.ts
  • apps/docs-smoke/src/routes/search.tsx
  • apps/docs-smoke/src/routes/api/docs/ask.ts
  • apps/docs-smoke/src/mdx-components.tsx
  • apps/docs-smoke/src/lib/search.ts
  • packages/docs/src/search/node.ts
  • packages/docs/src/search/ai-index.ts
  • packages/docs/src/search/ai.ts
  • packages/docs/src/search/bash.ts
  • apps/docs-smoke/src/routeTree.gen.ts
  • packages/docs/src/search/search.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Prefer for...of loops over .forEach() and indexed for loops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Use const by default, let only when reassignment is needed, never var
Always await promises in async functions - don't forget to use the return value
Use async/await syntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Remove console.log, debugger, and alert statements from production code
Throw Error objects with descriptive messages, not strings or other values
Use try-catch blocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Don't use eval() or assign directly to document.cookie
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Use descriptive names for functions, variables, and types for meaningful naming
Add comments for complex logic, but prefer self-documenting code

Files:

  • packages/docs/src/search/node-index.ts
  • apps/docs-smoke/scripts/mdx-convert.ts
  • packages/docs/scripts/generate-agent-docs.ts
  • apps/docs-smoke/src/routes/docs/search.tsx
  • apps/docs-smoke/scripts/search-generate.ts
  • packages/docs/src/search/node.test.ts
  • apps/docs-smoke/src/routes/api/docs/search.ts
  • packages/docs/src/search/bash-index.ts
  • packages/docs/src/search/bash.test.ts
  • apps/docs-smoke/src/lib/docs.ts
  • apps/docs-smoke/tests/e2e/smoke.e2e.ts
  • packages/docs/src/search/search.test.ts
  • packages/docs/tsup.config.ts
  • packages/docs/src/search/index.ts
  • packages/docs/src/search/ai.test.ts
  • apps/docs-smoke/src/routes/search.tsx
  • apps/docs-smoke/src/routes/api/docs/ask.ts
  • apps/docs-smoke/src/mdx-components.tsx
  • apps/docs-smoke/src/lib/search.ts
  • packages/docs/src/search/node.ts
  • packages/docs/src/search/ai-index.ts
  • packages/docs/src/search/ai.ts
  • packages/docs/src/search/bash.ts
  • apps/docs-smoke/src/routeTree.gen.ts
  • packages/docs/src/search/search.ts
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{jsx,tsx}: Use function components over class components in React
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use the key prop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props
Don't define components inside other components
Avoid dangerouslySetInnerHTML unless absolutely necessary
Use proper image components (e.g., Next.js <Image>) over <img> tags
Use Next.js <Image> component for images
Use next/head or App Router metadata API for head elements in Next.js
Use Server Components for async data fetching instead of async Client Components in Next.js
Use ref as a prop instead of React.forwardRef in React 19+

Files:

  • apps/docs-smoke/src/routes/docs/search.tsx
  • apps/docs-smoke/src/routes/search.tsx
  • apps/docs-smoke/src/mdx-components.tsx
**/*.{jsx,tsx,html}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{jsx,tsx,html}: Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, use semantic elements instead of divs with roles
Add rel="noopener" when using target="_blank" on links

Files:

  • apps/docs-smoke/src/routes/docs/search.tsx
  • apps/docs-smoke/src/routes/search.tsx
  • apps/docs-smoke/src/mdx-components.tsx
**/*.{test,spec}.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{test,spec}.{js,ts,jsx,tsx}: Write assertions inside it() or test() blocks
Avoid done callbacks in async tests - use async/await instead
Don't use .only or .skip in committed code
Keep test suites reasonably flat - avoid excessive describe nesting

Files:

  • packages/docs/src/search/node.test.ts
  • packages/docs/src/search/bash.test.ts
  • packages/docs/src/search/search.test.ts
  • packages/docs/src/search/ai.test.ts
**/index.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Avoid barrel files (index files that re-export everything)

Files:

  • packages/docs/src/search/index.ts
🧠 Learnings (84)
📚 Learning: 2026-02-20T14:21:40.180Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T14:21:40.180Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Avoid barrel files (index files that re-export everything)

Applied to files:

  • biome.jsonc
  • packages/docs/src/search/bash-index.ts
  • packages/docs/tsup.config.ts
  • packages/docs/src/search/index.ts
  • packages/docs/package.json
📚 Learning: 2026-02-20T14:21:40.180Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T14:21:40.180Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Don't use `.only` or `.skip` in committed code

Applied to files:

  • biome.jsonc
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : Include integration tests for component combinations

Applied to files:

  • biome.jsonc
  • apps/docs-smoke/src/lib/docs.ts
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : Include unit tests for components

Applied to files:

  • biome.jsonc
  • packages/docs/src/search/node.test.ts
📚 Learning: 2026-02-20T14:21:19.208Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-02-20T14:21:19.208Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : Don't use `.only` or `.skip` in committed code

Applied to files:

  • biome.jsonc
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't use disabled tests

Applied to files:

  • biome.jsonc
📚 Learning: 2026-03-19T22:57:45.114Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:45.114Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : Include unit tests for components, integration tests for component combinations, accessibility tests, and error boundary tests

Applied to files:

  • biome.jsonc
  • packages/docs/src/search/bash.test.ts
  • apps/docs-smoke/src/lib/docs.ts
  • apps/docs-smoke/tests/e2e/smoke.e2e.ts
  • packages/docs/src/search/search.test.ts
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't use specified modules when loaded by import or require

Applied to files:

  • biome.jsonc
  • packages/docs/tsup.config.ts
  • packages/docs/package.json
📚 Learning: 2026-04-18T15:01:52.722Z
Learnt from: CR
Repo: inthhq/docs PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-18T15:01:52.722Z
Learning: Applies to **/*.{test,spec}.{js,ts,jsx,tsx} : Don't use `.only` or `.skip` in committed code

Applied to files:

  • biome.jsonc
📚 Learning: 2026-04-16T03:27:04.353Z
Learnt from: CR
Repo: inthhq/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-16T03:27:04.353Z
Learning: Applies to **/*.{test,spec}.{js,jsx,ts,tsx} : Don't use `.only` or `.skip` in committed code

Applied to files:

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

Applied to files:

  • packages/docs/src/search/node-index.ts
  • packages/docs/src/search/bash-index.ts
  • packages/docs/tsup.config.ts
  • packages/docs/src/search/index.ts
  • packages/docs/package.json
  • apps/docs-smoke/src/lib/search.ts
  • packages/docs/src/search/node.ts
  • packages/docs/src/search/ai-index.ts
  • packages/docs/src/search/search.ts
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Export type definitions for public API

Applied to files:

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

Applied to files:

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

Applied to files:

  • packages/docs/src/search/node-index.ts
  • packages/docs/README.md
  • packages/docs/src/search/bash-index.ts
  • packages/docs/tsup.config.ts
  • packages/docs/src/search/index.ts
  • packages/docs/agent-docs/docs/search.md
  • packages/docs/package.json
  • packages/docs/src/search/node.ts
  • packages/docs/src/search/bash.ts
  • packages/docs/src/search/search.ts
📚 Learning: 2026-02-20T14:21:19.208Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-02-20T14:21:19.208Z
Learning: Applies to **/index.{ts,tsx,js,jsx} : Avoid barrel files (index files that re-export everything)

Applied to files:

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

Applied to files:

  • packages/docs/src/search/node-index.ts
  • packages/docs/src/search/bash-index.ts
  • packages/docs/tsup.config.ts
  • packages/docs/src/search/index.ts
  • apps/docs-smoke/src/lib/search.ts
  • packages/docs/src/search/ai-index.ts
📚 Learning: 2026-04-18T15:01:52.722Z
Learnt from: CR
Repo: inthhq/docs PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-18T15:01:52.722Z
Learning: Applies to **/index.{js,ts,jsx,tsx} : Avoid barrel files (index files that re-export everything)

Applied to files:

  • packages/docs/src/search/node-index.ts
  • packages/docs/src/search/bash-index.ts
  • packages/docs/tsup.config.ts
  • packages/docs/src/search/index.ts
  • packages/docs/package.json
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `export type` for types

Applied to files:

  • packages/docs/src/search/node-index.ts
📚 Learning: 2026-04-16T03:27:04.353Z
Learnt from: CR
Repo: inthhq/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-16T03:27:04.353Z
Learning: Applies to **/index.{js,jsx,ts,tsx} : Avoid barrel files (index files that re-export everything)

Applied to files:

  • packages/docs/src/search/node-index.ts
  • packages/docs/src/search/bash-index.ts
  • packages/docs/tsup.config.ts
  • packages/docs/src/search/index.ts
  • packages/docs/package.json
  • apps/docs-smoke/src/routeTree.gen.ts
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.mdx : Use MDX for documentation pages in the docs app

Applied to files:

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

Applied to files:

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

Applied to files:

  • apps/docs-smoke/scripts/mdx-convert.ts
  • apps/docs-smoke/content/docs/search.mdx
  • apps/docs-smoke/src/mdx-components.tsx
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't use __dirname and __filename in the global scope

Applied to files:

  • apps/docs-smoke/scripts/mdx-convert.ts
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/app/**/page.tsx : Place page components in appropriate route segments within the docs app

Applied to files:

  • apps/docs-smoke/src/routes/docs/search.tsx
  • apps/docs-smoke/src/routes/api/docs/search.ts
  • apps/docs-smoke/src/lib/docs.ts
  • apps/docs-smoke/tests/e2e/smoke.e2e.ts
  • apps/docs-smoke/src/routes/search.tsx
  • apps/docs-smoke/src/routes/api/docs/ask.ts
  • apps/docs-smoke/src/mdx-components.tsx
  • apps/docs-smoke/src/routeTree.gen.ts
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/app/**/*.{ts,tsx} : Use Next.js App Router conventions in the docs app

Applied to files:

  • apps/docs-smoke/src/routes/docs/search.tsx
  • apps/docs-smoke/src/routes/api/docs/search.ts
  • apps/docs-smoke/src/lib/docs.ts
  • apps/docs-smoke/src/routes/search.tsx
  • apps/docs-smoke/src/routes/api/docs/ask.ts
  • apps/docs-smoke/src/routeTree.gen.ts
📚 Learning: 2026-03-19T22:57:19.913Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/app/**/page.tsx : Place page components in appropriate route segments

Applied to files:

  • apps/docs-smoke/src/routes/docs/search.tsx
  • apps/docs-smoke/src/routes/api/docs/search.ts
  • apps/docs-smoke/src/lib/docs.ts
  • apps/docs-smoke/src/routes/search.tsx
  • apps/docs-smoke/src/routeTree.gen.ts
📚 Learning: 2026-03-19T16:08:15.951Z
Learnt from: CR
Repo: consentdotio/c-ray PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-19T16:08:15.951Z
Learning: When managing URL search params, load the router-core search-params skill file from node_modules/tanstack/router-core/skills/router-core/search-params/SKILL.md

Applied to files:

  • apps/docs-smoke/src/routes/docs/search.tsx
  • apps/docs-smoke/src/routes/api/docs/search.ts
  • apps/docs-smoke/src/routes/search.tsx
  • apps/docs-smoke/src/routes/api/docs/ask.ts
  • apps/docs-smoke/src/routeTree.gen.ts
📚 Learning: 2026-03-19T22:57:19.913Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/app/**/* : Use Next.js App Router conventions

Applied to files:

  • apps/docs-smoke/src/routes/docs/search.tsx
  • apps/docs-smoke/src/routes/api/docs/search.ts
  • apps/docs-smoke/src/lib/docs.ts
  • apps/docs-smoke/src/routes/search.tsx
  • apps/docs-smoke/src/routes/api/docs/ask.ts
  • apps/docs-smoke/src/routeTree.gen.ts
📚 Learning: 2026-03-19T16:08:15.951Z
Learnt from: CR
Repo: consentdotio/c-ray PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-19T16:08:15.951Z
Learning: When adding new routes and pages, load the router-core skill file from node_modules/tanstack/router-core/skills/router-core/SKILL.md

Applied to files:

  • apps/docs-smoke/src/routes/docs/search.tsx
  • apps/docs-smoke/src/routes/search.tsx
  • apps/docs-smoke/src/routes/api/docs/ask.ts
  • apps/docs-smoke/src/routeTree.gen.ts
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/app/**/{layout,page}.tsx : Keep layouts and pages separate in the docs app directory

Applied to files:

  • apps/docs-smoke/src/routes/docs/search.tsx
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/src/components/**/*.tsx : Place reusable components in `src/components` within the docs app

Applied to files:

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

Applied to files:

  • apps/docs-smoke/src/routes/docs/search.tsx
  • apps/docs-smoke/src/lib/search.ts
📚 Learning: 2026-03-19T16:08:15.951Z
Learnt from: CR
Repo: consentdotio/c-ray PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-19T16:08:15.951Z
Learning: When building API endpoints and server routes, load the start-core server-routes skill file from node_modules/tanstack/start-client-core/skills/start-core/server-routes/SKILL.md

Applied to files:

  • apps/docs-smoke/src/routes/docs/search.tsx
  • apps/docs-smoke/src/routes/api/docs/search.ts
  • apps/docs-smoke/src/routes/api/docs/ask.ts
  • apps/docs-smoke/src/routeTree.gen.ts
📚 Learning: 2026-03-19T16:08:15.951Z
Learnt from: CR
Repo: consentdotio/c-ray PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-19T16:08:15.951Z
Learning: When setting up navigation and links between pages, load the router-core navigation skill file from node_modules/tanstack/router-core/skills/router-core/navigation/SKILL.md

Applied to files:

  • apps/docs-smoke/src/routes/docs/search.tsx
  • apps/docs-smoke/src/routeTree.gen.ts
📚 Learning: 2026-04-18T15:01:52.722Z
Learnt from: CR
Repo: inthhq/docs PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-18T15:01:52.722Z
Learning: Applies to **/*.{jsx,tsx} : Use Server Components for async data fetching instead of async Client Components in Next.js

Applied to files:

  • apps/docs-smoke/src/routes/docs/search.tsx
📚 Learning: 2025-12-01T15:20:25.102Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/open-graph.mdc:0-0
Timestamp: 2025-12-01T15:20:25.102Z
Learning: Applies to src/**/route.{ts,tsx} : Read dynamic content from `request.nextUrl.searchParams` and pass into pure component props rather than fetching during render

Applied to files:

  • apps/docs-smoke/src/routes/docs/search.tsx
  • apps/docs-smoke/src/routes/api/docs/search.ts
  • apps/docs-smoke/src/routes/search.tsx
  • apps/docs-smoke/src/routeTree.gen.ts
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{test,spec}.{ts,tsx} : Write unit tests for components in Elements package using Vitest

Applied to files:

  • packages/docs/src/search/node.test.ts
  • packages/docs/src/search/bash.test.ts
  • packages/docs/src/search/search.test.ts
  • packages/docs/package.json
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.test.{ts,tsx} : Use Vitest for testing framework

Applied to files:

  • packages/docs/src/search/node.test.ts
  • packages/docs/src/search/bash.test.ts
  • packages/docs/src/search/search.test.ts
  • packages/docs/package.json
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{test,spec}.{ts,tsx} : Use Vitest as the testing framework for Elements package

Applied to files:

  • packages/docs/src/search/node.test.ts
  • packages/docs/src/search/bash.test.ts
  • packages/docs/src/search/search.test.ts
  • packages/docs/package.json
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.test.{ts,tsx} : Write unit tests for components

Applied to files:

  • packages/docs/src/search/node.test.ts
  • packages/docs/src/search/bash.test.ts
  • packages/docs/src/search/search.test.ts
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{test,spec}.{ts,tsx} : Include integration tests for Elements package components

Applied to files:

  • packages/docs/src/search/node.test.ts
  • packages/docs/src/search/bash.test.ts
  • apps/docs-smoke/tests/e2e/smoke.e2e.ts
  • packages/docs/src/search/search.test.ts
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{test,spec}.{ts,tsx} : Test accessibility features in Elements package component tests

Applied to files:

  • packages/docs/src/search/node.test.ts
  • packages/docs/src/search/bash.test.ts
  • apps/docs-smoke/tests/e2e/smoke.e2e.ts
  • packages/docs/src/search/search.test.ts
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.test.{ts,tsx} : Include integration tests

Applied to files:

  • packages/docs/src/search/node.test.ts
  • packages/docs/src/search/bash.test.ts
  • packages/docs/src/search/search.test.ts
📚 Learning: 2026-03-19T22:58:00.031Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc:0-0
Timestamp: 2026-03-19T22:58:00.031Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Write comprehensive unit tests with clear test case names describing what is being tested

Applied to files:

  • packages/docs/src/search/node.test.ts
  • packages/docs/src/search/bash.test.ts
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.test.{ts,tsx} : Test accessibility features

Applied to files:

  • packages/docs/src/search/node.test.ts
  • packages/docs/src/search/bash.test.ts
  • apps/docs-smoke/tests/e2e/smoke.e2e.ts
  • packages/docs/src/search/search.test.ts
📚 Learning: 2026-02-20T14:21:19.208Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-02-20T14:21:19.208Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : Write assertions inside `it()` or `test()` blocks

Applied to files:

  • packages/docs/src/search/node.test.ts
📚 Learning: 2026-04-18T15:01:52.722Z
Learnt from: CR
Repo: inthhq/docs PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-18T15:01:52.722Z
Learning: Applies to **/*.{test,spec}.{js,ts,jsx,tsx} : Write assertions inside `it()` or `test()` blocks

Applied to files:

  • packages/docs/src/search/node.test.ts
📚 Learning: 2026-04-16T03:27:04.353Z
Learnt from: CR
Repo: inthhq/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-16T03:27:04.353Z
Learning: Applies to **/*.{test,spec}.{js,jsx,ts,tsx} : Write assertions inside `it()` or `test()` blocks

Applied to files:

  • packages/docs/src/search/node.test.ts
📚 Learning: 2026-02-20T14:21:19.208Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-02-20T14:21:19.208Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : Keep test suites reasonably flat - avoid excessive `describe` nesting

Applied to files:

  • packages/docs/src/search/node.test.ts
📚 Learning: 2026-02-20T14:21:40.180Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T14:21:40.180Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Keep test suites reasonably flat - avoid excessive `describe` nesting

Applied to files:

  • packages/docs/src/search/node.test.ts
📚 Learning: 2026-04-18T15:01:52.722Z
Learnt from: CR
Repo: inthhq/docs PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-18T15:01:52.722Z
Learning: Applies to **/*.{test,spec}.{js,ts,jsx,tsx} : Keep test suites reasonably flat - avoid excessive `describe` nesting

Applied to files:

  • packages/docs/src/search/node.test.ts
📚 Learning: 2026-04-16T03:27:04.353Z
Learnt from: CR
Repo: inthhq/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-16T03:27:04.353Z
Learning: Applies to **/*.{test,spec}.{js,jsx,ts,tsx} : Keep test suites reasonably flat - avoid excessive `describe` nesting

Applied to files:

  • packages/docs/src/search/node.test.ts
📚 Learning: 2026-02-20T14:21:40.180Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T14:21:40.180Z
Learning: Applies to **/*.test.{ts,tsx,js,jsx} : Write assertions inside `it()` or `test()` blocks

Applied to files:

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

Applied to files:

  • apps/docs-smoke/package.json
📚 Learning: 2026-03-19T16:08:15.951Z
Learnt from: CR
Repo: consentdotio/c-ray PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-19T16:08:15.951Z
Learning: When handling errors and 404 pages, load the router-core not-found-and-errors skill file from node_modules/tanstack/router-core/skills/router-core/not-found-and-errors/SKILL.md

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

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

Applied to files:

  • apps/docs-smoke/src/lib/docs.ts
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{test,spec}.{ts,tsx,js,jsx} : Include accessibility tests for components

Applied to files:

  • apps/docs-smoke/src/lib/docs.ts
  • apps/docs-smoke/tests/e2e/smoke.e2e.ts
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{test,spec}.{ts,tsx} : Test with screen readers in Elements package component tests

Applied to files:

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

Applied to files:

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

Applied to files:

  • packages/docs/tsup.config.ts
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use the `node:` protocol for Node.js builtin modules

Applied to files:

  • packages/docs/tsup.config.ts
  • packages/docs/package.json
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Make sure to use the "use strict" directive in script files

Applied to files:

  • packages/docs/tsup.config.ts
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.{ts,tsx} : Include TypeScript examples

Applied to files:

  • packages/docs/tsup.config.ts
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `with { type: "json" }` for JSON module imports

Applied to files:

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

Applied to files:

  • packages/docs/package.json
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Make sure all dependencies are correctly specified in React hooks

Applied to files:

  • packages/docs/package.json
📚 Learning: 2026-02-20T14:21:19.208Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-02-20T14:21:19.208Z
Learning: Applies to **/*.{tsx,jsx} : Specify all dependencies in hook dependency arrays correctly

Applied to files:

  • packages/docs/package.json
📚 Learning: 2026-04-16T03:27:04.353Z
Learnt from: CR
Repo: inthhq/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-16T03:27:04.353Z
Learning: Applies to **/*.{jsx,tsx} : Specify all dependencies in hook dependency arrays correctly

Applied to files:

  • packages/docs/package.json
📚 Learning: 2026-02-20T14:21:19.208Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-02-20T14:21:19.208Z
Learning: Applies to **/*.{tsx,jsx,html} : Use proper heading hierarchy in HTML/React components

Applied to files:

  • apps/docs-smoke/src/mdx-components.tsx
📚 Learning: 2026-02-01T08:57:09.557Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/typography.mdc:0-0
Timestamp: 2026-02-01T08:57:09.557Z
Learning: Applies to **/*.{ts,tsx} : Use `text-title-h1` through `text-title-h6` tokens for heading elements, with appropriate line-height, letter-spacing, and font-weight already included

Applied to files:

  • apps/docs-smoke/src/mdx-components.tsx
📚 Learning: 2026-03-19T22:57:54.730Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/typography.mdc:0-0
Timestamp: 2026-03-19T22:57:54.730Z
Learning: Applies to **/*.{ts,tsx,css} : Use `text-title-h1` through `text-title-h6` tokens for heading elements with appropriate line-height, letter-spacing, and font-weight based on semantic heading level

Applied to files:

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

Applied to files:

  • apps/docs-smoke/src/mdx-components.tsx
📚 Learning: 2026-02-20T14:21:40.180Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T14:21:40.180Z
Learning: Applies to **/*.{tsx,jsx} : Use `next/head` or App Router metadata API for head elements

Applied to files:

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

Applied to files:

  • packages/docs/src/search/node.ts
  • packages/docs/src/search/search.ts
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't export empty modules that don't change anything

Applied to files:

  • packages/docs/src/search/ai-index.ts
📚 Learning: 2026-03-19T16:08:15.951Z
Learnt from: CR
Repo: consentdotio/c-ray PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-19T16:08:15.951Z
Learning: When implementing type safety patterns, load the router-core type-safety skill file from node_modules/tanstack/router-core/skills/router-core/type-safety/SKILL.md

Applied to files:

  • apps/docs-smoke/src/routeTree.gen.ts
📚 Learning: 2026-03-19T16:08:15.951Z
Learnt from: CR
Repo: consentdotio/c-ray PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-19T16:08:15.951Z
Learning: When adding auth and route guards, load the router-core auth-and-guards skill file from node_modules/tanstack/router-core/skills/router-core/auth-and-guards/SKILL.md

Applied to files:

  • apps/docs-smoke/src/routeTree.gen.ts
📚 Learning: 2025-12-17T14:48:41.563Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2025-12-17T14:48:41.563Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Don't import next/document outside of pages/_document.jsx in Next.js projects

Applied to files:

  • apps/docs-smoke/src/routeTree.gen.ts
📚 Learning: 2026-03-19T16:08:15.951Z
Learnt from: CR
Repo: consentdotio/c-ray PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-19T16:08:15.951Z
Learning: When fetching data in route loaders, load the router-core data-loading skill file from node_modules/tanstack/router-core/skills/router-core/data-loading/SKILL.md

Applied to files:

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

Applied to files:

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

Applied to files:

  • packages/docs/src/search/search.ts
🪛 LanguageTool
apps/docs-smoke/content/docs/search.mdx

[uncategorized] ~41-~41: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...st, then generate the search index from markdown: ```ts import { generateSearchIndex } ...

(MARKDOWN_NNP)

🔍 Remote MCP

Let me search for more specific information using a web search to supplement the Context7 limitations:Now let me search for information about request rate limiting and security considerations for search/AI APIs:## Summary of Additional Context Found

Based on my research, here's the relevant context gathered for reviewing this PR:

AI SDK Integration Context

The Vercel AI SDK's streamText function is designed for interactive use cases like chat bots and real-time applications, reducing boilerplate for streaming responses from AI providers and allowing easy provider switching. The PR integrates this via the new /api/docs/ask endpoint and streamDocsAnswer helper function, which aligns with the SDK's intended use pattern.

Search Algorithm Context: BM25 Implementation

BM25 (Best Matching 25) is a ranking algorithm used in information retrieval to determine document relevance, and is an improved version of TF-IDF that is widely used in modern search engines and databases. BM25 is a bag-of-words retrieval function that ranks documents based on query terms appearing in each document, regardless of their proximity.

The PR implements a BM25-like approach (1025 lines in search.ts) with:

  • Text normalization: lowercasing, diacritic removal, stopword filtering
  • Inverted indexing: tracking term postings across document sections
  • Term frequency weighting: with saturation to prevent keyword stuffing
  • Document length normalization: fair scoring across documents of varying lengths

Rate Limiting Context

Rate limiting protects APIs from abuse, DoS attacks, and accidental overuse by enhancing security, preventing brute force attacks, and improving performance through fair resource allocation.

For the in-memory implementation used in this PR: The in-memory approach uses a JavaScript Map object to track request timestamps per IP address, storing the current timestamp and checking how many requests were made in the last fixed window, blocking requests if the count exceeds the allowed limit, which is lightweight and great for learning, development, or single-server applications. However, it doesn't scale well — since each server instance has its own memory, users could bypass limits by hitting multiple instances.

The PR creates two memory-based limiters (ask and search) with fixed 60-second windows and explicit request limits, appropriate for a demo application.

Request Validation and Security Concerns

The PR implements several security layers documented in the code summary:

  • Query validation: length limits and control character checks via validateDocsQuery
  • Body size limits: via readJsonWithLimit with configurable max bytes
  • Client identification: extracting identifiers from request headers
  • Rate limiting with proper HTTP responses: 429 status codes with Retry-After headers

Key Design Characteristics

  1. Compact index format: Separates document metadata from answer content, reducing index size
  2. Static generation: Build-time index generation via generateSearchIndex (Node-only)
  3. Local search-first approach: Works without AI dependencies; AI/bash adapters are optional
  4. Source-grounded answers: Retrieved context is injected into prompts with citation markers [1], [2], etc.
  5. Bash tool sandbox: Read-only /docs filesystem for agent tool use with blocked network/write operations

Comment thread apps/docs-smoke/content/docs/index.mdx
Comment on lines +18 to +21
const result = await generateSearchIndex({
outDir,
baseUrl: "https://docs.example.com",
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid hardcoding a placeholder baseUrl in generated search artifacts.

Using https://docs.example.com here can produce incorrect result/source URLs outside that exact host.

Proposed fix
+const baseUrl =
+  process.env.INTH_DOCS_BASE_URL ?? "http://localhost:3000";
+
 const result = await generateSearchIndex({
   outDir,
-  baseUrl: "https://docs.example.com",
+  baseUrl,
 });
📝 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
const result = await generateSearchIndex({
outDir,
baseUrl: "https://docs.example.com",
});
const baseUrl =
process.env.INTH_DOCS_BASE_URL ?? "http://localhost:3000";
const result = await generateSearchIndex({
outDir,
baseUrl,
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/docs-smoke/scripts/search-generate.ts` around lines 18 - 21, The
generateSearchIndex invocation hardcodes baseUrl to "https://docs.example.com",
which produces incorrect result/source URLs; update the call in
search-generate.ts (the generateSearchIndex({ outDir, baseUrl }) call) to derive
baseUrl dynamically (e.g., from an environment variable, build config, or
package/site metadata) and pass that value instead of the literal string so
generated artifacts contain the actual site base URL.

Comment on lines +8 to +16
function textFromChildren(children: HeadingProps["children"]): string {
if (typeof children === "string" || typeof children === "number") {
return String(children);
}
if (Array.isArray(children)) {
return children.map(textFromChildren).join(" ");
}
return "";
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find apps/docs-smoke -name "mdx-components.tsx" -type f

Repository: inthhq/docs

Length of output: 93


🏁 Script executed:

fd -t f "mdx-components" apps/

Repository: inthhq/docs

Length of output: 93


🏁 Script executed:

cat -n apps/docs-smoke/src/mdx-components.tsx

Repository: inthhq/docs

Length of output: 1599


🏁 Script executed:

# Check how this file is used and if there are tests
cd apps/docs-smoke && find . -type f \( -name "*.test.*" -o -name "*.spec.*" \) | head -20

Repository: inthhq/docs

Length of output: 37


🏁 Script executed:

# Search for any usage or imports of textFromChildren or useMDXComponents
rg "textFromChildren|useMDXComponents" --type tsx --type ts

Repository: inthhq/docs

Length of output: 82


🏁 Script executed:

# Check the mdxComponents from `@inth/docs` to see if there's similar logic
rg "textFromChildren" --type tsx --type ts --type jsx

Repository: inthhq/docs

Length of output: 82


🏁 Script executed:

rg "textFromChildren|useMDXComponents" -t ts -t js

Repository: inthhq/docs

Length of output: 652


🏁 Script executed:

# Check what slugifyDocsHeading does and if it handles React elements
rg "slugifyDocsHeading" -A 10 -B 2 -t ts -t js

Repository: inthhq/docs

Length of output: 5600


🏁 Script executed:

# Look for similar heading handling in the `@inth/docs` package
find . -name "*heading*" -o -name "*slug*" | grep -E "\.(ts|tsx|js)$" | head -20

Repository: inthhq/docs

Length of output: 37


🏁 Script executed:

# Check how headings are extracted for the search index
rg "headingPath" -B 5 -A 5 -t js -t ts

Repository: inthhq/docs

Length of output: 15863


🏁 Script executed:

# Look at the mdx parsing logic to understand heading text extraction
find . -name "*parse*" -o -name "*md*" | grep -E "\.(ts|tsx|js)$" | grep -v node_modules | head -20

Repository: inthhq/docs

Length of output: 216


🏁 Script executed:

# Check the actual MDX content structure - look for markdown files
find apps/docs-smoke -name "*.md" -o -name "*.mdx" | head -5

Repository: inthhq/docs

Length of output: 307


🏁 Script executed:

# Check what cleanMarkdown does
rg "function cleanMarkdown" -A 10 -B 2 -t ts -t js

Repository: inthhq/docs

Length of output: 782


🏁 Script executed:

# Look at the mdx-convert script to understand how MDX content is processed
head -80 apps/docs-smoke/scripts/mdx-convert.ts

Repository: inthhq/docs

Length of output: 1510


🏁 Script executed:

# Check an actual example MDX file to see heading structure
cat apps/docs-smoke/content/docs/guides/quickstart.mdx | head -50

Repository: inthhq/docs

Length of output: 774


🏁 Script executed:

# Check how headings with inline markdown are handled during MDX conversion
rg "heading" -B 3 -A 3 -t ts packages/docs/src/convert/ | head -50

Repository: inthhq/docs

Length of output: 37


🏁 Script executed:

# Look for test cases that might show heading behavior with inline elements
rg "textFromChildren|heading" apps/docs-smoke --type ts -B 2 -A 2 | head -80

Repository: inthhq/docs

Length of output: 5536


🏁 Script executed:

# Check if there are actual heading examples with inline code or elements
rg "^#+.*\`" apps/docs-smoke/content/ --type mdx -A 1 -B 1

Repository: inthhq/docs

Length of output: 82


🏁 Script executed:

# Check for headings with inline markdown syntax in MDX files
find apps/docs-smoke/content -name "*.mdx" -type f -exec grep -H "^#.*\`" {} \;

Repository: inthhq/docs

Length of output: 37


🏁 Script executed:

# Look at what mdxComponents provides to understand if there are heading overrides there
cat packages/docs/src/components/mdx-components.ts

Repository: inthhq/docs

Length of output: 988


🏁 Script executed:

# Check if isValidElement is used elsewhere in the codebase
rg "isValidElement" -t ts -t js

Repository: inthhq/docs

Length of output: 37


🏁 Script executed:

# Search all content files for headings that might have inline code or other markdown formatting
grep -r "^#" apps/docs-smoke/content --include="*.mdx" | head -20

Repository: inthhq/docs

Length of output: 1185


🏁 Script executed:

# Check how MDX compiler renders headings - look for actual usage patterns
find apps/docs-smoke -name "*.mdx" -type f | xargs cat | grep -A 2 "^##"

Repository: inthhq/docs

Length of output: 1227


🏁 Script executed:

# Verify the flow: check if the converted markdown files are the source for search index
rg "collectSectionBlocks|DocsSearchDocument" -B 5 -A 5 -t ts | head -60

Repository: inthhq/docs

Length of output: 3578


🏁 Script executed:

# Look more carefully for headings with backticks or other inline formatting in MDX
find apps/docs-smoke/content -name "*.mdx" -type f | xargs grep -E "^#+.*\`"

Repository: inthhq/docs

Length of output: 39


🏁 Script executed:

# Check the test fixtures for heading with inline elements
cat apps/docs-smoke/content/docs/guides/components-fixture.mdx

Repository: inthhq/docs

Length of output: 3438


🏁 Script executed:

# Understand the exact flow: when MDX renders headings with inline code, what does React receive?
# Look at how convertAllMdx works to understand markdown -> React
cat packages/docs/src/convert/index.ts | head -100

Repository: inthhq/docs

Length of output: 192


🏁 Script executed:

# Final verification: check if there are any tests or examples of headings with inline elements
rg "createHeading|h1|h2|h3|h4|h5|h6" apps/docs-smoke/tests -A 5 -B 2

Repository: inthhq/docs

Length of output: 37


🏁 Script executed:

# Confirm the fix approach is idiomatic by checking React imports elsewhere
rg "isValidElement" packages/docs -B 2 -A 2 -t ts

Repository: inthhq/docs

Length of output: 37


Heading ID generation is incomplete for inline React elements.

When MDX renders headings with inline code (e.g., ## `apiKey` setup), textFromChildren skips the <code> element and returns an empty string, producing incomplete IDs. The search index generates complete IDs via markdown regex parsing, causing anchor mismatch.

Recursively process React elements to extract their text content:

Proposed fix
-import type { ComponentPropsWithoutRef } from "react";
+import {
+  isValidElement,
+  type ComponentPropsWithoutRef,
+  type ReactNode,
+} from "react";
@@
-function textFromChildren(children: HeadingProps["children"]): string {
+function textFromChildren(children: ReactNode): string {
   if (typeof children === "string" || typeof children === "number") {
     return String(children);
   }
   if (Array.isArray(children)) {
     return children.map(textFromChildren).join(" ");
   }
+  if (isValidElement<{ children?: ReactNode }>(children)) {
+    return textFromChildren(children.props.children);
+  }
   return "";
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function textFromChildren(children: HeadingProps["children"]): string {
if (typeof children === "string" || typeof children === "number") {
return String(children);
}
if (Array.isArray(children)) {
return children.map(textFromChildren).join(" ");
}
return "";
}
import {
isValidElement,
type ComponentPropsWithoutRef,
type ReactNode,
} from "react";
function textFromChildren(children: ReactNode): string {
if (typeof children === "string" || typeof children === "number") {
return String(children);
}
if (Array.isArray(children)) {
return children.map(textFromChildren).join(" ");
}
if (isValidElement<{ children?: ReactNode }>(children)) {
return textFromChildren(children.props.children);
}
return "";
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/docs-smoke/src/mdx-components.tsx` around lines 8 - 16, The
textFromChildren function currently only handles strings, numbers, and arrays,
so it returns empty for React elements like <code> and other inline components;
update textFromChildren (and its handling of HeadingProps["children"]) to
recursively handle React elements/objects by checking for an object with a props
property and extracting/recursing into props.children (also handle
null/boolean/undefined by returning ""), so inline elements (e.g., <code>)
contribute their textual content to generated heading IDs and match the
markdown-derived IDs.

Comment on lines +36 to +54
useEffect(() => {
let active = true;
async function loadAnswerConfig() {
const response = await fetch("/api/docs/ask");
if (!response.ok) {
return;
}
const data = (await response.json()) as AnswerConfig;
if (active) {
setAnswerConfig(data);
setAnswerStatus(data.enabled ? "idle" : "disabled");
}
}
const configPromise = loadAnswerConfig();
configPromise.catch(() => undefined);
return () => {
active = 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.

🧹 Nitpick | 🔵 Trivial

Silent error handling in config loading.

The catch(() => undefined) on line 50 silently discards errors from the config fetch. While this is acceptable for a non-critical feature toggle, consider logging the error in development for debugging:

-    configPromise.catch(() => undefined);
+    configPromise.catch((error) => {
+      if (process.env.NODE_ENV === "development") {
+        console.error("Failed to load answer config:", error);
+      }
+    });
📝 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
useEffect(() => {
let active = true;
async function loadAnswerConfig() {
const response = await fetch("/api/docs/ask");
if (!response.ok) {
return;
}
const data = (await response.json()) as AnswerConfig;
if (active) {
setAnswerConfig(data);
setAnswerStatus(data.enabled ? "idle" : "disabled");
}
}
const configPromise = loadAnswerConfig();
configPromise.catch(() => undefined);
return () => {
active = false;
};
}, []);
useEffect(() => {
let active = true;
async function loadAnswerConfig() {
const response = await fetch("/api/docs/ask");
if (!response.ok) {
return;
}
const data = (await response.json()) as AnswerConfig;
if (active) {
setAnswerConfig(data);
setAnswerStatus(data.enabled ? "idle" : "disabled");
}
}
const configPromise = loadAnswerConfig();
configPromise.catch((error) => {
if (process.env.NODE_ENV === "development") {
console.error("Failed to load answer config:", error);
}
});
return () => {
active = false;
};
}, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/docs-smoke/src/routes/search.tsx` around lines 36 - 54, Silent
swallowing of errors in loadAnswerConfig's configPromise.catch hides useful
debug info; update the catch on the promise returned by loadAnswerConfig (the
configPromise created inside the useEffect) to log the error in
non-production/dev environments (e.g., using console.error or the app's logger)
while preserving the current silent behavior in production, so replace catch(()
=> undefined) with a handler that checks the environment and logs the caught
error with context about fetching /api/docs/ask.

Comment thread packages/docs/README.md
maxOutputTokens: options.maxOutputTokens ?? DEFAULT_MAX_OUTPUT_TOKENS,
timeout: options.timeout ?? DEFAULT_TIMEOUT,
providerOptions: options.providerOptions,
onError: () => undefined,
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

Silent error swallowing hides streaming failures.

The onError callback discards all errors without logging or propagation. This makes debugging streaming failures difficult and could mask transient issues with the AI provider.

🔧 Proposed fix to log errors
-    onError: () => undefined,
+    onError: ({ error }) => {
+      console.error("[streamDocsAnswer] Streaming error:", error);
+    },

Alternatively, consider exposing an onError option in StreamDocsAnswerOptions to let callers handle errors appropriately.

📝 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
onError: () => undefined,
onError: ({ error }) => {
console.error("[streamDocsAnswer] Streaming error:", error);
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/docs/src/search/ai.ts` at line 71, The current onError: () =>
undefined in packages/docs/src/search/ai.ts silently swallows streaming errors;
change the implementation of the onError callback used by StreamDocsAnswer (and
any internal streaming handler) to at minimum log the error via the existing
logger (or console.error) and include the error object, and extend
StreamDocsAnswerOptions to accept an optional onError handler so callers can
override default behavior (use the provided onError if present, otherwise log
and optionally rethrow/propagate). Ensure references to the onError hook in
StreamDocsAnswerOptions and any streaming function (e.g., StreamDocsAnswer / the
streaming callback registration) are updated to call the option-provided handler
with the error.

Comment on lines +127 to +149
for (const filePath of files) {
const relativePath = path
.relative(docsDir, filePath)
.replace(WINDOWS_PATH_PATTERN, "/");
const raw = await readFile(filePath, "utf-8");
const parsed = matter(raw);
const title =
String(parsed.data.title ?? "").trim() ||
titleFromRelativePath(relativePath);
const description = normalizeDescription(
String(parsed.data.description ?? "")
);
const urlPath = toUrlPath(relativePath);
docs.push({
id: relativePath.replace(MD_EXTENSION_PATTERN, ""),
title,
description,
urlPath,
absoluteUrl: toAbsoluteUrl(urlPath, baseUrl),
relativePath: relativePath.replace(MD_EXTENSION_PATTERN, ""),
content: parsed.content.trim(),
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider parallel file reading for larger doc sets.

The sequential for...of loop reads files one at a time. For repositories with many markdown files, parallel reading could improve build performance.

♻️ Proposed parallel version
-  for (const filePath of files) {
-    const relativePath = path
-      .relative(docsDir, filePath)
-      .replace(WINDOWS_PATH_PATTERN, "/");
-    const raw = await readFile(filePath, "utf-8");
-    const parsed = matter(raw);
-    const title =
-      String(parsed.data.title ?? "").trim() ||
-      titleFromRelativePath(relativePath);
-    const description = normalizeDescription(
-      String(parsed.data.description ?? "")
-    );
-    const urlPath = toUrlPath(relativePath);
-    docs.push({
-      id: relativePath.replace(MD_EXTENSION_PATTERN, ""),
-      title,
-      description,
-      urlPath,
-      absoluteUrl: toAbsoluteUrl(urlPath, baseUrl),
-      relativePath: relativePath.replace(MD_EXTENSION_PATTERN, ""),
-      content: parsed.content.trim(),
-    });
-  }
-
-  return docs;
+  const docs = await Promise.all(
+    files.map(async (filePath) => {
+      const relativePath = path
+        .relative(docsDir, filePath)
+        .replace(WINDOWS_PATH_PATTERN, "/");
+      const raw = await readFile(filePath, "utf-8");
+      const parsed = matter(raw);
+      const title =
+        String(parsed.data.title ?? "").trim() ||
+        titleFromRelativePath(relativePath);
+      const description = normalizeDescription(
+        String(parsed.data.description ?? "")
+      );
+      const urlPath = toUrlPath(relativePath);
+      return {
+        id: relativePath.replace(MD_EXTENSION_PATTERN, ""),
+        title,
+        description,
+        urlPath,
+        absoluteUrl: toAbsoluteUrl(urlPath, baseUrl),
+        relativePath: relativePath.replace(MD_EXTENSION_PATTERN, ""),
+        content: parsed.content.trim(),
+      };
+    })
+  );
+
+  return docs;

Note: The current sequential approach maintains deterministic ordering and avoids potential file handle exhaustion. This is a nice-to-have optimization for very large doc sets.

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

In `@packages/docs/src/search/node.ts` around lines 127 - 149, The current
sequential for...of over files performs readFile and parsing one at a time
(using files, docsDir, readFile, matter, titleFromRelativePath, toUrlPath,
toAbsoluteUrl, docs.push), which is slow for large doc sets; refactor to process
files in parallel by mapping files to an array of promises that read and parse
each file (preserving relativePath, title, description, content, urlPath,
absoluteUrl using the same helper calls) and then await Promise.all to get
results, finally push or assign them into docs in the original files order
(e.g., build an array of items from Promise.all and set docs = items or splice
into docs) so you keep deterministic ordering while gaining concurrency;
consider using a concurrency limiter (p-limit) if file-handle exhaustion is a
concern.

Comment on lines +960 to +999
export function createMemoryRateLimiter(
options: MemoryRateLimiterOptions
): RateLimiter {
const entries = new Map<string, { count: number; resetAt: number }>();
const now = options.now ?? Date.now;

return {
check(identifier: string): RateLimitResult {
const currentTime = now();
const existing = entries.get(identifier);
if (!existing || existing.resetAt <= currentTime) {
const resetAt = currentTime + options.windowMs;
entries.set(identifier, { count: 1, resetAt });
return {
allowed: true,
limit: options.limit,
remaining: Math.max(0, options.limit - 1),
resetAt,
};
}

if (existing.count >= options.limit) {
return {
allowed: false,
limit: options.limit,
remaining: 0,
resetAt: existing.resetAt,
};
}

existing.count += 1;
return {
allowed: true,
limit: options.limit,
remaining: Math.max(0, options.limit - existing.count),
resetAt: existing.resetAt,
};
},
};
}
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

Memory leak: Rate limiter entries never expire.

The entries Map grows unbounded. Each unique client identifier persists indefinitely, even after its window expires. In production with many unique IPs, this will consume increasing memory.

🔧 Proposed fix with periodic cleanup
 export function createMemoryRateLimiter(
   options: MemoryRateLimiterOptions
 ): RateLimiter {
   const entries = new Map<string, { count: number; resetAt: number }>();
   const now = options.now ?? Date.now;
+  
+  // Periodically clean up expired entries
+  const cleanupInterval = setInterval(() => {
+    const currentTime = now();
+    for (const [key, value] of entries) {
+      if (value.resetAt <= currentTime) {
+        entries.delete(key);
+      }
+    }
+  }, options.windowMs);
+  
+  // Allow cleanup to be stopped if needed (e.g., for testing or shutdown)
+  if (typeof cleanupInterval.unref === 'function') {
+    cleanupInterval.unref();
+  }
 
   return {
     check(identifier: string): RateLimitResult {

Note: The external context mentions this is appropriate for demo applications. If this is strictly for demo/dev use, consider documenting the limitation or adding a max-entries cap.

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

In `@packages/docs/src/search/search.ts` around lines 960 - 999, The entries Map
in createMemoryRateLimiter grows without eviction; modify
createMemoryRateLimiter to remove expired entries periodically and/or on access:
inside createMemoryRateLimiter (function name) add a cleanup mechanism that
deletes entries whose resetAt <= now() — either run a setInterval cleanup using
options.cleanupIntervalMs (default e.g., 60_000) and clear it on process exit,
and additionally purge expired entry for the specific identifier at the start of
check(identifier) before reading/incrementing; optionally add an
options.maxEntries cap to reject or evict the oldest entries when entries.size
exceeds the cap to prevent unbounded growth.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
apps/docs-smoke/src/routes/search.tsx (1)

36-50: ⚠️ Potential issue | 🟡 Minor

Don’t silently discard /api/docs/ask config failures.

If loadAnswerConfig() rejects, Line 50 swallows the only diagnostic and the page just looks like AI is unavailable. Please at least log this in development or set an explicit disabled/error state so config failures are debuggable.

#!/bin/bash
set -euo pipefail
rg -n -C2 'configPromise\.catch\(\(\) => undefined\)' apps/docs-smoke/src/routes/search.tsx

Based on learnings: Applies to **/*.{ts,tsx,js,jsx} : Handle errors explicitly and avoid silent failures or unhandled promise rejections.

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

In `@apps/docs-smoke/src/routes/search.tsx` around lines 36 - 50, The current
useEffect spawns loadAnswerConfig() and then silently swallows failures with
configPromise.catch(() => undefined), which hides diagnostics; update the
loadAnswerConfig/configPromise handling (in the useEffect containing
loadAnswerConfig, loadAnswerConfig(), configPromise.catch) to explicitly handle
errors by catching the rejection and either (a) calling setAnswerStatus("error"
or "disabled") and optionally clearing setAnswerConfig, and (b) logging the
error (e.g., console.error or process-aware logger) in
non-production/development so config fetch failures are visible; ensure the code
references the existing loadAnswerConfig, configPromise.catch, setAnswerConfig
and setAnswerStatus symbols and does not leave the promise rejection silently
ignored.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/docs-smoke/src/routes/search.tsx`:
- Around line 87-115: The streaming fetch in handleAsk lacks cancellation, so
create and use an AbortController for the /api/docs/ask request in handleAsk
(store it in a ref like askControllerRef or return it from handleAsk) and pass
its signal to fetch; on receiving AbortError stop appending chunks. Then update
the search effect cleanup (the useEffect that trims query and uses runSearch) to
abort any active ask controller (askControllerRef.current?.abort()) when the
query changes or on unmount, and/or reset answer state (setAnswer("")) when
aborting so a stale stream cannot append to results for a different query;
ensure error handling distinguishes AbortError from real errors.
- Around line 56-83: runSearch currently sets setSearchStatus("loading") but can
exit via thrown network/JSON errors leaving state inconsistent and causing
unhandled rejections in callers like handleSearch/handleAsk; wrap the fetch and
response.json logic inside a try-catch in runSearch, re-throw if the caught
error is an AbortError to preserve abort semantics, and for any other error call
setSearchStatus("error"), setError(...) with a clear message, return an empty
array, and ensure finally/state paths still set results/searchStatus
appropriately so callers no longer receive unhandled rejections.

In `@packages/docs/src/search/ai.ts`:
- Around line 68-76: The helper getStreamErrorMessage currently returns raw
Error.message or string contents back to clients; change it to always return a
generic user-facing message (e.g., "The AI provider returned an error while
streaming.") and stop returning error.message/string directly. Instead, ensure
the detailed error is recorded server-side by logging it (call your existing
logger or add a logger parameter to getStreamErrorMessage) or forwarding it to
your internal error handler; remove branches that expose the raw error text and
replace them with the generic string while logging the original error details
using the logger from the surrounding module or passed into the function.
- Around line 36-40: The StreamDocsAnswerOptions type allows model:
LanguageModel | string but the code later passes a raw string to streamText via
a type assertion; update the code that calls streamText (the stream logic that
uses DEFAULT_MODEL and the model field) to first resolve string model IDs into
real LanguageModel instances (e.g., a resolveModelIdToLanguageModel helper) and
disallow blind type assertions, or remove string from StreamDocsAnswerOptions to
require a LanguageModel; also sanitize provider error messages returned to
clients (in the error handling around the streaming logic) instead of exposing
raw provider errors and stop swallowing errors silently—replace onError: () =>
undefined with onError: (err) => { const safe = sanitizeError(err); /* log full
err server-side */ return safe; } or rethrow so callers receive a sanitized
error. Ensure references to DEFAULT_MODEL, StreamDocsAnswerOptions, streamText,
and the streaming onError handler are updated accordingly.

---

Duplicate comments:
In `@apps/docs-smoke/src/routes/search.tsx`:
- Around line 36-50: The current useEffect spawns loadAnswerConfig() and then
silently swallows failures with configPromise.catch(() => undefined), which
hides diagnostics; update the loadAnswerConfig/configPromise handling (in the
useEffect containing loadAnswerConfig, loadAnswerConfig(), configPromise.catch)
to explicitly handle errors by catching the rejection and either (a) calling
setAnswerStatus("error" or "disabled") and optionally clearing setAnswerConfig,
and (b) logging the error (e.g., console.error or process-aware logger) in
non-production/development so config fetch failures are visible; ensure the code
references the existing loadAnswerConfig, configPromise.catch, setAnswerConfig
and setAnswerStatus symbols and does not leave the promise rejection silently
ignored.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: c8173c59-c149-411d-a931-0de47c55b873

📥 Commits

Reviewing files that changed from the base of the PR and between d33236f and 42c23f4.

📒 Files selected for processing (3)
  • apps/docs-smoke/src/routes/search.tsx
  • packages/docs/src/search/ai.test.ts
  • packages/docs/src/search/ai.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Prefer unknown over any when the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions

Files:

  • packages/docs/src/search/ai.test.ts
  • apps/docs-smoke/src/routes/search.tsx
  • packages/docs/src/search/ai.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Prefer for...of loops over .forEach() and indexed for loops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Use const by default, let only when reassignment is needed, never var
Always await promises in async functions - don't forget to use the return value
Use async/await syntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Remove console.log, debugger, and alert statements from production code
Throw Error objects with descriptive messages, not strings or other values
Use try-catch blocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Don't use eval() or assign directly to document.cookie
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Use descriptive names for functions, variables, and types for meaningful naming
Add comments for complex logic, but prefer self-documenting code

Files:

  • packages/docs/src/search/ai.test.ts
  • apps/docs-smoke/src/routes/search.tsx
  • packages/docs/src/search/ai.ts
**/*.{test,spec}.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{test,spec}.{js,ts,jsx,tsx}: Write assertions inside it() or test() blocks
Avoid done callbacks in async tests - use async/await instead
Don't use .only or .skip in committed code
Keep test suites reasonably flat - avoid excessive describe nesting

Files:

  • packages/docs/src/search/ai.test.ts
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{jsx,tsx}: Use function components over class components in React
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use the key prop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props
Don't define components inside other components
Avoid dangerouslySetInnerHTML unless absolutely necessary
Use proper image components (e.g., Next.js <Image>) over <img> tags
Use Next.js <Image> component for images
Use next/head or App Router metadata API for head elements in Next.js
Use Server Components for async data fetching instead of async Client Components in Next.js
Use ref as a prop instead of React.forwardRef in React 19+

Files:

  • apps/docs-smoke/src/routes/search.tsx
**/*.{jsx,tsx,html}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{jsx,tsx,html}: Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, use semantic elements instead of divs with roles
Add rel="noopener" when using target="_blank" on links

Files:

  • apps/docs-smoke/src/routes/search.tsx
🧠 Learnings (14)
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{test,spec}.{ts,tsx} : Write unit tests for components in Elements package using Vitest

Applied to files:

  • packages/docs/src/search/ai.test.ts
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/app/**/page.tsx : Place page components in appropriate route segments within the docs app

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
📚 Learning: 2026-03-19T16:08:15.951Z
Learnt from: CR
Repo: consentdotio/c-ray PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-19T16:08:15.951Z
Learning: When managing URL search params, load the router-core search-params skill file from node_modules/tanstack/router-core/skills/router-core/search-params/SKILL.md

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
📚 Learning: 2026-03-19T22:57:19.913Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/app/**/page.tsx : Place page components in appropriate route segments

Applied to files:

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

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
📚 Learning: 2025-12-01T15:20:25.102Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/open-graph.mdc:0-0
Timestamp: 2025-12-01T15:20:25.102Z
Learning: Applies to src/**/route.{ts,tsx} : Read dynamic content from `request.nextUrl.searchParams` and pass into pure component props rather than fetching during render

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
📚 Learning: 2026-03-19T16:08:15.951Z
Learnt from: CR
Repo: consentdotio/c-ray PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-19T16:08:15.951Z
Learning: When adding new routes and pages, load the router-core skill file from node_modules/tanstack/router-core/skills/router-core/SKILL.md

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
📚 Learning: 2026-04-16T03:27:04.353Z
Learnt from: CR
Repo: inthhq/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-16T03:27:04.353Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Handle errors appropriately in async code with try-catch blocks

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
  • packages/docs/src/search/ai.ts
📚 Learning: 2026-04-18T15:01:52.722Z
Learnt from: CR
Repo: inthhq/docs PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-18T15:01:52.722Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Handle errors appropriately in async code with try-catch blocks

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
  • packages/docs/src/search/ai.ts
📚 Learning: 2026-02-20T14:21:40.180Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T14:21:40.180Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Handle errors appropriately in async code with try-catch blocks

Applied to files:

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

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
  • packages/docs/src/search/ai.ts
📚 Learning: 2026-04-18T15:01:52.722Z
Learnt from: CR
Repo: inthhq/docs PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-18T15:01:52.722Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use `try-catch` blocks meaningfully - don't catch errors just to rethrow them

Applied to files:

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

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
📚 Learning: 2026-02-20T14:21:40.180Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-20T14:21:40.180Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Use `try-catch` blocks meaningfully - don't catch errors just to rethrow them

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
🔍 Remote MCP Context7

Additional context for reviewing PR #5

  • The Vercel AI SDK provides streamText which returns a streaming text result with:

    • a textStream async iterable and helpers like toTextStreamResponse()/toTextStreamResponse() for returning streamed HTTP responses.
    • options including model, prompt, maxOutputTokens, and callbacks (onChunk/onError). Example usage shows calling streamText(...) then returning result.toTextStreamResponse() from a server handler.,
  • Relevant doc pages describing streaming behavior and toTextStreamResponse:

    • streamText overview, signature, result properties (textStream, toTextStreamResponse, toUIMessageStreamResponse, etc.).
    • Example showing streaming a prompt and returning toTextStreamResponse for server handlers.
    • Notes on the text stream protocol and iterating textStream for partial outputs.

These details confirm that the PR's use of a streamText wrapper (streamDocsAnswer) and returning a text/plain streamed Response aligns with the Vercel AI SDK streaming APIs and response helpers.

🔇 Additional comments (3)
packages/docs/src/search/ai.ts (1)

125-133: onError is still silently swallowing streaming failures.

onError: () => undefined keeps provider-side failures invisible to logs and callers on the default streaming path. Please at least log the error and ideally let StreamDocsAnswerOptions accept an override so consumers can handle it explicitly.

Based on learnings, "Handle errors explicitly and avoid silent failures or unhandled promise rejections".

packages/docs/src/search/ai.test.ts (2)

19-63: Nice boundary test for prompt grounding.

Capturing the streamTextImpl call and asserting both the grounding instruction and the [1] citation marker gives good regression coverage for the prompt contract without tying the test to the SDK internals.


65-92: Good coverage for the streamed error fallback.

This test locks down the fullStream error-event behavior, which is exactly the kind of adapter edge case that tends to regress during SDK updates.

Comment thread apps/docs-smoke/src/routes/search.tsx
Comment on lines +36 to +40
export type StreamDocsAnswerOptions = {
index: DocsSearchIndex;
content?: DocsSearchContentStore;
query: string;
model?: LanguageModel | string;
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

🌐 Web query:

In the latest Vercel AI SDK, can streamText({ model })accept a plain string model id such as"openai/gpt-5.4-mini", or must modelbe aLanguageModel instance created by a provider/gateway helper? Please answer from official docs and include the relevant type signature or example.

💡 Result:

No, streamText({ model }) cannot accept a plain string model ID such as "openai/gpt-5.4-mini". The model parameter must be a LanguageModel instance created by a provider/gateway helper, such as openai('gpt-4o') from @ai-sdk/openai.

Citations:


🏁 Script executed:

cat -n packages/docs/src/search/ai.ts | sed -n '36,42p; 124,133p'

Repository: inthhq/docs

Length of output: 809


🏁 Script executed:

rg "DEFAULT_MODEL" packages/docs/src/search/ai.ts -A 2 -B 2

Repository: inthhq/docs

Length of output: 400


🏁 Script executed:

cat -n packages/docs/src/search/ai.ts | sed -n '68,76p; 92,103p'

Repository: inthhq/docs

Length of output: 868


Resolve model IDs to LanguageModel instances before calling streamText.

Line 40 types model as LanguageModel | string, but line 126 casts it with as LanguageModel without converting strings to proper instances. The Vercel AI SDK's streamText function requires a LanguageModel object—it does not accept plain string model IDs like "openai/gpt-5.4-mini". Since DEFAULT_MODEL is a string literal, the current code will fail at runtime when streamText receives a string where it expects a LanguageModel instance.

Either remove string from the public type, or add explicit logic to resolve string model IDs to proper LanguageModel instances before the SDK call. Do not use type assertions to mask the incompatibility.

Additionally, raw provider error messages are exposed to clients in lines 95 and 103, and line 132 silently swallows errors with onError: () => undefined.

Also applies to: 40, 68–76, 92–103, 132

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

In `@packages/docs/src/search/ai.ts` around lines 36 - 40, The
StreamDocsAnswerOptions type allows model: LanguageModel | string but the code
later passes a raw string to streamText via a type assertion; update the code
that calls streamText (the stream logic that uses DEFAULT_MODEL and the model
field) to first resolve string model IDs into real LanguageModel instances
(e.g., a resolveModelIdToLanguageModel helper) and disallow blind type
assertions, or remove string from StreamDocsAnswerOptions to require a
LanguageModel; also sanitize provider error messages returned to clients (in the
error handling around the streaming logic) instead of exposing raw provider
errors and stop swallowing errors silently—replace onError: () => undefined with
onError: (err) => { const safe = sanitizeError(err); /* log full err server-side
*/ return safe; } or rethrow so callers receive a sanitized error. Ensure
references to DEFAULT_MODEL, StreamDocsAnswerOptions, streamText, and the
streaming onError handler are updated accordingly.

Comment thread packages/docs/src/search/ai.ts
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

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

Inline comments:
In `@apps/docs-smoke/src/lib/search.ts`:
- Around line 13-21: The rate limiter calls in docsSearchLimiters use
magic-number literals for limits and windowMs; extract those values into
descriptive constants (e.g., ASK_RATE_LIMIT = 10, SEARCH_RATE_LIMIT = 60,
RATE_LIMIT_WINDOW_MS = 60_000) and replace the hard-coded numbers in the
createMemoryRateLimiter invocations so the intent is clear; update the
docsSearchLimiters object to reference these constants and ensure they are
exported or defined near the top of the module for visibility.
- Around line 46-53: The jsonResponse function currently spreads init?.headers
as an object which breaks when callers pass a Headers instance or header tuples;
update jsonResponse to build a proper Headers collection from init?.headers
(e.g., new Headers(init?.headers)), then ensure "Content-Type":
"application/json" is set only if not already present, and pass that Headers
instance into the Response options along with the rest of init so all header
forms (Headers, [][,] tuples, plain objects) are preserved; refer to the
jsonResponse function to locate and replace the current object-spread-based
header merge.

In `@packages/docs/src/search/ai.test.ts`:
- Around line 65-92: The test "streams provider errors as visible text"
currently asserts the raw provider error string ("model is unavailable") is
returned; update the assertion in this test to expect the new generic
user-facing error message produced by streamDocsAnswer instead (e.g., "AI answer
failed" or "AI answer failed: Please try again later.") — locate the test around
the call to streamDocsAnswer (and uses of createSearchIndex) and replace the
toContain expectation to match the generic message your implementation now emits
rather than the provider's raw error text.
- Around line 29-43: streamDocsAnswer currently accepts model?: LanguageModel |
string but forwards it to streamTextImpl using a type assertion, causing runtime
failure with the real Vercel SDK; update streamDocsAnswer to handle string
models properly by either (A) converting string model ids into a LanguageModel
object before calling streamTextImpl or (B) throwing a clear error when a string
is passed and require callers to supply a LanguageModel. Implement the
conversion or rejection inside streamDocsAnswer (reference the model parameter
and the streamTextImpl call site in streamDocsAnswer) and then update the test
to pass a mock LanguageModel object to validate the corrected contract rather
than a raw string.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1ef2bfe4-51df-4535-8c40-eecc21a83eab

📥 Commits

Reviewing files that changed from the base of the PR and between 42c23f4 and 36048df.

📒 Files selected for processing (3)
  • apps/docs-smoke/src/lib/search.ts
  • packages/docs/src/search/ai.test.ts
  • packages/docs/src/search/ai.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Prefer unknown over any when the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions

Files:

  • packages/docs/src/search/ai.test.ts
  • apps/docs-smoke/src/lib/search.ts
  • packages/docs/src/search/ai.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Prefer for...of loops over .forEach() and indexed for loops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Use const by default, let only when reassignment is needed, never var
Always await promises in async functions - don't forget to use the return value
Use async/await syntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Remove console.log, debugger, and alert statements from production code
Throw Error objects with descriptive messages, not strings or other values
Use try-catch blocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Don't use eval() or assign directly to document.cookie
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Use descriptive names for functions, variables, and types for meaningful naming
Add comments for complex logic, but prefer self-documenting code

Files:

  • packages/docs/src/search/ai.test.ts
  • apps/docs-smoke/src/lib/search.ts
  • packages/docs/src/search/ai.ts
**/*.{test,spec}.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{test,spec}.{js,ts,jsx,tsx}: Write assertions inside it() or test() blocks
Avoid done callbacks in async tests - use async/await instead
Don't use .only or .skip in committed code
Keep test suites reasonably flat - avoid excessive describe nesting

Files:

  • packages/docs/src/search/ai.test.ts
🧠 Learnings (9)
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{test,spec}.{ts,tsx} : Write unit tests for components in Elements package using Vitest

Applied to files:

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

Applied to files:

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

Applied to files:

  • apps/docs-smoke/src/lib/search.ts
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/src/components/**/*.tsx : Place reusable components in `src/components` within the docs app

Applied to files:

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

Applied to files:

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

Applied to files:

  • packages/docs/src/search/ai.ts
📚 Learning: 2026-04-18T15:01:52.722Z
Learnt from: CR
Repo: inthhq/docs PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-18T15:01:52.722Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Handle errors appropriately in async code with try-catch blocks

Applied to files:

  • packages/docs/src/search/ai.ts
📚 Learning: 2026-04-16T03:27:04.353Z
Learnt from: CR
Repo: inthhq/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-16T03:27:04.353Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Handle errors appropriately in async code with try-catch blocks

Applied to files:

  • packages/docs/src/search/ai.ts
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{tsx,jsx} : Use descriptive error messages that guide developers

Applied to files:

  • packages/docs/src/search/ai.ts
🔍 Remote MCP Context7

Relevant findings for reviewing PR #5

  • The Vercel AI SDK package in Context7 is /vercel/ai (Vercel AI SDK). It has high reputation and many code examples — appropriate source to validate streaming usage in this PR.

  • streamText returns a streaming result with a textStream async iterable and is commonly used to iterate tokens/chunks server-side; examples show using result.textStream directly in handlers.

  • The SDK exposes toTextStreamResponse (and streamText().toTextStreamResponse()) to convert a streamText result into a Response suitable for returning from server handlers; docs show using result.toTextStreamResponse() in POST handlers to return a text/plain streaming Response.

  • Cookbook examples demonstrate returning text/plain streams (setting Content-Type accordingly) or returning result.toTextStreamResponse() directly; these align with the PR’s streamDocsAnswer behavior (wrapping streamText and returning a streaming Response).

🔇 Additional comments (11)
packages/docs/src/search/ai.ts (6)

36-40: Model string type still incompatible with Vercel AI SDK.

The type model?: LanguageModel | string at line 40 and the type assertion as LanguageModel at line 137 remain unresolved. Per Vercel AI SDK documentation, streamText({ model }) requires a LanguageModel instance, not a plain string ID. The current code will fail at runtime when DEFAULT_MODEL (a string literal) is passed through.

Also applies to: 137-137


68-76: Raw provider errors still exposed to clients.

getStreamErrorMessage returns the raw Error.message or string directly. Lines 99, 108, and 114 embed these messages in client-visible response text, potentially leaking internal provider diagnostics on a public docs surface.

Also applies to: 95-103, 112-115


143-143: Silent error swallowing persists.

The onError: () => undefined callback discards all streaming errors without logging or propagation, making debugging streaming failures difficult.


1-16: LGTM on imports and constant definitions.

The imports are appropriate and the timeout configuration using as const satisfies TimeoutConfiguration is idiomatic TypeScript for immutable typed constants.


78-123: Stream response construction is well-structured.

The createDocsTextStreamResponse function correctly:

  • Uses TextEncoder for UTF-8 encoding
  • Tracks streamedText and streamedFailure flags to detect empty responses
  • Handles errors in the catch block
  • Closes the controller in finally

The defensive check for empty streams at lines 105-111 provides good UX feedback.


125-158: Core streaming flow and context construction look correct.

The createAnswerContext call properly merges defaults with caller options. The conditional response creation based on fullStream presence (lines 153-155) handles both streaming and fallback paths appropriately. Response headers correctly disable caching and set UTF-8 content type.

packages/docs/src/search/ai.test.ts (2)

94-118: Good coverage for empty provider response edge case.

Testing the empty generator path with yield* [] correctly verifies the defensive handling when the AI provider returns no content.


18-26: Well-structured test setup with appropriate invariant checks.

The fixture data and index creation are minimal yet sufficient. The thrown error at lines 24-26 correctly guards against unexpected test state rather than letting tests pass with undefined behavior.

apps/docs-smoke/src/lib/search.ts (3)

1-7: Clean import surface and good separation of generated artifacts.

This is a solid setup for centralizing search/runtime helpers in one module.


24-44: Type shape and AI-gating helper are clear and API-friendly.

DemoSearchApiResult is explicit, and isAiAnswerEnabled() is concise and readable.


9-11: The current double assertion pattern is a necessary workaround, not an error. When TypeScript imports JSON with resolveJsonModule: true, it infers readonly array types. The types DocsSearchIndex and DocsSearchContentStore define mutable arrays (documents: DocsSearchDocumentEntry[], chunks: DocsSearchChunkEntry[], terms: Record<string, DocsSearchPosting[]>). A direct type annotation like : DocsSearchIndex = searchIndex would fail compilation because TypeScript cannot assign readonly JSON-inferred types to mutable type definitions without an assertion. The double assertion as unknown as DocsSearchIndex is the correct pattern here—it bridges the structural gap between readonly JSON imports and the mutable runtime types these functions expect.

			> Likely an incorrect or invalid review comment.

Comment on lines +13 to +21
export const docsSearchLimiters = {
ask: createMemoryRateLimiter({
limit: 10,
windowMs: 60_000,
}),
search: createMemoryRateLimiter({
limit: 60,
windowMs: 60_000,
}),
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Extract rate-limit literals into named constants.

Line 15, Line 16, Line 19, and Line 20 use magic numbers that obscure policy intent.

Suggested change
+const ONE_MINUTE_MS = 60_000;
+const ASK_LIMIT_PER_MINUTE = 10;
+const SEARCH_LIMIT_PER_MINUTE = 60;
+
 export const docsSearchLimiters = {
   ask: createMemoryRateLimiter({
-    limit: 10,
-    windowMs: 60_000,
+    limit: ASK_LIMIT_PER_MINUTE,
+    windowMs: ONE_MINUTE_MS,
   }),
   search: createMemoryRateLimiter({
-    limit: 60,
-    windowMs: 60_000,
+    limit: SEARCH_LIMIT_PER_MINUTE,
+    windowMs: ONE_MINUTE_MS,
   }),
 } as const;

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

📝 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
export const docsSearchLimiters = {
ask: createMemoryRateLimiter({
limit: 10,
windowMs: 60_000,
}),
search: createMemoryRateLimiter({
limit: 60,
windowMs: 60_000,
}),
const ONE_MINUTE_MS = 60_000;
const ASK_LIMIT_PER_MINUTE = 10;
const SEARCH_LIMIT_PER_MINUTE = 60;
export const docsSearchLimiters = {
ask: createMemoryRateLimiter({
limit: ASK_LIMIT_PER_MINUTE,
windowMs: ONE_MINUTE_MS,
}),
search: createMemoryRateLimiter({
limit: SEARCH_LIMIT_PER_MINUTE,
windowMs: ONE_MINUTE_MS,
}),
} as const;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/docs-smoke/src/lib/search.ts` around lines 13 - 21, The rate limiter
calls in docsSearchLimiters use magic-number literals for limits and windowMs;
extract those values into descriptive constants (e.g., ASK_RATE_LIMIT = 10,
SEARCH_RATE_LIMIT = 60, RATE_LIMIT_WINDOW_MS = 60_000) and replace the
hard-coded numbers in the createMemoryRateLimiter invocations so the intent is
clear; update the docsSearchLimiters object to reference these constants and
ensure they are exported or defined near the top of the module for visibility.

Comment on lines +46 to +53
export function jsonResponse(data: unknown, init?: ResponseInit): Response {
return new Response(JSON.stringify(data), {
...init,
headers: {
"Content-Type": "application/json",
...init?.headers,
},
});
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:

In JavaScript Fetch API, does object spread ({ ...init.headers }) preserve entries when init.headersis aHeadersinstance or[string, string][] tuple array?

💡 Result:

No, the object spread operator { ...init.headers } does not preserve entries when init.headers is a Headers instance or a [string, string][] tuple array in the JavaScript Fetch API. For a Headers instance: - Headers objects have no own enumerable properties. The object spread operator { ...headers } iterates over own enumerable properties and produces an empty object {}. - Even though the resulting empty object is a valid HeadersInit (record<string, string>), the original header entries are lost. - Multiple sources confirm this browser behavior, including GitHub issues discussing Node.js fetch aligning with browsers where { ...baseHeaders, ...requestHeaders } results in no Content-Type from the Headers instance. For a tuple array [string, string][]: - Spreading an array into an object literal treats array indices as keys (e.g., { ...[["a", "1"]] } becomes { "0": ["a", "1"] }), which does not produce valid header key-value pairs. - The resulting object has numeric string keys with array values, losing the intended header structure. In both cases, using { ...init.headers } directly in fetch init will not preserve the original entries. To merge headers while preserving them, convert to a plain object first: - For Headers: Object.fromEntries(init.headers) - For tuple array: Object.fromEntries(init.headers) Example: const headersObj = { ...baseHeaders, ...Object.fromEntries(init.headers) // Works for both Headers and tuple array }; fetch(url, { headers: headersObj }); This produces a valid plain object that fetch accepts as HeadersInit, preserving all entries.

Citations:


🏁 Script executed:

cat -n apps/docs-smoke/src/lib/search.ts | head -70

Repository: inthhq/docs

Length of output: 1737


🌐 Web query:

JavaScript Headers constructor - does it accept Headers instance, record object, and tuple array as input

💡 Result:

Yes, the JavaScript Headers constructor accepts all three: an existing Headers instance, a record object (object literal with string key-value pairs), and a tuple array (array of 2-element string arrays). From MDN documentation: "This can be a simple object literal with String values, an array of name-value pairs, where each pair is a 2-element string array; or an existing Headers object."

Citations:


jsonResponse mishandles valid ResponseInit.headers forms.

Lines 49-51 spread init?.headers as an object. This fails for Headers instances and tuple-array headers—object spread only preserves own enumerable properties, losing all entries from Headers objects and converting tuple arrays to invalid numeric keys. Valid caller inputs are dropped or malformed.

Suggested fix
 export function jsonResponse(data: unknown, init?: ResponseInit): Response {
+  const headers = new Headers(init?.headers);
+  if (!headers.has("Content-Type")) {
+    headers.set("Content-Type", "application/json");
+  }
+
   return new Response(JSON.stringify(data), {
     ...init,
-    headers: {
-      "Content-Type": "application/json",
-      ...init?.headers,
-    },
+    headers,
   });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/docs-smoke/src/lib/search.ts` around lines 46 - 53, The jsonResponse
function currently spreads init?.headers as an object which breaks when callers
pass a Headers instance or header tuples; update jsonResponse to build a proper
Headers collection from init?.headers (e.g., new Headers(init?.headers)), then
ensure "Content-Type": "application/json" is set only if not already present,
and pass that Headers instance into the Response options along with the rest of
init so all header forms (Headers, [][,] tuples, plain objects) are preserved;
refer to the jsonResponse function to locate and replace the current
object-spread-based header merge.

Comment on lines +29 to +43
const result = streamDocsAnswer({
index: metadataOnlyIndex,
content,
query: "How do tabs work?",
model: "openai/gpt-5.4-mini",
productName: "@inth/docs",
maxOutputTokens: 123,
timeout: { totalMs: 1000, chunkMs: 500 },
streamTextImpl: (options) => {
calls.push(options);
return {
toTextStreamResponse: () => new Response("answer"),
};
},
});
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

🌐 Web query:

Vercel AI SDK streamText model parameter type

💡 Result:

The model parameter of the Vercel AI SDK's streamText function is of type LanguageModel. This is the core interface representing a language model instance created from provider packages like @ai-sdk/openai, @ai-sdk/anthropic, etc. Examples include openai('gpt-4o') or anthropic('claude-3-5-sonnet-20240620'). Usage: import { streamText } from 'ai'; import { openai } from '@ai-sdk/openai'; const result = streamText({ model: openai('gpt-4o'), // LanguageModel instance prompt: 'Your prompt here', }); The LanguageModel type is defined in the AI SDK core and encapsulates provider-specific model configurations, ensuring type-safe usage across different LLM providers.

Citations:


🏁 Script executed:

cat -n packages/docs/src/search/ai.test.ts | head -120

Repository: inthhq/docs

Length of output: 4532


🏁 Script executed:

find packages/docs/src/search -name "ai-index*" -type f

Repository: inthhq/docs

Length of output: 91


🏁 Script executed:

cat -n packages/docs/src/search/ai-index.ts

Repository: inthhq/docs

Length of output: 196


🏁 Script executed:

cat -n packages/docs/src/search/ai.ts

Repository: inthhq/docs

Length of output: 5696


Implementation passes string model to streamText without conversion, test validates broken behavior.

The implementation accepts model?: LanguageModel | string (line 40 of ai.ts) but uses as LanguageModel type assertion without actually converting strings to LanguageModel instances (line 137). When streamTextImpl is the real Vercel AI SDK streamText, this will fail since it requires an actual LanguageModel object, not a string.

The test passes model: "openai/gpt-5.4-mini" and asserts the string is forwarded unchanged (line 55), which correctly validates the current implementation's passthrough behavior. However, this test would fail if the mock were replaced with the real streamText.

Fix the implementation to either convert the string to a proper LanguageModel or reject string values, then update the test to use a mock LanguageModel object to validate the corrected contract.

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

In `@packages/docs/src/search/ai.test.ts` around lines 29 - 43, streamDocsAnswer
currently accepts model?: LanguageModel | string but forwards it to
streamTextImpl using a type assertion, causing runtime failure with the real
Vercel SDK; update streamDocsAnswer to handle string models properly by either
(A) converting string model ids into a LanguageModel object before calling
streamTextImpl or (B) throwing a clear error when a string is passed and require
callers to supply a LanguageModel. Implement the conversion or rejection inside
streamDocsAnswer (reference the model parameter and the streamTextImpl call site
in streamDocsAnswer) and then update the test to pass a mock LanguageModel
object to validate the corrected contract rather than a raw string.

Comment on lines +65 to +92
it("streams provider errors as visible text", async () => {
const index = createSearchIndex(docs, {
generatedAt: "2026-01-01T00:00:00.000Z",
});
const { content, ...metadataOnlyIndex } = index;
if (!content) {
throw new Error("Expected createSearchIndex to embed content.");
}

const result = streamDocsAnswer({
index: metadataOnlyIndex,
content,
query: "How do tabs work?",
streamTextImpl: () => ({
fullStream: (async function* () {
yield {
error: new Error("model is unavailable"),
type: "error",
};
})(),
toTextStreamResponse: () => new Response(""),
}),
});

await expect(result.response.text()).resolves.toContain(
"AI answer failed: model is unavailable"
);
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Error test validates raw message exposure.

This test asserts that raw error messages like "model is unavailable" appear in client responses. Once the implementation is updated to return generic user-facing messages (per the flagged issue), this assertion will need updating.

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

In `@packages/docs/src/search/ai.test.ts` around lines 65 - 92, The test "streams
provider errors as visible text" currently asserts the raw provider error string
("model is unavailable") is returned; update the assertion in this test to
expect the new generic user-facing error message produced by streamDocsAnswer
instead (e.g., "AI answer failed" or "AI answer failed: Please try again
later.") — locate the test around the call to streamDocsAnswer (and uses of
createSearchIndex) and replace the toContain expectation to match the generic
message your implementation now emits rather than the provider's raw error text.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (8)
apps/docs-smoke/src/routes/search.tsx (3)

37-55: 🧹 Nitpick | 🔵 Trivial

Silent error handling in config loading.

The configPromise.catch(() => undefined) on line 51 silently discards fetch errors. While acceptable for a non-critical feature toggle, consider logging in development for debugging.

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

In `@apps/docs-smoke/src/routes/search.tsx` around lines 37 - 55, The fetch error
is being swallowed by configPromise.catch(() => undefined); update the
loadAnswerConfig error path so failures are logged in development: catch the
error from loadAnswerConfig (or replace configPromise.catch) and call
console.error (or a debug logger) with a clear message and the error, while
preserving the current behavior of not failing in production; reference the
loadAnswerConfig async function, the configPromise.catch call, and state setters
setAnswerConfig/setAnswerStatus when making this change.

57-86: ⚠️ Potential issue | 🟠 Major

Wrap fetch/JSON parsing in try-catch to normalize error handling.

runSearch sets searchStatus to "loading" but a network error or JSON parse failure will reject without updating state. This can leave the UI in an inconsistent loading state and cause unhandled rejections in callers like handleSearch.

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

In `@apps/docs-smoke/src/routes/search.tsx` around lines 57 - 86, The runSearch
callback should wrap the fetch and response.json() steps in a try-catch to
ensure network/parse errors don't leave the UI stuck in "loading"; locate the
runSearch function and surround the fetch/await response.json() logic with try {
... } catch (err) { setSearchStatus("error"); setError(err?.message ||
String(err) || "Search failed."); return []; } and ensure successful path still
calls setResults(...) and setSearchStatus("idle") so every error path and
success path updates setSearchStatus, setError, and setResults consistently.

124-188: ⚠️ Potential issue | 🟠 Major

Cancel the answer stream when the query changes or component unmounts.

The /api/docs/ask stream in handleAsk has no AbortController. If a user types while streaming, the old answer continues appending while results updates for the new query, creating a mismatch between answer and sources.

Add an AbortController to the fetch and abort it when the query changes (in the search effect cleanup) or when the component unmounts.

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

In `@apps/docs-smoke/src/routes/search.tsx` around lines 124 - 188, handleAsk
currently fetches /api/docs/ask without an AbortController so streaming
continues after the query changes; create an AbortController per request (inside
handleAsk) and pass controller.signal to fetch, store the controller in a
persistent ref (e.g., abortControllerRef) and abort any previous controller
before starting a new request to prevent overlapping streams, and abort it in
the search effect cleanup / component unmount; also update the catch in
handleAsk to ignore AbortError (don't set state when aborted) and ensure the
reader/stream is not processed after abort to avoid appending stale chunks.
packages/docs/src/search/ai.test.ts (2)

29-43: ⚠️ Potential issue | 🟡 Minor

Test validates passing string model which will fail with real SDK.

Line 33 passes model: "openai/gpt-5.4-mini" and line 55 asserts it's forwarded unchanged. This test will need updating once the implementation properly requires or converts LanguageModel instances.

Also applies to: 55-55

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

In `@packages/docs/src/search/ai.test.ts` around lines 29 - 43, The test passes a
plain string for the model ("openai/gpt-5.4-mini") to streamDocsAnswer and
asserts it is forwarded unchanged, but real SDK code will require or convert a
LanguageModel instance; update the test to use a LanguageModel-compatible value
or mock instead of a raw string: replace the model argument in the
streamDocsAnswer call (refer to the streamDocsAnswer invocation and the model
assertion) with a stubbed/constructed LanguageModel object or adjust the
assertion to expect the conversion behavior your implementation will perform so
the test reflects the real contract.

65-92: 🧹 Nitpick | 🔵 Trivial

Test asserts raw error message exposure.

This test expects "AI answer failed: model is unavailable" in the response, validating the current behavior of exposing raw provider errors. Update this assertion once the implementation returns generic user-facing messages.

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

In `@packages/docs/src/search/ai.test.ts` around lines 65 - 92, The test "streams
provider errors as visible text" currently asserts the raw provider error
message; update the assertion so it no longer expects the raw error string but a
generic user-facing message. In the test that calls streamDocsAnswer (the test
block using streamDocsAnswer and the inline fullStream that yields an error),
replace the expectation that resolves.toContain("AI answer failed: model is
unavailable") with a check for the generic message, e.g. resolves.toContain("AI
answer failed") (or the exact generic phrase your implementation will return),
so the test no longer depends on provider internals.
packages/docs/src/search/ai.ts (3)

72-80: ⚠️ Potential issue | 🟠 Major

Don't stream raw provider errors back to clients.

getStreamErrorMessage returns the raw Error.message or string content directly to clients (lines 109, 130). On a public docs surface, this can leak internal provider diagnostics. Return a generic user-facing message and log the detailed error server-side.

Also applies to: 105-111, 128-131

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

In `@packages/docs/src/search/ai.ts` around lines 72 - 80, getStreamErrorMessage
currently returns raw error.message or strings to clients; change it to log the
full error server-side (use the existing logger or console.error) and always
return a generic user-facing message (e.g., "An error occurred while generating
the response.") so provider diagnostics are not leaked; update
getStreamErrorMessage and any callers that rely on its output (streaming
handlers) to ensure sensitive details are only in server logs and not returned
to clients.

159-159: ⚠️ Potential issue | 🟠 Major

Silent error swallowing hides streaming failures.

The onError: () => undefined callback discards all errors without logging or propagation, making debugging streaming failures difficult.

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

In `@packages/docs/src/search/ai.ts` at line 159, The onError callback currently
swallows all streaming errors (onError: () => undefined) which hides failures;
update the onError handler in packages/docs/src/search/ai.ts to accept the error
parameter, log the error (e.g., with console.error or the module's logger) and
either rethrow or propagate it to the caller so failures are visible and
debuggable; locate the onError option used in the streaming/event handler setup
and replace the no-op with an implementation that logs "AI stream error" plus
the error and then rethrows or surfaces the error to upstream.

36-40: ⚠️ Potential issue | 🔴 Critical

Resolve model IDs to LanguageModel instances before calling streamText.

Line 40 allows model?: LanguageModel | string, but line 153 uses a type assertion as LanguageModel without actual conversion. The Vercel AI SDK's streamText requires a LanguageModel object—it does not accept plain string model IDs. When callers pass a string (or rely on DEFAULT_MODEL), this will fail at runtime.

Either remove string from the type signature, or add explicit resolution logic using a provider helper (e.g., createOpenAI() from @ai-sdk/openai).

Also applies to: 151-153

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

In `@packages/docs/src/search/ai.ts` around lines 36 - 40, StreamDocsAnswerOptions
currently allows model?: LanguageModel | string but streamText is called with a
type assertion (as LanguageModel) which will fail if a string or DEFAULT_MODEL
is passed; fix by resolving any string model IDs into actual LanguageModel
instances before calling streamText—either restrict
StreamDocsAnswerOptions.model to LanguageModel only, or add resolution logic
(e.g., detect typeof model === "string" or model === DEFAULT_MODEL and call a
provider helper such as createOpenAI(...) from `@ai-sdk/openai` to produce a
LanguageModel) and pass that resolved LanguageModel into streamText; update all
call sites around streamText/DEFAULT_MODEL to use the resolved instance rather
than a raw string.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/docs-smoke/src/routes/api/docs/ask.ts`:
- Around line 71-79: The call site is passing a raw string
(process.env.DOCS_SEARCH_MODEL ?? DEFAULT_MODEL) into streamDocsAnswer which
expects a LanguageModel instance; update the code that calls streamDocsAnswer
(the block returning streamDocsAnswer in ask.ts) to resolve that string to a
real LanguageModel instance before passing it (use the same resolver helper in
ai.ts or create one that maps modelId strings to provider factory calls like
gateway('model-id') or openrouter('model-id')), and then pass the resulting
LanguageModel object into streamDocsAnswer.model so streamText won't receive a
plain string; reference streamDocsAnswer and the model-resolution code in ai.ts
(the code around the current as LanguageModel assertion) to implement the
resolver and use it here.

---

Duplicate comments:
In `@apps/docs-smoke/src/routes/search.tsx`:
- Around line 37-55: The fetch error is being swallowed by
configPromise.catch(() => undefined); update the loadAnswerConfig error path so
failures are logged in development: catch the error from loadAnswerConfig (or
replace configPromise.catch) and call console.error (or a debug logger) with a
clear message and the error, while preserving the current behavior of not
failing in production; reference the loadAnswerConfig async function, the
configPromise.catch call, and state setters setAnswerConfig/setAnswerStatus when
making this change.
- Around line 57-86: The runSearch callback should wrap the fetch and
response.json() steps in a try-catch to ensure network/parse errors don't leave
the UI stuck in "loading"; locate the runSearch function and surround the
fetch/await response.json() logic with try { ... } catch (err) {
setSearchStatus("error"); setError(err?.message || String(err) || "Search
failed."); return []; } and ensure successful path still calls setResults(...)
and setSearchStatus("idle") so every error path and success path updates
setSearchStatus, setError, and setResults consistently.
- Around line 124-188: handleAsk currently fetches /api/docs/ask without an
AbortController so streaming continues after the query changes; create an
AbortController per request (inside handleAsk) and pass controller.signal to
fetch, store the controller in a persistent ref (e.g., abortControllerRef) and
abort any previous controller before starting a new request to prevent
overlapping streams, and abort it in the search effect cleanup / component
unmount; also update the catch in handleAsk to ignore AbortError (don't set
state when aborted) and ensure the reader/stream is not processed after abort to
avoid appending stale chunks.

In `@packages/docs/src/search/ai.test.ts`:
- Around line 29-43: The test passes a plain string for the model
("openai/gpt-5.4-mini") to streamDocsAnswer and asserts it is forwarded
unchanged, but real SDK code will require or convert a LanguageModel instance;
update the test to use a LanguageModel-compatible value or mock instead of a raw
string: replace the model argument in the streamDocsAnswer call (refer to the
streamDocsAnswer invocation and the model assertion) with a stubbed/constructed
LanguageModel object or adjust the assertion to expect the conversion behavior
your implementation will perform so the test reflects the real contract.
- Around line 65-92: The test "streams provider errors as visible text"
currently asserts the raw provider error message; update the assertion so it no
longer expects the raw error string but a generic user-facing message. In the
test that calls streamDocsAnswer (the test block using streamDocsAnswer and the
inline fullStream that yields an error), replace the expectation that
resolves.toContain("AI answer failed: model is unavailable") with a check for
the generic message, e.g. resolves.toContain("AI answer failed") (or the exact
generic phrase your implementation will return), so the test no longer depends
on provider internals.

In `@packages/docs/src/search/ai.ts`:
- Around line 72-80: getStreamErrorMessage currently returns raw error.message
or strings to clients; change it to log the full error server-side (use the
existing logger or console.error) and always return a generic user-facing
message (e.g., "An error occurred while generating the response.") so provider
diagnostics are not leaked; update getStreamErrorMessage and any callers that
rely on its output (streaming handlers) to ensure sensitive details are only in
server logs and not returned to clients.
- Line 159: The onError callback currently swallows all streaming errors
(onError: () => undefined) which hides failures; update the onError handler in
packages/docs/src/search/ai.ts to accept the error parameter, log the error
(e.g., with console.error or the module's logger) and either rethrow or
propagate it to the caller so failures are visible and debuggable; locate the
onError option used in the streaming/event handler setup and replace the no-op
with an implementation that logs "AI stream error" plus the error and then
rethrows or surfaces the error to upstream.
- Around line 36-40: StreamDocsAnswerOptions currently allows model?:
LanguageModel | string but streamText is called with a type assertion (as
LanguageModel) which will fail if a string or DEFAULT_MODEL is passed; fix by
resolving any string model IDs into actual LanguageModel instances before
calling streamText—either restrict StreamDocsAnswerOptions.model to
LanguageModel only, or add resolution logic (e.g., detect typeof model ===
"string" or model === DEFAULT_MODEL and call a provider helper such as
createOpenAI(...) from `@ai-sdk/openai` to produce a LanguageModel) and pass that
resolved LanguageModel into streamText; update all call sites around
streamText/DEFAULT_MODEL to use the resolved instance rather than a raw string.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ba078807-65b3-4b51-98b2-7ba1478b1275

📥 Commits

Reviewing files that changed from the base of the PR and between 36048df and 040afaf.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • apps/docs-smoke/package.json
  • apps/docs-smoke/src/routes/api/docs/ask.ts
  • apps/docs-smoke/src/routes/search.tsx
  • apps/docs-smoke/src/styles.css
  • packages/docs/src/search/ai.test.ts
  • packages/docs/src/search/ai.ts
📜 Review details
🧰 Additional context used
📓 Path-based instructions (5)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Use explicit types for function parameters and return values when they enhance clarity
Prefer unknown over any when the type is genuinely unknown
Use const assertions (as const) for immutable values and literal types
Leverage TypeScript's type narrowing instead of type assertions

Files:

  • apps/docs-smoke/src/routes/api/docs/ask.ts
  • packages/docs/src/search/ai.test.ts
  • apps/docs-smoke/src/routes/search.tsx
  • packages/docs/src/search/ai.ts
**/*.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,jsx,tsx}: Use meaningful variable names instead of magic numbers - extract constants with descriptive names
Use arrow functions for callbacks and short functions
Prefer for...of loops over .forEach() and indexed for loops
Use optional chaining (?.) and nullish coalescing (??) for safer property access
Prefer template literals over string concatenation
Use destructuring for object and array assignments
Use const by default, let only when reassignment is needed, never var
Always await promises in async functions - don't forget to use the return value
Use async/await syntax instead of promise chains for better readability
Handle errors appropriately in async code with try-catch blocks
Don't use async functions as Promise executors
Remove console.log, debugger, and alert statements from production code
Throw Error objects with descriptive messages, not strings or other values
Use try-catch blocks meaningfully - don't catch errors just to rethrow them
Prefer early returns over nested conditionals for error cases
Extract complex conditions into well-named boolean variables
Use early returns to reduce nesting
Prefer simple conditionals over nested ternary operators
Don't use eval() or assign directly to document.cookie
Avoid spread syntax in accumulators within loops
Use top-level regex literals instead of creating them in loops
Prefer specific imports over namespace imports
Use descriptive names for functions, variables, and types for meaningful naming
Add comments for complex logic, but prefer self-documenting code

Files:

  • apps/docs-smoke/src/routes/api/docs/ask.ts
  • packages/docs/src/search/ai.test.ts
  • apps/docs-smoke/src/routes/search.tsx
  • packages/docs/src/search/ai.ts
**/*.{test,spec}.{js,ts,jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{test,spec}.{js,ts,jsx,tsx}: Write assertions inside it() or test() blocks
Avoid done callbacks in async tests - use async/await instead
Don't use .only or .skip in committed code
Keep test suites reasonably flat - avoid excessive describe nesting

Files:

  • packages/docs/src/search/ai.test.ts
**/*.{jsx,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{jsx,tsx}: Use function components over class components in React
Call hooks at the top level only, never conditionally
Specify all dependencies in hook dependency arrays correctly
Use the key prop for elements in iterables (prefer unique IDs over array indices)
Nest children between opening and closing tags instead of passing as props
Don't define components inside other components
Avoid dangerouslySetInnerHTML unless absolutely necessary
Use proper image components (e.g., Next.js <Image>) over <img> tags
Use Next.js <Image> component for images
Use next/head or App Router metadata API for head elements in Next.js
Use Server Components for async data fetching instead of async Client Components in Next.js
Use ref as a prop instead of React.forwardRef in React 19+

Files:

  • apps/docs-smoke/src/routes/search.tsx
**/*.{jsx,tsx,html}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{jsx,tsx,html}: Use semantic HTML and ARIA attributes for accessibility: provide meaningful alt text for images, use proper heading hierarchy, add labels for form inputs, include keyboard event handlers alongside mouse events, use semantic elements instead of divs with roles
Add rel="noopener" when using target="_blank" on links

Files:

  • apps/docs-smoke/src/routes/search.tsx
🧠 Learnings (31)
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.{ts,tsx,css} : Prefer utility classes over custom CSS in the docs app

Applied to files:

  • apps/docs-smoke/src/styles.css
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.{ts,tsx} : Implement responsive design with Tailwind CSS in the docs app; use a mobile-first approach

Applied to files:

  • apps/docs-smoke/src/styles.css
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/**/*.{ts,tsx} : Implement responsive design using Tailwind breakpoints in the docs app

Applied to files:

  • apps/docs-smoke/src/styles.css
📚 Learning: 2026-03-19T22:57:19.913Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.{tsx,ts} : Implement responsive design with Tailwind CSS using a mobile-first approach

Applied to files:

  • apps/docs-smoke/src/styles.css
📚 Learning: 2026-03-19T22:57:19.913Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/**/*.{tsx,css} : Prefer utility classes over custom CSS

Applied to files:

  • apps/docs-smoke/src/styles.css
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{ts,tsx,css,scss} : Avoid external styling dependencies like Tailwind in Elements package

Applied to files:

  • apps/docs-smoke/src/styles.css
  • apps/docs-smoke/package.json
📚 Learning: 2026-03-19T22:57:54.730Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/typography.mdc:0-0
Timestamp: 2026-03-19T22:57:54.730Z
Learning: Applies to **/*.{ts,tsx,css} : Avoid Tailwind default text sizes `text-3xl`, `text-4xl`, `text-5xl`, `text-6xl`, `text-7xl`, `text-8xl`, `text-9xl` in favor of semantic tokens

Applied to files:

  • apps/docs-smoke/src/styles.css
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/* : Avoid external styling dependencies like Tailwind

Applied to files:

  • apps/docs-smoke/src/styles.css
📚 Learning: 2026-03-19T22:57:54.730Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/typography.mdc:0-0
Timestamp: 2026-03-19T22:57:54.730Z
Learning: Applies to **/*.{ts,tsx,css} : Use semantic typography tokens from `inth/optin` (text-title-*, text-display-*, text-paragraph-*, text-label-*, text-subheading-*) instead of Tailwind's default text size classes

Applied to files:

  • apps/docs-smoke/src/styles.css
📚 Learning: 2026-03-19T16:08:15.951Z
Learnt from: CR
Repo: consentdotio/c-ray PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-19T16:08:15.951Z
Learning: When adding new routes and pages, load the router-core skill file from node_modules/tanstack/router-core/skills/router-core/SKILL.md

Applied to files:

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

Applied to files:

  • apps/docs-smoke/src/routes/api/docs/ask.ts
  • apps/docs-smoke/src/routes/search.tsx
📚 Learning: 2026-03-19T16:08:15.951Z
Learnt from: CR
Repo: consentdotio/c-ray PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-19T16:08:15.951Z
Learning: When building API endpoints and server routes, load the start-core server-routes skill file from node_modules/tanstack/start-client-core/skills/start-core/server-routes/SKILL.md

Applied to files:

  • apps/docs-smoke/src/routes/api/docs/ask.ts
📚 Learning: 2026-01-31T11:24:55.220Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-01-31T11:24:55.220Z
Learning: Applies to apps/docs/app/**/page.tsx : Place page components in appropriate route segments within the docs app

Applied to files:

  • apps/docs-smoke/src/routes/api/docs/ask.ts
  • apps/docs-smoke/src/routes/search.tsx
📚 Learning: 2026-03-19T22:57:19.913Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/app/**/* : Use Next.js App Router conventions

Applied to files:

  • apps/docs-smoke/src/routes/api/docs/ask.ts
📚 Learning: 2026-03-19T16:08:15.951Z
Learnt from: CR
Repo: consentdotio/c-ray PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-19T16:08:15.951Z
Learning: When managing URL search params, load the router-core search-params skill file from node_modules/tanstack/router-core/skills/router-core/search-params/SKILL.md

Applied to files:

  • apps/docs-smoke/src/routes/api/docs/ask.ts
  • apps/docs-smoke/src/routes/search.tsx
📚 Learning: 2026-03-19T16:08:15.951Z
Learnt from: CR
Repo: consentdotio/c-ray PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-19T16:08:15.951Z
Learning: When handling errors and 404 pages, load the router-core not-found-and-errors skill file from node_modules/tanstack/router-core/skills/router-core/not-found-and-errors/SKILL.md

Applied to files:

  • apps/docs-smoke/src/routes/api/docs/ask.ts
📚 Learning: 2026-03-19T22:57:33.411Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:33.411Z
Learning: Applies to packages/elements/**/*.{test,spec}.{ts,tsx} : Write unit tests for components in Elements package using Vitest

Applied to files:

  • packages/docs/src/search/ai.test.ts
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.test.{ts,tsx} : Use Vitest for testing framework

Applied to files:

  • packages/docs/src/search/ai.test.ts
📚 Learning: 2025-07-23T12:51:10.961Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/elements-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:10.961Z
Learning: Applies to packages/elements/**/*.test.{ts,tsx} : Test error boundaries

Applied to files:

  • packages/docs/src/search/ai.test.ts
📚 Learning: 2026-03-19T22:57:19.913Z
Learnt from: CR
Repo: consentdotio/GDPRish PR: 0
File: .cursor/rules/docs-rules.mdc:0-0
Timestamp: 2026-03-19T22:57:19.913Z
Learning: Applies to apps/docs/app/**/page.tsx : Place page components in appropriate route segments

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
📚 Learning: 2025-12-01T15:20:25.102Z
Learnt from: CR
Repo: consentdotio/c15t-docs PR: 0
File: .cursor/rules/open-graph.mdc:0-0
Timestamp: 2025-12-01T15:20:25.102Z
Learning: Applies to src/**/route.{ts,tsx} : Read dynamic content from `request.nextUrl.searchParams` and pass into pure component props rather than fetching during render

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
📚 Learning: 2026-04-16T03:27:04.353Z
Learnt from: CR
Repo: inthhq/dsar PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-16T03:27:04.353Z
Learning: Applies to **/*.{js,jsx,ts,tsx} : Handle errors appropriately in async code with try-catch blocks

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
  • packages/docs/src/search/ai.ts
📚 Learning: 2026-04-18T15:01:52.722Z
Learnt from: CR
Repo: inthhq/docs PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-18T15:01:52.722Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Handle errors appropriately in async code with try-catch blocks

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
  • packages/docs/src/search/ai.ts
📚 Learning: 2026-02-20T14:21:19.208Z
Learnt from: CR
Repo: consentdotio/dsar PR: 0
File: .cursor/rules/ultracite.mdc:0-0
Timestamp: 2026-02-20T14:21:19.208Z
Learning: Applies to **/*.{ts,tsx,js,jsx} : Handle errors appropriately in async code with try-catch blocks

Applied to files:

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

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
  • packages/docs/src/search/ai.ts
📚 Learning: 2026-04-18T15:01:52.722Z
Learnt from: CR
Repo: inthhq/docs PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-18T15:01:52.722Z
Learning: Applies to **/*.{js,ts,jsx,tsx} : Use `try-catch` blocks meaningfully - don't catch errors just to rethrow them

Applied to files:

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

Applied to files:

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

Applied to files:

  • apps/docs-smoke/src/routes/search.tsx
📚 Learning: 2026-03-19T16:08:15.951Z
Learnt from: CR
Repo: consentdotio/c-ray PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-03-19T16:08:15.951Z
Learning: When configuring devtools, load the devtools-vite-plugin skill file from node_modules/tanstack/devtools-vite/skills/devtools-vite-plugin/SKILL.md

Applied to files:

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

Applied to files:

  • apps/docs-smoke/package.json
📚 Learning: 2025-07-23T12:51:43.299Z
Learnt from: CR
Repo: consentdotio/monorepo PR: 0
File: .cursor/rules/global-rules.mdc:0-0
Timestamp: 2025-07-23T12:51:43.299Z
Learning: Applies to **/*.{tsx,jsx} : Use descriptive error messages that guide developers

Applied to files:

  • packages/docs/src/search/ai.ts
🪛 Stylelint (17.7.0)
apps/docs-smoke/src/styles.css

[error] 6-6: Unexpected unknown at-rule "@source" (scss/at-rule-no-unknown)

(scss/at-rule-no-unknown)

🔍 Remote MCP Context7

Relevant findings for PR review

  • The Vercel AI SDK library for streaming is /vercel/ai (Context7 resolution).

  • streamText returns a streaming result with a fullStream of events (types include text-delta, tool-call, tool-result, reasoning parts). Consumers can iterate result.fullStream to handle events or pipe it through transforms. Example server handler transforms events and returns a Response whose body is the streamed output.

  • The SDK provides result.toTextStreamResponse() which "creates a simple text stream response" by encoding each text-delta as UTF-8 and ignoring non-text events; it returns a Response suitable for server handlers (sets text/plain semantics via usage examples). The PR’s wrapping of streamText into a Response and custom handling of text-delta / reasoning / error events is consistent with SDK patterns and examples.

  • Examples show enabling provider-specific reasoning/thoughts via providerOptions (e.g., includeThoughts for Vertex) and handling reasoning events separately. If the PR filters/suppresses reasoning-delta and emits plain text-delta chunks, this matches documented usage patterns.

Sources:

  • Context7 resolve result for Vercel AI SDK
  • Vercel AI SDK docs/examples: streamText usage, event types, and toTextStreamResponse reference
🔇 Additional comments (11)
apps/docs-smoke/src/styles.css (2)

6-6: Static analysis false positive: @source is valid Tailwind v4 syntax.

The Stylelint error scss/at-rule-no-unknown is a false positive—this is a CSS file using Tailwind v4's @source directive for content scanning, not SCSS. Consider configuring Stylelint to recognize Tailwind-specific at-rules or switching to a CSS-appropriate rule set.


133-177: Well-structured .docs-answer styles.

The new class follows the established .docs-prose pattern, uses @apply with Tailwind utilities consistently, and covers all necessary elements including proper handling of nested pre code. The typography scales appropriately for the answer panel context.

packages/docs/src/search/ai.ts (2)

82-139: Stream handling implementation looks correct.

The createDocsTextStreamResponse function properly:

  • Iterates the async stream with for await
  • Encodes and enqueues text chunks
  • Handles error and finish events appropriately
  • Closes the controller in a finally block
  • Handles edge cases (empty answer, reasoning-only output)

161-173: Response construction and return structure are well-implemented.

The responseInit correctly sets Cache-Control: no-store and Content-Type: text/plain; charset=utf-8. The conditional use of createDocsTextStreamResponse vs toTextStreamResponse properly handles cases where fullStream may be unavailable.

packages/docs/src/search/ai.test.ts (1)

94-151: Good coverage for edge cases.

The empty response and reasoning budget tests properly validate important failure modes with clear assertions. The async generator mocking pattern is clean and effective.

apps/docs-smoke/package.json (2)

7-9: Pipeline and script updates look good.

The portless run vite wrapper and the new pipeline:search step integrate cleanly with the existing build flow.

Also applies to: 19-20


37-37: Dependencies added correctly.

The ai (^6.0.168) and streamdown (^2.5.0) dependencies align with the new search/answer functionality.

Also applies to: 43-43

apps/docs-smoke/src/routes/api/docs/ask.ts (2)

25-61: Route guards and rate limiting are well-implemented.

The GET handler cleanly exposes config. The POST handler correctly:

  • Returns 503 when AI is disabled with an actionable message
  • Implements per-client rate limiting with proper Retry-After header
  • Validates input with bounded body size and query length

80-91: Error handling appropriately distinguishes known vs unknown errors.

DocsSearchRequestError instances surface their message and status, while unknown errors return a generic "Answer generation failed." with 500—avoiding internal detail leakage.

apps/docs-smoke/src/routes/search.tsx (2)

192-286: Search form and results rendering are well-structured.

Good accessibility patterns:

  • aria-live="polite" on the results section for screen reader announcements
  • sr-only label for the search input
  • Proper form structure with explicit submit handling

288-342: Answer panel and sources sidebar implementation looks good.

The Streamdown component is used correctly with isAnimating tied to answerStatus === "streaming". The sources list properly slices to 6 items and formats citations consistently.

Comment on lines +71 to +79
return streamDocsAnswer({
index: docsSearchIndex,
content: docsSearchContent,
query,
model: process.env.DOCS_SEARCH_MODEL ?? DEFAULT_MODEL,
maxOutputTokens: DEFAULT_MAX_OUTPUT_TOKENS,
productName: "@inth/docs",
timeout: DEFAULT_TIMEOUT,
}).response;
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:

How to create a LanguageModel instance for Vercel AI SDK with AI Gateway or OpenRouter?

💡 Result:

To create a LanguageModel instance for the Vercel AI SDK with AI Gateway or OpenRouter, use the provider's model factory function (e.g., gateway('model-id') or openrouter('model-id')). These return LanguageModelV3 instances compatible with functions like generateText and streamText. ## With Vercel AI Gateway 1. Install: npm i ai @ai-sdk/gateway 2. Use the default gateway provider (requires AI_GATEWAY_API_KEY env var): ts import { gateway } from 'ai'; import { generateText } from 'ai'; const model = gateway('openai/gpt-5.4'); // Returns LanguageModel instance const { text } = await generateText({ model, prompt: 'Explain quantum computing.', }); Or create a custom gateway: ts import { createGateway } from 'ai'; const gatewayCustom = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY, }); const model = gatewayCustom('anthropic/claude-sonnet-4.6'); Model IDs use format provider/model (e.g., openai/gpt-5.4, xai/grok-4.1). ## With OpenRouter 1. Install: npm i ai @openrouter/ai-sdk-provider 2. Create the provider: ts import { createOpenRouter } from '@openrouter/ai-sdk-provider'; const openrouter = createOpenRouter({ apiKey: process.env.OPENROUTER_API_KEY, }); const model = openrouter('anthropic/claude-3.5-sonnet'); // Returns LanguageModel instance const { text } = await generateText({ model, prompt: 'Write a recipe.', }); Use chat models with openrouter.chat('model-id') or completion models with openrouter.completion('model-id'). You can wrap models with middleware using wrapLanguageModel({ model, middleware }) for custom behavior like settings or logging. No direct createLanguageModel function exists; providers handle instantiation via their factory methods.

Citations:


🏁 Script executed:

#!/bin/bash

# First, find the ask.ts file and read it
find . -name "ask.ts" -path "*/routes/api/docs/*" | head -5

Repository: inthhq/docs

Length of output: 99


🏁 Script executed:

#!/bin/bash

# Find all relevant files to understand the issue
fd "ask.ts" --type f

Repository: inthhq/docs

Length of output: 97


🏁 Script executed:

#!/bin/bash

# Also search for streamDocsAnswer and ai.ts
fd -e ts -e tsx | grep -E "(ask\.ts|ai\.ts|search)" | head -20

Repository: inthhq/docs

Length of output: 734


🏁 Script executed:

#!/bin/bash

# Read the ask.ts file to see lines 71-79 and surrounding context
cat -n ./apps/docs-smoke/src/routes/api/docs/ask.ts | head -100

Repository: inthhq/docs

Length of output: 3459


🏁 Script executed:

#!/bin/bash

# Find the streamDocsAnswer function - likely in packages/docs/src/search
rg "streamDocsAnswer" --type ts --type tsx -B 2 -A 10

Repository: inthhq/docs

Length of output: 82


🏁 Script executed:

#!/bin/bash

# Read the ai.ts file to understand the structure and type issues
cat -n ./packages/docs/src/search/ai.ts

Repository: inthhq/docs

Length of output: 6481


String model will fail at runtime with real Vercel AI SDK.

Line 75 passes process.env.DOCS_SEARCH_MODEL ?? DEFAULT_MODEL (a string like "moonshotai/kimi-k2.6") to streamDocsAnswer. The type assertion at ai.ts:153 (as LanguageModel) masks the incompatibility. When streamText receives a string instead of a LanguageModel instance, it will fail because the SDK expects a proper model instance created via a provider factory function (e.g., gateway('model-id') from @ai-sdk/gateway or openrouter('model-id') from @openrouter/ai-sdk-provider).

Update this call site to pass a properly instantiated LanguageModel once ai.ts resolves the model parameter to an actual instance.

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

In `@apps/docs-smoke/src/routes/api/docs/ask.ts` around lines 71 - 79, The call
site is passing a raw string (process.env.DOCS_SEARCH_MODEL ?? DEFAULT_MODEL)
into streamDocsAnswer which expects a LanguageModel instance; update the code
that calls streamDocsAnswer (the block returning streamDocsAnswer in ask.ts) to
resolve that string to a real LanguageModel instance before passing it (use the
same resolver helper in ai.ts or create one that maps modelId strings to
provider factory calls like gateway('model-id') or openrouter('model-id')), and
then pass the resulting LanguageModel object into streamDocsAnswer.model so
streamText won't receive a plain string; reference streamDocsAnswer and the
model-resolution code in ai.ts (the code around the current as LanguageModel
assertion) to implement the resolver and use it here.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Caution

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

⚠️ Outside diff range comments (3)
packages/docs/src/llm/index.ts (1)

1-12: 🛠️ Refactor suggestion | 🟠 Major

Avoid expanding barrel exports in index.ts.

This change adds more re-exports in a barrel entrypoint, which conflicts with the repo rule and increases hidden coupling in import surfaces. Prefer explicit module entrypoints (e.g., direct ./llm imports or a non-barrel curated API file).

As per coding guidelines: **/index.{js,ts,jsx,tsx}: Avoid barrel files (index files that re-export everything).

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

In `@packages/docs/src/llm/index.ts` around lines 1 - 12, The index.ts barrel
expands and re-exports many internal symbols (CuratedLink, CuratedSection,
FullTopic, generateLLMFullContextFiles, generateLlmsTxt, LLMFullContextConfig,
LlmsTxtConfig, MarkdownDoc, ProductInfo, SourceDoc) which violates the rule
against barrel files; fix by removing these broad re-exports from the barrel
(index.ts) and instead have callers import the specific symbols directly from
"./llm" or create a curated API module that explicitly exports only the intended
public surface (e.g., export only a small explicit set or none from index.ts),
updating any imports that used the barrel to point to "./llm" or the new curated
file and keeping the generateLLMFullContextFiles/generateLlmsTxt functions
available only from their concrete module if needed.
apps/docs-smoke/scripts/llm-generate.ts (1)

16-20: ⚠️ Potential issue | 🟡 Minor

Avoid hardcoded placeholder base URLs in generated LLM artifacts.

Using https://docs.example.com at Line [19] and Line [45] can generate invalid links for the smoke app outputs.

Proposed fix
+const baseUrl = process.env.INTH_DOCS_BASE_URL ?? "http://localhost:3000";
+
 await generateLlmsTxt({
   srcDir,
   outDir,
-  baseUrl: "https://docs.example.com",
+  baseUrl,
   product: {
@@
 await generateLLMFullContextFiles({
   outDir,
-  baseUrl: "https://docs.example.com",
+  baseUrl,
   product: { name: "Smoke SDK" },

Also applies to: 43-46

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

In `@apps/docs-smoke/scripts/llm-generate.ts` around lines 16 - 20, The code
passes a hardcoded placeholder baseUrl ("https://docs.example.com") into
generateLlmsTxt, which embeds invalid links into generated artifacts; change the
call sites that set baseUrl to source a real value (e.g., read from an
environment variable like process.env.BASE_URL, a CLI/config parameter, or
derive from project config) and fall back to a sensible default only for local
dev, then pass that variable into generateLlmsTxt (and any other places that set
baseUrl) so generated LLM outputs contain correct, environment-specific URLs.
packages/docs/src/remark/plugins/type-table.remark.ts (1)

730-763: ⚠️ Potential issue | 🟠 Major

Keep the old MDX spellings working during the rename.

This drops both <AutoTypeTable> and type={...} support immediately. Existing docs will now either stop generating a table or render the raw MDX component with no migration hint, which is a breaking change for current consumers. Please keep the old component/prop aliases for at least one compatibility window, or emit a visible error instead of silently omitting the table.

♻️ Compatibility fallback sketch
 function isValidTableNode(
   node: MdxJsxFlowElement | MdxJsxTextElement
 ): boolean {
-  return hasName(node, "TypeTable") || hasName(node, "ExtractedTypeTable");
+  return (
+    hasName(node, "TypeTable") ||
+    hasName(node, "ExtractedTypeTable") ||
+    hasName(node, "AutoTypeTable")
+  );
 }
@@
-  const propertiesRaw = getAttributeValue(node, "properties");
+  const propertiesRaw =
+    getAttributeValue(node, "properties") ?? getAttributeValue(node, "type");
@@
   return createJsxComponentProcessor(
-    ["TypeTable", "ExtractedTypeTable"],
+    ["TypeTable", "ExtractedTypeTable", "AutoTypeTable"],
     (node) => {
-      if (hasName(node, "ExtractedTypeTable")) {
+      if (hasName(node, "ExtractedTypeTable") || hasName(node, "AutoTypeTable")) {
         return processExtractedTypeTableNode(node, resolved);
       }
       return processTypeTableNode(node, resolved);
     }
   );

Also applies to: 850-857

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

In `@packages/docs/src/remark/plugins/type-table.remark.ts` around lines 730 -
763, The rename removed backward-compatible aliases (AutoTypeTable and the type
prop) and silently drops tables; update processTypeTableNode and
isValidTableNode to accept hasName(node, "AutoTypeTable") as valid, and when
propertiesRaw is null fall back to getAttributeValue(node, "type") before
calling parseTypeObject; if no properties are found after fallback, return a
visible error/warning AST node instead of an empty array so consumers see a
clear migration message. Reference functions/symbols: isValidTableNode,
processTypeTableNode, hasName, getAttributeValue, parseTypeObject,
processExtractedTypeTableNode.
♻️ Duplicate comments (2)
apps/docs-smoke/src/routes/search.tsx (2)

57-84: ⚠️ Potential issue | 🟠 Major

runSearch still leaks rejected fetch/JSON errors to its callers.

If fetch() or response.json() throws here, handleSearch() gets an unhandled rejection and handleAsk() can leave searchStatus stuck on "loading". Catch non-abort failures inside runSearch, update state there, and return [] consistently.

Also applies to: 118-122, 130-135

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

In `@apps/docs-smoke/src/routes/search.tsx` around lines 57 - 84, runSearch
currently lets exceptions from fetch() or response.json() bubble up, which
causes unhandled rejections and can leave searchStatus stuck; wrap the network +
parsing logic inside a try/catch that catches non-AbortError exceptions, sets
setSearchStatus("error"), sets a friendly setError(message) (use the caught
error.message or a default), and returns [] so callers never receive a thrown
rejection; treat AbortError by returning [] without updating error/status. Apply
the same pattern to the other identical fetch/parse blocks referenced (the
blocks around handleSearch/handleAsk).

88-116: ⚠️ Potential issue | 🟠 Major

Abort the in-flight answer stream when the query changes or the route unmounts.

The Ask request still is not tied to an AbortController, so stale chunks can keep appending to answer after the user has moved on to a different query and the results/source list have already changed.

Also applies to: 124-188

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

In `@apps/docs-smoke/src/routes/search.tsx` around lines 88 - 116, In the search
useEffect that creates an AbortController and calls runSearch(trimmedQuery,
controller.signal), ensure all downstream streaming requests (the Ask/answer
stream inside runSearch or the helper that processes chunks) accept and pass
that same AbortSignal so chunks stop when aborted; update the runSearch
implementation and any Ask/streaming helper to take an AbortSignal parameter,
check signal.aborted and listen to signal.onabort, and ensure the cleanup
(controller.abort() in the returned function) will cancel the active ask/stream
so stale chunks no longer append to answer when query changes or the route
unmounts.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/docs-smoke/playwright.config.ts`:
- Around line 18-20: Add a brief inline comment above the return that explains
why we rewrite HTTPS to HTTP: note that portlessUrl may return an https:// URL
(HTTPS_PROTOCOL) but the local Playwright test runner (or local dev server)
can't/doesn't use TLS for these ephemeral ports, so we coerce to http://
(HTTP_PROTOCOL) to avoid certificate/TLS issues during local testing; reference
the portlessUrl variable and the constants HTTPS_PROTOCOL and HTTP_PROTOCOL so
maintainers know exactly what the rewrite targets.
- Around line 14-16: The module currently calls execFileSync("portless", ["get",
"docs-smoke"]) at load time (assigning portlessUrl) which will throw if the
binary is missing or the command fails; wrap that call in a try-catch around the
execFileSync call that sets portlessUrl, handle ENOENT or other errors by either
falling back to a sensible default (e.g., process.env.DOCS_SMOKE_URL or a
localhost URL) and logging/throwing a clear, actionable error message indicating
that the 'portless' command is missing or failed and how to install/resolve it;
keep the variable name portlessUrl and ensure the rest of the config uses that
fallback value so parsing of playwright.config.ts does not crash unexpectedly.

In `@apps/docs-smoke/src/routes/index.tsx`:
- Around line 11-13: Extract the inline array of paths used in the
navigationRoutes filter into a named constant (e.g., START_ROUTE_PATHS or
VISIBLE_START_ROUTES) and then use that constant in the filter call that
produces startRoutes; update the line with navigationRoutes.filter to reference
the constant and keep the predicate testing route.to against the constant
(maintain using .includes(route.to)) so intent is clearer and future updates are
localized to the constant.

In `@apps/docs-smoke/src/routes/search.tsx`:
- Around line 218-224: The input's client-side maxLength (currently 600) does
not match the server-side guard (400), causing avoidable API errors; update the
JSX input element (the element using id={inputId}, value={query},
onChange={(event) => setQuery(event.target.value)}) to set maxLength={400} to
match the server cap, and if there is a shared constant or config used
elsewhere, consolidate the value into that constant and reference it from the
input to keep client and server caps in sync.
- Around line 88-116: The debounced effect's timeout and AbortController are
local and therefore not cancelled when a manual search (handleSearch) or the Ask
path triggers an immediate request; hoist the timeout and controller into refs
(e.g., timeoutRef and controllerRef) or module-level variables, update useEffect
to assign the window.setTimeout id to timeoutRef.current and the AbortController
to controllerRef.current, and in the manual handlers (handleSearch and the Ask
handler) clear the pending timeout (clearTimeout(timeoutRef.current)) and abort
the pending controller (controllerRef.current?.abort()) before invoking
runSearch so the pending debounced request is cancelled and won’t fire later.
Ensure the effect’s cleanup still clears timeoutRef.current and aborts
controllerRef.current.

In `@apps/docs-smoke/src/styles.css`:
- Around line 87-89: Replace the utility class text-3xl on the .docs-prose h1
rule with the semantic typography token used by our design system (e.g.,
text-title-h1); update the CSS selector .docs-prose h1 to remove text-3xl and
apply the semantic token so headings use the shared typography scale.

In `@packages/docs/agent-docs/docs/convert.md`:
- Line 16: The import line currently only imports convertAllMdx and
convertMdxToMarkdown but omits writeMdxFileAsMarkdown referenced in the “three
main APIs” list; update the import statement to also include
writeMdxFileAsMarkdown so the three exported functions (convertAllMdx,
convertMdxToMarkdown, writeMdxFileAsMarkdown) are consistently shown and
available for use.

In `@packages/docs/agent-docs/docs/llms-full/generation/search.txt`:
- Around line 141-142: The docs incorrectly call the returned stream
`toTextStreamResponse()` even though streamDocsAnswer now returns a plain text
Response owned by the helper; update the wording to describe the value as a
plain Response (e.g., "The response is a plain text Response returned by
streamDocsAnswer") and clarify that `sources` should be displayed separately in
the UI rather than being embedded in the stream; remove or replace any
references to `toTextStreamResponse()` in this file and point readers to use the
streamDocsAnswer Response shape and headers instead.
- Around line 19-29: The import block is missing the type imports used in the
examples (DocsSearchIndex and DocsSearchContentStore); update the import
statement that currently brings in createAnswerContext, createMemoryRateLimiter,
listDocsContentFiles, readDocsContentChunk, readDocsContentFile,
readJsonWithLimit, searchDocs, validateDocsQuery to also import the types
DocsSearchIndex and DocsSearchContentStore from "@inth/docs/search" so the
example type casts compile; ensure the same type imports are added to the other
snippet ranges mentioned (lines 80-104, 117-120, 133-152) where those types are
referenced.

In `@packages/docs/src/components/command-tabs.tsx`:
- Around line 8-19: The CommandTabsProps should be changed to a discriminated
union so `mode` cannot be provided without `command`: create two variants—one
where `mode?: undefined` and `command?: string`/`commands?:
Partial<Record<PackageManager,string>>` (the loose/default shape), and one where
`mode: CommandMode` and `command: string` (and optionally
`defaultManager`/`children`/`commands` as needed) so any prop object with `mode`
must include a `command`; update the exported type CommandTabsProps accordingly
(referencing CommandTabsProps, CommandMode, command, and commands) to enforce
this at the type level.

In `@packages/docs/src/convert/index.ts`:
- Around line 1-7: This file adds a new barrel export surface; remove this
index.ts re-export and stop exposing ConvertResult, convertAllMdx,
convertMdxToMarkdown, MdxToMarkdownOptions, and writeMdxFileAsMarkdown via a
barrel. Instead, update callers to import directly from "./convert" or add these
symbols to the repository's dedicated public API module, and delete or revert
this index.ts so consumers are routed through explicit module paths (or the
single non-barrel API module) rather than a new barrel export.

In `@packages/docs/src/llm/llm.ts`:
- Around line 195-196: The fallback for process.env.PORTLESS_URL currently uses
a hardcoded "http://localhost" which can produce incorrect absolute URLs on
non-80 dev ports; update the fallback to be port-aware by using the runtime
origin when available (e.g., window.location.origin) or by including a port from
the environment (e.g., `process.env.PORT` or a sensible default like 3000).
Locate the code that references process.env.PORTLESS_URL in
packages/docs/src/llm/llm.ts and replace the static "http://localhost" fallback
with a constructed origin that prefers window.location.origin, then falls back
to `http://localhost:${process.env.PORT || 3000}` so generated links include the
correct port.

In `@packages/docs/src/search/bash.ts`:
- Around line 90-94: The WRITE_REDIRECT_PATTERN currently misses redirection
variants like ">|" and ">&"; update the WRITE_REDIRECT_PATTERN to also detect
variants where a single or double '>' is followed by '|' or '&' (e.g. match ">",
">>", ">|", ">&", ">>&", etc.) so write-style redirections are caught, and add a
short inline comment near WRITE_REDIRECT_PATTERN documenting remaining edge-case
limitations (here-strings, complex FD syntax) so future readers know it's a
best-effort check for sandboxed read-only enforcement.
- Around line 25-88: The UNSAFE_COMMAND_PATTERN currently lets write-capable
flag usage slip through (e.g., "sed -i" or awk scripts that redirect output),
causing write attempts to fail with generic filesystem errors instead of the
clear "Blocked unsafe docs bash command" message; update the pattern in bash.ts
(the symbol UNSAFE_COMMAND_PATTERN) to explicitly match in-place/edit flags and
redirection usage such as matching `sed\s+-i` (and variants like `sed -i''`),
and patterns for awk printing to files like `awk.*\sprint\s*>` (or similar
redirect patterns), keeping READ_ONLY_COMMANDS (and entries like "sed" and
"awk") but ensuring these flag/redirect forms are blocked; update any related
tests for ReadOnlyDocsFileSystem/command filtering to assert the new
blocked-message behavior rather than filesystem errors.

In `@packages/docs/src/search/node.ts`:
- Around line 185-190: The code allows generating an empty search index when
docsDir exists but contains no .md files; after calling
readMarkdownDocs(docsDir, baseUrl) and before creating the index (i.e., after
the docs variable is populated), add a guard that throws a clear error if docs
is empty or contains no entries (e.g., check Array.isArray(docs) && docs.length
=== 0 or no markdown items) so the process fails fast; update the block around
readMarkdownDocs, createDocsSearchIndex, indexWithContent and content to
validate docs and throw an Error like "No markdown docs found in docsDir" before
proceeding to createDocsSearchIndex.
- Around line 192-201: The outputPath and contentOutputPath construction allows
path traversal via config.outputFile / config.contentOutputFile; fix by
resolving the candidate paths with path.resolve(docsDir, config.outputFile ??
DEFAULT_OUTPUT_FILE) and path.resolve(docsDir, config.contentOutputFile ??
DEFAULT_CONTENT_OUTPUT_FILE) and then verify each resolved path begins with the
resolved docsDir (e.g., path.resolve(docsDir) as prefix); if the check fails,
reject the value (throw or fall back to the default) so outputPath and
contentOutputPath cannot escape docsDir. Use the symbol names outputPath,
contentOutputPath, docsDir, config.outputFile, config.contentOutputFile,
DEFAULT_OUTPUT_FILE and DEFAULT_CONTENT_OUTPUT_FILE to locate the changes.

In `@packages/docs/src/search/search.ts`:
- Around line 922-958: The function readJsonWithLimit currently calls
requestError when bytesRead exceeds maxBytes without cancelling the request.body
reader; update the overflow handling so that before calling requestError you
call reader.cancel() (await it if needed) to properly close the stream and
release resources, then throw or call requestError as before; ensure the same
cancellation is applied in any other early-return/error paths that occur while
the reader is active so the reader is not left hanging.

---

Outside diff comments:
In `@apps/docs-smoke/scripts/llm-generate.ts`:
- Around line 16-20: The code passes a hardcoded placeholder baseUrl
("https://docs.example.com") into generateLlmsTxt, which embeds invalid links
into generated artifacts; change the call sites that set baseUrl to source a
real value (e.g., read from an environment variable like process.env.BASE_URL, a
CLI/config parameter, or derive from project config) and fall back to a sensible
default only for local dev, then pass that variable into generateLlmsTxt (and
any other places that set baseUrl) so generated LLM outputs contain correct,
environment-specific URLs.

In `@packages/docs/src/llm/index.ts`:
- Around line 1-12: The index.ts barrel expands and re-exports many internal
symbols (CuratedLink, CuratedSection, FullTopic, generateLLMFullContextFiles,
generateLlmsTxt, LLMFullContextConfig, LlmsTxtConfig, MarkdownDoc, ProductInfo,
SourceDoc) which violates the rule against barrel files; fix by removing these
broad re-exports from the barrel (index.ts) and instead have callers import the
specific symbols directly from "./llm" or create a curated API module that
explicitly exports only the intended public surface (e.g., export only a small
explicit set or none from index.ts), updating any imports that used the barrel
to point to "./llm" or the new curated file and keeping the
generateLLMFullContextFiles/generateLlmsTxt functions available only from their
concrete module if needed.

In `@packages/docs/src/remark/plugins/type-table.remark.ts`:
- Around line 730-763: The rename removed backward-compatible aliases
(AutoTypeTable and the type prop) and silently drops tables; update
processTypeTableNode and isValidTableNode to accept hasName(node,
"AutoTypeTable") as valid, and when propertiesRaw is null fall back to
getAttributeValue(node, "type") before calling parseTypeObject; if no properties
are found after fallback, return a visible error/warning AST node instead of an
empty array so consumers see a clear migration message. Reference
functions/symbols: isValidTableNode, processTypeTableNode, hasName,
getAttributeValue, parseTypeObject, processExtractedTypeTableNode.

---

Duplicate comments:
In `@apps/docs-smoke/src/routes/search.tsx`:
- Around line 57-84: runSearch currently lets exceptions from fetch() or
response.json() bubble up, which causes unhandled rejections and can leave
searchStatus stuck; wrap the network + parsing logic inside a try/catch that
catches non-AbortError exceptions, sets setSearchStatus("error"), sets a
friendly setError(message) (use the caught error.message or a default), and
returns [] so callers never receive a thrown rejection; treat AbortError by
returning [] without updating error/status. Apply the same pattern to the other
identical fetch/parse blocks referenced (the blocks around
handleSearch/handleAsk).
- Around line 88-116: In the search useEffect that creates an AbortController
and calls runSearch(trimmedQuery, controller.signal), ensure all downstream
streaming requests (the Ask/answer stream inside runSearch or the helper that
processes chunks) accept and pass that same AbortSignal so chunks stop when
aborted; update the runSearch implementation and any Ask/streaming helper to
take an AbortSignal parameter, check signal.aborted and listen to
signal.onabort, and ensure the cleanup (controller.abort() in the returned
function) will cancel the active ask/stream so stale chunks no longer append to
answer when query changes or the route unmounts.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 05549b07-6dcb-4d7d-91af-75a02f4a66d6

📥 Commits

Reviewing files that changed from the base of the PR and between 040afaf and dedf1a8.

⛔ Files ignored due to path filters (2)
  • apps/docs-smoke/src/generated/docs-search-content.json is excluded by !**/generated/**
  • apps/docs-smoke/src/generated/docs-search-index.json is excluded by !**/generated/**
📒 Files selected for processing (69)
  • .agents/skills/inth-docs/SKILL.md
  • README.md
  • apps/docs-smoke/content/docs/guides/components-fixture.mdx
  • apps/docs-smoke/content/docs/guides/extracted-type-table-fixture.mdx
  • apps/docs-smoke/content/docs/guides/quickstart.mdx
  • apps/docs-smoke/content/docs/index.mdx
  • apps/docs-smoke/content/docs/meta.json
  • apps/docs-smoke/content/docs/search.mdx
  • apps/docs-smoke/playwright.config.ts
  • apps/docs-smoke/scripts/bench.ts
  • apps/docs-smoke/scripts/llm-generate-real.ts
  • apps/docs-smoke/scripts/llm-generate.ts
  • apps/docs-smoke/scripts/mdx-convert.ts
  • apps/docs-smoke/scripts/search-generate.ts
  • apps/docs-smoke/scripts/test-pipeline.ts
  • apps/docs-smoke/src/components/component-matrix.tsx
  • apps/docs-smoke/src/components/docs-shell.tsx
  • apps/docs-smoke/src/components/site-header.tsx
  • apps/docs-smoke/src/lib/docs.ts
  • apps/docs-smoke/src/routes/docs/index.tsx
  • apps/docs-smoke/src/routes/index.tsx
  • apps/docs-smoke/src/routes/playground.tsx
  • apps/docs-smoke/src/routes/search.tsx
  • apps/docs-smoke/src/styles.css
  • apps/docs-smoke/tests/e2e/smoke.e2e.ts
  • packages/docs/README.md
  • packages/docs/agent-docs-src/docs/components.mdx
  • packages/docs/agent-docs-src/docs/convert.mdx
  • packages/docs/agent-docs-src/docs/index.mdx
  • packages/docs/agent-docs-src/docs/llm.mdx
  • packages/docs/agent-docs-src/docs/remark.mdx
  • packages/docs/agent-docs-src/docs/search.mdx
  • packages/docs/agent-docs/docs/components.md
  • packages/docs/agent-docs/docs/convert.md
  • packages/docs/agent-docs/docs/index.md
  • packages/docs/agent-docs/docs/llm.md
  • packages/docs/agent-docs/docs/llms-full/authoring/components.txt
  • packages/docs/agent-docs/docs/llms-full/authoring/remark.txt
  • packages/docs/agent-docs/docs/llms-full/generation/convert.txt
  • packages/docs/agent-docs/docs/llms-full/generation/llm.txt
  • packages/docs/agent-docs/docs/llms-full/generation/search.txt
  • packages/docs/agent-docs/docs/llms-full/overview.txt
  • packages/docs/agent-docs/docs/remark.md
  • packages/docs/agent-docs/docs/search.md
  • packages/docs/scripts/generate-agent-docs.ts
  • packages/docs/src/components/command-tabs.tsx
  • packages/docs/src/components/components.test.tsx
  • packages/docs/src/components/index.ts
  • packages/docs/src/components/mdx-components.ts
  • packages/docs/src/components/type-table.tsx
  • packages/docs/src/convert/convert.ts
  • packages/docs/src/convert/index.ts
  • packages/docs/src/lint/runner.ts
  • packages/docs/src/llm/index.ts
  • packages/docs/src/llm/llm.test.ts
  • packages/docs/src/llm/llm.ts
  • packages/docs/src/remark/index.ts
  • packages/docs/src/remark/plugins/command-tabs.remark.ts
  • packages/docs/src/remark/plugins/type-table.remark.ts
  • packages/docs/src/remark/remark-output.test.ts
  • packages/docs/src/search/ai.test.ts
  • packages/docs/src/search/bash.test.ts
  • packages/docs/src/search/bash.ts
  • packages/docs/src/search/index.ts
  • packages/docs/src/search/node-index.ts
  • packages/docs/src/search/node.test.ts
  • packages/docs/src/search/node.ts
  • packages/docs/src/search/search.test.ts
  • packages/docs/src/search/search.ts

Comment thread apps/docs-smoke/playwright.config.ts Outdated
Comment thread apps/docs-smoke/playwright.config.ts
Comment thread apps/docs-smoke/src/routes/index.tsx
Comment thread apps/docs-smoke/src/routes/search.tsx Outdated
Comment thread apps/docs-smoke/src/routes/search.tsx
Comment thread packages/docs/src/search/bash.ts
Comment thread packages/docs/src/search/bash.ts
Comment thread packages/docs/src/search/node.ts
Comment thread packages/docs/src/search/node.ts Outdated
Comment thread packages/docs/src/search/search.ts
@KayleeWilliams KayleeWilliams merged commit 6556ed5 into main Apr 22, 2026
2 checks passed
@KayleeWilliams KayleeWilliams deleted the KayleeWilliams/compact-docs-search branch April 22, 2026 18:36
KayleeWilliams added a commit that referenced this pull request Apr 26, 2026
Integrates main's component-ownership refactor (PR #8) and AI search
work (#5/#7). Adapts the c15t-compat changes from d289d82 onto the new
component locations under apps/docs-smoke/src/components/docs-mdx/:

  - Callout: keeps `type` alias and `warn`/`note` mapping. Inlined
    normalizeVariant/titleCase since the component now lives in the app
    (no shared internal/ helpers needed).
  - Card: keeps `icon`, `variant`, `CardVariant` type.
  - Tabs: keeps `groupId` prop with JSDoc.
  - CommandTabs: takes main's design wholesale (`mode: run|install|create`,
    discriminated union). Drops the duplicate PackageCommandTabs.

Drops `packages/docs/src/internal/{callout-variant,package-managers}.{ts,test.ts}`
since main's design has the component app-owned (with its own table)
and main's command-tabs.remark.ts is self-contained.

Keeps the new remark plugins (section, details, remove-jsx-comments) and
the warn/note alias in callout.remark.ts. Updates remark-output tests to
use the renamed convertMdxToMarkdown.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant