Skip to content

docs: sync doc site with main branch + fix visibility/auth bugs#230

Merged
seanhanca merged 3 commits into
mainfrom
feat/update-docs
Apr 2, 2026
Merged

docs: sync doc site with main branch + fix visibility/auth bugs#230
seanhanca merged 3 commits into
mainfrom
feat/update-docs

Conversation

@seanhanca
Copy link
Copy Markdown
Contributor

@seanhanca seanhanca commented Apr 2, 2026

Summary

  • Sync documentation site with current main branch implementation — adds comprehensive docs for Service Gateway, admin operations, authentication, teams API, and developer API
  • Fix admin visibility race condition in personalized plugins API (isAdmin resolved before visibility filtering)
  • Fix headless plugins bypassing visibility gate (hidden headless providers still load for non-admin users)
  • Require authentication on GET /api/v1/gw/admin/templates (was unauthenticated under an admin path)
  • Fix Playwright auth setup: correct title regex, skip admin setup when creds missing

New documentation pages

  • Service Gateway concept page + setup guide + API reference
  • Authentication guide (registration, login, OAuth, password reset, RBAC)
  • Admin operations guide (user management, plugin config, feedback, audit logs)
  • Developer API reference (API keys, AI models, projects, usage)
  • Teams API reference (team CRUD, members, roles, plugin installs)

Code fixes (from Copilot review)

  • personalized/route.ts: Derive headless plugins from publish-gated set without visibility gate; resolve admin status from token before filtering
  • gw/admin/templates/route.ts: Enforce auth on GET endpoint (consistent with POST)
  • auth.setup.ts: Fix title regex /NaaP//Livepeer|Dashboard/; skip admin setup when env vars missing

Test plan

  • All 10 personalized plugin unit tests pass (including new headless bypass test)
  • All 3 connector template visibility tests pass
  • Lint & TypeCheck CI passes
  • SDK Tests CI passes
  • Build CI passes
  • Verify new MDX pages render in preview deployment

Summary by CodeRabbit

  • New Features

    • Added error notification on public dashboard when data fails to load.
  • Documentation

    • Added comprehensive API reference guides for Developer API, Service Gateway, Teams, and Admin Operations.
    • Added Service Gateway concept documentation and end-to-end setup guide.
    • Updated architecture, authentication, and backend development guides.
    • Updated changelog with post-v0.1.0 platform capabilities and deprecated features.

@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented Apr 2, 2026

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

Project Deployment Actions Updated (UTC)
naap-platform Ready Ready Preview, Comment Apr 2, 2026 8:17pm

Request Review

@github-actions github-actions Bot added the size/XL Extra large PR (500+ lines) label Apr 2, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 2, 2026

⚠️ This PR is very large (2009 lines changed). Please split it into smaller, focused PRs if possible.

@github-actions github-actions Bot added the has-migration Includes database migration label Apr 2, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 2, 2026

🗃️ Database Migration Detected

This PR includes changes to Prisma schema files. Please ensure:

  • Migration is backward-compatible or a rollback plan exists
  • Data migration scripts are included if needed
  • Schema changes will be auto-applied to the preview database (Neon preview branch) during the Vercel preview deployment
  • Verify the preview deployment works correctly with the new schema
  • On merge to main, schema changes will auto-promote to production via prisma db push

Preview DB: This PR's Vercel preview deployment uses an isolated Neon database branch. Schema changes are applied automatically via prisma db push during the preview build. The preview branch is reset after each production deploy.

Requesting review from the core team: @livepeer/core

@github-actions github-actions Bot added scope/shell Shell app changes scope/packages Shared package changes and removed has-migration Includes database migration labels Apr 2, 2026
@seanhanca seanhanca requested a review from Copilot April 2, 2026 19:00
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 2, 2026

📝 Walkthrough

Walkthrough

This PR introduces Service Gateway infrastructure, plugin visibility refactoring, and authentication enhancements. It adds plugin name normalization for admin plugin configuration, refactors plugin availability logic to separate publish-gating from visibility filtering with special headless plugin handling, enforces session validation on template endpoints, displays dashboard errors on the public overview page, and significantly expands documentation across service gateway concepts, developer/teams/admin APIs, authentication workflows, and architecture updates.

Changes

