Skip to content

Configuration: UI/UX Revamp#157

Merged
Ayush8923 merged 18 commits into
feat/document-knowledge-base-ui-revampfrom
feat/configuration-revamp
May 11, 2026
Merged

Configuration: UI/UX Revamp#157
Ayush8923 merged 18 commits into
feat/document-knowledge-base-ui-revampfrom
feat/configuration-revamp

Conversation

@Ayush8923
Copy link
Copy Markdown
Collaborator

@Ayush8923 Ayush8923 commented May 11, 2026

Issue: #137

Summary

Update the Configuration surface and adjacent pages, color/style cleanup, component extraction, and dead-code removal in service of the configuration revamp.

Configuration pages

  • Replaced inline color objects with Tailwind utilities + semantic CSS variables across config pages.
  • Extracted ConfigLibrarySkeleton so the loading state is reusable across pages.

ConfigCard

  • Added a MetaPill subcomponent for consistent metadata display.
  • Standardized on the shared Button primitive instead of bespoke buttons.

Prompt editor

  • Wired up handleRenameConfig so configurations can be renamed inline via the API.
  • Dropped unused sidebar/pane toggle state.
  • Swapped the local loader for the shared Loader from the components barrel.

Keystore

  • Split keystore/page.tsx into KeysCard and AddKeyModal to keep files under the 500-LOC cap.
  • Replaced alert() notifications with useToast feedback.
  • Added copy-to-clipboard state tracking with visual feedback.

Evaluations

  • Consolidated Loader imports onto the centralized barrel export (no behavior change).

Onboarding

  • Tightened the skeleton spacing/shadow and responsive constraints on list items.

Cleanup

  • Removed the legacy ConfigDrawer.tsx (~1,240 LOC); its functionality now lives in the dedicated modal + sidebar components.

Summary by CodeRabbit

Release Notes

New Features

  • Added configuration rename functionality to the prompt editor
  • Introduced new modal interface for saving and updating configurations
  • Enhanced API keystore with improved modal and card-based layout

Refactor

  • Redesigned configuration library UI with updated styling and skeleton loaders
  • Reorganized shared UI components for improved maintainability
  • Updated prompt editor interface with simplified layout and components
  • Modernized design tokens across settings and keystore pages

Review Change Stack

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 11, 2026

Important

Review skipped

Auto reviews are limited based on label configuration.

🏷️ Required labels (at least one) (1)
  • ready-for-review

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fb0b51d7-6c32-429e-9d54-bb1988ef0005

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR performs a comprehensive refactoring of the frontend UI system, transitioning from inline colors-based styling to Tailwind utility classes and reusable shared components. It removes deprecated prompt-editor components, adds new reusable UI components, consolidates imports through barrel exports, and updates dozens of existing components to use a consistent tokenized design-system approach.

Changes

UI Consolidation & Component Refactor: Colors → Tailwind + Shared Components

Layer / File(s) Summary
Data Shapes & Public Types
app/components/Field.tsx, app/components/MultiSelect.tsx, app/components/configurations/VersionPill.tsx, app/components/keystore/AddKeyModal.tsx, app/components/keystore/KeysCard.tsx, app/components/prompt-editor/SaveConfigModal.tsx
FieldProps adds optional autoFocus; MultiSelect gains MultiSelectOption union type supporting { value, label } objects with dropdown viewport-aware positioning; new VersionPillProps, AddKeyModalProps, KeysCardProps, SaveConfigModalProps interfaces defined for new components.
New Shared UI Components
app/components/ConfigLibrarySkeleton.tsx, app/components/configurations/SelectedConfigPreview.tsx, app/components/keystore/AddKeyModal.tsx, app/components/keystore/KeysCard.tsx, app/components/prompt-editor/SaveConfigModal.tsx, app/components/configurations/VersionPill.tsx
Six new reusable components: VersionPill renders version badges with tone/size variants; ConfigLibrarySkeleton generates responsive grid of animated placeholder cards; AddKeyModal and KeysCard manage API key UI; SelectedConfigPreview displays config details with collapsible prompt overflow; SaveConfigModal confirms config saves.
Removed Prompt-Editor & Config Components
app/components/ConfigDrawer.tsx, app/components/SimplifiedConfigEditor.tsx, app/components/prompt-editor/ABTestTab.tsx, app/components/prompt-editor/BranchModal.tsx, app/components/prompt-editor/CommitNode.tsx, app/components/prompt-editor/ConfigDrawer.tsx, app/components/prompt-editor/CurrentConfigTab.tsx, app/components/prompt-editor/EditorView.tsx, app/components/prompt-editor/HistoryTab.tsx, app/components/prompt-editor/MergeModal.tsx, app/components/prompt-editor/UnifiedCommitBar.tsx
Deleted ten components (~3.1k LOC): ConfigDrawer tabbed UI, SimplifiedConfigEditor config save flow, branch/merge/AB-test modals, commit node visualization, unified commit bar, and history tab; functionality refactored into ConfigEditorPane, HistorySidebar, and new SaveConfigModal.
Configuration & Keystore Pages
app/(main)/configurations/page.tsx, app/(main)/configurations/prompt-editor/page.tsx, app/(main)/keystore/page.tsx
Configurations library page: swapped LoaderBox/colors for ConfigLibrarySkeleton, updated auth guards in evaluation-counts effect. Prompt editor page: added handleRenameConfig async PATCH endpoint for in-place config renaming with validation and toast feedback; wired ConfigEditorPane with rename props; removed showConfigPane toggle. Keystore page: replaced 490 LOC of embedded form UI with KeysCard/AddKeyModal components; added toast-based validation; clipboard copy now tracks copiedKeyId with auto-timeout feedback.
Refactored Shared Components
app/components/ConfigCard.tsx, app/components/ConfigSelector.tsx, app/components/PageHeader.tsx, app/components/prompt-editor/ConfigEditorPane.tsx, app/components/prompt-editor/PromptEditorPane.tsx, app/components/prompt-editor/DiffView.tsx, app/components/prompt-editor/HistorySidebar.tsx, app/components/prompt-editor/Header.tsx, app/components/prompt-editor/GuardrailsSection.tsx, app/components/auth/LoginModal.tsx
ConfigCard: added KB-ID copy state/handlers, refactored evaluation nav via URLSearchParams, replaced inline buttons with shared Button component, added MetaPill helper. ConfigSelector: delegated prompt preview to SelectedConfigPreview, added dropdown expansion/version-loading state, tokenized styling. PageHeader: updated styling to include bg-bg-primary token. ConfigEditorPane: added rename/save-modal UI, switched to VersionPill, used Field/Button for tool editing. PromptEditorPane: removed colors, added char/line counter, Tailwind textarea styling. DiffView: refactored compare UI to use Select/Button/VersionPill, added ReadOnlyField helper. HistorySidebar: replaced inline styles with Tailwind, VersionPill, Loader, Button; removed collapsed toggle. Header: imports from barrel, uses VersionPill, updated status tokens. GuardrailsSection: memoized MultiSelect with validator ID mapping. LoginModal: improved Google auth error mapping for "no account found" scenario.
Settings & Onboarding UI
app/components/settings/credentials/CredentialForm.tsx, app/components/settings/credentials/CredentialFormPanel.tsx, app/components/settings/onboarding/OrganizationList.tsx, app/components/settings/onboarding/ProjectList.tsx, app/components/settings/onboarding/UserList.tsx, app/(main)/settings/onboarding/page.tsx
Consolidated imports to use barrel exports (@/app/components). Replaced inline loading spinners with Loader component. Updated list item styling: removed bordered white containers, added theme tokens (bg-bg-primary, shadow, hover effects). Updated badge colors to status tokens (status-success-*, status-error-*). Changed icon buttons to use aria-label and styled button elements. Skeleton components updated to theme-based background/shadow.
Document & Knowledge-Base Styling
app/components/document/DocumentPreview.tsx, app/components/document/DocumentPreviewSkeleton.tsx, app/components/knowledge-base/CollectionsList.tsx, app/components/text-to-speech/DatasetDescription.tsx
DocumentPreview and DocumentPreviewSkeleton: replaced bordered white containers with bg-bg-primary and shadow tokens. CollectionsList: removed background on icon span. DatasetDescription: changed break-words to wrap-break-word.
Component Barrel & Imports
app/components/index.ts, app/(main)/evaluations/[id]/page.tsx, app/(main)/evaluations/page.tsx
Added VersionPill and Loader re-exports to barrel. Consolidated Loader imports across pages to use barrel instead of @/app/components/Loader direct path.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement, refactor, ui

