Skip to content

fix: conversation rename & delete dialog#18

Merged
Krasimir-Hristov merged 2 commits into
mainfrom
fix/conversation-rename-delete-dialog
May 10, 2026
Merged

fix: conversation rename & delete dialog#18
Krasimir-Hristov merged 2 commits into
mainfrom
fix/conversation-rename-delete-dialog

Conversation

@Krasimir-Hristov

@Krasimir-Hristov Krasimir-Hristov commented May 10, 2026

Copy link
Copy Markdown
Owner
  • Fix update_conversation_title using two-query pattern (supabase-py v2 does not support .select() chaining after .update())
  • Add global unhandled exception handler in FastAPI to ensure 500 errors include CORS headers (previously ServerErrorMiddleware bypassed CORS)
  • Catch all exceptions in _get_jwks() not just httpx.HTTPError
  • Add try/except with logging in PATCH conversation route
  • Replace double-click delete confirm with a modal dialog (base-ui Dialog)
  • Add new Dialog UI component using @base-ui/react/dialog
  • Fix absolute .env path resolution in config.py
  • Update COMMANDS.md with correct PowerShell activation command

Summary by CodeRabbit

  • New Features

    • Dialog UI component for improved user interactions
    • Automatic YouTube URL detection for streamlined agent routing
  • Bug Fixes

    • Fixed environment configuration loading from different working directories
    • Improved error handling for unavailable or private videos with clearer messaging
    • Enhanced global exception handling for more reliable error responses
    • Refactored conversation deletion with modal confirmation dialog
  • Documentation

    • Updated backend startup instructions for Windows PowerShell compatibility

Review Change Stack

- Fix update_conversation_title using two-query pattern (supabase-py v2
  does not support .select() chaining after .update())
- Add global unhandled exception handler in FastAPI to ensure 500 errors
  include CORS headers (previously ServerErrorMiddleware bypassed CORS)
- Catch all exceptions in _get_jwks() not just httpx.HTTPError
- Add try/except with logging in PATCH conversation route
- Replace double-click delete confirm with a modal dialog (base-ui Dialog)
- Add new Dialog UI component using @base-ui/react/dialog
- Fix absolute .env path resolution in config.py
- Update COMMANDS.md with correct PowerShell activation command
@coderabbitai

coderabbitai Bot commented May 10, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@Krasimir-Hristov has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 10 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

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

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

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

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

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 094c2063-12f3-47ae-90ad-3ddbf4afcc02

📥 Commits

Reviewing files that changed from the base of the PR and between 1ea8a69 and 9ec147e.

📒 Files selected for processing (5)
  • backend/app/core/security.py
  • backend/app/features/chat/conversation_service.py
  • backend/app/features/chat/router.py
  • frontend/components/ui/dialog.tsx
  • frontend/features/chat/components/ConversationSidebar.tsx

Walkthrough

This PR introduces YouTube video detection and routing, improves backend error handling, refactors the conversation update service, and adds a modal dialog pattern to the frontend. Changes span backend infrastructure (config, security, exceptions), agent orchestration (YouTube routing), chat features (conversation service), and frontend UI (dialog component and sidebar integration).

Changes

Core Infrastructure Updates

Layer / File(s) Summary
Config Path Resolution
backend/app/core/config.py
Resolves .env file path relative to config module location and sets absolute path in Pydantic settings for consistent startup regardless of working directory.
Security Error Handling
backend/app/core/security.py
Broadens JWKS fetch error handler to catch httpx.HTTPError and general exceptions, falling back to cached JWKS or empty dict; adjusts log formatting for combined validation failures.
Global Exception Handler
backend/app/main.py
Registers unhandled exception handler that logs request details and returns standardized JSON 500 response, ensuring CORS headers are applied to error responses.

YouTube Agent and Supervisor Routing