Cohort / File(s) Summary
Plugin Name Resolution & Core Configuration
apps/web-next/src/app/api/v1/admin/plugins/core/route.ts
Added normalizePluginName() helper for case-insensitive, delimiter-insensitive plugin name matching. Updated PUT handler to resolve corePluginNames and hiddenPluginNames to actual DB package names before DB updates; changed previousCoreNames derivation to use isCore flags from all non-deprecated packages; gated visibility updates on resolved names.
Plugin Availability & Headless Extraction
apps/web-next/src/app/api/v1/base/plugins/personalized/route.ts, apps/web-next/src/app/api/v1/base/plugins/personalized/__tests__/personalized.test.ts
Introduced publish-gated publishedGlobalPlugins subset; moved headless plugin extraction earlier to derive from publish-gated plugins only, ensuring headless plugins bypass visibility gates even when non-admin visibility rules would hide them. Updated test fixtures to validate headless plugin inclusion and hidden non-headless exclusion.
Authentication & Template Access
apps/web-next/src/app/api/v1/gw/admin/templates/route.ts
Added token and session validation requirement to GET handler; returns unauthorized errors when token is missing or session validation fails; isAdmin flag now computed only after successful validation.
Dashboard Error Display
apps/web-next/src/app/page.tsx
Updated PublicOverviewPage to capture and conditionally render error from usePublicDashboard hook; displays destructive-styled notice when error is present indicating incomplete data.
Test Setup & Authentication
apps/web-next/tests/auth.setup.ts
Updated page title assertion from /NaaP/ to /Livepeer|Dashboard/; enhanced admin setup to create stub storage state when credentials missing; adjusted admin login flow with broader input selectors and navigation waits to /dashboard or /admin.
Service Gateway & Architecture Documentation
apps/web-next/src/content/docs/concepts/service-gateway.mdx, apps/web-next/src/content/docs/concepts/architecture.mdx
Added comprehensive Service Gateway documentation covering proxy pipeline, authentication modes, transformations, and multi-tenancy. Updated architecture diagram to show "Service Gateway" and Next.js API Route Handlers for production; increased Next.js API count from 46+ to 197+; replaced standalone "Express backends" reference with production routing details.
Database & Plugin System Architecture Documentation
apps/web-next/src/content/docs/concepts/database-architecture.mdx, apps/web-next/src/content/docs/concepts/plugin-system.mdx, apps/web-next/src/content/docs/concepts/dashboard-data-architecture.mdx
Updated database schema count from "60+ models across 8 schemas" to "70+ models across 9 schemas"; added plugin_service_gateway schema; replaced Prisma schema path documentation with schema identifier format; added BFF Facade Layer section documenting /api/v1/dashboard/* endpoints backed by Data Facade; added Neon preview database branch information.
API Reference Documentation
apps/web-next/src/content/docs/api-reference/developer-api.mdx, apps/web-next/src/content/docs/api-reference/service-gateway-api.mdx, apps/web-next/src/content/docs/api-reference/teams-api.mdx
Added three new API reference pages documenting Developer API (keys, models, projects, usage), Service Gateway (public/admin routes, proxy pipeline, error shapes), and Teams API (team/member/plugin management, role permissions).
Operational Guides
apps/web-next/src/content/docs/guides/admin-operations.mdx, apps/web-next/src/content/docs/guides/authentication.mdx, apps/web-next/src/content/docs/guides/service-gateway-setup.mdx, apps/web-next/src/content/docs/guides/backend-development.mdx
Added Admin Operations guide documenting user management, core plugin configuration, feedback/audit endpoints. Added Authentication guide covering registration/login/OAuth/password reset/session management plus security features. Added Service Gateway Setup guide with end-to-end connector configuration workflow. Updated Backend Development guide to clarify local Express vs. production Next.js routing and recommend Service Gateway for API proxying.
Changelog & Example Updates
apps/web-next/src/content/docs/community/changelog.mdx, apps/web-next/src/content/docs/examples/database-plugin.mdx
Added "Post-v0.1.0 Updates" section documenting Service Gateway features, Agent Tool Interface, new connectors, dashboard/metrics integrations, auth hardening, and developer experience improvements; marked several plugins as deprecated. Updated example schema identifier from plugin_gateway to plugin_service_gateway.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

scope/backend, size/M

Suggested reviewers

  • eliteprox
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly summarizes the main changes: documentation syncing and fixes for visibility/authentication bugs.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/update-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
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR substantially updates the docs site to match main (Service Gateway, admin, auth, teams, developer API), and also introduces product/runtime changes to support a public “Network Overview” landing page plus admin-controlled visibility for plugins and gateway connector templates.

Changes:

  • Add visibleToUsers visibility flags to plugin packages and gateway connector templates, and apply visibility filtering across relevant APIs/admin screens.
  • Introduce a public (unauthenticated) network overview at / using a new usePublicDashboard hook and a shared OverviewContent renderer for both public and authenticated dashboards.
  • Add/refresh multiple MDX docs pages (Service Gateway, Authentication, Admin Ops, Teams API, Developer API, architecture/database updates, changelog).

Reviewed changes

Copilot reviewed 37 out of 37 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
packages/database/prisma/schema.prisma Adds visibleToUsers fields for plugin packages and gateway connector templates.
apps/web-next/tests/auth.setup.ts Adds admin storage-state setup for Playwright and adjusts auth setup flow.
apps/web-next/tests/admin-visibility.spec.ts New E2E coverage for admin “Launch Experience” visibility toggles.
apps/web-next/src/middleware.ts Makes / public for unauthenticated users (no forced redirect to /login).
apps/web-next/src/lib/gateway/connector-templates.ts Adds visibleOnly filtering option when loading connector templates.
apps/web-next/src/lib/gateway/tests/connector-templates-visibility.test.ts Unit tests for template visibility filtering behavior.
apps/web-next/src/hooks/usePublicDashboard.ts New hook to fetch dashboard data from REST endpoints for the public overview.
apps/web-next/src/contexts/auth-context.tsx Adjusts logout flow to navigate to / after clearing auth storage.
apps/web-next/src/content/docs/guides/service-gateway-setup.mdx New guide page for creating and publishing Service Gateway connectors.
apps/web-next/src/content/docs/guides/backend-development.mdx Updates backend dev guidance (Express in dev; Next.js route handlers in prod) and references Service Gateway.
apps/web-next/src/content/docs/guides/authentication.mdx New end-to-end authentication lifecycle documentation.
apps/web-next/src/content/docs/guides/admin-operations.mdx New admin operations API documentation (users, core plugins, feedback, audit, templates).
apps/web-next/src/content/docs/examples/database-plugin.mdx Updates schema list reference to plugin_service_gateway.
apps/web-next/src/content/docs/concepts/service-gateway.mdx New concept page describing the Service Gateway architecture/pipeline.
apps/web-next/src/content/docs/concepts/plugin-system.mdx Fixes database.schema documentation to use schema name instead of file path.
apps/web-next/src/content/docs/concepts/database-architecture.mdx Updates schema/model counts; adds plugin_service_gateway and Neon preview DB notes.
apps/web-next/src/content/docs/concepts/dashboard-data-architecture.mdx Adds BFF/data facade layer explanation and route list.
apps/web-next/src/content/docs/concepts/architecture.mdx Updates architecture diagram and production deployment notes (API routes, Neon, Service Gateway).
apps/web-next/src/content/docs/community/changelog.mdx Adds post-v0.1.0 feature series notes and removes deprecated plugin references.
apps/web-next/src/content/docs/api-reference/teams-api.mdx New Teams API reference page.
apps/web-next/src/content/docs/api-reference/service-gateway-api.mdx New Service Gateway REST API reference page.
apps/web-next/src/content/docs/api-reference/developer-api.mdx New Developer API reference page (keys, models, usage, billing providers).
apps/web-next/src/components/layout/public-top-bar.tsx New top bar UI for the public overview page.
apps/web-next/src/components/dashboard/overview-content.tsx New shared dashboard renderer used by both public and authenticated overview pages.
apps/web-next/src/components/dashboard/auth-cta-banner.tsx New CTA banner for unauthenticated users on the public overview.
apps/web-next/src/app/page.tsx Replaces marketing landing page with the public “Network Overview” dashboard.
apps/web-next/src/app/api/v1/registry/packages/route.ts Applies plugin package visibility filtering for non-admin users.
apps/web-next/src/app/api/v1/gw/admin/templates/route.ts Applies connector template visibility filtering based on system-admin status.
apps/web-next/src/app/api/v1/base/plugins/personalized/route.ts Publish/visibility gating for personalized/global plugin lists and core plugin resolution.
apps/web-next/src/app/api/v1/base/plugins/personalized/tests/personalized.test.ts Unit tests for publish/visibility gating logic.
apps/web-next/src/app/api/v1/admin/templates/route.ts New system-admin endpoint to view/update gateway template visibility.
apps/web-next/src/app/api/v1/admin/plugins/core/route.ts Extends admin core plugins endpoint to also manage visibleToUsers.
apps/web-next/src/app/(dashboard)/dashboard/page.tsx Refactors dashboard overview page to delegate rendering to OverviewContent.
apps/web-next/src/app/(dashboard)/admin/plugins/page.tsx Expands admin UI to manage both plugin visibility/core settings and template visibility (tabs).
apps/web-next/src/app/(auth)/register/register-form.tsx Adds “Back to Overview” link.
apps/web-next/src/app/(auth)/login/login-form.tsx Adds “Back to Overview” link.
apps/web-next/src/tests/page.test.tsx Updates unit tests to match the new public overview page composition.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 50 to 71
// Publish-gate: only show plugins that have a published PluginPackage.
// This prevents plugins synced from stale branches (or preview deploys on
// a shared DB) from appearing in the UI when their package is unlisted/draft.
// Also fetch visibleToUsers for admin-controlled visibility filtering.
const publishedPackages = await prisma.pluginPackage.findMany({
where: { publishStatus: 'published', deprecated: false },
select: { name: true, isCore: true, visibleToUsers: true },
});
const publishedNames = new Set(
publishedPackages.map((p) => normalizePluginName(p.name))
);
const hiddenNames = new Set(
publishedPackages
.filter((p) => !p.visibleToUsers)
.map((p) => normalizePluginName(p.name))
);
const visibleGlobalPlugins = globalPlugins.filter((p) => {
const normalized = normalizePluginName(p.name);
if (!publishedNames.has(normalized)) return false;
if (!isAdmin && hiddenNames.has(normalized)) return false;
return true;
});
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

visibleGlobalPlugins is filtered using isAdmin before isAdmin is resolved from the requested user’s roles (the role lookup happens later). If the request supplies userId (or the user is looked up by address) but no auth token, admin users will still be treated as non-admin here and will incorrectly have visibleToUsers=false plugins filtered out. Consider determining isAdmin before computing visibleGlobalPlugins, or recomputing the visibility-filtered plugin list after the role lookup.

Copilot uses AI. Check for mistakes.
Comment on lines 82 to 87
// Headless plugins (no routes) are background data providers that must always
// be loaded regardless of context — they register event bus handlers the shell
// and dashboard rely on. We extract them once and append to every response.
const headlessPlugins = globalPlugins.filter(
const headlessPlugins = visibleGlobalPlugins.filter(
(p) => !p.routes || (Array.isArray(p.routes) && (p.routes as string[]).length === 0),
);
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The comment says headless plugins “must always be loaded regardless of context,” but headlessPlugins is now derived from visibleGlobalPlugins, which applies the visibleToUsers gate. That means a headless provider hidden from non-admin users would no longer be loaded for them, potentially breaking event-bus-backed features that rely on those providers. If headless plugins must truly always load, derive them from the published set without applying the visibility gate (or explicitly exempt headless plugins from visibleToUsers).

Copilot uses AI. Check for mistakes.
Comment on lines 21 to 33
/** List all available connector templates with basic metadata. */
export async function GET() {
const templates = await loadConnectorTemplates();
export async function GET(request: NextRequest) {
let isAdmin = false;
const token = getAuthToken(request);
if (token) {
const sessionUser = await validateSession(token);
if (sessionUser) {
isAdmin = sessionUser.roles?.includes('system:admin') ?? false;
}
}

const templates = await loadConnectorTemplates({ visibleOnly: !isAdmin });

Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

GET /api/v1/gw/admin/templates no longer enforces authentication (it only optionally inspects a token to decide whether to include hidden templates). Given this is under /gw/admin/* and POST uses getAdminContext() (which requires a valid session and scope), this makes the route inconsistent and allows unauthenticated callers to enumerate templates. Consider requiring a valid token/session (e.g., via getAdminContext() or explicit 401/403) and then applying visibleOnly based on system-admin status.

Copilot uses AI. Check for mistakes.
Comment on lines +460 to +463
publishStatus String @default("draft")
visibleToUsers Boolean @default(true)
publisherId String?
publisher Publisher? @relation("PackagePublisher", fields: [publisherId], references: [id])
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The PR title/description describe a docs-only sync, but this change introduces a new persisted field (visibleToUsers) on core data models, which is a functional product change requiring migrations and potentially rollout coordination. Please update the PR title/description (or split into separate PRs) so reviewers and release notes reflect the non-doc changes.

Copilot uses AI. Check for mistakes.
Comment thread apps/web-next/tests/auth.setup.ts Outdated
Comment on lines 14 to 21
@@ -19,3 +20,26 @@ setup('authenticate', async ({ page }) => {
// Save the storage state
await page.context().storageState({ path: authFile });
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The setup asserts toHaveTitle(/NaaP/), but the app’s default metadata title is Livepeer Dashboard (see apps/web-next/src/app/layout.tsx). This will make the Playwright setup fail even when the page loads successfully. Consider asserting a stable UI element instead of the document title, or update the title regex to match the actual configured title.

Copilot uses AI. Check for mistakes.
Comment on lines +24 to +45
/**
* Admin authentication setup.
* Uses ADMIN_EMAIL / ADMIN_PASSWORD env vars to log in as an admin user.
* Falls back to regular auth state if admin credentials are not configured.
*/
setup('authenticate as admin', async ({ page }) => {
const adminEmail = process.env.ADMIN_EMAIL;
const adminPassword = process.env.ADMIN_PASSWORD;

if (adminEmail && adminPassword) {
await page.goto('/login');
await page.fill('input[name="email"], input[type="email"]', adminEmail);
await page.fill('input[name="password"], input[type="password"]', adminPassword);
await page.click('button[type="submit"]');
await page.waitForURL(/\/(dashboard|admin)/, { timeout: 15000 });
} else {
await page.goto('/');
await expect(page).toHaveTitle(/NaaP/);
}

await page.context().storageState({ path: adminAuthFile });
});
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

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

The “authenticate as admin” setup writes playwright/.auth/admin.json even when ADMIN_EMAIL / ADMIN_PASSWORD are not set (it just visits / and saves an unauthenticated storage state). Tests that test.use({ storageState: 'playwright/.auth/admin.json' }) will then fail by redirecting to /login. Either make this setup fail/skip when admin creds are missing, or ensure CI always provides admin credentials (and document that requirement).

Copilot uses AI. Check for mistakes.
@seanhanca seanhanca enabled auto-merge (squash) April 2, 2026 19:06
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

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

⚠️ Outside diff range comments (1)
apps/web-next/src/content/docs/examples/database-plugin.mdx (1)

53-65: ⚠️ Potential issue | 🟡 Minor

Documentation schema list is incomplete and potentially misleading.

The example shows plugin_service_gateway replacing plugin_gateway, but per packages/database/prisma/schema.prisma (line 27), both schemas exist:

schemas = ["public", "plugin_community", "plugin_wallet", "plugin_dashboard", 
           "plugin_daydream", "plugin_gateway", "plugin_capacity", 
           "plugin_developer_api", "plugin_service_gateway"]
  • plugin_gateway → used by Gateway, GatewayOrchestratorConnection, etc. (legacy gateway manager plugin)
  • plugin_service_gateway → used by ServiceConnector, GatewayApiKey, etc. (new service gateway plugin)

The documentation should include both schemas to match the actual Prisma schema:

   schemas  = [
     "public",
     "plugin_community",
     "plugin_wallet",
     "plugin_dashboard",
     "plugin_daydream",
-    "plugin_service_gateway",
+    "plugin_gateway",
     "plugin_capacity",
     "plugin_developer_api",
+    "plugin_service_gateway",
     "plugin_tracker"              // ← New
   ]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/content/docs/examples/database-plugin.mdx` around lines 53
- 65, The example's schemas array is missing the legacy plugin_gateway entry;
update the schemas array (the "schemas" value shown in the example) to include
both "plugin_gateway" and "plugin_service_gateway" alongside the other entries
so it matches the actual Prisma schema, keeping the same ordering/format as the
existing list and ensuring both plugin_gateway and plugin_service_gateway are
present.
🧹 Nitpick comments (9)
apps/web-next/tests/auth.setup.ts (1)

33-42: Fallback creates potentially misleading auth state.

When ADMIN_EMAIL/ADMIN_PASSWORD are not set, the fallback visits / and saves that state to admin.json. Tests using this storage state will run as unauthenticated, which may cause unexpected failures in admin-specific tests like admin-visibility.spec.ts.

Consider failing explicitly or logging a warning when admin credentials are missing:

   if (adminEmail && adminPassword) {
     await page.goto('/login');
     await page.fill('input[name="email"], input[type="email"]', adminEmail);
     await page.fill('input[name="password"], input[type="password"]', adminPassword);
     await page.click('button[type="submit"]');
     await page.waitForURL(/\/(dashboard|admin)/, { timeout: 15000 });
   } else {
-    await page.goto('/');
-    await expect(page).toHaveTitle(/NaaP/);
+    console.warn('ADMIN_EMAIL/ADMIN_PASSWORD not set — admin tests may fail');
+    // Skip saving admin auth state entirely, or save an empty/marker state
+    await page.goto('/');
+    await expect(page).toHaveTitle(/NaaP/);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/tests/auth.setup.ts` around lines 33 - 42, In
tests/auth.setup.ts the else branch that runs when adminEmail/adminPassword are
missing currently navigates to '/' and results in an unauthenticated admin.json
being saved; change that behavior to fail fast or at least warn and skip
persisting admin credentials: update the else branch that calls page.goto('/') /
expect(page).toHaveTitle(...) to instead throw a clear Error (or at minimum
console.warn) indicating missing ADMIN_EMAIL/ADMIN_PASSWORD and ensure no
admin.json (the storage-state write for admin) is created; reference the
adminEmail/adminPassword checks and the admin.json storage-state write logic so
the test harness does not save an unauthenticated admin state.
apps/web-next/src/__tests__/page.test.tsx (1)

41-53: Make the localStorage mock behavior closer to real storage.

length and key() are static right now, which can mask regressions if page logic starts relying on them.

Optional test-mock improvement
   Object.defineProperty(window, 'localStorage', {
     value: {
       getItem: vi.fn((key: string) => storage[key] ?? null),
       setItem: vi.fn((key: string, val: string) => { storage[key] = val; }),
       removeItem: vi.fn((key: string) => { delete storage[key]; }),
       clear: vi.fn(() => { Object.keys(storage).forEach(k => delete storage[k]); }),
-      length: 0,
-      key: vi.fn(() => null),
+      get length() {
+        return Object.keys(storage).length;
+      },
+      key: vi.fn((index: number) => Object.keys(storage)[index] ?? null),
     },
+    configurable: true,
     writable: true,
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/__tests__/page.test.tsx` around lines 41 - 53, The
localStorage mock in beforeEach currently uses a static length and a key() that
always returns null; update the mock so length reflects current storage keys and
key(index) returns the key at that index (like real localStorage), and ensure
setItem, removeItem, and clear update that length accordingly; keep using the
existing storage object and vi.fn wrappers around getItem, setItem, removeItem,
clear, length (as a getter) and key to mirror real localStorage behavior.
apps/web-next/src/hooks/usePublicDashboard.ts (1)

148-168: Polling implementation is sound but could benefit from a minor enhancement.

The polling correctly starts only after initial fetch (hasFetched) and cleans up on dependency changes. Silent error handling (line 162-164) is appropriate for non-critical polling.

One consideration: if the user is on a slow connection, rapid polling attempts could stack up since the interval doesn't wait for the previous fetch to complete. For robustness, consider using setTimeout chaining instead of setInterval.

💡 Optional: Use setTimeout chaining to prevent overlapping polls
   // Job feed polling — starts after the initial fetch completes (hasFetched is reactive)
   useEffect(() => {
     if (skip || !hasFetched || !jobFeedPollInterval || jobFeedPollInterval <= 0) return;

-    const id = setInterval(async () => {
+    let timeoutId: ReturnType<typeof setTimeout>;
+    const poll = async () => {
       try {
         const result = await fetchJson<{ streams: JobFeedEntry[]; queryFailed?: boolean }>(`${API}/job-feed`);
         if (mountedRef.current && result) {
           setData(prev => ({
             ...prev,
             jobs: result.streams ?? [],
             jobFeedConnected: !result.queryFailed,
           }));
         }
       } catch {
         // polling failure is non-critical; next tick will retry
       }
-    }, jobFeedPollInterval);
+      if (mountedRef.current) {
+        timeoutId = setTimeout(poll, jobFeedPollInterval);
+      }
+    };
+    timeoutId = setTimeout(poll, jobFeedPollInterval);

-    return () => clearInterval(id);
+    return () => clearTimeout(timeoutId);
   }, [skip, hasFetched, jobFeedPollInterval]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/hooks/usePublicDashboard.ts` around lines 148 - 168, The
current polling in useEffect uses setInterval which can cause overlapping
fetches on slow connections; replace it with a setTimeout-chained async poll
loop: inside the useEffect (the same dependency list: skip, hasFetched,
jobFeedPollInterval) create an async function poll() that returns early if skip,
!hasFetched, or a mounted flag is false, performs the fetchJson call and updates
state via setData (same jobs/jobFeedConnected logic), then schedules the next
poll with setTimeout(poll, jobFeedPollInterval); store the timeout id and clear
it on cleanup and set a mountedRef/aborted flag so in-flight results are
ignored, ensuring no concurrent fetches when jobFeedPollInterval is shorter than
request time.
apps/web-next/src/app/api/v1/admin/plugins/core/route.ts (1)

113-129: Visibility update order is correct but consider edge case documentation.

The two-step approach (reset all to visible, then hide specified) within the transaction is correct. However, note that passing an empty hiddenPluginNames array will reset all plugins to visible. If this is intentional (e.g., "unhide everything"), the behavior is fine. If not, you may want to skip the reset when hiddenPluginNames.length === 0.

Also, there's no validation that hiddenPluginNames contains valid strings—malformed input (e.g., [null, 123]) would pass the Array.isArray check.

💡 Optional: Add input validation for hiddenPluginNames elements
     // Update visibility if hiddenPluginNames is provided
     if (Array.isArray(hiddenPluginNames)) {
+      const validHiddenNames = hiddenPluginNames.filter(
+        (n): n is string => typeof n === 'string' && n.length > 0
+      );
       txOps.push(
         prisma.pluginPackage.updateMany({
           where: { deprecated: false },
           data: { visibleToUsers: true },
         }),
       );
-      if (hiddenPluginNames.length > 0) {
+      if (validHiddenNames.length > 0) {
         txOps.push(
           prisma.pluginPackage.updateMany({
-            where: { name: { in: hiddenPluginNames } },
+            where: { name: { in: validHiddenNames } },
             data: { visibleToUsers: false },
           }),
         );
       }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/app/api/v1/admin/plugins/core/route.ts` around lines 113 -
129, The current transaction logic uses hiddenPluginNames to first reset all
pluginPackage.visibleToUsers to true then hide those in hiddenPluginNames;
calling this with an empty array will unhide everything and malformed elements
like null or numbers will pass Array.isArray; update the logic in the route
handler to (1) only perform the global reset (prisma.pluginPackage.updateMany
via txOps) when hiddenPluginNames.length > 0 unless the explicit “unhide all”
behavior is intended, and (2) validate that every element of hiddenPluginNames
is a non-empty string (reject the request or sanitize) before pushing the second
prisma.pluginPackage.updateMany that sets visibleToUsers: false for names in
hiddenPluginNames; reference the hiddenPluginNames variable, txOps array, and
prisma.pluginPackage.updateMany calls when making these changes.
apps/web-next/tests/admin-visibility.spec.ts (2)

112-119: Replace waitForTimeout with deterministic waits.

Using waitForTimeout(2000) (line 113) is flaky—templates may load faster or slower depending on conditions. Consider waiting for a specific element or network idle state instead.

♻️ Suggested improvement
       // Click Templates tab
       const templatesTab = page.getByRole('tab', { name: /Templates/i });
       await templatesTab.click();

-      // Wait for templates to load or show empty state
-      await page.waitForTimeout(2000);
+      // Wait for templates to load or show empty state
+      await Promise.race([
+        page.waitForSelector('[aria-label*="Toggle visibility"]', { timeout: 10000 }),
+        page.getByText(/No gateway templates found/i).waitFor({ timeout: 10000 }),
+      ]).catch(() => {}); // Either condition is acceptable
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/tests/admin-visibility.spec.ts` around lines 112 - 119, Replace
the flaky sleep in the test by waiting deterministically for the templates or
the empty-state element: remove page.waitForTimeout(2000) and instead wait for
either the '[aria-label*="Toggle visibility"]' locator to appear (use
page.locator(...).first().waitFor or locator.count() via page.waitForFunction)
or for the "No gateway templates found" element via page.getByText(...).waitFor;
alternatively use page.waitForLoadState('networkidle') before checking counts,
then evaluate hasTemplates and hasEmptyState—update the assertions to rely on
these deterministic waits in the test that currently uses page.waitForTimeout,
the locator '[aria-label*="Toggle visibility"]', and getByText(/No gateway
templates found/i).

155-158: Replace search debounce timeout with element assertion.

Using waitForTimeout(500) assumes the debounce timing. Consider waiting for the "No plugins match" text to appear directly.

♻️ Suggested improvement
       // Type a search query that should filter results
       await searchInput.fill('zzz-nonexistent-plugin');
-      await page.waitForTimeout(500);

       // Should show "No plugins match your search"
       await expect(
         page.getByText(/No plugins match your search/i)
-      ).toBeVisible();
+      ).toBeVisible({ timeout: 5000 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/tests/admin-visibility.spec.ts` around lines 155 - 158, Replace
the hard-coded debounce sleep after searchInput.fill('zzz-nonexistent-plugin')
with a deterministic assertion that waits for the "No plugins match" message to
appear; specifically remove the page.waitForTimeout(500) and instead wait on the
page locator for the "No plugins match" text (e.g., using page.waitForSelector
or the test runner's expect on page.locator('text=No plugins match') or similar)
so the test no longer depends on timing and reliably asserts the empty-results
state after calling searchInput.fill.
apps/web-next/src/app/api/v1/admin/templates/route.ts (1)

64-69: Add validation for array element types.

The code checks Array.isArray(hiddenTemplateIds) but doesn't validate that each element is a string. Malformed payloads like { hiddenTemplateIds: [123, null, {}] } would pass validation and could cause unexpected behavior in the Prisma query.

Proposed validation improvement
   const body = await request.json();
   const { hiddenTemplateIds } = body as { hiddenTemplateIds: string[] };

-  if (!Array.isArray(hiddenTemplateIds)) {
+  if (!Array.isArray(hiddenTemplateIds) || !hiddenTemplateIds.every((id) => typeof id === 'string')) {
     return errors.badRequest('hiddenTemplateIds must be an array of template IDs');
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/app/api/v1/admin/templates/route.ts` around lines 64 - 69,
Validate that hiddenTemplateIds is an array of strings by checking each
element’s type (e.g., Array.isArray(hiddenTemplateIds) &&
hiddenTemplateIds.every(id => typeof id === 'string' && id.length > 0)); if any
element is not a string (or is empty) return errors.badRequest with a clear
message. Update the validation around request.json() / hiddenTemplateIds in
route.ts before using it in the Prisma query so only a sanitized string[] is
passed to the database operation.
apps/web-next/src/content/docs/community/changelog.mdx (1)

49-49: Minor: Consider hyphenating "rate-limiting" when used as an adjective.

In "Password reset flow with rate limiting", "rate limiting" functions as a noun phrase and is acceptable. However, in "rate limiting" at line 50 ("Auth endpoint protection against brute-force attacks"), consistency with "brute-force" (which is hyphenated) could be improved.

This is a minor stylistic observation and can be deferred.

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

In `@apps/web-next/src/content/docs/community/changelog.mdx` at line 49, Update
the changelog text to use consistent hyphenation: change "rate limiting" to
"rate-limiting" when it modifies another noun (e.g., in "Password reset flow
with rate-limiting" or similar phrases in
apps/web-next/src/content/docs/community/changelog.mdx) so it matches the
hyphenation style used for "brute-force" and keeps adjective phrases consistent
across entries.
apps/web-next/src/components/dashboard/overview-content.tsx (1)

1113-1121: Remove unnecessary optional chaining on fetch().

The fetch()?.then() pattern with optional chaining is unusual since fetch() always returns a Promise (never undefined). This doesn't cause bugs but is misleading.

Cleaner fetch pattern
-    fetch('/api/v1/network/capacity')
-      ?.then((res) => (res.ok ? res.json() : null))
-      ?.then((body: { capacityByPipelineModel?: Record<string, number> } | null) => {
+    fetch('/api/v1/network/capacity')
+      .then((res) => (res.ok ? res.json() : null))
+      .then((body: { capacityByPipelineModel?: Record<string, number> } | null) => {

Same pattern applies to lines 1132-1138 and 1146-1152.

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

In `@apps/web-next/src/components/dashboard/overview-content.tsx` around lines
1113 - 1121, The fetch calls use unnecessary optional chaining
(fetch()?.then(...).catch(...)) even though fetch always returns a Promise;
remove the `?` before `.then` and `.catch` in the network capacity fetch block
(the block that calls fetch('/api/v1/network/capacity') and updates
setNetCapacity and lastFetchedNetCapacityKeyRef.current while checking
cancelled) and apply the same change to the two other similar fetch blocks in
this file that follow the same pattern; keep the existing response handling,
null checks, cancelled flag logic, and the return cleanup as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/web-next/src/app/api/v1/base/plugins/personalized/__tests__/personalized.test.ts`:
- Around line 10-46: The test duplicates the filtering logic
(normalizePluginName and applyFilters) from the real handler, so extract that
logic into a shared helper used by both the route and the test or invoke the
real route handler with mocked Prisma data instead; specifically, move
normalizePluginName and the filtering behavior into a reusable function (e.g.,
exported filterPublishedPlugins) and update the route (route.ts) to call that
helper, then have this test import and use that helper (or replace the test body
to call the route handler with a mocked Prisma response) so the test validates
the actual production logic rather than a local copy.

In `@apps/web-next/src/app/api/v1/base/plugins/personalized/route.ts`:
- Around line 61-71: Normalize the admin-provided plugin name arrays before
running Prisma updates: call normalizePluginName on each element of
corePluginNames and hiddenPluginNames (the same normalization used in the
personalized route) and use those normalized arrays in the updateMany where
clauses (the WHERE { name: { in: [...] } } used in the admin handlers) so the
names match the DB format and the updates to visibility/core status succeed.

In `@apps/web-next/src/components/layout/public-top-bar.tsx`:
- Around line 10-19: The SVG logo in the PublicTopBar component is decorative
and should be hidden from assistive tech; update the <svg> element in
public-top-bar.tsx (the inline SVG inside the PublicTopBar component) to include
aria-hidden="true" and also add focusable="false" (for IE/SVG keyboard focus)
and/or role="img" only if it's semantic — otherwise remove role — so screen
readers ignore it; ensure you modify the exact <svg> tag shown in the diff.

In `@apps/web-next/src/content/docs/api-reference/developer-api.mdx`:
- Around line 9-23: The overview line in the Developer API docs currently says
"All endpoints require an authenticated session (JWT)" but the Billing Providers
endpoint `GET /api/v1/billing-providers` is public; change the phrasing in the
Developer API overview to something like "Most endpoints require an
authenticated session (JWT); some, such as `GET /api/v1/billing-providers`, are
public" or add a parenthetical exception calling out `GET
/api/v1/billing-providers` so the Developer API overview and the Billing
Providers section are consistent.

In `@apps/web-next/src/content/docs/api-reference/service-gateway-api.mdx`:
- Around line 315-318: The docs for GET /api/v1/gw/admin/templates overstates
what is returned; update the documentation text to document the visibility
filter: explain that non-admin callers only receive templates where
visibleToUsers = true while admin callers see all templates including hidden
ones, and remove or qualify the absolute claim "Returns the 19 pre-built
connector templates" (e.g., state "returns up to 19 pre-built templates; admins
see all, non-admins only those with visibleToUsers = true"). Also mention the
visibleToUsers field as the toggle used by the service.

In `@apps/web-next/src/content/docs/concepts/architecture.mdx`:
- Line 40: The page is inconsistent about the backend model: update the
"Vertical Slicing" section (the bullet referencing "Express API server") and the
other occurrences around the "Express API server" phrase (also at ~119-125) to
explicitly state that Express servers are used for local development only (ports
4001-4012) and that in production the same API logic runs as Next.js API route
handlers; ensure the language near the sentence "Local development uses
standalone Express backends on ports 4001-4012..." and the "Vertical Slicing"
bullet both reflect this single model so the doc no longer implies Express is
used in production.

In `@apps/web-next/src/content/docs/guides/admin-operations.mdx`:
- Around line 82-90: The docs in admin-operations.mdx show a "plugins" array but
the actual POST handler in route.ts expects "corePluginNames" and
"hiddenPluginNames"; update the documentation request body to replace the
"plugins" array with a JSON example using "corePluginNames":
["plugin-name-1","plugin-name-2"] and optional "hiddenPluginNames":
["plugin-to-hide-1"], and add brief bullet descriptions for both fields
(corePluginNames = plugins auto-installed for all users, hiddenPluginNames =
plugins hidden from non-admins); remove the old "plugins" example to avoid
mismatch with the POST handler.

In `@apps/web-next/src/content/docs/guides/authentication.mdx`:
- Around line 199-207: The table currently lists short paths (`POST
/auth/register`, `POST /auth/login`, `POST /auth/forgot-password`) while the
rest of the doc uses full API prefixes; update each table entry to the full
paths (`POST /api/v1/auth/register`, `POST /api/v1/auth/login`, `POST
/api/v1/auth/forgot-password`) so they match the documented `/api/v1/auth/...`
routes and keep the "Rate-limited requests receive a `429 Too Many Requests`
response." line unchanged.

In `@apps/web-next/tests/admin-visibility.spec.ts`:
- Line 10: The admin auth setup can yield a session without the required
system:admin role, causing admin/plugins/page.tsx to redirect to /dashboard and
flake admin-visibility.spec.ts; update auth.setup.ts (where
ADMIN_EMAIL/ADMIN_PASSWORD are used) to verify the authenticated user's roles by
calling the user info endpoint (or decoding the token) and assert presence of
"system:admin", failing the setup if missing, or alternatively add a role-check
helper at the top of admin-visibility.spec.ts that fetches /api/me (or the same
user-info call) and throws a clear error if the session in
playwright/.auth/admin.json lacks "system:admin" so tests fail-fast with a
meaningful message.

---

Outside diff comments:
In `@apps/web-next/src/content/docs/examples/database-plugin.mdx`:
- Around line 53-65: The example's schemas array is missing the legacy
plugin_gateway entry; update the schemas array (the "schemas" value shown in the
example) to include both "plugin_gateway" and "plugin_service_gateway" alongside
the other entries so it matches the actual Prisma schema, keeping the same
ordering/format as the existing list and ensuring both plugin_gateway and
plugin_service_gateway are present.

---

Nitpick comments:
In `@apps/web-next/src/__tests__/page.test.tsx`:
- Around line 41-53: The localStorage mock in beforeEach currently uses a static
length and a key() that always returns null; update the mock so length reflects
current storage keys and key(index) returns the key at that index (like real
localStorage), and ensure setItem, removeItem, and clear update that length
accordingly; keep using the existing storage object and vi.fn wrappers around
getItem, setItem, removeItem, clear, length (as a getter) and key to mirror real
localStorage behavior.

In `@apps/web-next/src/app/api/v1/admin/plugins/core/route.ts`:
- Around line 113-129: The current transaction logic uses hiddenPluginNames to
first reset all pluginPackage.visibleToUsers to true then hide those in
hiddenPluginNames; calling this with an empty array will unhide everything and
malformed elements like null or numbers will pass Array.isArray; update the
logic in the route handler to (1) only perform the global reset
(prisma.pluginPackage.updateMany via txOps) when hiddenPluginNames.length > 0
unless the explicit “unhide all” behavior is intended, and (2) validate that
every element of hiddenPluginNames is a non-empty string (reject the request or
sanitize) before pushing the second prisma.pluginPackage.updateMany that sets
visibleToUsers: false for names in hiddenPluginNames; reference the
hiddenPluginNames variable, txOps array, and prisma.pluginPackage.updateMany
calls when making these changes.

In `@apps/web-next/src/app/api/v1/admin/templates/route.ts`:
- Around line 64-69: Validate that hiddenTemplateIds is an array of strings by
checking each element’s type (e.g., Array.isArray(hiddenTemplateIds) &&
hiddenTemplateIds.every(id => typeof id === 'string' && id.length > 0)); if any
element is not a string (or is empty) return errors.badRequest with a clear
message. Update the validation around request.json() / hiddenTemplateIds in
route.ts before using it in the Prisma query so only a sanitized string[] is
passed to the database operation.

In `@apps/web-next/src/components/dashboard/overview-content.tsx`:
- Around line 1113-1121: The fetch calls use unnecessary optional chaining
(fetch()?.then(...).catch(...)) even though fetch always returns a Promise;
remove the `?` before `.then` and `.catch` in the network capacity fetch block
(the block that calls fetch('/api/v1/network/capacity') and updates
setNetCapacity and lastFetchedNetCapacityKeyRef.current while checking
cancelled) and apply the same change to the two other similar fetch blocks in
this file that follow the same pattern; keep the existing response handling,
null checks, cancelled flag logic, and the return cleanup as-is.

In `@apps/web-next/src/content/docs/community/changelog.mdx`:
- Line 49: Update the changelog text to use consistent hyphenation: change "rate
limiting" to "rate-limiting" when it modifies another noun (e.g., in "Password
reset flow with rate-limiting" or similar phrases in
apps/web-next/src/content/docs/community/changelog.mdx) so it matches the
hyphenation style used for "brute-force" and keeps adjective phrases consistent
across entries.

In `@apps/web-next/src/hooks/usePublicDashboard.ts`:
- Around line 148-168: The current polling in useEffect uses setInterval which
can cause overlapping fetches on slow connections; replace it with a
setTimeout-chained async poll loop: inside the useEffect (the same dependency
list: skip, hasFetched, jobFeedPollInterval) create an async function poll()
that returns early if skip, !hasFetched, or a mounted flag is false, performs
the fetchJson call and updates state via setData (same jobs/jobFeedConnected
logic), then schedules the next poll with setTimeout(poll, jobFeedPollInterval);
store the timeout id and clear it on cleanup and set a mountedRef/aborted flag
so in-flight results are ignored, ensuring no concurrent fetches when
jobFeedPollInterval is shorter than request time.

In `@apps/web-next/tests/admin-visibility.spec.ts`:
- Around line 112-119: Replace the flaky sleep in the test by waiting
deterministically for the templates or the empty-state element: remove
page.waitForTimeout(2000) and instead wait for either the '[aria-label*="Toggle
visibility"]' locator to appear (use page.locator(...).first().waitFor or
locator.count() via page.waitForFunction) or for the "No gateway templates
found" element via page.getByText(...).waitFor; alternatively use
page.waitForLoadState('networkidle') before checking counts, then evaluate
hasTemplates and hasEmptyState—update the assertions to rely on these
deterministic waits in the test that currently uses page.waitForTimeout, the
locator '[aria-label*="Toggle visibility"]', and getByText(/No gateway templates
found/i).
- Around line 155-158: Replace the hard-coded debounce sleep after
searchInput.fill('zzz-nonexistent-plugin') with a deterministic assertion that
waits for the "No plugins match" message to appear; specifically remove the
page.waitForTimeout(500) and instead wait on the page locator for the "No
plugins match" text (e.g., using page.waitForSelector or the test runner's
expect on page.locator('text=No plugins match') or similar) so the test no
longer depends on timing and reliably asserts the empty-results state after
calling searchInput.fill.

In `@apps/web-next/tests/auth.setup.ts`:
- Around line 33-42: In tests/auth.setup.ts the else branch that runs when
adminEmail/adminPassword are missing currently navigates to '/' and results in
an unauthenticated admin.json being saved; change that behavior to fail fast or
at least warn and skip persisting admin credentials: update the else branch that
calls page.goto('/') / expect(page).toHaveTitle(...) to instead throw a clear
Error (or at minimum console.warn) indicating missing ADMIN_EMAIL/ADMIN_PASSWORD
and ensure no admin.json (the storage-state write for admin) is created;
reference the adminEmail/adminPassword checks and the admin.json storage-state
write logic so the test harness does not save an unauthenticated admin state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 35eeae60-3435-4eb6-beeb-421cf8d31126

📥 Commits

Reviewing files that changed from the base of the PR and between 6b877ff and a1ac479.

📒 Files selected for processing (37)
  • apps/web-next/src/__tests__/page.test.tsx
  • apps/web-next/src/app/(auth)/login/login-form.tsx
  • apps/web-next/src/app/(auth)/register/register-form.tsx
  • apps/web-next/src/app/(dashboard)/admin/plugins/page.tsx
  • apps/web-next/src/app/(dashboard)/dashboard/page.tsx
  • apps/web-next/src/app/api/v1/admin/plugins/core/route.ts
  • apps/web-next/src/app/api/v1/admin/templates/route.ts
  • apps/web-next/src/app/api/v1/base/plugins/personalized/__tests__/personalized.test.ts
  • apps/web-next/src/app/api/v1/base/plugins/personalized/route.ts
  • apps/web-next/src/app/api/v1/gw/admin/templates/route.ts
  • apps/web-next/src/app/api/v1/registry/packages/route.ts
  • apps/web-next/src/app/page.tsx
  • apps/web-next/src/components/dashboard/auth-cta-banner.tsx
  • apps/web-next/src/components/dashboard/overview-content.tsx
  • apps/web-next/src/components/layout/public-top-bar.tsx
  • apps/web-next/src/content/docs/api-reference/developer-api.mdx
  • apps/web-next/src/content/docs/api-reference/service-gateway-api.mdx
  • apps/web-next/src/content/docs/api-reference/teams-api.mdx
  • apps/web-next/src/content/docs/community/changelog.mdx
  • apps/web-next/src/content/docs/concepts/architecture.mdx
  • apps/web-next/src/content/docs/concepts/dashboard-data-architecture.mdx
  • apps/web-next/src/content/docs/concepts/database-architecture.mdx
  • apps/web-next/src/content/docs/concepts/plugin-system.mdx
  • apps/web-next/src/content/docs/concepts/service-gateway.mdx
  • apps/web-next/src/content/docs/examples/database-plugin.mdx
  • apps/web-next/src/content/docs/guides/admin-operations.mdx
  • apps/web-next/src/content/docs/guides/authentication.mdx
  • apps/web-next/src/content/docs/guides/backend-development.mdx
  • apps/web-next/src/content/docs/guides/service-gateway-setup.mdx
  • apps/web-next/src/contexts/auth-context.tsx
  • apps/web-next/src/hooks/usePublicDashboard.ts
  • apps/web-next/src/lib/gateway/__tests__/connector-templates-visibility.test.ts
  • apps/web-next/src/lib/gateway/connector-templates.ts
  • apps/web-next/src/middleware.ts
  • apps/web-next/tests/admin-visibility.spec.ts
  • apps/web-next/tests/auth.setup.ts
  • packages/database/prisma/schema.prisma

Comment thread apps/web-next/src/app/api/v1/base/plugins/personalized/route.ts Outdated
Comment on lines +10 to +19
<svg viewBox="115 0 596 90" fill="none" xmlns="http://www.w3.org/2000/svg" className="h-4 w-auto text-foreground">
<path d="M118.899 88.6863V0.97998H135.921V73.6405H185.815V88.6863H118.899Z" fill="currentColor"/>
<path d="M195.932 88.6863V0.97998H212.954V88.6863H195.932Z" fill="currentColor"/>
<path d="M291.653 0.97998H310.34L277.221 88.6863H255.142L221.283 0.97998H240.34L266.551 70.9493L291.653 0.97998Z" fill="currentColor"/>
<path d="M319.038 88.6863V52.5316H336.06V37.121H319.038V0.97998H385.955V16.0258H336.06V37.121H378.369V52.5316H336.06V73.6405H387.25V88.6863H319.038Z" fill="currentColor"/>
<path d="M400.019 88.6863V0.97998H439.798C457.005 0.97998 468.23 9.63853 468.23 26.9229C468.23 42.2786 457.005 52.6235 439.798 52.6235H417.041V88.6863H400.019ZM417.041 37.0306H437.886C446.521 37.0306 451.146 32.8877 451.146 26.7406C451.146 20.1235 446.521 16.0258 437.886 16.0258H417.041V37.0306Z" fill="currentColor"/>
<path d="M479.889 88.6863V52.5316H496.911V37.121H479.889V0.97998H546.805V16.0258H496.911V37.121H539.219V52.5316H496.911V73.6405H548.1V88.6863H479.889Z" fill="currentColor"/>
<path d="M560.869 88.6863V52.5316H577.891V37.121H560.869V0.97998H627.785V16.0258H577.891V37.121H620.2V52.5316H577.891V73.6405H629.081V88.6863H560.869Z" fill="currentColor"/>
<path d="M641.85 88.6863V0.97998H682.925C698.488 0.983166 710.061 8.54418 710.061 22.8274C710.061 33.708 705.127 40.3254 695.013 44.0563C704.202 44.0563 708.766 48.2153 708.766 56.4722V88.6863H691.744V60.6923C691.744 54.3927 689.894 52.5578 683.541 52.5578H658.872V88.6863H641.85ZM658.872 37.0884H677.867C687.797 37.0884 692.977 33.7995 692.977 26.616C692.977 19.4325 687.982 16.0258 677.867 16.0258H658.872V37.0884Z" fill="currentColor"/>
</svg>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hide decorative SVG from assistive tech.

On Line 10, this logo appears decorative; mark it aria-hidden to avoid noisy screen reader output.

Suggested fix
-          <svg viewBox="115 0 596 90" fill="none" xmlns="http://www.w3.org/2000/svg" className="h-4 w-auto text-foreground">
+          <svg
+            viewBox="115 0 596 90"
+            fill="none"
+            xmlns="http://www.w3.org/2000/svg"
+            className="h-4 w-auto text-foreground"
+            aria-hidden="true"
+            focusable="false"
+          >
📝 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
<svg viewBox="115 0 596 90" fill="none" xmlns="http://www.w3.org/2000/svg" className="h-4 w-auto text-foreground">
<path d="M118.899 88.6863V0.97998H135.921V73.6405H185.815V88.6863H118.899Z" fill="currentColor"/>
<path d="M195.932 88.6863V0.97998H212.954V88.6863H195.932Z" fill="currentColor"/>
<path d="M291.653 0.97998H310.34L277.221 88.6863H255.142L221.283 0.97998H240.34L266.551 70.9493L291.653 0.97998Z" fill="currentColor"/>
<path d="M319.038 88.6863V52.5316H336.06V37.121H319.038V0.97998H385.955V16.0258H336.06V37.121H378.369V52.5316H336.06V73.6405H387.25V88.6863H319.038Z" fill="currentColor"/>
<path d="M400.019 88.6863V0.97998H439.798C457.005 0.97998 468.23 9.63853 468.23 26.9229C468.23 42.2786 457.005 52.6235 439.798 52.6235H417.041V88.6863H400.019ZM417.041 37.0306H437.886C446.521 37.0306 451.146 32.8877 451.146 26.7406C451.146 20.1235 446.521 16.0258 437.886 16.0258H417.041V37.0306Z" fill="currentColor"/>
<path d="M479.889 88.6863V52.5316H496.911V37.121H479.889V0.97998H546.805V16.0258H496.911V37.121H539.219V52.5316H496.911V73.6405H548.1V88.6863H479.889Z" fill="currentColor"/>
<path d="M560.869 88.6863V52.5316H577.891V37.121H560.869V0.97998H627.785V16.0258H577.891V37.121H620.2V52.5316H577.891V73.6405H629.081V88.6863H560.869Z" fill="currentColor"/>
<path d="M641.85 88.6863V0.97998H682.925C698.488 0.983166 710.061 8.54418 710.061 22.8274C710.061 33.708 705.127 40.3254 695.013 44.0563C704.202 44.0563 708.766 48.2153 708.766 56.4722V88.6863H691.744V60.6923C691.744 54.3927 689.894 52.5578 683.541 52.5578H658.872V88.6863H641.85ZM658.872 37.0884H677.867C687.797 37.0884 692.977 33.7995 692.977 26.616C692.977 19.4325 687.982 16.0258 677.867 16.0258H658.872V37.0884Z" fill="currentColor"/>
</svg>
<svg
viewBox="115 0 596 90"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className="h-4 w-auto text-foreground"
aria-hidden="true"
focusable="false"
>
<path d="M118.899 88.6863V0.97998H135.921V73.6405H185.815V88.6863H118.899Z" fill="currentColor"/>
<path d="M195.932 88.6863V0.97998H212.954V88.6863H195.932Z" fill="currentColor"/>
<path d="M291.653 0.97998H310.34L277.221 88.6863H255.142L221.283 0.97998H240.34L266.551 70.9493L291.653 0.97998Z" fill="currentColor"/>
<path d="M319.038 88.6863V52.5316H336.06V37.121H319.038V0.97998H385.955V16.0258H336.06V37.121H378.369V52.5316H336.06V73.6405H387.25V88.6863H319.038Z" fill="currentColor"/>
<path d="M400.019 88.6863V0.97998H439.798C457.005 0.97998 468.23 9.63853 468.23 26.9229C468.23 42.2786 457.005 52.6235 439.798 52.6235H417.041V88.6863H400.019ZM417.041 37.0306H437.886C446.521 37.0306 451.146 32.8877 451.146 26.7406C451.146 20.1235 446.521 16.0258 437.886 16.0258H417.041V37.0306Z" fill="currentColor"/>
<path d="M479.889 88.6863V52.5316H496.911V37.121H479.889V0.97998H546.805V16.0258H496.911V37.121H539.219V52.5316H496.911V73.6405H548.1V88.6863H479.889Z" fill="currentColor"/>
<path d="M560.869 88.6863V52.5316H577.891V37.121H560.869V0.97998H627.785V16.0258H577.891V37.121H620.2V52.5316H577.891V73.6405H629.081V88.6863H560.869Z" fill="currentColor"/>
<path d="M641.85 88.6863V0.97998H682.925C698.488 0.983166 710.061 8.54418 710.061 22.8274C710.061 33.708 705.127 40.3254 695.013 44.0563C704.202 44.0563 708.766 48.2153 708.766 56.4722V88.6863H691.744V60.6923C691.744 54.3927 689.894 52.5578 683.541 52.5578H658.872V88.6863H641.85ZM658.872 37.0884H677.867C687.797 37.0884 692.977 33.7995 692.977 26.616C692.977 19.4325 687.982 16.0258 677.867 16.0258H658.872V37.0884Z" fill="currentColor"/>
</svg>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/components/layout/public-top-bar.tsx` around lines 10 - 19,
The SVG logo in the PublicTopBar component is decorative and should be hidden
from assistive tech; update the <svg> element in public-top-bar.tsx (the inline
SVG inside the PublicTopBar component) to include aria-hidden="true" and also
add focusable="false" (for IE/SVG keyboard focus) and/or role="img" only if it's
semantic — otherwise remove role — so screen readers ignore it; ensure you
modify the exact <svg> tag shown in the diff.

Comment on lines +9 to +23
The Developer API provides endpoints for developers to manage API keys, browse available AI models, organize projects, and track usage. Keys are linked to billing providers through an OAuth flow.

All endpoints require an authenticated session (JWT).

---

## Billing Providers

### List Providers

```
GET /api/v1/billing-providers
```

Returns the catalog of enabled billing providers. No authentication required.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Don't say every Developer API endpoint requires JWT here.

The overview says all endpoints require an authenticated session, but the Billing Providers section immediately below documents GET /api/v1/billing-providers as public. Reword this to “most endpoints” or call out the exception up front.

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

In `@apps/web-next/src/content/docs/api-reference/developer-api.mdx` around lines
9 - 23, The overview line in the Developer API docs currently says "All
endpoints require an authenticated session (JWT)" but the Billing Providers
endpoint `GET /api/v1/billing-providers` is public; change the phrasing in the
Developer API overview to something like "Most endpoints require an
authenticated session (JWT); some, such as `GET /api/v1/billing-providers`, are
public" or add a parenthetical exception calling out `GET
/api/v1/billing-providers` so the Developer API overview and the Billing
Providers section are consistent.

Comment on lines +315 to +318
GET /api/v1/gw/admin/templates — List available connector templates
```

Returns the 19 pre-built connector templates (OpenAI, Stripe, ClickHouse, etc.) that can be used to quickly create new connector instances.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Document the visibility filter on templates.

/api/v1/gw/admin/templates does not always return the full catalog anymore: non-admin callers only receive templates with visibleToUsers = true, while admins see hidden ones too. “Returns the 19 pre-built connector templates” over-promises the response for standard users.

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

In `@apps/web-next/src/content/docs/api-reference/service-gateway-api.mdx` around
lines 315 - 318, The docs for GET /api/v1/gw/admin/templates overstates what is
returned; update the documentation text to document the visibility filter:
explain that non-admin callers only receive templates where visibleToUsers =
true while admin callers see all templates including hidden ones, and remove or
qualify the absolute claim "Returns the 19 pre-built connector templates" (e.g.,
state "returns up to 19 pre-built templates; admins see all, non-admins only
those with visibleToUsers = true"). Also mention the visibleToUsers field as the
toggle used by the service.

Comment thread apps/web-next/src/content/docs/concepts/architecture.mdx
Comment thread apps/web-next/src/content/docs/guides/admin-operations.mdx
Comment thread apps/web-next/src/content/docs/guides/authentication.mdx
* Requires admin authentication (uses playwright/.auth/admin.json storage state).
*/

test.use({ storageState: 'playwright/.auth/admin.json' });
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if auth.setup.ts verifies admin role
rg -n -A5 'system:admin' apps/web-next/tests/auth.setup.ts

# Check the admin page redirect logic
rg -n -B2 -A5 "router.push.*dashboard" apps/web-next/src/app/\(dashboard\)/admin/plugins/page.tsx

Repository: livepeer/naap

Length of output: 215


🏁 Script executed:

# Read the full auth.setup.ts file to understand what it does
cat -n apps/web-next/tests/auth.setup.ts

Repository: livepeer/naap

Length of output: 1882


🏁 Script executed:

# Check the admin page component to see where isAdmin comes from and how it's set
cat -n apps/web-next/src/app/\(dashboard\)/admin/plugins/page.tsx | head -100

Repository: livepeer/naap

Length of output: 3497


Test reliability concern: Admin auth setup doesn't verify required role.

The admin auth setup (auth.setup.ts lines 29-45) does not verify that the authenticated user has the system:admin role. If ADMIN_EMAIL/ADMIN_PASSWORD credentials don't correspond to a user with this role, or if these env vars are missing (causing a fallback to the unauthenticated homepage), the admin plugins page will redirect to /dashboard (see admin/plugins/page.tsx lines 71-75), causing tests to fail or timeout.

Consider verifying the role in the auth setup or adding a role check at the start of these tests.

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

In `@apps/web-next/tests/admin-visibility.spec.ts` at line 10, The admin auth
setup can yield a session without the required system:admin role, causing
admin/plugins/page.tsx to redirect to /dashboard and flake
admin-visibility.spec.ts; update auth.setup.ts (where ADMIN_EMAIL/ADMIN_PASSWORD
are used) to verify the authenticated user's roles by calling the user info
endpoint (or decoding the token) and assert presence of "system:admin", failing
the setup if missing, or alternatively add a role-check helper at the top of
admin-visibility.spec.ts that fetches /api/me (or the same user-info call) and
throws a clear error if the session in playwright/.auth/admin.json lacks
"system:admin" so tests fail-fast with a meaningful message.

Comment thread apps/web-next/src/app/page.tsx
@seanhanca seanhanca changed the title docs: sync doc site with main branch — Service Gateway, admin, auth, teams, developer API docs: sync doc site with main branch + fix visibility/auth bugs Apr 2, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/web-next/tests/auth.setup.ts (1)

26-29: Consider creating an empty storage state on skip to prevent downstream failures.

When credentials are missing, the setup skips without creating admin.json. Any test file using this storage state will crash with a file-not-found error rather than being skipped. This makes CI failures confusing when admin credentials aren't configured.

♻️ Proposed fix: create placeholder file on skip
   if (!adminEmail || !adminPassword) {
+    // Create an empty storage state to prevent file-not-found errors.
+    // Tests should check for valid auth and skip if needed.
+    await page.context().storageState({ path: adminAuthFile });
     setup.skip();
     return;
   }

Alternatively, tests consuming this state should guard themselves:

// In admin-visibility.spec.ts
import * as fs from 'fs';

test.beforeAll(async () => {
  if (!fs.existsSync('playwright/.auth/admin.json')) {
    test.skip();
  }
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/tests/auth.setup.ts` around lines 26 - 29, When adminEmail or
adminPassword are missing and the code calls setup.skip(), also create an empty
placeholder storage state file so downstream tests that read the auth state
don't crash; inside the same conditional branch that checks
adminEmail/adminPassword (where setup.skip() is invoked) create the directory if
needed and write an empty JSON auth file (e.g., 'playwright/.auth/admin.json')
so tests can either skip or detect the missing credentials safely. Ensure this
logic lives in the auth.setup.ts flow that contains the adminEmail/adminPassword
check and setup.skip() call.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web-next/tests/auth.setup.ts`:
- Around line 22-37: The admin-auth setup (setup('authenticate as admin')) may
skip and never create adminAuthFile, causing admin-visibility.spec.ts (which
uses test.use({ storageState: 'playwright/.auth/admin.json' })) to throw a
file-not-found error; fix by adding a pre-check in admin-visibility.spec.ts
(e.g., in test.beforeAll or at top-level) that checks for the existence of the
storage file referenced ('playwright/.auth/admin.json' or the adminAuthFile
symbol) and calls test.skip() for the whole suite if missing, or alternatively
modify the setup('authenticate as admin') implementation to create a minimal
placeholder adminAuthFile when skipping so the test.use loading succeeds.

---

Nitpick comments:
In `@apps/web-next/tests/auth.setup.ts`:
- Around line 26-29: When adminEmail or adminPassword are missing and the code
calls setup.skip(), also create an empty placeholder storage state file so
downstream tests that read the auth state don't crash; inside the same
conditional branch that checks adminEmail/adminPassword (where setup.skip() is
invoked) create the directory if needed and write an empty JSON auth file (e.g.,
'playwright/.auth/admin.json') so tests can either skip or detect the missing
credentials safely. Ensure this logic lives in the auth.setup.ts flow that
contains the adminEmail/adminPassword check and setup.skip() call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: f26904c0-3424-44a2-90fe-b985167abc6b

📥 Commits

Reviewing files that changed from the base of the PR and between a1ac479 and 2d51afb.

📒 Files selected for processing (4)
  • apps/web-next/src/app/api/v1/base/plugins/personalized/__tests__/personalized.test.ts
  • apps/web-next/src/app/api/v1/base/plugins/personalized/route.ts
  • apps/web-next/src/app/api/v1/gw/admin/templates/route.ts
  • apps/web-next/tests/auth.setup.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/web-next/src/app/api/v1/base/plugins/personalized/route.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/web-next/src/app/api/v1/gw/admin/templates/route.ts
  • apps/web-next/src/app/api/v1/base/plugins/personalized/tests/personalized.test.ts

Comment thread apps/web-next/tests/auth.setup.ts
…teams, developer API

Close major documentation gaps between the doc site and features on main:

Phase 1 — Fix stale content:
- Remove deprecated plugins (Gateway Manager, Orchestrator Manager, Network Analytics)
  from changelog and add post-v0.1.0 feature entries
- Fix database.schema inconsistency in plugin-system.mdx (file path → schema name)
- Update architecture diagram to reflect Next.js API routes in production, add Neon
  preview DB branches, update route count to 197+

Phase 2 — New Service Gateway documentation (3 pages):
- concepts/service-gateway.mdx: proxy pipeline, auth modes, transforms, multi-tenancy,
  19 built-in connectors, agent/MCP integration, database schema
- guides/service-gateway-setup.mdx: step-by-step connector creation and publishing
- api-reference/service-gateway-api.mdx: full REST reference for all 41 gateway routes

Phase 3 — New platform feature pages (4 pages):
- guides/admin-operations.mdx: user mgmt, core plugin config, feedback, audit
- api-reference/developer-api.mdx: API keys, AI models, billing providers, OAuth flow
- guides/authentication.mdx: registration, email verification, OAuth, password reset,
  rate limiting, RBAC
- api-reference/teams-api.mdx: team CRUD, members, roles, plugin installs, access control

Phase 4 — Update existing pages:
- database-architecture.mdx: add plugin_service_gateway schema (11 models), Neon preview DB
- backend-development.mdx: production deployment note, Service Gateway alternative
- dashboard-data-architecture.mdx: BFF facade layer, metrics APIs, subgraph sources
- database-plugin.mdx: fix plugin_gateway → plugin_service_gateway reference

Made-with: Cursor
@github-actions github-actions Bot added the needs-rebase Has merge conflicts label Apr 2, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 2, 2026

⚠️ Merge conflict detected

This PR has conflicts with the base branch. Please rebase to resolve them:

git fetch origin
git rebase origin/main
# resolve conflicts, then:
git push --force-with-lease

The needs-rebase label will be removed automatically once the conflicts are resolved.

…on templates, test assertions

- Derive headless plugins from publish-gated set (without visibility gate)
  so hidden headless providers still load for non-admin users
- Always resolve admin status from token before applying visibility filter,
  even when userId is passed as a query parameter
- Require authentication on GET /api/v1/gw/admin/templates to prevent
  unauthenticated template enumeration (consistent with POST)
- Fix auth.setup.ts title regex to match actual "Livepeer Dashboard" title
- Skip admin auth setup when ADMIN_EMAIL/ADMIN_PASSWORD are not configured
  instead of saving unauthenticated storage state
- Update personalized plugin tests to cover headless visibility bypass

Made-with: Cursor
- Normalize plugin names in admin core plugin API before WHERE clause
  to prevent silent update failures when name formats differ (my_plugin vs my-plugin)
- Fix admin-operations.mdx: correct request body to match actual API
  (corePluginNames/hiddenPluginNames arrays, not plugins array)
- Fix authentication.mdx: use full /api/v1/auth/* paths in rate-limit table
- Create empty admin.json storage state on skip to prevent file-not-found
  errors in dependent test suites
- Add error banner on public landing page when API requests fail

Made-with: Cursor
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
apps/web-next/src/app/api/v1/admin/plugins/core/route.ts (1)

194-200: ⚠️ Potential issue | 🟡 Minor

Response message may report incorrect hidden plugin count.

Line 199 uses hiddenPluginNames.length (the raw input count) rather than the resolved count. If some names don't resolve (typos, deprecated packages), the message will overstate how many plugins were actually hidden.

🔧 Proposed fix

Capture resolvedHiddenNames at a broader scope and use its length in the message:

     // Update visibility if hiddenPluginNames is provided
+    let resolvedHiddenNames: string[] = [];
     if (Array.isArray(hiddenPluginNames)) {
-      const resolvedHiddenNames = resolveNames(hiddenPluginNames);
+      resolvedHiddenNames = resolveNames(hiddenPluginNames);
       txOps.push(

Then update the message:

-    if (Array.isArray(hiddenPluginNames) && hiddenPluginNames.length > 0) {
-      parts.push(`${hiddenPluginNames.length} plugin(s) hidden from non-admin users.`);
+    if (resolvedHiddenNames.length > 0) {
+      parts.push(`${resolvedHiddenNames.length} plugin(s) hidden from non-admin users.`);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/app/api/v1/admin/plugins/core/route.ts` around lines 194 -
200, The response message currently uses the raw input hiddenPluginNames length
which can overstate hidden plugins if some names fail to resolve; capture the
resolvedHiddenNames array at a broader scope (where hiddenPluginNames is
processed) and change the message construction (the parts.push that references
hiddenPluginNames.length) to use resolvedHiddenNames.length instead, and also
update the text to reflect "hidden" based on resolvedHiddenNames; ensure you
reference the same variables used in this file (parts, newlyCore,
hiddenPluginNames, resolvedHiddenNames) so the message reflects the actual
resolved count.
♻️ Duplicate comments (1)
apps/web-next/src/content/docs/concepts/architecture.mdx (1)

40-40: ⚠️ Potential issue | 🟡 Minor

Resolve remaining backend-model inconsistency with Line 48.

These lines correctly describe Express as local-dev only, but Line 48 still states “Backend: Express API server,” which makes the page internally inconsistent. Please align that bullet with the same local-dev-vs-production wording.

Also applies to: 125-125

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

In `@apps/web-next/src/content/docs/concepts/architecture.mdx` at line 40, Update
the inconsistent bullets that read "Backend: Express API server" (occurrences of
the exact phrase "Backend: Express API server") to reflect the local-dev vs
production distinction: change them to something like "Backend: Express API
server (local development) — runs as Next.js API routes on Vercel/production" so
the wording matches the earlier sentence that Express is local-dev only; update
both occurrences mentioned in the comment.
🧹 Nitpick comments (2)
apps/web-next/src/app/api/v1/admin/plugins/core/route.ts (2)

80-88: Consider validating that array elements are strings.

If corePluginNames or hiddenPluginNames contain non-string elements, normalizePluginName will throw a TypeError on .toLowerCase(), resulting in a generic 500 error. Validating element types upfront provides clearer feedback to API consumers.

♻️ Proposed validation
     if (!Array.isArray(corePluginNames)) {
       return errors.badRequest('corePluginNames must be an array of plugin names');
     }
+    if (!corePluginNames.every((n) => typeof n === 'string')) {
+      return errors.badRequest('corePluginNames must contain only strings');
+    }

Similarly for hiddenPluginNames at line 129:

     if (Array.isArray(hiddenPluginNames)) {
+      if (!hiddenPluginNames.every((n) => typeof n === 'string')) {
+        return errors.badRequest('hiddenPluginNames must contain only strings');
+      }
       const resolvedHiddenNames = resolveNames(hiddenPluginNames);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/app/api/v1/admin/plugins/core/route.ts` around lines 80 -
88, corePluginNames (and hiddenPluginNames) arrays are currently only checked
for being arrays but not that their elements are strings, which lets
normalizePluginName call .toLowerCase() on non-strings and throw; update the
validation in route.ts to iterate the arrays (corePluginNames and
hiddenPluginNames if present) and ensure every element is typeof 'string'
(return errors.badRequest with a clear message like "corePluginNames must be an
array of strings" / "hiddenPluginNames must be an array of strings" when
validation fails) before calling normalizePluginName so we return a 400 instead
of letting a TypeError produce a 500.

149-176: Optional: Batch preference queries to reduce database round-trips.

The current implementation performs O(N) database queries for N newly-core plugins. For typical admin operations this is likely fine, but if batching multiple plugins becomes common, consolidating the queries would improve performance.

♻️ Batched approach sketch
if (newlyCore.length > 0) {
  const allUsers = await prisma.user.findMany({ select: { id: true } });
  
  // Single query for all existing preferences
  const existingPrefs = await prisma.userPluginPreference.findMany({
    where: { pluginName: { in: newlyCore } },
    select: { userId: true, pluginName: true },
  });
  
  // Build set of "userId:pluginName" pairs that exist
  const existingPairs = new Set(
    existingPrefs.map((p) => `${p.userId}:${p.pluginName}`)
  );
  
  // Collect all missing records
  const toCreate = allUsers.flatMap((u) =>
    newlyCore
      .filter((name) => !existingPairs.has(`${u.id}:${name}`))
      .map((pluginName) => ({
        userId: u.id,
        pluginName,
        enabled: true,
        order: 0,
        pinned: false,
      }))
  );
  
  if (toCreate.length > 0) {
    await prisma.userPluginPreference.createMany({
      data: toCreate,
      skipDuplicates: true,
    });
  }
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web-next/src/app/api/v1/admin/plugins/core/route.ts` around lines 149 -
176, The loop issues O(N) DB queries for each plugin in newlyCore; change it to
batch: fetch all users via prisma.user.findMany (select id), fetch existingPrefs
for all plugins in one call with prisma.userPluginPreference.findMany({ where: {
pluginName: { in: newlyCore } }, select: { userId, pluginName } }), build a Set
of existing "userId:pluginName" pairs, compute a single toCreate array by
iterating all users and newlyCore and skipping pairs that exist, then call
prisma.userPluginPreference.createMany once with data: toCreate and
skipDuplicates: true; update or remove variables like existingPrefs,
usersWithPref and the per-plugin loop accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/web-next/src/app/api/v1/admin/plugins/core/route.ts`:
- Around line 194-200: The response message currently uses the raw input
hiddenPluginNames length which can overstate hidden plugins if some names fail
to resolve; capture the resolvedHiddenNames array at a broader scope (where
hiddenPluginNames is processed) and change the message construction (the
parts.push that references hiddenPluginNames.length) to use
resolvedHiddenNames.length instead, and also update the text to reflect "hidden"
based on resolvedHiddenNames; ensure you reference the same variables used in
this file (parts, newlyCore, hiddenPluginNames, resolvedHiddenNames) so the
message reflects the actual resolved count.

---

Duplicate comments:
In `@apps/web-next/src/content/docs/concepts/architecture.mdx`:
- Line 40: Update the inconsistent bullets that read "Backend: Express API
server" (occurrences of the exact phrase "Backend: Express API server") to
reflect the local-dev vs production distinction: change them to something like
"Backend: Express API server (local development) — runs as Next.js API routes on
Vercel/production" so the wording matches the earlier sentence that Express is
local-dev only; update both occurrences mentioned in the comment.

---

Nitpick comments:
In `@apps/web-next/src/app/api/v1/admin/plugins/core/route.ts`:
- Around line 80-88: corePluginNames (and hiddenPluginNames) arrays are
currently only checked for being arrays but not that their elements are strings,
which lets normalizePluginName call .toLowerCase() on non-strings and throw;
update the validation in route.ts to iterate the arrays (corePluginNames and
hiddenPluginNames if present) and ensure every element is typeof 'string'
(return errors.badRequest with a clear message like "corePluginNames must be an
array of strings" / "hiddenPluginNames must be an array of strings" when
validation fails) before calling normalizePluginName so we return a 400 instead
of letting a TypeError produce a 500.
- Around line 149-176: The loop issues O(N) DB queries for each plugin in
newlyCore; change it to batch: fetch all users via prisma.user.findMany (select
id), fetch existingPrefs for all plugins in one call with
prisma.userPluginPreference.findMany({ where: { pluginName: { in: newlyCore } },
select: { userId, pluginName } }), build a Set of existing "userId:pluginName"
pairs, compute a single toCreate array by iterating all users and newlyCore and
skipping pairs that exist, then call prisma.userPluginPreference.createMany once
with data: toCreate and skipDuplicates: true; update or remove variables like
existingPrefs, usersWithPref and the per-plugin loop accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 34520ba4-f6b7-46c0-9864-e7cf4ac6d33d

📥 Commits

Reviewing files that changed from the base of the PR and between 2d51afb and 0cfd7bd.

📒 Files selected for processing (20)
  • apps/web-next/src/app/api/v1/admin/plugins/core/route.ts
  • apps/web-next/src/app/api/v1/base/plugins/personalized/__tests__/personalized.test.ts
  • apps/web-next/src/app/api/v1/base/plugins/personalized/route.ts
  • apps/web-next/src/app/api/v1/gw/admin/templates/route.ts
  • apps/web-next/src/app/page.tsx
  • apps/web-next/src/content/docs/api-reference/developer-api.mdx
  • apps/web-next/src/content/docs/api-reference/service-gateway-api.mdx
  • apps/web-next/src/content/docs/api-reference/teams-api.mdx
  • apps/web-next/src/content/docs/community/changelog.mdx
  • apps/web-next/src/content/docs/concepts/architecture.mdx
  • apps/web-next/src/content/docs/concepts/dashboard-data-architecture.mdx
  • apps/web-next/src/content/docs/concepts/database-architecture.mdx
  • apps/web-next/src/content/docs/concepts/plugin-system.mdx
  • apps/web-next/src/content/docs/concepts/service-gateway.mdx
  • apps/web-next/src/content/docs/examples/database-plugin.mdx
  • apps/web-next/src/content/docs/guides/admin-operations.mdx
  • apps/web-next/src/content/docs/guides/authentication.mdx
  • apps/web-next/src/content/docs/guides/backend-development.mdx
  • apps/web-next/src/content/docs/guides/service-gateway-setup.mdx
  • apps/web-next/tests/auth.setup.ts
✅ Files skipped from review due to trivial changes (9)
  • apps/web-next/src/content/docs/guides/backend-development.mdx
  • apps/web-next/src/content/docs/examples/database-plugin.mdx
  • apps/web-next/src/content/docs/api-reference/teams-api.mdx
  • apps/web-next/src/content/docs/api-reference/service-gateway-api.mdx
  • apps/web-next/src/content/docs/guides/service-gateway-setup.mdx
  • apps/web-next/src/content/docs/guides/admin-operations.mdx
  • apps/web-next/src/content/docs/api-reference/developer-api.mdx
  • apps/web-next/src/content/docs/concepts/service-gateway.mdx
  • apps/web-next/src/content/docs/concepts/dashboard-data-architecture.mdx
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/web-next/src/content/docs/concepts/plugin-system.mdx
  • apps/web-next/src/content/docs/concepts/database-architecture.mdx
  • apps/web-next/src/app/page.tsx
  • apps/web-next/src/app/api/v1/base/plugins/personalized/tests/personalized.test.ts
  • apps/web-next/src/app/api/v1/gw/admin/templates/route.ts
  • apps/web-next/src/app/api/v1/base/plugins/personalized/route.ts

@seanhanca seanhanca merged commit a9da7f8 into main Apr 2, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-rebase Has merge conflicts scope/shell Shell app changes size/XL Extra large PR (500+ lines)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants