Skip to content

Conversation

@akshaydeo
Copy link
Contributor

@akshaydeo akshaydeo commented Oct 21, 2025

Summary

Updated the sidebar navigation with new menu items and renamed an existing one to improve the admin interface organization.

Changes

  • Added a new "Plugins" menu item with a puzzle icon for managing custom plugins
  • Renamed "MCP Clients" to "MCP Gateway" for clarity
  • Added a new "Alert channels" menu item with a siren icon for managing alert notifications
  • Imported two new icons from lucide-react: Puzzle and Siren

Type of change

  • Feature
  • UI (Next.js)

Affected areas

  • UI (Next.js)

How to test

  1. Navigate to the admin interface
  2. Verify the sidebar contains the new "Plugins" and "Alert channels" menu items
  3. Confirm "MCP Clients" has been renamed to "MCP Gateway"
  4. Check that all icons display correctly
# UI
cd ui
pnpm i || npm i
pnpm dev || npm run dev

Screenshots/Recordings

[Add screenshots of the updated sidebar navigation]

Breaking changes

  • No

Related issues

[Link to related issue if applicable]

Security considerations

No security implications as this is a UI-only change affecting navigation labels and icons.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I verified builds succeed (UI)
  • I verified the UI changes display correctly in the browser

@akshaydeo akshaydeo mentioned this pull request Oct 21, 2025
18 tasks
Copy link
Contributor Author

akshaydeo commented Oct 21, 2025

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

@akshaydeo akshaydeo marked this pull request as ready for review October 21, 2025 19:38
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 21, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Restructured navigation with new Governance section combining Virtual Keys, Users & Groups, User Provisioning, and Audit Logs
    • Added custom plugins management with install and edit capabilities
    • Introduced Observability section and Prompt Repository placeholder
    • Added Alert Channels feature
  • Improvements

    • Enhanced page layouts with consistent width constraints and centering
    • Upgraded plugin management interface with improved forms and validation
    • Improved navigation organization with Model Providers and MCP Gateway sections
    • Better plugin status tracking and management
    • Updated UI framework and dependencies for enhanced performance

Walkthrough

Reworked sidebar navigation and added Plugins, Governance, Prompt Repository, and Alert Channels; introduced plugin path/isCustom fields with DB migration and server-side plugin status tracking; converted several Dialogs to Sheets; standardized centered max-width page wrappers; promo-card empty-state now informs sidebar layout.

Changes

Cohort / File(s) Summary
Sidebar Navigation
ui/components/sidebar.tsx
Reorganized top-level items and subItems (Observability, Prompt Repository, Governance, Plugins, MCP Gateway); updated icons, routes, typing, sub-item rendering; removed enterprise block; consolidated chevron/sub-item rendering.
Plugins UI, Forms & Sheets
ui/app/plugins/page.tsx, ui/app/plugins/fragments/pluginFormFragement.tsx, ui/app/plugins/sheets/addNewPluginSheet.tsx, ui/app/plugins/sheets/*, ui/app/plugins/*
New dynamic Plugins page using backend data, AddNewPluginSheet, PluginFormFragment; form validation, JSON config editor, create/update flows include path; UI for install/update plugin added.
Promo cards & Sidebar integration
ui/components/ui/promoCardStack.tsx, ui/components/sidebar.tsx
PromoCardStack gains onCardsEmpty callback; sidebar tracks promo emptiness and computes sidebar group height accordingly.
Providers: Dialog→Sheet & Key flows
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx, ui/app/providers/dialogs/addNewKeySheet.tsx, ui/app/providers/page.tsx, ui/app/providers/views/modelProviderKeysTableView.tsx
Replaced Dialog primitives with Sheet primitives; renamed components (AddCustomProviderDialog→AddCustomProviderSheet, AddNewKeyDialog→AddNewKeySheet); added keyIndex prop to sheet usage and updated imports/usages.
Provider views: accordion & form
ui/app/providers/views/modelProviderConfig.tsx, ui/app/providers/views/providerKeyForm.tsx
Added controlled accordion state, renamed isCustomProverisCustomProvider, mount validation, tooltip logic, minor UI fixes.
New / Updated Pages
ui/app/alert-channels/page.tsx, ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx, ui/app/prompt-repo/page.tsx, many ui/app/*/page.tsx
Added Alert Channels view/page and Prompt Repository placeholder; many pages now wrapped in centered mx-auto w-full max-w-7xl containers for consistent layout.
Logs UI
ui/app/logs/views/logsTable.tsx, ui/app/logs/page.tsx, ui/components/sidebar.tsx
Reduced logs table max-height; moved Logs under Observability in sidebar and adjusted observability code to dispatch plugin objects including isCustom/path.
Config & Plugins form integration
ui/app/config/views/pluginsForm.tsx, ui/app/observability/views/observabilityView.tsx
Create plugin payload now includes path; observability view dispatches full plugin shape with config, isCustom, path.
Types & UI types
ui/lib/types/plugins.ts, ui/app/plugins/fragments/*
Added isCustom:boolean and `path:string
Promo / UI tweaks
assorted ui/* files
Minor className fixes, import/icon updates, console debug, removal of enterprise navigation block, controlled promo behavior.
Config store & DB migration
framework/configstore/migrations.go, framework/configstore/rdb.go, framework/configstore/tables/plugin.go
Migration renamed and extended to add is_custom column; TablePlugin gains IsCustom bool; Create/Update set IsCustom=true when Path provided.
Bifrost HTTP server & plugin status
transports/bifrost-http/handlers/server.go, transports/bifrost-http/handlers/plugins.go, core/schemas/plugin.go
LoadPlugins now returns plugin statuses; added pluginStatus storage and mutex; UpdatePluginStatus method; CreatePluginRequest gains Path; new PluginStatus type and status constants in schemas.
Misc: build & deps
ui/next.config.ts, ui/package.json, ui/tsconfig.json, framework/go.mod, plugin go.mod files, plugins/otel/docker-compose.yml
Next/Turbopack alias changes, Next bumped to 16.0.0, tsconfig jsx change, Go module dependency bumps, Tempo gRPC port change in docker-compose.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant UI
  participant PluginsAPI
  participant Server

  User->>UI: Open Plugins page
  UI->>PluginsAPI: useGetPluginsQuery()
  PluginsAPI-->>UI: plugins list (includes path,isCustom)
  User->>UI: Click "Install New Plugin" / "Edit"
  UI->>UI: Open AddNewPluginSheet (prefill if edit)
  User->>UI: Submit plugin form (name,path,config)
  UI->>PluginsAPI: create/update plugin (payload includes path)
  PluginsAPI->>Server: persist plugin, call ReloadPlugin if enabled
  Server-->>PluginsAPI: success/error (and plugin status updates)
  PluginsAPI-->>UI: response
  UI-->>User: close sheet & refresh list
Loading
sequenceDiagram
  autonumber
  participant Bootstrap
  participant Server
  participant ConfigStore

  Bootstrap->>Server: LoadPlugins(ctx, config)
  Server->>ConfigStore: read plugin configs
  Server-->>Server: initialize pluginStatus entries (loading/disabled/loaded/error)
  Server->>Server: set pluginStatus mutex and store statuses
  Server-->>Bootstrap: return plugins + pluginStatus
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas needing extra attention:
    • DB migration and TablePlugin.IsCustom interactions (migrations.go, tables/plugin.go, rdb.go)
    • transports/bifrost-http handlers and changed LoadPlugins signature + UpdatePluginStatus usage
    • Plugin create/update flows: payload includes path, JSON config parsing and validation in AddNewPluginSheet
    • Dialog→Sheet renames and prop changes (keyIndex) across provider views and usages
    • UI typing changes for Plugin/CreatePluginRequest (ui/lib/types/plugins.ts) and observability dispatch updates

Poem

🐰 I hopped the sidebar, found a Puzzle sign,
Siren sang for alerts in tidy line,
Sheets snugly tuck the dialogs away,
Plugins bring paths and flags to play,
I nibble code and bound — hooray, new day!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The PR description follows the template structure with all major sections present, but it is significantly incomplete in scope. The Summary and Changes sections focus narrowly on sidebar navigation updates (Plugins menu item, MCP Gateway rename, Alert channels item), while the raw changeset summary reveals substantial changes across multiple areas: backend plugin system enhancements with database migrations, API handler changes for plugin status tracking, new plugin management components, form improvements, and dependency updates. The description incorrectly states this is a "UI-only change" in the security section, when the PR also includes Core and Transports modifications. The Affected areas checkbox selection only marks "UI (Next.js)" when it should also include "Core (Go)", "Transports (HTTP)", and "Plugins." Expand the Summary and Changes sections to cover all major modifications: include the plugin system backend changes, database migrations (plugin status tracking, custom plugin support), API enhancements for plugin loading and status management, new frontend components for plugin management and forms, and layout standardization across pages. Update the Affected areas checkboxes to accurately reflect Core, Transports, Plugins, and UI changes. Correct the security considerations to acknowledge that this is not UI-only. Finally, add or link to actual related issues in the Related issues section instead of placeholder text.
Docstring Coverage ⚠️ Warning Docstring coverage is 12.12% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title "adds dynamic ui" uses a vague, non-descriptive term that does not convey meaningful information about the specific changes in the pull request. While it is somewhat related to the changeset (which does include UI additions), the phrasing is too generic and overly broad to clearly communicate the primary changes to a teammate reviewing history. Terms like "dynamic ui" lack specificity and could apply to many different types of changes, making it difficult to understand the actual scope from the title alone.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 10-20-dynamic-plugins-ui

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

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c12ca7 and abcd1d1.

📒 Files selected for processing (1)
  • ui/components/sidebar.tsx (4 hunks)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (2)
ui/components/sidebar.tsx (2)

15-15: LGTM!

The icon imports are correctly added and used by the new navigation items.

Also applies to: 19-19


102-107: No issues found—the /plugins route is properly implemented.

The verification confirms that ui/app/plugins/page.tsx exists, so the new "Plugins" navigation item at lines 102-107 correctly points to an existing route. The navigation item follows the established pattern and is ready for merge.


{
title: "MCP Clients",
title: "MCP Gateway",
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

🧩 Analysis chain

Verify naming consistency across the codebase.

The navigation item has been renamed from "MCP Clients" to "MCP Gateway", but the URL remains /mcp-clients. Ensure this terminology change is consistent across:

  • Page titles and headers
  • Documentation
  • Other UI references
  • API endpoint names

Run the following script to check for references to the old name:


🏁 Script executed:

#!/bin/bash
# Description: Check for "MCP Clients" references that may need updating

# Search for "MCP Clients" in UI files (case-insensitive to catch variations)
echo "=== Searching for 'MCP Clients' references ==="
rg -i "mcp\s+clients?" ui/ --type tsx --type ts --type jsx --type js -C 2

# Search for page titles or headers that might still use old terminology
echo -e "\n=== Checking mcp-clients page files ==="
fd -e tsx -e ts "page|layout|index" ui/app/mcp-clients/ ui/src/app/mcp-clients/ 2>/dev/null | xargs cat 2>/dev/null || echo "No files found"

Length of output: 864


Let me search more broadly for inconsistencies:


---

🏁 Script executed:

```shell
#!/bin/bash
# Corrected search without type specifiers and broader scope

echo "=== Searching for 'MCP Clients' (all files) ==="
rg -i "mcp clients|mcp_clients" ui/ -C 2 | head -50

echo -e "\n=== Searching for 'MCP Gateway' references ==="
rg -i "mcp gateway|mcp.gateway" ui/ -C 2

echo -e "\n=== Checking sidebar.tsx for context ==="
rg -n "MCP Gateway|MCP Clients" ui/components/sidebar.tsx -B 3 -A 3

echo -e "\n=== Checking table and hook files ==="
fd -type f "table|hook" ui/ | rg -i "mcp" | head -20

Length of output: 4664


🏁 Script executed:

# Also check imports and related queries
echo "=== Files importing mcp-clients related items ==="
rg "import.*[Mm]cp.*lient" ui/ -l | head -20

Length of output: 166


Incomplete rename: "MCP Clients" to "MCP Gateway" only applied to sidebar navigation.

The sidebar title was changed to "MCP Gateway" (line 110), but this terminology is not reflected elsewhere in the codebase, creating user-facing inconsistency. The underlying functionality, components, documentation, and UI elements still use "MCP Clients":

  • UI Components: mcpClientsTable.tsx displays "Registered MCP Clients"
  • Documentation: README.md references "MCP clients" multiple times
  • API/Hooks: All remain as getMCPClients, MCPClientsTable, etc.
  • Icons: sidebar.tsx SVG retains "MCP clients icon" label
  • Virtual Keys: Configuration UI still refers to "MCP clients"

Either complete the rename across the codebase or revert the sidebar change to "MCP Clients" for consistency.

🤖 Prompt for AI Agents
In ui/components/sidebar.tsx around line 110 the sidebar title was changed to
"MCP Gateway" but the rest of the codebase still uses "MCP Clients"; either
revert this line back to "MCP Clients" to restore consistency, or perform a
complete rename across files (UI labels, component names, API/hook names,
documentation, SVG aria/labels, and configuration keys) ensuring casing and
plurals match, update import/exports and tests accordingly, and run a
project-wide search/replace to update getMCPClients, MCPClientsTable, README
references, SVG labels and any virtual-key strings to the chosen term.

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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c12ca7 and abcd1d1.

📒 Files selected for processing (1)
  • ui/components/sidebar.tsx (4 hunks)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (3)
ui/components/sidebar.tsx (3)

15-15: LGTM!

The new icon imports are appropriate for their respective menu items (Puzzle for plugins, Siren for alert channels).

Also applies to: 19-19


110-110: LGTM!

The label change from "MCP Clients" to "MCP Gateway" improves clarity while maintaining backward compatibility by keeping the URL unchanged.


102-107: Route verified—no changes needed.

The /plugins route exists at ui/app/plugins/page.tsx with a fully implemented page component. The new Plugins menu item will function correctly without 404 errors.

@akshaydeo akshaydeo force-pushed the 10-20-dynamic-plugins-ui branch from abcd1d1 to c6f4b95 Compare October 22, 2025 01:15
@akshaydeo akshaydeo force-pushed the 10-16-dynamic_plugin_architecture branch from 8c12ca7 to ddd7b35 Compare October 22, 2025 01:15
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: 4

🧹 Nitpick comments (6)
ui/app/providers/views/providerKeyForm.tsx (2)

107-107: Optimize: Call getTooltipContent() only once.

The function is called twice on this line—once to check if it's truthy and once to render the content. Store the result in a variable to avoid redundant calls.

Apply this diff:

+								const tooltipContent = getTooltipContent();
-								{getTooltipContent() && <TooltipContent>{getTooltipContent()}</TooltipContent>}
+								{tooltipContent && <TooltipContent>{tooltipContent}</TooltipContent>}

Or use a more compact approach by moving the logic inside the component:

-								{getTooltipContent() && <TooltipContent>{getTooltipContent()}</TooltipContent>}
+								<TooltipContent>{getTooltipContent()}</TooltipContent>

Note: The second approach renders TooltipContent even when the content is null, but Tooltip components typically handle null/empty content gracefully by not displaying.


53-61: Remove unnecessary root error check that will never execute.

The tooltip logic checks form.formState.errors.root?.message, but this path is unreachable because your form never calls form.setError("root", ...). Root errors aren't set anywhere in this component or anywhere in the codebase—Zod validation only produces field-level errors.

Simplify the tooltip logic to remove the dead code path:

const getTooltipContent = useCallback(() => {
	if (!form.formState.isDirty) {
		return "No changes made";
	}
	return null;
}, [form?.formState.isDirty]);

If you need to display validation errors in the future, either implement form.setError("root", ...) in your error handler or extract field-level errors from form.formState.errors directly.

ui/app/logs/views/logsTable.tsx (1)

81-82: Height calculation adjusted for new page layout.

The reduced offset (20rem → 15.5rem) increases the visible table area by 4.5rem, likely compensating for the centered page wrapper changes applied elsewhere. Both the container and Table maintain matching max-heights.

Consider documenting why 15.5rem was chosen, or use CSS variables to make the calculation more maintainable if the page layout changes again.

ui/app/cluster/page.tsx (1)

4-8: Layout wrapper applied consistently.

This matches the centered container pattern used across multiple pages in this PR.

The layout wrapper pattern <div className="mx-auto w-full max-w-7xl"> is repeated across at least 7 pages in this PR. Consider extracting this into a reusable layout component (e.g., PageContainer or CenteredLayout) to reduce duplication and simplify future layout changes.

Example:

// components/layouts/PageContainer.tsx
export function PageContainer({ children }: { children: React.ReactNode }) {
  return <div className="mx-auto w-full max-w-7xl">{children}</div>;
}

Then use it as:

export default async function ClusterPage() {
  return (
    <PageContainer>
      <ClusterView />
    </PageContainer>
  );
}
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (1)

154-154: Remove redundant value prop.

The {...field} spread already includes the value property from react-hook-form. The explicit value={field.value || ""} is redundant and the empty string fallback is unnecessary since the form's default value is already an empty string.

Apply this diff:

-											<Input placeholder={"https://api.your-provider.com"} {...field} value={field.value || ""} />
+											<Input placeholder="https://api.your-provider.com" {...field} />
ui/app/providers/dialogs/addNewKeySheet.tsx (1)

14-17: Rename variable for consistency.

The component was renamed from AddNewKeyDialog to AddNewKeySheet, but the variable dialogTitle still references "dialog". Consider renaming it to match the Sheet pattern.

Apply this diff to improve naming consistency:

 export default function AddNewKeySheet({ show, onCancel, provider, keyIndex }: Props) {
 	const isEditing = keyIndex < provider.keys.length;
-	const dialogTitle = isEditing ? "Edit key" : "Add new key";
+	const title = isEditing ? "Edit key" : "Add new key";
 	const successMessage = isEditing ? "Key updated successfully" : "Key added successfully";

And update the usage on line 28:

-							{dialogTitle}
+							{title}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between abcd1d1 and c6f4b95.

⛔ Files ignored due to path filters (3)
  • plugins/governance/go.sum is excluded by !**/*.sum
  • plugins/semanticcache/go.sum is excluded by !**/*.sum
  • plugins/telemetry/go.sum is excluded by !**/*.sum
📒 Files selected for processing (23)
  • ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1 hunks)
  • ui/app/adaptive-routing/page.tsx (1 hunks)
  • ui/app/alert-channels/page.tsx (1 hunks)
  • ui/app/audit-logs/page.tsx (1 hunks)
  • ui/app/cluster/page.tsx (1 hunks)
  • ui/app/config/page.tsx (1 hunks)
  • ui/app/guardrails/page.tsx (1 hunks)
  • ui/app/layout.tsx (1 hunks)
  • ui/app/logs/page.tsx (1 hunks)
  • ui/app/logs/views/logsTable.tsx (1 hunks)
  • ui/app/mcp-clients/page.tsx (1 hunks)
  • ui/app/observability/page.tsx (1 hunks)
  • ui/app/plugins/page.tsx (1 hunks)
  • ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (5 hunks)
  • ui/app/providers/dialogs/addNewKeySheet.tsx (3 hunks)
  • ui/app/providers/page.tsx (3 hunks)
  • ui/app/providers/views/modelProviderConfig.tsx (1 hunks)
  • ui/app/providers/views/modelProviderKeysTableView.tsx (3 hunks)
  • ui/app/providers/views/providerKeyForm.tsx (5 hunks)
  • ui/app/scim/page.tsx (1 hunks)
  • ui/app/user-groups/page.tsx (1 hunks)
  • ui/app/virtual-keys/page.tsx (2 hunks)
  • ui/components/sidebar.tsx (4 hunks)
✅ Files skipped from review due to trivial changes (2)
  • ui/app/observability/page.tsx
  • ui/app/user-groups/page.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • ui/components/sidebar.tsx
🧰 Additional context used
🧬 Code graph analysis (12)
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)
ui/app/_fallbacks/enterprise/components/views/contactUsView.tsx (1)
  • ContactUsView (15-46)
ui/app/providers/views/modelProviderConfig.tsx (3)
ui/lib/types/config.ts (1)
  • isKnownProvider (15-17)
ui/app/providers/views/utils.ts (1)
  • keysRequired (1-1)
ui/components/ui/accordion.tsx (1)
  • Accordion (47-47)
ui/app/audit-logs/page.tsx (1)
ui/app/_fallbacks/enterprise/components/audit-logs/auditLogsView.tsx (1)
  • AuditLogsView (4-16)
ui/app/virtual-keys/page.tsx (1)
ui/app/virtual-keys/views/virtualKeysTable.tsx (1)
  • VirtualKeysTable (34-230)
ui/app/adaptive-routing/page.tsx (1)
ui/app/_fallbacks/enterprise/components/adaptive-routing/adaptiveRoutingView.tsx (1)
  • AdaptiveRoutingView (4-16)
ui/app/providers/page.tsx (1)
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (1)
  • AddCustomProviderSheet (41-176)
ui/app/providers/views/providerKeyForm.tsx (1)
ui/app/providers/fragments/apiKeysFormFragment.tsx (1)
  • ApiKeyFormFragment (21-445)
ui/app/alert-channels/page.tsx (1)
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)
  • AlertChannelsView (4-16)
ui/app/scim/page.tsx (1)
ui/app/_fallbacks/enterprise/components/scim/scimView.tsx (1)
  • SCIMView (4-16)
ui/app/logs/views/logsTable.tsx (1)
ui/components/ui/table.tsx (1)
  • Table (71-71)
ui/app/providers/views/modelProviderKeysTableView.tsx (2)
ui/components/ui/alertDialog.tsx (1)
  • AlertDialogCancel (85-85)
ui/app/providers/dialogs/addNewKeySheet.tsx (1)
  • AddNewKeySheet (14-46)
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (1)
ui/components/ui/input.tsx (1)
  • Input (15-69)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (10)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (19)
ui/app/providers/views/modelProviderConfig.tsx (3)

46-47: Good typo fix and state addition for controlled accordion.

The typo fix (isCustomProverisCustomProvider) is correct, and adding accordionValue state enables a proper controlled component pattern for the Accordion.


52-52: LGTM: Proper controlled component initialization.

The showApiKeys constant is correctly computed early and used in the initialization effect. The effect properly initializes accordionValue based on provider characteristics and will reset the accordion state when the provider changes, which is the expected behavior.

Also applies to: 58-60


64-64: Excellent refactoring to a fully controlled accordion.

The accordion now uses a proper controlled component pattern with explicit state management. This change also improves UX consistency—the accordion is now always collapsible (previously, providers without API key requirements like Ollama/SGL had non-collapsible accordions). This aligns well with the broader UI standardization effort mentioned in the PR context.

ui/app/providers/views/providerKeyForm.tsx (1)

88-88: Good catch on the CSS class fix!

The correction from bg:white to bg-white fixes invalid Tailwind syntax.

ui/app/adaptive-routing/page.tsx (1)

4-8: Layout wrapper applied consistently.

The centered, constrained-width container follows the same pattern used across other pages in this PR.

ui/app/audit-logs/page.tsx (1)

4-6: LGTM!

Layout wrapper applied consistently with the broader refactor pattern.

ui/app/scim/page.tsx (1)

5-9: LGTM!

Layout wrapper applied consistently.

ui/app/config/page.tsx (1)

64-64: Layout constraints added to existing flex container.

The max-w-7xl mx-auto classes center and constrain the config page width, consistent with other pages in this PR.

ui/app/virtual-keys/page.tsx (2)

6-6: LGTM!

The import is properly used in the governance configuration check below.


73-80: Layout wrapper applied consistently.

The centered container follows the same pattern as other pages in this PR.

ui/app/mcp-clients/page.tsx (1)

29-31: LGTM!

Layout wrapper applied consistently with the broader refactor.

ui/app/layout.tsx (1)

41-41: LGTM!

The layout refactor appropriately delegates width constraints to individual pages, which now consistently apply max-w-7xl mx-auto wrappers. This creates a more flexible and maintainable layout structure.

ui/app/guardrails/page.tsx (1)

4-8: LGTM!

The layout wrapper is consistent with the pattern applied across other pages in this PR.

ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (1)

5-5: LGTM!

The migration from Dialog to Sheet components is well-executed and maintains the same functionality while updating the UI pattern.

Also applies to: 41-41, 97-174

ui/app/providers/page.tsx (1)

24-24: LGTM!

The import and usage are correctly updated to use the new AddCustomProviderSheet component, and the layout wrapper is consistent with the pattern applied across the codebase.

Also applies to: 94-94, 113-113

ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)

1-16: LGTM!

The AlertChannelsView component follows the established pattern for enterprise feature fallbacks, using ContactUsView with appropriate messaging and links.

ui/app/alert-channels/page.tsx (1)

1-9: LGTM!

The AlertChannelsPage correctly wraps the AlertChannelsView with the consistent layout container pattern used throughout the codebase.

ui/app/providers/views/modelProviderKeysTableView.tsx (2)

42-51: Good: Explicit state management for dialog dismissal.

The explicit onClick handler on AlertDialogCancel correctly manages the dialog state. Since the AlertDialog doesn't have an onOpenChange handler, this explicit state reset is necessary for the Cancel button to work properly.


78-83: LGTM: AddNewKeySheet integration looks correct.

The component is properly integrated with the correct props (show, onCancel, provider, keyIndex) and the state management is consistent with the delete dialog pattern.