Layer / File(s) Summary
YouTube Agent Error Handling
backend/app/agents/subagents/youtube_agent.py
Imports VideoUnavailable exception and adds explicit error handling during transcript/oEmbed fetch, returning user-facing error when video is unavailable or private.
Supervisor YouTube Fast-Path
backend/app/agents/supervisor.py
Detects YouTube URLs in human message using extract_video_id, generates unique tool-call ID with uuid, and emits direct transfer_to_youtube_agent tool call to bypass LLM streaming.

Chat Conversation Update Flow

Layer / File(s) Summary
Conversation Service Update
backend/app/features/chat/conversation_service.py
Splits update_conversation_title into separate update and select operations with distinct error handling/logging for each database operation.
Router Error Handling
backend/app/features/chat/router.py
Wraps service call in try/except, logs failures with context, returns HTTP 500 on exception, preserves 404 when service returns None.

Frontend Dialog and Sidebar

Layer / File(s) Summary
Dialog UI Component Library
frontend/components/ui/dialog.tsx
Introduces typed React.forwardRef wrappers for dialog backdrop, popup (with portal and centering), title, and description with consistent Tailwind styling.
Sidebar Delete Confirmation
frontend/features/chat/components/ConversationSidebar.tsx
Replaces inline delete confirmation with modal dialog controlled by deleteOpen state; delete button opens dialog, modal provides Cancel and Delete actions with mutation pending state.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • Krasimir-Hristov/AXON#17: Both PRs add and refine YouTube agent integration—PR #17 introduces the YouTube agent and routing infrastructure, while this PR adds error handling for unavailable videos and supervisor fast-path detection.
  • Krasimir-Hristov/AXON#16: Both PRs modify the same conversation update flow—changes to conversation_service.py and router endpoint for handling title updates.
  • Krasimir-Hristov/AXON#11: Both PRs update security JWT/JWKS error handling and supervisor routing behavior in the same files.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: conversation rename & delete dialog' directly addresses the main changes: adding a dialog-based delete confirmation and improving conversation management error handling, which are the primary focuses of this changeset.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.
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 fix/conversation-rename-delete-dialog

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (2)
backend/app/features/chat/conversation_service.py (1)

239-276: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

The "no .select() chaining" comment is inaccurate — chaining is supported.

The Supabase Python reference explicitly documents "Chain .select() after update() to return specific columns from the updated row(s)." This is the same pattern used by .insert() calls elsewhere in this same module. Replacing the second round-trip with a chained .select(...) would halve the DB calls per rename and remove a benign race window where a concurrently deleted row makes a successful update appear as 404.

♻️ Proposed simplification
-    """Update a conversation's title.
-
-    Returns the updated ConversationOut, or None if not found / not owned.
-    supabase-py v2 update() builder does not support .select() chaining, so we
-    execute the update first, then fetch the updated row in a separate query.
-    """
+    """Update a conversation's title.
+
+    Returns the updated ConversationOut, or None if not found / not owned.
+    """
     client = await get_supabase_client()
     try:
         result = (
             await client.table("conversations")
             .update({"title": title})
             .eq("id", conversation_id)
             .eq("user_id", user_id)
+            .select("id, title, model_id, created_at, updated_at")
             .execute()
         )
     except Exception:  # noqa: BLE001  # pylint: disable=broad-exception-caught
         logger.exception(
             "update_conversation_title: update failed conversation_id=%s",
             conversation_id,
         )
         raise
     if not result.data:
         return None
-    try:
-        fetch = (
-            await client.table("conversations")
-            .select("id, title, model_id, created_at, updated_at")
-            .eq("id", conversation_id)
-            .eq("user_id", user_id)
-            .limit(1)
-            .execute()
-        )
-    except Exception:  # noqa: BLE001  # pylint: disable=broad-exception-caught
-        logger.exception(
-            "update_conversation_title: fetch failed conversation_id=%s",
-            conversation_id,
-        )
-        raise
-    if not fetch.data:
-        return None
-    return ConversationOut.model_validate(fetch.data[0])
+    return ConversationOut.model_validate(result.data[0])

