E541 [Docs][Site] + [Dashboard][UI] - Adds docs to Stack Companion by madster456 · Pull Request #869 · stack-auth/stack-auth · GitHub
[go: up one dir, main page]

Skip to content

Conversation

madster456
Copy link
Collaborator
@madster456 madster456 commented Aug 29, 2025

Important

Adds a unified documentation widget to the dashboard, enabling in-app viewing and switching of documentation types with platform-specific adaptations.

  • Behavior:
    • Adds UnifiedDocsWidget to stack-companion.tsx for viewing docs within the dashboard.
    • Supports platform switching, back navigation, sidebar toggle, loading/error states, and external opening.
    • Adapts content based on current page across dashboard, docs, and API.
  • Documentation:
    • Adds embedded routes/layouts in docs/src/app for api-embed, dashboard-embed, and docs-embed.
    • Implements EmbeddedLinkInterceptor and PlatformChangeNotifier for link handling and platform change notifications.
    • Updates generate-docs.js to include dashboard docs generation.
  • Configuration:
    • Adds NEXT_PUBLIC_STACK_DOCS_BASE_URL to .env.development and env.tsx.
    • Configures CORS headers in next.config.mjs for dashboard embedding.
  • Misc:
    • Updates styling in global.css to support embedded content.
    • Adds EmbeddedLink component for MDX link handling in mdx-components.tsx.

This description was created by Ellipsis for 5760b90. You can customize this summary. It will automatically update as commits are pushed.


Summary by CodeRabbit

  • New Features

    • Unified docs widget in the dashboard: embedded viewer with platform switching, back navigation, sidebar toggle, loading/error handling, switch prompt, and “open externally”. Adapts to current page across dashboard, docs, and API.
  • Documentation

    • Embedded docs mode: new embed routes/layouts, MDX embedded-mode links/components, embedded sidebar UI, link interception for embedded navigation, and platform-change notifications. Adds dashboard docs source and generation support.
  • Chores

    • CORS headers and dashboard env vars configured for embedding; gitignore updated for generated dashboard content.

Copy link
vercel bot commented Aug 29, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
stack-backend Ready Ready Preview Comment Oct 8, 2025 11:39am
stack-dashboard Error Error Oct 8, 2025 11:39am
stack-demo Ready Ready Preview Comment Oct 8, 2025 11:39am
stack-docs Error Error Oct 8, 2025 11:39am

Copy link
Contributor
coderabbitai bot commented Aug 29, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Replaces the dashboard's inline docs UI with a new UnifiedDocsWidget and adds embed-first support across the docs site: embed routes/layouts/pages, MDX embedded components, embedded link interception and rewriting, a sidebar toggle via postMessage, dashboard docs source/config/generation, CORS headers for embed routes, env var for docs base URL, and embed-focused CSS.

Changes

Cohort / File(s) Summary
Dashboard integration
apps/dashboard/src/components/stack-companion.tsx, apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx
Replaced inline docs UI with UnifiedDocsWidget. Added an iframe-based docs viewer supporting doc-type/platform selection, path→embed resolution, postMessage sidebar toggle, back navigation, loading/error handling, and open-external behavior.
Env & env resolution
apps/dashboard/.env, apps/dashboard/.env.development, apps/dashboard/src/lib/env.tsx
Added NEXT_PUBLIC_STACK_DOCS_BASE_URL to env files and integrated it into public env resolution (inline map and post-build sentinel).
Embed routes & layouts
docs/src/app/docs-embed/..., docs/src/app/api-embed/..., docs/src/app/dashboard-embed/...
Added embed-optimized Next.js layouts and catch-all embed pages that render MDX via embedded MDX components, redirect when missing, and provide iframe-friendly containers.
Docs sources & config
docs/lib/source.ts, docs/source.config.ts, docs/next.config.mjs, docs/.gitignore
Added dashboardSource and dashboard docs collection, appended CORS headers for /dashboard/:path* and /dashboard-embed/:path*, and ignored generated dashboard content under /content/dashboard/.
Docs generation script
docs/scripts/generate-docs.js
Added dashboard docs generation from templates-dashboard into content/dashboard (copies MDX and optional meta.json); note: diff shows duplicate generateDashboardDocs() declarations to review.
Embedded MDX & link handling
docs/src/mdx-components.tsx, docs/src/components/mdx/embedded-link.tsx, docs/src/components/embedded-link-interceptor.tsx
Added getEmbeddedMDXComponents, EmbeddedLink to map internal routes to -embed variants, and EmbeddedLinkInterceptor which rewrites internal links to embed routes, verifies targets via HEAD, and navigates client-side.
Embedded sidebar & helpers
docs/src/components/embedded-docs-with-sidebar.tsx, docs/src/components/layouts/docs.tsx
Added EmbeddedDocsWithSidebar (drawer + TOGGLE_SIDEBAR postMessage listener) and exported internal helpers (AccordionProvider, useAccordionState, findPlatformContent, PageTreeItem, renderSidebarContent).
Embed presentation styles
docs/src/app/global.css
Added .scrollbar-hide and .prose rules to improve embed scrollbar behavior, wrapping, code/table/image responsiveness, and long-URL/string breaking.
Embed helpers & notifications
docs/src/components/platform-change-notifier.tsx, docs/src/components/embedded-link-interceptor.tsx, docs/src/components/embedded-docs-with-sidebar.tsx
Added PlatformChangeNotifier (posts PLATFORM_CHANGE to parent), link interceptor, and sidebar visibility toggle handling in embedded sidebar component.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Dashboard as Dashboard (StackCompanion)
  participant Widget as UnifiedDocsWidget
  participant Iframe as Docs Embed (iframe)
  participant DocsApp as Docs App (Next.js)

  User->>Dashboard: open "Docs" tab
  Dashboard->>Widget: mount(isActive=true)
  Widget->>Widget: resolve path, docType, platform -> get embed URL
  Widget->>Iframe: set src = embed URL
  Iframe-->>Widget: onload
  Widget->>Widget: set iframeLoaded, enable controls
  Widget->>Iframe: postMessage("TOGGLE_SIDEBAR")
  User->>Widget: click Back
  Widget->>Iframe: iframe.contentWindow.history.back()
  alt history.back not available
    Widget->>Iframe: reload iframe src
  end
Loading
sequenceDiagram
  autonumber
  actor User
  participant Iframe as Embedded Page
  participant Interceptor as EmbeddedLinkInterceptor
  participant Router as Embedded Router

  User->>Iframe: click internal link
  Interceptor->>Interceptor: compute embeddedHref
  Interceptor->>Router: HEAD embeddedHref
  alt Exists
    Interceptor->>Router: client navigate(embeddedHref)
  else Not found
    Interceptor-->>User: prevent navigation (no-op)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

I hop through iframes with a curious grin,
Sidebar taps and postMessages — let the docs slide in.
Links I nudge to embed, platforms hum and play,
Next or Python, pick a pane — I guide the way.
A small rabbit, big docs — I compile and stay. 🐇📚

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title “[Docs][Site] + [Dashboard][UI] - Adds docs to Stack Companion” succinctly highlights the main feature of embedding documentation in the dashboard while using familiar scope tags, directly reflecting the core change implemented in the PR.
Description Check ✅ Passed The description begins with the required contributing guidelines comment and then clearly outlines each aspect of the changes—including widget behavior, embedded routes, link handling components, configuration updates, and styling—providing complete context that matches the detailed modifications in the PR.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch stack-companion-docs

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
Contributor
@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Greptile Summary

This PR implements the "Stack Companion" documentation system, which embeds contextual documentation directly within the Stack Auth dashboard interface. The changes introduce a comprehensive embedded documentation architecture with three main components:

  1. Documentation Templates and Content: New MDX documentation files are added in docs/templates-dashboard/ covering all major dashboard sections (overview, users, teams, permissions, settings, auth-providers, analytics). These templates provide contextual help content that explains dashboard features and workflows.

  2. Embedded Documentation Infrastructure: The docs application is extended with embedded routes (/docs-embed/, /api-embed/, /dashboard-embed/) that render documentation content optimized for iframe embedding. This includes specialized layouts without navigation elements, embedded link interceptors that rewrite internal links to maintain embedded context, and CORS configuration to allow cross-origin access from the dashboard.

  3. Dashboard Integration: The dashboard's Stack Companion sidebar is enhanced with sophisticated documentation widgets (UnifiedDocsWidget and DashboardDocsWidget) that automatically display relevant documentation based on the current dashboard page. These widgets support multiple documentation types, include navigation controls, and provide seamless switching between different documentation contexts.

The system uses fumadocs for content management, with a new dashboardSource loader that processes dashboard-specific MDX content. The embedded documentation system maintains proper styling with Tailwind CSS classes optimized for constrained iframe spaces, including overflow management and responsive design considerations. The architecture allows users to access comprehensive help without leaving their current workflow, significantly improving the developer experience by reducing context switching between the dashboard and external documentation.

Confidence score: 2/5

  • This PR introduces significant functionality but contains critical production-breaking issues with hardcoded localhost URLs
  • Score reflects major deployment concerns that will cause complete failure in non-development environments
  • Pay close attention to unified-docs-widget.tsx and dashboard-docs-widget.tsx for hardcoded localhost:8104 URLs that must be resolved before deployment

26 files reviewed, 8 comments

Edit Code Review Bot Settings | Greptile

Copy link
recurseml bot commented Aug 29, 2025

Review by RecurseML

🔍 Review performed on 6a3459e..dde5282

✨ No bugs found, your code is sparkling clean

✅ Files analyzed, no issues (5)

apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx
docs/src/components/embedded-link-interceptor.tsx
apps/dashboard/src/components/stack-companion/dashboard-docs-widget.tsx
docs/scripts/generate-docs.js
docs/src/components/mdx/embedded-link.tsx

⏭️ Files skipped (low suspicion) (21)

apps/dashboard/src/components/stack-companion.tsx
docs/.gitignore
docs/lib/source.ts
docs/next.config.mjs
docs/source.config.ts
docs/src/app/api-embed/[[...slug]]/page.tsx
docs/src/app/api-embed/layout.tsx
docs/src/app/dashboard-embed/[[...slug]]/page.tsx
docs/src/app/dashboard-embed/layout.tsx
docs/src/app/docs-embed/[[...slug]]/page.tsx
docs/src/app/docs-embed/layout.tsx
docs/src/app/global.css
docs/src/mdx-components.tsx
docs/templates-dashboard/analytics.mdx
docs/templates-dashboard/auth-providers.mdx
docs/templates-dashboard/meta.json
docs/templates-dashboard/overview.mdx
docs/templates-dashboard/permissions.mdx
docs/templates-dashboard/settings.mdx
docs/templates-dashboard/teams.mdx
docs/templates-dashboard/users.mdx

Need help? Join our Discord

Copy link
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: 8

♻️ Duplicate comments (1)
docs/src/app/api-embed/layout.tsx (1)

1-16: Same embed-layout nits as docs-embed.

Mirror the dvh change and verify EmbeddedLinkInterceptor is a client component as noted in docs-embed/layout.tsx.

🧹 Nitpick comments (42)
docs/templates-dashboard/permissions.mdx (1)

48-57: Add a brief best-practices note and link the Companion.

Strengthen the “Permission Validation” section with least-privilege guidance and make “Stack Companion” a clickable link to its entry point.

Apply:

 ## Permission Validation
 Ensure proper access control by:
 - Testing permission assignments
 - Reviewing access logs
 - Validating permission logic
+
+### Best Practices
+- Prefer least-privilege roles; avoid granting broad project-wide permissions by default.
+- Review effective permissions after team/role changes.
+- Periodically audit dormant users and stale teams.
 
 ## Need Help?
 
-Use the Stack Companion for detailed permission configuration guidance.
+Use the [Stack Companion](</dashboard-embed/companion>) for detailed permission configuration guidance.
docs/templates-dashboard/settings.mdx (1)

28-48: Clarify duplicated “Rate limiting” bullets.

“Rate limiting” appears under both Security Settings and API Configuration; clarify scope or deduplicate to avoid confusion.

Option A (clarify scopes):

 ### Security Settings
 Configure security policies:
 - Password requirements
 - Multi-factor authentication
 - Session management
-- Rate limiting
+- Authentication rate limiting (login/signup, OTP attempts)

 ### API Configuration
 Manage API access:
 - API keys
 - Webhook endpoints
 - CORS settings
-- Rate limiting
+- API rate limiting (per-key/per-route quotas)

Option B (deduplicate to Security Settings only and replace API bullet):

- - Rate limiting
+ - SDK configuration (environments, base URLs)
docs/templates-dashboard/users.mdx (2)

2-7: Name consistency: prefer “User Management”.

Use singular form to align with common docs conventions and sidebar patterns (“User Management”).

-title: Users Management
+title: User Management
@@
-# Users Management
+# User Management

29-35: Add privacy/audit guidance when editing user data.

A short caution helps set expectations around PII and compliance.

 ### Managing User Data
 You can update user information including:
 - Profile details
 - Custom metadata
 - Contact preferences
 - Account status
+> Note: Limit access to PII to authorized roles only. Changes should be auditable (record who changed what and when).
docs/templates-dashboard/teams.mdx (1)

2-7: Name consistency: prefer “Team Management”.

Match singular form used elsewhere.

-title: Teams Management
+title: Team Management
@@
-# Teams Management
+# Team Management
docs/templates-dashboard/auth-providers.mdx (5)

21-26: Name the provider consistently as “X (formerly Twitter)”.

Minor branding nit for clarity.

-- **Twitter/X**: Social media integration
+- **X (formerly Twitter)**: Social media integration

27-31: Clarify SAML is a protocol and mention common IdPs.

Prevents confusion vs. OAuth “providers.”

-### Enterprise Providers
-- **Microsoft**: Azure AD and Office 365 integration
-- **SAML**: Enterprise single sign-on
-- **LinkedIn**: Professional network integration
+### Enterprise Providers
+- **Microsoft Entra ID (Azure AD)**: Enterprise Microsoft login
+- **SAML (via IdPs like Okta, OneLogin, Entra ID, Auth0)**: Enterprise single sign‑on
+- **LinkedIn**: Professional network integration

32-38: Add critical OAuth setup steps (security + reliability).

PKCE, exact redirect URIs, and environment-specific callbacks are easy to miss.

  ### Configuration Steps