<EmptyState isSocketConnected={isSocketConnected} error={error} />
) : (
<div className="space-y-6">
<div className="space-y-6 max-w-7xl mx-auto">
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 | 🟡 Minor

Fix whitespace in className.

There's a tab character between max-w-7xl and mx-auto in the className. This should be a single space for consistency.

Apply this diff:

-				<div className="space-y-6 max-w-7xl	mx-auto">
+				<div className="space-y-6 max-w-7xl mx-auto">
📝 Committable suggestion

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

Suggested change
<div className="space-y-6 max-w-7xl mx-auto">
<div className="space-y-6 max-w-7xl mx-auto">
🤖 Prompt for AI Agents
In ui/app/logs/page.tsx around line 352, the className value contains a tab
character between "max-w-7xl" and "mx-auto"; replace the tab with a single space
so the className reads "space-y-6 max-w-7xl mx-auto" to ensure consistent
whitespace and proper Tailwind parsing.

<DialogHeader>
<DialogTitle>
<Sheet open={show} onOpenChange={onCancel}>
<SheetContent className="custom-scrollbar bg-white dark:bg-card min-w-[600px] py-4">
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 | 🟠 Major

Fixed min-width may cause responsive issues.

The min-w-[600px] will cause horizontal overflow on mobile devices (typical viewport: 360-428px). Consider using a responsive approach.

Apply this diff to make it mobile-friendly:

-			<SheetContent className="custom-scrollbar bg-white dark:bg-card min-w-[600px] py-4">
+			<SheetContent className="custom-scrollbar bg-white dark:bg-card w-full sm:min-w-[600px] sm:max-w-[600px] py-4">

This ensures full width on mobile and fixed 600px on larger screens.

📝 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
<SheetContent className="custom-scrollbar bg-white dark:bg-card min-w-[600px] py-4">
<SheetContent className="custom-scrollbar bg-white dark:bg-card w-full sm:min-w-[600px] sm:max-w-[600px] py-4">
🤖 Prompt for AI Agents
In ui/app/providers/dialogs/addNewKeySheet.tsx around line 21, the fixed
min-w-[600px] causes horizontal overflow on small viewports; update the
SheetContent class to be full width on mobile and only enforce the 600px minimum
at larger breakpoints (e.g., use a mobile w-full and apply min-w-[600px]
starting at the sm breakpoint) so the sheet fits narrow screens without
overflow.

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

🧹 Nitpick comments (4)
ui/app/observability/page.tsx (1)

4-8: Class order inconsistency (minor).

The wrapper uses max-w-7xl mx-auto w-full while other pages use mx-auto w-full max-w-7xl. Both are functionally identical, but consistency would improve maintainability.

Apply this diff for consistency:

-		<div className="max-w-7xl mx-auto w-full">
+		<div className="mx-auto w-full max-w-7xl">
ui/app/providers/views/providerKeyForm.tsx (1)

107-107: Optimize: getTooltipContent() is called twice.

The function is called once in the condition and once in the content, which is inefficient. Consider storing the result in a variable.

Apply this diff to optimize the tooltip rendering:

-							{getTooltipContent() && <TooltipContent>{getTooltipContent()}</TooltipContent>}
+							{(() => {
+								const content = getTooltipContent();
+								return content && <TooltipContent>{content}</TooltipContent>;
+							})()}
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (1)

97-97: Consider simplifying onOpenChange for consistency.

The onOpenChange handler uses (open) => !open && onClose() which works but differs from the simpler pattern used in addNewKeySheet.tsx at line 20: onOpenChange={onCancel}. Both Sheet components close on user dismissal, but the inconsistent pattern could be confusing.

For consistency with addNewKeySheet.tsx, consider this simpler pattern:

-		<Sheet open={show} onOpenChange={(open) => !open && onClose()}>
+		<Sheet open={show} onOpenChange={onClose}>

However, if you prefer the explicit check to only call onClose when the sheet is being closed (not opened), both approaches work correctly.

ui/app/plugins/page.tsx (1)

1-1: Remove unnecessary "use client"; convert to server component and add metadata.

The page contains no client-side hooks or interactivity. Route wiring to "/plugins" is confirmed in ui/components/sidebar.tsx (line 104). This change reduces JS bundle and enables metadata export.

-"use client";
+export const metadata = {
+  title: "Plugins",
+  description: "Manage custom plugins",
+};
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between abcd1d1 and c6f4b95.

⛔ Files ignored due to path filters (3)
  • plugins/governance/go.sum is excluded by !**/*.sum
  • plugins/semanticcache/go.sum is excluded by !**/*.sum
  • plugins/telemetry/go.sum is excluded by !**/*.sum
📒 Files selected for processing (23)
  • ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1 hunks)
  • ui/app/adaptive-routing/page.tsx (1 hunks)
  • ui/app/alert-channels/page.tsx (1 hunks)
  • ui/app/audit-logs/page.tsx (1 hunks)
  • ui/app/cluster/page.tsx (1 hunks)
  • ui/app/config/page.tsx (1 hunks)
  • ui/app/guardrails/page.tsx (1 hunks)
  • ui/app/layout.tsx (1 hunks)
  • ui/app/logs/page.tsx (1 hunks)
  • ui/app/logs/views/logsTable.tsx (1 hunks)
  • ui/app/mcp-clients/page.tsx (1 hunks)
  • ui/app/observability/page.tsx (1 hunks)
  • ui/app/plugins/page.tsx (1 hunks)
  • ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (5 hunks)
  • ui/app/providers/dialogs/addNewKeySheet.tsx (3 hunks)
  • ui/app/providers/page.tsx (3 hunks)
  • ui/app/providers/views/modelProviderConfig.tsx (1 hunks)
  • ui/app/providers/views/modelProviderKeysTableView.tsx (3 hunks)
  • ui/app/providers/views/providerKeyForm.tsx (5 hunks)
  • ui/app/scim/page.tsx (1 hunks)
  • ui/app/user-groups/page.tsx (1 hunks)
  • ui/app/virtual-keys/page.tsx (2 hunks)
  • ui/components/sidebar.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • ui/components/sidebar.tsx
🧰 Additional context used
🧬 Code graph analysis (12)
ui/app/providers/views/providerKeyForm.tsx (1)
ui/app/providers/fragments/apiKeysFormFragment.tsx (1)
  • ApiKeyFormFragment (21-445)
ui/app/adaptive-routing/page.tsx (1)
ui/app/_fallbacks/enterprise/components/adaptive-routing/adaptiveRoutingView.tsx (1)
  • AdaptiveRoutingView (4-16)
ui/app/audit-logs/page.tsx (1)
ui/app/_fallbacks/enterprise/components/audit-logs/auditLogsView.tsx (1)
  • AuditLogsView (4-16)
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)
ui/app/_fallbacks/enterprise/components/views/contactUsView.tsx (1)
  • ContactUsView (15-46)
ui/app/scim/page.tsx (1)
ui/app/_fallbacks/enterprise/components/scim/scimView.tsx (1)
  • SCIMView (4-16)
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (2)
ui/components/ui/form.tsx (1)
  • Form (160-160)
ui/components/ui/input.tsx (1)
  • Input (15-69)
ui/app/alert-channels/page.tsx (1)
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)
  • AlertChannelsView (4-16)
ui/app/providers/views/modelProviderKeysTableView.tsx (1)
ui/app/providers/dialogs/addNewKeySheet.tsx (1)
  • AddNewKeySheet (14-46)
ui/app/logs/views/logsTable.tsx (1)
ui/components/ui/table.tsx (1)
  • Table (71-71)
ui/app/providers/page.tsx (1)
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (1)
  • AddCustomProviderSheet (41-176)
ui/app/virtual-keys/page.tsx (1)
ui/app/virtual-keys/views/virtualKeysTable.tsx (1)
  • VirtualKeysTable (34-230)
ui/app/providers/views/modelProviderConfig.tsx (2)
ui/lib/types/config.ts (1)
  • isKnownProvider (15-17)
ui/app/providers/views/utils.ts (1)
  • keysRequired (1-1)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (23)
ui/app/adaptive-routing/page.tsx (1)

4-8: LGTM!

The centered max-width container follows the consistent layout pattern applied across all pages in this PR.

ui/app/cluster/page.tsx (1)

4-8: LGTM!

The centered max-width container follows the consistent layout pattern applied across all pages in this PR.

ui/app/config/page.tsx (1)

63-91: LGTM!

The max-width constraint and centering have been correctly added to the existing flex container, maintaining the layout structure while applying the consistent page width pattern.

ui/app/mcp-clients/page.tsx (1)

28-32: LGTM!

The centered max-width container follows the consistent layout pattern applied across all pages in this PR.

ui/app/audit-logs/page.tsx (1)

4-6: LGTM!

The centered max-width container follows the consistent layout pattern applied across all pages in this PR.

ui/app/logs/views/logsTable.tsx (1)

81-82: Verify the height calculation adjustment across viewports.

The height calculation has been reduced from calc(100vh-20rem) to calc(100vh-15.5rem), allocating 4.5rem more vertical space for the table. While this aligns with the page layout changes, ensure this doesn't cause overflow issues on smaller screens or with varying header heights.

Test the logs page on different viewport sizes to confirm the table height adjusts correctly without causing layout issues.

ui/app/guardrails/page.tsx (1)

4-8: LGTM! Consistent layout wrapper applied.

The centered max-width container aligns with the broader layout standardization across the UI.

ui/app/scim/page.tsx (1)

5-9: LGTM! Consistent layout wrapper applied.

The centered max-width container follows the same pattern used across other pages in this PR.

ui/app/user-groups/page.tsx (1)

105-105: LGTM! Consistent layout constraint added.

Adding max-w-7xl mx-auto to the existing flex container maintains the layout while applying the standard width constraint used across the UI.

ui/app/virtual-keys/page.tsx (1)

73-80: LGTM! Consistent layout wrapper applied.

The centered max-width container aligns with the layout standardization across the application.

ui/app/layout.tsx (1)

41-41: Layout refactor moves width control to individual pages.

This change removes the responsive width constraints (w-5xl, 2xl:w-7xl) from the main container, allowing individual pages to control their own max-width via max-w-7xl wrappers. The reduced padding (py-12p-4) appears intentional but should be verified across all pages to ensure consistent spacing.

Please verify that the reduced padding and flex layout work correctly across all pages, especially on mobile viewports.

ui/app/providers/views/providerKeyForm.tsx (2)

53-61: LGTM! Tooltip logic extracted into reusable function.

The useCallback hook properly memoizes the tooltip content logic with the correct dependencies.


88-88: Good catch! Typo fixed.

Corrected bg:white to bg-white.

ui/app/providers/views/modelProviderConfig.tsx (3)

46-47: LGTM! Accordion state management added.

Adding controlled accordion state allows better control over the accordion's open/closed state. The variable rename from isCustomProver to isCustomProvider fixes a typo.


52-60: LGTM! Smart accordion initialization logic.

The effect automatically opens the accordion for custom providers or when API keys are not required, improving the user experience.


64-64: LGTM! Accordion is now controlled.

Converting to a controlled accordion with value and onValueChange provides better state management.

ui/app/alert-channels/page.tsx (1)

1-9: LGTM! New alert channels page follows established patterns.

The page structure is consistent with other pages in the application, using the same centered max-width container layout.

ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)

1-16: LGTM!

The AlertChannelsView component is well-structured and correctly integrates with ContactUsView to provide an enterprise feature placeholder. The props are properly configured to direct users to the alert channels documentation and demo booking.

ui/app/providers/page.tsx (1)

24-24: LGTM! Consistent migration to Sheet-based UI.

The changes successfully migrate from Dialog to Sheet components and apply consistent page-width constraints. The component wiring (show, onSave, onClose) remains intact.

Also applies to: 94-94, 113-124

ui/app/providers/views/modelProviderKeysTableView.tsx (1)

13-23: LGTM! Sheet migration and keyIndex prop support added correctly.

The migration from AddNewKeyDialog to AddNewKeySheet is consistent, and the keyIndex prop is now properly passed for per-key edit operations. The explicit onClick handler on AlertDialogCancel (lines 49-51) is redundant since the disabled prop already prevents interaction, but it's harmless and may improve code clarity.

Also applies to: 49-51, 78-83

ui/app/providers/dialogs/addNewKeySheet.tsx (1)

1-46: LGTM! Clean Dialog-to-Sheet migration.

The component has been successfully migrated from Dialog to Sheet with appropriate styling and structure. The onOpenChange handler directly calls onCancel, which is simple and effective.

ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (2)

154-154: Good practice: Explicit controlled component value.

The explicit value={field.value || ""} ensures the Input remains a controlled component even when field.value is undefined, preventing React warnings about switching between controlled and uncontrolled states.


1-176: LGTM! Successful Dialog-to-Sheet migration with proper form handling.

The component has been successfully migrated from Dialog to Sheet with appropriate structure, styling, and form integration. The Sheet-based UI provides better UX for complex forms.

<EmptyState isSocketConnected={isSocketConnected} error={error} />
) : (
<div className="space-y-6">
<div className="space-y-6 max-w-7xl mx-auto">
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 | 🟡 Minor

Fix whitespace in className.

There appears to be a tab character between max-w-7xl and mx-auto instead of a regular space. This could cause formatting inconsistencies.

Apply this diff:

-				<div className="space-y-6 max-w-7xl	mx-auto">
+				<div className="space-y-6 mx-auto w-full max-w-7xl">
📝 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
<div className="space-y-6 max-w-7xl mx-auto">
<div className="space-y-6 mx-auto w-full max-w-7xl">
🤖 Prompt for AI Agents
In ui/app/logs/page.tsx around line 352, the className string contains a tab
character between "max-w-7xl" and "mx-auto"; replace the tab with a regular
space so the class list reads "space-y-6 max-w-7xl mx-auto" to ensure consistent
whitespace and proper Tailwind class parsing.

});
};

console.log(form.formState.isDirty);
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

Remove debug console.log statement.

This debug statement should be removed before merging to production.

Apply this diff to remove the debug statement:

-	console.log(form.formState.isDirty);
-
📝 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
console.log(form.formState.isDirty);
🤖 Prompt for AI Agents
In ui/app/providers/views/providerKeyForm.tsx around line 81, remove the debug
console.log(form.formState.isDirty); statement — delete that line (or the entire
debug call) so no console output remains in production, then run lint/format to
ensure no leftover whitespace or unused variables warnings.

@akshaydeo akshaydeo force-pushed the 10-20-dynamic-plugins-ui branch 2 times, most recently from 0536336 to a3a156b Compare October 22, 2025 02:49
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: 7

🧹 Nitpick comments (10)
ui/app/providers/views/modelProviderConfig.tsx (1)

46-46: Verify the behavioral change: accordion is now always collapsible.

The accordion has been converted from an uncontrolled to a controlled component, which is a reasonable refactor. However, the collapsible prop appears to have changed from collapsible={showApiKeys} (conditional) to collapsible={true} (always), meaning users can now collapse the accordion even when API keys are not required. Please confirm this is the intended behavior.

Optional refactor: Simplify useEffect dependencies.

The useEffect dependencies [showApiKeys, isCustomProvider] are derived from provider.name. Consider depending directly on the source:

 useEffect(() => {
   setAccordionValue(!showApiKeys || isCustomProvider ? "item-1" : undefined);
-}, [showApiKeys, isCustomProvider]);
+}, [provider.name]);

This makes the dependency chain more explicit and easier to understand.

Also applies to: 58-64

ui/components/sidebar.tsx (1)

260-278: Improve type safety for sub-item rendering.

The sub-item mapping uses any type for the subItem parameter (line 311), which bypasses TypeScript's type checking.

Consider defining a proper type for sub-items:

+interface SubItem {
+  title: string;
+  url: string;
+  icon: React.ComponentType<{ className?: string }>;
+  description: string;
+}
+
 interface DataTableProps {
-  item: (typeof items)[0];
+  item: (typeof items)[0] & { subItems?: SubItem[] };

Then update line 311:

-{item.subItems?.map((subItem: any) => {
+{item.subItems?.map((subItem) => {
framework/configstore/migrations.go (1)

600-634: Migration name doesn't reflect all changes.

The migration ID is "add_plugin_path_column" but it also adds the is_custom column (lines 611-613). This naming mismatch could cause confusion during maintenance.

Consider renaming the migration to reflect both changes:

-ID: "add_plugin_path_column",
+ID: "add_plugin_path_and_is_custom_columns",

Or update the comment on line 600 to clarify:

-// migrationAddPluginPathColumn adds the path column to the plugin table
+// migrationAddPluginPathColumn adds the path and is_custom columns to the plugin table
ui/app/plugins/page.tsx (2)

10-12: Loading state is fetched but unused.

Render a loader/skeleton while plugins load, and handle errors if exposed by the hook.

-const { data: plugins, isLoading } = useGetPluginsQuery();
+const { data: plugins, isLoading, isError, error } = useGetPluginsQuery();
@@
- return (
+ if (isLoading) return <div className="text-sm text-muted-foreground">Loading plugins…</div>;
+ if (isError) return <div className="text-sm text-destructive">Failed to load plugins.</div>;
+ return (

14-20: Add a page heading for a11y/navigation.

Provide a landmark title so screen readers can identify the page.

-<div className="mx-auto w-full max-w-7xl">
+<div className="mx-auto w-full max-w-7xl">
+  <h1 className="sr-only">Plugins</h1>
ui/app/providers/page.tsx (1)

113-124: Avoid timeout for provider selection after save.

Using setTimeout(300) is brittle. Prefer updating selection immediately on onSave, and let the sheet close independently (animations won’t require a delay).

- onSave={(id) => {
-   setTimeout(() => {
-     setProvider(id);
-   }, 300);
-   setShowCustomProviderDialog(false);
- }}
+ onSave={(id) => {
+   setProvider(id);
+   setShowCustomProviderDialog(false);
+ }}

Optionally rename state to showCustomProviderSheet for clarity.

ui/app/providers/views/modelProviderKeysTableView.tsx (2)

88-95: Add accessible label to the primary action.

Give the “Add new key” button an aria-label for clarity.

-<Button
+<Button aria-label="Add new API key"
   onClick={() => {
     handleAddKey(provider.keys.length);
   }}
 >

132-134: Ellipsis menu trigger: add an accessible label.

Screen readers need a descriptive label for the actions menu button.

-<Button onClick={(e) => e.stopPropagation()} variant="ghost">
+<Button aria-label="Open key actions menu" onClick={(e) => e.stopPropagation()} variant="ghost">
ui/app/layout.tsx (1)

41-43: Remove redundant custom-scrollbar and ineffective mx-auto from main element.

Verified: the parent <div> with overflow-auto handles scrolling, making the custom-scrollbar class on <main> redundant. The mx-auto on <main> is ineffective—all 14 pages consistently apply max-w-7xl (and mx-auto) at their own level, not relying on the layout's main container. Trimming these unused classes improves clarity.

ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (1)

154-154: Redundant value prop in Input field.

The explicit value={field.value || ""} is unnecessary because {...field} already spreads all field props including value. The fallback to empty string is also redundant since base_url is initialized to "" in defaultValues (line 48).

Apply this diff to simplify:

-<Input placeholder={"https://api.your-provider.com"} {...field} value={field.value || ""} />
+<Input placeholder="https://api.your-provider.com" {...field} />
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6f4b95 and a3a156b.

⛔ Files ignored due to path filters (3)
  • plugins/governance/go.sum is excluded by !**/*.sum
  • plugins/semanticcache/go.sum is excluded by !**/*.sum
  • plugins/telemetry/go.sum is excluded by !**/*.sum
📒 Files selected for processing (29)
  • framework/configstore/migrations.go (2 hunks)
  • framework/configstore/rdb.go (2 hunks)
  • framework/configstore/tables/plugin.go (1 hunks)
  • transports/bifrost-http/handlers/plugins.go (1 hunks)
  • transports/bifrost-http/lib/config.go (1 hunks)
  • ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1 hunks)
  • ui/app/adaptive-routing/page.tsx (1 hunks)
  • ui/app/alert-channels/page.tsx (1 hunks)
  • ui/app/audit-logs/page.tsx (1 hunks)
  • ui/app/cluster/page.tsx (1 hunks)
  • ui/app/config/page.tsx (1 hunks)
  • ui/app/guardrails/page.tsx (1 hunks)
  • ui/app/layout.tsx (1 hunks)
  • ui/app/logs/page.tsx (1 hunks)
  • ui/app/logs/views/logsTable.tsx (2 hunks)
  • ui/app/mcp-clients/page.tsx (1 hunks)
  • ui/app/observability/page.tsx (1 hunks)
  • ui/app/plugins/page.tsx (1 hunks)
  • ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (5 hunks)
  • ui/app/providers/dialogs/addNewKeySheet.tsx (3 hunks)
  • ui/app/providers/page.tsx (3 hunks)
  • ui/app/providers/views/modelProviderConfig.tsx (1 hunks)
  • ui/app/providers/views/modelProviderKeysTableView.tsx (3 hunks)
  • ui/app/providers/views/providerKeyForm.tsx (5 hunks)
  • ui/app/scim/page.tsx (1 hunks)
  • ui/app/user-groups/page.tsx (1 hunks)
  • ui/app/virtual-keys/page.tsx (2 hunks)
  • ui/components/sidebar.tsx (11 hunks)
  • ui/lib/types/plugins.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • transports/bifrost-http/lib/config.go
🚧 Files skipped from review as they are similar to previous changes (13)
  • ui/app/scim/page.tsx
  • ui/app/mcp-clients/page.tsx
  • ui/app/guardrails/page.tsx
  • ui/app/logs/page.tsx
  • ui/app/alert-channels/page.tsx
  • ui/app/user-groups/page.tsx
  • ui/app/providers/dialogs/addNewKeySheet.tsx
  • ui/app/adaptive-routing/page.tsx
  • ui/app/providers/views/providerKeyForm.tsx
  • ui/app/cluster/page.tsx
  • ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx
  • ui/app/virtual-keys/page.tsx
  • ui/app/audit-logs/page.tsx
🧰 Additional context used
🧬 Code graph analysis (6)
ui/app/providers/views/modelProviderKeysTableView.tsx (2)
ui/components/ui/alertDialog.tsx (1)
  • AlertDialogCancel (85-85)
ui/app/providers/dialogs/addNewKeySheet.tsx (1)
  • AddNewKeySheet (14-46)
ui/app/providers/views/modelProviderConfig.tsx (2)
ui/lib/types/config.ts (1)
  • isKnownProvider (15-17)
ui/app/providers/views/utils.ts (1)
  • keysRequired (1-1)
framework/configstore/migrations.go (1)
framework/configstore/tables/plugin.go (2)
  • TablePlugin (12-24)
  • TablePlugin (27-27)
ui/components/sidebar.tsx (1)
ui/components/ui/sidebar.tsx (5)
  • SidebarMenuSub (613-613)
  • SidebarMenuSubItem (615-615)
  • SidebarMenuSubButton (614-614)
  • SidebarContent (598-598)
  • SidebarGroup (600-600)
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (1)
ui/components/ui/input.tsx (1)
  • Input (15-69)
ui/app/providers/page.tsx (1)
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (1)
  • AddCustomProviderSheet (41-176)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (17)
ui/app/providers/views/modelProviderConfig.tsx (2)

47-47: LGTM: Typo fix improves code clarity.

Good catch on the typo! The variable name isCustomProvider is now consistent with its purpose and more readable than the previous isCustomProver.


52-52: LGTM: Good extraction for clarity.

Extracting keysRequired(provider.name) into a named constant showApiKeys improves readability and makes the intent clearer throughout the component.

ui/app/logs/views/logsTable.tsx (1)

81-82: LGTM! Vertical sizing adjustment aligns with layout standardization.

The height increase from calc(100vh-20rem) to calc(100vh-16.5rem) provides 3.5rem additional vertical space, which aligns with the PR's broader layout changes that add centered, max-width containers across pages.

framework/configstore/tables/plugin.go (1)

20-20: LGTM! New IsCustom field properly integrated.

The IsCustom field addition is well-structured and aligns with the database migration in framework/configstore/migrations.go (lines 611-613) that adds the is_custom column. The field is properly exported with a JSON tag for API serialization.

ui/components/sidebar.tsx (3)

74-92: LGTM! Observability restructured with logical sub-navigation.

The conversion of Observability into a parent item with "Logs" and "Connectors" sub-items provides better organization. The icon choices (Telescope for parent, Logs and ChevronsLeftRightEllipsis for children) are appropriate.


106-136: Well-structured Governance section with comprehensive sub-items.

The new Governance section effectively groups access control features (Virtual Keys, Users & Groups, User Provisioning, Audit Logs) under a single parent with the Landmark icon. The structure improves discoverability and navigation hierarchy.


139-143: ✓ Verified: /plugins route exists

The Plugins menu item is properly configured. The /plugins route page component has been confirmed at ui/app/plugins/page.tsx.

framework/configstore/migrations.go (1)

611-625: LGTM! Migration correctly handles both columns in forward and rollback.

The migration properly adds both path and is_custom columns together in the Migrate step and removes both in the Rollback step, maintaining database consistency.

ui/app/observability/page.tsx (1)

4-8: LGTM! Layout wrapper aligns with PR-wide standardization.

The max-w-7xl mx-auto w-full wrapper provides consistent centering and width constraints, matching the pattern applied across other pages (logs, audit-logs, config, etc.) in this PR.

ui/app/config/page.tsx (1)

64-64: LGTM! Layout constraint added for consistency.

Adding max-w-7xl mx-auto to the config page maintains visual consistency with other pages updated in this PR while preserving the existing two-column layout structure.

ui/lib/types/plugins.ts (1)

10-11: LGTM! Frontend types properly synchronized with backend.

The isCustom and path fields correctly match the backend TablePlugin struct in framework/configstore/tables/plugin.go (lines 16, 20), including JSON tag naming conventions.

ui/app/providers/page.tsx (2)

24-24: Switch to Sheet looks good.

Import aligns with the default export in the new component. No issues spotted.


94-94: Layout wrapper update LGTM.

Constrained, centered content improves consistency with the new global layout.

ui/app/providers/views/modelProviderKeysTableView.tsx (2)

49-51: Cancel handler improvement LGTM.

AlertDialogCancel now resets local state; good UX and state hygiene.


78-83: Sheet migration LGTM.

AddNewKeySheet wiring (show/cancel/provider/keyIndex) mirrors prior dialog behavior.

ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (2)

5-5: LGTM! Clean Dialog → Sheet conversion.

The import changes and component renaming correctly implement the Dialog to Sheet migration pattern. The changes align with the broader UI refactoring mentioned in the PR objectives.

Also applies to: 41-41


163-170: LGTM! Appropriate footer button alignment.

The nested div with ml-auto correctly aligns the Cancel/Add buttons to the right side of the SheetFooter while maintaining consistent spacing. This is a standard pattern for sheet footers in shadcn/ui.

Comment on lines +854 to +857
// Mark plugin as custom if path is not empty
if plugin.Path != nil && *plugin.Path != "" {
plugin.IsCustom = true
}
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 | 🟠 Major

Derive IsCustom deterministically (also when path is cleared).

Currently only sets true; if a plugin’s path becomes empty, IsCustom may stay stale. Compute from Path every time.

-// Mark plugin as custom if path is not empty
-if plugin.Path != nil && *plugin.Path != "" {
-    plugin.IsCustom = true
-}
+// Derive IsCustom from Path (trimmed) on every write
+if plugin.Path != nil && strings.TrimSpace(*plugin.Path) != "" {
+    plugin.IsCustom = true
+} else {
+    plugin.IsCustom = false
+}

Add import:

 import (
   "context"
   "encoding/json"
   "errors"
   "fmt"
+  "strings"
 )
📝 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
// Mark plugin as custom if path is not empty
if plugin.Path != nil && *plugin.Path != "" {
plugin.IsCustom = true
}
// Derive IsCustom from Path (trimmed) on every write
if plugin.Path != nil && strings.TrimSpace(*plugin.Path) != "" {
plugin.IsCustom = true
} else {
plugin.IsCustom = false
}
🤖 Prompt for AI Agents
In framework/configstore/rdb.go around lines 854 to 857, the code only sets
plugin.IsCustom true when Path is non-empty, leaving it stale if Path is later
cleared; change the logic to deterministically compute IsCustom from Path on
every update (e.g., set plugin.IsCustom to the boolean expression (plugin.Path
!= nil && *plugin.Path != "")) so it becomes false when Path is cleared; update
any related code paths accordingly and add any necessary import if you reference
helpers for nil/empty checks.

Comment on lines +873 to +877
// Mark plugin as custom if path is not empty
if plugin.Path != nil && *plugin.Path != "" {
plugin.IsCustom = true
}

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 | 🟠 Major

Apply the same IsCustom derivation in UpdatePlugin.

Mirror the deterministic computation here as well to avoid divergence.