Suggested reviewers

  • vprashrex
  • AkhileshNegi
  • Prajna1999

🐰 Colors fade to tokens true,
Components share, and pages brew,
ConfigDrawer's dreams now rest,
VersionPill shines its best,
Tailwind reigns—a fresh debut!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title "Configuration: UI/UX Revamp" accurately summarizes the main objective of the PR, which is to comprehensively update the Configuration surface with UI/UX improvements including component extraction, style cleanup, and removal of dead code.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

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

@Ayush8923 Ayush8923 self-assigned this May 11, 2026
@Ayush8923 Ayush8923 requested a review from vprashrex May 11, 2026 08:41
@Ayush8923 Ayush8923 linked an issue May 11, 2026 that may be closed by this pull request
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 9

Caution

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

⚠️ Outside diff range comments (5)
app/components/MultiSelect.tsx (1)

145-155: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use display label for remove button announcement

At Line 154, the aria-label still uses raw value IDs. Since chips now display labelFor(v), screen reader text should match that user-facing label.

Proposed fix
-              aria-label={`Remove ${v}`}
+              aria-label={`Remove ${labelFor(v)}`}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/MultiSelect.tsx` around lines 145 - 155, The remove button's
aria-label currently exposes the raw value ID (aria-label={`Remove ${v}`})
instead of the user-facing chip label; update the aria-label to use the
displayed label by calling labelFor(v) (e.g., aria-label={`Remove
${labelFor(v)}`}) inside the MultiSelect component so screen readers announce
the same text users see, ensuring labelFor is in scope where the remove handler
and element are defined and leaving the remove(v, e) handler unchanged.
app/components/settings/onboarding/ProjectList.tsx (1)

86-136: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Fix inconsistent interactive element pattern.

The wrapper div on line 88 has cursor-pointer and hover:shadow effects, suggesting the entire card is clickable, but the div itself is not interactive. Instead, there are two separate <button> elements inside (lines 90–105 and 129–135) that both call onSelectProject(project).

Issues:

  1. Misleading cursor: cursor-pointer on a non-interactive div creates false affordance.
  2. Redundant targets: Two buttons performing identical actions increase complexity without clear UX benefit.
  3. Inconsistent pattern: Compare to OrganizationList.tsx (line 35), where the entire card is wrapped in a single <button>.
♻️ Recommended fix: wrap the entire card in a button
         <div className="space-y-2">
           {projects.map((project) => (
-            <div
+            <button
               key={project.id}
-              className="flex items-center justify-between gap-3 p-4 rounded-lg bg-bg-primary shadow-[0_2px_6px_rgba(0,0,0,0.06),0_1px_2px_rgba(0,0,0,0.04)] hover:shadow-[0_4px_12px_rgba(0,0,0,0.08),0_1px_2px_rgba(0,0,0,0.04)] transition-shadow cursor-pointer"
+              onClick={() => onSelectProject(project)}
+              className="w-full flex items-center justify-between gap-3 p-4 rounded-lg bg-bg-primary text-left shadow-[0_2px_6px_rgba(0,0,0,0.06),0_1px_2px_rgba(0,0,0,0.04)] hover:shadow-[0_4px_12px_rgba(0,0,0,0.08),0_1px_2px_rgba(0,0,0,0.04)] transition-shadow cursor-pointer"
             >
-              <button
-                onClick={() => onSelectProject(project)}
-                className="flex-1 text-left min-w-0 cursor-pointer"
-              >
+              <div className="flex-1 min-w-0">
                 <p className="text-sm font-medium text-text-primary truncate">
                   {project.name}
                 </p>
                 {project.description && (
                   <p className="text-xs text-text-secondary mt-0.5 truncate">
                     {project.description}
                   </p>
                 )}
                 <p className="text-xs text-text-secondary mt-0.5">
                   Created {formatRelativeTime(project.inserted_at)}
                 </p>
-              </button>
+              </div>
               <div className="flex items-center gap-2 shrink-0">
                 <span
                   className={`inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-full font-medium border ${
                     project.is_active
                       ? "bg-status-success-bg text-status-success-text border-status-success-border"
                       : "bg-bg-secondary text-text-secondary border-border"
                   }`}
                 >
                   {project.is_active ? "Active" : "Inactive"}
                 </span>
                 {currentUser?.is_superuser && (
                   <button
                     onClick={(e) => {
                       e.stopPropagation();
                       setEditingProject(project);
                     }}
                     className="p-1.5 rounded-md text-text-secondary hover:bg-neutral-100 hover:text-text-primary transition-colors cursor-pointer"
                     title="Edit project"
                     aria-label="Edit project"
                   >
                     <EditIcon className="w-4 h-4" />
                   </button>
                 )}
-                <button
-                  onClick={() => onSelectProject(project)}
-                  className="p-1.5 rounded-md text-text-secondary hover:bg-neutral-100 hover:text-text-primary transition-colors cursor-pointer"
-                  aria-label="View project"
-                >
-                  <ChevronRightIcon className="w-4 h-4" />
-                </button>
+                <ChevronRightIcon className="w-4 h-4 text-text-secondary" />
               </div>
-            </div>
+            </button>
           ))}
         </div>

This matches the pattern used in OrganizationList.tsx and provides a single, clear interaction target.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/settings/onboarding/ProjectList.tsx` around lines 86 - 136,
The card div is non-interactive but styled as clickable and contains two buttons
that both call onSelectProject(project); change the wrapper <div> into a single
interactive <button> (preserving the existing className, key, and onClick
behavior) so the whole card is the click target (use onClick={() =>
onSelectProject(project)} and set aria-label/title like "View project"), remove
the duplicate right-side button that also calls onSelectProject, keep the Edit
button (setEditingProject) as a separate <button> and ensure it calls
e.stopPropagation() so it doesn't trigger the card button; update any
cursor/hover classes to remain on the wrapper button to match
OrganizationList.tsx pattern.
app/components/prompt-editor/DiffView.tsx (1)