Please verify against the exact supabase/postgrest versions pinned in backend/pyproject.toml (or equivalent) — if a regression genuinely blocks .select() chaining there, please link the upstream issue in the comment instead of stating the chaining is unsupported.

🤖 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 `@backend/app/features/chat/conversation_service.py` around lines 239 - 276,
The comment incorrectly claims .select() chaining after .update() is
unsupported; change update_conversation_title to use a single chained call on
client.table("conversations") — i.e., call .update({"title": title}).eq("id",
conversation_id).eq("user_id", user_id).select("id, title, model_id, created_at,
updated_at").limit(1).execute() instead of performing a separate fetch, then
validate the returned row with ConversationOut.model_validate(fetch.data[0]);
also verify the pinned supabase/postgrest versions in pyproject.toml and, if
chaining fails, replace the comment with a link to the upstream issue.
frontend/features/chat/components/ConversationSidebar.tsx (1)

84-89: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Keep dialog open until deletion completes.

Closing the dialog immediately (line 85) before the mutation finishes hides the loading state and any potential errors from the user. On slow networks or if the deletion fails, the user sees no feedback.

🔄 Recommended fix to show loading state and handle errors
 function handleDeleteConfirm() {
-  setDeleteOpen(false);
   deleteConv(conversation.id, {
-    onSuccess: () => onDeleted(conversation.id),
+    onSuccess: () => {
+      setDeleteOpen(false);
+      onDeleted(conversation.id);
+    },
+    onError: (err) => {
+      console.error('Failed to delete conversation:', err);
+      // Optionally: show error message in dialog or toast
+      setDeleteOpen(false);
+    },
   });
 }

Then update the dialog buttons to show loading state:

 <DialogClose className='...'>
   Cancel
 </DialogClose>
 <button
   type='button'
   onClick={handleDeleteConfirm}
+  disabled={deleteLoading}
-  className='rounded-lg bg-[`#ff4444`]/90 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-[`#ff4444`] cursor-pointer'
+  className={[
+    'rounded-lg bg-[`#ff4444`]/90 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-[`#ff4444`] cursor-pointer',
+    deleteLoading ? 'opacity-50 cursor-not-allowed' : '',
+  ].join(' ')}
 >
-  Delete
+  {deleteLoading ? 'Deleting...' : 'Delete'}
 </button>
🤖 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 `@frontend/features/chat/components/ConversationSidebar.tsx` around lines 84 -
89, The dialog is being closed immediately in handleDeleteConfirm which hides
loading and errors; instead remove the immediate setDeleteOpen(false) and call
deleteConv(conversation.id, { onSuccess: () => { setDeleteOpen(false);
onDeleted(conversation.id); }, onError: (err) => { /* keep dialog open and
surface err.message to user */ } });; update the dialog's action buttons to use
the mutation's loading state (e.g., deleteConv.isLoading or a local isDeleting
derived from the mutation) to show a spinner/disable buttons while deleting and
display the mutation error message in the dialog when onError runs; keep
references to handleDeleteConfirm, deleteConv, setDeleteOpen, and onDeleted to
locate and modify the code.
🤖 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 `@backend/app/core/security.py`:
- Line 39: The except clause in backend.app.core.security.py currently uses a
redundant tuple "except (httpx.HTTPError, Exception) as exc"; change this to
either a single broad handler "except Exception as exc" or, preferably, a
narrower handler that only catches the intended httpx errors like "except
httpx.HTTPError as exc" (or add the project's lint suppression if a broad catch
is required). Update the except clause near the existing try/except block in
security.py (the line containing "except (httpx.HTTPError, Exception) as exc")
to use the chosen single exception form and keep the existing logging/handling
of exc unchanged.

In `@backend/app/features/chat/router.py`:
- Around line 119-127: The current except block in
backend/app/features/chat/router.py is catching all Exception and will convert
any raised HTTPException into a 500; change the error handling so HTTPException
is re-raised unchanged and only other exceptions are logged and wrapped into a
500. Concretely, inside the update_conversation handler (the block using
logger.exception and raising HTTPException("Failed to update conversation")),
either add an explicit except HTTPException: raise to propagate client errors,
then have a separate except Exception as exc that logs via logger.exception(...)
and raises the 500, or detect isinstance(exc, HTTPException) and re-raise before
wrapping; ensure you reference the existing logger.exception call and the raised
HTTPException detail message.