-// Mark plugin as custom if path is not empty
-if plugin.Path != nil && *plugin.Path != "" {
-    plugin.IsCustom = true
-}
+// Derive IsCustom from Path consistently
+if plugin.Path != nil && strings.TrimSpace(*plugin.Path) != "" {
+    plugin.IsCustom = true
+} else {
+    plugin.IsCustom = 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
// Mark plugin as custom if path is not empty
if plugin.Path != nil && *plugin.Path != "" {
plugin.IsCustom = true
}
// Derive IsCustom from Path consistently
if plugin.Path != nil && strings.TrimSpace(*plugin.Path) != "" {
plugin.IsCustom = true
} else {
plugin.IsCustom = false
}
🤖 Prompt for AI Agents
In framework/configstore/rdb.go around lines 873-877, the code sets
plugin.IsCustom = true when plugin.Path is non-nil and non-empty; apply the same
deterministic derivation inside UpdatePlugin so updates can't diverge: in the
UpdatePlugin implementation recompute plugin.IsCustom = (plugin.Path != nil &&
*plugin.Path != "") before saving (and handle the false case explicitly),
ensuring the updated record persists the derived IsCustom value consistently.

Name string `json:"name"`
Enabled bool `json:"enabled"`
Config map[string]any `json:"config"`
Path *string `json:"path"`
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

🧩 Analysis chain

Verify Path field usage in create handler.

The Path field is added to CreatePluginRequest but is not used in the createPlugin handler (lines 134-138). The Path is not passed to h.configStore.CreatePlugin() or to h.pluginsLoader.ReloadPlugin() at line 153.

Check if Path handling is implemented elsewhere or if this is incomplete:


🏁 Script executed:

#!/bin/bash
# Description: Check if Path is used in plugin creation logic

# Check CreatePlugin implementation in configstore
ast-grep --pattern 'func $_ CreatePlugin($$$) $_ {
  $$$
}'

# Check if Path is passed anywhere in the handlers
rg -n "request\.Path" transports/bifrost-http/handlers/plugins.go -A 3 -B 3

# Check rdb.go for Path handling in CreatePlugin
rg -n "Path" framework/configstore/rdb.go -A 3 -B 3 | head -30

Length of output: 858


I need to examine the createPlugin handler implementation to understand the complete flow of how the Path field is handled.


🏁 Script executed:

#!/bin/bash
# Description: Examine the createPlugin handler and how Path is handled

# Show the createPlugin handler
rg -n "func.*createPlugin" transports/bifrost-http/handlers/plugins.go -A 30

# Search for all Path references in the entire handler file
rg -n "Path" transports/bifrost-http/handlers/plugins.go -B 2 -A 2

# Check if there's a mapping or assignment from request to the plugin object
rg -n "CreatePluginRequest\|CreatePlugin" transports/bifrost-http/handlers/plugins.go -B 3 -A 3

Length of output: 1303


Path field is not extracted from request in createPlugin handler.

The review comment is verified as correct. The CreatePluginRequest struct includes the Path field (line 44), but the createPlugin handler (lines 114-142) does not extract it from the request. When calling h.configStore.CreatePlugin() at lines 134-138, only Name, Enabled, and Config are passed to the TablePlugin struct—Path is omitted entirely.

However, the underlying configstore implementation in rdb.go (lines 855, 874) expects and uses the Path field to determine if a plugin should be marked as custom. This creates an incomplete implementation where the field is defined but never passed through the handler layer.

The Path field must be extracted from the request and included in the TablePlugin struct passed to configstore.CreatePlugin() at lines 134-138.

🤖 Prompt for AI Agents
transports/bifrost-http/handlers/plugins.go lines ~44 and 114-142: the
CreatePluginRequest defines Path at line 44 but the createPlugin handler (lines
~114-142) never reads that field and omits it when calling
h.configStore.CreatePlugin() (call around lines 134-138). Update the handler to
extract req.Path from the parsed request and set the TablePlugin.Path field when
constructing the object passed to h.configStore.CreatePlugin(); preserve
existing behavior for nil/empty (pass nil or empty string as-is) and include any
necessary validation or sanitization consistent with other fields before calling
CreatePlugin.

Comment on lines +36 to 56
Read our{" "}
<Link className="text-primary hover:underline dark:text-green-400" href="https://docs.getbifrost.ai/plugins" target="_blank">
documentation
</Link>{" "}
to learn more about plugins.
</div>
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 | 🟡 Minor

External link: add rel to avoid reverse‑tabnabbing.

Add rel="noopener noreferrer" with target="_blank".

-<Link className="text-primary hover:underline dark:text-green-400" href="https://docs.getbifrost.ai/plugins" target="_blank">
+<Link className="text-primary hover:underline dark:text-green-400" href="https://docs.getbifrost.ai/plugins" target="_blank" rel="noopener noreferrer">
📝 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
Read our{" "}
<Link className="text-primary hover:underline dark:text-green-400" href="https://docs.getbifrost.ai/plugins" target="_blank">
documentation
</Link>{" "}
to learn more about plugins.
</div>
Read our{" "}
<Link className="text-primary hover:underline dark:text-green-400" href="https://docs.getbifrost.ai/plugins" target="_blank" rel="noopener noreferrer">
documentation
</Link>{" "}
to learn more about plugins.
</div>
🤖 Prompt for AI Agents
In ui/app/plugins/page.tsx around lines 36 to 41, the external Link opens with
target="_blank" but is missing rel attributes; add rel="noopener noreferrer" to
the Link component (alongside the existing target="_blank") so the rendered
anchor includes rel="noopener noreferrer" to prevent reverse-tabnabbing.

<DialogDescription>Enter the details of your custom provider.</DialogDescription>
</DialogHeader>
<Sheet open={show} onOpenChange={(open) => !open && onClose()}>
<SheetContent className="custom-scrollbar dark:bg-card bg-white py-4 min-w-[600px]">
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

🧩 Analysis chain

Verify responsive behavior with fixed minimum width.

The min-w-[600px] could cause horizontal overflow on smaller viewports (tablets in portrait, mobile landscape). Consider using responsive breakpoints or a max-width constraint instead.

Run this script to check if other Sheet usages in the codebase have similar fixed widths:


🏁 Script executed:

#!/bin/bash
# Description: Check for fixed width patterns in Sheet components across the codebase

# Search for SheetContent with min-w or w- classes
rg -n "SheetContent.*min-w-\[" --type tsx --type ts -C2

# Also check for responsive patterns in Sheet components
rg -n "SheetContent.*className" --type tsx --type ts | head -20

Length of output: 55


🏁 Script executed:

#!/bin/bash
# Description: Check for fixed width patterns in Sheet components across the codebase
# Using corrected ripgrep syntax with glob patterns

# Search for SheetContent with min-w or w- classes using glob patterns
rg -n "SheetContent.*min-w-\[" --include="*.tsx" --include="*.ts" -C2

# Check all SheetContent usages to see width patterns
echo "=== All SheetContent className usages ===" 
rg -n "SheetContent.*className" --include="*.tsx" --include="*.ts" -B1 -A1

# Also look at the specific file mentioned in the review
echo "=== Checking addNewCustomProviderSheet.tsx ===" 
rg -n "SheetContent" ui/app/providers/dialogs/addNewCustomProviderSheet.tsx -B2 -A2

Length of output: 1728



🏁 Script executed:

#!/bin/bash
# Description: Find all SheetContent usages to compare width patterns

# Find all files containing SheetContent
echo "=== All SheetContent usages across codebase ==="
fd -e tsx -e ts | xargs rg -n "SheetContent" -B1 -A1 | head -100

# Also check what the Sheet component itself expects/provides
echo ""
echo "=== Sheet component definition ==="
fd -name "sheet*" -e tsx -e ts | head -5

Length of output: 6780



🏁 Script executed:

#!/bin/bash
# Description: Examine the Sheet component definition to understand constraints

# View the sheet component source
cat ui/components/ui/sheet.tsx

Length of output: 3791


Inconsistent responsive width: use responsive max-width instead of fixed minimum.

The min-w-[600px] causes horizontal overflow on tablets and mobile devices. Replace with the responsive pattern used throughout the codebase: w-full flex-col overflow-x-hidden bg-white p-8 sm:max-w-2xl.

Note: addNewKeySheet.tsx (line 21) has the same issue and should be updated similarly.

🤖 Prompt for AI Agents
In ui/app/providers/dialogs/addNewCustomProviderSheet.tsx around line 98,
replace the fixed CSS class min-w-[600px] which causes horizontal overflow on
smaller screens with the responsive pattern used elsewhere: use full width with
responsive max-width and padding (e.g. w-full flex-col overflow-x-hidden
bg-white p-8 sm:max-w-2xl) to ensure it scales on mobile/tablet; also update
addNewKeySheet.tsx (around line 21) the same way to keep consistency.

Comment on lines 404 to 411
useEffect(() => {
const newExpandedItems = new Set<string>();

enterpriseItems.forEach(item => {
if ('subItems' in item && item.subItems && Array.isArray(item.subItems)) {
const hasActiveSubItem = item.subItems.some((subItem: { url: string }) => pathname.startsWith(subItem.url));
if (hasActiveSubItem) {
newExpandedItems.add(item.title);
}
}
});


if (newExpandedItems.size > 0) {
setExpandedItems(prev => new Set([...prev, ...newExpandedItems]));
setExpandedItems((prev) => new Set([...prev, ...newExpandedItems]));
}
}, [pathname]);
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 | 🟡 Minor

Auto-expansion logic appears incomplete.

The auto-expand effect creates a newExpandedItems Set but never populates it. The logic that should identify which items to auto-expand based on active subitems is missing.

Apply this diff to restore auto-expansion when subitems are active:

 useEffect(() => {
   const newExpandedItems = new Set<string>();
+  items.forEach((item) => {
+    if (item.subItems?.some((subItem) => pathname.startsWith(subItem.url))) {
+      newExpandedItems.add(item.title);
+    }
+  });
 
   if (newExpandedItems.size > 0) {
     setExpandedItems((prev) => new Set([...prev, ...newExpandedItems]));
   }
 }, [pathname]);
📝 Committable suggestion

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

Suggested change
useEffect(() => {
const newExpandedItems = new Set<string>();
enterpriseItems.forEach(item => {
if ('subItems' in item && item.subItems && Array.isArray(item.subItems)) {
const hasActiveSubItem = item.subItems.some((subItem: { url: string }) => pathname.startsWith(subItem.url));
if (hasActiveSubItem) {
newExpandedItems.add(item.title);
}
}
});
if (newExpandedItems.size > 0) {
setExpandedItems(prev => new Set([...prev, ...newExpandedItems]));
setExpandedItems((prev) => new Set([...prev, ...newExpandedItems]));
}
}, [pathname]);
useEffect(() => {
const newExpandedItems = new Set<string>();
items.forEach((item) => {
if (item.subItems?.some((subItem) => pathname.startsWith(subItem.url))) {
newExpandedItems.add(item.title);
}
});
if (newExpandedItems.size > 0) {
setExpandedItems((prev) => new Set([...prev, ...newExpandedItems]));
}
}, [pathname]);
🤖 Prompt for AI Agents
In ui/components/sidebar.tsx around lines 404 to 410, the effect creates a
newExpandedItems Set but never fills it, so auto-expansion for active subitems
is missing; update the effect to iterate your sidebar data (e.g., sidebarItems
or nav structure) and add the parent item IDs to newExpandedItems when any of
their child/subitem paths match the current pathname (or pathname startsWith the
child path), then call setExpandedItems(prev => new Set([...prev,
...newExpandedItems])) as already done; ensure you handle edge cases (no
children, trailing slashes) and keep the dependency on pathname.

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

Caution

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

⚠️ Outside diff range comments (4)
framework/configstore/migrations.go (1)

607-614: Critical: Independent column checks needed to prevent schema inconsistency.

The is_custom column addition is nested inside the path column existence check. If the path column already exists (e.g., from a partial migration or earlier version), the is_custom column won't be added, creating schema inconsistency across installations.

This will cause runtime failures when the application expects the is_custom column but it's missing.

Apply this diff to check each column independently:

 		if !migrator.HasColumn(&tables.TablePlugin{}, "path") {
 			if err := migrator.AddColumn(&tables.TablePlugin{}, "path"); err != nil {
 				return err
 			}
+		}
+		if !migrator.HasColumn(&tables.TablePlugin{}, "is_custom") {
 			if err := migrator.AddColumn(&tables.TablePlugin{}, "is_custom"); err != nil {
 				return err
 			}
 		}
transports/bifrost-http/handlers/plugins.go (2)

134-138: Critical: Path field not being passed to database.

The Path field is accepted in CreatePluginRequest (line 44) but is not included when creating the TablePlugin at lines 134-138. This means the Path will always be nil in the database, and IsCustom will never be set to true in the creation flow.