129-134: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard async compare fetch with try/catch/finally.

Line 130 can leave isLoadingCompare stuck true if onFetchVersionDetail rejects, and the handler can fail without a graceful fallback.

Suggested fix
     if (onFetchVersionDetail) {
       setIsLoadingCompare(true);
-      detail = (await onFetchVersionDetail(config_id, version)) ?? undefined;
-      setIsLoadingCompare(false);
+      try {
+        detail = (await onFetchVersionDetail(config_id, version)) ?? undefined;
+      } catch {
+        detail = undefined;
+      } finally {
+        setIsLoadingCompare(false);
+      }
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/prompt-editor/DiffView.tsx` around lines 129 - 134, Wrap the
async call to onFetchVersionDetail in a try/catch/finally:
setIsLoadingCompare(true) before the try, await detail = await
onFetchVersionDetail(config_id, version) inside try, call onCompareChange(detail
?? null) in the success path, catch errors to call onCompareChange(null) (or
fallback) and optionally log/propagate the error, and always call
setIsLoadingCompare(false) in finally so isLoadingCompare cannot be left true if
onFetchVersionDetail rejects.
app/(main)/configurations/page.tsx (1)

99-129: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the client-side fetch wrapper for this request.

This page is a client component, but the new evaluation-count call goes through apiFetch. That bypasses the shared 401 refresh / auth-expiry flow, so this panel can fail independently once the session token expires.

As per coding guidelines, "Use clientFetch(endpoint, options) on the client-side for API requests, which handles token refresh on 401 errors and dispatches AUTH_EXPIRED_EVENT when refresh fails".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/`(main)/configurations/page.tsx around lines 99 - 129, The client-side
fetch in useEffect/fetchEvaluationCounts currently calls apiFetch which bypasses
client token-refresh logic; switch to clientFetch for the "/api/evaluations"
call (keeping the same response parsing into EvalJob[] / {data: EvalJob[]}),
ensure you pass the apiKey via options/headers as needed, keep the cancelled
guard and setEvaluationCounts logic intact, and add the clientFetch import at
the top of the module so the component uses the client-side wrapper that handles
401 refresh and AUTH_EXPIRED_EVENT.
app/components/prompt-editor/HistorySidebar.tsx (1)

124-133: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset version-loading state in finally blocks

If the async detail fetch throws/rejects, the loading flags never clear, leaving actions disabled until a rerender/reset.

Suggested fix
   const handleAction = useCallback(
     async (item: ConfigVersionItems, action: "load" | "compare") => {
       let detail = savedConfigs.find(
         (c) => c.config_id === configId && c.version === item.version,
       );
       if (!detail && onFetchVersionDetail) {
-        setFetchingVersion(item.version);
-        detail = (await onFetchVersionDetail(item.version)) ?? undefined;
-        setFetchingVersion(null);
+        setFetchingVersion(item.version);
+        try {
+          detail = (await onFetchVersionDetail(item.version)) ?? undefined;
+        } finally {
+          setFetchingVersion(null);
+        }
       }
       if (!detail) return;
       if (action === "load") onLoadVersion(detail);
       else onSelectVersion(detail);
     },
@@
   const handleAction = useCallback(
     async (item: ConfigVersionItems, action: "load" | "compare") => {
@@
       if (!detail && loadSingleVersionForConfig) {
         const key = `${meta.id}:${item.version}`;
         setLoadingVersionKey(key);
-        detail = await loadSingleVersionForConfig(meta.id, item.version);
-        setLoadingVersionKey(null);
+        try {
+          detail = await loadSingleVersionForConfig(meta.id, item.version);
+        } finally {
+          setLoadingVersionKey(null);
+        }
       }
       if (!detail) return;
       if (action === "load") onLoadVersion(detail);
       else onSelectVersion(detail);
     },

Also applies to: 227-238

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/prompt-editor/HistorySidebar.tsx` around lines 124 - 133, The
async handler handleAction currently sets fetching state with
setFetchingVersion(item.version) but only clears it after a successful await, so
if onFetchVersionDetail throws the fetching flag remains set; move the
setFetchingVersion(null) into a finally block around the await to guarantee the
loading flag is cleared regardless of success/failure and do the same fix for
the similar logic at the other handler (lines referenced include the other
version-fetching block). Ensure you still assign detail from the awaited
onFetchVersionDetail result in the try block and preserve existing
onFetchVersionDetail and savedConfigs checks while wrapping the await in
try/catch/finally (or try/finally) so errors don’t leave the UI stuck loading.
🧹 Nitpick comments (3)
app/components/auth/LoginModal.tsx (1)

53-55: ⚡ Quick win

Verify backend error code stability.

The regex pattern /no account found/i depends on exact backend error message wording. If the backend changes this message, the custom user-facing message won't display and users will see the raw error instead.

Consider coordinating with the backend team to use structured error codes (e.g., error.code === 'ACCOUNT_NOT_FOUND') instead of string matching. This would make error handling more robust and less prone to breaking on backend message updates.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/auth/LoginModal.tsx` around lines 53 - 55, The current logic
in LoginModal (the message variable derived from raw) relies on regex matching
the backend error string (/no account found/i), which is brittle; update the
error handling to prefer a structured error code (e.g., check
response.error?.code or error.code for 'ACCOUNT_NOT_FOUND') inside the login
error path in LoginModal.tsx and set the user-facing message accordingly,
falling back to the existing regex or raw text only if no structured code
exists; coordinate with the backend to ensure they emit a stable code like
ACCOUNT_NOT_FOUND so the code path (message, raw) becomes robust.
app/components/knowledge-base/CollectionsList.tsx (1)

66-66: 💤 Low value

Consider restoring icon background for consistency.

The icon bubble no longer has a background (bg-accent-primary/10 was removed), while the empty state at line 39 still renders a filled circle. If visual hierarchy differs intentionally (list items lighter, empty state emphasized), this is fine; otherwise, consider adding back bg-accent-primary/10 to match.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/knowledge-base/CollectionsList.tsx` at line 66, In
CollectionsList.tsx the icon span inside the list rows (the <span
className="shrink-0 inline-flex items-center justify-center w-6 h-6 rounded-full
text-accent-primary"> element) lost its background class; restore consistency by
adding the background class (e.g., bg-accent-primary/10) back to that span so
list item icons match the filled circle used in the empty state (the empty-state
marker around line 39), keeping the same visual hierarchy across both render
paths.
app/components/keystore/KeysCard.tsx (1)

81-82: ⚡ Quick win

Format the added date with the shared date utilities.

Using toLocaleDateString() here makes this card's output depend on the runtime locale/timezone instead of the app's standard date path. Please switch this to the repo's date-fns/date-fns-tz formatting helper.

As per coding guidelines, "Use date-fns 4.1.0 and date-fns-tz 3.2.0 for date/time handling across the application".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/keystore/KeysCard.tsx` around lines 81 - 82, Replace the
runtime-dependent toLocaleDateString() usage in KeysCard (the expression new
Date(apiKey.createdAt).toLocaleDateString()) with the repo's shared
date-fns/date-fns-tz formatting helper; import the app's standard formatter
(e.g., formatDate or the project's date utility) at the top of KeysCard.tsx and
call that helper with apiKey.createdAt to produce the displayed "Added" date so
the card follows the app-wide date/time formatting conventions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/`(main)/configurations/prompt-editor/page.tsx:
- Around line 403-408: In handleRenameConfig replace the client-side call to
apiFetch with clientFetch so the request benefits from built-in
401/token-refresh handling: change the call that currently invokes
apiFetch(`/api/configs/${configId}`, apiKey, { method: "PATCH", body:
JSON.stringify({ name: trimmed }) }) to use
clientFetch(`/api/configs/${configId}`, { method: "PATCH", body:
JSON.stringify({ name: trimmed }), headers: { Authorization: `Bearer ${apiKey}`
} } ) (or the app's standard way of passing the apiKey) and ensure clientFetch
is imported; keep the same response typing ({ success: boolean; error?: string
}) and existing error/response handling in handleRenameConfig.

In `@app/components/ConfigCard.tsx`:
- Around line 49-56: The handleCopyKbId callback starts a timeout to clear
setCopiedKbId but doesn't cancel any previous timer, so rapid successive copies
can clear a newer success state; add a ref (e.g., copyTimeoutRef) to store the
timeout id, call clearTimeout(copyTimeoutRef.current) before scheduling a new
setTimeout, assign the new id to copyTimeoutRef.current, and clear it when the
timeout runs (or on unmount) to ensure copied state isn't cleared prematurely in
the handleCopyKbId useCallback and associated cleanup.

In `@app/components/ConfigLibrarySkeleton.tsx`:
- Around line 7-15: Clamp and sanitize the incoming columnCount and rows inside
ConfigLibrarySkeleton before computing total and gridTemplateColumns: coerce to
integers (e.g. Math.floor or Math.round), default NaN to 1, and ensure a minimum
of 1 (e.g. const safeCols = Math.max(1, Math.floor(columnCount || 1)); const
safeRows = Math.max(1, Math.floor(rows || 1));), then use safeCols/safeRows for
total and the gridTemplateColumns string so invalid/zero/negative/non-integer
inputs cannot produce broken CSS.

In `@app/components/configurations/SelectedConfigPreview.tsx`:
- Around line 45-56: The PreviewField showing "Max Results" currently renders
String(config.tools[0].max_num_results) which will display "undefined" if
max_num_results is missing; update SelectedConfigPreview to guard this value by
either conditionally rendering the PreviewField only when
config.tools[0].max_num_results != null/undefined or formatting it with a real
fallback (e.g., String(config.tools[0].max_num_results ?? "None" or a default
number)); ensure you reference the same symbol (config.tools[0].max_num_results
and the PreviewField with label "Max Results") when applying the fix.
- Around line 18-23: The effect using useLayoutEffect that sets
setPromptExpanded(false) and computes setIsPromptOverflowing based only on
[config.config_id, config.version] can miss layout changes (window resize or
sidebar collapse); update it to re-measure whenever the prompt element or layout
changes by adding a ResizeObserver on promptRef.current (and a window 'resize'
listener as a fallback) inside the useLayoutEffect so you recalculate
el.scrollHeight > el.clientHeight on observable size changes, clean up the
observer/listener on unmount, and keep the existing behavior of resetting
setPromptExpanded(false) when the config changes.

In `@app/components/prompt-editor/ConfigEditorPane.tsx`:
- Around line 123-137: handleApplyRename can leave the UI stuck loading if
onRenameConfig rejects; change it so setIsApplyingRename(true) is called before
awaiting onRenameConfig and the await is wrapped in a try/finally that always
calls setIsApplyingRename(false). Inside the try block await
onRenameConfig(boundConfigId, trimmed) and handle the successful-ok path
(setIsRenaming(false); setRenameDraft("")) as now; use the finally to always
clear the loading state so failures don't wedge the pane.

In `@app/components/prompt-editor/DiffView.tsx`:
- Around line 137-147: The sameConfig check currently compares compareWith?.name
to selectedCommit.name which is brittle; update the comparison to use the unique
config identifier (compareWith?.config_id === selectedCommit.config_id) when
computing sameConfig (and ensure compareWith and selectedCommit are non-null),
leaving the existing compareLabelLeft and compareLabelRight label logic
unchanged so labels and breadcrumbs are correct even if names are duplicated or
configs renamed.

In `@app/components/prompt-editor/HistorySidebar.tsx`:
- Around line 149-152: The clickable header divs in HistorySidebar.tsx should be
changed to semantic buttons that expose their expanded state; replace the <div
onClick={onToggle} ...> used for the expandable headers with a <button> that
preserves the same className and onClick handler (onToggle) and add
aria-expanded={isExpanded} (or the relevant expanded state variable) so
assistive tech can read the state; make the same change for the second
occurrence noted (lines ~258-261) so both headers are keyboard-accessible and
announce expansion.

In `@app/components/prompt-editor/SaveConfigModal.tsx`:
- Around line 54-65: The label and input in the SaveConfigModal component are
not associated for accessibility; add a unique id (e.g., "commitMessageInput")
to the input that binds to the label via htmlFor on the label so screen readers
and keyboard navigation can correctly focus the commitMessage input; update the
label element and the input element (which uses value commitMessage, onChange
onCommitMessageChange, placeholder isUpdate and disabled isSaving) to include
the matching htmlFor/id pair.

---

Outside diff comments:
In `@app/`(main)/configurations/page.tsx:
- Around line 99-129: The client-side fetch in useEffect/fetchEvaluationCounts
currently calls apiFetch which bypasses client token-refresh logic; switch to
clientFetch for the "/api/evaluations" call (keeping the same response parsing
into EvalJob[] / {data: EvalJob[]}), ensure you pass the apiKey via
options/headers as needed, keep the cancelled guard and setEvaluationCounts
logic intact, and add the clientFetch import at the top of the module so the
component uses the client-side wrapper that handles 401 refresh and
AUTH_EXPIRED_EVENT.

In `@app/components/MultiSelect.tsx`:
- Around line 145-155: The remove button's aria-label currently exposes the raw
value ID (aria-label={`Remove ${v}`}) instead of the user-facing chip label;
update the aria-label to use the displayed label by calling labelFor(v) (e.g.,
aria-label={`Remove ${labelFor(v)}`}) inside the MultiSelect component so screen
readers announce the same text users see, ensuring labelFor is in scope where
the remove handler and element are defined and leaving the remove(v, e) handler
unchanged.

In `@app/components/prompt-editor/DiffView.tsx`:
- Around line 129-134: Wrap the async call to onFetchVersionDetail in a
try/catch/finally: setIsLoadingCompare(true) before the try, await detail =
await onFetchVersionDetail(config_id, version) inside try, call
onCompareChange(detail ?? null) in the success path, catch errors to call
onCompareChange(null) (or fallback) and optionally log/propagate the error, and
always call setIsLoadingCompare(false) in finally so isLoadingCompare cannot be
left true if onFetchVersionDetail rejects.

In `@app/components/prompt-editor/HistorySidebar.tsx`:
- Around line 124-133: The async handler handleAction currently sets fetching
state with setFetchingVersion(item.version) but only clears it after a
successful await, so if onFetchVersionDetail throws the fetching flag remains
set; move the setFetchingVersion(null) into a finally block around the await to
guarantee the loading flag is cleared regardless of success/failure and do the
same fix for the similar logic at the other handler (lines referenced include
the other version-fetching block). Ensure you still assign detail from the
awaited onFetchVersionDetail result in the try block and preserve existing
onFetchVersionDetail and savedConfigs checks while wrapping the await in
try/catch/finally (or try/finally) so errors don’t leave the UI stuck loading.

In `@app/components/settings/onboarding/ProjectList.tsx`:
- Around line 86-136: The card div is non-interactive but styled as clickable
and contains two buttons that both call onSelectProject(project); change the
wrapper <div> into a single interactive <button> (preserving the existing
className, key, and onClick behavior) so the whole card is the click target (use
onClick={() => onSelectProject(project)} and set aria-label/title like "View
project"), remove the duplicate right-side button that also calls
onSelectProject, keep the Edit button (setEditingProject) as a separate <button>
and ensure it calls e.stopPropagation() so it doesn't trigger the card button;
update any cursor/hover classes to remain on the wrapper button to match
OrganizationList.tsx pattern.

---

Nitpick comments:
In `@app/components/auth/LoginModal.tsx`:
- Around line 53-55: The current logic in LoginModal (the message variable
derived from raw) relies on regex matching the backend error string (/no account
found/i), which is brittle; update the error handling to prefer a structured
error code (e.g., check response.error?.code or error.code for
'ACCOUNT_NOT_FOUND') inside the login error path in LoginModal.tsx and set the
user-facing message accordingly, falling back to the existing regex or raw text
only if no structured code exists; coordinate with the backend to ensure they
emit a stable code like ACCOUNT_NOT_FOUND so the code path (message, raw)
becomes robust.

In `@app/components/keystore/KeysCard.tsx`:
- Around line 81-82: Replace the runtime-dependent toLocaleDateString() usage in
KeysCard (the expression new Date(apiKey.createdAt).toLocaleDateString()) with
the repo's shared date-fns/date-fns-tz formatting helper; import the app's
standard formatter (e.g., formatDate or the project's date utility) at the top
of KeysCard.tsx and call that helper with apiKey.createdAt to produce the
displayed "Added" date so the card follows the app-wide date/time formatting
conventions.

In `@app/components/knowledge-base/CollectionsList.tsx`:
- Line 66: In CollectionsList.tsx the icon span inside the list rows (the <span
className="shrink-0 inline-flex items-center justify-center w-6 h-6 rounded-full
text-accent-primary"> element) lost its background class; restore consistency by
adding the background class (e.g., bg-accent-primary/10) back to that span so
list item icons match the filled circle used in the empty state (the empty-state
marker around line 39), keeping the same visual hierarchy across both render
paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e98aff75-fe6b-4aad-b047-ab17de8734b4

📥 Commits

Reviewing files that changed from the base of the PR and between d948048 and 06548fc.

📒 Files selected for processing (45)
  • app/(main)/configurations/page.tsx
  • app/(main)/configurations/prompt-editor/page.tsx
  • app/(main)/evaluations/[id]/page.tsx
  • app/(main)/evaluations/page.tsx
  • app/(main)/keystore/page.tsx
  • app/(main)/settings/onboarding/page.tsx
  • app/components/ConfigCard.tsx
  • app/components/ConfigDrawer.tsx
  • app/components/ConfigLibrarySkeleton.tsx
  • app/components/ConfigSelector.tsx
  • app/components/Field.tsx
  • app/components/MultiSelect.tsx
  • app/components/PageHeader.tsx
  • app/components/SimplifiedConfigEditor.tsx
  • app/components/auth/LoginModal.tsx
  • app/components/configurations/SelectedConfigPreview.tsx
  • app/components/configurations/VersionPill.tsx
  • app/components/document/DocumentPreview.tsx
  • app/components/document/DocumentPreviewSkeleton.tsx
  • app/components/index.ts
  • app/components/keystore/AddKeyModal.tsx
  • app/components/keystore/KeysCard.tsx
  • app/components/knowledge-base/CollectionsList.tsx
  • app/components/prompt-editor/ABTestTab.tsx
  • app/components/prompt-editor/BranchModal.tsx
  • app/components/prompt-editor/CommitNode.tsx
  • app/components/prompt-editor/ConfigDrawer.tsx
  • app/components/prompt-editor/ConfigEditorPane.tsx
  • app/components/prompt-editor/CurrentConfigTab.tsx
  • app/components/prompt-editor/DiffView.tsx
  • app/components/prompt-editor/EditorView.tsx
  • app/components/prompt-editor/GuardrailsSection.tsx
  • app/components/prompt-editor/Header.tsx
  • app/components/prompt-editor/HistorySidebar.tsx
  • app/components/prompt-editor/HistoryTab.tsx
  • app/components/prompt-editor/MergeModal.tsx
  • app/components/prompt-editor/PromptEditorPane.tsx
  • app/components/prompt-editor/SaveConfigModal.tsx
  • app/components/prompt-editor/UnifiedCommitBar.tsx
  • app/components/settings/credentials/CredentialForm.tsx
  • app/components/settings/credentials/CredentialFormPanel.tsx
  • app/components/settings/onboarding/OrganizationList.tsx
  • app/components/settings/onboarding/ProjectList.tsx
  • app/components/settings/onboarding/UserList.tsx
  • app/components/text-to-speech/DatasetDescription.tsx
💤 Files with no reviewable changes (11)
  • app/components/prompt-editor/HistoryTab.tsx
  • app/components/prompt-editor/UnifiedCommitBar.tsx
  • app/components/prompt-editor/CommitNode.tsx
  • app/components/prompt-editor/BranchModal.tsx
  • app/components/SimplifiedConfigEditor.tsx
  • app/components/prompt-editor/EditorView.tsx
  • app/components/prompt-editor/ABTestTab.tsx
  • app/components/prompt-editor/CurrentConfigTab.tsx
  • app/components/ConfigDrawer.tsx
  • app/components/prompt-editor/ConfigDrawer.tsx
  • app/components/prompt-editor/MergeModal.tsx

Comment on lines +403 to +408
try {
const data = await apiFetch<{ success: boolean; error?: string }>(
`/api/configs/${configId}`,
apiKey,
{ method: "PATCH", body: JSON.stringify({ name: trimmed }) },
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use clientFetch in the client rename path

handleRenameConfig is a client-side API call but currently uses apiFetch; this bypasses the standardized 401 refresh/auth-expiry handling required in this app.

Suggested fix
-import { apiFetch } from "@/app/lib/apiClient";
+import { apiFetch, clientFetch } from "@/app/lib/apiClient";
@@
-    const apiKey = activeKey?.key ?? "";
     if (!isAuthenticated) {
       toast.error("Please log in to rename configurations.");
       return false;
     }
@@
-      const data = await apiFetch<{ success: boolean; error?: string }>(
+      const data = await clientFetch<{ success: boolean; error?: string }>(
         `/api/configs/${configId}`,
-        apiKey,
-        { method: "PATCH", body: JSON.stringify({ name: trimmed }) },
+        { method: "PATCH", body: JSON.stringify({ name: trimmed }) },
       );

As per coding guidelines, "Use clientFetch(endpoint, options) on the client-side for API requests, which handles token refresh on 401 errors and dispatches AUTH_EXPIRED_EVENT when refresh fails".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/`(main)/configurations/prompt-editor/page.tsx around lines 403 - 408, In
handleRenameConfig replace the client-side call to apiFetch with clientFetch so
the request benefits from built-in 401/token-refresh handling: change the call
that currently invokes apiFetch(`/api/configs/${configId}`, apiKey, { method:
"PATCH", body: JSON.stringify({ name: trimmed }) }) to use
clientFetch(`/api/configs/${configId}`, { method: "PATCH", body:
JSON.stringify({ name: trimmed }), headers: { Authorization: `Bearer ${apiKey}`
} } ) (or the app's standard way of passing the apiKey) and ensure clientFetch
is imported; keep the same response typing ({ success: boolean; error?: string
}) and existing error/response handling in handleRenameConfig.

Comment on lines +49 to +56
const handleCopyKbId = useCallback(
async (e: React.MouseEvent, kbId: string) => {
e.stopPropagation();
try {
await navigator.clipboard.writeText(kbId);
setCopiedKbId(kbId);
toast.success("Knowledge Base ID copied to clipboard");
setTimeout(() => setCopiedKbId(null), 1500);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clear the previous KB-copy timer before starting a new one.

Copying two Knowledge Base IDs back-to-back lets the first setTimeout clear the second item's success state early. Store the timeout id in a ref and cancel it before scheduling the next reset.

Suggested fix
-import { useState, useCallback } from "react";
+import { useState, useCallback, useRef, useEffect } from "react";
@@
   const [copiedKbId, setCopiedKbId] = useState<string | null>(null);
+  const copiedKbTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
@@
       try {
         await navigator.clipboard.writeText(kbId);
         setCopiedKbId(kbId);
         toast.success("Knowledge Base ID copied to clipboard");
-        setTimeout(() => setCopiedKbId(null), 1500);
+        if (copiedKbTimeoutRef.current) {
+          clearTimeout(copiedKbTimeoutRef.current);
+        }
+        copiedKbTimeoutRef.current = setTimeout(() => {
+          setCopiedKbId(null);
+        }, 1500);
       } catch {
         toast.error("Failed to copy");
       }
@@
+  useEffect(() => {
+    return () => {
+      if (copiedKbTimeoutRef.current) {
+        clearTimeout(copiedKbTimeoutRef.current);
+      }
+    };
+  }, []);
📝 Committable suggestion

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

Suggested change
const handleCopyKbId = useCallback(
async (e: React.MouseEvent, kbId: string) => {
e.stopPropagation();
try {
await navigator.clipboard.writeText(kbId);
setCopiedKbId(kbId);
toast.success("Knowledge Base ID copied to clipboard");
setTimeout(() => setCopiedKbId(null), 1500);
import { useState, useCallback, useRef, useEffect } from "react";
const [copiedKbId, setCopiedKbId] = useState<string | null>(null);
const copiedKbTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const handleCopyKbId = useCallback(
async (e: React.MouseEvent, kbId: string) => {
e.stopPropagation();
try {
await navigator.clipboard.writeText(kbId);
setCopiedKbId(kbId);
toast.success("Knowledge Base ID copied to clipboard");
if (copiedKbTimeoutRef.current) {
clearTimeout(copiedKbTimeoutRef.current);
}
copiedKbTimeoutRef.current = setTimeout(() => {
setCopiedKbId(null);
}, 1500);
} catch {
toast.error("Failed to copy");
}
},
[]
);
useEffect(() => {
return () => {
if (copiedKbTimeoutRef.current) {
clearTimeout(copiedKbTimeoutRef.current);
}
};
}, []);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/ConfigCard.tsx` around lines 49 - 56, The handleCopyKbId
callback starts a timeout to clear setCopiedKbId but doesn't cancel any previous
timer, so rapid successive copies can clear a newer success state; add a ref
(e.g., copyTimeoutRef) to store the timeout id, call
clearTimeout(copyTimeoutRef.current) before scheduling a new setTimeout, assign
the new id to copyTimeoutRef.current, and clear it when the timeout runs (or on
unmount) to ensure copied state isn't cleared prematurely in the handleCopyKbId
useCallback and associated cleanup.

Comment on lines +7 to +15
columnCount = 3,
rows = 2,
}: ConfigLibrarySkeletonProps) {
const total = columnCount * rows;
return (
<div
className="grid gap-4 items-start w-full animate-pulse"
style={{ gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))` }}
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clamp columnCount and rows before computing layout.

Line 14 can emit invalid gridTemplateColumns when these props are zero/negative/non-integer, which can break skeleton layout unexpectedly.

Suggested fix
 export default function ConfigLibrarySkeleton({
   columnCount = 3,
   rows = 2,
 }: ConfigLibrarySkeletonProps) {
-  const total = columnCount * rows;
+  const safeColumnCount = Math.max(1, Math.floor(columnCount));
+  const safeRows = Math.max(1, Math.floor(rows));
+  const total = safeColumnCount * safeRows;
   return (
     <div
       className="grid gap-4 items-start w-full animate-pulse"
-      style={{ gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))` }}
+      style={{
+        gridTemplateColumns: `repeat(${safeColumnCount}, minmax(0, 1fr))`,
+      }}
     >
📝 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
columnCount = 3,
rows = 2,
}: ConfigLibrarySkeletonProps) {
const total = columnCount * rows;
return (
<div
className="grid gap-4 items-start w-full animate-pulse"
style={{ gridTemplateColumns: `repeat(${columnCount}, minmax(0, 1fr))` }}
>
export default function ConfigLibrarySkeleton({
columnCount = 3,
rows = 2,
}: ConfigLibrarySkeletonProps) {
const safeColumnCount = Math.max(1, Math.floor(columnCount));
const safeRows = Math.max(1, Math.floor(rows));
const total = safeColumnCount * safeRows;
return (
<div
className="grid gap-4 items-start w-full animate-pulse"
style={{
gridTemplateColumns: `repeat(${safeColumnCount}, minmax(0, 1fr))`,
}}
>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/ConfigLibrarySkeleton.tsx` around lines 7 - 15, Clamp and
sanitize the incoming columnCount and rows inside ConfigLibrarySkeleton before
computing total and gridTemplateColumns: coerce to integers (e.g. Math.floor or
Math.round), default NaN to 1, and ensure a minimum of 1 (e.g. const safeCols =
Math.max(1, Math.floor(columnCount || 1)); const safeRows = Math.max(1,
Math.floor(rows || 1));), then use safeCols/safeRows for total and the
gridTemplateColumns string so invalid/zero/negative/non-integer inputs cannot
produce broken CSS.

Comment on lines +18 to +23
useLayoutEffect(() => {
setPromptExpanded(false);
const el = promptRef.current;
if (!el) return;
setIsPromptOverflowing(el.scrollHeight > el.clientHeight);
}, [config.config_id, config.version]);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Re-measure prompt overflow when the layout changes.

This effect only reruns when config.config_id or config.version changes. Resizing the window or collapsing the sidebar changes line wrapping too, so the expand/collapse affordance can get stuck in the wrong state for the same config.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/configurations/SelectedConfigPreview.tsx` around lines 18 -
23, The effect using useLayoutEffect that sets setPromptExpanded(false) and
computes setIsPromptOverflowing based only on [config.config_id, config.version]
can miss layout changes (window resize or sidebar collapse); update it to
re-measure whenever the prompt element or layout changes by adding a
ResizeObserver on promptRef.current (and a window 'resize' listener as a
fallback) inside the useLayoutEffect so you recalculate el.scrollHeight >
el.clientHeight on observable size changes, clean up the observer/listener on
unmount, and keep the existing behavior of resetting setPromptExpanded(false)
when the config changes.

Comment on lines +45 to +56
{config.tools && config.tools.length > 0 && (
<>
<div className="col-span-2">
<PreviewLabel>Knowledge Base IDs</PreviewLabel>
<div className="text-xs font-mono break-all text-text-primary">
{knowledgeBaseIds || "None"}
</div>
</div>
<PreviewField
label="Max Results"
value={String(config.tools[0].max_num_results)}
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Guard max_num_results before stringifying it.

If the first tool omits max_num_results, this renders "undefined" in the preview. Either skip the field when the value is absent or provide a real fallback.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/configurations/SelectedConfigPreview.tsx` around lines 45 -
56, The PreviewField showing "Max Results" currently renders
String(config.tools[0].max_num_results) which will display "undefined" if
max_num_results is missing; update SelectedConfigPreview to guard this value by
either conditionally rendering the PreviewField only when
config.tools[0].max_num_results != null/undefined or formatting it with a real
fallback (e.g., String(config.tools[0].max_num_results ?? "None" or a default
number)); ensure you reference the same symbol (config.tools[0].max_num_results
and the PreviewField with label "Max Results") when applying the fix.

Comment on lines +123 to +137
const handleApplyRename = async () => {
if (!boundConfigId || !onRenameConfig) return;
const trimmed = renameDraft.trim();
if (!trimmed || trimmed === configName) {
handleCancelRename();
return;
}
setIsApplyingRename(true);
const ok = await onRenameConfig(boundConfigId, trimmed);
setIsApplyingRename(false);
if (ok) {
setIsRenaming(false);
setRenameDraft("");
}
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Always clear the rename loading state on failure.

If onRenameConfig rejects, this exits before setIsApplyingRename(false) runs, and the rename UI stays disabled. Wrap the await in try/finally so transient API failures don't wedge the pane.

Suggested fix
   const handleApplyRename = async () => {
     if (!boundConfigId || !onRenameConfig) return;
     const trimmed = renameDraft.trim();
     if (!trimmed || trimmed === configName) {
       handleCancelRename();
       return;
     }
-    setIsApplyingRename(true);
-    const ok = await onRenameConfig(boundConfigId, trimmed);
-    setIsApplyingRename(false);
+    setIsApplyingRename(true);
+    let ok = false;
+    try {
+      ok = await onRenameConfig(boundConfigId, trimmed);
+    } finally {
+      setIsApplyingRename(false);
+    }
     if (ok) {
       setIsRenaming(false);
       setRenameDraft("");
     }
   };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/prompt-editor/ConfigEditorPane.tsx` around lines 123 - 137,
handleApplyRename can leave the UI stuck loading if onRenameConfig rejects;
change it so setIsApplyingRename(true) is called before awaiting onRenameConfig
and the await is wrapped in a try/finally that always calls
setIsApplyingRename(false). Inside the try block await
onRenameConfig(boundConfigId, trimmed) and handle the successful-ok path
(setIsRenaming(false); setRenameDraft("")) as now; use the finally to always
clear the loading state so failures don't wedge the pane.

Comment on lines +137 to +147
const sameConfig = compareWith?.name === selectedCommit.name;
const compareLabelLeft = compareWith
? sameConfig
? `Load v${compareWith.version}`
: `Load ${compareWith.name} v${compareWith.version}`
: "";
const compareLabelRight = compareWith
? sameConfig
? `Load v${selectedCommit.version}`
: `Load ${selectedCommit.name} v${selectedCommit.version}`
: "";
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use config_id for same-config checks, not name.

Line 137 currently compares names; duplicate names or renamed configs can produce wrong labels and breadcrumbs.

Suggested fix
-  const sameConfig = compareWith?.name === selectedCommit.name;
+  const sameConfig = compareWith?.config_id === selectedCommit.config_id;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/prompt-editor/DiffView.tsx` around lines 137 - 147, The
sameConfig check currently compares compareWith?.name to selectedCommit.name
which is brittle; update the comparison to use the unique config identifier
(compareWith?.config_id === selectedCommit.config_id) when computing sameConfig
(and ensure compareWith and selectedCommit are non-null), leaving the existing
compareLabelLeft and compareLabelRight label logic unchanged so labels and
breadcrumbs are correct even if names are duplicated or configs renamed.

Comment on lines 149 to 152
<div
onClick={onToggle}
className="p-3 cursor-pointer"
style={{
backgroundColor: colors.bg.secondary,
transition: "all 0.15s ease",
}}
onMouseEnter={(e) =>
(e.currentTarget.style.backgroundColor = "#f5f5f5")
}
onMouseLeave={(e) =>
(e.currentTarget.style.backgroundColor = colors.bg.secondary)
}
className="p-3 cursor-pointer bg-bg-primary hover:bg-neutral-50 transition-colors"
>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use semantic toggle controls for expandable headers

These expandable headers are clickable divs, which are not keyboard-accessible and do not expose expanded state to assistive tech. Use button with aria-expanded.

Suggested fix
-  <div
-    onClick={onToggle}
-    className="p-3 cursor-pointer bg-bg-primary hover:bg-neutral-50 transition-colors"
-  >
+  <button
+    type="button"
+    onClick={onToggle}
+    aria-expanded={isExpanded}
+    className="w-full text-left p-3 cursor-pointer bg-bg-primary hover:bg-neutral-50 transition-colors"
+  >
@@
-  </div>
+  </button>

Also applies to: 258-261

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/prompt-editor/HistorySidebar.tsx` around lines 149 - 152, The
clickable header divs in HistorySidebar.tsx should be changed to semantic
buttons that expose their expanded state; replace the <div onClick={onToggle}
...> used for the expandable headers with a <button> that preserves the same
className and onClick handler (onToggle) and add aria-expanded={isExpanded} (or
the relevant expanded state variable) so assistive tech can read the state; make
the same change for the second occurrence noted (lines ~258-261) so both headers
are keyboard-accessible and announce expansion.

Comment on lines +54 to +65
<label className="block text-xs font-semibold mb-2 text-text-primary">
Commit Message{" "}
<span className="font-normal text-text-secondary">(Optional)</span>
</label>
<input
type="text"
value={commitMessage}
onChange={(e) => onCommitMessageChange(e.target.value)}
placeholder={isUpdate ? "Describe your changes…" : "Initial version"}
disabled={isSaving}
className="w-full px-3 py-2 rounded-md border border-border bg-bg-primary text-text-primary text-sm focus:outline-none focus:ring-2 focus:ring-accent-primary/20 focus:border-accent-primary transition-colors"
/>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Associate the commit-message label with its input.

Line 54 and Line 58 are not explicitly linked, which hurts accessibility in form navigation.

Suggested fix
 export default function SaveConfigModal({
@@
 }: SaveConfigModalProps) {
+  const commitMessageInputId = "save-config-commit-message";
   return (
@@
-        <label className="block text-xs font-semibold mb-2 text-text-primary">
+        <label
+          htmlFor={commitMessageInputId}
+          className="block text-xs font-semibold mb-2 text-text-primary"
+        >
           Commit Message{" "}
           <span className="font-normal text-text-secondary">(Optional)</span>
         </label>
         <input
+          id={commitMessageInputId}
           type="text"
📝 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
<label className="block text-xs font-semibold mb-2 text-text-primary">
Commit Message{" "}
<span className="font-normal text-text-secondary">(Optional)</span>
</label>
<input
type="text"
value={commitMessage}
onChange={(e) => onCommitMessageChange(e.target.value)}
placeholder={isUpdate ? "Describe your changes…" : "Initial version"}
disabled={isSaving}
className="w-full px-3 py-2 rounded-md border border-border bg-bg-primary text-text-primary text-sm focus:outline-none focus:ring-2 focus:ring-accent-primary/20 focus:border-accent-primary transition-colors"
/>
export default function SaveConfigModal({
isUpdate,
isSaving,
commitMessage,
onCommitMessageChange,
}: SaveConfigModalProps) {
const commitMessageInputId = "save-config-commit-message";
return (
// ... previous JSX ...
<>
<label
htmlFor={commitMessageInputId}
className="block text-xs font-semibold mb-2 text-text-primary"
>
Commit Message{" "}
<span className="font-normal text-text-secondary">(Optional)</span>
</label>
<input
id={commitMessageInputId}
type="text"
value={commitMessage}
onChange={(e) => onCommitMessageChange(e.target.value)}
placeholder={isUpdate ? "Describe your changes…" : "Initial version"}
disabled={isSaving}
className="w-full px-3 py-2 rounded-md border border-border bg-bg-primary text-text-primary text-sm focus:outline-none focus:ring-2 focus:ring-accent-primary/20 focus:border-accent-primary transition-colors"
/>
</>
// ... remaining JSX ...
);
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/components/prompt-editor/SaveConfigModal.tsx` around lines 54 - 65, The
label and input in the SaveConfigModal component are not associated for
accessibility; add a unique id (e.g., "commitMessageInput") to the input that
binds to the label via htmlFor on the label so screen readers and keyboard
navigation can correctly focus the commitMessage input; update the label element
and the input element (which uses value commitMessage, onChange
onCommitMessageChange, placeholder isUpdate and disabled isSaving) to include
the matching htmlFor/id pair.

@Ayush8923 Ayush8923 merged commit c9461f9 into feat/document-knowledge-base-ui-revamp May 11, 2026
2 checks passed
@Ayush8923 Ayush8923 deleted the feat/configuration-revamp branch May 11, 2026 14:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Config Management: UI Revamp

2 participants