In `@frontend/components/ui/dialog.tsx`:
- Around line 11-73: The components DialogBackdrop, DialogPopup, DialogTitle and
DialogDescription still use React.forwardRef and assign displayName; update them
to the React 19 pattern by accepting ref as a normal prop in the component
signature (remove React.forwardRef wrappers), remove the now-unnecessary
displayName assignments, and pass the ref through to the underlying Dialog.*
elements via the ref prop; ensure prop types remain React.ElementRef<typeof
Dialog.Backdrop/Popup/Title/Description> and
React.ComponentPropsWithoutRef<typeof Dialog.Backdrop/Popup/Title/Description>
so type compatibility is preserved.

---

Outside diff comments:
In `@backend/app/features/chat/conversation_service.py`:
- Around line 239-276: The comment incorrectly claims .select() chaining after
.update() is unsupported; change update_conversation_title to use a single
chained call on client.table("conversations") — i.e., call .update({"title":
title}).eq("id", conversation_id).eq("user_id", user_id).select("id, title,
model_id, created_at, updated_at").limit(1).execute() instead of performing a
separate fetch, then validate the returned row with
ConversationOut.model_validate(fetch.data[0]); also verify the pinned
supabase/postgrest versions in pyproject.toml and, if chaining fails, replace
the comment with a link to the upstream issue.

In `@frontend/features/chat/components/ConversationSidebar.tsx`:
- Around line 84-89: The dialog is being closed immediately in
handleDeleteConfirm which hides loading and errors; instead remove the immediate
setDeleteOpen(false) and call deleteConv(conversation.id, { onSuccess: () => {
setDeleteOpen(false); onDeleted(conversation.id); }, onError: (err) => { /* keep
dialog open and surface err.message to user */ } });; update the dialog's action
buttons to use the mutation's loading state (e.g., deleteConv.isLoading or a
local isDeleting derived from the mutation) to show a spinner/disable buttons
while deleting and display the mutation error message in the dialog when onError
runs; keep references to handleDeleteConfirm, deleteConv, setDeleteOpen, and
onDeleted to locate and modify the code.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 8759e42b-c89e-408d-869b-fa3d7e33a897

📥 Commits

Reviewing files that changed from the base of the PR and between 3386bb5 and 1ea8a69.

📒 Files selected for processing (10)
  • COMMANDS.md
  • backend/app/agents/subagents/youtube_agent.py
  • backend/app/agents/supervisor.py
  • backend/app/core/config.py
  • backend/app/core/security.py
  • backend/app/features/chat/conversation_service.py
  • backend/app/features/chat/router.py
  • backend/app/main.py
  • frontend/components/ui/dialog.tsx
  • frontend/features/chat/components/ConversationSidebar.tsx

Comment thread backend/app/core/security.py Outdated
Comment thread backend/app/features/chat/router.py
Comment thread frontend/components/ui/dialog.tsx Outdated
- security.py: simplify redundant except tuple to bare 'except Exception'
- router.py: re-raise HTTPException before generic except so client errors
  are not converted to 500
- dialog.tsx: migrate forwardRef components to React 19 ref-as-prop pattern,
  remove displayName assignments
- conversation_service.py: improve comment, add link to upstream postgrest-py
  issue #394 explaining why two-query approach is required
- ConversationSidebar: move setDeleteOpen(false) into onSuccess, add onError
  to surface delete errors in dialog, disable buttons while deleting
@Krasimir-Hristov Krasimir-Hristov merged commit 4f41ed3 into main May 10, 2026
2 checks passed
@Krasimir-Hristov Krasimir-Hristov deleted the fix/conversation-rename-delete-dialog branch May 10, 2026 13:27
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