Apply this diff to pass the Path field:

 if err := h.configStore.CreatePlugin(ctx, &configstoreTables.TablePlugin{
 	Name:    request.Name,
 	Enabled: request.Enabled,
 	Config:  request.Config,
+	Path:    request.Path,
 }); err != nil {

152-153: Pass Path to plugin loader.

The ReloadPlugin call should include the path from the request to properly load custom plugins.

Apply this diff:

-if err := h.pluginsLoader.ReloadPlugin(ctx, request.Name, nil, request.Config); err != nil {
+if err := h.pluginsLoader.ReloadPlugin(ctx, request.Name, request.Path, request.Config); err != nil {
ui/app/plugins/page.tsx (1)

10-47: Missing loading and error states.

The component fetches plugin data but doesn't display loading state or handle errors, which could lead to poor UX when the API is slow or fails.

Apply this diff to add loading and error handling:

 export default function PluginsPage() {
-	const { data: plugins, isLoading } = useGetPluginsQuery();
+	const { data: plugins, isLoading, error } = useGetPluginsQuery();
 	const customPlugins = useMemo(() => plugins?.filter((plugin) => plugin.isCustom), [plugins]);
 
+	if (isLoading) {
+		return (
+			<div className="mx-auto w-full max-w-7xl">
+				<div className="flex items-center justify-center p-8">
+					<span className="text-muted-foreground">Loading plugins...</span>
+				</div>
+			</div>
+		);
+	}
+
+	if (error) {
+		return (
+			<div className="mx-auto w-full max-w-7xl">
+				<div className="flex items-center justify-center p-8">
+					<span className="text-destructive">Failed to load plugins</span>
+				</div>
+			</div>
+		);
+	}
+
 	return (
♻️ Duplicate comments (2)
ui/app/providers/views/providerKeyForm.tsx (1)

81-81: Debug console.log still present.

This debug statement was already flagged in previous reviews and marked as addressed in commit 0536336, but it still appears in the code. Please remove it before merging.

ui/components/sidebar.tsx (1)

146-146: Incomplete rename creates user-facing inconsistency.

Per the previous review, renaming "MCP Clients" to "MCP Gateway" only in the sidebar creates inconsistency with the rest of the codebase (table headers, documentation, API hooks, etc. still use "MCP Clients"). Either complete the rename across all user-facing surfaces or revert to "MCP Clients" for consistency.

🧹 Nitpick comments (2)
framework/configstore/rdb.go (2)

854-857: Consider explicit false assignment for IsCustom.

The logic correctly sets IsCustom = true when Path exists, but relies on Go's zero value for the false case. While functionally correct, explicitly setting IsCustom = false when Path is nil/empty would make the intent clearer.

Apply this diff to make the logic explicit:

 // Mark plugin as custom if path is not empty
 if plugin.Path != nil && *plugin.Path != "" {
 	plugin.IsCustom = true
+} else {
+	plugin.IsCustom = false
 }

854-857: Consider extracting duplicate IsCustom logic.

The same logic for setting IsCustom based on Path appears in both CreatePlugin and UpdatePlugin. Consider extracting this to a helper method on TablePlugin to follow DRY principles.

Add a helper method in framework/configstore/tables/plugin.go:

// SetIsCustomFromPath updates IsCustom based on whether Path is set
func (p *TablePlugin) SetIsCustomFromPath() {
	p.IsCustom = p.Path != nil && *p.Path != ""
}

Then use it in both locations:

-// Mark plugin as custom if path is not empty
-if plugin.Path != nil && *plugin.Path != "" {
-	plugin.IsCustom = true
-}
+plugin.SetIsCustomFromPath()

Also applies to: 873-877

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6f4b95 and a3a156b.

⛔ Files ignored due to path filters (3)
  • plugins/governance/go.sum is excluded by !**/*.sum
  • plugins/semanticcache/go.sum is excluded by !**/*.sum
  • plugins/telemetry/go.sum is excluded by !**/*.sum
📒 Files selected for processing (29)
  • framework/configstore/migrations.go (2 hunks)
  • framework/configstore/rdb.go (2 hunks)
  • framework/configstore/tables/plugin.go (1 hunks)
  • transports/bifrost-http/handlers/plugins.go (1 hunks)
  • transports/bifrost-http/lib/config.go (1 hunks)
  • ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1 hunks)
  • ui/app/adaptive-routing/page.tsx (1 hunks)
  • ui/app/alert-channels/page.tsx (1 hunks)
  • ui/app/audit-logs/page.tsx (1 hunks)
  • ui/app/cluster/page.tsx (1 hunks)
  • ui/app/config/page.tsx (1 hunks)
  • ui/app/guardrails/page.tsx (1 hunks)
  • ui/app/layout.tsx (1 hunks)
  • ui/app/logs/page.tsx (1 hunks)
  • ui/app/logs/views/logsTable.tsx (2 hunks)
  • ui/app/mcp-clients/page.tsx (1 hunks)
  • ui/app/observability/page.tsx (1 hunks)
  • ui/app/plugins/page.tsx (1 hunks)
  • ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (5 hunks)
  • ui/app/providers/dialogs/addNewKeySheet.tsx (3 hunks)
  • ui/app/providers/page.tsx (3 hunks)
  • ui/app/providers/views/modelProviderConfig.tsx (1 hunks)
  • ui/app/providers/views/modelProviderKeysTableView.tsx (3 hunks)
  • ui/app/providers/views/providerKeyForm.tsx (5 hunks)
  • ui/app/scim/page.tsx (1 hunks)
  • ui/app/user-groups/page.tsx (1 hunks)
  • ui/app/virtual-keys/page.tsx (2 hunks)
  • ui/components/sidebar.tsx (11 hunks)
  • ui/lib/types/plugins.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • transports/bifrost-http/lib/config.go
🚧 Files skipped from review as they are similar to previous changes (12)
  • ui/app/observability/page.tsx
  • ui/app/guardrails/page.tsx
  • ui/app/logs/views/logsTable.tsx
  • ui/app/user-groups/page.tsx
  • ui/app/scim/page.tsx
  • ui/app/alert-channels/page.tsx
  • ui/app/mcp-clients/page.tsx
  • ui/app/providers/page.tsx
  • ui/app/providers/dialogs/addNewKeySheet.tsx
  • ui/app/logs/page.tsx
  • ui/app/audit-logs/page.tsx
  • ui/app/virtual-keys/page.tsx
🧰 Additional context used
🧬 Code graph analysis (8)
ui/app/providers/views/providerKeyForm.tsx (2)
ui/app/providers/fragments/apiKeysFormFragment.tsx (1)
  • ApiKeyFormFragment (21-445)
ui/components/ui/tooltip.tsx (1)
  • TooltipContent (43-43)
ui/app/providers/views/modelProviderKeysTableView.tsx (1)
ui/app/providers/dialogs/addNewKeySheet.tsx (1)
  • AddNewKeySheet (14-46)
ui/app/adaptive-routing/page.tsx (1)
ui/app/_fallbacks/enterprise/components/adaptive-routing/adaptiveRoutingView.tsx (1)
  • AdaptiveRoutingView (4-16)
framework/configstore/migrations.go (1)
framework/configstore/tables/plugin.go (2)
  • TablePlugin (12-24)
  • TablePlugin (27-27)
ui/components/sidebar.tsx (1)
ui/components/ui/sidebar.tsx (5)
  • SidebarMenuSub (613-613)
  • SidebarMenuSubItem (615-615)
  • SidebarMenuSubButton (614-614)
  • SidebarContent (598-598)
  • SidebarGroup (600-600)
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)
ui/app/_fallbacks/enterprise/components/views/contactUsView.tsx (1)
  • ContactUsView (15-46)
ui/app/providers/views/modelProviderConfig.tsx (2)
ui/lib/types/config.ts (1)
  • isKnownProvider (15-17)
ui/app/providers/views/utils.ts (1)
  • keysRequired (1-1)
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (2)
ui/components/ui/sheet.tsx (6)
  • Sheet (90-90)
  • SheetContent (90-90)
  • SheetHeader (90-90)
  • SheetTitle (90-90)
  • SheetDescription (90-90)
  • SheetFooter (90-90)
ui/components/ui/input.tsx (1)
  • Input (15-69)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (29)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (20)
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (2)

1-2: LGTM! Clean imports.

The Siren icon is semantically appropriate for alert channels, and the relative import path correctly references the ContactUsView component.


4-16: LGTM! Well-structured enterprise fallback component.

The implementation is clean and follows the expected pattern for enterprise feature gates. All required props are correctly passed to ContactUsView, the content is clear and appropriate, and the layout ensures proper centering and spacing.

ui/app/cluster/page.tsx (1)

4-8: LGTM! Consistent layout pattern applied.

The centered max-width wrapper aligns with the layout standardization across the PR.

ui/app/adaptive-routing/page.tsx (1)

4-8: LGTM! Consistent layout pattern applied.

Same centered max-width wrapper as other pages in this PR.

ui/app/config/page.tsx (1)

64-64: LGTM! Layout standardization applied.

Adding max-width constraint aligns with the consistent layout approach across the PR.

ui/app/providers/views/providerKeyForm.tsx (2)

53-61: LGTM! Well-structured tooltip logic.

The memoized callback efficiently computes tooltip content based on form state with correct dependencies.


88-88: LGTM! Typo fix and improved conditional rendering.

The className typo correction and conditional TooltipContent rendering improve code quality.

Also applies to: 107-107

ui/app/providers/views/modelProviderConfig.tsx (1)

46-60: LGTM! Improved accordion control and typo fix.

The controlled accordion state with proper initialization based on provider type and API key requirements is well-implemented. The variable rename from isCustomProver to isCustomProvider corrects a typo.

Also applies to: 64-64

ui/app/providers/views/modelProviderKeysTableView.tsx (1)

23-23: LGTM! Clean migration to Sheet component.

The transition from AddNewKeyDialog to AddNewKeySheet with proper keyIndex prop passing supports the editing functionality correctly.

Also applies to: 78-83

ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (1)

5-5: LGTM! Complete migration to Sheet pattern.

The component has been successfully migrated from Dialog to Sheet, maintaining all functionality while adopting the consistent Sheet-based UI pattern used throughout the PR.

Also applies to: 41-41, 97-174

framework/configstore/tables/plugin.go (1)

20-20: LGTM! IsCustom field properly integrated.

The new IsCustom boolean field is correctly added with appropriate JSON serialization. The field integrates well with the existing serialization hooks and will be automatically persisted by GORM as is_custom column.

ui/app/layout.tsx (1)

41-41: Layout refactor aligns with page-level container pattern.

Switching from fixed-width responsive classes to full-width flexible layout is consistent with the PR's pattern of applying centered max-width containers at the page level (e.g., mx-auto max-w-7xl in various pages).

ui/lib/types/plugins.ts (1)

10-11: LGTM! Type definitions match backend schema.

The new isCustom and path fields correctly align with the backend TablePlugin struct, using appropriate TypeScript types (boolean and nullable string).

transports/bifrost-http/handlers/plugins.go (1)

44-44: Path field added to request type.

The new Path field enables custom plugin installations via path specification.

ui/app/plugins/page.tsx (1)

10-11: Good use of memoization for custom plugin filtering.

The useMemo hook efficiently filters plugins by the isCustom flag, preventing unnecessary re-computations.

ui/components/sidebar.tsx (5)

74-92: Well-structured Observability section with subItems.

The restructured Observability navigation with Logs and Connectors as subItems provides better organization. The icon choices (Telescope for parent, Logs and ChevronsLeftRightEllipsis for children) are appropriate.


106-136: Governance section properly organized.

The new Governance section with Virtual Keys, Users & Groups, User Provisioning, and Audit Logs as subItems follows a logical structure. The Landmark icon appropriately represents governance concepts.


139-143: Plugins navigation item added correctly.

The new Plugins menu item is properly structured with appropriate icon (Puzzle) and description. The /plugins route corresponds to the page component added in this PR.


260-336: SubItem implementation is robust.

The enhanced SidebarItem component properly handles:

  • SubItem expansion/collapse with chevron rotation
  • Active state detection for both parent and sub-items
  • Click handling that distinguishes between navigation and expansion
  • Proper styling for hover and active states

The implementation follows good React patterns with appropriate state management.


310-333: SubItem rendering follows consistent styling patterns.

The SidebarMenuSub rendering logic correctly:

  • Applies indentation with left border
  • Renders sub-item icons and titles
  • Handles active states
  • Uses proper spacing and transitions

The use of any type for subItem at line 311 is acceptable given the dynamic nature of the items array.

@akshaydeo akshaydeo force-pushed the 10-16-dynamic_plugin_architecture branch from ddd7b35 to bce70c7 Compare October 22, 2025 21:20
@akshaydeo akshaydeo force-pushed the 10-20-dynamic-plugins-ui branch from a3a156b to de194ef Compare October 22, 2025 21:20
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: 0

Caution

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

⚠️ Outside diff range comments (1)
framework/configstore/migrations.go (1)

611-625: Add is_custom via a fresh migration, not by editing this one.
add_plugin_path_column has already been executed everywhere and its ID is stored in the migration ledger. Updating the existing migration means live databases will not rerun this code, leaving config_plugins.is_custom absent while the rest of the stack now expects it—resulting in runtime failures. Even if the migration were forced to rerun, the guard if !migrator.HasColumn(&tables.TablePlugin{}, "path") short-circuits once path exists, so is_custom would still never be created. Please revert the new column work here and introduce a brand-new migration (with a new ID) that independently checks for/creates is_custom and drops it on rollback.

-               if err := migrator.AddColumn(&tables.TablePlugin{}, "is_custom"); err != nil {
-                   return err
-               }
@@
-           if err := migrator.DropColumn(&tables.TablePlugin{}, "is_custom"); err != nil {
-               return err
-           }

Add a follow-up migration (hook it in triggerMigrations) like:

func migrationAddPluginIsCustomColumn(ctx context.Context, db *gorm.DB) error {
	m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{
		ID: "add_plugin_is_custom_column",
		Migrate: func(tx *gorm.DB) error {
			tx = tx.WithContext(ctx)
			migrator := tx.Migrator()
			if !migrator.HasColumn(&tables.TablePlugin{}, "is_custom") {
				if err := migrator.AddColumn(&tables.TablePlugin{}, "is_custom"); err != nil {
					return err
				}
			}
			return nil
		},
		Rollback: func(tx *gorm.DB) error {
			tx = tx.WithContext(ctx)
			return tx.Migrator().DropColumn(&tables.TablePlugin{}, "is_custom")
		},
	}})
	return wrapMigrateError(m.Migrate())
}
♻️ Duplicate comments (6)
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (1)

98-170: Fix the sheet width for small screens.

Line 99 still forces min-w-[600px], which overflows on phones and small tablets. Please switch to a responsive pattern (full width on mobile with a max width at sm+) so the sheet renders without horizontal scrolling.

-			<SheetContent className="custom-scrollbar dark:bg-card bg-white py-4 min-w-[600px]">
+			<SheetContent className="custom-scrollbar dark:bg-card bg-white py-4 w-full sm:min-w-[600px] sm:max-w-[600px]">
ui/app/providers/dialogs/addNewKeySheet.tsx (1)

20-44: Make the key sheet responsive.

Line 21 keeps min-w-[600px], so the sheet still breaks on sub-600px viewports. Please mirror the responsive width pattern (mobile w-full, constrained width on sm and up) to prevent overflow.

-		<Sheet open={show} onOpenChange={onCancel}>
-			<SheetContent className="custom-scrollbar bg-white dark:bg-card min-w-[600px] py-4">
+		<Sheet open={show} onOpenChange={onCancel}>
+			<SheetContent className="custom-scrollbar bg-white dark:bg-card w-full sm:min-w-[600px] sm:max-w-[600px] py-4">
ui/components/sidebar.tsx (2)

139-143: Rename consistency: “MCP Gateway” vs legacy “MCP Clients” artifacts.

URL remains /mcp-clients, and the custom SVG still has “MCP clients icon” title/aria-label. Either complete the rename (route, component titles, a11y labels) or revert the sidebar label for consistency.

- aria-label="MCP clients icon"
- <title>MCP clients icon</title>
+ aria-label="MCP gateway icon"
+ <title>MCP gateway icon</title>

397-403: Auto‑expand is broken: effect never adds parents with active subitems.

Populate newExpandedItems based on pathname and subItems.

 useEffect(() => {
   const newExpandedItems = new Set<string>();
+  items.forEach((it) => {
+    if (it.subItems?.some((s) => pathname.startsWith(s.url))) {
+      newExpandedItems.add(it.title);
+    }
+  });
 
   if (newExpandedItems.size > 0) {
     setExpandedItems((prev) => new Set([...prev, ...newExpandedItems]));
   }
 }, [pathname]);
ui/app/plugins/page.tsx (2)

22-33: “Install New Plugin” button is inert; wire it or link docs.

Make it navigate (e.g., /plugins/new) or open docs. Remove preventDefault/stopPropagation.

- <Button
-   variant="outline"
-   size="sm"
-   className="w-full justify-start"
-   onClick={(e) => {
-     e.preventDefault();
-     e.stopPropagation();
-   }}
- >
-   <PlusIcon className="h-4 w-4" />
-   <div className="text-xs">Install New Plugin</div>
- </Button>
+ <Link href="/plugins/new">
+   <Button variant="outline" size="sm" className="w-full justify-start">
+     <PlusIcon className="h-4 w-4" />
+     <div className="text-xs">Install New Plugin</div>
+   </Button>
+ </Link>

36-40: External link: add rel to prevent reverse‑tabnabbing.

Include rel="noopener noreferrer" with target="_blank".

- <Link className="text-primary hover:underline dark:text-green-400" href="https://docs.getbifrost.ai/plugins" target="_blank">
+ <Link className="text-primary hover:underline dark:text-green-400" href="https://docs.getbifrost.ai/plugins" target="_blank" rel="noopener noreferrer">
🧹 Nitpick comments (3)
ui/app/plugins/page.tsx (1)

10-12: Handle loading/empty states.

Currently renders an empty list while fetching. Show a loader/skeleton or an empty-state message.

- const { data: plugins, isLoading } = useGetPluginsQuery();
+ const { data: plugins, isLoading } = useGetPluginsQuery();

  const customPlugins = useMemo(() => plugins?.filter((plugin) => plugin.isCustom), [plugins]);
 
- return (
+ if (isLoading) return <div className="p-4 text-sm text-muted-foreground">Loading plugins…</div>;
+ const hasCustom = (customPlugins?.length ?? 0) > 0;
+ return (
    <div className="mx-auto w-full max-w-7xl">
      <div className="flex w-[250px] flex-col gap-2 pb-10">
        <div className="rounded-md bg-zinc-50/50 p-4 dark:bg-zinc-800/20">
          <div className="mb-4">
-           {customPlugins?.map((plugin) => (
+           {!hasCustom && <div className="text-xs text-muted-foreground">No custom plugins yet.</div>}
+           {hasCustom && customPlugins!.map((plugin) => (
              <div key={plugin.name}>{plugin.name}</div>
            ))}
ui/components/sidebar.tsx (2)

73-91: Observability item/URLs: confirm intended default and active states.

Top-level “Observability” points to /logs while a “Logs” subitem also points to /logs; ensure this duplication is intentional. If the intended hub is /observability, consider setting the parent url to /observability and keep “Logs” as a subitem.

- { title: "Observability", url: "/logs", ... subItems: [
+ { title: "Observability", url: "/observability", ... subItems: [
    { title: "Logs", url: "/logs", ... },
    { title: "Connectors", url: "/observability", ... },
  ] }

303-326: Type subItems instead of using any.

Avoid any for subItem; define a SubItem type and use it in items and map.

- { item.subItems?.map((subItem: any) => {
+ type SubItem = { title: string; url: string; icon?: React.ComponentType<{ className?: string }>; description?: string };
+ // items: Array<{ title: string; url: string; icon: any; description?: string; subItems?: SubItem[] }>
+ { item.subItems?.map((subItem: SubItem) => {
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3a156b and de194ef.

⛔ Files ignored due to path filters (3)
  • plugins/governance/go.sum is excluded by !**/*.sum
  • plugins/semanticcache/go.sum is excluded by !**/*.sum
  • plugins/telemetry/go.sum is excluded by !**/*.sum
📒 Files selected for processing (30)
  • framework/configstore/migrations.go (2 hunks)
  • framework/configstore/rdb.go (2 hunks)
  • framework/configstore/tables/plugin.go (1 hunks)
  • transports/bifrost-http/handlers/plugins.go (1 hunks)
  • transports/bifrost-http/lib/config.go (1 hunks)
  • ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1 hunks)
  • ui/app/adaptive-routing/page.tsx (1 hunks)
  • ui/app/alert-channels/page.tsx (1 hunks)
  • ui/app/audit-logs/page.tsx (1 hunks)
  • ui/app/cluster/page.tsx (1 hunks)
  • ui/app/config/page.tsx (1 hunks)
  • ui/app/guardrails/page.tsx (1 hunks)
  • ui/app/layout.tsx (1 hunks)
  • ui/app/logs/page.tsx (1 hunks)
  • ui/app/logs/views/logsTable.tsx (2 hunks)
  • ui/app/mcp-clients/page.tsx (1 hunks)
  • ui/app/observability/page.tsx (1 hunks)
  • ui/app/observability/views/observabilityView.tsx (2 hunks)
  • ui/app/plugins/page.tsx (1 hunks)
  • ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (5 hunks)
  • ui/app/providers/dialogs/addNewKeySheet.tsx (3 hunks)
  • ui/app/providers/page.tsx (3 hunks)
  • ui/app/providers/views/modelProviderConfig.tsx (1 hunks)
  • ui/app/providers/views/modelProviderKeysTableView.tsx (3 hunks)
  • ui/app/providers/views/providerKeyForm.tsx (5 hunks)
  • ui/app/scim/page.tsx (1 hunks)
  • ui/app/user-groups/page.tsx (1 hunks)
  • ui/app/virtual-keys/page.tsx (2 hunks)
  • ui/components/sidebar.tsx (11 hunks)
  • ui/lib/types/plugins.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (17)
  • ui/app/providers/views/providerKeyForm.tsx
  • ui/app/audit-logs/page.tsx
  • framework/configstore/tables/plugin.go
  • ui/app/layout.tsx
  • transports/bifrost-http/lib/config.go
  • framework/configstore/rdb.go
  • ui/app/providers/views/modelProviderConfig.tsx
  • ui/lib/types/plugins.ts
  • ui/app/scim/page.tsx
  • transports/bifrost-http/handlers/plugins.go
  • ui/app/providers/page.tsx
  • ui/app/logs/page.tsx
  • ui/app/virtual-keys/page.tsx
  • ui/app/logs/views/logsTable.tsx
  • ui/app/user-groups/page.tsx
  • ui/app/cluster/page.tsx
  • ui/app/config/page.tsx
🧰 Additional context used
🧬 Code graph analysis (8)
ui/app/providers/views/modelProviderKeysTableView.tsx (1)
ui/app/providers/dialogs/addNewKeySheet.tsx (1)
  • AddNewKeySheet (14-46)
ui/app/observability/page.tsx (1)
ui/app/observability/views/observabilityView.tsx (1)
  • ObservabilityView (67-168)
ui/app/adaptive-routing/page.tsx (1)
ui/app/_fallbacks/enterprise/components/adaptive-routing/adaptiveRoutingView.tsx (1)
  • AdaptiveRoutingView (4-16)
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (1)
ui/components/ui/input.tsx (1)
  • Input (15-69)
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)
ui/app/_fallbacks/enterprise/components/views/contactUsView.tsx (1)
  • ContactUsView (15-46)
ui/components/sidebar.tsx (1)
ui/components/ui/sidebar.tsx (5)
  • SidebarMenuSub (613-613)
  • SidebarMenuSubItem (615-615)
  • SidebarMenuSubButton (614-614)
  • SidebarContent (598-598)
  • SidebarGroup (600-600)
ui/app/alert-channels/page.tsx (1)
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)
  • AlertChannelsView (4-16)
framework/configstore/migrations.go (1)
framework/configstore/tables/plugin.go (2)
  • TablePlugin (12-24)
  • TablePlugin (27-27)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (3)
ui/app/adaptive-routing/page.tsx (1)

4-8: LGTM: layout wrapper added.

Centering with max-w-7xl is consistent with other pages.

ui/app/guardrails/page.tsx (1)

4-8: LGTM: layout wrapper added.

Matches the app-wide container pattern.

ui/app/mcp-clients/page.tsx (1)

29-31: LGTM: width-constrained wrapper.

No functional changes; table still receives a safe fallback array.

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

Caution

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

⚠️ Outside diff range comments (2)
ui/components/sidebar.tsx (2)

268-271: Governance gating bypass: sub-items still navigate when disabled.

Clicking a sub-item routes even if governance is disabled. Block navigation and visually disable sub-items when !isAllowed.

 const handleSubItemClick = (url: string) => {
-  router.push(url);
+  if (!isAllowed) return;
+  router.push(url);
 };
@@
-  <SidebarMenuSubButton
-    className={`h-7 cursor-pointer rounded-md px-2 transition-all duration-200 ${
-      isSubItemActive
-        ? "bg-sidebar-accent text-primary font-medium"
-        : "hover:bg-sidebar-accent hover:text-accent-foreground text-slate-500 dark:text-zinc-400"
-    }`}
-    onClick={() => handleSubItemClick(subItem.url)}
-  >
+  <SidebarMenuSubButton
+    aria-disabled={!isAllowed || undefined}
+    className={`h-7 rounded-md px-2 transition-all duration-200 ${
+      isSubItemActive
+        ? "bg-sidebar-accent text-primary font-medium"
+        : "hover:bg-sidebar-accent hover:text-accent-foreground text-slate-500 dark:text-zinc-400"
+    } ${!isAllowed ? "cursor-not-allowed opacity-50 pointer-events-none" : "cursor-pointer"}`}
+    onClick={() => handleSubItemClick(subItem.url)}
+  >

Also applies to: 304-320


441-447: External link: add rel with target="_blank".

Prevent reverse‑tabnabbing on release notes link.

-<Link
+<Link
   href={`https://docs.getbifrost.ai/changelogs/${latestRelease.name}`}
   target="_blank"
-  className="text-primary mt-auto pb-1 font-medium underline"
+  rel="noopener noreferrer"
+  className="text-primary mt-auto pb-1 font-medium underline"
 >
♻️ Duplicate comments (6)
ui/app/logs/page.tsx (1)

352-352: Replace the tab separator in the Tailwind class list

There’s still a tab character between max-w-7xl and mx-auto; please swap it for a normal space to keep Tailwind class parsing consistent.

-				<div className="space-y-6 max-w-7xl	mx-auto">
+				<div className="space-y-6 max-w-7xl mx-auto">
ui/app/providers/views/providerKeyForm.tsx (1)

81-81: Remove debug logging before shipping.

This console.log reintroduces the debug output we asked to drop earlier; please delete it so production logs stay clean.

-	console.log(form.formState.isDirty);
ui/components/sidebar.tsx (2)

62-66: ARIA text still says “MCP clients”; update to “MCP gateway”.

Aligns with the sidebar label rename.

-  aria-label="MCP clients icon"
+  aria-label="MCP gateway icon"
 ...
-  <title>MCP clients icon</title>
+  <title>MCP gateway icon</title>

397-403: Auto‑expand logic incomplete; never populates the set.

Populate newExpandedItems for parents whose subItem matches the current path.

 useEffect(() => {
   const newExpandedItems = new Set<string>();
+  items.forEach((item) => {
+    if (item.subItems?.some((sub) => pathname.startsWith(sub.url))) {
+      newExpandedItems.add(item.title);
+    }
+  });
 
   if (newExpandedItems.size > 0) {
     setExpandedItems((prev) => new Set([...prev, ...newExpandedItems]));
   }
 }, [pathname]);
ui/app/plugins/page.tsx (2)

22-33: “Install New Plugin” button is inert; wire it or make it a link.

Route to a creation flow or docs instead of preventing default.

-<Button
-  variant="outline"
-  size="sm"
-  className="w-full justify-start"
-  onClick={(e) => {
-    e.preventDefault();
-    e.stopPropagation();
-  }}
->
-  <PlusIcon className="h-4 w-4" />
-  <div className="text-xs">Install New Plugin</div>
-</Button>
+<Link href="/plugins/new">
+  <Button variant="outline" size="sm" className="w-full justify-start">
+    <PlusIcon className="h-4 w-4" />
+    <div className="text-xs">Install New Plugin</div>
+  </Button>
+</Link>

Alternative (temporary):

-<Button ... onClick={...}>
+<a href="https://docs.getbifrost.ai/plugins#install" target="_blank" rel="noopener noreferrer">
+  <Button variant="outline" size="sm" className="w-full justify-start">
     ...
   </Button>
+</a>

36-41: External link: add rel to avoid reverse‑tabnabbing.

Add rel="noopener noreferrer" with target="_blank".

-<Link className="text-primary hover:underline dark:text-green-400" href="https://docs.getbifrost.ai/plugins" target="_blank">
+<Link
+  className="text-primary hover:underline dark:text-green-400"
+  href="https://docs.getbifrost.ai/plugins"
+  target="_blank"
+  rel="noopener noreferrer"
+>
🧹 Nitpick comments (4)
ui/app/plugins/page.tsx (2)

13-16: Add a page title for a11y.

Include a minimal h1 (can be sr-only) so screen readers have a landmark.

 return (
   <div className="mx-auto w-full max-w-7xl">
+    <h1 className="sr-only">Plugins</h1>

10-11: Handle loading/empty states.

Render a small placeholder while fetching and when no custom plugins exist.

-const { data: plugins, isLoading } = useGetPluginsQuery();
+const { data: plugins, isLoading } = useGetPluginsQuery();

-const customPlugins = useMemo(() => plugins?.filter((plugin) => plugin.isCustom), [plugins]);
+const customPlugins = useMemo(() => plugins?.filter((plugin) => plugin.isCustom), [plugins]);

@@
-  <div className="flex w-[250px] flex-col gap-2 pb-10">
+  <div className="flex w-[250px] flex-col gap-2 pb-10">
     <div className="rounded-md bg-zinc-50/50 p-4 dark:bg-zinc-800/20">
       <div className="mb-4">
-        {customPlugins?.map((plugin) => (
-          <div key={plugin.name}>{plugin.name}</div>
-        ))}
+        {isLoading && <div className="text-xs text-muted-foreground">Loading plugins…</div>}
+        {!isLoading && (!customPlugins || customPlugins.length === 0) && (
+          <div className="text-xs text-muted-foreground">No custom plugins installed yet.</div>
+        )}
+        {!isLoading &&
+          customPlugins?.map((plugin) => <div key={plugin.name}>{plugin.name}</div>)}
ui/components/sidebar.tsx (1)

292-295: Optional: logs live‑pulse never shows for Observability (has subItems).

The pulse renders only when !hasSubItems && item.url === "/logs". Consider moving the indicator to the “Logs” sub‑item.

ui/app/observability/views/observabilityView.tsx (1)

82-90: Use null for missing plugin paths and guard redundant dispatch

  • The Plugin.path field is string | null, so replace path: "" with path: null in both fallback objects.
  • Wrap dispatch(setSelectedPlugin(plugin)) in a check (selectedPlugin?.name !== plugin.name) to avoid unnecessary state updates.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a3a156b and de194ef.

⛔ Files ignored due to path filters (3)
  • plugins/governance/go.sum is excluded by !**/*.sum
  • plugins/semanticcache/go.sum is excluded by !**/*.sum
  • plugins/telemetry/go.sum is excluded by !**/*.sum
📒 Files selected for processing (30)
  • framework/configstore/migrations.go (2 hunks)
  • framework/configstore/rdb.go (2 hunks)
  • framework/configstore/tables/plugin.go (1 hunks)
  • transports/bifrost-http/handlers/plugins.go (1 hunks)
  • transports/bifrost-http/lib/config.go (1 hunks)
  • ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1 hunks)
  • ui/app/adaptive-routing/page.tsx (1 hunks)
  • ui/app/alert-channels/page.tsx (1 hunks)
  • ui/app/audit-logs/page.tsx (1 hunks)
  • ui/app/cluster/page.tsx (1 hunks)
  • ui/app/config/page.tsx (1 hunks)
  • ui/app/guardrails/page.tsx (1 hunks)
  • ui/app/layout.tsx (1 hunks)
  • ui/app/logs/page.tsx (1 hunks)
  • ui/app/logs/views/logsTable.tsx (2 hunks)
  • ui/app/mcp-clients/page.tsx (1 hunks)
  • ui/app/observability/page.tsx (1 hunks)
  • ui/app/observability/views/observabilityView.tsx (2 hunks)
  • ui/app/plugins/page.tsx (1 hunks)
  • ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (5 hunks)
  • ui/app/providers/dialogs/addNewKeySheet.tsx (3 hunks)
  • ui/app/providers/page.tsx (3 hunks)
  • ui/app/providers/views/modelProviderConfig.tsx (1 hunks)
  • ui/app/providers/views/modelProviderKeysTableView.tsx (3 hunks)
  • ui/app/providers/views/providerKeyForm.tsx (5 hunks)
  • ui/app/scim/page.tsx (1 hunks)
  • ui/app/user-groups/page.tsx (1 hunks)
  • ui/app/virtual-keys/page.tsx (2 hunks)
  • ui/components/sidebar.tsx (11 hunks)
  • ui/lib/types/plugins.ts (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • ui/app/cluster/page.tsx
🚧 Files skipped from review as they are similar to previous changes (22)
  • ui/app/adaptive-routing/page.tsx
  • ui/app/audit-logs/page.tsx
  • ui/app/providers/page.tsx
  • ui/app/user-groups/page.tsx
  • ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx
  • framework/configstore/tables/plugin.go
  • framework/configstore/rdb.go
  • ui/app/guardrails/page.tsx
  • ui/app/alert-channels/page.tsx
  • ui/app/virtual-keys/page.tsx
  • transports/bifrost-http/lib/config.go
  • ui/app/config/page.tsx
  • ui/app/providers/dialogs/addNewKeySheet.tsx
  • transports/bifrost-http/handlers/plugins.go
  • ui/lib/types/plugins.ts
  • ui/app/scim/page.tsx
  • framework/configstore/migrations.go
  • ui/app/logs/views/logsTable.tsx
  • ui/app/providers/dialogs/addNewCustomProviderSheet.tsx
  • ui/app/layout.tsx
  • ui/app/mcp-clients/page.tsx
  • ui/app/providers/views/modelProviderConfig.tsx
🧰 Additional context used
🧬 Code graph analysis (4)
ui/app/observability/page.tsx (1)
ui/app/observability/views/observabilityView.tsx (1)
  • ObservabilityView (67-168)
ui/app/providers/views/providerKeyForm.tsx (1)
ui/app/providers/fragments/apiKeysFormFragment.tsx (1)
  • ApiKeyFormFragment (21-445)
ui/components/sidebar.tsx (1)
ui/components/ui/sidebar.tsx (5)
  • SidebarMenuSub (613-613)
  • SidebarMenuSubItem (615-615)
  • SidebarMenuSubButton (614-614)
  • SidebarContent (598-598)
  • SidebarGroup (600-600)
ui/app/providers/views/modelProviderKeysTableView.tsx (2)
ui/components/ui/alertDialog.tsx (1)
  • AlertDialogCancel (85-85)
ui/app/providers/dialogs/addNewKeySheet.tsx (1)
  • AddNewKeySheet (14-46)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (14)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (1)
ui/app/observability/page.tsx (1)

4-8: LGTM — layout wrapper is appropriate.

@akshaydeo akshaydeo force-pushed the 10-16-dynamic_plugin_architecture branch from bce70c7 to 127675d Compare October 23, 2025 20:23
@akshaydeo akshaydeo force-pushed the 10-20-dynamic-plugins-ui branch from de194ef to e14cdec Compare October 23, 2025 20:23
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: 4

Caution

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

⚠️ Outside diff range comments (4)
transports/bifrost-http/handlers/inference.go (2)

832-900: SSE “[DONE]” emission is incorrect; requestType is never set.

[DONE] is sent for Responses API too, diverging from OpenAI semantics. Track whether the stream is “responses” and skip [DONE].

Proposed fix:

- var requestType schemas.RequestType
+ isResponses := false

   // Use streaming response writer
   ctx.Response.SetBodyStreamWriter(func(w *bufio.Writer) {
@@
-     includeEventType := false
+     includeEventType := false
      if chunk.BifrostResponsesStreamResponse != nil ||
         (chunk.BifrostError != nil && chunk.BifrostError.ExtraFields.RequestType == schemas.ResponsesStreamRequest) {
         includeEventType = true
      }
+     if includeEventType {
+       isResponses = true
+     }
@@
-     if requestType != schemas.ResponsesStreamRequest {
+     if !isResponses {
        // Send the [DONE] marker to indicate the end of the stream (only for non-responses APIs)
        if _, err := fmt.Fprint(w, "data: [DONE]\n\n"); err != nil {
          h.logger.Warn(fmt.Sprintf("Failed to write SSE done marker: %v", err))
        }
      }

685-690: Guard file reads and fix int64→int mismatch; re-enable size/type validation.

Use a LimitedReader to cap memory and avoid using int64 directly in make(); restore MIME/size validation.

Suggested changes:

- // // Validate file size and format
- // if err := h.validateAudioFile(fileHeader); err != nil {
- // 	SendError(ctx, fasthttp.StatusBadRequest, err.Error(), h.logger)
- // 	return
- // }
+ // Validate file size and format
+ if err := h.validateAudioFile(fileHeader); err != nil {
+   SendError(ctx, fasthttp.StatusBadRequest, err.Error(), h.logger)
+   return
+ }

@@
- // Read file data
- fileData := make([]byte, fileHeader.Size)
- if _, err := file.Read(fileData); err != nil {
- 	SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("Failed to read uploaded file: %v", err), h.logger)
- 	return
- }
+ // Read file data with hard cap to prevent OOM
+ lr := &io.LimitedReader{R: file, N: MaxFileSize + 1}
+ fileData, err := io.ReadAll(lr)
+ if err != nil {
+   SendError(ctx, fasthttp.StatusInternalServerError, fmt.Sprintf("Failed to read uploaded file: %v", err), h.logger)
+   return
+ }
+ if len(fileData) > MaxFileSize {
+   SendError(ctx, fasthttp.StatusBadRequest, fmt.Sprintf("File exceeds maximum size of %d MB", MaxFileSize/1024/1024), h.logger)
+   return
+ }

Also applies to: 699-703

ui/components/sidebar.tsx (2)

263-269: Governance gating bypass: prevent toggle and subitem navigation when not allowed.

Currently, users can expand Governance and navigate its subitems even when disallowed.

Apply:

 const handleClick = (e: React.MouseEvent) => {
-  if (hasSubItems && onToggle) {
+  if (!isAllowed) {
+    e.preventDefault();
+    return;
+  }
+  if (hasSubItems && onToggle) {
     e.preventDefault();
     onToggle();
   }
 };
 
 const handleSubItemClick = (url: string) => {
-  router.push(url);
+  if (!isAllowed) return;
+  router.push(url);
 };
 
 ...
 <SidebarItem
   key={item.title}
   item={item}
   isActive={isActive}
   isAllowed={isAllowed}
   isWebSocketConnected={isWebSocketConnected}
   isExpanded={expandedItems.has(item.title)}
-  onToggle={() => toggleItem(item.title)}
+  onToggle={() => isAllowed && toggleItem(item.title)}
   pathname={pathname}
   router={router}
 />
 
 ...
 <SidebarMenuSubButton
   className={`h-7 cursor-pointer rounded-md px-2 transition-all duration-200 ${/* unchanged */""}`}
-  onClick={() => handleSubItemClick(subItem.url)}
+  onClick={() => handleSubItemClick(subItem.url)}
+  disabled={!isAllowed}
 >

Also applies to: 275-277, 496-507, 317-323


449-456: Add rel="noopener noreferrer" to “View release notes” link.

External link opens in a new tab without rel.

Apply:

- <Link
+ <Link
   href={`https://docs.getbifrost.ai/changelogs/${latestRelease.name}`}
   target="_blank"
+  rel="noopener noreferrer"
   className="text-primary mt-auto pb-1 font-medium underline"
 >
♻️ Duplicate comments (3)
ui/app/plugins/page.tsx (1)

58-58: Security: Add rel attribute to external link.

The external link opens with target="_blank" but is missing rel="noopener noreferrer", which prevents reverse tabnabbing attacks and referrer leakage.

Apply this diff:

-<Link className="text-primary hover:underline" href="https://docs.getbifrost.ai/plugins" target="_blank">
+<Link className="text-primary hover:underline" href="https://docs.getbifrost.ai/plugins" target="_blank" rel="noopener noreferrer">
ui/components/sidebar.tsx (2)

405-411: Auto-expand logic incomplete: populate expanded parents for active subitems.

Apply:

 useEffect(() => {
   const newExpandedItems = new Set<string>();
+  items.forEach((item) => {
+    if (item.subItems?.some((sub) => pathname.startsWith(sub.url))) {
+      newExpandedItems.add(item.title);
+    }
+  });
   if (newExpandedItems.size > 0) {
     setExpandedItems((prev) => new Set([...prev, ...newExpandedItems]));
   }
 }, [pathname]);

146-150: MCP naming consistency (label vs icon labels/route).

Sidebar label is “MCP Gateway”, icon title/aria says “MCP clients icon” and route remains “/mcp-clients”. Either revert label to “MCP Clients” or align icon labels and route to “mcp-gateway” for consistency across UI/docs.

Also applies to: 63-66

🧹 Nitpick comments (5)
ui/app/config/page.tsx (1)

65-81: Add basic tab semantics for accessibility.

Consider role="tablist"/role="tab" and aria-selected for keyboard/screen-reader support.

Example diff:

- <div className="flex min-w-[250px] flex-col gap-1 rounded-md bg-zinc-50/50 p-4 dark:bg-zinc-800/20">
+ <div role="tablist" aria-label="Config sections" className="flex min-w-[250px] flex-col gap-1 rounded-md bg-zinc-50/50 p-4 dark:bg-zinc-800/20">

-   <button
+   <button
+     role="tab"
+     aria-selected={activeTab === tab.id}
      key={tab.id}
      className={cn(
        "mb-1 flex w-full items-center gap-2 rounded-sm border px-3 py-1.5 text-sm",
        activeTab === tab.id
          ? "bg-secondary opacity-100 hover:opacity-100"
          : "hover:bg-secondary cursor-pointer border-transparent opacity-100 hover:border",
      )}
      onClick={() => setActiveTab(tab.id)}
      type="button"
    >
transports/bifrost-http/handlers/inference.go (1)

599-606: Remove duplicate nil-check for SpeechParameters.

The second block repeats the same initialization.

Apply:

- // Extract extra params
- if req.SpeechParameters == nil {
-   req.SpeechParameters = &schemas.SpeechParameters{}
- }
framework/configstore/tables/plugin.go (1)

13-21: JSON casing inconsistency: isCustom vs created_at/updated_at.

API fields mix camelCase and snake_case. If external clients consume this JSON, consider aligning to a single convention (e.g., json:"is_custom") or confirm consumers expect isCustom.

ui/app/layout.tsx (1)

40-42: Avoid duplicate custom-scrollbar classes.

Parent div already controls scrolling; remove custom-scrollbar from main to prevent style duplication.

- <main className="custom-scrollbar relative mx-auto flex flex-col p-4">
+ <main className="relative mx-auto flex flex-col p-4">
ui/app/plugins/page.tsx (1)

30-44: Replace window.confirm() with AlertDialog component.

The native window.confirm() provides poor UX and doesn't match the app's design system. Consider using a consistent AlertDialog component like the one in modelProviderKeysTableView.tsx for better user experience and styling consistency.

Example pattern from ui/app/providers/views/modelProviderKeysTableView.tsx (lines 41-76):

const [showDeleteDialog, setShowDeleteDialog] = useState<{show: boolean; pluginName: string} | undefined>();

// In render:
{showDeleteDialog && (
  <AlertDialog open={showDeleteDialog.show}>
    <AlertDialogContent>
      <AlertDialogHeader>
        <AlertDialogTitle>Delete Plugin</AlertDialogTitle>
        <AlertDialogDescription>
          Are you sure you want to delete "{showDeleteDialog.pluginName}"? This action cannot be undone.
        </AlertDialogDescription>
      </AlertDialogHeader>
      <AlertDialogFooter>
        <AlertDialogCancel onClick={() => setShowDeleteDialog(undefined)}>
          Cancel
        </AlertDialogCancel>
        <AlertDialogAction onClick={() => { /* delete logic */ }}>
          Delete
        </AlertDialogAction>
      </AlertDialogFooter>
    </AlertDialogContent>
  </AlertDialog>
)}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between de194ef and e14cdec.

⛔ Files ignored due to path filters (3)
  • plugins/governance/go.sum is excluded by !**/*.sum
  • plugins/semanticcache/go.sum is excluded by !**/*.sum
  • plugins/telemetry/go.sum is excluded by !**/*.sum
📒 Files selected for processing (36)
  • framework/configstore/migrations.go (2 hunks)
  • framework/configstore/rdb.go (2 hunks)
  • framework/configstore/tables/plugin.go (1 hunks)
  • transports/bifrost-http/handlers/inference.go (1 hunks)
  • transports/bifrost-http/handlers/plugins.go (1 hunks)
  • transports/bifrost-http/lib/config.go (1 hunks)
  • ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1 hunks)
  • ui/app/adaptive-routing/page.tsx (1 hunks)
  • ui/app/alert-channels/page.tsx (1 hunks)
  • ui/app/audit-logs/page.tsx (1 hunks)
  • ui/app/cluster/page.tsx (1 hunks)
  • ui/app/config/page.tsx (1 hunks)
  • ui/app/config/views/pluginsForm.tsx (1 hunks)
  • ui/app/guardrails/page.tsx (1 hunks)
  • ui/app/layout.tsx (1 hunks)
  • ui/app/logs/page.tsx (1 hunks)
  • ui/app/logs/views/logsTable.tsx (2 hunks)
  • ui/app/mcp-clients/page.tsx (1 hunks)
  • ui/app/observability/page.tsx (1 hunks)
  • ui/app/observability/views/observabilityView.tsx (2 hunks)
  • ui/app/plugins/fragments/pluginFormFragement.tsx (1 hunks)
  • ui/app/plugins/page.tsx (1 hunks)
  • ui/app/plugins/sheets/addNewPluginSheet.tsx (1 hunks)
  • ui/app/prompt-repo/page.tsx (1 hunks)
  • ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (5 hunks)
  • ui/app/providers/dialogs/addNewKeySheet.tsx (3 hunks)
  • ui/app/providers/page.tsx (3 hunks)
  • ui/app/providers/views/modelProviderConfig.tsx (1 hunks)
  • ui/app/providers/views/modelProviderKeysTableView.tsx (3 hunks)
  • ui/app/providers/views/providerKeyForm.tsx (5 hunks)
  • ui/app/scim/page.tsx (1 hunks)
  • ui/app/user-groups/page.tsx (1 hunks)
  • ui/app/virtual-keys/page.tsx (2 hunks)
  • ui/components/sidebar.tsx (13 hunks)
  • ui/components/ui/promoCardStack.tsx (3 hunks)
  • ui/lib/types/plugins.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (18)
  • ui/app/logs/page.tsx
  • ui/app/user-groups/page.tsx
  • ui/app/adaptive-routing/page.tsx
  • ui/app/providers/views/modelProviderConfig.tsx
  • ui/app/observability/page.tsx
  • ui/app/audit-logs/page.tsx
  • ui/app/scim/page.tsx
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/handlers/plugins.go
  • ui/app/providers/dialogs/addNewKeySheet.tsx
  • ui/app/logs/views/logsTable.tsx
  • ui/app/providers/views/providerKeyForm.tsx
  • ui/app/observability/views/observabilityView.tsx
  • framework/configstore/rdb.go
  • ui/app/cluster/page.tsx
  • ui/app/mcp-clients/page.tsx
  • ui/app/providers/dialogs/addNewCustomProviderSheet.tsx
  • ui/app/virtual-keys/page.tsx
🧰 Additional context used
🧬 Code graph analysis (10)
ui/app/alert-channels/page.tsx (1)
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)
  • AlertChannelsView (4-16)
ui/app/prompt-repo/page.tsx (1)
ui/lib/utils.ts (1)
  • cn (4-6)
framework/configstore/migrations.go (1)
framework/configstore/tables/plugin.go (2)
  • TablePlugin (12-24)
  • TablePlugin (27-27)
ui/app/providers/views/modelProviderKeysTableView.tsx (2)
ui/components/ui/alertDialog.tsx (1)
  • AlertDialogCancel (85-85)
ui/app/providers/dialogs/addNewKeySheet.tsx (1)
  • AddNewKeySheet (14-46)
ui/app/plugins/fragments/pluginFormFragement.tsx (2)
ui/components/ui/input.tsx (1)
  • Input (15-69)
ui/app/logs/views/codeEditor.tsx (1)
  • CodeEditor (82-245)
ui/app/providers/page.tsx (1)
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (1)
  • AddCustomProviderSheet (41-176)
ui/app/plugins/sheets/addNewPluginSheet.tsx (2)
ui/lib/types/plugins.ts (1)
  • Plugin (6-12)
ui/app/plugins/fragments/pluginFormFragement.tsx (1)
  • PluginFormFragment (23-131)
ui/app/plugins/page.tsx (2)
ui/lib/types/plugins.ts (1)
  • Plugin (6-12)
ui/app/plugins/sheets/addNewPluginSheet.tsx (1)
  • AddNewPluginSheet (51-178)
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)
ui/app/_fallbacks/enterprise/components/views/contactUsView.tsx (1)
  • ContactUsView (15-46)
ui/components/sidebar.tsx (1)
ui/components/ui/promoCardStack.tsx (1)
  • PromoCardStack (21-120)
🪛 Biome (2.1.2)
ui/app/plugins/fragments/pluginFormFragement.tsx

[error] 34-34: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.

(lint/security/noBlankTarget)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (15)
ui/app/alert-channels/page.tsx (1)

1-9: LGTM! Clean page component implementation.

The page component correctly wraps AlertChannelsView with a centered, max-width container. This follows Next.js conventions and aligns with the PR's goal of standardizing layout wrappers across UI pages.

ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (2)

4-16: LGTM! Well-structured enterprise fallback component.

The component correctly renders a ContactUsView with appropriate enterprise upsell messaging. The min-h-[80vh] ensures good visual presentation, and the Siren icon aligns with the PR's stated icon additions.


12-12: Documentation URL is accessible and functional.

The URL https://docs.getbifrost.ai/enterprise/alert-channels returns HTTP 200, confirming the documentation link is live and will work for users clicking "Read more" in the alert channels view.

ui/app/config/page.tsx (1)

64-64: Layout centering LGTM.

Adding max-w-7xl mx-auto keeps content readable and consistent with other routes.

transports/bifrost-http/handlers/inference.go (1)

354-356: No-op formatting change.

Whitespace-only; safe to merge.

ui/app/guardrails/page.tsx (1)

4-8: Centered wrapper LGTM.

Consistent with app-wide max-w-7xl layout.

ui/app/layout.tsx (1)

41-41: Main wrapper update LGTM.

Flexible, centered base pairs well with page-level max-w-7xl wrappers.

ui/app/providers/page.tsx (1)

24-24: LGTM: Clean migration from Dialog to Sheet component.

The transition from AddCustomProviderDialog to AddCustomProviderSheet maintains the same prop interface and component wiring. The Sheet-based UI pattern is consistent with similar changes across the PR.

Also applies to: 113-124

ui/app/providers/views/modelProviderKeysTableView.tsx (1)

78-83: LGTM: Sheet migration with proper keyIndex propagation.

The transition to AddNewKeySheet correctly passes the keyIndex prop for per-key edit/add operations. The component usage aligns with the Sheet-based pattern adopted across the PR.

ui/app/config/views/pluginsForm.tsx (1)

119-124: LGTM: Empty path correctly identifies built-in plugin.

The path: "" field is appropriately set for the semantic cache built-in plugin. The backend will derive isCustom: false from the empty path, correctly distinguishing it from custom plugins.

framework/configstore/migrations.go (1)

611-613: LGTM: Migration correctly adds and removes is_custom column.

The is_custom column is properly added alongside the path column in the migration, and both are correctly removed in the rollback. This supports the custom plugin schema changes across the codebase.

Also applies to: 623-625

ui/lib/types/plugins.ts (1)

10-11: LGTM: Type extensions support custom plugin feature.

The isCustom and path fields properly extend the plugin model to distinguish and locate custom plugins. The changes align with backend schema updates and migration logic.

Also applies to: 21-21

ui/components/ui/promoCardStack.tsx (2)

18-18: LGTM: onCardsEmpty callback properly implemented.

The optional onCardsEmpty prop is correctly typed and invoked when the items list becomes empty. The effect dependencies are appropriate, enabling dynamic UI adjustments based on card presence.

Also applies to: 21-21, 41-45


62-64: LGTM: Early return guards against empty visible slice.

The updated condition checks both the input cards and the computed visibleCards, ensuring no render when the visible slice is empty. This prevents rendering an empty container.

ui/app/plugins/page.tsx (1)

13-131: LGTM: Comprehensive plugin CRUD implementation.

The plugins page now provides full functionality with proper data fetching, state management, and CRUD operations. The implementation successfully addresses past review concerns about the placeholder page. The integration with AddNewPluginSheet and mutation hooks follows established patterns.

Comment on lines +34 to +35
} <a href="https://docs.getbifrost.ai/plugins/building-bifrost-plugin" target="_blank" className="text-primary hover:underline">Learn more</a>
</p>
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 | 🟠 Major

Add rel="noopener noreferrer" to external link.

Opening in a new tab without rel is a security risk.

Apply:

- } <a href="https://docs.getbifrost.ai/plugins/building-bifrost-plugin" target="_blank" className="text-primary hover:underline">Learn more</a>
+ } <a href="https://docs.getbifrost.ai/plugins/building-bifrost-plugin" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">Learn more</a>

Based on static analysis hints.

📝 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
} <a href="https://docs.getbifrost.ai/plugins/building-bifrost-plugin" target="_blank" className="text-primary hover:underline">Learn more</a>
</p>
} <a href="https://docs.getbifrost.ai/plugins/building-bifrost-plugin" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">Learn more</a>
</p>
🧰 Tools
🪛 Biome (2.1.2)

[error] 34-34: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.

(lint/security/noBlankTarget)

🤖 Prompt for AI Agents
In ui/app/plugins/fragments/pluginFormFragement.tsx around lines 34 to 35, the
external anchor opens in a new tab without rel attributes; update the <a> tag to
include rel="noopener noreferrer" in addition to target="_blank" to mitigate
security and performance risks when linking to external sites.

Comment on lines 58 to 66
const form = useForm<PluginFormData>({
resolver: zodResolver(pluginFormSchema),
defaultValues: {
name: '',
path: '',
hasConfig: false,
config: undefined,
},
})
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

Submit stays disabled: enable RHF validation on change or relax the guard.

isValid won’t become true before submit with RHF’s default mode ("onSubmit"), so the submit button remains disabled.

Apply one of:

  • Preferred: turn on change validation.
 const form = useForm<PluginFormData>({
   resolver: zodResolver(pluginFormSchema),
+  mode: "onChange",
+  reValidateMode: "onChange",
   defaultValues: {
  • Or relax the button guard (less ideal):
- disabled={isLoading || !form.formState.isValid}
+ disabled={isLoading || !form.formState.isSubmitting && !form.formState.isValid}

Also applies to: 165-169

🤖 Prompt for AI Agents
In ui/app/plugins/sheets/addNewPluginSheet.tsx around lines 58-66 (and similarly
at 165-169) the form uses React Hook Form's default "onSubmit" mode so
formState.isValid stays false until submit, leaving the submit button disabled;
update the useForm call to enable validation on change (set mode to "onChange"
and optionally reValidateMode to "onChange") so isValid updates as fields
change, or alternatively loosen the submit-button guard to not rely on isValid
(preferred: enable onChange validation).

Comment on lines 132 to 140
const handleClose = () => {
form.reset()
onClose()
}

return (
<Sheet open={open} onOpenChange={handleClose}>
<SheetContent className="dark:bg-card flex w-full flex-col overflow-x-hidden bg-white p-8 sm:max-w-2xl">
<SheetHeader className="p-0">
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 | 🟠 Major

Fix onOpenChange: handle the boolean and reset only on close.

Passing a no‑arg handler risks closing on open toggles and ignores the next state.

Apply:

- const handleClose = () => {
-   form.reset()
-   onClose()
- }
+ const handleClose = () => {
+   form.reset();
+   onClose();
+ }
+
+ const handleOpenChange = (nextOpen: boolean) => {
+   if (!nextOpen) {
+     handleClose();
+   }
+ }
...
- <Sheet open={open} onOpenChange={handleClose}>
+ <Sheet open={open} onOpenChange={handleOpenChange}>
🤖 Prompt for AI Agents
In ui/app/plugins/sheets/addNewPluginSheet.tsx around lines 132 to 140, the
Sheet onOpenChange is wired to a no-arg handler which can't tell open vs close
and may reset on open; change handleClose to accept the boolean nextOpen (or
isOpen) parameter, call form.reset() and onClose() only when nextOpen === false
(closing), and pass that handler to onOpenChange so open toggles won't
incorrectly reset the form.

Comment on lines +11 to +14
<h1 className="text-muted-foreground text-xl font-medium">Prompt repository is coming soon</h1>
<div className="text-muted-foreground mt-2 max-w-[600px] text-sm font-normal">
Prompt repositories is coming soon. Please check back soon for updates.
</div>
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 | 🟡 Minor

Fix grammatical error and inconsistency.

Line 11 uses singular "repository" but line 13 uses plural "repositories" with incorrect verb agreement. Use singular throughout for consistency.

Apply this diff:

 				<h1 className="text-muted-foreground text-xl font-medium">Prompt repository is coming soon</h1>
 				<div className="text-muted-foreground mt-2 max-w-[600px] text-sm font-normal">
-					Prompt repositories is coming soon. Please check back soon for updates.
+					Prompt repository is coming soon. Please check back soon for updates.
 				</div>
📝 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
<h1 className="text-muted-foreground text-xl font-medium">Prompt repository is coming soon</h1>
<div className="text-muted-foreground mt-2 max-w-[600px] text-sm font-normal">
Prompt repositories is coming soon. Please check back soon for updates.
</div>
<h1 className="text-muted-foreground text-xl font-medium">Prompt repository is coming soon</h1>
<div className="text-muted-foreground mt-2 max-w-[600px] text-sm font-normal">
Prompt repository is coming soon. Please check back soon for updates.
</div>
🤖 Prompt for AI Agents
In ui/app/prompt-repo/page.tsx around lines 11 to 14, the first line uses
singular "repository" while the following line uses plural "repositories" with
incorrect verb agreement; update the second line to use singular to match the
heading and correct the verb (e.g., "Prompt repository is coming soon. Please
check back soon for updates." or more natural: "The prompt repository is coming
soon. Please check back for updates.").

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: 4

♻️ Duplicate comments (7)
ui/components/sidebar.tsx (2)

146-150: “MCP Gateway” label conflicts with /mcp-clients and icon aria text.

Either keep label “MCP Clients” or complete the rename (route, MCPIcon aria/title, page headings, docs).


405-411: Auto-expand logic is incomplete — populate the Set.

newExpandedItems is never filled, so parents don’t auto-expand when a sub-route is active.

 useEffect(() => {
-  const newExpandedItems = new Set<string>();
-
-  if (newExpandedItems.size > 0) {
-    setExpandedItems((prev) => new Set([...prev, ...newExpandedItems]));
-  }
+  const newExpandedItems = new Set<string>();
+  items.forEach((item) => {
+    if (item.subItems?.some((sub) => pathname.startsWith(sub.url))) {
+      newExpandedItems.add(item.title);
+    }
+  });
+  if (newExpandedItems.size > 0) {
+    setExpandedItems((prev) => new Set([...prev, ...newExpandedItems]));
+  }
 }, [pathname]);
transports/bifrost-http/handlers/plugins.go (1)

114-168: Path field is not extracted from request or passed to CreatePlugin.

The Path field is defined in CreatePluginRequest (line 44) but is never used in the createPlugin handler. It must be extracted from the request and passed to both CreatePlugin (lines 134-138) and ReloadPlugin (line 153).

Apply this diff:

 	if err := h.configStore.CreatePlugin(ctx, &configstoreTables.TablePlugin{
 		Name:    request.Name,
 		Enabled: request.Enabled,
 		Config:  request.Config,
+		Path:    request.Path,
 	}); err != nil {

Also update the ReloadPlugin call to use request.Path:

 	if request.Enabled {
-		if err := h.pluginsLoader.ReloadPlugin(ctx, request.Name, nil, request.Config); err != nil {
+		if err := h.pluginsLoader.ReloadPlugin(ctx, request.Name, request.Path, request.Config); err != nil {
ui/app/providers/views/providerKeyForm.tsx (1)

81-81: Remove debug console.log statement.

This debug statement should be removed before merging to production.

Apply this diff:

-	console.log(form.formState.isDirty);
-
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (1)

98-98: Fixed minimum width causes responsive issues on mobile.

The min-w-[600px] will cause horizontal overflow on mobile devices (viewport: 360-428px). Use a responsive approach instead.

Apply this diff:

-			<SheetContent className="custom-scrollbar dark:bg-card bg-white py-4 min-w-[600px]">
+			<SheetContent className="custom-scrollbar dark:bg-card bg-white py-4 w-full sm:max-w-2xl">

Note: The same issue exists in addNewKeySheet.tsx at line 21 and should be fixed similarly.

ui/app/providers/dialogs/addNewKeySheet.tsx (1)

21-21: Fixed minimum width causes responsive issues on mobile.

The min-w-[600px] will cause horizontal overflow on mobile devices (viewport: 360-428px).

Apply this diff:

-			<SheetContent className="custom-scrollbar bg-white dark:bg-card min-w-[600px] py-4">
+			<SheetContent className="custom-scrollbar bg-white dark:bg-card w-full sm:max-w-2xl py-4">
ui/app/plugins/page.tsx (1)

58-60: External link: add rel to prevent reverse‑tabnabbing.

Add rel="noopener noreferrer" alongside target="_blank".

-<Link className="text-primary hover:underline" href="https://docs.getbifrost.ai/plugins" target="_blank">
+<Link className="text-primary hover:underline" href="https://docs.getbifrost.ai/plugins" target="_blank" rel="noopener noreferrer">
🧹 Nitpick comments (8)
ui/app/observability/page.tsx (1)

4-8: Inconsistent class ordering across similar page layouts.

The wrapper div uses the class order max-w-7xl mx-auto w-full, while ui/app/audit-logs/page.tsx (Line 4) uses mx-auto w-full max-w-7xl. Although Tailwind is order-independent for these utilities, consistent ordering improves maintainability across the codebase.

Apply this diff to align with the ordering in ui/app/audit-logs/page.tsx:

-		<div className="max-w-7xl mx-auto w-full">
+		<div className="mx-auto w-full max-w-7xl">

Alternatively, update all similar pages to use a consistent order (e.g., max-w-7xl mx-auto w-full).

ui/components/ui/promoCardStack.tsx (1)

15-22: Prop addition is fine; consider documenting semantics.

onCardsEmpty is useful; clarify in a brief JSDoc whether it should fire only after user dismissals vs. also when initial cards is empty. This avoids divergent parent expectations.

ui/components/sidebar.tsx (2)

311-327: Avoid any for subItems; add simple types.

Strong typing improves DX and prevents runtime mismatches.

+// Types for sidebar items
+type SidebarSubItem = {
+  title: string;
+  url: string;
+  icon?: React.ElementType;
+  description?: string;
+};
+type SidebarItemType = {
+  title: string;
+  url: string;
+  icon: React.ElementType;
+  description?: string;
+  subItems?: SidebarSubItem[];
+};
-
-const items = [
+const items: SidebarItemType[] = [
   // ...
 ];
 
-const SidebarItem = ({
+const SidebarItem = ({
   item,
   isActive,
   isAllowed,
   isWebSocketConnected,
   isExpanded,
   onToggle,
   pathname,
   router,
 }: {
-  item: (typeof items)[0];
+  item: SidebarItemType;
   isActive: boolean;
   isAllowed: boolean;
   isWebSocketConnected: boolean;
   isExpanded?: boolean;
   onToggle?: () => void;
   pathname: string;
   router: ReturnType<typeof useRouter>;
 }) => {
   // ...
-  {item.subItems?.map((subItem: any) => {
+  {item.subItems?.map((subItem: SidebarSubItem) => {

476-479: Stabilize handleCardsEmpty with useCallback.

Prevents re-creating the handler every render and reduces redundant effects downstream.

-import { useEffect, useMemo, useState } from "react";
+import { useCallback, useEffect, useMemo, useState } from "react";
@@
-const handleCardsEmpty = () => {
-  setAreCardsEmpty(true);
-};
+const handleCardsEmpty = useCallback(() => {
+  setAreCardsEmpty(true);
+}, []);
ui/app/plugins/sheets/addNewPluginSheet.tsx (2)

138-138: Only close the sheet when it changes to closed.

onOpenChange passes the next boolean state. Calling handleClose() unconditionally can fight open transitions. Gate on !open.

-    <Sheet open={open} onOpenChange={handleClose}>
+    <Sheet open={open} onOpenChange={(next) => { if (!next) handleClose() }}>

15-26: Trim inputs before validation to reduce user error.

Minor polish: trim name and path before checks to avoid leading/trailing space issues.

-const pluginFormSchema = z.object({
-  name: z.string().min(1, 'Plugin name is required').regex(/^[a-z0-9-_]+$/, 'Plugin name must contain only lowercase letters, numbers, hyphens, and underscores'),
-  path: z.string().min(1, 'Plugin path/URL is required').refine(
+const pluginFormSchema = z.object({
+  name: z.string().transform((v) => v.trim()).min(1, 'Plugin name is required').regex(/^[a-z0-9-_]+$/, 'Plugin name must contain only lowercase letters, numbers, hyphens, and underscores'),
+  path: z.string().transform((v) => v.trim()).min(1, 'Plugin path/URL is required').refine(
ui/app/plugins/page.tsx (2)

76-83: Add an accessible label for the status dot.

The colored dot alone isn’t announced by screen readers. Include an sr-only label.

-                <CardTitle className="flex items-center justify-between">
-                  <span>{plugin.name}</span>
-                  <div className={`h-2 w-2 rounded-full ${plugin.enabled ? 'bg-green-500' : 'bg-gray-400'}`} />
-                </CardTitle>
+                <CardTitle className="flex items-center justify-between">
+                  <span>{plugin.name}</span>
+                  <div className="flex items-center gap-2">
+                    <span className="sr-only">{plugin.enabled ? 'Enabled' : 'Disabled'}</span>
+                    <span
+                      aria-hidden
+                      className={`h-2 w-2 rounded-full ${plugin.enabled ? 'bg-green-500' : 'bg-gray-400'}`}
+                    />
+                  </div>
+                </CardTitle>

95-101: Disable only the row being deleted (better UX).

Global isDeleting disables all delete buttons. Track the deleting plugin name to disable just one.

-  const [deletePlugin, { isLoading: isDeleting }] = useDeletePluginMutation();
+  const [deletePlugin, { isLoading: isDeleting }] = useDeletePluginMutation();
+  const [deletingName, setDeletingName] = useState<string | null>(null);
@@
-  const handleDelete = async (pluginName: string) => {
+  const handleDelete = async (pluginName: string) => {
     if (!confirm(`Are you sure you want to delete the plugin "${pluginName}"?`)) {
       return;
     }
     try {
+      setDeletingName(pluginName);
       await deletePlugin(pluginName).unwrap();
       toast.success('Plugin deleted successfully');
     } catch (error) {
       const errorMessage = error && typeof error === 'object' && 'data' in error
         ? (error.data as any)?.error || 'Failed to delete plugin'
         : 'Failed to delete plugin';
       toast.error(errorMessage);
     } finally {
+      setDeletingName(null);
     }
   };
@@
-                    disabled={isDeleting}
+                    disabled={isDeleting && deletingName === plugin.name}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between de194ef and e14cdec.

⛔ Files ignored due to path filters (3)
  • plugins/governance/go.sum is excluded by !**/*.sum
  • plugins/semanticcache/go.sum is excluded by !**/*.sum
  • plugins/telemetry/go.sum is excluded by !**/*.sum
📒 Files selected for processing (36)
  • framework/configstore/migrations.go (2 hunks)
  • framework/configstore/rdb.go (2 hunks)
  • framework/configstore/tables/plugin.go (1 hunks)
  • transports/bifrost-http/handlers/inference.go (1 hunks)
  • transports/bifrost-http/handlers/plugins.go (1 hunks)
  • transports/bifrost-http/lib/config.go (1 hunks)
  • ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1 hunks)
  • ui/app/adaptive-routing/page.tsx (1 hunks)
  • ui/app/alert-channels/page.tsx (1 hunks)
  • ui/app/audit-logs/page.tsx (1 hunks)
  • ui/app/cluster/page.tsx (1 hunks)
  • ui/app/config/page.tsx (1 hunks)
  • ui/app/config/views/pluginsForm.tsx (1 hunks)
  • ui/app/guardrails/page.tsx (1 hunks)
  • ui/app/layout.tsx (1 hunks)
  • ui/app/logs/page.tsx (1 hunks)
  • ui/app/logs/views/logsTable.tsx (2 hunks)
  • ui/app/mcp-clients/page.tsx (1 hunks)
  • ui/app/observability/page.tsx (1 hunks)
  • ui/app/observability/views/observabilityView.tsx (2 hunks)
  • ui/app/plugins/fragments/pluginFormFragement.tsx (1 hunks)
  • ui/app/plugins/page.tsx (1 hunks)
  • ui/app/plugins/sheets/addNewPluginSheet.tsx (1 hunks)
  • ui/app/prompt-repo/page.tsx (1 hunks)
  • ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (5 hunks)
  • ui/app/providers/dialogs/addNewKeySheet.tsx (3 hunks)
  • ui/app/providers/page.tsx (3 hunks)
  • ui/app/providers/views/modelProviderConfig.tsx (1 hunks)
  • ui/app/providers/views/modelProviderKeysTableView.tsx (3 hunks)
  • ui/app/providers/views/providerKeyForm.tsx (5 hunks)
  • ui/app/scim/page.tsx (1 hunks)
  • ui/app/user-groups/page.tsx (1 hunks)
  • ui/app/virtual-keys/page.tsx (2 hunks)
  • ui/components/sidebar.tsx (13 hunks)
  • ui/components/ui/promoCardStack.tsx (3 hunks)
  • ui/lib/types/plugins.ts (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • transports/bifrost-http/handlers/inference.go
  • ui/app/mcp-clients/page.tsx
🚧 Files skipped from review as they are similar to previous changes (18)
  • ui/app/logs/page.tsx
  • ui/lib/types/plugins.ts
  • transports/bifrost-http/lib/config.go
  • ui/app/layout.tsx
  • ui/app/adaptive-routing/page.tsx
  • ui/app/providers/page.tsx
  • ui/app/user-groups/page.tsx
  • framework/configstore/tables/plugin.go
  • ui/app/scim/page.tsx
  • framework/configstore/rdb.go
  • ui/app/providers/views/modelProviderKeysTableView.tsx
  • ui/app/logs/views/logsTable.tsx
  • ui/app/observability/views/observabilityView.tsx
  • ui/app/alert-channels/page.tsx
  • ui/app/cluster/page.tsx
  • ui/app/config/page.tsx
  • ui/app/virtual-keys/page.tsx
  • ui/app/guardrails/page.tsx
🧰 Additional context used
🧬 Code graph analysis (12)
ui/app/providers/views/providerKeyForm.tsx (1)
ui/app/providers/fragments/apiKeysFormFragment.tsx (1)
  • ApiKeyFormFragment (21-445)
ui/app/audit-logs/page.tsx (1)
ui/app/_fallbacks/enterprise/components/audit-logs/auditLogsView.tsx (1)
  • AuditLogsView (4-16)
framework/configstore/migrations.go (1)
framework/configstore/tables/plugin.go (2)
  • TablePlugin (12-24)
  • TablePlugin (27-27)
ui/app/plugins/sheets/addNewPluginSheet.tsx (2)
ui/lib/types/plugins.ts (1)
  • Plugin (6-12)
ui/app/plugins/fragments/pluginFormFragement.tsx (1)
  • PluginFormFragment (23-131)
ui/app/providers/views/modelProviderConfig.tsx (2)
ui/lib/types/config.ts (1)
  • isKnownProvider (15-17)
ui/app/providers/views/utils.ts (1)
  • keysRequired (1-1)
ui/app/observability/page.tsx (1)
ui/app/observability/views/observabilityView.tsx (1)
  • ObservabilityView (67-168)
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (2)
ui/components/ui/sheet.tsx (6)
  • Sheet (90-90)
  • SheetContent (90-90)
  • SheetHeader (90-90)
  • SheetTitle (90-90)
  • SheetDescription (90-90)
  • SheetFooter (90-90)
ui/components/ui/input.tsx (1)
  • Input (15-69)
ui/app/plugins/page.tsx (2)
ui/lib/types/plugins.ts (1)
  • Plugin (6-12)
ui/app/plugins/sheets/addNewPluginSheet.tsx (1)
  • AddNewPluginSheet (51-178)
ui/app/plugins/fragments/pluginFormFragement.tsx (2)
ui/components/ui/input.tsx (1)
  • Input (15-69)
ui/app/logs/views/codeEditor.tsx (1)
  • CodeEditor (82-245)
ui/components/sidebar.tsx (1)
ui/components/ui/promoCardStack.tsx (1)
  • PromoCardStack (21-120)
ui/app/prompt-repo/page.tsx (1)
ui/lib/utils.ts (1)
  • cn (4-6)
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)
ui/app/_fallbacks/enterprise/components/views/contactUsView.tsx (1)
  • ContactUsView (15-46)
🪛 Biome (2.1.2)
ui/app/plugins/fragments/pluginFormFragement.tsx

[error] 34-34: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.

(lint/security/noBlankTarget)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (11)
ui/app/audit-logs/page.tsx (1)

4-6: LGTM!

The centered max-width container correctly applies the consistent layout pattern across pages.

ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)