-1. **Create OAuth Application**: Set up your app with the provider
-2. **Configure Credentials**: Add client ID and secret to Stack Auth
-3. **Set Callback URLs**: Configure redirect URLs for your application
-4. **Test Integration**: Verify the authentication flow works correctly
+1. **Create OAuth Application**: Set up your app with the provider
+2. **Configure Credentials**: Add client ID and secret to Stack Auth (store secrets server-side only)
+3. **Set Callback URLs**: Add exact authorized redirect URIs per environment (e.g., dev, staging, prod)
+4. **Enable PKCE** (recommended) and required grant types per provider
+5. (If supported) **Set post‑logout redirect URIs**
+6. **Test Integration**: Verify success, error, and cancel flows

41-47: Call out post‑logout URIs and least‑privilege scopes.

Keeps deployments secure by default.

  ### OAuth Configuration
  Each provider requires:
  - Client ID
  - Client Secret
  - Authorized redirect URIs
- - Scope permissions
+ - (If supported) Post‑logout redirect URIs
+ - Scope permissions (use least privilege; request only what you need)

54-57: Link to provider‑specific runbooks from Companion.

If you have deep links (Google/GitHub/etc.), adding them here reduces bounce.

I can add Companion deeplinks for top providers if you share their slugs.

docs/src/components/mdx/embedded-link.tsx (3)

9-26: Harden path rewriting and handle “/docs” root, queries, and hashes.

Current replace() misses “/docs” without trailing slash and query/hash forms; also guard against re‑rewriting embedded routes.

