Skip to content

feat: is it agent ready#71

Merged
Debbl merged 3 commits intomainfrom
feat/isitagentready
Apr 27, 2026
Merged

feat: is it agent ready#71
Debbl merged 3 commits intomainfrom
feat/isitagentready

Conversation

@Debbl
Copy link
Copy Markdown
Owner

@Debbl Debbl commented Apr 27, 2026

Summary by CodeRabbit

  • New Features

    • Added Web MCP provider for agent tool integration
    • Added new UI components: Combobox and NativeSelect
    • Added health check, API discovery, and OAuth endpoints
    • Added support for directional layout in sidebar navigation
    • Added Markdown documentation serving with metadata headers
  • Improvements

    • Enhanced component styling and accessibility across UI library
    • Improved tooltip positioning and navigation menu transitions
    • Better responsive design with bidirectional support
    • Refined form components and interactive controls
    • Updated to latest React and Next.js versions

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 27, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
shadcn-hooks 76fcafe Commit Preview URL

Branch Preview URL
Apr 27 2026, 03:40 AM

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 27, 2026

Warning

Rate limit exceeded

@Debbl has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 29 minutes and 19 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 315f2982-d44d-49ac-a52f-49fa37b2c0f5

📥 Commits

Reviewing files that changed from the base of the PR and between b385cd2 and 76fcafe.

📒 Files selected for processing (2)
  • eslint.config.js
  • vitest.config.ts
📝 Walkthrough

Walkthrough

This PR updates dependencies (pnpm to 10.33.2, React/Next.js stacks, design libraries), introduces agent discovery endpoints (MCP server card, API catalog, OAuth/OpenID configurations), adds a WebMCP provider for client-side tool registration, refactors UI component styling with Tailwind class reordering, and integrates new agent metadata infrastructure.

Changes

Cohort / File(s) Summary
Dependency Updates
package.json, packages/shadcn-hooks/package.json
Updated pnpm to 10.33.2; bumped React/React-DOM to ^19.2.5, Next.js to ^16.2.4, major upgrades to @base-ui/react (^1.4.1), fumadocs (^16.8.x), lucide-react (^1.11.0), and tooling (TypeScript ^6.0.3, Tailwind ^4.2.4, ESLint ^3.16.0, Vitest ^4.1.5).
Agent Discovery Routes
src/app/.well-known/api-catalog/route.ts, src/app/.well-known/agent-skills/..., src/app/.well-known/mcp/..., src/app/.well-known/oauth-*.../route.ts, src/app/.well-known/openid-configuration/route.ts
Added six new discovery endpoint handlers that serve static JSON/Markdown (API catalog, MCP server card, OAuth/OpenID config, agent skills index and markdown).
Agent Discovery Utilities
src/lib/agent-discovery.ts
New module providing helpers for generating agent metadata (OpenAPI docs, OAuth configs, MCP card, API catalog linkset) and response wrappers (JSON/text with proper content-types, SHA-256 hashing, Markdown token counting).
Client-Side MCP Integration
src/components/webmcp-provider.tsx
New provider component that registers three WebMCP tools (search_docs, list_agent_resources, open_docs) into navigator.modelContext via useEffect.
Health & Static Routes
src/app/health/route.ts, src/app/index.md/route.ts, src/app/openapi.json/route.ts, src/app/robots.txt/route.ts
Added health check and static content routes (home Markdown, OpenAPI spec, robots.txt); removed legacy robots.ts metadata route.
Configuration & Integration
public/_headers, src/app/(home)/page.tsx
Added HTTP Link headers for discovery endpoints; injected WebMcpProvider into home page.
UI Component Styling
src/components/ui/accordion.tsx, src/components/ui/alert-dialog.tsx, src/components/ui/alert.tsx, src/components/ui/avatar.tsx, src/components/ui/badge.tsx, src/components/ui/breadcrumb.tsx, src/components/ui/button.tsx, src/components/ui/button-group.tsx, src/components/ui/calendar.tsx, src/components/ui/card.tsx, src/components/ui/carousel.tsx, src/components/ui/chart.tsx, src/components/ui/checkbox.tsx, src/components/ui/combobox.tsx, src/components/ui/command.tsx, src/components/ui/context-menu.tsx, src/components/ui/dialog.tsx, src/components/ui/direction.tsx, src/components/ui/drawer.tsx, src/components/ui/dropdown-menu.tsx, src/components/ui/empty.tsx, src/components/ui/field.tsx, src/components/ui/hover-card.tsx, src/components/ui/input.tsx, src/components/ui/input-group.tsx, src/components/ui/input-otp.tsx, src/components/ui/item.tsx, src/components/ui/kbd.tsx, src/components/ui/label.tsx, src/components/ui/menubar.tsx, src/components/ui/native-select.tsx, src/components/ui/navigation-menu.tsx, src/components/ui/pagination.tsx, src/components/ui/popover.tsx, src/components/ui/progress.tsx, src/components/ui/radio-group.tsx, src/components/ui/resizable.tsx, src/components/ui/scroll-area.tsx, src/components/ui/select.tsx, src/components/ui/separator.tsx, src/components/ui/sheet.tsx, src/components/ui/sidebar.tsx, src/components/ui/skeleton.tsx, src/components/ui/slider.tsx, src/components/ui/switch.tsx, src/components/ui/table.tsx, src/components/ui/tabs.tsx, src/components/ui/textarea.tsx, src/components/ui/toggle-group.tsx, src/components/ui/toggle.tsx, src/components/ui/tooltip.tsx
Extensive refactoring of Tailwind class composition: reordered utilities, changed attribute selectors (from data-[orientation=...] to data-horizontal/vertical), updated focus-ring sizing to ring-3, modified popup background colors from bg-background to bg-popover, added inset props to menu/dropdown items, and rewrote animation/transition classes using data-starting-style/data-ending-style patterns. Added new NativeSelect and Combobox component modules; updated Calendar and Chart with new props/type signatures; simplified RadioGroup indicator rendering; migrated Resizable to new react-resizable-panels API; removed TooltipProvider wrapper; updated NavigationMenu styling and added align prop support; changed Sidebar collapsible default and added dir passthrough.
Minor Fixes
src/components/mdx/container-with-demo.client.tsx, src/registry/hooks/use-event-listener/demo/demo-01.tsx
Removed unused ref and ResizablePanelGroup prop; fixed demo button element type annotation.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • feat: upgrade fumadocs #54: Updates overlapping root-level dependencies (fumadocs, lucide-react, motion, react-day-picker, react-resizable-panels, pnpm).
  • feat: use base-ui #38: Implements related UI library migrations and component refactoring for @base-ui/react adoption with updated component signatures.
  • feat: add skills #55: Adds shadcn-hooks skill documentation content served by the agent-discovery routes introduced in this PR.

Poem

🐰 Routes now bloom in .well-known space,
Discovery metadata in every place!
Tailwind classes danced and swayed,
UI components gently repaid.
WebMCP tools hop through the sand,
Agent skills flourish, perfectly fanned! 🌿✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'is it agent ready' is vague and doesn't clearly communicate the main changes to the codebase. While the PR includes agent discovery endpoints and WebMCP provider setup, the title uses non-descriptive phrasing that doesn't convey meaningful information about these substantial additions. Use a more specific title that describes the primary change, such as 'Add agent discovery endpoints and WebMCP provider integration' or 'feat: implement agent-ready infrastructure with discovery endpoints'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/isitagentready

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

❤️ Share

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 13

Note

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

Caution

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

⚠️ Outside diff range comments (5)
src/components/ui/pagination.tsx (1)

65-99: ⚠️ Potential issue | 🟡 Minor

aria-label not synchronized with the new text prop.

When consumers pass a custom text (e.g. for localization like text="上一页" / text="下一页"), the visible label updates but aria-label stays hardcoded as "Go to previous page" / "Go to next page". This creates a mismatch for screen-reader users on non-English UIs and partially defeats the purpose of exposing the text prop.

Consider accepting an optional aria-label (or deriving it from text) so the accessible name stays consistent with the visible label.

♻️ Proposed adjustment
 function PaginationPrevious({
   className,
   text = "Previous",
+  "aria-label": ariaLabel = "Go to previous page",
   ...props
 }: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
   return (
     <PaginationLink
-      aria-label="Go to previous page"
+      aria-label={ariaLabel}
       size="default"
       className={cn("pl-2!", className)}
       {...props}
     >
       <ChevronLeftIcon data-icon="inline-start" />
       <span className="hidden sm:block">{text}</span>
     </PaginationLink>
   )
 }
@@
 function PaginationNext({
   className,
   text = "Next",
+  "aria-label": ariaLabel = "Go to next page",
   ...props
 }: React.ComponentProps<typeof PaginationLink> & { text?: string }) {
   return (
     <PaginationLink
-      aria-label="Go to next page"
+      aria-label={ariaLabel}
       size="default"
       className={cn("pr-2!", className)}
       {...props}
     >
       <span className="hidden sm:block">{text}</span>
       <ChevronRightIcon data-icon="inline-end" />
     </PaginationLink>
   )
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/pagination.tsx` around lines 65 - 99, PaginationPrevious
and PaginationNext hardcode aria-labels that can diverge from the visible text
when consumers pass a custom text (e.g. localized labels); update both
components to accept an optional ariaLabel prop (or derive the accessible name
from the text prop) and use aria-label={ariaLabel ?? text} instead of the fixed
English string so screen reader names always match the visible label; adjust the
prop types for PaginationPrevious and PaginationNext (the props union with
React.ComponentProps<typeof PaginationLink>) to include ariaLabel?: string and
apply the new aria value to the PaginationLink elements.
src/components/ui/tabs.tsx (1)

14-22: ⚠️ Potential issue | 🔴 Critical

Fix Tailwind selectors to match Base UI's data-orientation attribute, not non-existent data-horizontal/data-vertical attributes.

Base UI's Tabs.Root sets a data-orientation attribute with values 'horizontal' or 'vertical' — not separate data-horizontal and data-vertical boolean attributes. The Tailwind selectors on line 18 (data-horizontal:flex-col) and throughout the file (group-data-horizontal/tabs:*, group-data-vertical/tabs:*) will not match anything and styles will silently fail to apply.

Change selectors to use data-[orientation=horizontal]:flex-col, group-data-[orientation=horizontal]/tabs:*, and group-data-[orientation=vertical]/tabs:* instead. The manual data-orientation={orientation} prop on line 16 is correct and should be kept.

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

In `@src/components/ui/tabs.tsx` around lines 14 - 22, Tailwind selectors are
using non-existent data-horizontal/data-vertical attributes so update all
selectors to target Base UI's data-orientation attribute: in the
TabsPrimitive.Root JSX (symbol: TabsPrimitive.Root) keep
data-orientation={orientation} but replace class names like
"data-horizontal:flex-col" with "data-[orientation=horizontal]:flex-col" and
update any group selectors (e.g., "group-data-horizontal/tabs:*",
"group-data-vertical/tabs:*") to "group-data-[orientation=horizontal]/tabs:*"
and "group-data-[orientation=vertical]/tabs:*" respectively so styles correctly
match orientation values.
src/components/mdx/container-with-demo.client.tsx (1)

52-60: ⚠️ Potential issue | 🟡 Minor

Verify the second <ResizablePanel /> is still needed.

With <ResizableHandle> commented out and the first panel pinned at defaultSize={100}, the empty second <ResizablePanel /> will receive 0% size and effectively renders nothing. If the goal is just to show children with no resizing UX, consider dropping the second panel (and likely the entire group wrapper) to remove dead structure. If it’s a placeholder for upcoming work, a brief TODO comment would help future readers.

♻️ Optional simplification if the second panel/handle aren't planned
-          <ResizablePanelGroup
-            orientation='horizontal'
-            className='bg-transparent'
-          >
-            <ResizablePanel defaultSize={100} className='bg-transparent'>
-              {children}
-            </ResizablePanel>
-            {/* <ResizableHandle withHandle className='bg-transparent' /> */}
-            <ResizablePanel />
-          </ResizablePanelGroup>
+          <div className='bg-transparent'>{children}</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/mdx/container-with-demo.client.tsx` around lines 52 - 60, The
second empty ResizablePanel inside ResizablePanelGroup is currently dead (first
panel is pinned with defaultSize={100} and ResizableHandle is commented out);
either remove the trailing <ResizablePanel /> and collapse the group (or remove
ResizablePanelGroup entirely) to eliminate the unused element, or if it's
intentionally reserved for future work, add a clear TODO comment near
ResizablePanelGroup indicating the placeholder purpose and restore or uncomment
ResizableHandle to enable resizing; reference the ResizablePanelGroup,
ResizablePanel, ResizableHandle and the defaultSize={100} usage when making the
change.
src/components/ui/sidebar.tsx (1)

152-216: ⚠️ Potential issue | 🟡 Minor

dir is only forwarded on mobile; the desktop wrapper silently drops it.

dir is destructured at Line 158 and applied to <SheetContent dir={dir} /> (Line 186) on the mobile branch, but in the desktop branch (Lines 208–251) it never lands on any rendered element — the {...props} spread on the inner <div data-slot="sidebar-container"> no longer contains dir because it was destructured out. If the prop is meant to support per-component RTL on desktop, apply it to one of the desktop wrappers; otherwise consider removing it from the API to avoid confusion.

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

In `@src/components/ui/sidebar.tsx` around lines 152 - 216, The Sidebar component
currently destructures dir but only uses it on the mobile SheetContent; for
desktop the prop is dropped. Fix by forwarding dir to the desktop wrapper: add
dir={dir} (or spread {...props} onto the outer desktop <div className="group
peer..."> with data-slot="sidebar") so the desktop branch receives the dir prop
(instead of relying on the inner data-slot="sidebar-container" spread which no
longer has dir). Update the Sidebar function (referenced by name) to apply dir
to that desktop wrapper element.
package.json (1)

1-120: ⚠️ Potential issue | 🔴 Critical

CI is red — vitest is failing on this branch.

The pipeline log shows nr test (vitest) exiting with code 1. Given the simultaneous bumps of vitest@^4.1.5, @vitejs/plugin-react@^6.0.1, jsdom@^29, @testing-library/react@^16.3.2, and React 19.2.5, this is most likely a breaking-change knock-on. Please rerun locally and either pin compatible versions or update tests/setup before merging.

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

In `@package.json` around lines 1 - 120, CI is failing because vitest tests break
after simultaneous dependency bumps (vitest, `@vitejs/plugin-react`, jsdom,
`@testing-library/react`, react); reproduce the failure locally by running the
"test" script (vitest), identify the breaking package(s) from the stack traces,
and either pin the offending packages back to known-compatible versions in
package.json (e.g., adjust "vitest", "@vitejs/plugin-react", "jsdom",
"@testing-library/react", or "react") or update the test environment/setup
(vitest config, jsdom environment initialization, React testing-library adapters
or wrappers) to match the new major versions before committing; ensure
rollback/pins are committed to package.json and rerun "pnpm install" and "pnpm
test" to confirm CI will pass.
🟡 Minor comments (9)
src/components/ui/navigation-menu.tsx-116-116 (1)

116-116: ⚠️ Potential issue | 🟡 Minor

Replace data-[ending-style]:easing-[ease] with data-[ending-style]:ease-[ease]

Tailwind v4 uses the ease-* prefix for transition timing functions (e.g., ease-linear, ease-[ease], ease-[cubic-bezier(...)]). The easing-* prefix does not exist and will not generate any CSS rule, causing the intended ease override to be dropped.

Additionally, xs: is not a configured breakpoint in this project—Tailwind v4 ships only with sm and up by default—so that utility will also not apply.

Also note: The same line uses data-ending-style: and data-starting-style: in later selectors (e.g., data-ending-style:scale-90), which should use bracket syntax to match the variant syntax: data-[ending-style]:scale-90 and data-[starting-style]:opacity-0.

Proposed fix
-        <NavigationMenuPrimitive.Popup className="data-[ending-style]:easing-[ease] xs:w-(--popup-width) relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) rounded-lg bg-popover text-popover-foreground shadow ring-1 ring-foreground/10 transition-[opacity,transform,width,height,scale,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] outline-none data-ending-style:scale-90 data-ending-style:opacity-0 data-ending-style:duration-150 data-starting-style:scale-90 data-starting-style:opacity-0">
+        <NavigationMenuPrimitive.Popup className="data-[ending-style]:ease-[ease] xs:w-(--popup-width) relative h-(--popup-height) w-(--popup-width) origin-(--transform-origin) rounded-lg bg-popover text-popover-foreground shadow ring-1 ring-foreground/10 transition-[opacity,transform,width,height,scale,translate] duration-[0.35s] ease-[cubic-bezier(0.22,1,0.36,1)] outline-none data-[ending-style]:scale-90 data-[ending-style]:opacity-0 data-[ending-style]:duration-150 data-[starting-style]:scale-90 data-[starting-style]:opacity-0">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/navigation-menu.tsx` at line 116, In the
NavigationMenuPrimitive.Popup className, fix invalid Tailwind utilities: replace
the non-existent "data-[ending-style]:easing-[ease]" with
"data-[ending-style]:ease-[ease]", change any "data-ending-style:scale-90" and
"data-starting-style:opacity-0" occurrences to the bracket-variant form
"data-[ending-style]:scale-90" and "data-[starting-style]:opacity-0", and remove
or replace the unused "xs:" breakpoint (use an existing project breakpoint such
as "sm:" or omit it) so the intended utilities actually generate CSS.
packages/shadcn-hooks/package.json-54-54 (1)

54-54: ⚠️ Potential issue | 🟡 Minor

@types/node@^25 targets Node 25.x but CI runs on LTS (currently 22.x) — add engines.node field or align types.

The package uses @types/node@^25.6.0 in devDependencies while CI tests against lts/* (Node 22.x). This mismatch means TypeScript may suggest APIs not available in the LTS runtime. Add an engines.node field to package.json clarifying supported versions (e.g., "engines": {"node": ">=20"}) or downgrade @types/node to a version aligned with the Node LTS versions you support.

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

In `@packages/shadcn-hooks/package.json` at line 54, The package.json currently
references `@types/node`@^25.6.0 in devDependencies which targets Node 25.x while
CI runs LTS (22.x); either add an engines.node field in package.json (e.g.,
"engines": {"node": ">=20"} or a specific range you support) to declare the
supported Node runtime, or align the types by downgrading the devDependency
`@types/node` to a version matching your LTS (e.g., Node 22.x) so TypeScript
typings match CI; update package.json's devDependencies/@types/node or add the
engines.node entry accordingly and run npm install to update lockfile.
src/components/ui/switch.tsx-19-19 (1)

19-19: ⚠️ Potential issue | 🟡 Minor

h-[18.4px] looks like a typo or a sub-pixel value.

Fractional pixel heights are unusual for a control like this and can render inconsistently across DPR / zoom levels. Given the thumb is size-4 (16px), an integer height like h-[18px] or h-5 (20px) is more likely what was intended. Please double-check this isn't a typo from h-[18px].

🛠️ Likely intended value
-        "peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18.4px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",
+        "peer group/switch relative inline-flex shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none after:absolute after:-inset-x-3 after:-inset-y-2 focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=default]:h-[18px] data-[size=default]:w-[32px] data-[size=sm]:h-[14px] data-[size=sm]:w-[24px] dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 data-checked:bg-primary data-unchecked:bg-input dark:data-unchecked:bg-input/80 data-disabled:cursor-not-allowed data-disabled:opacity-50",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/switch.tsx` at line 19, The class string in the Switch
component includes a fractional height token "h-[18.4px]" which is likely a typo
and can cause inconsistent rendering; update the token inside the class list in
src/components/ui/switch.tsx (the string used to define the switch root/button
styles) to an integer pixel value such as "h-[18px]" or a Tailwind utility like
"h-5" to match the 16px thumb (size-4), and verify the corresponding
data-[size=sm] value remains consistent.
src/lib/agent-discovery.ts-145-163 (1)

145-163: ⚠️ Potential issue | 🟡 Minor

OAuth metadata stubs publish empty arrays for required fields.

Per RFC 8414, response_types_supported is REQUIRED for an authorization server metadata document, and per RFC 9728 the authorization_servers list is what tells a client where to authenticate against a protected resource. Returning empty arrays at /.well-known/oauth-authorization-server and /.well-known/oauth-protected-resource is technically a malformed AS metadata document and may cause OAuth-aware agents/scanners to flag the site or attempt fallbacks.

If this site genuinely doesn't offer OAuth, consider not exposing these .well-known endpoints at all (a 404 is the spec-compliant signal that auth is not required); or, if MCP discovery requires their presence, add a comment explaining the stub-by-design intent.

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

In `@src/lib/agent-discovery.ts` around lines 145 - 163, The current handlers
oauthAuthorizationServer() and oauthProtectedResource() return empty arrays for
required OAuth metadata fields; update oauthAuthorizationServer() to return a
minimally valid AS metadata document (e.g., set issuer to websiteConfig.baseUrl,
response_types_supported to at least ["code"], grant_types_supported to
appropriate values like ["authorization_code","refresh_token"], and populate
scopes_supported with whatever scopes the app exposes or ["openid"] if
applicable), and update oauthProtectedResource() to populate
authorization_servers with the AS issuer URL (e.g., websiteConfig.baseUrl or the
value returned by oauthAuthorizationServer().issuer), add at least one scope in
scopes_supported and keep bearer_methods_supported, and include a short comment
above both functions noting these are minimal/stub values by design (or
alternatively remove/expose 404 behavior if you want the endpoints absent).
Ensure you modify the functions named oauthAuthorizationServer and
oauthProtectedResource accordingly.
src/lib/agent-discovery.ts-42-44 (1)

42-44: ⚠️ Potential issue | 🟡 Minor

markdownTokenCount returns "1" for empty/whitespace input.

"".trim().split(/\s+/) is [""] (length 1), so markdownTokenCount("") and markdownTokenCount(" ") both report "1". Not a correctness bug for the current call site (homeMarkdown is non-empty), but the value is exposed publicly via the x-markdown-tokens response header and a future caller could trip over it. Also worth nothing the function name implies LLM-token counting while the implementation counts whitespace-separated words.

🐛 Proposed guard + clarified name
-export function markdownTokenCount(markdown: string): string {
-  return markdown.trim().split(/\s+/).length.toString()
-}
+export function markdownWordCount(markdown: string): string {
+  const trimmed = markdown.trim()
+  if (!trimmed) return "0"
+  return trimmed.split(/\s+/).length.toString()
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/agent-discovery.ts` around lines 42 - 44, The current
markdownTokenCount implementation returns "1" for empty or whitespace-only input
and also misleadingly counts words, not LLM tokens; update the function
markdownTokenCount(markdown: string) so that it trims input and if the result is
an empty string it returns "0", otherwise split on /\s+/ and return the length
as a string; also either rename the function to markdownWordCount or add a clear
comment that it returns a whitespace-separated word count (not LLM tokens) to
avoid future confusion.
src/components/ui/chart.tsx-60-60 (1)

60-60: ⚠️ Potential issue | 🟡 Minor

id ?? uniqueId allows an empty string to slip through.

Switching from || to ?? means a caller passing id="" now produces chart- (no suffix). The previous || form fell back to uniqueId for any falsy value. Probably benign, but worth flagging since id is a <div> attribute and an empty string is a plausible incoming value. If you want to keep the strict-null-coalesce style, consider normalizing empty strings:

🐛 Optional guard
-  const chartId = `chart-${id ?? uniqueId.replace(/:/g, "")}`
+  const chartId = `chart-${id || uniqueId.replace(/:/g, "")}`
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/chart.tsx` at line 60, The current chartId computation uses
the nullish coalescing form id ?? uniqueId which lets an empty string slip
through and produce a bare "chart-" id; change the logic to treat empty strings
as missing by normalizing id before composing chartId (e.g., compute a
resolvedId from id that treats ""/all-whitespace as absent, then use resolvedId
|| uniqueId.replace(/:/g, "") to build chartId). Update the identifier
construction around the chartId variable so it references the normalized
resolvedId and still strips colons from uniqueId as before.
src/components/ui/sidebar.tsx-291-298 (1)

291-298: ⚠️ Potential issue | 🟡 Minor

Identical ltr:/rtl: translate looks like a copy-paste bug.

ltr:-translate-x-1/2 rtl:-translate-x-1/2

Both directions apply the same -translate-x-1/2, which is functionally equivalent to writing -translate-x-1/2 with no direction modifier. Combined with the logical after:start-1/2 (which flips X position between LTR and RTL), the rail won't be visually centered in RTL. The intent is almost certainly to mirror in RTL.

🐛 Suggested fix
-        "absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:-translate-x-1/2",
+        "absolute inset-y-0 z-20 hidden w-4 transition-all ease-linear group-data-[side=left]:-right-4 group-data-[side=right]:left-0 after:absolute after:inset-y-0 after:start-1/2 after:w-[2px] hover:after:bg-sidebar-border sm:flex ltr:-translate-x-1/2 rtl:translate-x-1/2",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/sidebar.tsx` around lines 291 - 298, Update the mirrored
translate utility in the class string used by the Sidebar component's className
(the cn(...) call) so RTL flips the X translation instead of repeating the LTR
negative value: change the current "ltr:-translate-x-1/2 rtl:-translate-x-1/2"
pattern to use the opposite sign for rtl (so RTL uses translate-x-1/2), ensuring
the rail is visually centered when combined with after:start-1/2; modify the
token inside the className array/string in src/components/ui/sidebar.tsx where
the cn(...) is defined.
src/app/robots.txt/route.ts-5-16 (1)

5-16: ⚠️ Potential issue | 🟡 Minor

Verify the baked-in Sitemap URL in production builds.

Because the route is force-static, siteUrl('/sitemap.xml') is evaluated at build time. The websiteConfig.baseUrl switches on process.env.NODE_ENV === 'development', so if a build ever runs without NODE_ENV=production the static robots.txt will ship http://localhost:3000/sitemap.xml. Worth confirming the deploy pipeline always builds with NODE_ENV=production, or switch to a non-NODE_ENV config (e.g. a NEXT_PUBLIC_SITE_URL) to make the baseUrl deterministic.

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

In `@src/app/robots.txt/route.ts` around lines 5 - 16, The robots.txt GET handler
bakes siteUrl('/sitemap.xml') at build time (route is force-static) which can
produce a localhost URL if the build runs without NODE_ENV=production; update
the GET implementation to use a deterministic public config/value instead of
NODE_ENV-driven websiteConfig (e.g. read process.env.NEXT_PUBLIC_SITE_URL or
another NEXT_PUBLIC_* variable and build the sitemap URL from that), or change
the route to be runtime/dynamic so siteUrl is resolved at request time; ensure
you update references in GET and any helper that calls siteUrl('/sitemap.xml')
to use the chosen deterministic source.
src/components/webmcp-provider.tsx-55-72 (1)

55-72: ⚠️ Potential issue | 🟡 Minor

Add error handling around the search fetch.

If /api/search is unreachable or returns a non-OK status, fetch either throws (network error) or returns an error body that is still JSON-parsed/text-read silently. The tool will surface a raw rejection to provideContext with no actionable message for the agent.

🛠️ Proposed fix
-      const response = await fetch(
-        `/api/search?query=${encodeURIComponent(query)}`,
-      )
-      const contentType = response.headers.get('content-type') ?? ''
-
-      if (contentType.includes('application/json')) {
-        return response.json()
-      }
-
-      return { text: await response.text() }
+      try {
+        const response = await fetch(
+          `/api/search?query=${encodeURIComponent(query)}`,
+        )
+        if (!response.ok) {
+          return { error: `search failed with status ${response.status}` }
+        }
+        const contentType = response.headers.get('content-type') ?? ''
+        return contentType.includes('application/json')
+          ? await response.json()
+          : { text: await response.text() }
+      } catch (err) {
+        return { error: err instanceof Error ? err.message : 'search failed' }
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/webmcp-provider.tsx` around lines 55 - 72, Wrap the fetch call
inside execute(...) in a try/catch and validate response.ok: after getting query
(via readStringField), perform fetch in a try block, check response.ok and if
false read the response body (use content-type to decide json vs text) and
return a structured error like { error: `Search failed`, status:
response.status, body: ... }; also catch network exceptions and return { error:
'Network error contacting /api/search', details: error.message } so callers
(e.g., provideContext) get actionable messages instead of raw rejections.
🧹 Nitpick comments (16)
src/components/ui/native-select.tsx (2)

27-27: Nit: selection:* and placeholder:* utilities have no effect on <select>.

<select> elements don’t render a text selection or a placeholder pseudo-element, so selection:bg-primary, selection:text-primary-foreground, and placeholder:text-muted-foreground are dead classes carried over from input-style components. Safe to drop to keep the class list focused.

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

In `@src/components/ui/native-select.tsx` at line 27, The class list on the select
element in the NativeSelect component contains irrelevant utilities
(`selection:bg-primary`, `selection:text-primary-foreground`,
`placeholder:text-muted-foreground`) that have no effect on <select> elements;
remove these dead classes from the className string in
src/components/ui/native-select.tsx (the className assigned inside the
NativeSelect component) so the class list only contains applicable utilities for
<select> elements.

10-33: className is applied to the wrapper, not the <select>.

Consumers passing className (e.g. width, focus-visible utilities, sizing) expect to style the underlying <select>, since NativeSelect is presented as a select replacement and ...props are spread to the <select>. Currently it merges into the wrapper <div>, which makes overriding select-specific styles awkward and asymmetric with SelectTrigger in src/components/ui/select.tsx (where className styles the trigger itself).

Consider routing className to the <select> and exposing a separate prop (e.g. wrapperClassName) if wrapper-level overrides are needed.

♻️ Suggested change
 function NativeSelect({
   className,
   size = "default",
   ...props
 }: NativeSelectProps) {
   return (
     <div
-      className={cn(
-        "group/native-select relative w-fit has-[select:disabled]:opacity-50",
-        className
-      )}
+      className="group/native-select relative w-fit has-[select:disabled]:opacity-50"
       data-slot="native-select-wrapper"
       data-size={size}
     >
       <select
         data-slot="native-select"
         data-size={size}
-        className="h-9 w-full min-w-0 appearance-none rounded-md border border-input bg-transparent py-1 pr-8 pl-2.5 text-sm shadow-xs transition-[color,box-shadow] outline-none select-none selection:bg-primary selection:text-primary-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=sm]:h-8 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40"
+        className={cn(
+          "h-9 w-full min-w-0 appearance-none rounded-md border border-input bg-transparent py-1 pr-8 pl-2.5 text-sm shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-[size=sm]:h-8 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
+          className
+        )}
         {...props}
       />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/native-select.tsx` around lines 10 - 33, The component
NativeSelect currently applies the incoming className to the wrapper div instead
of the <select>, so change the API to forward className to the <select> and add
a new wrapperClassName (or similar) prop for the outer div; update the
NativeSelectProps type to include wrapperClassName, leave the wrapper's default
classes intact but merge wrapperClassName into the div, and move the existing
className merge into the <select> (ensure spread {...props} does not
accidentally override the computed className by applying className on the
<select> after spreading or by composing it before spreading).
src/components/ui/select.tsx (1)

165-166: Nit: collapse empty multi-line JSX self-closing tags.

<ChevronUpIcon /> and <ChevronDownIcon /> are split across two lines with no props, which is awkward formatting. Consider collapsing them onto a single line for readability (a formatter like Prettier should normalize this automatically).

✏️ Proposed tweak
-      <ChevronUpIcon
-      />
+      <ChevronUpIcon />
-      <ChevronDownIcon
-      />
+      <ChevronDownIcon />

Also applies to: 184-185

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

In `@src/components/ui/select.tsx` around lines 165 - 166, Collapse the empty
multi-line JSX self-closing tags for the icons into single-line form: replace
the multi-line instances of ChevronUpIcon and ChevronDownIcon in the Select
component with single-line self-closing tags (use <ChevronUpIcon /> and
<ChevronDownIcon />) and run the project's formatter (e.g., Prettier) to
normalize formatting.
src/components/ui/dropdown-menu.tsx (1)

138-138: Add missing inline-start/inline-end slide variants to DropdownMenuSubContent for consistency.

DropdownMenuContent includes data-[side=inline-end]:slide-in-from-left-2 and data-[side=inline-start]:slide-in-from-right-2 slide variants (line 44), but DropdownMenuSubContent (line 138) is missing these. Since Base UI's Positioner can resolve sides to logical inline-start and inline-end values—especially in RTL contexts or depending on positioning logic—the sub-content will lack animations in those scenarios. Mirror the same side selectors for consistency.

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

In `@src/components/ui/dropdown-menu.tsx` at line 138, DropdownMenuSubContent's
className is missing the logical side slide variants present in
DropdownMenuContent; update the className on DropdownMenuSubContent to also
include data-[side=inline-end]:slide-in-from-left-2 and
data-[side=inline-start]:slide-in-from-right-2 so the sub-menu gets the same
slide animations when Positioner resolves to inline-start/inline-end (look for
the DropdownMenuSubContent component and mirror the data-[side=inline-*]
selectors used in DropdownMenuContent).
src/components/ui/breadcrumb.tsx (1)

110-111: Consider formatting MoreHorizontalIcon consistently.

The ChevronRightIcon on line 89 was reformatted to a single line, but MoreHorizontalIcon here remains split across two lines. For consistency, consider formatting it similarly.

✨ Formatting suggestion
-      <MoreHorizontalIcon
-      />
+      <MoreHorizontalIcon />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/breadcrumb.tsx` around lines 110 - 111, The
MoreHorizontalIcon JSX should be formatted consistently with ChevronRightIcon;
locate the MoreHorizontalIcon usage in breadcrumb.tsx (the instance currently
split over two lines) and collapse it to a single self-closing tag on one line
(e.g., <MoreHorizontalIcon />) matching the style used for ChevronRightIcon to
keep component formatting uniform.
src/components/ui/calendar.tsx (2)

189-189: Tighten locale typing to match DayPicker's own type.

Calendar already types locale via React.ComponentProps<typeof DayPicker> (i.e., Locale | undefined). Using Partial<Locale> here is looser than what is actually passed in and inconsistent with the upstream prop. Consider aligning to Locale for type accuracy.

🧹 Proposed fix
-}: React.ComponentProps<typeof DayButton> & { locale?: Partial<Locale> }) {
+}: React.ComponentProps<typeof DayButton> & { locale?: Locale }) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/calendar.tsx` at line 189, The Calendar component's prop
typing currently uses locale?: Partial<Locale>, which is looser than DayPicker's
locale type; update the Calendar props to use the exact DayPicker locale type
(e.g., replace locale?: Partial<Locale> with locale?:
React.ComponentProps<typeof DayPicker>['locale'] or Locale | undefined) so the
locale prop matches DayPicker's typing for the Calendar function signature.

164-166: Memoize the DayButton component to prevent per-render remounts.

Defining DayButton as an inline arrow function creates a new component reference on every render of Calendar. In react-day-picker v9, the components object uses reference equality to determine if a component has changed; a new reference causes React to unmount and remount all day buttons on each parent render. This triggers unnecessary DOM churn and causes the useEffect in CalendarDayButton (which focuses the ref when modifiers.focused changes) to re-fire on remount, potentially disrupting focus management.

Memoize the component with React.useCallback keyed on [locale]:

Suggested fix
const defaultClassNames = getDefaultClassNames()

+const DayButtonComponent = React.useCallback(
+  (props: React.ComponentProps<typeof DayButton>) => (
+    <CalendarDayButton locale={locale} {...props} />
+  ),
+  [locale]
+)
+
return (
  <DayPicker
    ...
    components={{
      ...
-     DayButton: ({ ...props }) => (
-       <CalendarDayButton locale={locale} {...props} />
-     ),
+     DayButton: DayButtonComponent,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/calendar.tsx` around lines 164 - 166, The inline DayButton
arrow creates a new component reference each render causing remounts; change the
inline definition to a memoized callback (e.g. const DayButton =
React.useCallback(({ ...props }) => <CalendarDayButton locale={locale}
{...props} />, [locale])) and pass that DayButton variable into the components
prop of Calendar instead of the inline function so reference equality is stable
and CalendarDayButton won't remount on every parent render.
src/components/ui/radio-group.tsx (1)

28-33: Indicator centering is redundant.

RadioPrimitive.Indicator already centers children via flex items-center justify-center, but the inner <span> then uses absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 (positioning against the relative Root). Either approach works alone — combining them is just dead styling. Pick one for clarity.

♻️ Proposed simplification
       <RadioPrimitive.Indicator
         data-slot="radio-group-indicator"
         className="flex size-4 items-center justify-center"
       >
-        <span className="absolute top-1/2 left-1/2 size-2 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary-foreground" />
+        <span className="size-2 rounded-full bg-primary-foreground" />
       </RadioPrimitive.Indicator>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/radio-group.tsx` around lines 28 - 33, The Indicator
centering is redundant: decide between using the parent
RadioPrimitive.Indicator's flex centering (className="flex items-center
justify-center") or the child's absolute centering (the inner <span> with
absolute/top-1/2/left-1/2 and translate classes) and remove the other approach
for clarity; for example, keep RadioPrimitive.Indicator's flex centering and
strip the absolute/top/left/-translate classes from the inner span (leave
size/rounded/bg classes), or alternatively remove the flex classes on
RadioPrimitive.Indicator and keep the span's absolute centering—apply the change
in the RadioPrimitive.Indicator block and the inner span element.
src/components/ui/toggle.tsx (1)

39-39: Inconsistent className composition pattern.

Other components updated in this PR (e.g., src/components/ui/badge.tsx line 40, plus card.tsx, alert-dialog.tsx, button.tsx) moved from passing className into the cva call to appending it after via cn(variants({ variant }), className). This file still uses the older toggleVariants({ variant, size, className }) form, which makes the PR's normalization inconsistent.

♻️ Proposed alignment
-      className={cn(toggleVariants({ variant, size, className }))}
+      className={cn(toggleVariants({ variant, size }), className)}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/toggle.tsx` at line 39, The toggle component currently
passes className into toggleVariants causing inconsistent composition; update
the JSX so cn composes the variant output and className separately by calling
toggleVariants with only variant and size (e.g., toggleVariants({ variant, size
})) and then passing className as a second argument to cn
(cn(toggleVariants(...), className)), matching the pattern used in
badge/card/alert-dialog/button components.
src/components/ui/resizable.tsx (2)

23-23: Tiny nit: empty rest destructure can be inlined.

({ ...props }: ResizablePrimitive.PanelProps) is equivalent to (props: ResizablePrimitive.PanelProps). Optional cleanup, not blocking.

♻️ Optional simplification
-function ResizablePanel({ ...props }: ResizablePrimitive.PanelProps) {
+function ResizablePanel(props: ResizablePrimitive.PanelProps) {
   return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/resizable.tsx` at line 23, The ResizablePanel component
currently uses an unnecessary rest destructure in its parameter list; change the
function signature from "function ResizablePanel({ ...props }:
ResizablePrimitive.PanelProps)" to simply "function ResizablePanel(props:
ResizablePrimitive.PanelProps)" and update any internal references accordingly
(keep using props as before) to remove the redundant spread.

1-50: LGTM — migration to react-resizable-panels v4 is correct.

The rename to Group/Separator, prop type updates (GroupProps/SeparatorProps/PanelProps), and switch to aria-[orientation=…]: selectors all align with v4 API (v4 renames PanelResizeHandleSeparator and directionorientation to match ARIA semantics).

Minor refinement: Line 23's ResizablePanel destructures only ...props, which is redundant — directly type the parameter instead:

function ResizablePanel(props: ResizablePrimitive.PanelProps) {
  return <ResizablePrimitive.Panel data-slot="resizable-panel" {...props} />
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/resizable.tsx` around lines 1 - 50, The ResizablePanel
function unnecessarily destructures only ...props in its parameter; change its
signature from function ResizablePanel({ ...props }:
ResizablePrimitive.PanelProps) to function ResizablePanel(props:
ResizablePrimitive.PanelProps) and keep the body return
<ResizablePrimitive.Panel data-slot="resizable-panel" {...props} /> so the
parameter is directly typed as PanelProps (update the ResizablePanel function
declaration accordingly).
src/components/ui/chart.tsx (1)

200-210: Using array index as React key for tooltip/legend rows.

Both list maps now key children by index. For static, never-reordered rows this is fine, but Recharts can re-order the payload array (e.g., when series are toggled via the legend). Prefer a stable identifier like item.dataKey ?? item.name ?? index so React can preserve DOM/animation state across updates.

Also applies to: 299-308

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

In `@src/components/ui/chart.tsx` around lines 200 - 210, Replace the unstable
index-based React keys used when mapping payload rows with a stable identifier
so DOM/animation state persists when Recharts reorders series; specifically, in
the map that computes const key = `${nameKey ?? item.name ?? item.dataKey ??
"value"}` (and the similar map around lines 299-308), use a deterministic key
such as item.dataKey ?? item.name ?? nameKey ?? String(index) (or a
concatenation that includes dataKey/name/nameKey) instead of just index, and
pass that as the element key where getPayloadConfigFromPayload, payload, and
item are used so rows remain stable across updates.
src/components/ui/combobox.tsx (2)

39-50: ComboboxClear is defined but never exported.

ComboboxClear is implemented (Lines 39–50) and used internally by ComboboxInput, but it is not present in the public export { ... } block. Consumers can configure <ComboboxInput showClear /> but cannot place <ComboboxClear /> themselves (e.g., inside a custom input layout). Either remove it (if internal-only) or add it to the export list to match the other primitives.

♻️ Proposed export addition
 export {
   Combobox,
+  ComboboxClear,
   ComboboxInput,
   ComboboxContent,

Also applies to: 280-297

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

In `@src/components/ui/combobox.tsx` around lines 39 - 50, ComboboxClear is
implemented but not exported, preventing consumers from using it directly (while
ComboboxInput supports showClear); either remove the component if it should
remain internal or add ComboboxClear to the module's public export block
alongside the other combobox primitives so it is accessible to consumers; update
the export list to include ComboboxClear (and mirror the same change for the
similar unexported component referenced around lines 280–297) and ensure
imports/exports remain consistent with ComboboxInput and other Combobox*
symbols.

215-219: Remove redundant type intersection on ComboboxChips props.

React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> is unnecessary here. Every other wrapper in this file uses just ComboboxPrimitive.X.Props, and the underlying @base-ui/react component doesn't actually support ref forwarding (it uses ComponentPropsWithoutRef). Removing the intersection improves consistency and avoids type confusion.

♻️ Proposed simplification
 function ComboboxChips({
   className,
   ...props
-}: React.ComponentPropsWithRef<typeof ComboboxPrimitive.Chips> &
-  ComboboxPrimitive.Chips.Props) {
+}: ComboboxPrimitive.Chips.Props) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/combobox.tsx` around lines 215 - 219, The ComboboxChips
props type unnecessarily intersects React.ComponentPropsWithRef<typeof
ComboboxPrimitive.Chips> with ComboboxPrimitive.Chips.Props; remove the
redundant intersection and use only ComboboxPrimitive.Chips.Props for the
function signature of ComboboxChips (keeping the same parameters like className
and ...props) so it matches other wrappers and avoids implying ref forwarding
for ComboboxPrimitive.Chips.
src/app/index.md/route.ts (1)

9-17: Consider reusing textResponse for consistency.

The sibling markdown route (.well-known/agent-skills/shadcn-hooks.md/route.ts) uses textResponse(...). Extending textResponse to accept extra headers (or adding a thin markdownResponse helper) would centralize charset, Content-Type, Link, and the token header so all markdown endpoints behave identically.

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

In `@src/app/index.md/route.ts` around lines 9 - 17, Refactor the GET handler to
reuse the existing textResponse helper by either extending textResponse to
accept extra headers or by adding a thin markdownResponse wrapper; specifically,
change the GET function that currently returns new Response(homeMarkdown, {
headers: { 'Content-Type': 'text/markdown; charset=utf-8', 'Link':
linkHeaderValue, 'x-markdown-tokens': markdownTokenCount(homeMarkdown) } }) to
call textResponse(homeMarkdown, { headers: { 'Link': linkHeaderValue,
'x-markdown-tokens': markdownTokenCount(homeMarkdown) } }) after updating
textResponse to set Content-Type with charset by default (or create
markdownResponse(homeMarkdown, { link: linkHeaderValue, tokens: ... }) which
delegates to textResponse), so GET, textResponse/markdownResponse, homeMarkdown,
markdownTokenCount, and linkHeaderValue are used consistently.
src/components/webmcp-provider.tsx (1)

81-92: Resource list duplicates the agent-discovery source of truth.

linkHeaderValue, mcpServerCard(), and agentSkillsIndex() in src/lib/agent-discovery.ts already enumerate these endpoints. Hardcoding them again here will drift (already missing several .well-known/* endpoints introduced in this PR). Consider exposing a small helper from agent-discovery.ts (e.g. agentResources()) and consuming it here so both server discovery routes and the WebMCP tool stay in sync.

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

In `@src/components/webmcp-provider.tsx` around lines 81 - 92, The WebMCP
provider's execute() hardcodes the same resource list already defined in
agent-discovery.ts, causing drift; add a small exported helper (e.g.
agentResources()) to src/lib/agent-discovery.ts that returns the canonical
resources used by linkHeaderValue, mcpServerCard(), and agentSkillsIndex(), then
import and call that helper from the execute() implementation in
src/components/webmcp-provider.tsx (replace the inline resources array with the
returned agentResources() value) so both server discovery routes and the WebMCP
tool stay in sync.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 92fb299d-e4b3-4fbb-8aed-4f89847092d3

📥 Commits

Reviewing files that changed from the base of the PR and between 4324b9f and b385cd2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (71)
  • package.json
  • packages/shadcn-hooks/package.json
  • public/_headers
  • src/app/(home)/page.tsx
  • src/app/.well-known/agent-skills/index.json/route.ts
  • src/app/.well-known/agent-skills/shadcn-hooks.md/route.ts
  • src/app/.well-known/api-catalog/route.ts
  • src/app/.well-known/mcp/server-card.json/route.ts
  • src/app/.well-known/oauth-authorization-server/route.ts
  • src/app/.well-known/oauth-protected-resource/route.ts
  • src/app/.well-known/openid-configuration/route.ts
  • src/app/health/route.ts
  • src/app/index.md/route.ts
  • src/app/openapi.json/route.ts
  • src/app/robots.ts
  • src/app/robots.txt/route.ts
  • src/components/mdx/container-with-demo.client.tsx
  • src/components/ui/accordion.tsx
  • src/components/ui/alert-dialog.tsx
  • src/components/ui/alert.tsx
  • src/components/ui/avatar.tsx
  • src/components/ui/badge.tsx
  • src/components/ui/breadcrumb.tsx
  • src/components/ui/button-group.tsx
  • src/components/ui/button.tsx
  • src/components/ui/calendar.tsx
  • src/components/ui/card.tsx
  • src/components/ui/carousel.tsx
  • src/components/ui/chart.tsx
  • src/components/ui/checkbox.tsx
  • src/components/ui/combobox.tsx
  • src/components/ui/command.tsx
  • src/components/ui/context-menu.tsx
  • src/components/ui/dialog.tsx
  • src/components/ui/direction.tsx
  • src/components/ui/drawer.tsx
  • src/components/ui/dropdown-menu.tsx
  • src/components/ui/empty.tsx
  • src/components/ui/field.tsx
  • src/components/ui/hover-card.tsx
  • src/components/ui/input-group.tsx
  • src/components/ui/input-otp.tsx
  • src/components/ui/input.tsx
  • src/components/ui/item.tsx
  • src/components/ui/kbd.tsx
  • src/components/ui/label.tsx
  • src/components/ui/menubar.tsx
  • src/components/ui/native-select.tsx
  • src/components/ui/navigation-menu.tsx
  • src/components/ui/pagination.tsx
  • src/components/ui/popover.tsx
  • src/components/ui/progress.tsx
  • src/components/ui/radio-group.tsx
  • src/components/ui/resizable.tsx
  • src/components/ui/scroll-area.tsx
  • src/components/ui/select.tsx
  • src/components/ui/separator.tsx
  • src/components/ui/sheet.tsx
  • src/components/ui/sidebar.tsx
  • src/components/ui/skeleton.tsx
  • src/components/ui/slider.tsx
  • src/components/ui/switch.tsx
  • src/components/ui/table.tsx
  • src/components/ui/tabs.tsx
  • src/components/ui/textarea.tsx
  • src/components/ui/toggle-group.tsx
  • src/components/ui/toggle.tsx
  • src/components/ui/tooltip.tsx
  • src/components/webmcp-provider.tsx
  • src/lib/agent-discovery.ts
  • src/registry/hooks/use-event-listener/demo/demo-01.tsx
💤 Files with no reviewable changes (1)
  • src/app/robots.ts

Comment thread package.json
"fumadocs-docgen": "^3.0.10",
"fumadocs-mdx": "^14.3.1",
"fumadocs-ui": "^16.8.4",
"glob": "^13.0.6",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Duplicate entries in dependencies and devDependencies.

glob (^13.0.6) and tailwindcss-animate (^1.0.7) are listed in both sections. Pick one section per package — keeping them in both is redundant and can produce ambiguous resolutions during pnpm install or publish flows.

♻️ Proposed cleanup
   "devDependencies": {
     ...
-    "glob": "^13.0.6",
     ...
-    "tailwindcss-animate": "^1.0.7",

Also applies to: 62-62, 90-90, 105-105

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

In `@package.json` at line 46, package.json currently lists duplicate entries for
the same packages in both "dependencies" and "devDependencies" (e.g., "glob" and
"tailwindcss-animate"); remove the redundant copy so each package appears in
only one section: decide whether each package is needed at runtime (keep in
"dependencies") or only for development/build (keep in "devDependencies"),
delete the other entry, and run your package manager's install/lock update to
verify resolution; specifically search for "glob" and "tailwindcss-animate" (and
the other duplicates referenced in the review) and consolidate them into the
correct single dependency section.

Comment thread public/_headers
Comment on lines +1 to +7
/
Link: </.well-known/api-catalog>; rel="api-catalog", </docs>; rel="service-doc", </index.md>; rel="alternate"; type="text/markdown", </.well-known/agent-skills/index.json>; rel="service-desc"; type="application/json"
Vary: Accept

/index.html
Link: </.well-known/api-catalog>; rel="api-catalog", </docs>; rel="service-doc", </index.md>; rel="alternate"; type="text/markdown", </.well-known/agent-skills/index.json>; rel="service-desc"; type="application/json"
Vary: Accept
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Link header duplicated and out of sync with new discovery endpoints.

The same Link string is repeated for / and /index.html, and it duplicates linkHeaderValue in src/lib/agent-discovery.ts. With this PR adding oauth-authorization-server, openid-configuration, oauth-protected-resource, mcp/server-card.json, and openapi.json, those are not advertised here, so anything relying on the Link header will miss them.

Two options:

  • Generate public/_headers from linkHeaderValue (or vice versa) in a build step so they cannot drift.
  • At minimum, extend linkHeaderValue (and this file) with the new endpoints, e.g. </.well-known/mcp/server-card.json>; rel="service-desc"; type="application/json" and </openapi.json>; rel="service-desc"; type="application/openapi+json".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@public/_headers` around lines 1 - 7, The Link header in public/_headers is
duplicated and out of sync with the canonical linkHeaderValue in
src/lib/agent-discovery.ts; update the source of truth so discovery endpoints
aren’t missed: either (preferred) make public/_headers generated from
linkHeaderValue at build time, or (simpler) extend linkHeaderValue in
src/lib/agent-discovery.ts and mirror the same entries into public/_headers to
include the new endpoints (</.well-known/mcp/server-card.json>;
rel="service-desc"; type="application/json", </openapi.json>;
rel="service-desc"; type="application/openapi+json",
</.well-known/oauth-authorization-server>; rel="authorization-server" or
appropriate rel, </.well-known/openid-configuration>;
rel="openid-configuration", </.well-known/oauth-protected-resource>;
rel="protected-resource") so both places stay in sync and advertise the new
endpoints.

Comment on lines +1 to +7
import { jsonResponse, oauthAuthorizationServer } from '~/lib/agent-discovery'

export const dynamic = 'force-static'

export function GET() {
return jsonResponse(oauthAuthorizationServer())
}
Copy link
Copy Markdown
Contributor

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:

OpenID Connect Discovery 1.0 required fields openid-configuration

💡 Result:

The required fields (marked as REQUIRED) in the OpenID Provider Configuration document (/.well-known/openid-configuration) per OpenID Connect Discovery 1.0 incorporating errata set 2 are: - issuer: URL using the https scheme with no query or fragment components that the OP asserts as its Issuer Identifier. - authorization_endpoint: URL of the OP's OAuth 2.0 Authorization Endpoint. - response_types_supported: JSON array containing a list of the OAuth 2.0 Response Type values that this OP supports. Dynamic OpenID Providers MUST support the code, id_token, and the id_token token Response Type values. - subject_types_supported: JSON array containing a list of the Subject Identifier types that this OP supports. Valid types include pairwise and public. - id_token_signing_alg_values_supported: JSON array containing a list of the JWS signing algorithms (alg values) supported by the OP for the ID Token to encode the Claims in a JWT. The algorithm RS256 MUST be included. Note: token_endpoint is REQUIRED unless only the Implicit Flow is used.

Citations:


🏁 Script executed:

# Find the agent-discovery file and examine oauthAuthorizationServer function
fd -t f agent-discovery src/lib/

Repository: Debbl/shadcn-hooks

Length of output: 89


🏁 Script executed:

# Search for oauthAuthorizationServer definition
rg "oauthAuthorizationServer" -A 20

Repository: Debbl/shadcn-hooks

Length of output: 1140


🏁 Script executed:

# Check if openidConfiguration function exists
rg "openidConfiguration"

Repository: Debbl/shadcn-hooks

Length of output: 44


🏁 Script executed:

# Search for all .well-known routes
fd -t f route.ts | xargs grep -l "well-known" 2>/dev/null

Repository: Debbl/shadcn-hooks

Length of output: 46


🏁 Script executed:

# Also check for any openid or oidc related files/routes
fd -t f -e ts | xargs grep -l "openid-configuration" 2>/dev/null

Repository: Debbl/shadcn-hooks

Length of output: 46


🏁 Script executed:

# Simpler check for the route file mentioned in the review
cat -n src/app/.well-known/openid-configuration/route.ts

Repository: Debbl/shadcn-hooks

Length of output: 306


Wrong payload for /.well-known/openid-configuration.

This endpoint serves the result of oauthAuthorizationServer(), which only returns issuer, service_documentation, and empty *_supported arrays. OpenID Connect Discovery 1.0 mandates additional fields (at minimum authorization_endpoint, jwks_uri, subject_types_supported, and id_token_signing_alg_values_supported). Strict OIDC clients will reject this document.

Either introduce a dedicated openidConfiguration() generator in src/lib/agent-discovery.ts that returns the proper OIDC metadata, or drop this route until a real OIDC provider is wired up — exposing /.well-known/openid-configuration advertises an OP that doesn't actually exist here, which can confuse agents/crawlers.

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

In `@src/app/.well-known/openid-configuration/route.ts` around lines 1 - 7, The
route GET currently returns oauthAuthorizationServer() which lacks required
OpenID Connect Discovery fields; add a new openidConfiguration() in
src/lib/agent-discovery.ts that builds a full OIDC metadata document (use issuer
from oauthAuthorizationServer(), include authorization_endpoint, token_endpoint
if applicable, jwks_uri, subject_types_supported,
id_token_signing_alg_values_supported, response_types_supported,
scopes_supported, grant_types_supported, and any relevant endpoints) and change
the route's GET to return jsonResponse(openidConfiguration()); alternatively
remove/disable this route until a real OP is available to avoid advertising a
non-compliant provider.

orientation={orientation}
className={cn(
"bg-input relative self-stretch data-[orientation=horizontal]:mx-px data-[orientation=horizontal]:w-auto data-[orientation=vertical]:my-px data-[orientation=vertical]:h-auto",
"relative self-stretch bg-input data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect what data attributes the wrapper Separator (and its underlying primitive) emit.
fd -t f 'separator.tsx' --exec sh -c 'echo "== {} =="; cat {}'

Repository: Debbl/shadcn-hooks

Length of output: 647


🏁 Script executed:

# Check the button-group.tsx usage context around line 74
fd -t f 'button-group.tsx' --exec sh -c 'echo "== {} =="; head -80 {} | tail -20'

Repository: Debbl/shadcn-hooks

Length of output: 547


🌐 Web query:

Base UI React Separator data-horizontal data-vertical attributes

💡 Result:

In Base UI React Separator component, there is no data-horizontal or data-vertical attributes. The component renders a div element with role="separator" and a data-orientation attribute that is set to either "horizontal" or "vertical" based on the orientation prop (default: "horizontal"). To style differently for orientations in CSS: [role="separator"][data-orientation="horizontal"] { /* styles / } [role="separator"][data-orientation="vertical"] { / styles */ } Note: Some shadcn/ui implementations or Tailwind configs incorrectly reference data-horizontal/data-vertical selectors, but official Base UI uses data-orientation exclusively, as confirmed in docs and type definitions.

Citations:


data-horizontal: / data-vertical: selectors won't match the Separator DOM.

Base UI's SeparatorPrimitive only emits data-orientation="horizontal" or data-orientation="vertical", not boolean data-horizontal / data-vertical attributes. The margin/sizing rules here (mx-px w-auto / my-px h-auto) will silently fail to apply, leaving only the base bg-input styling.

Use data-[orientation=horizontal]: and data-[orientation=vertical]: instead:

Suggested fix
-        "relative self-stretch bg-input data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto",
+        "relative self-stretch bg-input data-[orientation=horizontal]:mx-px data-[orientation=horizontal]:w-auto data-[orientation=vertical]:my-px data-[orientation=vertical]:h-auto",
📝 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
"relative self-stretch bg-input data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px data-vertical:h-auto",
"relative self-stretch bg-input data-[orientation=horizontal]:mx-px data-[orientation=horizontal]:w-auto data-[orientation=vertical]:my-px data-[orientation=vertical]:h-auto",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/button-group.tsx` at line 74, Update the responsive
attribute selectors used on the separator element in the ButtonGroup styles:
replace the incorrect `data-horizontal:` and `data-vertical:` tokens with the
correct attribute selectors `data-[orientation=horizontal]:` and
`data-[orientation=vertical]:` so the Separator (from SeparatorPrimitive)
matches `data-orientation="horizontal"` / `data-orientation="vertical"` and the
`mx-px w-auto` / `my-px h-auto` sizing rules apply; locate the class string in
the ButtonGroup component (the string containing "relative self-stretch bg-input
data-horizontal:mx-px data-horizontal:w-auto data-vertical:my-px
data-vertical:h-auto") and make the substitution there.

data-slot="field-description"
className={cn(
"text-muted-foreground text-left text-sm [[data-variant=legend]+&]:-mt-1.5 leading-normal font-normal group-has-data-[orientation=horizontal]/field:text-balance",
"text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

group-has-data-horizontal/field:text-balance will never match.

This expands to .group\/field:has([data-horizontal]) & (presence-based), but neither Field nor any of its descendants render a data-horizontal attribute — Field only sets data-orientation={orientation} (line 81). The text-balance rule for horizontal orientation will therefore be dead CSS.

Also note: even the pre-refactor group-has-data-[orientation=horizontal]/field: was likely wrong, because :has() only matches descendants and data-orientation lives on the group/field element itself. The correct variant for "the field group is horizontal" is group-data-[orientation=horizontal]/field: (no has-).

🛠️ Suggested fix
-        "text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
+        "text-left text-sm leading-normal font-normal text-muted-foreground group-data-[orientation=horizontal]/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
📝 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
"text-left text-sm leading-normal font-normal text-muted-foreground group-has-data-horizontal/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
"text-left text-sm leading-normal font-normal text-muted-foreground group-data-[orientation=horizontal]/field:text-balance [[data-variant=legend]+&]:-mt-1.5",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/field.tsx` at line 136, The generated Tailwind variant
"group-has-data-horizontal/field:text-balance" will never match because Field
sets data-orientation (data-orientation={orientation}) on the group element, not
a descendant; change the variant to use the group data attribute form
"group-data-[orientation=horizontal]/field:text-balance" (or
"group-data-[orientation=vertical]" as appropriate) in the class list used by
the Field component so the text-balance rule applies when the field group has
orientation="horizontal"; update the string containing
"group-has-data-horizontal/field:text-balance" inside the Field component's
className definition accordingly.

style={{ "--gap": spacing } as React.CSSProperties}
className={cn(
"rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch",
"group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs data-vertical:flex-col data-vertical:items-stretch",
Copy link
Copy Markdown
Contributor

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:

Does @base-ui/react ToggleGroup emit a data-vertical or data-horizontal boolean attribute on its root element, or only data-orientation?

💡 Result:

No, @base-ui/react ToggleGroup emits only the data-orientation boolean attribute on its root element, not data-vertical or data-horizontal.

Citations:


Fix Tailwind selectors to use data-[orientation=vertical] instead of data-vertical:.

In Tailwind v4, data-vertical: matches a boolean [data-vertical] attribute, but @base-ui/react ToggleGroup only emits data-orientation={orientation}. The current data-vertical:flex-col data-vertical:items-stretch rules will never apply when orientation="vertical".

Replace with value-based selectors:

Suggested fix
-        "group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs data-vertical:flex-col data-vertical:items-stretch",
+        "group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch",
📝 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
"group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs data-vertical:flex-col data-vertical:items-stretch",
"group/toggle-group flex w-fit flex-row items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=0]:data-[variant=outline]:shadow-xs data-[orientation=vertical]:flex-col data-[orientation=vertical]:items-stretch",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/ui/toggle-group.tsx` at line 45, The Tailwind selectors in the
ToggleGroup class string use the boolean-style `data-vertical:` which never
matches the component's `data-orientation={orientation}`; update the class
definition in src/components/ui/toggle-group.tsx (the string containing
"group/toggle-group ... data-vertical:flex-col data-vertical:items-stretch") to
use value-based selectors `data-[orientation=vertical]:flex-col` and
`data-[orientation=vertical]:items-stretch` (or equivalent
`data-[orientation=vertical]:...`) so the vertical orientation styles apply when
orientation="vertical".

data-spacing={context.spacing}
className={cn(
"data-[state=on]:bg-muted group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 group-data-[spacing=0]/toggle-group:shadow-none group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-md group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-md group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-md group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-md shrink-0 focus:z-10 focus-visible:z-10 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
"shrink-0 group-data-[spacing=0]/toggle-group:rounded-none group-data-[spacing=0]/toggle-group:px-2 group-data-[spacing=0]/toggle-group:shadow-none focus:z-10 focus-visible:z-10 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-end]:pr-1.5 group-data-[spacing=0]/toggle-group:has-data-[icon=inline-start]:pl-1.5 group-data-horizontal/toggle-group:data-[spacing=0]:first:rounded-l-md group-data-vertical/toggle-group:data-[spacing=0]:first:rounded-t-md group-data-horizontal/toggle-group:data-[spacing=0]:last:rounded-r-md group-data-vertical/toggle-group:data-[spacing=0]:last:rounded-b-md data-[state=on]:bg-muted group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:border-l-0 group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:border-t-0 group-data-horizontal/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-l group-data-vertical/toggle-group:data-[spacing=0]:data-[variant=outline]:first:border-t",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Downstream impact of the data-horizontal/data-vertical selectors.

Same root cause as the comment on line 45: the group-data-horizontal/toggle-group:* and group-data-vertical/toggle-group:* rules look up an ancestor with [data-horizontal] / [data-vertical], which the parent does not currently set. As a result, the per-orientation first:rounded-*, last:rounded-*, and data-[variant=outline]:border-* rules will likely no-op in production. If the verification on line 45 confirms this, please apply the equivalent value-based selectors here too.

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

In `@src/components/ui/toggle-group.tsx` at line 75, The orientation-specific
selectors in the long class string (the ones starting with
group-data-horizontal/toggle-group: and group-data-vertical/toggle-group:) rely
on an ancestor setting [data-horizontal]/[data-vertical] which we don't set, so
those rules no-op; update those selectors to use the value-based form used
elsewhere (e.g. group-data-[orientation=horizontal]/toggle-group:... and
group-data-[orientation=vertical]/toggle-group:...) for every occurrence that
controls per-orientation styling (the first:rounded-*, last:rounded-*, and
data-[variant=outline]:border-* rules) so the orientation-specific rounding and
border rules apply in production.

Comment on lines +107 to +116
execute(input) {
const path = readStringField(input, 'path')

if (!path?.startsWith('/docs')) {
return { error: 'path must begin with /docs' }
}

window.location.assign(path)
return { opened: path }
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

open_docs path validation is too permissive.

path.startsWith('/docs') accepts inputs like /docs..%2f..%2fadmin, /docsanything, or /docs/\\evil.com (protocol-relative once the browser normalizes backslashes). Since this is invoked by an LLM-supplied argument and triggers a top-level navigation, it should be tightened.

🛡️ Suggested hardening
     execute(input) {
       const path = readStringField(input, 'path')

-      if (!path?.startsWith('/docs')) {
-        return { error: 'path must begin with /docs' }
-      }
-
-      window.location.assign(path)
-      return { opened: path }
+      if (
+        !path ||
+        !/^\/docs(?:\/[\w\-./]*)?$/.test(path) ||
+        path.includes('..') ||
+        path.startsWith('//')
+      ) {
+        return { error: 'path must be an absolute /docs path' }
+      }
+
+      window.location.assign(path)
+      return { opened: path }
     },
📝 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
execute(input) {
const path = readStringField(input, 'path')
if (!path?.startsWith('/docs')) {
return { error: 'path must begin with /docs' }
}
window.location.assign(path)
return { opened: path }
},
execute(input) {
const path = readStringField(input, 'path')
if (
!path ||
!/^\/docs(?:\/[\w\-./]*)?$/.test(path) ||
path.includes('..') ||
path.startsWith('//')
) {
return { error: 'path must be an absolute /docs path' }
}
window.location.assign(path)
return { opened: path }
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/webmcp-provider.tsx` around lines 107 - 116, The execute
method (open_docs handler) currently allows dangerous paths via
path.startsWith('/docs'); tighten validation by: 1) require the normalized
pathname to be within the /docs prefix (use new URL(path,
window.location.origin).pathname and ensure it equals '/docs' or
startsWith('/docs/')); 2) reject any path containing backslashes or
percent-encoded traversal (reject if path includes '\\' or /%2f|%5c|%2e%2e/i or
if decodeURIComponent(path) contains '..' or '\\'); 3) after validation perform
a safe navigation using window.location.assign(new URL(path,
window.location.origin).toString()) and return opened path; update references:
execute, readStringField, and window.location.assign accordingly.

Comment on lines +120 to +126
export function WebMcpProvider() {
useEffect(() => {
void navigator.modelContext?.provideContext({ tools })
}, [])

return null
}
Copy link
Copy Markdown
Contributor

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:

WebMCP navigator.modelContext provideContext idempotent unregister

💡 Result:

navigator.modelContext is the core API in WebMCP (Web Model Context Protocol), a W3C draft spec from the Web Machine Learning Community Group that enables websites to register JavaScript tools callable by AI agents. Available only in secure contexts (HTTPS). Key methods: - registerTool(tool): Registers a single tool. Throws InvalidStateError on duplicate name, empty name/description, or invalid inputSchema. - unregisterTool(name): Removes tool by name. No-op if not registered. Not natively idempotent: registerTool fails on duplicates to prevent accidental overwrites. For idempotent registration (e.g., HMR/SPA re-init), check existing tools first via navigator.modelContextTesting.listTools (testing API), then register if absent: const tools = navigator.modelContextTesting?.listTools || []; if (!tools.some(t => t.name === tool.name)) { navigator.modelContext.registerTool(tool); } Or safe unregister-then-register pattern: try { navigator.modelContext.unregisterTool(tool.name); } catch {} navigator.modelContext.registerTool(tool); provideContext: Early proposal method to atomically replace all tools (clears existing first). Removed from current spec due to security concerns (allows overwriting others' tools). See GitHub issue #101, resolved by dropping in PR #132. Official spec: https://webmachinelearning.github.io/webmcp/ (Draft Community Group Report 23 Apr 2026). Core IDL: [Exposed=Window, SecureContext] interface ModelContext { undefined registerTool(ModelContextTool tool, optional ModelContextRegisterToolOptions options = {}); }; Chrome 146+ Canary supports natively (behind flag). Polyfills like @mcp-b/webmcp-polyfill available.

Citations:


🏁 Script executed:

# First, let's look at the actual file around the specified lines
cat -n src/components/webmcp-provider.tsx | head -n 130 | tail -n 20

Repository: Debbl/shadcn-hooks

Length of output: 598


🏁 Script executed:

# Search for all uses of provideContext or registerTool in the codebase
rg "provideContext|registerTool" --type ts --type tsx -n

Repository: Debbl/shadcn-hooks

Length of output: 90


🏁 Script executed:

# Check for any imports or type definitions related to modelContext
rg "modelContext|WebMCP|navigator\." --type ts --type tsx -B 2 -A 2 src/components/webmcp-provider.tsx

Repository: Debbl/shadcn-hooks

Length of output: 90


🏁 Script executed:

# Search for any imports of WebMCP or tool definitions
rg "tools" src/components/webmcp-provider.tsx -B 5 -A 2

Repository: Debbl/shadcn-hooks

Length of output: 516


🏁 Script executed:

# Look for imports at the top of the file
head -n 30 src/components/webmcp-provider.tsx

Repository: Debbl/shadcn-hooks

Length of output: 632


🏁 Script executed:

# Search for any WebMCP imports or setup in the project
rg "webmcp|modelContext" -i

Repository: Debbl/shadcn-hooks

Length of output: 1963


Migrate from deprecated provideContext to the standard WebMCP API.

The provideContext method used here was removed from the official WebMCP specification (as of April 2026, per PR #132) due to security concerns. The current standard API provides only registerTool, which throws InvalidStateError on duplicate names—not idempotent by design.

Additionally, with the empty dependency array and React 19 Strict Mode, the effect fires twice during development. Migrate to the official registerTool API, checking for existing tools first to avoid duplicates:

const existing = navigator.modelContextTesting?.listTools || [];
tools.forEach(tool => {
  if (!existing.some(t => t.name === tool.name)) {
    navigator.modelContext?.registerTool(tool);
  }
});

Or use the unregister-then-register pattern for idempotency. Define the tools array in the dependency array and add cleanup to prevent accumulation across re-mounts.

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

In `@src/components/webmcp-provider.tsx` around lines 120 - 126, The
WebMcpProvider currently calls the deprecated
navigator.modelContext.provideContext inside a useEffect; replace it with the
standard API by registering each tool via navigator.modelContext.registerTool
while avoiding duplicate registrations (use
navigator.modelContextTesting?.listTools or navigator.modelContext?.listTools to
check existing tool names before calling registerTool), include the tools array
in the useEffect dependency list, and add a cleanup/unregister step or
unregister-then-register pattern to ensure idempotency across re-mounts and
React 19 Strict Mode double-invocations; update references to provideContext to
use registerTool and listTools (and optionally unregister) inside the
WebMcpProvider function.

Comment on lines +4 to +9
export const linkHeaderValue = [
'</.well-known/api-catalog>; rel="api-catalog"',
'</docs>; rel="service-doc"',
'</index.md>; rel="alternate"; type="text/markdown"',
'</.well-known/agent-skills/index.json>; rel="service-desc"; type="application/json"',
].join(', ')
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm route handlers / static assets exist for each advertised path
fd -HI -t f 'route\.ts' src/app/.well-known src/app | sort
fd -HI -t f 'llms(\.|-full\.)txt' .
fd -HI -t d 'docs' src/app | head

Repository: Debbl/shadcn-hooks

Length of output: 1067


🏁 Script executed:

cat -n src/lib/agent-discovery.ts

Repository: Debbl/shadcn-hooks

Length of output: 6601


🏁 Script executed:

# Check for dynamic routes under /docs
fd -HI -t f 'route\.(ts|tsx)' src/app/docs

# Search for /r route
fd -HI -t f 'route\.(ts|tsx)' src/app/r

Repository: Debbl/shadcn-hooks

Length of output: 215


🏁 Script executed:

# Check docs directory structure
ls -la src/app/docs/ | head -20

# Search for /r anywhere
fd -HI 'r' src/app | grep -E '^\./.*r$|^./.*r/$' | head -20

# Check if there are page files
find src/app/docs -name 'page.*' -o -name 'layout.*' | sort

Repository: Debbl/shadcn-hooks

Length of output: 445


🏁 Script executed:

# Search for /r route anywhere
fd -HI -type d 'r' src/app | head -20

# Also search with rg for any route files that might handle /r
rg -t ts -t tsx 'export.*route' src/app | grep -i '\br\b'

Repository: Debbl/shadcn-hooks

Length of output: 261


🏁 Script executed:

# Search for directories named 'r' under src/app
find src/app -maxdepth 1 -type d -name 'r'

# Search for route files that might handle /r
rg -t ts '/r' src/app --max-count 5

Repository: Debbl/shadcn-hooks

Length of output: 141


🏁 Script executed:

# Final verification: search entire codebase for /r route
find src/app -name 'route.*' -type f | xargs rg 'export.*route|^/r$'

Repository: Debbl/shadcn-hooks

Length of output: 46


🏁 Script executed:

# Direct check for /r directory
ls -la src/app/r 2>&1 || echo "No /r directory found"

# List all route.ts files to confirm no /r route exists
find src/app -name 'route.ts' -o -name 'route.tsx' | sort

Repository: Debbl/shadcn-hooks

Length of output: 770


Fix broken /r route advertised in agentSkillMarkdown.

The /r path referenced in agentSkillMarkdown (line 39) has no corresponding route handler. All other advertised URLs in linkHeaderValue and homeMarkdown resolve correctly:

  • linkHeaderValue routes: ✓ /.well-known/api-catalog, /.well-known/agent-skills/index.json, /index.md, /docs
  • homeMarkdown routes: ✓ /llms.txt, /llms-full.txt, /docs/* (dynamic route)
  • agentSkillMarkdown broken route: ✗ /r (no route handler exists)

Either implement the missing /r route or remove the reference from agentSkillMarkdown.

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

In `@src/lib/agent-discovery.ts` around lines 4 - 9, The agentSkillMarkdown string
references a non-existent "/r" route causing a broken advertised URL; update
agentSkillMarkdown to either remove the "/r" reference or replace it with an
existing route (for example one of the routes already used in linkHeaderValue or
homeMarkdown such as "/docs" or "/index.md"), and ensure advertised endpoints in
agentSkillMarkdown match the actual handlers; locate the agentSkillMarkdown
constant in src/lib/agent-discovery.ts and edit its contents so no advertised
path lacks a corresponding route handler.

@Debbl Debbl merged commit 1ea3253 into main Apr 27, 2026
7 checks passed
@Debbl Debbl deleted the feat/isitagentready branch April 27, 2026 03:43
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