1-16: LGTM! Clean enterprise fallback implementation.

The component follows the established pattern for enterprise feature promotional views, correctly reuses the ContactUsView component, and provides appropriate marketing content for the Alert Channels feature. The implementation is straightforward with no concerns.

ui/components/ui/promoCardStack.tsx (1)

62-65: Render guard looks good.

Returning null when there are no input cards or no visibleCards is correct and prevents stray containers.

ui/components/sidebar.tsx (2)

513-516: PromoCardStack integration looks good; confirm desired empty-state layout shift.

The height calculation toggles from 13rem to 8rem when cards empty. Verify this matches design specs and doesn’t cause jarring layout jumps.


79-91: Verify routes for subitems (“Connectors” → /observability) exist.

Ensure /observability resolves, alongside /logs.

framework/configstore/migrations.go (1)

600-634: LGTM!

The migration correctly adds both path and is_custom columns together, and the rollback properly removes both. This aligns with the schema changes in framework/configstore/tables/plugin.go.

ui/app/providers/views/modelProviderConfig.tsx (1)

46-60: LGTM!

The controlled accordion pattern is implemented correctly. The useEffect appropriately initializes the accordion to expand when API keys are not required or when the provider is custom, improving the user experience.

ui/app/providers/views/providerKeyForm.tsx (1)