-const getEmbeddedUrl = (href: string): string => {
+const getEmbeddedUrl = (href: string): string => {
   // Handle relative links
   if (href.startsWith('/')) {
-    // Convert regular doc routes to embedded routes
-    if (href.startsWith('/docs/')) {
-      return href.replace('/docs/', '/docs-embed/');
-    }
-    if (href.startsWith('/api/')) {
-      return href.replace('/api/', '/api-embed/');
-    }
-    if (href.startsWith('/dashboard/')) {
-      return href.replace('/dashboard/', '/dashboard-embed/');
-    }
+    // Skip if already embedded
+    if (href.startsWith('/docs-embed/') || href.startsWith('/api-embed/') || href.startsWith('/dashboard-embed/')) {
+      return href;
+    }
+    // Convert regular routes to embedded routes (match with or without trailing slash)
+    if (href === '/docs' || href.startsWith('/docs/') || href.startsWith('/docs?') || href.startsWith('/docs#')) {
+      return href.replace(/^\/docs(?=\/|$)/, '/docs-embed');
+    }
+    if (href === '/api' || href.startsWith('/api/') || href.startsWith('/api?') || href.startsWith('/api#')) {
+      return href.replace(/^\/api(?=\/|$)/, '/api-embed');
+    }
+    if (href === '/dashboard' || href.startsWith('/dashboard/') || href.startsWith('/dashboard?') || href.startsWith('/dashboard#')) {
+      return href.replace(/^\/dashboard(?=\/|$)/, '/dashboard-embed');
+    }
   }
   // Return unchanged for external links or already embedded links
   return href;
 };

45-47: Protect target=_blank links against reverse‑tabnabbing.

Add rel when opening new tabs.

-  // For external links, use regular anchor tag
-  return <a href={embeddedHref} {...props}>{children}</a>;
+  // For external links, use regular anchor tag
+  const rel = props.target === '_blank'
+    ? [props.rel, 'noopener', 'noreferrer'].filter(Boolean).join(' ')
+    : props.rel;
+  return <a href={embeddedHref} {...props} rel={rel}>{children}</a>;

8-26: Deduplicate getEmbeddedUrl across the codebase.

If a similar helper exists in embedded-link-interceptor.tsx, consider exporting a shared util to avoid drift.

Would you like me to extract a shared helper (e.g., docs/src/lib/embedded-url.ts) and update both call sites?

docs/templates-dashboard/analytics.mdx (2)

46-51: Verify feature availability (“CSV export”, “PDF generation”, “API access”, “Custom dashboard”).

If any are roadmap-only, mark as “coming soon” or scope by plan to avoid overpromising.

I can update copy once you confirm which features ship today vs. later.


21-37: Tighten metric definitions (time windows, filters).

Add brief definitions (e.g., DAU/MAU timezones, sign‑in “success” criteria) to reduce ambiguity.

docs/src/app/global.css (3)

198-205: Be cautious hiding scrollbars (a11y).

Hidden scrollbars can hurt discoverability and keyboard users. Limit usage to the embed container only, and ensure visible focus/scroll affordances.

Do all elements with .scrollbar-hide still meet WCAG 2.2 focus visibility?


241-249: Avoid aggressive word-break on links/code.

word-break: break-all can shatter readable text. Prefer overflow-wrap:anywhere for long tokens/URLs.

-.prose a[href*="://"] {
-  word-break: break-all;
-}
+.prose a[href*="://"] {
+  overflow-wrap: anywhere;
+  word-break: break-word;
+}
 
 /* Break very long strings that have no spaces (like tokens, hashes, etc.) */
-.prose code:not(pre code) {
-  word-break: break-all;
-}
+.prose code:not(pre code) {
+  overflow-wrap: anywhere;
+  word-break: break-word;
+  hyphens: auto;
+}

227-233: Consider momentum scrolling on narrow tables.

Improves mobile UX inside embeds.

 .prose table {
   /* Make tables responsive */
   display: block;
   overflow-x: auto;
   white-space: nowrap;
   max-width: 100%;
+  -webkit-overflow-scrolling: touch;
 }
apps/dashboard/src/components/stack-companion/dashboard-docs-widget.tsx (7)

16-26: Remove unused ROUTE_TO_DOC_MAP and prefer ES6 Map per guidelines

This constant isn’t referenced; drop it to avoid drift. Also, for future lookups, prefer Map over Record.

-// Map dashboard routes to documentation pages
-const ROUTE_TO_DOC_MAP: Record<string, string> = {
-  '/users': 'users',
-  '/teams': 'teams',
-  '/permissions': 'permissions',
-  '/settings': 'settings',
-  '/auth-providers': 'auth-providers',
-  '/analytics': 'analytics',
-  '/': 'overview',
-  '': 'overview',
-};
+// (removed) Unused; route resolution happens via DASHBOARD_ROUTE_PATTERNS.

29-37: Switch TITLE_MAP to ES6 Map

Aligns with repo guideline and avoids accidental key collisions.

-// Title mapping for pages
-const TITLE_MAP: Record<string, string> = {
-  'overview': 'Dashboard Overview',
-  'users': 'Users Management',
-  'teams': 'Teams Management',
-  'permissions': 'Permissions Management',
-  'settings': 'Project Settings',
-  'auth-providers': 'Authentication Providers',
-  'analytics': 'Analytics & Insights',
-};
+// Title mapping for pages
+const TITLE_MAP = new Map<string, string>([
+  ['overview', 'Dashboard Overview'],
+  ['users', 'Users Management'],
+  ['teams', 'Teams Management'],
+  ['permissions', 'Permissions Management'],
+  ['settings', 'Project Settings'],
+  ['auth-providers', 'Authentication Providers'],
+  ['analytics', 'Analytics & Insights'],
+]);

Follow-ups needed where the map is read; see lines 75 and 212.


39-48: Auth route mapping: handle both /auth-methods and /auth-providers

If the app ever routes to /auth-providers directly, we’ll miss it. Add an explicit pattern.

   { pattern: /\/settings(?:\/.*)?$/, docPage: 'settings' },
   { pattern: /\/auth-methods(?:\/.*)?$/, docPage: 'auth-providers' }, // Route is auth-methods but docs are auth-providers
+  { pattern: /\/auth-providers(?:\/.*)?$/, docPage: 'auth-providers' },
   { pattern: /\/analytics(?:\/.*)?$/, docPage: 'analytics' },

Please confirm actual dashboard paths.


191-199: Harden iframe attrs and improve UX

Add lazy loading, referrer policy, and popup allowances (if embedded links open windows).

-            <iframe
+            <iframe
               src={docContent.url}
               className="w-full h-full border-0 rounded-md bg-white dark:bg-gray-900"
               onLoad={handleIframeLoad}
               onError={handleIframeError}
               title={`Documentation: ${docContent.title}`}
-              sandbox="allow-scripts allow-same-origin"
+              sandbox="allow-scripts allow-same-origin allow-popups allow-popups-to-escape-sandbox"
+              referrerPolicy="strict-origin-when-cross-origin"
+              loading="lazy"
             />

If popups aren’t desired from inside the embed, drop the allow-popups flags.


75-75: Update title access for Map

Follow-up to Map switch.

-  const title = TITLE_MAP[page] || `Dashboard ${page}`;
+  const title = TITLE_MAP.get(page) ?? `Dashboard ${page}`;

212-213: Update prompt title access for Map

Follow-up to Map switch.

-                    Switch to <span className="font-medium">{TITLE_MAP[getDashboardPage(pathname)] || 'current page'}</span>?
+                    Switch to <span className="font-medium">{TITLE_MAP.get(getDashboardPage(pathname)) ?? 'current page'}</span>?

90-116: Simplify effect logic to avoid extra runs

Current deps (docContent/currentPageDoc) can retrigger the effect; splitting “initial load” and “route change while active” into two effects will reduce state juggling.

I can provide a small refactor if you want it applied here.

apps/dashboard/src/components/stack-companion.tsx (2)

12-12: Lazy-load UnifiedDocsWidget to cut initial bundle

Avoid pulling the docs embed code until the Docs tab is opened.

Example:

import dynamic from 'next/dynamic';
// …
const UnifiedDocsWidget = dynamic(
  () => import('./stack-companion/unified-docs-widget').then(m => m.UnifiedDocsWidget),
  { ssr: false }
);

366-366: Render condition is fine; consider passing isActive=activeItem==='docs' for clarity

No behavior change; slightly clearer intent and future-proof if reused.

-  <UnifiedDocsWidget isActive={true} />
+  <UnifiedDocsWidget isActive={activeItem === 'docs'} />
docs/lib/source.ts (1)

42-50: Confirm /dashboard-embed loader exists (or add it here)

Embeds point to /dashboard-embed/. If that route isn’t defined elsewhere, add a companion loader to mirror /dashboard with embedded components.

Example (add alongside):

export const dashboardEmbedSource = loader({
  baseUrl: '/dashboard-embed',
  source: dashboard.toFumadocsSource(),
  pageTree: { attachFile },
  icon: createIconResolver(),
});
docs/templates-dashboard/overview.mdx (1)

14-19: Link the quick navigation items to their pages

Improves discoverability and verifies routes.

- - **Users**: Manage user accounts, view user details, and handle user-related operations
+ - **[Users](/dashboard/users)**: Manage user accounts, view user details, and handle user-related operations
- - **Teams**: Create and manage teams, handle team memberships and permissions  
+ - **[Teams](/dashboard/teams)**: Create and manage teams, handle team memberships and permissions  
- - **Permissions**: Configure role-based access control and user permissions
+ - **[Permissions](/dashboard/permissions)**: Configure role-based access control and user permissions
- - **Settings**: Configure your project settings, authentication providers, and integrations
+ - **[Settings](/dashboard/settings)**: Configure your project settings, authentication providers, and integrations
- - **Analytics**: View authentication metrics and user engagement data
+ - **[Analytics](/dashboard/analytics)**: View authentication metrics and user engagement data

If the embed uses a custom link component, swap for it as appropriate.

docs/src/app/docs-embed/[[...slug]]/page.tsx (2)

5-11: Fix params typing; Next.js App Router does not pass a Promise for params

params should be a plain object. You don't need await params. This also improves type-safety and consistency with Next types.

-export default async function DocsEmbedPage({
+export default async function DocsEmbedPage({
   params,
 }: {
-  params: Promise<{ slug?: string[] }>,
+  params: { slug?: string[] },
 }) {
-  const { slug } = await params;
+  const { slug } = params;

3-3: Prefer notFound() over redirect("/") for missing pages

Use notFound() to return a proper 404 and avoid unexpected cross-origin redirects inside embeds.

-import { redirect } from 'next/navigation';
+import { notFound } from 'next/navigation';
...
-  if (!page) redirect("/");
+  if (!page) notFound();

Also applies to: 13-13

docs/src/app/api-embed/[[...slug]]/page.tsx (2)

5-11: Align params typing with Next.js conventions

Same as docs-embed: params should not be a Promise; drop await.

-export default async function ApiEmbedPage({
+export default async function ApiEmbedPage({
   params,
 }: {
-  params: Promise<{ slug?: string[] }>,
+  params: { slug?: string[] },
 }) {
-  const { slug } = await params;
+  const { slug } = params;

3-3: Return 404 instead of redirect for missing pages

Prefer notFound() for correct HTTP semantics in embeds.

-import { redirect } from 'next/navigation';
+import { notFound } from 'next/navigation';
...
-  if (!page) redirect("/");
+  if (!page) notFound();

Also applies to: 13-13

docs/src/app/dashboard-embed/layout.tsx (1)

6-14: Use dynamic viewport units to avoid mobile "100vh" issues

Replace h-screen/min-h-screen with 100dvh to prevent overflow under mobile browser UI.

-    <div className="min-h-screen bg-fd-background">
+    <div className="min-h-[100dvh] bg-fd-background">
...
-      <main className="h-screen overflow-hidden">
+      <main className="h-[100dvh] overflow-hidden">
docs/src/app/docs-embed/layout.tsx (1)

9-11: Use dvh to avoid mobile viewport bugs.

Replace h-screen/h-full with h-dvh (and keep the inner overflow-y-auto) to avoid iOS Safari 100vh issues in iframes.

Apply:

-      <main className="h-screen overflow-hidden">
-        <div className="h-full overflow-y-auto overflow-x-hidden scrollbar-hide">
+      <main className="h-dvh overflow-hidden">
+        <div className="h-dvh overflow-y-auto overflow-x-hidden scrollbar-hide">
docs/scripts/generate-docs.js (1)

368-417: Include dashboard static assets (images) alongside MDX.

generateDashboardDocs() copies only .mdx and optional meta.json. If dashboard docs reference images (e.g., imgs), they’ll 404.

Append inside generateDashboardDocs() after writing meta.json:

   console.log('Dashboard documentation generation complete!');
 }
 
+// Copy dashboard asset dirs if present (parity with copyAssets())
+const DASHBOARD_ASSET_DIRS = ['imgs'];
+for (const dir of DASHBOARD_ASSET_DIRS) {
+  const srcDir = path.join(DASHBOARD_TEMPLATE_DIR, dir);
+  if (fs.existsSync(srcDir)) {
+    const files = glob.sync('**/*', { cwd: srcDir, nodir: true });
+    for (const file of files) {
+      const srcFile = path.join(srcDir, file);
+      const destFile = path.join(DASHBOARD_OUTPUT_DIR, dir, file);
+      fs.mkdirSync(path.dirname(destFile), { recursive: true });
+      fs.copyFileSync(srcFile, destFile);
+      console.log(`Copied dashboard asset: ${srcFile} -> ${destFile}`);
+    }
+  }
+}
apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx (2)

20-29: Prefer ES6 Map over Record for title map (per repo guideline).

Use Map for key lookups; adjust call site accordingly.

Apply:

-const DASHBOARD_TITLE_MAP: Record<string, string> = {
-  'overview': 'Dashboard Overview',
-  'users': 'Users Management',
-  'teams': 'Teams Management',
-  'permissions': 'Permissions Management',
-  'settings': 'Project Settings',
-  'auth-providers': 'Authentication Providers',
-  'analytics': 'Analytics & Insights',
-};
+const DASHBOARD_TITLE_MAP = new Map<string, string>([
+  ['overview', 'Dashboard Overview'],
+  ['users', 'Users Management'],
+  ['teams', 'Teams Management'],
+  ['permissions', 'Permissions Management'],
+  ['settings', 'Project Settings'],
+  ['auth-providers', 'Authentication Providers'],
+  ['analytics', 'Analytics & Insights'],
+]);
-      const title = DASHBOARD_TITLE_MAP[page] || `Dashboard ${page}`;
+      const title = DASHBOARD_TITLE_MAP.get(page) ?? `Dashboard ${page}`;

Also applies to: 88-89


58-75: Remove unused DOC_TYPE_OPTIONS or wire it to UI.

It’s currently unused; trim to reduce dead code or integrate into a selector.

Apply:

-// Navigation options for different doc types
-const DOC_TYPE_OPTIONS: Array<{ type: DocType, label: string, description: string }> = [
-  {
-    type: 'dashboard',
-    label: 'Dashboard Docs',
-    description: 'Documentation for this dashboard page'
-  },
-  {
-    type: 'docs',
-    label: 'Main Docs',
-    description: 'Complete Stack Auth documentation'
-  },
-  {
-    type: 'api',
-    label: 'API Reference',
-    description: 'API endpoints and reference'
-  },
-];
docs/src/components/embedded-link-interceptor.tsx (4)

80-86: Remove unused navigation history state.

setNavigationHistory is write-only and not rendered. Trim for clarity.

-  const [, setNavigationHistory] = useState<string[]>([]);
-
-  // Initialize navigation history with current URL
-  useEffect(() => {
-    setNavigationHistory([window.location.pathname]);
-  }, []);
+  // (removed) navigation history state
-            // Add to navigation history
-            setNavigationHistory(prev => [...prev, embeddedHref]);
+            // navigation history removed

Also applies to: 140-142


127-133: Guard console logging or remove.

Avoid noisy logs in production.

-        // Debug logging
-        console.log('Link Debug:', {
-          originalHref: href,
-          currentPath,
-          resolvedHref: embeddedHref
-        });
+        // Debug logging (guarded)
+        if (process.env.NEXT_PUBLIC_EMBED_DEBUG === '1') {
+          console.debug('Link Debug:', { originalHref: href, currentPath, resolvedHref: embeddedHref });
+        }

171-181: Hide the debug UI behind an env flag to avoid exposing it to end users.

Gate the toggle/window with NEXT_PUBLIC_EMBED_DEBUG === '1'.

-      {/* Debug Toggle Button */}
-      <div className="fixed top-4 right-4 z-50">
+      {process.env.NEXT_PUBLIC_EMBED_DEBUG === '1' && (
+      {/* Debug Toggle Button */}
+      <div className="fixed top-4 right-4 z-50">
         <button
           onClick={() => setShowDebug(!showDebug)}
           className="px-3 py-2 bg-gray-800 hover:bg-gray-700 text-white text-xs font-medium rounded-md shadow-lg transition-colors"
           title="Toggle debug window"
         >
           Debug {showDebug ? '▼' : '▲'}
         </button>
       </div>
 
-      {/* Debug Window */}
-      {showDebug && (
+      {/* Debug Window */}
+      {showDebug && (
         <div className="fixed top-16 right-4 w-96 max-h-96 bg-gray-900 text-white text-xs rounded-lg shadow-2xl z-50 overflow-hidden">
           ...
         </div>
-      )}
+      )}
+      )}

Also applies to: 183-257


5-67: Extract shared URL‐rewriting helper. Both embedded-link-interceptor.tsx and mdx/embedded-link.tsx duplicate the /docs/, /api/, and /dashboard/-embed/ mappings—pull that logic into a common util to keep them in sync.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 6a3459e and ca42d6c.

📒 Files selected for processing (26)
  • apps/dashboard/src/components/stack-companion.tsx (2 hunks)
  • apps/dashboard/src/components/stack-companion/dashboard-docs-widget.tsx (1 hunks)
  • apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx (1 hunks)
  • docs/.gitignore (1 hunks)
  • docs/lib/source.ts (2 hunks)
  • docs/next.config.mjs (1 hunks)
  • docs/scripts/generate-docs.js (3 hunks)
  • docs/source.config.ts (1 hunks)
  • docs/src/app/api-embed/[[...slug]]/page.tsx (1 hunks)
  • docs/src/app/api-embed/layout.tsx (1 hunks)
  • docs/src/app/dashboard-embed/[[...slug]]/page.tsx (1 hunks)
  • docs/src/app/dashboard-embed/layout.tsx (1 hunks)
  • docs/src/app/docs-embed/[[...slug]]/page.tsx (1 hunks)
  • docs/src/app/docs-embed/layout.tsx (1 hunks)
  • docs/src/app/global.css (1 hunks)
  • docs/src/components/embedded-link-interceptor.tsx (1 hunks)
  • docs/src/components/mdx/embedded-link.tsx (1 hunks)
  • docs/src/mdx-components.tsx (3 hunks)
  • docs/templates-dashboard/analytics.mdx (1 hunks)
  • docs/templates-dashboard/auth-providers.mdx (1 hunks)
  • docs/templates-dashboard/meta.json (1 hunks)
  • docs/templates-dashboard/overview.mdx (1 hunks)
  • docs/templates-dashboard/permissions.mdx (1 hunks)
  • docs/templates-dashboard/settings.mdx (1 hunks)
  • docs/templates-dashboard/teams.mdx (1 hunks)
  • docs/templates-dashboard/users.mdx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer ES6 Map over Record where feasible

Files:

  • docs/src/app/dashboard-embed/layout.tsx
  • apps/dashboard/src/components/stack-companion.tsx
  • docs/source.config.ts
  • docs/lib/source.ts
  • docs/src/mdx-components.tsx
  • docs/src/components/mdx/embedded-link.tsx
  • docs/src/app/docs-embed/layout.tsx
  • docs/src/app/api-embed/layout.tsx
  • docs/src/app/dashboard-embed/[[...slug]]/page.tsx
  • docs/src/app/api-embed/[[...slug]]/page.tsx
  • docs/src/app/docs-embed/[[...slug]]/page.tsx
  • docs/src/components/embedded-link-interceptor.tsx
  • apps/dashboard/src/components/stack-companion/dashboard-docs-widget.tsx
  • apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx
🧬 Code graph analysis (8)
docs/src/app/dashboard-embed/layout.tsx (1)
docs/src/components/embedded-link-interceptor.tsx (1)
  • EmbeddedLinkInterceptor (77-260)
docs/src/mdx-components.tsx (1)
docs/src/components/mdx/embedded-link.tsx (1)
  • EmbeddedLink (28-47)
docs/src/app/docs-embed/layout.tsx (1)
docs/src/components/embedded-link-interceptor.tsx (1)
  • EmbeddedLinkInterceptor (77-260)
docs/src/app/api-embed/layout.tsx (1)
docs/src/components/embedded-link-interceptor.tsx (1)
  • EmbeddedLinkInterceptor (77-260)
docs/src/app/dashboard-embed/[[...slug]]/page.tsx (1)
docs/src/mdx-components.tsx (1)
  • getEmbeddedMDXComponents (88-94)
docs/src/app/api-embed/[[...slug]]/page.tsx (1)
docs/src/mdx-components.tsx (1)
  • getEmbeddedMDXComponents (88-94)
docs/src/app/docs-embed/[[...slug]]/page.tsx (1)
docs/src/mdx-components.tsx (1)
  • getEmbeddedMDXComponents (88-94)
docs/next.config.mjs (1)
apps/dashboard/next.config.mjs (1)
  • async headers() { (94-127)
🪛 LanguageTool
docs/templates-dashboard/settings.mdx

[grammar] ~14-~14: There might be a mistake here.
Context: ...c.) - Security policies and requirements - Email templates and branding - API keys ...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ...uirements - Email templates and branding - API keys and webhooks - Project integrat...

(QB_NEW_EN)


[grammar] ~16-~16: There might be a mistake here.
Context: ...tes and branding - API keys and webhooks - Project integrations ## Configuration A...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: ...Microsoft, etc. - Custom OAuth providers - Provider-specific settings - Callback UR...

(QB_NEW_EN)


[grammar] ~25-~25: There might be a mistake here.
Context: ...h providers - Provider-specific settings - Callback URL configuration ### Security...

(QB_NEW_EN)


[grammar] ~28-~28: There might be a mistake here.
Context: ...URL configuration ### Security Settings Configure security policies: - Password ...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ...ty Settings Configure security policies: - Password requirements - Multi-factor aut...

(QB_NEW_EN)


[grammar] ~30-~30: There might be a mistake here.
Context: ...curity policies: - Password requirements - Multi-factor authentication - Session ma...

(QB_NEW_EN)


[grammar] ~31-~31: There might be a mistake here.
Context: ...quirements - Multi-factor authentication - Session management - Rate limiting ### ...

(QB_NEW_EN)


[grammar] ~32-~32: There might be a mistake here.
Context: ...ctor authentication - Session management - Rate limiting ### Branding & Templates ...

(QB_NEW_EN)


[grammar] ~35-~35: There might be a mistake here.
Context: ... Rate limiting ### Branding & Templates Customize the user experience: - Email t...

(QB_NEW_EN)


[grammar] ~36-~36: There might be a mistake here.
Context: ...Templates Customize the user experience: - Email templates - Login page branding - ...

(QB_NEW_EN)


[grammar] ~37-~37: There might be a mistake here.
Context: ...e the user experience: - Email templates - Login page branding - Custom domains - T...

(QB_NEW_EN)


[grammar] ~38-~38: There might be a mistake here.
Context: ... - Email templates - Login page branding - Custom domains - Theme configuration ##...

(QB_NEW_EN)


[grammar] ~39-~39: There might be a mistake here.
Context: ...s - Login page branding - Custom domains - Theme configuration ### API Configurati...

(QB_NEW_EN)


[grammar] ~42-~42: There might be a mistake here.
Context: ...eme configuration ### API Configuration Manage API access: - API keys - Webhook ...

(QB_NEW_EN)


[grammar] ~43-~43: There might be a mistake here.
Context: ...### API Configuration Manage API access: - API keys - Webhook endpoints - CORS sett...

(QB_NEW_EN)


[grammar] ~44-~44: There might be a mistake here.
Context: ...figuration Manage API access: - API keys - Webhook endpoints - CORS settings - Rate...

(QB_NEW_EN)


[grammar] ~45-~45: There might be a mistake here.
Context: ...I access: - API keys - Webhook endpoints - CORS settings - Rate limiting ## Need H...

(QB_NEW_EN)


[grammar] ~46-~46: There might be a mistake here.
Context: ...keys - Webhook endpoints - CORS settings - Rate limiting ## Need Help? Consult th...

(QB_NEW_EN)

docs/templates-dashboard/analytics.mdx

[grammar] ~12-~12: There might be a mistake here.
Context: ...erview Track key metrics to understand: - User authentication patterns - Sign-up a...

(QB_NEW_EN)


[grammar] ~13-~13: There might be a mistake here.
Context: ...derstand: - User authentication patterns - Sign-up and conversion rates - Popular a...

(QB_NEW_EN)


[grammar] ~14-~14: There might be a mistake here.
Context: ... patterns - Sign-up and conversion rates - Popular authentication methods - User en...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ...n rates - Popular authentication methods - User engagement trends - System performa...

(QB_NEW_EN)


[grammar] ~16-~16: There might be a mistake here.
Context: ...ication methods - User engagement trends - System performance metrics ## Key Metri...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...ey Metrics ### Authentication Analytics - Daily/Monthly Active Users: Track user...

(QB_NEW_EN)


[grammar] ~27-~27: There might be a mistake here.
Context: ...ders are most popular ### User Behavior - Session Duration: Understand user enga...

(QB_NEW_EN)


[grammar] ~33-~33: There might be a mistake here.
Context: ...users' platforms ### System Performance - API Response Times: Monitor system per...

(QB_NEW_EN)


[grammar] ~40-~40: There might be a mistake here.
Context: ...ata Visualization ### Charts and Graphs - Time series data for trends - Pie charts...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ...and Graphs - Time series data for trends - Pie charts for distribution analysis - B...

(QB_NEW_EN)


[grammar] ~42-~42: There might be a mistake here.
Context: ...s - Pie charts for distribution analysis - Bar charts for comparative metrics - Hea...

(QB_NEW_EN)


[grammar] ~43-~43: There might be a mistake here.
Context: ...sis - Bar charts for comparative metrics - Heatmaps for geographic data ### Export...

(QB_NEW_EN)


[grammar] ~46-~46: There might be a mistake here.
Context: ... for geographic data ### Export Options - CSV data export - PDF report generation ...

(QB_NEW_EN)


[grammar] ~47-~47: There might be a mistake here.
Context: ...ta ### Export Options - CSV data export - PDF report generation - API access to an...

(QB_NEW_EN)


[grammar] ~48-~48: There might be a mistake here.
Context: ... CSV data export - PDF report generation - API access to analytics data - Custom da...

(QB_NEW_EN)


[grammar] ~49-~49: There might be a mistake here.
Context: ...eneration - API access to analytics data - Custom dashboard creation ## Need Help?...

(QB_NEW_EN)

docs/templates-dashboard/permissions.mdx

[grammar] ~12-~12: There might be a mistake here.
Context: ...access to your application resources by: - Defining custom permissions - Creating r...

(QB_NEW_EN)


[grammar] ~13-~13: There might be a mistake here.
Context: ...ources by: - Defining custom permissions - Creating roles and permission sets - Ass...

(QB_NEW_EN)


[grammar] ~14-~14: There might be a mistake here.
Context: ...ons - Creating roles and permission sets - Assigning permissions to users and teams...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ...Assigning permissions to users and teams - Managing permission inheritance ## Perm...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ...ermission Types ### Project Permissions Global permissions that apply across you...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...s that apply across your entire project: - Admin access - User management - Team ma...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...ross your entire project: - Admin access - User management - Team management - Sett...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...roject: - Admin access - User management - Team management - Settings configuration...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: ...cess - User management - Team management - Settings configuration ### Team Permiss...

(QB_NEW_EN)


[grammar] ~27-~27: There might be a mistake here.
Context: ...ings configuration ### Team Permissions Permissions specific to team contexts: -...

(QB_NEW_EN)


[grammar] ~28-~28: There might be a mistake here.
Context: ...s Permissions specific to team contexts: - Team membership management - Team resour...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ...m contexts: - Team membership management - Team resource access - Team-specific ope...

(QB_NEW_EN)


[grammar] ~30-~30: There might be a mistake here.
Context: ...ership management - Team resource access - Team-specific operations ### Custom Per...

(QB_NEW_EN)


[grammar] ~33-~33: There might be a mistake here.
Context: ...cific operations ### Custom Permissions Define your own permissions for: - Appli...

(QB_NEW_EN)


[grammar] ~34-~34: There might be a mistake here.
Context: ...issions Define your own permissions for: - Application-specific features - Resource...

(QB_NEW_EN)


[grammar] ~35-~35: There might be a mistake here.
Context: ...ons for: - Application-specific features - Resource-level access control - Custom b...

(QB_NEW_EN)


[grammar] ~36-~36: There might be a mistake here.
Context: ...features - Resource-level access control - Custom business logic ## Managing Permi...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ...g Permissions ### Assigning Permissions Grant permissions through: - Direct user...

(QB_NEW_EN)


[grammar] ~42-~42: There might be a mistake here.
Context: ...g Permissions Grant permissions through: - Direct user assignment - Team membership...

(QB_NEW_EN)


[grammar] ~43-~43: There might be a mistake here.
Context: ...ssions through: - Direct user assignment - Team membership - Role-based assignment ...

(QB_NEW_EN)


[grammar] ~44-~44: There might be a mistake here.
Context: ...Direct user assignment - Team membership - Role-based assignment - Permission inher...

(QB_NEW_EN)


[grammar] ~45-~45: There might be a mistake here.
Context: ... Team membership - Role-based assignment - Permission inheritance ### Permission V...

(QB_NEW_EN)


[grammar] ~48-~48: There might be a mistake here.
Context: ...n inheritance ### Permission Validation Ensure proper access control by: - Testi...

(QB_NEW_EN)


[grammar] ~49-~49: There might be a mistake here.
Context: ...idation Ensure proper access control by: - Testing permission assignments - Reviewi...

(QB_NEW_EN)

docs/templates-dashboard/teams.mdx

[grammar] ~12-~12: There might be a mistake here.
Context: ...ontrol at scale. From this page you can: - Create new teams - Manage team membershi...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ... ## Team Operations ### Creating Teams Set up new teams with: - Team name and d...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...## Creating Teams Set up new teams with: - Team name and description - Initial memb...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ... teams with: - Team name and description - Initial members - Default permissions - ...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...m name and description - Initial members - Default permissions - Team settings ###...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: ... - Initial members - Default permissions - Team settings ### Managing Members Cont...

(QB_NEW_EN)


[grammar] ~27-~27: There might be a mistake here.
Context: ...ns - Team settings ### Managing Members Control team membership by: - Adding new...

(QB_NEW_EN)


[grammar] ~28-~28: There might be a mistake here.
Context: ...ging Members Control team membership by: - Adding new members - Removing members - ...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ...team membership by: - Adding new members - Removing members - Changing member roles...

(QB_NEW_EN)


[grammar] ~30-~30: There might be a mistake here.
Context: ... - Adding new members - Removing members - Changing member roles - Setting team-spe...

(QB_NEW_EN)


[grammar] ~31-~31: There might be a mistake here.
Context: ...Removing members - Changing member roles - Setting team-specific permissions ### T...

(QB_NEW_EN)


[grammar] ~34-~34: There might be a mistake here.
Context: ...ecific permissions ### Team Permissions Configure what team members can access: ...

(QB_NEW_EN)


[grammar] ~35-~35: There might be a mistake here.
Context: ... Configure what team members can access: - Team-level permissions - Resource access...

(QB_NEW_EN)


[grammar] ~36-~36: There might be a mistake here.
Context: ...ers can access: - Team-level permissions - Resource access control - Role-based res...

(QB_NEW_EN)


[grammar] ~37-~37: There might be a mistake here.
Context: ...el permissions - Resource access control - Role-based restrictions ## Need Help? ...

(QB_NEW_EN)

docs/templates-dashboard/users.mdx

[grammar] ~12-~12: There might be a mistake here.
Context: ...n. ## Overview From this page you can: - View all registered users - Search and f...

(QB_NEW_EN)


[grammar] ~25-~25: There might be a mistake here.
Context: ...uthentication methods - Team memberships - Permission assignments - Recent activity...

(QB_NEW_EN)


[grammar] ~26-~26: There might be a mistake here.
Context: ...eam memberships - Permission assignments - Recent activity ### Managing User Data ...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ... Recent activity ### Managing User Data You can update user information includin...

(QB_NEW_EN)


[grammar] ~30-~30: There might be a mistake here.
Context: ...u can update user information including: - Profile details - Custom metadata - Cont...

(QB_NEW_EN)


[grammar] ~31-~31: There might be a mistake here.
Context: ...information including: - Profile details - Custom metadata - Contact preferences - ...

(QB_NEW_EN)


[grammar] ~32-~32: There might be a mistake here.
Context: ...ing: - Profile details - Custom metadata - Contact preferences - Account status ##...

(QB_NEW_EN)


[grammar] ~33-~33: There might be a mistake here.
Context: ... - Custom metadata - Contact preferences - Account status ### User Permissions Con...

(QB_NEW_EN)


[grammar] ~36-~36: There might be a mistake here.
Context: ...s - Account status ### User Permissions Control what users can access by: - Assi...

(QB_NEW_EN)


[grammar] ~37-~37: There might be a mistake here.
Context: ...ssions Control what users can access by: - Assigning roles - Setting specific permi...

(QB_NEW_EN)


[grammar] ~38-~38: There might be a mistake here.
Context: ...t users can access by: - Assigning roles - Setting specific permissions - Managing ...

(QB_NEW_EN)


[grammar] ~39-~39: There might be a mistake here.
Context: ...ing roles - Setting specific permissions - Managing team memberships ## Need Help?...

(QB_NEW_EN)

docs/templates-dashboard/auth-providers.mdx

[grammar] ~13-~13: There might be a mistake here.
Context: ...oviders (Google, GitHub, Facebook, etc.) - Enterprise providers (Microsoft, SAML, e...

(QB_NEW_EN)


[grammar] ~15-~15: There might be a mistake here.
Context: ...ft, SAML, etc.) - Custom OAuth providers - Passwordless options ## Supported Provi...

(QB_NEW_EN)


[grammar] ~20-~20: There might be a mistake here.
Context: ... Providers ### Popular Social Providers - Google: OAuth 2.0 integration with Goo...

(QB_NEW_EN)


[grammar] ~21-~21: There might be a mistake here.
Context: ...uth 2.0 integration with Google accounts - GitHub: Perfect for developer-focused ...

(QB_NEW_EN)


[grammar] ~22-~22: There might be a mistake here.
Context: ...rfect for developer-focused applications - Facebook: Social login for consumer ap...

(QB_NEW_EN)


[grammar] ~23-~23: There might be a mistake here.
Context: ...: Social login for consumer applications - Discord: Great for gaming and communit...

(QB_NEW_EN)


[grammar] ~24-~24: There might be a mistake here.
Context: ...d**: Great for gaming and community apps - Twitter/X: Social media integration #...

(QB_NEW_EN)


[grammar] ~27-~27: There might be a mistake here.
Context: ...ia integration ### Enterprise Providers - Microsoft: Azure AD and Office 365 int...

(QB_NEW_EN)


[grammar] ~28-~28: There might be a mistake here.
Context: ...t**: Azure AD and Office 365 integration - SAML: Enterprise single sign-on - **Li...

(QB_NEW_EN)


[grammar] ~29-~29: There might be a mistake here.
Context: ...on - SAML: Enterprise single sign-on - LinkedIn: Professional network integra...

(QB_NEW_EN)


[grammar] ~41-~41: There might be a mistake here.
Context: ...ovider Settings ### OAuth Configuration Each provider requires: - Client ID - Cl...

(QB_NEW_EN)


[grammar] ~42-~42: There might be a mistake here.
Context: ...th Configuration Each provider requires: - Client ID - Client Secret - Authorized r...

(QB_NEW_EN)


[grammar] ~43-~43: There might be a mistake here.
Context: ...tion Each provider requires: - Client ID - Client Secret - Authorized redirect URIs...

(QB_NEW_EN)


[grammar] ~44-~44: There might be a mistake here.
Context: ...er requires: - Client ID - Client Secret - Authorized redirect URIs - Scope permiss...

(QB_NEW_EN)


[grammar] ~45-~45: There might be a mistake here.
Context: ...Client Secret - Authorized redirect URIs - Scope permissions ### Advanced Options ...

(QB_NEW_EN)


[grammar] ~48-~48: There might be a mistake here.
Context: ... Scope permissions ### Advanced Options - Custom button styling - Provider-specifi...

(QB_NEW_EN)


[grammar] ~49-~49: There might be a mistake here.
Context: ...Advanced Options - Custom button styling - Provider-specific settings - User data m...

(QB_NEW_EN)


[grammar] ~50-~50: There might be a mistake here.
Context: ...ton styling - Provider-specific settings - User data mapping - Permission scopes #...

(QB_NEW_EN)


[grammar] ~51-~51: There might be a mistake here.
Context: ...er-specific settings - User data mapping - Permission scopes ## Need Help? Use th...

(QB_NEW_EN)

🪛 Biome (2.1.2)
apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx

[error] 220-220: src is assigned to itself.

This is where is assigned.

Self assignments have no effect and can be removed.

(lint/correctness/noSelfAssign)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: all-good
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: docker
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: Security Check
🔇 Additional comments (14)
docs/.gitignore (1)

30-35: Good addition: ignore generated dashboard docs output.

Adding /content/dashboard/ aligns with existing generated content ignores and prevents noisy diffs. No action needed.

docs/templates-dashboard/teams.mdx (1)

12-17: Verify “team analytics” availability or rephrase.

If analytics aren’t yet in the Teams section, reword to avoid overpromising (e.g., “View team activity”).

Would you confirm the current dashboard includes team-level analytics in this view? If not, I can propose precise wording.

docs/next.config.mjs (1)

12-51: Scope CORS headers to development, use dynamic origin, restrict methods, add Vary and caching, and verify credentials

  • Wrap the existing headers() return in a if (process.env.NODE_ENV === 'development') guard in docs/next.config.mjs.
  • Replace hard-coded origin with process.env.DASHBOARD_APP_ORIGIN ?? 'http://localhost:8101'.
  • Change Access-Control-Allow-Methods to only GET, OPTIONS.
  • Add Vary: Origin and Access-Control-Max-Age: 600 to each route.
  • Confirm whether cookies are sent; if so, also add Access-Control-Allow-Credentials: true and echo the exact origin.
docs/templates-dashboard/meta.json (1)

4-15: Validate slugs and group markers with generator

Ensure the generator supports section dividers like "---General---" and that each slug matches the corresponding MDX filename (users, teams, permissions, settings, auth-providers, analytics).

If needed, I can script-check for missing/extra pages against content/dashboard.

docs/lib/source.ts (1)

1-1: Import looks good

Extending to include dashboard is consistent with the loaders below.

docs/src/app/docs-embed/[[...slug]]/page.tsx (1)

15-23: MDX render path looks correct

Using page.data.body with getEmbeddedMDXComponents() aligns with the embed strategy.

docs/source.config.ts (1)

30-39: Dashboard collection wiring LGTM

defineDocs({ dir: './content/dashboard', ... }) mirrors the API collection. No issues spotted.

docs/src/app/api-embed/[[...slug]]/page.tsx (1)

15-23: MDX embed rendering looks good

Consistent with the embedded MDX components strategy.

docs/src/app/dashboard-embed/layout.tsx (2)

1-8: EmbeddedLinkInterceptor includes 'use client' directive; no changes required


10-10: scrollbar-hide utility confirmed in global.css; no changes needed.

docs/src/mdx-components.tsx (2)

5-5: Imports look correct

ComponentProps typing and EmbeddedLink import are appropriate.

Also applies to: 17-17


87-94: Embedded MDX components override is solid

Overriding a to EmbeddedLink with isEmbedded enforces embedded URL rewriting as intended.

docs/src/app/docs-embed/layout.tsx (1)

1-8: No action needed—EmbeddedLinkInterceptor already declares "use client" at the top.

apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx (1)

330-331: Minimize iframe sandbox permissions (verify necessity of allow-same-origin).

allow-same-origin significantly increases the embedded page’s capabilities. If not required, drop it to harden the sandbox.

Proposed tightened sandbox (pending validation with embedded link behavior):

-              sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox"
+              sandbox="allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox"

Please confirm embedded navigation and external links still function as expected.

Comment on lines +5 to +11
export default async function DashboardEmbedPage({
params,
}: {
params: Promise<{ slug?: string[] }>,
}) {
const { slug } = await params;
const page = dashboardSource.getPage(slug ?? []);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix params typing (don’t await params).

params is a plain object in the App Router, not a Promise. Current code will type- or runtime-fail.

Apply:

-export default async function DashboardEmbedPage({
-  params,
-}: {
-  params: Promise<{ slug?: string[] }>,
-}) {
-  const { slug } = await params;
+export default async function DashboardEmbedPage({
+  params,
+}: {
+  params: { slug?: string[] },
+}) {
+  const { slug } = params;
📝 Committable suggestion

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

Suggested change
export default async function DashboardEmbedPage({
params,
}: {
params: Promise<{ slug?: string[] }>,
}) {
const { slug } = await params;
const page = dashboardSource.getPage(slug ?? []);
export default async function DashboardEmbedPage({
params,
}: {
params: { slug?: string[] },
}) {
const { slug } = params;
const page = dashboardSource.getPage(slug ?? []);
// …
}
🤖 Prompt for AI Agents
In docs/src/app/dashboard-embed/[[...slug]]/page.tsx around lines 5 to 11, the
handler types params as a Promise and uses await, but in the App Router params
is a plain object; remove the Promise typing and the await. Change the function
signature to accept params: { slug?: string[] } (not Promise<{ slug?: string[]
}>) and use const { slug } = params; then pass slug ?? [] to
dashboardSource.getPage(...) as before.

Copy link
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: 2

Caution

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

⚠️ Outside diff range comments (2)
docs/src/components/layouts/docs.tsx (2)

96-108: Prefer Map over Record for accordion state (guideline) and avoid object churn

Switch to Map to align with project guideline and reduce spread-copy churn on updates.

Apply this diff:

-export function AccordionProvider({ children }: { children: ReactNode }) {
-  const [accordionState, setAccordionStateInternal] = useState<Record<string, boolean>>({});
+export function AccordionProvider({ children }: { children: ReactNode }) {
+  const [accordionState, setAccordionStateInternal] = useState<Map<string, boolean>>(new Map());
@@
-  const setAccordionState = (key: string, isOpen: boolean) => {
-    setAccordionStateInternal(prev => ({ ...prev, [key]: isOpen }));
-  };
+  const setAccordionState = (key: string, isOpen: boolean) => {
+    setAccordionStateInternal((prev) => {
+      const next = new Map(prev);
+      next.set(key, isOpen);
+      return next;
+    });
+  };

And update the context type accordingly (outside this hunk):

type AccordionContextType = {
  accordionState: Map<string, boolean>;
  setAccordionState: (key: string, isOpen: boolean) => void;
};

268-294: Avoid substring matches that can mis-route platforms (e.g., “react” matching “preact”); also prefer Map over Record

Drop the includes() check and use a Map. This prevents accidental matches and follows the Map guideline.

Apply this diff:

-export function findPlatformContent(tree: PageTree.Root, platform: string): PageTree.Node[] {
-  // Platform folder name mappings
-  const platformMappings: Record<string, string[]> = {
-    'next': ['next.js', 'nextjs'],
-    'react': ['react'],
-    'js': ['javascript'],
-    'python': ['python']
-  };
+export function findPlatformContent(tree: PageTree.Root, platform: string): PageTree.Node[] {
+  // Platform folder name mappings
+  const platformMappings = new Map<string, string[]>([
+    ['next', ['next.js', 'nextjs']],
+    ['react', ['react']],
+    ['js', ['javascript']],
+    ['python', ['python']],
+  ]);
@@
-  const possibleNames = platformKey in platformMappings ? platformMappings[platformKey] : [platformKey];
+  const possibleNames = platformMappings.get(platformKey) ?? [platformKey];
@@
-      if (possibleNames.some(name => {
-        const normalizedName = name.trim().toLowerCase();
-        return itemName === normalizedName || itemName.includes(normalizedName);
-      })) {
+      if (possibleNames.some((name) => itemName === name.trim().toLowerCase())) {
         return item.children;
       }
♻️ Duplicate comments (2)
apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx (2)

99-113: Remove hardcoded localhost; parameterize docs origin (avoids mixed-content and non-prod breakage).

Build embed URLs from a configurable origin (e.g., NEXT_PUBLIC_DOCS_ORIGIN) or current origin; stop hardcoding http://localhost:8104.

Apply:

-      const url = `http://localhost:8104/docs-embed/${docMapping.path}`;
+      const url = joinDocUrl(`/docs-embed/${docMapping.path}`);
-      const url = `http://localhost:8104/docs-embed/${platform}/getting-started/setup`;
+      const url = joinDocUrl(`/docs-embed/${platform}/getting-started/setup`);
-      const url = `http://localhost:8104/api-embed/overview`;
+      const url = joinDocUrl(`/api-embed/overview`);

Add near the top (after types):

+const DOCS_ORIGIN = process.env.NEXT_PUBLIC_DOCS_ORIGIN ?? '';
+const joinDocUrl = (p: string) => {
+  const base =
+    DOCS_ORIGIN.trim() ||
+    (typeof window !== 'undefined' ? window.location.origin : '');
+  return `${base.replace(/\/$/, '')}${p.startsWith('/') ? p : `/${p}`}`;
+};

241-255: Fix iframe reload fallback (no-op self-assign + Biome error).

Replace self-assign with cache-busting URL update to actually trigger reload.

-    } catch (error) {
-      // If we can't access iframe history, try to reload the previous page
-      // This is a fallback that at least resets the iframe
-      console.warn('Cannot access iframe history, reloading current page');
-      if (iframeRef) {
-        iframeRef.src = iframeRef.src;
-      }
-    }
+    } catch (error) {
+      console.warn('Cannot access iframe history, forcing reload via cache-buster');
+      if (iframeRef?.src) {
+        try {
+          const u = new URL(iframeRef.src, window.location.origin);
+          u.searchParams.set('_r', Date.now().toString());
+          setDocContent(prev => (prev ? { ...prev, url: u.toString() } : prev));
+        } catch {
+          setDocContent(prev => (prev ? { ...prev } : prev)); // react remount fallback
+        }
+      }
+    }
🧹 Nitpick comments (12)
docs/src/components/layouts/docs.tsx (3)

110-124: Use Map accessors in the hook; expose stable tuple

If you adopt Map in the provider, read via get().

Apply this diff:

-export function useAccordionState(key: string, defaultValue: boolean) {
+export function useAccordionState(key: string, defaultValue: boolean) {
@@
-  const { accordionState, setAccordionState } = context;
-  const isOpen = accordionState[key] ?? defaultValue;
+  const { accordionState, setAccordionState } = context;
+  const isOpen = (accordionState instanceof Map
+    ? accordionState.get(key)
+    : // backward-compat if context not yet migrated
+      (accordionState as Record<string, boolean>)[key]) ?? defaultValue;

297-344: Use stable React keys; avoid index to prevent reorder bugs

Keys using index can cause state leakage on reordering. Prefer semantic keys.

Apply this diff:

-          {item.children.map((child, index) => (
-            <PageTreeItem key={child.type === 'page' ? child.url : index} item={child} currentPlatform={currentPlatform} />
-          ))}
+          {item.children.map((child) => (
+            <PageTreeItem
+              key={`${child.type}:${child.type === 'page' ? child.url : String(child.name ?? '')}`}
+              item={child}
+              currentPlatform={currentPlatform}
+            />
+          ))}
@@
-        {item.children.map((child, index) => (
-          <PageTreeItem key={child.type === 'page' ? child.url : index} item={child} currentPlatform={currentPlatform} />
-        ))}
+        {item.children.map((child) => (
+          <PageTreeItem
+            key={`${child.type}:${child.type === 'page' ? child.url : String(child.name ?? '')}`}
+            item={child}
+            currentPlatform={currentPlatform}
+          />
+        ))}

346-376: LGTM on making renderSidebarContent public; just one key nit

Approach looks good. Mirror the stable-key change here too.

Apply this diff:

-      {tree.children.map((item, index) => (
-        <PageTreeItem key={item.type === 'page' ? item.url : index} item={item} currentPlatform={currentPlatform} />
-      ))}
+      {tree.children.map((item) => (
+        <PageTreeItem
+          key={`${item.type}:${item.type === 'page' ? item.url : String(item.name ?? '')}`}
+          item={item}
+          currentPlatform={currentPlatform}
+        />
+      ))}
docs/src/app/docs-embed/[[...slug]]/page.tsx (1)

18-23: Consider 404 for unknown pages (optional)

Redirecting unknown slugs to overview can mask broken links. Using notFound() yields a proper 404.

Apply this diff:

-  if (!page) {
-    // Try to redirect to a sensible default if page not found
-    redirect('/docs-embed/next/overview');
-  }
+  if (!page) {
+    // Unknown page → 404 (keeps bad links visible)
+    // import { notFound } from 'next/navigation'
+    // notFound();
+    redirect('/docs-embed/next/overview');
+  }
docs/src/components/embedded-docs-with-sidebar.tsx (3)

24-34: Harden postMessage handling with a simple channel guard

Anyone can postMessage into the iframe; add a shared “source” token check (keeps cross-origin support without origin allowlists).

Apply this diff:

-  useEffect(() => {
+  useEffect(() => {
     const handleMessage = (event: MessageEvent) => {
-      if (event.data?.type === 'TOGGLE_SIDEBAR') {
+      if (event.data?.source === 'STACK_EMBED' && event.data?.type === 'TOGGLE_SIDEBAR') {
         setIsSidebarVisible(event.data.visible);
       }
     };

Parent should send: window.postMessage({ source: 'STACK_EMBED', type: 'TOGGLE_SIDEBAR', visible: true }, '*').


56-74: Add dialog semantics for accessibility on the Drawer

Mark the drawer as a modal dialog for AT users.

Apply this diff:

-                <div className="fixed left-0 top-0 bottom-0 w-[268px] z-50 bg-fd-background/95 backdrop-blur-md border-r border-fd-border shadow-lg">
+                <div
+                  className="fixed left-0 top-0 bottom-0 w-[268px] z-50 bg-fd-background/95 backdrop-blur-md border-r border-fd-border shadow-lg"
+                  role="dialog"
+                  aria-modal="true"
+                  aria-label="Documentation navigation"
+                >

11-15: Remove unused currentSlug prop (keeps API tight)

The prop isn’t used; drop it here and at the call site.

Apply this diff:

 type EmbeddedDocsWithSidebarProps = {
   pageTree: PageTree.Root,
-  currentSlug: string[],
   children: React.ReactNode,
 }
 
-export function EmbeddedDocsWithSidebar({ pageTree, currentSlug, children }: EmbeddedDocsWithSidebarProps) {
+export function EmbeddedDocsWithSidebar({ pageTree, children }: EmbeddedDocsWithSidebarProps) {

And in docs/src/app/docs-embed/[[...slug]]/page.tsx remove the prop:

-    <EmbeddedDocsWithSidebar
-      pageTree={source.pageTree}
-      currentSlug={slug}
-    >
+    <EmbeddedDocsWithSidebar pageTree={source.pageTree}>

Also applies to: 17-18

apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx (5)

85-101: Prefer Map over Record for key–value collections (repo guideline).

Use Map and safe fallback for unknown pages.

-      const dashboardToDocsMap: Record<string, { path: string, title: string }> = {
-        'overview': { path: `${platform}/overview`, title: 'Stack Auth Overview' },
-        'users': { path: `${platform}/getting-started/users`, title: 'User Management' },
-        'auth-methods': { path: `${platform}/concepts/auth-providers`, title: 'Authentication Providers' },
-        'orgs-and-teams': { path: `${platform}/concepts/orgs-and-teams`, title: 'Teams & Organizations' },
-        'team-permissions': { path: `${platform}/concepts/permissions#team-permissions`, title: 'Team Permissions' },
-        'emails': { path: `${platform}/concepts/emails`, title: 'Emails' },
-        'domains': { path: `${platform}/getting-started/production#domains`, title: 'Domains' },
-        'webhooks': { path: `${platform}/concepts/webhooks`, title: 'Webhooks' },
-        'stack-auth-keys': { path: `${platform}/getting-started/setup#update-api-keys`, title: 'Stack Auth Keys' },
-        'project-settings': { path: `${platform}/getting-started/production#enabling-production-mode`, title: 'Project Configuration' },
-      };
-
-      const docMapping = dashboardToDocsMap[page];
+      const dashboardToDocsMap = new Map<string, { path: string; title: string }>([
+        ['overview', { path: `${platform}/overview`, title: 'Stack Auth Overview' }],
+        ['users', { path: `${platform}/getting-started/users`, title: 'User Management' }],
+        ['auth-methods', { path: `${platform}/concepts/auth-providers`, title: 'Authentication Providers' }],
+        ['orgs-and-teams', { path: `${platform}/concepts/orgs-and-teams`, title: 'Teams & Organizations' }],
+        ['team-permissions', { path: `${platform}/concepts/permissions#team-permissions`, title: 'Team Permissions' }],
+        ['emails', { path: `${platform}/concepts/emails`, title: 'Emails' }],
+        ['domains', { path: `${platform}/getting-started/production#domains`, title: 'Domains' }],
+        ['webhooks', { path: `${platform}/concepts/webhooks`, title: 'Webhooks' }],
+        ['stack-auth-keys', { path: `${platform}/getting-started/setup#update-api-keys`, title: 'Stack Auth Keys' }],
+        ['project-settings', { path: `${platform}/getting-started/production#enabling-production-mode`, title: 'Project Configuration' }],
+      ]);
+
+      const docMapping =
+        dashboardToDocsMap.get(page) ?? dashboardToDocsMap.get('overview')!;

136-147: Reset back-button state on new loads.

Avoid stale “Back” visibility after reloads.

         setLoading(true);
         setError(null);
         setIframeLoaded(false);
+        setCanGoBack(false);
         s
1AF0
etCurrentPageDoc(newPageDoc);

149-158: Guard debug logs for production.

Keep console noise out of prod.

-          console.log('Debug mapping:', {
+          if (process.env.NODE_ENV !== 'production') console.log('Debug mapping:', {
             pathname,
             normalizedPath: pathname.replace(/^\/projects\/[^/]+/, ''),
             detectedPage: page,
             platform: selectedPlatform
           });
-          const content = getDocContentForPath(pathname, selectedDocType, selectedPlatform);
-          console.log('Loading docs:', { page, platform: selectedPlatform, url: content.url });
+          const content = getDocContentForPath(pathname, selectedDocType, selectedPlatform);
+          if (process.env.NODE_ENV !== 'production') console.log('Loading docs:', { page, platform: selectedPlatform, url: content.url });

149-149: Avoid recomputing getDashboardPage.

Use the already computed value.

-          const page = getDashboardPage(pathname);
+          const page = newPageDoc;

224-230: Hide sidebar in the embed when switching doc types.

Mirror local state to the iframe.

       setSelectedDocType(docType);
       setShowSwitchPrompt(false);
       setIsSidebarVisible(false); // Hide sidebar when switching doc types
+      if (iframeRef) toggleEmbeddedSidebar(iframeRef, false);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between ca42d6c and 2e83268.

📒 Files selected for processing (4)
  • apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx (1 hunks)
  • docs/src/app/docs-embed/[[...slug]]/page.tsx (1 hunks)
  • docs/src/components/embedded-docs-with-sidebar.tsx (1 hunks)
  • docs/src/components/layouts/docs.tsx (5 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • docs/src/components/embedded-docs-with-sidebar.tsx
  • docs/src/app/docs-embed/[[...slug]]/page.tsx
  • docs/src/components/layouts/docs.tsx
  • apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

For blocking alerts and errors in UI, do not use toast notifications; use alerts instead

Files:

  • apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx
{apps/dashboard,apps/dev-launchpad,packag F0D0 es/stack-ui,packages/react}/**/*.{tsx,jsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Keep hover/click animations snappy; avoid pre-transition delays on hover and apply transitions after the action (e.g., fade-out on hover end)

Files:

  • apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx
🧬 Code graph analysis (3)
docs/src/components/embedded-docs-with-sidebar.tsx (2)
docs/src/components/layouts/docs.tsx (2)
  • AccordionProvider (96-108)
  • renderSidebarContent (346-376)
docs/src/components/layouts/sidebar-context.tsx (1)
  • SidebarProvider (51-184)
docs/src/app/docs-embed/[[...slug]]/page.tsx (3)
docs/lib/source.ts (1)
  • source (23-30)
docs/src/components/embedded-docs-with-sidebar.tsx (1)
  • EmbeddedDocsWithSidebar (17-91)
docs/src/mdx-components.tsx (1)
  • getEmbeddedMDXComponents (88-94)
apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx (1)
docs/src/components/icons.tsx (2)
  • Menu (137-141)
  • ExternalLink (98-108)
🪛 Biome (2.1.2)
apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx

[error] 252-252: src is assigned to itself.

This is where is assigned.

Self assignments have no effect and can be removed.

(lint/correctness/noSelfAssign)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: all-good
  • GitHub Check: docker
  • GitHub Check: setup-tests
  • GitHub Check: restart-dev-and-test
  • GitHub Check: Security Check
🔇 Additional comments (3)
docs/src/app/docs-embed/[[...slug]]/page.tsx (1)

1-5: Import 'lib/source' resolves correctly via baseUrl
docs/tsconfig.json sets baseUrl to "." so non-relative imports map to project root (docs/lib/source.ts exists and is already used via relative paths); no changes needed.

apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx (2)

398-406: Revisit sandbox allowances; narrow if possible.

If not strictly required, drop allow-same-origin and/or allow-popups-to-escape-sandbox to reduce risk. PostMessage doesn’t need same-origin.

Do you know if the embedded docs require same-origin APIs (e.g., localStorage)? If not, I can propose a tightened sandbox string.


286-313: Good: inline alert for errors (no toasts).

Complies with the repo’s UI guideline for blocking errors.

Copy link
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: 2

🧹 Nitpick comments (2)
docs/src/components/embedded-docs-with-sidebar.tsx (2)

59-65: Prefer a Map-based label lookup over chained conditionals.

Cleaner, extensible, and aligns with the repo guideline to prefer ES6 Map for key–value collections.

-                        <div id="embed-sidebar-title" className="text-sm font-medium text-fd-foreground">
-                          {currentPlatform === 'next' ? 'Next.js' :
-                            currentPlatform === 'js' ? 'JavaScript' :
-                            currentPlatform.charAt(0).toUpperCase() + currentPlatform.slice(1)} Documentation
-                        </div>
+                        <div id="embed-sidebar-title" className="text-sm font-medium text-fd-foreground">
+                          {platformLabel} Documentation
+                        </div>

Add these helpers (place near the top of the file, after imports):

const PLATFORM_LABELS = new Map<string, string>([
  ['next', 'Next.js'],
  ['js', 'JavaScript'],
  ['react', 'React'],
  ['react-native', 'React Native'],
  ['node', 'Node.js'],
]);

function toTitleCaseFromSlug(slug: string) {
  return slug.replace(/(^\w)|(-\w)/g, s => s.replace('-', ' ').toUpperCase());
}

And compute the label after currentPlatform:

const platformLabel = currentPlatform
  ? PLATFORM_LABELS.get(currentPlatform) ?? toTitleCaseFromSlug(currentPlatform)
  : 'Documentation';

34-35: Optionally lock body scroll while the drawer is open (iOS Safari).

Prevents background scrolling in some mobile browsers.

+  useEffect(() => {
+    if (!isSidebarVisible) return;
+    const prev = document.body.style.overflow;
+    document.body.style.overflow = 'hidden';
+    return () => {
+      document.body.style.overflow = prev;
+    };
+  }, [isSidebarVisible]);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2e83268 and 2e01464.

📒 Files selected for processing (1)
  • docs/src/components/embedded-docs-with-sidebar.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • docs/src/components/embedded-docs-with-sidebar.tsx
🧬 Code graph analysis (1)
docs/src/components/embedded-docs-with-sidebar.tsx (2)
docs/src/components/layouts/docs.tsx (2)
  • AccordionProvider (96-108)
  • renderSidebarContent (346-376)
docs/src/components/layouts/sidebar-context.tsx (1)
  • SidebarProvider (51-184)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: docker
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: all-good
  • GitHub Check: docker
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: Security Check

Comment on lines +55 to +56
<div className="fixed left-0 top-0 bottom-0 w-[268px] z-50 bg-fd-background/95 backdrop-blur-md border-r border-fd-border shadow-lg">
<div className="h-full flex flex-col">
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add dialog/nav semantics and ARIA for accessibility.

Improve screen reader support and expected modal behavior.

-                <div className="fixed left-0 top-0 bottom-0 w-[268px] z-50 bg-fd-background/95 backdrop-blur-md border-r border-fd-border shadow-lg">
+                <div
+                  className="fixed left-0 top-0 bottom-0 w-[268px] z-50 bg-fd-background/95 backdrop-blur-md border-r border-fd-border shadow-lg"
+                  role="dialog"
+                  aria-modal="true"
+                  aria-labelledby="embed-sidebar-title"
+                >
                   <div className="h-full flex flex-col">
                     {/* Header */}
                     <div className="flex items-center justify-between p-4 border-b border-fd-border">
                       {currentPlatform && (
-                        <div className="text-sm font-medium text-fd-foreground">
+                        <div id="embed-sidebar-title" className="text-sm font-medium text-fd-foreground">
                           {currentPlatform === 'next' ? 'Next.js' :
                             currentPlatform === 'js' ? 'JavaScript' :
                             currentPlatform.charAt(0).toUpperCase() + currentPlatform.slice(1)} Documentation
                         </div>
                       )}
                       <button
                         onClick={() => setIsSidebarVisible(false)}
-                        className="p-1 rounded-md hover:bg-fd-accent/50 text-fd-muted-foreground hover:text-fd-foreground transition-colors"
-                        title="Close sidebar"
+                        className="p-1 rounded-md hover:bg-fd-accent/50 text-fd-muted-foreground hover:text-fd-foreground transition-colors"
+                        title="Close sidebar"
+                        aria-label="Close sidebar"
                       >
                         ×
                       </button>
                     </div>
 
                     {/* Scrollable Content */}
-                    <div className="flex-1 overflow-y-auto">
+                    <nav className="flex-1 overflow-y-auto" aria-label="Documentation navigation">
                       <div className="p-4">
                         {renderSidebarContent(pageTree, docsPath)}
                       </div>
-                    </div>
+                    </nav>

Also applies to: 58-66, 75-80

@madster456 madster456 changed the title Stack companion docs [Docs][Site] + [Dashboard][UI] - Adds docs to Stack Companion Sep 5, 2025
Copy link
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: 3

♻️ Duplicate comments (4)
docs/src/components/embedded-link-interceptor.tsx (2)

88-96: Remove HEAD preflight; navigate directly to avoid 405/308 pitfalls.

HEAD can block valid navigations and adds latency. Let the platform handle 404s.

-  // Function to check if a URL exists
-  const checkUrlExists = async (url: string): Promise<boolean> => {
-    try {
-      const response = await fetch(url, { method: 'HEAD' });
-      return response.ok;
-    } catch {
-      return false;
-    }
-  };
+  // (Optional) Add non-blocking prefetch later if needed.
-        // Check if the URL exists before navigating (async operation)
-        checkUrlExists(embeddedHref).then((urlExists) => {
-          if (urlExists) {
-            // Navigate to the embedded version
-            window.location.href = embeddedHref;
-          }
-        }).catch(() => {
-          // Network error or other issue - silently ignore
-        });
+        // Navigate to the embedded version
+        window.location.href = embeddedHref;

Also applies to: 129-137


5-84: Avoid duplication with EmbeddedLink MDX component. Extract shared util.

The rewriting logic here mirrors docs/src/components/mdx/embedded-link.tsx. Factor into a shared helper to keep rules in one place.

apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx (2)

28-43: Good: docs origin is configurable (no hardcoded localhost).

This addresses the earlier feedback and enables non-local/HTTPS deployments.


312-317: Fix fallback reload (self-assign no-op; linter error).

Force a real reload via cache-buster; resolves Biome error.

-      // If we can't access iframe history, try to reload the previous page
-      // This is a fallback that at least resets the iframe
-      console.warn('Cannot access iframe history, reloading current page');
-      if (iframeRef) {
-        iframeRef.src = iframeRef.src;
-      }
+      console.warn('Cannot access iframe history, forcing reload via cache-buster');
+      if (iframeRef) {
+        try {
+          const u = new URL(iframeRef.src);
+          u.searchParams.set('_r', Date.now().toString());
+          setDocContent(prev => (prev ? { ...prev, url: u.toString() } : prev));
+        } catch {
+          // Fallback: trigger a remount
+          setDocContent(prev => (prev ? { ...prev } : prev));
+        }
+      }
🧹 Nitpick comments (11)
apps/dashboard/.env (2)

8-8: Add docs base URL: OK. Verify framing/CORS and slash semantics.

  • Confirm docs origin is allowed to be iframed by the dashboard via CSP frame-ancestors/X-Frame-Options and CORS on the docs app.
  • Decide and document whether a trailing slash is allowed/required; ensure consumers normalize.

6-9: Fix dotenv-linter UnorderedKey warning by reordering keys.

Move NEXT_PUBLIC_STACK_DOCS_BASE_URL before NEXT_PUBLIC_STACK_EXTRA_REQUEST_HEADERS.

-NEXT_PUBLIC_STACK_EXTRA_REQUEST_HEADERS=# a list of extra request headers to add to all Stack Auth API requests, as a JSON record
-NEXT_PUBLIC_STACK_STRIPE_PUBLISHABLE_KEY=# enter your Stripe publishable key here
-NEXT_PUBLIC_STACK_DOCS_BASE_URL=https://docs.stack-auth.com
+NEXT_PUBLIC_STACK_DOCS_BASE_URL=https://docs.stack-auth.com
+NEXT_PUBLIC_STACK_EXTRA_REQUEST_HEADERS=# a list of extra request headers to add to all Stack Auth API requests, as a JSON record
+NEXT_PUBLIC_STACK_STRIPE_PUBLISHABLE_KEY=# enter your Stripe publishable key here
docs/src/components/platform-change-notifier.tsx (1)

13-15: Make platform regex robust for paths without a trailing slash.

Handle both /docs-embed/ and /docs-embed//.

-      const match = path.match(/\/docs-embed\/([^\/]+)\//);
+      const match = path.match(/\/docs-embed\/([^/]+)(?:\/|$)/);
docs/src/components/embedded-link-interceptor.tsx (3)

5-84: URL rewriting is well-hardened.

Preserves query/hash, handles roots, and normalizes relative paths. Consider also rewriting absolute same-origin doc links (e.g., https://docs.example.com/docs/...) as a nice-to-have.


100-101: Tiny readability nit.

Space around operator.

-      if (event.defaultPrevented || event.button !==0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
+      if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {

121-129: Optionally intercept absolute same-origin links.

Covers anchors that use fully-qualified URLs to the same docs host.

-      // Intercept internal links that need to be rewritten OR relative links
+      // Intercept internal links that need to be rewritten OR relative links
       if (href.startsWith('/docs/') || href.startsWith('/api/') || href.startsWith('/dashboard/') ||
           (!/^[a-zA-Z][a-zA-Z+\-.]*:/.test(href) && !href.startsWith('#'))) {
         event.preventDefault();
         const currentPath = window.location.pathname;
         const embeddedHref = getEmbeddedUrl(href, currentPath);
         // Navigate to the embedded version
         window.location.href = embeddedHref;
+      } else if (/^https?:\/\//.test(href)) {
+        try {
+          const u = new URL(href);
+          if (u.origin === window.location.origin &&
+              (u.pathname.startsWith('/docs/') || u.pathname.startsWith('/api/') || u.pathname.startsWith('/dashboard/'))) {
+            event.preventDefault();
+            const embeddedHref = getEmbeddedUrl(u.pathname + u.search + u.hash, window.location.pathname);
+            window.location.href = embeddedHref;
+          }
+        } catch {
+          /* ignore */
+        }
       }
apps/dashboard/.env.development (1)

2-2: Dev docs base URL: OK. Verify it matches the local docs server and is allowed by embed CSP.

  • Ensure the docs dev server actually runs on :8104.
  • Confirm the docs app’s CSP (frame-ancestors) and CORS allow embedding from the dashboard dev origin.
apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx (4)

45-57: Derive postMessage target from iframe.src (avoid window-origin fallback).

Use the resolved iframe.src; guard for empty src.

-  try {
-    const src = iframe.getAttribute("src") ?? "";
-    const targetOrigin = new URL(src, window.location.origin).origin;
+  try {
+    if (!iframe.src) return;
+    const targetOrigin = new URL(iframe.src, window.location.href).origin;
     iframe.contentWindow?.postMessage(
       { type: "TOGGLE_SIDEBAR", visible },
       targetOrigin
     )

104-121: Prefer ES6 Map over Record for key–value collections.

This follows the repo guideline and avoids accidental prototype key collisions.

-      const dashboardToDocsMap: Record<string, { path: string, title: string }> = {
-        'overview': { path: `${platform}/overview`, title: 'Stack Auth Overview' },
-        'users': { path: `${platform}/getting-started/users`, title: 'User Management' },
-        'auth-methods': { path: `${platform}/concepts/auth-providers`, title: 'Authentication Providers' },
-        'orgs-and-teams': { path: `${platform}/concepts/orgs-and-teams`, title: 'Teams & Organizations' },
-        'team-permissions': { path: `${platform}/concepts/permissions#team-permissions`, title: 'Team Permissions' },
-        'emails': { path: `${platform}/concepts/emails`, title: 'Emails' },
-        'domains': { path: `${platform}/getting-started/production#domains`, title: 'Domains' },
-        'webhooks': { path: `${platform}/concepts/webhooks`, title: 'Webhooks' },
-        'stack-auth-keys': { path: `${platform}/getting-started/setup#update-api-keys`, title: 'Stack Auth Keys' },
-        'project-settings': { path: `${platform}/getting-started/production#enabling-production-mode`, title: 'Project Configuration' },
-      };
-
-      const docMapping = dashboardToDocsMap[page];
+      const dashboardToDocsMap = new Map<string, { path: string; title: string }>([
+        ['overview', { path: `${platform}/overview`, title: 'Stack Auth Overview' }],
+        ['users', { path: `${platform}/getting-started/users`, title: 'User Management' }],
+        ['auth-methods', { path: `${platform}/concepts/auth-providers`, title: 'Authentication Providers' }],
+        ['orgs-and-teams', { path: `${platform}/concepts/orgs-and-teams`, title: 'Teams & Organizations' }],
+        ['team-permissions', { path: `${platform}/concepts/permissions#team-permissions`, title: 'Team Permissions' }],
+        ['emails', { path: `${platform}/concepts/emails`, title: 'Emails' }],
+        ['domains', { path: `${platform}/getting-started/production#domains`, title: 'Domains' }],
+        ['webhooks', { path: `${platform}/concepts/webhooks`, title: 'Webhooks' }],
+        ['stack-auth-keys', { path: `${platform}/getting-started/setup#update-api-keys`, title: 'Stack Auth Keys' }],
+        ['project-settings', { path: `${platform}/getting-started/production#enabling-production-mode`, title: 'Project Configuration' }],
+      ]);
+
+      const docMapping = dashboardToDocsMap.get(page) ?? dashboardToDocsMap.get('overview')!;

171-179: Gate debug logs in production.

Prevent noisy consoles in prod.

-          console.log('Debug mapping:', {
-            pathname,
-            normalizedPath: pathname.replace(/^\/projects\/[^/]+/, ''),
-            detectedPage: page,
-            platform: selectedPlatform
-          });
+          if (process.env.NODE_ENV !== 'production') {
+            console.log('Debug mapping:', {
+              pathname,
+              normalizedPath: pathname.replace(/^\/projects\/[^/]+/, ''),
+              detectedPage: page,
+              platform: selectedPlatform
+            });
+          }
           const content = getDocContentForPath(pathname, selectedDocType, selectedPlatform);
-          console.log('Loading docs:', { page, platform: selectedPlatform, url: content.url });
+          if (process.env.NODE_ENV !== 'production') {
+            console.log('Loading docs:', { page, platform: selectedPlatform, url: content.url });
+          }

469-470: Set iframe referrer policy.

Minimize cross-origin referrer leakage.

-              sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox"
+              sandbox="allow-same-origin allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox"
+              referrerPolicy="strict-origin-when-cross-origin"
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 2e01464 and 5760b90.

📒 Files selected for processing (10)
  • apps/dashboard/.env (1 hunks)
  • apps/dashboard/.env.development (1 hunks)
  • apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx (1 hunks)
  • apps/dashboard/src/lib/env.tsx (2 hunks)
  • docs/src/app/api-embed/layout.tsx (1 hunks)
  • docs/src/app/docs-embed/[[...slug]]/page.tsx (1 hunks)
  • docs/src/app/docs-embed/layout.tsx (1 hunks)
  • docs/src/app/global.css (1 hunks)
  • docs/src/components/embedded-link-interceptor.tsx (1 hunks)
  • docs/src/components/platform-change-notifier.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • docs/src/app/docs-embed/layout.tsx
  • docs/src/app/api-embed/layout.tsx
  • docs/src/app/docs-embed/[[...slug]]/page.tsx
  • docs/src/app/global.css
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value col 4020 lections

Files:

  • docs/src/components/platform-change-notifier.tsx
  • docs/src/components/embedded-link-interceptor.tsx
  • apps/dashboard/src/lib/env.tsx
  • apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

For blocking alerts and errors in UI, do not use toast notifications; use alerts instead

Files:

  • apps/dashboard/src/lib/env.tsx
  • apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Keep hover/click animations snappy; avoid pre-transition delays on hover and apply transitions after the action (e.g., fade-out on hover end)

Files:

  • apps/dashboard/src/lib/env.tsx
  • apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx
🧬 Code graph analysis (1)
apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx (2)
apps/dashboard/src/lib/env.tsx (1)
  • getPublicEnvVar (51-61)
packages/stack-shared/src/utils/urls.tsx (2)
  • url (278-280)
  • isLocalhost (199-205)
🪛 dotenv-linter (3.3.0)
apps/dashboard/.env

[warning] 8-8: [UnorderedKey] The NEXT_PUBLIC_STACK_DOCS_BASE_URL key should go before the NEXT_PUBLIC_STACK_EXTRA_REQUEST_HEADERS key

(UnorderedKey)

🪛 Biome (2.1.2)
apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx

[error] 316-316: src is assigned to itself.

This is where is assigned.

Self assignments have no effect and can be removed.

(lint/correctness/noSelfAssign)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: docker
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: docker
  • GitHub Check: Security Check
🔇 Additional comments (1)
apps/dashboard/src/lib/env.tsx (1)

10-10: Sentinel mapping for NEXT_PUBLIC_STACK_DOCS_BASE_URL verified present.

Comment on lines +209 to +215
// Verify origin for security - allow localhost in dev and configured docs URL
const isLocalhost = event.origin.includes('localhost') || event.origin.includes('127.0.0.1');
const expectedDocsOrigin = new URL(getDocsBaseUrl()).origin;
const isValidDocsOrigin = event.origin === expectedDocsOrigin;

if (!isLocalhost && !isValidDocsOrigin) return;

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Tighten postMessage origin check (drop substring localhost match).

Compare strictly to the iframe’s origin (or configured docs origin) to avoid spoofed origins.

-      // Verify origin for security - allow localhost in dev and configured docs URL
-      const isLocalhost = event.origin.includes('localhost') || event.origin.includes('127.0.0.1');
-      const expectedDocsOrigin = new URL(getDocsBaseUrl()).origin;
-      const isValidDocsOrigin = event.origin === expectedDocsOrigin;
-      
-      if (!isLocalhost && !isValidDocsOrigin) return;
+      // Verify origin strictly against the embedded docs origin
+      const expectedDocsOrigin =
+        (docContent?.url ? new URL(docContent.url) : new URL(getDocsBaseUrl())).origin;
+      if (event.origin !== expectedDocsOrigin) return;
📝 Committable suggestion

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

Suggested change
// Verify origin for security - allow localhost in dev and configured docs URL
const isLocalhost = event.origin.includes('localhost') || event.origin.includes('127.0.0.1');
const expectedDocsOrigin = new URL(getDocsBaseUrl()).origin;
const isValidDocsOrigin = event.origin === expectedDocsOrigin;
if (!isLocalhost && !isValidDocsOrigin) return;
// Verify origin strictly against the embedded docs origin
const expectedDocsOrigin =
(docContent?.url ? new URL(docContent.url) : new URL(getDocsBaseUrl())).origin;
if (event.origin !== expectedDocsOrigin) return;
🤖 Prompt for AI Agents
In apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx around
lines 209–215, the postMessage origin check currently uses substring matching
for localhost which is unsafe; replace it with a strict origin comparison: build
an allowedOrigins set containing the configured docs origin (new
URL(getDocsBaseUrl()).origin) and, if the running host is local, add the exact
window.location.origin (or explicitly provided allowed localhost origins from
env/config), then only accept the message if event.origin exactly equals one of
those allowedOrigins.

Comment on lines +229 to +236
// Update docContent to reflect the new URL without reloading the iframe
if (docContent && event.data.pathname) {
const newUrl = `${getDocsBaseUrl()}${event.data.pathname}`;
setDocContent({
...docContent,
url: newUrl
});
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Sanitize iframe-driven URL updates.

Only accept embed paths; prevents unintended navigation.

-          // Update docContent to reflect the new URL without reloading the iframe
-          if (docContent && event.data.pathname) {
-            const newUrl = `${getDocsBaseUrl()}${event.data.pathname}`;
+          // Update docContent to reflect the new URL without reloading the iframe
+          if (docContent && event.data.pathname) {
+            const p = String(event.data.pathname);
+            if (!/^\/(docs-embed|api-embed)\//.test(p)) return;
+            const newUrl = `${getDocsBaseUrl()}${p}`;
             setDocContent({
               ...docContent,
               url: newUrl
             });
           }
📝 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
// Update docContent to reflect the new URL without reloading the iframe
if (docContent && event.data.pathname) {
const newUrl = `${getDocsBaseUrl()}${event.data.pathname}`;
setDocContent({
...docContent,
url: newUrl
});
}
// Update docContent to reflect the new URL without reloading the iframe
if (docContent && event.data.pathname) {
const p = String(event.data.pathname);
// Only allow known embed routes to prevent unexpected navigation
if (!/^\/(docs-embed|api-embed)\//.test(p)) return;
const newUrl = `${getDocsBaseUrl()}${p}`;
setDocContent({
...docContent,
url: newUrl
});
}
🤖 Prompt for AI Agents
In apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx around
lines 229 to 236, the code updates iframe-driven URLs without validation;
restrict updates to only safe embed paths. Validate that event.data.pathname is
a string and matches a strict embed-path pattern (for example begins with
"/embed/" and matches a whitelist 
5B10
regex like ^/embed/[A-Za-z0-9/_-]*$), reject
any values containing protocol markers, "//", or ".."; only construct the newUrl
from getDocsBaseUrl() + pathname and call setDocContent when the pathname passes
validation; otherwise ignore the update (optionally log a warning).

Comment on lines +23 to +31
const targetOrigin = process.env.NODE_ENV === 'development' ? '*' : '*';

window.parent.postMessage(
{
type: 'PLATFORM_CHANGE',
platform,
pathname
},
targetOrigin
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Avoid wildcard targetOrigin; gate to parent origin and skip when not in an iframe.

Limit postMessage to the embedding origin and no-op if not framed.

-        // In development, send to any origin. In production, be more specific if needed
-        const targetOrigin = process.env.NODE_ENV === 'development' ? '*' : '*';
+        // Prefer the embedding parent origin; fallback to '*' only in dev.
+        const parentOrigin = (() => {
+          try {
+            return document.referrer ? new URL(document.referrer).origin : '';
+          } catch {
+            return '';
+          }
+        })();
+        const targetOrigin =
+          process.env.NODE_ENV === 'development' ? '*' : (parentOrigin || '*');
+        if (window.parent === window) return; // not embedded
         
         window.parent.postMessage(
-          { 
-            type: 'PLATFORM_CHANGE', 
+          {
+            source: 'stack-docs-embed',
+            type: 'PLATFORM_CHANGE',
             platform,
             pathname 
           },
           targetOrigin
         );
📝 Committable suggestion

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

Suggested change
const targetOrigin = process.env.NODE_ENV === 'development' ? '*' : '*';
window.parent.postMessage(
{
type: 'PLATFORM_CHANGE',
platform,
pathname
},
targetOrigin
// Prefer the embedding parent origin; fallback to '*' only in dev.
const parentOrigin = (() => {
try {
return document.referrer ? new URL(document.referrer).origin : '';
} catch {
return '';
}
})();
const targetOrigin =
process.env.NODE_ENV === 'development'
? '*'
: (parentOrigin || '*');
if (window.parent === window) return; // not embedded
window.parent.postMessage(
{
source: 'stack-docs-embed',
type: 'PLATFORM_CHANGE',
platform,
pathname
},
targetOrigin
);
🤖 Prompt for AI Agents
In docs/src/components/platform-change-notifier.tsx around lines 23 to 31, the
code uses a wildcard targetOrigin and always posts to window.parent; change it
to no-op when not inside an iframe (skip if window.parent === window), and
compute a safe parent origin instead of '*' by deriving it from
document.referrer (fallback to window.location.origin) using
URL(document.referrer).origin in a try/catch; if you cannot determine a valid
origin, do not postMessage, otherwise pass that origin as targetOrigin to
window.parent.postMessage.

Copy link
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: 1

🧹 Nitpick comments (2)
docs/next.config.mjs (2)

13-50: Refactor duplicate CORS header configuration.

The CORS headers for /dashboard/:path* and /dashboard-embed/:path* are identical. Extract this into a reusable constant to improve maintainability.

+const corsHeaders = [
+  {
+    key: 'Access-Control-Allow-Origin',
+    value: process.env.NEXT_PUBLIC_DASHBOARD_ORIGIN || 'http://localhost:8101',
+  },
+  {
+    key: 'Access-Control-Allow-Methods',
+    value: 'GET, POST, PUT, DELETE, OPTIONS',
+  },
+  {
+    key: 'Access-Control-Allow-Headers',
+    value: 'Content-Type, Authorization',
+  },
+];
+
 async headers() {
   return [
     {
-      // Allow CORS for dashboard routes to be accessed by the dashboard app
       source: '/dashboard/:path*',
-      headers: [
-        {
-          key: 'Access-Control-Allow-Origin',
-          value: 'http://localhost:8101', // Dashboard app origin
-        },
-        {
-          key: 'Access-Control-Allow-Methods',
-          value: 'GET, POST, PUT, DELETE, OPTIONS',
-        },
-        {
-          key: 'Access-Control-Allow-Headers',
-          value: 'Content-Type, Authorization',
-        },
-      ],
+      headers: corsHeaders,
     },
     {
-      // Allow CORS for embedded dashboard routes to be accessed by the dashboard app
       source: '/dashboard-embed/:path*',
-      headers: [
-        {
-          key: 'Access-Control-Allow-Origin',
-          value: 'http://localhost:8101', // Dashboard app origin
-        },
-        {
-          key: 'Access-Control-Allow-Methods',
-          value: 'GET, POST, PUT, DELETE, OPTIONS',
-        },
-        {
-          key: 'Access-Control-Allow-Headers',
-          value: 'Content-Type, Authorization',
-        },
-      ],
+      headers: corsHeaders,
     },
   ];
 },

24-24: Restrict CORS to GET, OPTIONS
No POST, PUT, or DELETE calls to /dashboard or /dashboard-embed were found in apps/dashboard, so you can safely remove them:

- value: 'GET, POST, PUT, DELETE, OPTIONS',
+ value: 'GET, OPTIONS',
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6306e59 and 26114c8.

📒 Files selected for processing (1)
  • docs/next.config.mjs (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: docker
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: docker
  • GitHub Check: Security Check

Comment on lines +12 to +51
5B10
async headers() {
return [
{
// Allow CORS for dashboard routes to be accessed by the dashboard app
source: '/dashboard/:path*',
headers: [
{
key: 'Access-Control-Allow-Origin',
value: 'http://localhost:8101', // Dashboard app origin
},
{
key: 'Access-Control-Allow-Methods',
value: 'GET, POST, PUT, DELETE, OPTIONS',
},
{
key: 'Access-Control-Allow-Headers',
value: 'Content-Type, Authorization',
},
],
},
{
// Allow CORS for embedded dashboard routes to be accessed by the dashboard app
source: '/dashboard-embed/:path*',
headers: [
{
key: 'Access-Control-Allow-Origin',
value: 'http://localhost:8101', // Dashboard app origin
},
{
key: 'Access-Control-Allow-Methods',
value: 'GET, POST, PUT, DELETE, OPTIONS',
},
{
key: 'Access-Control-Allow-Headers',
value: 'Content-Type, Authorization',
},
],
},
];
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Hardcoded localhost origin is a production security risk.

The CORS configuration only allows http://localhost:8101, which will block embedded docs access in production and staging environments. This creates a critical deployment gap.

Consider replacing the hardcoded origin with an environment variable:

+const dashboardOrigin = process.env.NEXT_PUBLIC_DASHBOARD_ORIGIN || 'http://localhost:8101';
+
 async headers() {
   return [
     {
       // Allow CORS for dashboard routes to be accessed by the dashboard app
       source: '/dashboard/:path*',
       headers: [
         {
           key: 'Access-Control-Allow-Origin',
-          value: 'http://localhost:8101', // Dashboard app origin
+          value: dashboardOrigin,
         },
         // ... other headers
       ],
     },
     {
       // Allow CORS for embedded dashboard routes to be accessed by the dashboard app
       source: '/dashboard-embed/:path*',
       headers: [
         {
           key: 'Access-Control-Allow-Origin',
-          value: 'http://localhost:8101', // Dashboard app origin
+          value: dashboardOrigin,
         },
         // ... other headers
       ],
     },
   ];
 },

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In docs/next.config.mjs around lines 12-51, the CORS headers hardcode
'http://localhost:8101'; replace that literal with a server-side environment
variable (e.g., process.env.DASHBOARD_ORIGIN) so staging/production can
configure allowed origins. Implement reading the env var at the top of the file,
provide a sensible default of 'http://localhost:8101' for local dev, and support
multiple origins by splitting a comma-separated env value and selecting the
incoming request Origin (or falling back to the default) before setting
Access-Control-Allow-Origin; keep the rest of the CORS headers unchanged.

}

// Fallback logic for when env var is not set
if (process.env.NODE_ENV === 'development' || window.location.hostname === 'localhost') {
Copy link

Choose a reason for hiding this comment

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

Suggested change
if (process.env.NODE_ENV === 'development' || window.location.hostname === 'localhost') {
if (process.env.NODE_ENV === 'development' || (typeof window !== 'undefined' && window.location.hostname === 'localhost')) {

The fallback logic uses window.location.hostname which is undefined during server-side rendering, causing a runtime error.

View Details

Analysis

SSR runtime error in getDocsBaseUrl() function due to unchecked window access

What fails: getDocsBaseUrl() function in unified-docs-widget.tsx at line 37 accesses window.location.hostname without checking if window exists, causing a ReferenceError during server-side rendering

How to reproduce:

# In server environment (SSR):
node -e "delete global.window; console.log(process.env.NODE_ENV === 'development' || window.location.hostname === 'localhost')"

Result: ReferenceError: window is not defined - This breaks Next.js SSR for any page using this component

Expected: Should safely check for window existence before accessing it, as confirmed by Next.js client components documentation which states client components are initially server-rendered and cannot access browser APIs like window during that phase

const { slug } = await params;
const page = dashboardSource.getPage(slug ?? []);

if (!page) redirect("/");
Copy link

Choose a reason for hiding this comment

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

Suggested change
if (!page) redirect("/");
if (!page) redirect("/dashboard-embed");

Same redirect issue as API embed - redirects to "/" instead of staying in embedded context.

View Details

Analysis

Dashboard embed redirects break embedded iframe experience

What fails: DashboardEmbedPage component in docs/src/app/dashboard-embed/[[...slug]]/page.tsx redirects to "/" when page not found, which loads the main docs site inside the iframe instead of staying in dashboard embed context

How to reproduce:

# Access non-existent dashboard embed page in iframe
curl -I http://localhost:3000/dashboard-embed/nonexistent-page

Result: Redirects to "/" (HTTP 307), causing iframe to display full docs site instead of dashboard content

Expected: Should redirect to "/dashboard-embed" to maintain embedded context, following the pattern established by docs-embed route which correctly redirects to "/docs-embed/next/overview"

Evidence: The docs-embed route demonstrates correct behavior by staying within its embed context when pages are not found

const { slug } = await params;
const page = apiSource.getPage(slug ?? []);

if (!page) redirect("/");
Copy link

Choose a reason for hiding this comment

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

The redirect destination "/" will break users trying to access non-existent API pages in embedded context.

View Details
📝 Patch Details
diff --git a/docs/src/app/api-embed/[[...slug]]/page.tsx b/docs/src/app/api-embed/[[...slug]]/page.tsx
index 10f9574d..ed90dd79 100644
--- a/docs/src/app/api-embed/[[...slug]]/page.tsx
+++ b/docs/src/app/api-embed/[[...slug]]/page.tsx
@@ -10,7 +10,7 @@ export default async function ApiEmbedPage({
   const { slug } = await params;
   const page = apiSource.getPage(slug ?? []);
 
-  if (!page) redirect("/");
+  if (!page) redirect("/api-embed");
 
   const MDX = page.data.body;
 
diff --git a/docs/src/app/api-embed/page.tsx b/docs/src/app/api-embed/page.tsx
new file mode 100644
index 00000000..ccbd1a79
--- /dev/null
+++ b/docs/src/app/api-embed/page.tsx
@@ -0,0 +1,28 @@
+import { getEmbeddedMDXComponents } from '@/mdx-components';
+import { apiSource } from 'lib/source';
+
+export default async function ApiEmbedIndexPage() {
+  // Get the API overview page as the default
+  const page = apiSource.getPage(['overview']);
+  
+  if (!page) {
+    return (
+      <div className="p-6 prose prose-neutral dark:prose-invert max-w-none overflow-x-hidden">
+        <div className="w-full">
+          <h1>API Documentation</h1>
+          <p>Welcome to the Stack Auth API documentation. Please select a specific API endpoint to view its documentation.</p>
+        </div>
+      </div>
+    );
+  }
+
+  const MDX = page.data.body;
+
+  return (
+    <div className="p-6 prose prose-neutral dark:prose-invert max-w-none overflow-x-hidden">
+      <div className="w-full">
+        <MDX components={getEmbeddedMDXComponents()} />
+      </div>
+    </div>
+  );
+}
\ No newline at end of file

Analysis

API embed route redirects to root instead of staying in embedded context

What fails: ApiEmbedPage in docs/src/app/api-embed/[[...slug]]/page.tsx redirects to "/" when no page is found, taking users out of the embedded API documentation context

How to reproduce:

  1. Navigate to /api-embed/nonexistent-page in embedded iframe
  2. Page not found triggers redirect to "/"
  3. User is taken to main docs site root instead of staying in API embed context

Result: Users lose embedded API documentation context and navigate to main site

Expected: Should redirect to /api-embed to maintain embedded context, consistent with docs-embed route pattern that redirects to /docs-embed/next/overview

Fix: Changed redirect destination from "/" to "/api-embed" and created root API embed page for proper fallback experience

Comment on lines +134 to +136
}
}).catch(() => {
// Network error or other issue - silently ignore
Copy link

Choose a reason for hiding this comment

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

Suggested change
}
}).catch(() => {
// Network error or other issue - silently ignore
} else {
// URL doesn't exist, fall back to original href
window.location.href = href;
}
}).catch(() => {
// Network error or other issue - fall back to original href
window.location.href = href;

The link interceptor prevents navigation when URL checking fails, creating broken links that don't work when clicked.

View Details

Analysis

Link interceptor blocks navigation when URL validation fails

What fails: EmbeddedLinkInterceptor.handleClick() calls preventDefault() then checkUrlExists(), but navigation is permanently blocked if URL check fails (returns false or throws error)

How to reproduce:

  1. Load any page with embedded layout (e.g., /docs-embed/some-page)
  2. Click on a link to /docs/non-existent-page or any URL that fails HEAD request validation
  3. Observe the link becomes completely non-functional

Result: Link appears clickable but does nothing when clicked. User gets no feedback or navigation.

Expected: Link should either navigate to embedded URL (if it exists) or fallback to original URL (if validation fails), ensuring users can always navigate even during network issues or false negatives from HEAD requests.

Root cause: Lines 130-137 in embedded-link-interceptor.tsx only handle the success case of checkUrlExists(), leaving failed checks with no navigation action.


window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, [selectedPlatform, docContent]);
Copy link

Choose a reason for hiding this comment

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

The postMessage handler useEffect has docContent in dependencies, causing it to re-register the event listener every time content changes.

View Details
📝 Patch Details
diff --git a/apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx b/apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx
index 7f8d8beb..224dc611 100644
--- a/apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx
+++ b/apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx
@@ -2,7 +2,7 @@
 
 import { ArrowLeft, BookOpen, ExternalLink, Loader2, Menu } from 'lucide-react';
 import { usePathname } from 'next/navigation';
-import { useEffect, useState } from 'react';
+import { useEffect, useRef, useState } from 'react';
 import { getPublicEnvVar } from '../../lib/env';
 
 type UnifiedDocsWidgetProps = {
@@ -152,6 +152,12 @@ export function UnifiedDocsWidget({ isActive }: UnifiedDocsWidgetProps) {
   const [iframeRef, setIframeRef] = useState<HTMLIFrameElement | null>(null);
   const [isSidebarVisible, setIsSidebarVisible] = useState(false);
   const [platformChangeSource, setPlatformChangeSource] = useState<'manual' | 'iframe'>('manual');
+  const docContentRef = useRef<DocContent | null>(null);
+
+  // Keep docContentRef current
+  useEffect(() => {
+    docContentRef.current = docContent;
+  }, [docContent]);
 
   // Load documentation when the component becomes active, doc type changes, platform changes, or pathname changes
   useEffect(() => {
@@ -227,10 +233,11 @@ export function UnifiedDocsWidget({ isActive }: UnifiedDocsWidgetProps) {
           setSelectedPlatform(detectedPlatform);
           
           // Update docContent to reflect the new URL without reloading the iframe
-          if (docContent && event.data.pathname) {
+          const currentDocContent = docContentRef.current;
+          if (currentDocContent && event.data.pathname) {
             const newUrl = `${getDocsBaseUrl()}${event.data.pathname}`;
             setDocContent({
-              ...docContent,
+              ...currentDocContent,
               url: newUrl
             });
           }
@@ -243,7 +250,7 @@ export function UnifiedDocsWidget({ isActive }: UnifiedDocsWidgetProps) {
 
     window.addEventListener('message', handleMessage);
     return () => window.removeEventListener('message', handleMessage);
-  }, [selectedPlatform, docContent]);
+  }, [selectedPlatform]);
 
   // Handle iframe load events
   const handleIframeLoad = (event: React.SyntheticEvent<HTMLIFrameElement>) => {

Analysis

Unnecessary event listener re-registration in postMessage useEffect

What fails: The postMessage useEffect in UnifiedDocsWidget (lines 207-246) includes docContent in dependencies, causing the event listener to be removed and re-added every time docContent changes.

How to reproduce:

  1. Open dashboard component with docs widget active
  2. Navigate between different dashboard pages (users → teams → emails)
  3. Each navigation triggers docContent state change
  4. Event listener gets re-registered on every change

Result: Performance degradation from unnecessary removeEventListener/addEventListener calls. During rapid navigation, brief moments exist where no listener is active.

Expected: Event listener should be registered once and 5B10 use current docContent via ref, following React's separation of events from effects pattern

Fix applied: Used useRef to store current docContent value, allowing handler to access latest state without including it in useEffect dependencies.

headers: [
{
key: 'Access-Control-Allow-Origin',
value: 'http://localhost:8101', // Dashboard app origin
Copy link

Choose a reason for hiding this comment

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

CORS headers are hardcoded to localhost:8101 which will break in production environments where the dashboard runs on different origins.

View Details
📝 Patch Details
diff --git a/docs/.env.development b/docs/.env.development
index b3d3207d..232b9049 100644
--- a/docs/.env.development
+++ b/docs/.env.development
@@ -1,6 +1,7 @@
 # Contains the credentials for the internal project of Stack's default development environment setup.
 # Do not use in a production environment, instead replace it with actual values gathered from https://app.stack-auth.com.
 NEXT_PUBLIC_STACK_API_URL=http://localhost:8102
+NEXT_PUBLIC_STACK_DASHBOARD_URL=http://localhost:8101
 NEXT_PUBLIC_STACK_PROJECT_ID=internal
 NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=this-publishable-client-key-is-for-local-development-only
 STACK_SECRET_SERVER_KEY=this-secret-server-key-is-for-local-development-only
diff --git a/docs/next.config.mjs b/docs/next.config.mjs
index c0ab4ac2..2bcfe92e 100644
--- a/docs/next.config.mjs
+++ b/docs/next.config.mjs
@@ -17,7 +17,7 @@ const config = {
         headers: [
           {
             key: 'Access-Control-Allow-Origin',
-            value: 'http://localhost:8101', // Dashboard app origin
+            value: process.env.NEXT_PUBLIC_STACK_DASHBOARD_URL || 'http://localhost:8101',
           },
           {
             key: 'Access-Control-Allow-Methods',
@@ -35,7 +35,7 @@ const config = {
         headers: [
           {
             key: 'Access-Control-Allow-Origin',
-            value: 'http://localhost:8101', // Dashboard app origin
+            value: process.env.NEXT_PUBLIC_STACK_DASHBOARD_URL || 'http://localhost:8101',
           },
           {
             key: 'Access-Control-Allow-Methods',
@@ -98,4 +98,3 @@ const config = {
 };
 
 export default withMDX(config);
-

Analysis

CORS configuration hardcoded to localhost:8101 prevents production dashboard embedding

What fails: The Next.js CORS headers in docs/next.config.mjs lines 20 and 38 are hardcoded to 'http://localhost:8101', preventing the production Stack Auth dashboard (https://app.stack-auth.com) from embedding docs pages via iframe

How to reproduce:

# Production dashboard at https://app.stack-auth.com tries to embed docs
curl -H "Origin: https://app.stack-auth.com" -I http://localhost:8104/dashboard/overview
# Returns no Access-Control-Allow-Origin header or localhost:8101 only

Result: Production dashboard cannot embed docs content due to CORS policy violation. Browser blocks cross-origin requests from https://app.stack-auth.com to docs site.

Expected: Should allow the production dashboard origin https://app.stack-auth.com per Stack Auth production documentation which shows dashboard at app.stack-auth.com

Context: The docs site has /dashboard-embed/ routes specifically designed for iframe embedding from the dashboard, with embedded layouts and link interceptors, confirming this is an intended cross-origin use case.

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.

2 participants
0