53-61: Good use of useCallback for tooltip content.

The getTooltipContent helper is correctly memoized and provides clear conditional logic for tooltip display.

ui/app/config/views/pluginsForm.tsx (1)

119-124: LGTM!

The addition of path: "" to the createPlugin payload correctly aligns with the extended CreatePluginRequest type that now includes the path field.

ui/app/plugins/sheets/addNewPluginSheet.tsx (1)

101-119: Confirm create/update payloads and tag invalidation.

Sanity check that:

  • create sets whatever flags backend expects to mark a plugin “custom” (if required), and
  • update/create/delete invalidate the plugins list so the grid refreshes.
ui/app/plugins/page.tsx (1)

14-19: Verify data refresh after mutations.

Ensure useGetPluginsQuery is invalidated by create/update/delete so the list reflects changes immediately.

Also applies to: 124-128

{isEditMode
? 'Update your plugin configuration. Plugin name and path are read-only.'
: 'Install a custom plugin by providing an absolute file path or HTTP URL accessible to Bifrost deployment (.so).'
} <a href="https://docs.getbifrost.ai/plugins/building-bifrost-plugin" target="_blank" className="text-primary hover:underline">Learn more</a>
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

Add rel="noopener noreferrer" to external link.

Opening external links with target="_blank" without rel="noopener noreferrer" is a security risk that can allow the opened page to access the window.opener object.

Apply this diff:

-			} <a href="https://docs.getbifrost.ai/plugins/building-bifrost-plugin" target="_blank" className="text-primary hover:underline">Learn more</a>
+			} <a href="https://docs.getbifrost.ai/plugins/building-bifrost-plugin" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">Learn more</a>
📝 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
} <a href="https://docs.getbifrost.ai/plugins/building-bifrost-plugin" target="_blank" className="text-primary hover:underline">Learn more</a>
} <a href="https://docs.getbifrost.ai/plugins/building-bifrost-plugin" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">Learn more</a>
🧰 Tools
🪛 Biome (2.1.2)

[error] 34-34: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.

(lint/security/noBlankTarget)

🤖 Prompt for AI Agents
In ui/app/plugins/fragments/pluginFormFragement.tsx around line 34, the external
anchor uses target="_blank" but is missing rel="noopener noreferrer"; update the
<a> tag to include rel="noopener noreferrer" so the external link opens in a new
tab safely by preventing access to window.opener and avoiding potential security
vulnerabilities.

Comment on lines 58 to 66
const form = useForm<PluginFormData>({
resolver: zodResolver(pluginFormSchema),
defaultValues: {
name: '',
path: '',
hasConfig: false,
config: undefined,
},
})
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

Submit button will stay disabled (RHF isValid deadlock).

With default RHF mode ("onSubmit"), form.formState.isValid remains false until a submit occurs, but the submit button is disabled when !isValid, so users can’t submit at all. Enable validation on change.

Apply this diff:

   const form = useForm<PluginFormData>({
     resolver: zodResolver(pluginFormSchema),
+    mode: 'onChange',
     defaultValues: {
       name: '',
       path: '',
       hasConfig: false,
       config: undefined,
     },
   })

Also applies to: 166-169

🤖 Prompt for AI Agents
In ui/app/plugins/sheets/addNewPluginSheet.tsx around lines 58-66 (and similarly
at lines 166-169), the form is initialized with the default React Hook Form mode
("onSubmit") so form.formState.isValid stays false until a submit and the submit
button remains disabled; change the useForm initialization to enable validation
on change (e.g., set mode to "onChange" or "all") so isValid updates as the user
types, keeping the submit button enabled when the form is valid.

Comment on lines +11 to +13
<h1 className="text-muted-foreground text-xl font-medium">Prompt repository is coming soon</h1>
<div className="text-muted-foreground mt-2 max-w-[600px] text-sm font-normal">
Prompt repositories is coming soon. Please check back soon for updates.
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 | 🟡 Minor

Fix grammatical error and reduce redundancy in user-facing text.

Two text quality issues:

  1. Line 13: "Prompt repositories is" has subject-verb disagreement. Should be "Prompt repository is" (singular, matching the title) or "Prompt repositories are" (plural).
  2. The description repeats "coming soon" information from the title without adding new details.

Apply this diff to fix the grammatical error and improve the messaging:

 			<div className="flex flex-col gap-1">
 				<h1 className="text-muted-foreground text-xl font-medium">Prompt repository is coming soon</h1>
 				<div className="text-muted-foreground mt-2 max-w-[600px] text-sm font-normal">
-					Prompt repositories is coming soon. Please check back soon for updates.
+					This feature will allow you to manage and version your prompts. Please check back soon for updates.
 				</div>
 			</div>
📝 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
<h1 className="text-muted-foreground text-xl font-medium">Prompt repository is coming soon</h1>
<div className="text-muted-foreground mt-2 max-w-[600px] text-sm font-normal">
Prompt repositories is coming soon. Please check back soon for updates.
<div className="flex flex-col gap-1">
<h1 className="text-muted-foreground text-xl font-medium">Prompt repository is coming soon</h1>
<div className="text-muted-foreground mt-2 max-w-[600px] text-sm font-normal">
This feature will allow you to manage and version your prompts. Please check back soon for updates.
</div>
</div>
🤖 Prompt for AI Agents
In ui/app/prompt-repo/page.tsx around lines 11 to 13, fix the subject-verb
disagreement and remove redundant phrasing by making the description complement
the title instead of repeating it; change the second line so it matches the
singular title ("Prompt repository") and replaces the repeated "coming soon"
with a concise follow-up such as "Check back for updates" or "Stay tuned for
updates."

Comment on lines +41 to +46
useEffect(() => {
if (items.length === 0 && onCardsEmpty) {
onCardsEmpty();
}
}, [items.length, onCardsEmpty]);

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 | 🟠 Major

Fire onCardsEmpty only on transition to empty; prevent repeated callbacks.

Current effect re-fires whenever the parent re-renders with a new onCardsEmpty (common with inline lambdas), causing multiple calls when items.length is 0. Guard on transition (>0 → 0) and stabilize deps.

-	useEffect(() => {
-		if (items.length === 0 && onCardsEmpty) {
-			onCardsEmpty();
-		}
-	}, [items.length, onCardsEmpty]);
+	// Call once when the stack transitions from non-empty to empty
+	const prevLenRef = React.useRef(items.length);
+	useEffect(() => {
+		if (prevLenRef.current > 0 && items.length === 0) {
+			onCardsEmpty?.();
+		}
+		prevLenRef.current = items.length;
+	}, [items.length]);

Also add useRef to the import:

-import React, { useEffect, useState } from "react";
+import React, { useEffect, useRef, useState } from "react";
📝 Committable suggestion

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

Suggested change
useEffect(() => {
if (items.length === 0 && onCardsEmpty) {
onCardsEmpty();
}
}, [items.length, onCardsEmpty]);
// Call once when the stack transitions from non-empty to empty
const prevLenRef = React.useRef(items.length);
useEffect(() => {
if (prevLenRef.current > 0 && items.length === 0) {
onCardsEmpty?.();
}
prevLenRef.current = items.length;
}, [items.length]);
🤖 Prompt for AI Agents
In ui/components/ui/promoCardStack.tsx around lines 41–46, import useRef and add
a ref to track the previous items.length and a ref to hold the latest
onCardsEmpty callback; in the effect compare prevLengthRef.current > 0 &&
items.length === 0 and only then call onCardsEmptyRef.current(), update
prevLengthRef.current = items.length, and change the effect dependency to
[items.length] (no onCardsEmpty) so the callback only fires on the transition
from non-empty to empty and not on parent re-renders or changing inline lambdas.

@akshaydeo akshaydeo force-pushed the 10-16-dynamic_plugin_architecture branch from 127675d to 285f004 Compare October 25, 2025 00:37
@akshaydeo akshaydeo force-pushed the 10-20-dynamic-plugins-ui branch from e14cdec to 1c19b16 Compare October 25, 2025 00:37
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

Caution

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

⚠️ Outside diff range comments (4)
plugins/otel/docker-compose.yml (1)

29-29: Fix port configuration mismatch in Tempo service.

The configuration splits gRPC (port 3201) and HTTP (port 3200) across different ports, but only port 3200 is exposed in the service ports. The comment on line 29 labels it as "multiplexed," which no longer reflects the actual configuration.

Choose one approach:

  1. If gRPC should be accessible externally, add - "3201:3201" to line 29 and update the comment to reflect separate ports.
  2. If multiplexed behavior is intended, change line 143 to grpc_listen_port: 3200 to align with the exposed port.

Currently, external gRPC connections to Tempo will fail since port 3201 is not exposed.

ui/components/sidebar.tsx (3)

3-23: Import Siren icon for “Alert channels”.

PR objective mentions Siren; it’s not imported.

   Layers,
   LogOut,
   Logs,
   Puzzle,
+  Siren,
   ScrollText,

263-277: Block navigation when Governance is disabled.

Currently, disabled items can expand and subitems can navigate.

 const handleClick = (e: React.MouseEvent) => {
-  if (hasSubItems && onToggle) {
+  if (!isAllowed) {
+    e.preventDefault();
+    return;
+  }
+  if (hasSubItems && onToggle) {
     e.preventDefault();
     onToggle();
   }
 };
 
 const handleSubItemClick = (url: string) => {
-  router.push(url);
+  if (!isAllowed) return;
+  router.push(url);
 };
-              className={`h-7 cursor-pointer rounded-md px-2 transition-all duration-200 ${
+              className={`h-7 rounded-md px-2 transition-all duration-200 ${
+                !isAllowed
+                  ? "text-muted-foreground cursor-not-allowed"
+                  :
                 isSubItemActive
                   ? "bg-sidebar-accent text-primary font-medium"
                   : "hover:bg-sidebar-accent hover:text-accent-foreground text-slate-500 dark:text-zinc-400"
               }`}
+              aria-disabled={!isAllowed}

Also applies to: 317-323


53-69: A11y: update MCP icon labels to new naming.

ARIA/title still say “MCP clients”.

-   aria-label="MCP clients icon"
+   aria-label="MCP gateway icon"
 ...
-    <title>MCP clients icon</title>
+    <title>MCP gateway icon</title>
♻️ Duplicate comments (11)
ui/app/providers/views/providerKeyForm.tsx (1)

81-81: Remove debug console.log statement.

This debug statement should be removed before merging to production.

Apply this diff to remove the debug statement:

-	console.log(form.formState.isDirty);
-
ui/app/plugins/fragments/pluginFormFragement.tsx (1)

34-34: Add rel="noopener noreferrer" to external link.

Opening external links with target="_blank" without rel="noopener noreferrer" is a security risk that can allow the opened page to access the window.opener object.

Apply this diff:

-			} <a href="https://docs.getbifrost.ai/plugins/building-bifrost-plugin" target="_blank" className="text-primary hover:underline">Learn more</a>
+			} <a href="https://docs.getbifrost.ai/plugins/building-bifrost-plugin" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">Learn more</a>

Based on static analysis hints.

ui/components/ui/promoCardStack.tsx (1)

41-45: Fire onCardsEmpty only on transition to empty; prevent repeated callbacks.

The effect re-fires whenever the parent re-renders with a new onCardsEmpty reference (common with inline lambdas), causing multiple calls when items.length is 0. Guard on transition (>0 → 0) and stabilize deps.

Apply this diff:

+	const prevLenRef = React.useRef(items.length);
 	useEffect(() => {
-		if (items.length === 0 && onCardsEmpty) {
-			onCardsEmpty();
+		if (prevLenRef.current > 0 && items.length === 0) {
+			onCardsEmpty?.();
 		}
-	}, [items.length, onCardsEmpty]);
+		prevLenRef.current = items.length;
+	}, [items.length, onCardsEmpty]);

Also add useRef to the import:

-import React, { useEffect, useState } from "react";
+import React, { useEffect, useRef, useState } from "react";

Based on past review comments.

ui/app/prompt-repo/page.tsx (1)

11-14: Fix grammatical error and reduce redundancy in user-facing text.

Line 13 has subject-verb disagreement ("repositories is") and repeats "coming soon" without adding value.

Apply this diff:

 				<h1 className="text-muted-foreground text-xl font-medium">Prompt repository is coming soon</h1>
 				<div className="text-muted-foreground mt-2 max-w-[600px] text-sm font-normal">
-					Prompt repositories is coming soon. Please check back soon for updates.
+					This feature will allow you to manage and version your prompts. Check back soon for updates.
 				</div>

Based on past review comments.

ui/app/plugins/page.tsx (1)

52-52: Add rel attribute to external link for security.

The external documentation link opens in a new tab but is missing rel="noopener noreferrer" to prevent reverse-tabnabbing.

Apply this diff:

-			<Link className="text-primary hover:underline dark:text-green-400" href="https://docs.getbifrost.ai/plugins" target="_blank">
+			<Link className="text-primary hover:underline dark:text-green-400" href="https://docs.getbifrost.ai/plugins" target="_blank" rel="noopener noreferrer">

Based on past review comments.

ui/app/plugins/sheets/addNewPluginSheet.tsx (2)

58-66: Enable validation on change to prevent submit button deadlock.

With the default React Hook Form mode ("onSubmit"), form.formState.isValid remains false until a submit attempt, but the submit button is disabled when !isValid, creating a deadlock where users can't submit valid forms.

Apply this diff:

   const form = useForm<PluginFormData>({
     resolver: zodResolver(pluginFormSchema),
+    mode: 'onChange',
     defaultValues: {
       name: '',
       path: '',
       hasConfig: false,
       config: undefined,
     },
   })

Based on past review comments.


132-138: Fix onOpenChange to handle the boolean parameter correctly.

The onOpenChange handler receives a boolean indicating the next open state, but handleClose doesn't accept parameters. This can cause the form to reset when opening the sheet.

Apply this diff:

  const handleClose = () => {
    form.reset()
    onClose()
  }
+
+ const handleOpenChange = (nextOpen: boolean) => {
+   if (!nextOpen) {
+     handleClose();
+   }
+ }

  return (
-   <Sheet open={open} onOpenChange={handleClose}>
+   <Sheet open={open} onOpenChange={handleOpenChange}>

Based on past review comments.

ui/app/providers/dialogs/addNewKeySheet.tsx (1)

21-21: Prevent mobile overflow: make sheet width responsive.

Use full width on mobile, fixed width from sm+.

-      <SheetContent className="custom-scrollbar bg-white dark:bg-card min-w-[600px] py-4">
+      <SheetContent className="custom-scrollbar bg-white dark:bg-card w-full sm:min-w-[600px] sm:max-w-[600px] py-4">
ui/components/sidebar.tsx (3)

405-411: Restore auto‑expand for active subitems.

Set parents expanded when a subitem matches pathname.

 useEffect(() => {
   const newExpandedItems = new Set<string>();
+  items.forEach((item) => {
+    if (item.subItems?.some((subItem) => pathname.startsWith(subItem.url))) {
+      newExpandedItems.add(item.title);
+    }
+  });
   if (newExpandedItems.size > 0) {
     setExpandedItems((prev) => new Set([...prev, ...newExpandedItems]));
   }
 }, [pathname]);

146-150: Align label and route: “MCP Gateway” vs “/mcp-clients”.

Either revert label to “MCP Clients” or rename the route consistently.

Option A (revert label):

-  title: "MCP Gateway",
+  title: "MCP Clients",

Option B (rename route – broader scope): update path to /mcp-gateway and adjust all references, pages, docs, and tests.

#!/bin/bash
# Find remaining "MCP Clients" vs "MCP Gateway" references
rg -nI "MCP Clients" ui/ -C1
rg -nI "MCP Gateway" ui/ -C1

138-143: Add missing "Alert channels" nav item.

The objective requires this navigation item, but it's not present in the sidebar. The route and icon are both available.

   {
     title: "Plugins",
     url: "/plugins",
     icon: Puzzle,
     description: "Manage custom plugins",
   },
+  {
+    title: "Alert channels",
+    url: "/alert-channels",
+    icon: Siren,
+    description: "Manage alert notifications",
+  },

Add Siren to the lucide-react import on line 3.

🧹 Nitpick comments (5)
ui/app/logs/views/logsTable.tsx (1)

81-82: Height adjustment looks correct for sidebar changes.

The reduction from 20rem to 16.5rem gives the logs table more vertical space, which aligns with the sidebar navigation reorganization mentioned in the PR objectives.

Consider extracting the magic number 16.5rem as a constant or CSS variable to improve maintainability:

// At the top of the file or in a constants file
const LOGS_TABLE_VERTICAL_OFFSET = "16.5rem";

Then use it in both places:

-<div className="rounded-sm border max-h-[calc(100vh-16.5rem)]">
-  <Table containerClassName="max-h-[calc(100vh-16.5rem)]">
+<div className={`rounded-sm border max-h-[calc(100vh-${LOGS_TABLE_VERTICAL_OFFSET})]`}>
+  <Table containerClassName={`max-h-[calc(100vh-${LOGS_TABLE_VERTICAL_OFFSET})]`}>

This makes it easier to adjust the offset if sidebar dimensions change in the future.

ui/app/providers/views/providerKeyForm.tsx (1)

107-107: Consider storing tooltip content to avoid double invocation.

getTooltipContent() is called twice on this line—once for the condition check and once to render the content. While not a performance concern given the simple logic, storing the result would be cleaner.

You could refactor by computing it once within an IIFE or extract it earlier in the JSX:

-								{getTooltipContent() && <TooltipContent>{getTooltipContent()}</TooltipContent>}
+								{(() => {
+									const content = getTooltipContent();
+									return content && <TooltipContent>{content}</TooltipContent>;
+								})()}
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)

6-6: Consider simplifying the wrapper div.

The wrapper div with h-full w-full may be redundant since:

  • The parent page already applies mx-auto w-full max-w-7xl (based on the summary)
  • ContactUsView has its own min-h-[80vh] styling

If no specific layout constraint requires this wrapper, you could simplify to:

 export default function AlertChannelsView() {
-	return (
-		<div className="h-full w-full">
-			<ContactUsView
+	return (
+		<ContactUsView
 				className="mx-auto min-h-[80vh]"
 				icon={<Siren className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
 				title="Unlock alert channels for better observability"
 				description="This feature is a part of the Bifrost enterprise license. We would love to know more about your use case and how we can help you."
 				readmeLink="https://docs.getbifrost.ai/enterprise/alert-channels"
 			/>
-		</div>
 	);
 }
ui/app/observability/views/observabilityView.tsx (1)

82-89: Use null instead of empty string for path in fallback plugin.

The Plugin type defines path: string | null, but the fallback object uses an empty string. For consistency and to represent "no path", use null instead.

Apply this diff:

 			const plugin = plugins.find((plugin) => plugin.name === selectedPluginId) ?? {
 				name: selectedPluginId,
 				enabled: false,
 				config: {},
 				isCustom: false,
-				path: "",
+				path: null,
 			};
 			dispatch(setSelectedPlugin(plugin));

Also applies to: 96-102

ui/app/plugins/page.tsx (1)

12-13: Consider handling loading state for better UX.

The isLoading state from useGetPluginsQuery is destructured but not used. Consider showing a loading indicator while plugins are being fetched.

Example:

if (isLoading) {
  return <div className="mx-auto w-full max-w-7xl">Loading plugins...</div>;
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e14cdec and 1c19b16.

⛔ Files ignored due to path filters (2)
  • plugins/governance/go.sum is excluded by !**/*.sum
  • plugins/telemetry/go.sum is excluded by !**/*.sum
📒 Files selected for processing (38)
  • framework/configstore/migrations.go (2 hunks)
  • framework/configstore/rdb.go (2 hunks)
  • framework/configstore/tables/plugin.go (1 hunks)
  • plugins/otel/docker-compose.yml (1 hunks)
  • transports/bifrost-http/handlers/inference.go (1 hunks)
  • transports/bifrost-http/handlers/plugins.go (3 hunks)
  • transports/bifrost-http/handlers/server.go (1 hunks)
  • transports/bifrost-http/lib/config.go (1 hunks)
  • ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1 hunks)
  • ui/app/adaptive-routing/page.tsx (1 hunks)
  • ui/app/alert-channels/page.tsx (1 hunks)
  • ui/app/audit-logs/page.tsx (1 hunks)
  • ui/app/cluster/page.tsx (1 hunks)
  • ui/app/config/page.tsx (1 hunks)
  • ui/app/config/views/pluginsForm.tsx (1 hunks)
  • ui/app/guardrails/page.tsx (1 hunks)
  • ui/app/layout.tsx (1 hunks)
  • ui/app/logs/page.tsx (1 hunks)
  • ui/app/logs/views/logsTable.tsx (2 hunks)
  • ui/app/mcp-clients/page.tsx (1 hunks)
  • ui/app/observability/page.tsx (1 hunks)
  • ui/app/observability/views/observabilityView.tsx (2 hunks)
  • ui/app/plugins/fragments/pluginFormFragement.tsx (1 hunks)
  • ui/app/plugins/page.tsx (1 hunks)
  • ui/app/plugins/sheets/addNewPluginSheet.tsx (1 hunks)
  • ui/app/prompt-repo/page.tsx (1 hunks)
  • ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (5 hunks)
  • ui/app/providers/dialogs/addNewKeySheet.tsx (3 hunks)
  • ui/app/providers/page.tsx (3 hunks)
  • ui/app/providers/views/modelProviderConfig.tsx (1 hunks)
  • ui/app/providers/views/modelProviderKeysTableView.tsx (2 hunks)
  • ui/app/providers/views/providerKeyForm.tsx (5 hunks)
  • ui/app/scim/page.tsx (1 hunks)
  • ui/app/user-groups/page.tsx (1 hunks)
  • ui/app/virtual-keys/page.tsx (2 hunks)
  • ui/components/sidebar.tsx (13 hunks)
  • ui/components/ui/promoCardStack.tsx (3 hunks)
  • ui/lib/types/plugins.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • ui/app/config/page.tsx
🚧 Files skipped from review as they are similar to previous changes (15)
  • ui/app/providers/dialogs/addNewCustomProviderSheet.tsx
  • transports/bifrost-http/handlers/plugins.go
  • ui/app/audit-logs/page.tsx
  • framework/configstore/rdb.go
  • ui/app/guardrails/page.tsx
  • ui/app/mcp-clients/page.tsx
  • ui/app/observability/page.tsx
  • ui/app/providers/views/modelProviderConfig.tsx
  • ui/app/logs/page.tsx
  • ui/app/providers/page.tsx
  • transports/bifrost-http/lib/config.go
  • ui/app/config/views/pluginsForm.tsx
  • ui/app/alert-channels/page.tsx
  • framework/configstore/migrations.go
  • ui/app/layout.tsx
🧰 Additional context used
🧬 Code graph analysis (12)
ui/app/prompt-repo/page.tsx (1)
ui/lib/utils.ts (1)
  • cn (4-6)
ui/app/scim/page.tsx (1)
ui/app/_fallbacks/enterprise/components/scim/scimView.tsx (1)
  • SCIMView (4-16)
ui/app/adaptive-routing/page.tsx (1)
ui/app/_fallbacks/enterprise/components/adaptive-routing/adaptiveRoutingView.tsx (1)
  • AdaptiveRoutingView (4-16)
ui/app/plugins/sheets/addNewPluginSheet.tsx (2)
ui/lib/types/plugins.ts (1)
  • Plugin (6-12)
ui/app/plugins/fragments/pluginFormFragement.tsx (1)
  • PluginFormFragment (23-131)
ui/app/virtual-keys/page.tsx (1)
ui/app/virtual-keys/views/virtualKeysTable.tsx (1)
  • VirtualKeysTable (34-230)
transports/bifrost-http/handlers/server.go (2)
ui/lib/types/plugins.ts (1)
  • Plugin (6-12)
core/schemas/plugin.go (1)
  • Plugin (45-71)
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)
ui/app/_fallbacks/enterprise/components/views/contactUsView.tsx (1)
  • ContactUsView (15-46)
ui/app/plugins/page.tsx (2)
ui/lib/types/plugins.ts (1)
  • Plugin (6-12)
ui/app/plugins/sheets/addNewPluginSheet.tsx (1)
  • AddNewPluginSheet (51-178)
ui/app/providers/views/modelProviderKeysTableView.tsx (1)
ui/app/providers/dialogs/addNewKeySheet.tsx (1)
  • AddNewKeySheet (14-46)
ui/components/sidebar.tsx (1)
ui/components/ui/promoCardStack.tsx (1)
  • PromoCardStack (21-120)
ui/app/providers/views/providerKeyForm.tsx (1)
ui/app/providers/fragments/apiKeysFormFragment.tsx (1)
  • ApiKeyFormFragment (21-445)
ui/app/plugins/fragments/pluginFormFragement.tsx (2)
ui/components/ui/input.tsx (1)
  • Input (15-69)
ui/app/logs/views/codeEditor.tsx (1)
  • CodeEditor (82-245)
🪛 Biome (2.1.2)
ui/app/plugins/fragments/pluginFormFragement.tsx

[error] 34-34: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.

(lint/security/noBlankTarget)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (15)
plugins/otel/docker-compose.yml (1)

1-229: Scope mismatch: infrastructure change unrelated to PR objectives.

This PR is titled "adds dynamic ui" and focuses on updating the admin interface sidebar (menu items, icons). However, this infrastructure/DevOps file appears to have been included unintentionally. The Tempo gRPC port change has no documented relationship to the UI feature.

Verify: Was this file change intentional, or should it be excluded from this PR?

ui/app/providers/views/providerKeyForm.tsx (3)

46-51: Good addition for immediate validation feedback.

Triggering validation on mount when editing existing data provides immediate feedback to users about any validation issues with the current data.


53-61: Good abstraction for tooltip content logic.

The getTooltipContent callback centralizes the tooltip logic and makes it easier to understand the different disabled states. The memoization with useCallback is appropriate here.


88-88: Good fix for Tailwind class syntax.

Corrected the invalid bg:white to the proper bg-white Tailwind class.

ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (2)

1-2: LGTM!

The imports are correct and align with the PR objectives to add a Siren icon for the Alert Channels feature.


7-13: Pattern verification complete—code approved.

The alertChannelsView.tsx implementation follows the well-established enterprise fallback pattern across all 10 similar components in the codebase. Icon sizing, prop structure, documentation links, and accessibility handling are all consistent and correct. No issues found.

ui/app/user-groups/page.tsx (1)

105-105: LGTM! Layout standardization.

The centered, width-constrained container aligns with the layout pattern applied consistently across multiple pages in this PR.

ui/app/scim/page.tsx (1)

5-9: LGTM! Layout standardization.

The centered, width-constrained container aligns with the layout pattern applied consistently across multiple pages in this PR.

ui/app/adaptive-routing/page.tsx (1)

4-8: LGTM! Layout standardization.

The centered, width-constrained container aligns with the layout pattern applied consistently across multiple pages in this PR.

ui/app/cluster/page.tsx (1)

4-8: LGTM! Layout standardization.

The centered, width-constrained container aligns with the layout pattern applied consistently across multiple pages in this PR.

framework/configstore/tables/plugin.go (1)

20-20: LGTM! IsCustom field addition.

The new IsCustom field is properly defined with appropriate GORM and JSON tags, integrates cleanly with existing serialization hooks, and supports the PR's custom plugin functionality.

ui/app/virtual-keys/page.tsx (1)

73-80: LGTM! Consistent centered layout pattern.

The addition of the centered container wrapper aligns with the standardized layout approach applied across multiple pages in this PR.

ui/lib/types/plugins.ts (2)

6-12: LGTM! Type extensions support custom plugin functionality.

The addition of isCustom and path fields properly extends the Plugin interface to support the dynamic plugin architecture introduced in this PR.


19-24: LGTM! CreatePluginRequest appropriately requires path.

The path field is correctly required for plugin creation, aligning with the validation schema in addNewPluginSheet.tsx where path is mandatory.

ui/app/providers/views/modelProviderKeysTableView.tsx (1)

78-83: LGTM! Clean migration from Dialog to Sheet.

The replacement of AddNewKeyDialog with AddNewKeySheet is implemented correctly, with all necessary props including the keyIndex properly passed through.

Comment on lines 374 to 382
func (s *BifrostHTTPServer) GetPluginsWithStatus() ([]schemas.Plugin, error) {
for _, plugin := range s.Plugins {
pluginStatus := map[string]any{
Name: plugin.GetName(),
Status: "active",
}
}
return pluginStatus, nil
}
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

Fix critical issues in GetPluginsWithStatus method.

This method has multiple critical problems that will prevent compilation or cause runtime errors:

  1. Line 377: Name is used as an undefined identifier; it should be the string literal "Name"
  2. Lines 376-379: pluginStatus is created inside the loop but never accumulated—each iteration overwrites it
  3. Line 381: Returns pluginStatus (type map[string]any) but the function signature declares ([]schemas.Plugin, error)

Apply this diff to fix the issues:

-func (s *BifrostHTTPServer) GetPluginsWithStatus() ([]schemas.Plugin, error) {
+func (s *BifrostHTTPServer) GetPluginsWithStatus() ([]map[string]any, error) {
+	pluginStatuses := make([]map[string]any, 0, len(s.Plugins))
 	for _, plugin := range s.Plugins {
 		pluginStatus := map[string]any{
-			Name: plugin.GetName(),
-			Status: "active",
+			"name":   plugin.GetName(),
+			"status": "active",
 		}
+		pluginStatuses = append(pluginStatuses, pluginStatus)
 	}
-	return pluginStatus, nil
+	return pluginStatuses, nil
 }

Comment on lines +20 to +21
<Sheet open={show} onOpenChange={onCancel}>
<SheetContent className="custom-scrollbar bg-white dark:bg-card min-w-[600px] py-4">
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

Fix onOpenChange typing to avoid TS error and only close on dismiss.

onOpenChange expects (open: boolean) => void; passing onCancel directly can fail typing and reopen-close loops.

-    <Sheet open={show} onOpenChange={onCancel}>
+    <Sheet
+      open={show}
+      onOpenChange={(open) => {
+        if (!open) onCancel();
+      }}
+    >
📝 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
<Sheet open={show} onOpenChange={onCancel}>
<SheetContent className="custom-scrollbar bg-white dark:bg-card min-w-[600px] py-4">
<Sheet
open={show}
onOpenChange={(open) => {
if (!open) onCancel();
}}
>
<SheetContent className="custom-scrollbar bg-white dark:bg-card min-w-[600px] py-4">
🤖 Prompt for AI Agents
In ui/app/providers/dialogs/addNewKeySheet.tsx around lines 20-21, onOpenChange
currently passes onCancel directly which mismatches the expected (open: boolean)
=> void signature and can cause reopen/close loops; change it to a properly
typed handler such as onOpenChange={(open: boolean) => { if (!open) onCancel();
}} so the sheet only calls onCancel when it is being dismissed (open === false)
and the handler matches the required boolean parameter type.

Comment on lines 24 to +30
<div className="font-lg flex items-center gap-2">
<div className={"flex items-center"}>
<Provider provider={provider.name} size={24} />:
</div>
{dialogTitle}
</div>
</DialogTitle>
</DialogHeader>
<div>
</SheetTitle>
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 | 🟡 Minor

Tailwind class typo: use text-lg (and optional font weight).

font-lg isn’t a Tailwind class.

-            <div className="font-lg flex items-center gap-2">
+            <div className="text-lg font-medium flex items-center gap-2">
📝 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
<div className="font-lg flex items-center gap-2">
<div className={"flex items-center"}>
<Provider provider={provider.name} size={24} />:
</div>
{dialogTitle}
</div>
</DialogTitle>
</DialogHeader>
<div>
</SheetTitle>
<div className="text-lg font-medium flex items-center gap-2">
<div className={"flex items-center"}>
<Provider provider={provider.name} size={24} />:
</div>
{dialogTitle}
</div>
</SheetTitle>
🤖 Prompt for AI Agents
In ui/app/providers/dialogs/addNewKeySheet.tsx around lines 24 to 30 the
Tailwind class "font-lg" is invalid; replace it with the correct class "text-lg"
(optionally add a weight like "font-medium" or "font-semibold" if desired) by
updating the div's className to "text-lg" (or "text-lg font-medium") so the
title size renders correctly.

@akshaydeo akshaydeo force-pushed the 10-20-dynamic-plugins-ui branch from 1c19b16 to 7af0d58 Compare October 25, 2025 23:09
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: 7

Caution

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

⚠️ Outside diff range comments (2)
framework/configstore/migrations.go (1)

619-629: Add defensive checks in rollback to prevent errors.

The rollback attempts to drop both columns without checking if they exist first. If the migration was partially applied or never run, the rollback will fail when trying to drop non-existent columns.

Apply this diff to add defensive checks:

 		Rollback: func(tx *gorm.DB) error {
 			tx = tx.WithContext(ctx)
 			migrator := tx.Migrator()
-			if err := migrator.DropColumn(&tables.TablePlugin{}, "path"); err != nil {
-				return err
+			if migrator.HasColumn(&tables.TablePlugin{}, "path") {
+				if err := migrator.DropColumn(&tables.TablePlugin{}, "path"); err != nil {
+					return err
+				}
 			}
-			if err := migrator.DropColumn(&tables.TablePlugin{}, "is_custom"); err != nil {
-				return err
+			if migrator.HasColumn(&tables.TablePlugin{}, "is_custom") {
+				if err := migrator.DropColumn(&tables.TablePlugin{}, "is_custom"); err != nil {
+					return err
+				}
 			}
 			return nil
 		},
ui/next.config.ts (1)

23-31: Fix path mismatch in tsconfig and consolidate duplicate alias logic.

Two issues found:

  1. Duplication: Both webpack and turbopack (lines 23-31, 39-46) define identical alias mappings—extract to constants to prevent drift.
  2. TypeScript path misalignment: ui/tsconfig.json defines @schemas fallback as ./app/_fallbacks/enterprise/lib/schemas/*, but this directory doesn't exist. The actual structure only has lib/ with store/ subdirectories. Update tsconfig to match the actual directory structure or align next.config.ts accordingly.
+const enterprisePath = haveEnterprise
+  ? path.join(__dirname, "app", "enterprise")
+  : path.join(__dirname, "app", "_fallbacks", "enterprise");
+const schemasPath = haveEnterprise
+  ? path.join(__dirname, "app", "enterprise", "lib", "schemas")
+  : path.join(__dirname, "app", "_fallbacks", "enterprise", "lib");
+
 webpack: (config) => {
   config.resolve = config.resolve || {};
   config.resolve.alias = config.resolve.alias || {};
-  config.resolve.alias["@enterprise"] = haveEnterprise
-    ? path.join(__dirname, "app", "enterprise")
-    : path.join(__dirname, "app", "_fallbacks", "enterprise");
-  config.resolve.alias["@schemas"] = haveEnterprise
-    ? path.join(__dirname, "app", "enterprise", "lib", "schemas")
-    : path.join(__dirname, "app", "_fallbacks", "enterprise", "lib");
+  config.resolve.alias["@enterprise"] = enterprisePath;
+  config.resolve.alias["@schemas"] = schemasPath;
   
-turbopack: {
-  resolveAlias: {
-    "@enterprise": haveEnterprise ? path.join(__dirname, "app", "enterprise") : path.join(__dirname, "app", "_fallbacks", "enterprise"),
-    "@schemas": haveEnterprise
-      ? path.join(__dirname, "app", "enterprise", "lib", "schemas")
-      : path.join(__dirname, "app", "_fallbacks", "enterprise", "lib"),
-  },
-},
+turbopack: {
+  resolveAlias: {
+    "@enterprise": enterprisePath,
+    "@schemas": schemasPath,
+  },
+},

Also verify and fix ui/tsconfig.json paths—the @schemas fallback references a non-existent /schemas subdirectory.

♻️ Duplicate comments (8)
ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (1)

98-98: Fixed minimum width still causes mobile overflow.

The min-w-[600px] continues to cause horizontal overflow on mobile devices (360-428px viewports). The past review comment recommended using the responsive pattern w-full flex-col overflow-x-hidden bg-white p-8 sm:max-w-2xl found elsewhere in the codebase.

ui/app/providers/dialogs/addNewKeySheet.tsx (3)

20-20: Incorrect onOpenChange handler type (previously flagged).

The onOpenChange={onCancel} has a type mismatch: onCancel is () => void but onOpenChange expects (open: boolean) => void. This can cause TypeScript errors and unexpected behavior.

Note that addNewCustomProviderSheet.tsx (line 97) uses the correct pattern: onOpenChange={(open) => !open && onClose()}. Apply the same fix here for consistency.


21-21: Fixed minimum width causes mobile overflow (previously flagged).

The min-w-[600px] will cause horizontal overflow on mobile devices (typical viewport: 360-428px). Past review suggested: w-full sm:min-w-[600px] sm:max-w-[600px] to ensure full width on mobile and fixed 600px on larger screens.


24-24: Invalid Tailwind class (previously flagged).

font-lg is not a valid Tailwind class. The correct class is text-lg (optionally with font-medium or font-semibold for weight).

Apply this diff:

-            <div className="font-lg flex items-center gap-2">
+            <div className="text-lg font-medium flex items-center gap-2">
ui/app/plugins/fragments/pluginFormFragement.tsx (1)

34-34: Add rel="noopener noreferrer" to external link.

Security risk with target="_blank" without rel. Patch below.

- } <a href="https://docs.getbifrost.ai/plugins/building-bifrost-plugin" target="_blank" className="text-primary hover:underline">Learn more</a>
+ } <a href="https://docs.getbifrost.ai/plugins/building-bifrost-plugin" target="_blank" rel="noopener noreferrer" className="text-primary hover:underline">Learn more</a>

Based on static analysis hints.

ui/app/plugins/page.tsx (1)

51-55: External link: add rel to prevent reverse‑tabnabbing.

- <Link className="text-primary hover:underline dark:text-green-400" href="https://docs.getbifrost.ai/plugins" target="_blank">
+ <Link className="text-primary hover:underline dark:text-green-400" href="https://docs.getbifrost.ai/plugins" target="_blank" rel="noopener noreferrer">

Based on static analysis hints.

ui/components/sidebar.tsx (2)

146-150: MCP naming mismatch: label vs route.

Title “MCP Gateway” points to /mcp-clients. Either keep label “MCP Clients” or rename the route/codebase consistently.

-    title: "MCP Gateway",
+    title: "MCP Clients",

Or plan a full rename: route folder, URLs, headings, docs, tests.


405-411: Auto‑expand logic is a no‑op.

newExpandedItems is never populated; parent sections won’t open on active subroutes.

 useEffect(() => {
   const newExpandedItems = new Set<string>();
+  items.forEach((it) => {
+    if (it.subItems?.some((s) => pathname.startsWith(s.url))) {
+      newExpandedItems.add(it.title);
+    }
+  });
 
   if (newExpandedItems.size > 0) {
     setExpandedItems((prev) => new Set([...prev, ...newExpandedItems]));
   }
 }, [pathname]);
🧹 Nitpick comments (17)
framework/configstore/migrations.go (1)

607-616: Minor: Clean up inconsistent spacing.

Line 610 has unusual trailing whitespace or spacing. While this doesn't affect functionality, consistent formatting improves code readability.

Apply this diff to fix the spacing:

 			if !migrator.HasColumn(&tables.TablePlugin{}, "path") {
 				if err := migrator.AddColumn(&tables.TablePlugin{}, "path"); err != nil {
 					return err
-				}				
+				}
 			}
ui/app/plugins/page.tsx (2)

36-44: Remove unnecessary preventDefault/stopPropagation on the button.

This Button isn’t inside a Link/form; the handlers add noise.

- onClick={(e) => {
-   e.preventDefault();
-   e.stopPropagation();
-   handleAddNew();
- }}
+ onClick={handleAddNew}

31-35: Show a lightweight loading/empty state.

Improve UX while plugins load or when none exist.

- {customPlugins?.map((plugin) => (
-   <div key={plugin.name}>{plugin.name}</div>
- ))}
+ {isLoading ? (
+   <div className="text-xs text-muted-foreground">Loading plugins…</div>
+ ) : customPlugins && customPlugins.length > 0 ? (
+   customPlugins.map((plugin) => <div key={plugin.name}>{plugin.name}</div>)
+ ) : (
+   <div className="text-xs text-muted-foreground">No custom plugins yet.</div>
+ )}
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)

9-13: Mark decorative icon as aria-hidden.

Icon is purely visual; hide from assistive tech.

-				icon={<Siren className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
+				icon={<Siren aria-hidden className="h-[5.5rem] w-[5.5rem]" strokeWidth={1} />}
ui/app/alert-channels/page.tsx (1)

3-8: LGTM on route and wrapper.

Optional: add page metadata for title to improve UX/SEO.

+export const metadata = { title: "Alert channels • Bifrost" };
ui/components/sidebar.tsx (3)

63-66: Update icon a11y text to match label or make decorative.

Current aria/title say “MCP clients icon” while label is “MCP Gateway”. Prefer decorative icon or synced text.

-  xmlns="http://www.w3.org/2000/svg"
-  aria-label="MCP clients icon"
+  xmlns="http://www.w3.org/2000/svg"
+  aria-hidden="true"
@@
-    <title>MCP clients icon</title>
+    {/* decorative */}

311-327: Avoid any for subitems; add a minimal SubItem type.

Improves safety and IntelliSense.

-          {item.subItems?.map((subItem: any) => {
+          type SubItem = { title: string; url: string; icon?: React.ComponentType<{ className?: string }>; description?: string };
+          {item.subItems?.map((subItem: SubItem) => {

Optionally type items with this shape to propagate types.


284-294: Expose disabled state to AT.

Add aria-disabled when navigation is disallowed.

-        <SidebarMenuButton
+        <SidebarMenuButton
           className={`relative h-7.5 cursor-pointer rounded-md border px-3 transition-all duration-200 ${
             ...
           } `}
-          onClick={hasSubItems ? handleClick : () => handleNavigation(item.url)}
+          aria-disabled={!isAllowed}
+          onClick={hasSubItems ? handleClick : () => handleNavigation(item.url)}
         >
ui/app/virtual-keys/page.tsx (2)

26-39: Handle config fetch errors explicitly.

Use unwrap + catch to surface failures; current code ignores network/500s.

- useEffect(() => {
-   triggerGetConfig({ fromDB: true }).then((res) => {
-     if (res.data && res.data.client_config.enable_governance) {
+ useEffect(() => {
+   triggerGetConfig({ fromDB: true })
+     .unwrap()
+     .then((data) => {
+       if (data?.client_config?.enable_governance) {
          setGovernanceEnabled(true);
          triggerGetVirtualKeys();
          triggerGetTeams({});
          triggerGetCustomers();
-     } else {
+       } else {
          setGovernanceEnabled(false);
          toast.error("Governance is not enabled. Please enable it in the config.");
-     }
-   });
+       }
+     })
+     .catch((e) => toast.error(getErrorMessage(e)));
 }, [triggerGetConfig, triggerGetVirtualKeys, triggerGetTeams, triggerGetCustomers]);

72-81: Show a clear empty‑state when governance is disabled.

Rendering an empty table after a toast is confusing. Gate the view.

-return (
-  <div className="mx-auto w-full max-w-7xl">
-    <VirtualKeysTable
-      virtualKeys={virtualKeysData?.virtual_keys || []}
-      teams={teamsData?.teams || []}
-      customers={customersData?.customers || []}
-      onRefresh={handleRefresh}
-    />
-  </div>
-);
+return (
+  <div className="mx-auto w-full max-w-7xl">
+    {governanceEnabled ? (
+      <VirtualKeysTable
+        virtualKeys={virtualKeysData?.virtual_keys || []}
+        teams={teamsData?.teams || []}
+        customers={customersData?.customers || []}
+        onRefresh={handleRefresh}
+      />
+    ) : (
+      <div className="text-muted-foreground rounded-md border p-6">
+        Governance is not enabled. Enable it in Settings to manage Virtual Keys.
+      </div>
+    )}
+  </div>
+);
core/schemas/plugin.go (1)

14-23: Type the status enum and omit empty logs.

Improves compile‑time safety and JSON ergonomics.

-// PluginStatus constants
-const (
-  PluginStatusActive        = "active"
-  PluginStatusError         = "error"
-  PluginStatusDisabled      = "disabled"
-  PluginStatusLoading       = "loading"
-  PluginStatusUninitialized = "uninitialized"
-  PluginStatusUnloaded      = "unloaded"
-  PluginStatusLoaded        = "loaded"
-)
+// PluginStatusValue is the canonical status type for plugins.
+type PluginStatusValue string
+const (
+  PluginStatusActive        PluginStatusValue = "active"
+  PluginStatusError         PluginStatusValue = "error"
+  PluginStatusDisabled      PluginStatusValue = "disabled"
+  PluginStatusLoading       PluginStatusValue = "loading"
+  PluginStatusUninitialized PluginStatusValue = "uninitialized"
+  PluginStatusUnloaded      PluginStatusValue = "unloaded"
+  PluginStatusLoaded        PluginStatusValue = "loaded"
+)

 // PluginStatus represents the status of a plugin.
 type PluginStatus struct {
   Name   string   `json:"name"`
-  Status string   `json:"status"`
-  Logs   []string `json:"logs"`
+  Status PluginStatusValue `json:"status"`
+  Logs   []string `json:"logs,omitempty"`
 }

Also applies to: 27-30

ui/app/observability/views/observabilityView.tsx (1)

82-90: DRY up default plugin construction.

Build a small helper to avoid duplicating the fallback object.

function fallbackPlugin(name: string) {
  return { name, enabled: false, config: {}, isCustom: false, path: "" };
}

Then:

- const plugin = plugins.find((p) => p.name === selectedPluginId) ?? {
-   name: selectedPluginId, enabled: false, config: {}, isCustom: false, path: "",
- };
+ const plugin = plugins.find((p) => p.name === selectedPluginId) ?? fallbackPlugin(selectedPluginId);

Also applies to: 96-104

transports/bifrost-http/handlers/server.go (5)

663-666: Move the lock to cover only assignment, not plugin loading.

Holding the write lock during LoadPlugins is unnecessary and blocks readers longer.

- s.pluginStatusMutex.Lock()
- defer s.pluginStatusMutex.Unlock()
- s.Plugins, s.pluginStatus, err = LoadPlugins(ctx, s.Config)
+ plugins, statuses, err := LoadPlugins(ctx, s.Config)
  if err != nil {
    return fmt.Errorf("failed to load plugins %v", err)
  }
+ s.pluginStatusMutex.Lock()
+ s.Plugins, s.pluginStatus = plugins, statuses
+ s.pluginStatusMutex.Unlock()

650-651: Mutex re-init is unnecessary.

sync.RWMutex{} zero value is already valid; reassigning can surprise future readers.

-    s.pluginStatusMutex = sync.RWMutex{}
+    // no-op: zero value of sync.RWMutex is ready to use

436-454: Validate status input and keep one entry per plugin.

Add a quick guard to reject invalid statuses.

 func (s *BifrostHTTPServer) UpdatePluginStatus(name string, status string, logs []string) error {
+  switch status {
+  case string(schemas.PluginStatusActive), string(schemas.PluginStatusError),
+       string(schemas.PluginStatusDisabled), string(schemas.PluginStatusLoading),
+       string(schemas.PluginStatusUninitialized), string(schemas.PluginStatusUnloaded),
+       string(schemas.PluginStatusLoaded):
+    // ok
+  default:
+    return fmt.Errorf("invalid plugin status: %s", status)
+  }
   s.pluginStatusMutex.Lock()
   defer s.pluginStatusMutex.Unlock()
   // Remove plugin status if already exists

493-499: Mark plugin as active after successful reload.

Reflect success in status to keep UI in sync.

-      s.Plugins = newPlugins // Keep BifrostHTTPServer.Plugins in sync
-      return nil
+      s.Plugins = newPlugins // Keep BifrostHTTPServer.Plugins in sync
+      _ = s.UpdatePluginStatus(name, schemas.PluginStatusActive, []string{"plugin reloaded successfully"})
+      return nil

60-66: Provide a read-only getter for plugin statuses.

Expose a copy under RLock to avoid races and slice aliasing.

Add this method somewhere in this file:

// GetPluginStatuses returns a snapshot copy of plugin statuses.
func (s *BifrostHTTPServer) GetPluginStatuses() []schemas.PluginStatus {
  s.pluginStatusMutex.RLock()
  defer s.pluginStatusMutex.RUnlock()
  out := make([]schemas.PluginStatus, len(s.pluginStatus))
  copy(out, s.pluginStatus)
  return out
}

If helpful, I can thread this into the Plugins handler.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1c19b16 and 7af0d58.

⛔ Files ignored due to path filters (6)
  • framework/go.sum is excluded by !**/*.sum
  • plugins/governance/go.sum is excluded by !**/*.sum
  • plugins/logging/go.sum is excluded by !**/*.sum
  • plugins/otel/go.sum is excluded by !**/*.sum
  • plugins/telemetry/go.sum is excluded by !**/*.sum
  • ui/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (45)
  • core/schemas/plugin.go (1 hunks)
  • framework/configstore/migrations.go (2 hunks)
  • framework/configstore/rdb.go (2 hunks)
  • framework/configstore/tables/plugin.go (1 hunks)
  • framework/go.mod (1 hunks)
  • plugins/logging/go.mod (2 hunks)
  • plugins/otel/docker-compose.yml (1 hunks)
  • plugins/otel/go.mod (1 hunks)
  • transports/bifrost-http/handlers/inference.go (1 hunks)
  • transports/bifrost-http/handlers/plugins.go (3 hunks)
  • transports/bifrost-http/handlers/server.go (8 hunks)
  • transports/bifrost-http/lib/config.go (1 hunks)
  • ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1 hunks)
  • ui/app/adaptive-routing/page.tsx (1 hunks)
  • ui/app/alert-channels/page.tsx (1 hunks)
  • ui/app/audit-logs/page.tsx (1 hunks)
  • ui/app/cluster/page.tsx (1 hunks)
  • ui/app/config/page.tsx (1 hunks)
  • ui/app/config/views/pluginsForm.tsx (1 hunks)
  • ui/app/guardrails/page.tsx (1 hunks)
  • ui/app/layout.tsx (1 hunks)
  • ui/app/logs/page.tsx (1 hunks)
  • ui/app/logs/views/logsTable.tsx (2 hunks)
  • ui/app/mcp-clients/page.tsx (1 hunks)
  • ui/app/observability/page.tsx (1 hunks)
  • ui/app/observability/views/observabilityView.tsx (2 hunks)
  • ui/app/plugins/fragments/pluginFormFragement.tsx (1 hunks)
  • ui/app/plugins/page.tsx (1 hunks)
  • ui/app/plugins/sheets/addNewPluginSheet.tsx (1 hunks)
  • ui/app/prompt-repo/page.tsx (1 hunks)
  • ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (5 hunks)
  • ui/app/providers/dialogs/addNewKeySheet.tsx (3 hunks)
  • ui/app/providers/page.tsx (3 hunks)
  • ui/app/providers/views/modelProviderConfig.tsx (1 hunks)
  • ui/app/providers/views/modelProviderKeysTableView.tsx (2 hunks)
  • ui/app/providers/views/providerKeyForm.tsx (5 hunks)
  • ui/app/scim/page.tsx (1 hunks)
  • ui/app/user-groups/page.tsx (1 hunks)
  • ui/app/virtual-keys/page.tsx (2 hunks)
  • ui/components/sidebar.tsx (13 hunks)
  • ui/components/ui/promoCardStack.tsx (3 hunks)
  • ui/lib/types/plugins.ts (2 hunks)
  • ui/next.config.ts (1 hunks)
  • ui/package.json (1 hunks)
  • ui/tsconfig.json (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • transports/bifrost-http/handlers/inference.go
🚧 Files skipped from review as they are similar to previous changes (22)
  • ui/app/logs/page.tsx
  • ui/app/adaptive-routing/page.tsx
  • framework/configstore/rdb.go
  • ui/app/guardrails/page.tsx
  • transports/bifrost-http/lib/config.go
  • ui/app/providers/page.tsx
  • transports/bifrost-http/handlers/plugins.go
  • ui/app/user-groups/page.tsx
  • ui/lib/types/plugins.ts
  • ui/components/ui/promoCardStack.tsx
  • ui/app/providers/views/providerKeyForm.tsx
  • ui/app/audit-logs/page.tsx
  • framework/configstore/tables/plugin.go
  • ui/app/providers/views/modelProviderConfig.tsx
  • ui/app/prompt-repo/page.tsx
  • ui/app/config/views/pluginsForm.tsx
  • ui/app/scim/page.tsx
  • ui/app/cluster/page.tsx
  • ui/app/logs/views/logsTable.tsx
  • plugins/otel/docker-compose.yml
  • ui/app/config/page.tsx
  • ui/app/plugins/sheets/addNewPluginSheet.tsx
🧰 Additional context used
🧬 Code graph analysis (10)
ui/app/plugins/page.tsx (2)
ui/lib/types/plugins.ts (1)
  • Plugin (6-12)
ui/app/plugins/sheets/addNewPluginSheet.tsx (1)
  • AddNewPluginSheet (60-174)
ui/components/sidebar.tsx (1)
ui/components/ui/promoCardStack.tsx (1)
  • PromoCardStack (21-120)
ui/app/virtual-keys/page.tsx (1)
ui/app/virtual-keys/views/virtualKeysTable.tsx (1)
  • VirtualKeysTable (34-230)
framework/configstore/migrations.go (1)
framework/configstore/tables/plugin.go (2)
  • TablePlugin (12-24)
  • TablePlugin (27-27)
transports/bifrost-http/handlers/server.go (6)
core/schemas/plugin.go (5)
  • Plugin (63-89)
  • PluginStatus (26-30)
  • PluginStatusError (17-17)
  • PluginStatusActive (16-16)
  • PluginStatusDisabled (18-18)
core/bifrost.go (1)
  • Bifrost (34-51)
transports/bifrost-http/lib/config.go (1)
  • Config (136-166)
plugins/governance/main.go (2)
  • Config (29-31)
  • PluginName (20-20)
plugins/telemetry/main.go (2)
  • PrometheusPlugin (30-44)
  • PluginName (18-18)
plugins/logging/main.go (1)
  • PluginName (21-21)
ui/app/plugins/fragments/pluginFormFragement.tsx (2)
ui/components/ui/input.tsx (1)
  • Input (15-69)
ui/app/logs/views/codeEditor.tsx (1)
  • CodeEditor (82-245)
ui/app/observability/page.tsx (1)
ui/app/observability/views/observabilityView.tsx (1)
  • ObservabilityView (67-168)
ui/app/alert-channels/page.tsx (1)
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)
  • AlertChannelsView (4-16)
ui/app/providers/views/modelProviderKeysTableView.tsx (1)
ui/app/providers/dialogs/addNewKeySheet.tsx (1)
  • AddNewKeySheet (14-46)
ui/app/_fallbacks/enterprise/components/alert-channels/alertChannelsView.tsx (1)
ui/app/_fallbacks/enterprise/components/views/contactUsView.tsx (1)
  • ContactUsView (15-46)
🪛 Biome (2.1.2)
ui/app/plugins/fragments/pluginFormFragement.tsx

[error] 34-34: Avoid using target="_blank" without rel="noopener" or rel="noreferrer".

Opening external links in new tabs without rel="noopener" is a security risk. See the explanation for more details.
Safe fix: Add the rel="noopener" attribute.

(lint/security/noBlankTarget)

⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (15)
plugins/logging/go.mod (1)

8-8: Low-risk dependency updates; clarify alignment with PR objectives.

The sonic v1.14.1 release (Sep 2, 2025) includes bug fixes for boundary pointer handling, TextMarshaler key encoding, UTF-8 validation, and JSON decoding with null characters. Sonic shows 0 unresolved vulnerabilities and no reported dependency vulnerabilities. The new indirect dependency on gopkg v0.1.3 (published Aug 13, 2025) is Apache-2.0 licensed.

Both changes are low-risk and appear appropriate for dependency maintenance. However, the PR objectives describe UI/Next.js changes for the admin sidebar navigation. It's unclear why a Go module update for the logging plugin is included here. Consider clarifying whether this change:

  • Is part of a broader plugin architecture refactor (mentioned in the PR stack context),
  • Was included accidentally, or
  • Needs to be merged as part of the Graphite stack.

Also applies to: 33-33

ui/tsconfig.json (2)

44-45: Generated types path addition is well-motivated.

The inclusion of "out/dev/types/**/*.ts" aligns with the PR's new plugin architecture and dynamic type generation. Ensure the build process creates this directory and that no unintended files are included in type checking.


18-18: All concerns verified—no action needed.

React 19.1.1 fully supports the react-jsx runtime (required React 17+), and no legacy import React from 'react' statements were found in the codebase. The JSX configuration change is safe and compatible.

framework/go.mod (2)

1-17: Significant mismatch between PR objectives and file changes.

The PR is described as a UI-only feature for Next.js sidebar navigation (Plugins menu, Alert channels, icons), but this file contains backend Go dependency version upgrades. The AI summary mentions plugin path/isCustom DB fields and server-side tracking, suggesting infrastructure changes beyond UI.

This requires clarification on whether the backend changes are intentional and properly scoped.

Can you confirm whether this PR should include backend infrastructure changes, or if this file was inadvertently included?


10-10: I need to verify if these dependency updates introduce breaking changes. Let me search for release information.

Dependencies are compatible and secure. Critical breaking change in weaviate-go-client v5 has been addressed.

The redis/go-redis update adjusts buffer sizes from 512KiB to 256KiB with no API breaking changes. Weaviate v1.31.0 has no breaking changes, and v1.33.1 is a minor release.

For weaviate-go-client: A breaking change exists in v5.0+ where models.Vector changed from []float32 to interface{} to support multi-dimensional vectors. Since the PR uses v5.5.0, this change is already past that breaking change. Verify that your codebase handles the Vector type correctly if you have direct Vector type usage.

Security status: redis/go-redis v9.14.0 is outside all reported vulnerable ranges; weaviate v1.33.1 has no known vulnerabilities in the queried advisories; weaviate-go-client shows no security advisories.

ui/app/providers/dialogs/addNewCustomProviderSheet.tsx (2)

5-5: LGTM: Clean migration to Sheet-based component.

The import changes and function renaming are consistent with the Dialog → Sheet migration pattern seen across the codebase.

Also applies to: 37-37, 41-41


104-104: LGTM: Proper Sheet layout structure.

The padding and footer layout align with standard Sheet component patterns.

Also applies to: 163-170

ui/app/providers/views/modelProviderKeysTableView.tsx (1)

13-23: LGTM: Correct integration of AddNewKeySheet.

The component is properly integrated with all required props (show, onCancel, provider, keyIndex) correctly passed. The state management at line 32 appropriately tracks the keyIndex for both add and edit operations.

Also applies to: 78-83

plugins/otel/go.mod (1)

10-10: Clarify whether the gRPC dependency bump belongs in this PR.

The gRPC v1.76.0 bump is compatible with your project's Go version requirement (Go 1.24.1 already satisfies the v1.76.0 minimum Go 1.24 requirement), and the release introduces no critical breaking changes—only new JWT credentials, bug fixes, and performance improvements.

However, this PR's objectives describe UI-only changes (sidebar navigation, icons from lucide-react), yet line 10 modifies a Go module dependency. Clarify whether:

  1. This is part of a larger stack change managed by Graphite, or
  2. This dependency update should be in a separate PR.
ui/app/plugins/fragments/pluginFormFragement.tsx (1)

7-7: Remove review comment — PlusIcon is a valid lucide-react export.

Lucide includes both prefixed and suffixed names to use within your project to prevent import name collisions with other libraries or your own code. Both import { Camera } from "lucide-react" and import { CameraIcon } from "lucide-react" reference the same icon file. The import statement using PlusIcon will not cause build failures.

ui/package.json (1)

49-49: Verify Node.js version meets minimum requirements (>= 20.9.0) and test build/dev server locally before merging.

The upgrade to Next.js 16.0.0 appears compatible with your current dependency tree:

  • React 19.1.1 is officially supported by Next.js 16
  • Radix UI has official React 19 support, so all radix-ui components in your dependencies are compatible
  • Your next.config.ts uses static export mode (output: "export"), which avoids most breaking changes related to runtime APIs and middleware
  • No deprecated patterns detected (no serverRuntimeConfig/publicRuntimeConfig, no legacy middleware)
  • No usage of APIs requiring async/await refactoring (cookies, headers, params, draftMode)

However, verify:

  • Node.js >= 20.9.0 and TypeScript >= 5.1.0 are enforced in your CI/deployment environment (not pinned in the codebase)
  • Run npm install and test the dev server and build locally to catch any transitive dependency or configuration issues
ui/app/observability/page.tsx (1)

4-8: LGTM.

Consistent container wrapper with other pages.

ui/next.config.ts (1)

39-46: Turbopack config is supported — no changes needed.

Next.js supports a top-level turbopack.resolveAlias in next.config.js/ts, which accepts string replacements. The code correctly maps aliases to strings via path.join(), matching the expected shape. No validation required.

ui/app/mcp-clients/page.tsx (1)

29-31: LGTM: centered container wrapper aligns with new layout system.

ui/app/observability/views/observabilityView.tsx (1)

7-9: All referenced images are present and accessible.

Verification confirms that all image assets referenced in observabilityView.tsx exist in the public directory:

  • /maxim-logo.png
  • /maxim-logo-dark.png
  • /images/datadog-logo.png

No runtime 404 risks for the Image component usage on lines 50 and 54.

func migrationAddPluginPathColumn(ctx context.Context, db *gorm.DB) error {
m := migrator.New(db, migrator.DefaultOptions, []*migrator.Migration{{
ID: "add_plugin_path_column",
ID: "update_plugins_table_for_custom_plugins",
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

🧩 Analysis chain

CRITICAL: Verify migration ID change impact before merging.

Changing the migration ID from "add_plugin_path_column" to "update_plugins_table_for_custom_plugins" will cause the migration system to treat this as a completely new migration. If the old ID was already executed in any environment (dev, staging, production), the system will attempt to run this migration again, potentially causing conflicts even with the HasColumn checks.

Please verify:

  1. Has the migration with ID "add_plugin_path_column" been deployed to any environment?
  2. If yes, you'll need a different approach (e.g., a separate additive migration for the is_custom column)
  3. If no, document that this change is safe because the old ID was never released

Run this script to check if the old migration ID exists in the migration history:


🏁 Script executed:

#!/bin/bash
# Search for any references to the old migration ID in the codebase and git history
echo "=== Checking for old migration ID in current codebase ==="
rg -n "add_plugin_path_column" --type go

echo -e "\n=== Checking git history for when old migration ID was introduced ==="
git log --all --oneline --grep="add_plugin_path_column" || echo "No commits found with old migration ID in message"

echo -e "\n=== Checking if old migration ID exists in git history ==="
git log -p --all -S "add_plugin_path_column" -- "*.go" | head -50

Length of output: 188


🏁 Script executed:

#!/bin/bash
# Check the git history of the migrations.go file
echo "=== Git history of migrations.go file ==="
git log --oneline framework/configstore/migrations.go | head -20

echo -e "\n=== When was this specific migration function added? ==="
git log -p --all -- framework/configstore/migrations.go | grep -A5 -B5 "migrationAddPluginPathColumn" | head -40

echo -e "\n=== Current state of the function around line 600-630 ==="
sed -n '600,630p' framework/configstore/migrations.go

Length of output: 306


🏁 Script executed:

#!/bin/bash
# Check the git history of the migrations.go file
echo "=== Git history of migrations.go file ==="
git log --oneline framework/configstore/migrations.go | head -20

echo -e "\n=== When was this specific migration function added? ==="
git log -p --all -- framework/configstore/migrations.go | grep -A10 -B5 "migrationAddPluginPathColumn" | head -50

echo -e "\n=== Current state of the function around line 600-636 ==="
sed -n '600,636p' framework/configstore/migrations.go

echo -e "\n=== Check for rollback defensive checks ==="
sed -n '619,629p' framework/configstore/migrations.go

Length of output: 2291


🏁 Script executed:

#!/bin/bash
# Search for the old migration ID across entire git history
echo "=== Search for any migration with old ID 'add_plugin_path_column' across all history ==="
git log -p --all -S "add_plugin_path_column" -- framework/configstore/migrations.go | head -100

echo -e "\n=== Check when migrationAddPluginPathColumn was first added ==="
git log --all --source --remotes --branches -p -- framework/configstore/migrations.go | grep -A30 "func migrationAddPluginPathColumn" | head -50

echo -e "\n=== Check if this function exists in main/master branch ==="
git show origin/main:framework/configstore/migrations.go 2>/dev/null | grep -A5 "migrationAddPluginPathColumn" || echo "Function not found in main branch"

Length of output: 2118


CRITICAL: Migration ID change will re-execute already-deployed migration in production.

The git history confirms that the migration ID "add_plugin_path_column" was already deployed. Changing it to "update_plugins_table_for_custom_plugins" will cause the migration system to treat this as a completely new migration and attempt to execute it again across all environments, even though the HasColumn checks exist.

Action required: Either:

  • Revert the ID change to "add_plugin_path_column" and create a separate new migration for the is_custom column, OR
  • Confirm this migration was never actually deployed and document why the ID change is safe

Secondary issue: The rollback logic lacks defensive checks. Add HasColumn guards before DropColumn calls (lines 625–627) to prevent failures if the migration partially executed.

🤖 Prompt for AI Agents
In framework/configstore/migrations.go around line 603 (where ID:
"update_plugins_table_for_custom_plugins" is set) revert the migration ID back
to the original "add_plugin_path_column" unless you can prove this migration was
never deployed and document that justification; if you need to add the new
is_custom column, create a separate migration file with a new unique ID instead
of renaming the existing one. Additionally, in the rollback section around lines
625–627, add defensive HasColumn checks before each DropColumn call so the
rollback only attempts to drop columns that exist, preventing failures from
partially-applied migrations.

Comment on lines +612 to 616
if !migrator.HasColumn(&tables.TablePlugin{}, "is_custom") {
if err := migrator.AddColumn(&tables.TablePlugin{}, "is_custom"); err != nil {
return err
}
}
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

🧩 Analysis chain

Verify default value is applied to existing rows.

When adding the is_custom column, existing plugin rows should default to false. The GORM struct tag gorm:"default:false" should handle this, but the behavior can vary across database engines.

Consider running this verification script after the migration to confirm existing rows have is_custom = false:


🏁 Script executed:

#!/bin/bash
# Verify that all existing plugins have is_custom set to false after migration
# This requires database access credentials to be configured

echo "This script requires manual execution with database access"
echo "Example SQL to verify after migration:"
echo ""
echo "SELECT name, is_custom, path FROM config_plugins WHERE is_custom IS NULL OR is_custom = true;"
echo ""
echo "Expected: No rows returned (all existing plugins should have is_custom = false)"

Length of output: 273



---

🏁 Script executed:

```shell
#!/bin/bash
# Examine the is_custom field definition in tables/plugin.go
fd -type f -name "plugin.go" --path "*/tables/*" --exec cat {} \; | grep -A5 -B5 "is_custom"

Length of output: 170


🏁 Script executed:

#!/bin/bash
# View the migration code context around lines 612-616
cat framework/configstore/migrations.go | sed -n '605,625p'

Length of output: 694


🏁 Script executed:

#!/bin/bash
# Check if there are any explicit ALTER statements or default value assignments in the migration
rg -n "is_custom" framework/configstore/migrations.go -B3 -A3

Length of output: 483



---

🏁 Script executed:

```shell
#!/bin/bash
# Find plugin.go in tables directory
fd -type f -name "plugin.go" 

Length of output: 170


🏁 Script executed:

#!/bin/bash
# Once found, search for is_custom field definition with GORM tags
rg -n "is_custom" --type go -B2 -A2 | grep -A2 -B2 "is_custom"

Length of output: 750



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for TablePlugin struct definition in Go files
rg -n "type TablePlugin" -A30 | head -60

Length of output: 2320


🏁 Script executed:

#!/bin/bash
# Search for is_custom across all files to find the struct definition
find . -name "*.go" -type f | xargs grep -l "is_custom" | head -10

Length of output: 37


🏁 Script executed:

#!/bin/bash
# Look for tables directory and plugin definitions
find . -path "*/tables/*.go" -type f 2>/dev/null | head -20

Length of output: 802



---

🏁 Script executed:

```shell
#!/bin/bash
# Check the full migration block around is_custom to see if there's an UPDATE statement
rg -n "is_custom" framework/configstore/migrations.go -B10 -A10

Length of output: 1157


🏁 Script executed:

#!/bin/bash
# Look for UPDATE statements in the migration function around line 612
sed -n '600,635p' framework/configstore/migrations.go

Length of output: 1170


Comment on lines +284 to 286
test, _ := sonic.Marshal(config.PluginConfigs)
logger.Debug("loading plugins from config %s", string(test))
var err error
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 | 🟠 Major

Don’t log raw plugin configs; may leak secrets.

sonic.Marshal(config.PluginConfigs) and the debug log can expose credentials/paths.

- test, _ := sonic.Marshal(config.PluginConfigs)
- logger.Debug("loading plugins from config %s", string(test))
+ // Avoid logging raw plugin configs to prevent credential leakage.
+ logger.Debug("loading plugins from config (redacted)")
📝 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
test, _ := sonic.Marshal(config.PluginConfigs)
logger.Debug("loading plugins from config %s", string(test))
var err error
// Avoid logging raw plugin configs to prevent credential leakage.
logger.Debug("loading plugins from config (redacted)")
var err error
🤖 Prompt for AI Agents
In transports/bifrost-http/handlers/server.go around lines 284 to 286, the code
marshals and debug-logs the entire PluginConfigs which can leak secrets; remove
the raw marshal-and-log or replace it with a safe redaction step: build a
shallow copy of config.PluginConfigs that omits or masks known sensitive keys
(e.g., "password", "secret", "token", "key", "credentials", "access_key",
"secret_key"), marshal and log that redacted copy or simply log only plugin
names/keys, and ensure no raw credential values are printed.

<Sidebar />
<div className="dark:bg-card custom-scrollbar my-[1rem] h-[calc(100dvh-2rem)] w-full overflow-auto rounded-md border border-gray-200 bg-white dark:border-zinc-800">
<main className="custom-scrollbar relative mx-auto flex w-5xl flex-col px-4 py-12 2xl:w-7xl">
<main className="custom-scrollbar relative mx-auto flex flex-col p-4">
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

🧩 Analysis chain

Avoid nested scroll areas; remove duplicate class on

.

Both wrapper and main have custom-scrollbar. Keep it on the scroll container only.

- <main className="custom-scrollbar relative mx-auto flex flex-col p-4">
+ <main className="relative mx-auto flex flex-col p-4">

Quick check to ensure every page is now self-wrapping (so removing old global max-width won’t make content edge-to-edge):


🏁 Script executed:

#!/bin/bash
# Lists app pages missing the max-w-7xl wrapper
fd -a -t f 'page.tsx' ui/app | while read -r f; do
  if ! rg -n 'max-w-7xl' "$f" >/dev/null; then
    echo "Missing wrapper: $f"
  fi
done

Length of output: 259



Avoid nested scroll areas AND wrap these 4 pages with max-w-7xl.

Removing custom-scrollbar from <main> is correct (keep it only on scroll container). However, verification found 4 pages still missing the max-w-7xl wrapper required after global max-width removal:

  • ui/app/guardrails/configuration/page.tsx
  • ui/app/guardrails/providers/page.tsx
  • ui/app/login/page.tsx
  • ui/app/page.tsx

Add max-w-7xl mx-auto wrapper to each page's content container before merging.

- <main className="custom-scrollbar relative mx-auto flex flex-col p-4">
+ <main className="relative mx-auto flex flex-col p-4">
📝 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
<main className="custom-scrollbar relative mx-auto flex flex-col p-4">
<main className="relative mx-auto flex flex-col p-4">
🤖 Prompt for AI Agents
In ui/app/layout.tsx around line 41, remove the custom-scrollbar class from the
<main> element so the global scroll container is the only scroll area (avoid
nested scrollbars); then, for each of the four pages listed
(ui/app/guardrails/configuration/page.tsx, ui/app/guardrails/providers/page.tsx,
ui/app/login/page.tsx, ui/app/page.tsx) wrap the page content container with a
div that adds the layout constraint classes max-w-7xl mx-auto (place this
wrapper as the outermost content container inside the page so content is
centered and constrained to max width).

@@ -0,0 +1,131 @@
"use client";
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 | 🟠 Major

🧩 Analysis chain

Rename file to pluginFormFragment.tsx for consistency.

Typo in filename (“Fragement”). Rename file and update imports to improve discoverability and avoid future import mistakes.


🏁 Script executed:

#!/bin/bash
# Find all imports to update in a rename
rg -n "pluginFormFragement" -S

Length of output: 117



---

🏁 Script executed:

```shell
#!/bin/bash
# Search for any other references to the file (including re-exports, test files, etc.)
rg -n "pluginFormFragement" --type-list | head -20
rg -n "pluginFormFragement" -g '!node_modules' 2>/dev/null || rg -n "pluginFormFragement"

Length of output: 507


🏁 Script executed:

#!/bin/bash
# Alternative broader search for the filename or component
rg "pluginForm" -n

Length of output: 393



---

🏁 Script executed:

```shell
#!/bin/bash
# Check for test files or other file extensions that might reference the fragment
rg "pluginFormFragement" -g '*.test.*' -g '*.spec.*' 2>/dev/null || echo "No test files found with reference"
# Also check for barrel exports or index files
fd "index\.(ts|tsx|js|jsx)" -x rg -l "pluginFormFragement" {} \;

Length of output: 34


Rename file to pluginFormFragment.tsx to fix typo and update import.

Confirmed one import location that requires update:

  • ui/app/plugins/sheets/addNewPluginSheet.tsx:13: Change import path from "../fragments/pluginFormFragement" to "../fragments/pluginFormFragment"
🤖 Prompt for AI Agents
In ui/app/plugins/fragments/pluginFormFragement.tsx around line 1 the filename
contains a typo; rename the file to pluginFormFragment.tsx and update its
default/relative export path; then update the import in
ui/app/plugins/sheets/addNewPluginSheet.tsx line 13 to import from
"../fragments/pluginFormFragment" (ensure any other imports/exports referencing
the old name are updated accordingly).

Comment on lines +24 to +25
const [showConfig, setShowConfig] = useState(form.getValues("hasConfig") || false);

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 | 🟠 Major

Fix state drift between showConfig and form.hasConfig.

showConfig is initialized once from form and won’t reflect later form.reset()/watch updates (e.g., editing a plugin with existing config). This can hide the editor incorrectly. Minimal sync fix below; preferred approach removes duplicate state.

Option A — minimal sync (keep state):

-import { useState } from "react";
+import { useEffect, useState } from "react";
@@
-const [showConfig, setShowConfig] = useState(form.getValues("hasConfig") || false);
+const [showConfig, setShowConfig] = useState(form.getValues("hasConfig") || false);
+const hasConfig = form.watch("hasConfig");
+useEffect(() => {
+  setShowConfig(!!hasConfig);
+}, [hasConfig]);

Option B — preferred (remove local state; derive from form):

-import { useState } from "react";
+// no local state needed
@@
-const [showConfig, setShowConfig] = useState(form.getValues("hasConfig") || false);
+const showConfig = !!form.watch("hasConfig");
@@
- onClick={() => {
-   setShowConfig(true);
-   form.setValue("hasConfig", true);
-   if (!form.getValues("config")) {
-     form.setValue("config", "{}");
-   }
- }}
+ onClick={() => {
+   form.setValue("hasConfig", true, { shouldDirty: true });
+   if (!form.getValues("config")) form.setValue("config", "{}", { shouldDirty: true });
+ }}
@@
- onClick={() => {
-   setShowConfig(false);
-   form.setValue("hasConfig", false);
-   form.setValue("config", undefined);
- }}
+ onClick={() => {
+   form.setValue("hasConfig", false, { shouldDirty: true });
+   form.setValue("config", undefined, { shouldDirty: true });
+ }}

Also applies to: 66-83, 91-103

"moment": "2.30.1",
"monaco-editor": "0.52.2",
"next": "15.5.3",
"next": "16.0.0",
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

Fix version mismatch between next and eslint-config-next.

The next package has been upgraded to 16.0.0, but eslint-config-next on line 76 is still pinned to 15.5.2. This version mismatch can lead to linting inconsistencies and compatibility issues.

Update eslint-config-next to match the Next.js version:

-		"eslint-config-next": "15.5.2",
+		"eslint-config-next": "16.0.0",

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

🤖 Prompt for AI Agents
In ui/package.json around lines 49 and 76, Next.js is upgraded to 16.0.0 but
eslint-config-next remains at 15.5.2; update the eslint-config-next dependency
to match Next.js (set it to 16.0.0 or a matching semver range), save the
package.json change and reinstall dependencies (npm/yarn install) so the
lockfile is updated and the linter uses the compatible config.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant