Skip to content

Dev#38

Merged
BuckyMcYolo merged 4 commits intomainfrom
dev
Apr 3, 2026
Merged

Dev#38
BuckyMcYolo merged 4 commits intomainfrom
dev

Conversation

@BuckyMcYolo
Copy link
Copy Markdown
Owner

@BuckyMcYolo BuckyMcYolo commented Apr 3, 2026

Pull Request Summary

Overview

This PR implements mobile-responsive sidebar behavior across the app and adds a full guild create/join dialog. It introduces a MobileSidebar context/provider and hooks, converts desktop side panels into mobile Sheets, updates navigation to close the mobile sidebar after navigation, and adds CreateGuildDialog with create and join flows.

Key Changes

  • Mobile sidebar context & provider

    • New exports: MobileSidebarProvider, useMobileSidebar, and MobileSidebarContextValue (apps/web/src/context/mobile-sidebar-context.tsx).
    • MobileSidebarProvider now wraps the authenticated app shell (apps/web/src/routes/_authenticated.tsx).
  • Mobile layout & navigation

    • Sidebar layout now renders a mobile Sheet-based left sidebar and right panel when useIsMobile() is true; desktop behavior preserved via DesktopSidebarLayout (apps/web/src/components/sidebar/index.tsx).
    • Left mobile sheet shows ChannelPanel or DMPanel based on route; right mobile sheet opens when right-sidebar view is set and clears view on close.
    • Components that navigate (ChannelList, DMList, DMPanel, AlliesPage, ChatHeader) now call setOpen(false) from useMobileSidebar() to close the mobile sidebar after navigation.
  • Right sidebar behavior adapted for mobile

    • Right-sidebar interface usage extended to include clearView(); mobile UIs call clearView() to close right-side Sheets while desktop keeps toggleCollapsed() behavior (used in chat header, guild-members, pinned-messages, etc.).
  • Create/join guild dialog

    • New exported component CreateGuildDialog({ open, onOpenChange }) (apps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsx) with a three-step flow: choose/create/join.
    • Create: slug normalization and slugEdited handling, calls authClient.organization.create, invalidates ["guilds"], fetches first channel (prefers uncategorized then category channels), closes dialog and navigates to guild/channel or guild root on success; maps server errors to user-facing messages.
    • Join: tolerant invite-code extraction from raw code or invite URL and navigation to /invite/{code}; sets invite-specific error when extraction fails.
    • Dialog resets internal state when closed.
  • Small UX updates and fixes

    • AlliesPage and ChatHeader render a mobile menu (Menu icon) that opens the mobile sidebar on mobile.
    • OnboardingDialog invite placeholder updated to https://app.townhall.chat/invite/abc123.
    • Route $guildSlug/$channelId effect now gates setting guild-members view to desktop only (effect depends on isMobile).
    • ChannelList/DMList/DMPanel and DMPanel updated to close mobile sidebar after navigation.

Files of Note

  • apps/web/src/context/mobile-sidebar-context.tsx
  • apps/web/src/components/sidebar/index.tsx
  • apps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsx
  • apps/web/src/routes/_authenticated.tsx
  • Multiple components updated to use useMobileSidebar and useIsMobile (allies, chat header, channel list, dm list, dm panel, right panels, route files).

Code Quality & Observations

  • Uses idiomatic React context/hooks and isolates mobile/desktop behavior via useIsMobile and clearView/toggleCollapsed branching.
  • CreateGuildDialog covers server error handling and query invalidation; invite parsing is tolerant of common URL formats.
  • State reset on dialog close is implemented.

Potential Concerns / Recommendations

  • No visible automated tests for the new mobile interactions, Sheet behaviors, or CreateGuildDialog flows; add unit/integration/E2E tests.
  • Client-side slug validation is minimal; consider additional client-side checks or clearer server error surfacing for better UX.
  • Fetching the first channel after guild creation swallows failures; consider surfacing non-fatal fetch errors for observability.
  • Verify navigation sequencing (dialog close → navigate) for race conditions on slow networks.

Confidence Score: 4/5

  • Overall: well-structured, follows existing patterns, and appears production-ready.
  • Reasoning: Good isolation of mobile/desktop logic and solid dialog flow; score lowered due to missing automated tests for the new flows and minor client-side validation/observability gaps.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 3, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 93b6f015-4397-4989-99eb-270d35537c88

📥 Commits

Reviewing files that changed from the base of the PR and between d62c3d9 and aaf4fbd.

📒 Files selected for processing (1)
  • apps/web/src/components/sidebar/index.tsx

📝 Walkthrough

Walkthrough

Adds mobile sidebar infrastructure and mobile-aware UI: a MobileSidebarProvider/context, mobile detection usage, mobile menu buttons, auto-closing mobile sidebar on navigation, mobile right-panel behavior, a responsive Sidebar layout, and a new CreateGuildDialog component.

Changes

Cohort / File(s) Summary
Mobile Sidebar Context & Provider
apps/web/src/context/mobile-sidebar-context.tsx, apps/web/src/routes/_authenticated.tsx
New MobileSidebarContext and MobileSidebarProvider; authenticated layout wrapped to provide mobile sidebar open/setOpen state.
Sidebar Layout & Mobile Sheets
apps/web/src/components/sidebar/index.tsx
Refactored into desktop/mobile flows; added left/right mobile Sheet components driven by useIsMobile() and useMobileSidebar().
Mobile Menu Buttons / Header
apps/web/src/components/allies/allies-page.tsx, apps/web/src/components/chat/header.tsx
Added hamburger/menu button on mobile that opens mobile sidebar; ChatHeader branches behavior for mobile vs desktop and uses expanded right-sidebar API (view/setView/clearView).
Mobile Auto-Close on Navigation
apps/web/src/components/sidebar/channel-panel/channel-list.tsx, apps/web/src/components/sidebar/dm-panel/dm-list.tsx, apps/web/src/components/sidebar/dm-panel/dm-panel.tsx
Navigation handlers updated to call setOpen(false) (close mobile sidebar) after navigation; converted some concise lambdas to block bodies to add side effects.
Right Panel Mobile Behavior
apps/web/src/components/sidebar/right-panel/guild-members-panel.tsx, apps/web/src/components/sidebar/right-panel/pinned-messages-panel.tsx, apps/web/src/routes/_authenticated/$guildSlug/$channelId.tsx
Right-sidebar controls now call clearView() on mobile and toggleCollapsed() on desktop; route effect that sets view is gated to non-mobile.
Create / Join Guild Dialog
apps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsx, apps/web/src/components/sidebar/guild-bar/guild-bar.tsx
Added exported CreateGuildDialog component (choose/create/join flows, validation, API calls, navigation) and wired it into GuildBar with open state.
Onboarding Text & Channel selection
apps/web/src/components/onboarding/onboarding-dialog.tsx
Refactored getFirstChannelId to explicit checks; updated invite placeholder to https://app.townhall.chat/invite/abc123 or abc123.
Allies & Small UI tweaks
apps/web/src/components/allies/allies-page.tsx
Allies page now uses mobile detection and renders a menu button on mobile to open the mobile sidebar.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI
    participant MobileSidebar as MobileSidebarContext
    participant Router

    User->>UI: Tap mobile menu (hamburger)
    UI->>MobileSidebar: setOpen(true)
    MobileSidebar-->>UI: open = true (sidebar visible)

    User->>UI: Tap channel/DM item
    UI->>Router: navigate(to channel/DM)
    Router-->>UI: route updated
    UI->>MobileSidebar: setOpen(false)
    MobileSidebar-->>UI: open = false (sidebar hidden)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Dev #5: Modifies channel-list and channel-panel components — directly related to mobile auto-close navigation changes.
  • Dev #7: Modifies onboarding-dialog.tsx — related to the onboarding channel selection and invite placeholder edits.
  • feat: added global rate limiting #20: Touches chat/header.tsx — overlaps with header/right-panel mobile-aware behavior changes.

Poem

🐰 I nibble code and tap the bar,
A tiny menu opens far,
Guilds spring up, invites hop through,
Sidebars close when paths are new,
Hooray — a rabbit's mobile debut!

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Title check ❓ Inconclusive The title "Dev" is vague and generic, providing no meaningful information about the changeset. It does not convey what the pull request accomplishes. Replace the title with a descriptive summary of the main change, such as "Add mobile-responsive sidebar and member panel UI" or "Implement mobile sidebar layout and guild creation dialog".
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

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

❤️ Share

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

Copy link
Copy Markdown

@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: 6

Caution

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

⚠️ Outside diff range comments (1)
apps/web/src/routes/_authenticated/$guildSlug/$channelId.tsx (1)

201-210: ⚠️ Potential issue | 🟡 Minor

Inconsistency in mobile handling for togglePinnedMessages.

The useEffect at line 79 checks isMobile === false before calling setView, meaning the sidebar view is not set on mobile. However, togglePinnedMessages (line 201) calls setView unconditionally without checking isMobile. This creates an inconsistency: if the intent is to prevent right sidebar views from opening automatically on mobile, togglePinnedMessages should also check isMobile to match this pattern. If the current behavior is intentional (allowing pinned messages to open via this callback on mobile), this should be clarified.

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

In `@apps/web/src/routes/_authenticated/`$guildSlug/$channelId.tsx around lines
201 - 210, The togglePinnedMessages callback sets the sidebar view
unconditionally while the earlier useEffect prevents setting right-sidebar views
when isMobile is true; update togglePinnedMessages to respect the same mobile
guard by checking isMobile before calling setView (and only toggleCollapsed when
appropriate), so replace the unconditional setView calls in togglePinnedMessages
with a check like if (!isMobile) then setView({ type: "pinned-messages",
guildSlug, channelId }) (and mirror the else branch behavior) to ensure
consistent mobile handling with the existing useEffect that prevents opening
right-sidebar views on mobile; reference the togglePinnedMessages function and
the setView/isCollapsed/toggleCollapsed variables.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/components/chat/header.tsx`:
- Around line 41-48: The Menu and Members icon-only buttons rendered in the Chat
header (conditional on isMobile and calling openMobileSidebar/openMobileMembers
or similar handlers) lack accessible names; add aria-label attributes (e.g.,
aria-label="Open menu" for the Menu button and aria-label="Members" for the
Members button) to both buttons and, if the Members button is a toggle, also
expose its toggle state with aria-pressed={isMembersOpen} (or the appropriate
state variable) so screen readers receive a meaningful name and state.

In `@apps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsx`:
- Around line 107-119: The success paths only call onOpenChange(false) which
closes the dialog but not the mobile GuildBar sheet; update both success
branches (the blocks containing onOpenChange(false) followed by navigate(...) at
the shown diff and the similar block around lines 147-151) to also close the
mobile left sheet before navigation by invoking the function/prop that controls
the GuildBar mobile sheet (e.g., call closeGuildBarSheet(),
setGuildBarOpen(false), or the existing mobile-sheet close handler used
elsewhere in this component/context) so the sheet is dismissed, then perform
navigate(...) as before.
- Around line 64-75: getFirstChannelId currently only checks
channels.categories[0], so if the first category is empty it misses channels in
later categories; update getFirstChannelId to first check
channels.uncategorized[0]?.id, then iterate over channels.categories (e.g., for
loop or find) and return the first non-empty category.channels[0]?.id, finally
return null if none found, making sure to handle missing/empty categories arrays
from the channels response.

In `@apps/web/src/components/sidebar/guild-bar/guild-bar.tsx`:
- Around line 126-134: The add-guild button that calls setCreateDialogOpen and
renders only the Plus icon is missing an accessible name; update the button
element in guild-bar.tsx to provide an accessible label (e.g., add
aria-label="Create guild" or aria-label="Add guild"), or include visually-hidden
text inside the button while keeping the Plus icon, so assistive tech can
announce it; ensure the attribute is placed on the same button that wraps the
Plus icon and that no duplicate interactive elements are introduced.

In `@apps/web/src/components/sidebar/index.tsx`:
- Around line 23-33: LeftSidebarContent renders GuildBar inside the mobile Sheet
but GuildBar’s navigation handlers don’t close the mobile drawer; update the
flow so GuildBar triggers closing the mobile sidebar after route changes—either
pass a close callback from LeftSidebarContent into GuildBar (e.g., pass a prop
like onNavigate that calls useMobileSidebar().setOpen(false) from
LeftSidebarContent) or add useMobileSidebar().setOpen(false) calls directly
inside GuildBar’s navigation/open-dialog handlers; ensure the same fix also
applies for the alternate rendering paths (ChannelPanel/DMPanel) referenced in
this component so taps on Home or guild icons close the sheet.
- Around line 150-170: MobileRightPanel currently opens solely based on !!view
causing stale guild-scoped content to persist when a user navigates off a guild
route; update MobileRightPanel to read the current guild slug (e.g., from
useGuild or the existing hook that exposes guildSlug), change open to !!view &&
!!guildSlug, and add an effect that calls clearView() whenever guildSlug becomes
falsy so the sheet is closed/cleared when leaving a guild route; reference the
existing symbols MobileRightPanel, useRightSidebar (view, clearView) and the
guildSlug value in your changes.

---

Outside diff comments:
In `@apps/web/src/routes/_authenticated/`$guildSlug/$channelId.tsx:
- Around line 201-210: The togglePinnedMessages callback sets the sidebar view
unconditionally while the earlier useEffect prevents setting right-sidebar views
when isMobile is true; update togglePinnedMessages to respect the same mobile
guard by checking isMobile before calling setView (and only toggleCollapsed when
appropriate), so replace the unconditional setView calls in togglePinnedMessages
with a check like if (!isMobile) then setView({ type: "pinned-messages",
guildSlug, channelId }) (and mirror the else branch behavior) to ensure
consistent mobile handling with the existing useEffect that prevents opening
right-sidebar views on mobile; reference the togglePinnedMessages function and
the setView/isCollapsed/toggleCollapsed variables.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4ade616b-31ca-465b-9f92-4365a0ea3c4b

📥 Commits

Reviewing files that changed from the base of the PR and between 8405398 and 68028c7.

📒 Files selected for processing (14)
  • apps/web/src/components/allies/allies-page.tsx
  • apps/web/src/components/chat/header.tsx
  • apps/web/src/components/onboarding/onboarding-dialog.tsx
  • apps/web/src/components/sidebar/channel-panel/channel-list.tsx
  • apps/web/src/components/sidebar/dm-panel/dm-list.tsx
  • apps/web/src/components/sidebar/dm-panel/dm-panel.tsx
  • apps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsx
  • apps/web/src/components/sidebar/guild-bar/guild-bar.tsx
  • apps/web/src/components/sidebar/index.tsx
  • apps/web/src/components/sidebar/right-panel/guild-members-panel.tsx
  • apps/web/src/components/sidebar/right-panel/pinned-messages-panel.tsx
  • apps/web/src/context/mobile-sidebar-context.tsx
  • apps/web/src/routes/_authenticated.tsx
  • apps/web/src/routes/_authenticated/$guildSlug/$channelId.tsx

Comment on lines +41 to +48
{isMobile && (
<button
type="button"
onClick={() => openMobileSidebar(true)}
className="rounded-sm p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
>
<Menu className="size-5" />
</button>
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

Give the new icon-only mobile controls accessible names.

The new Menu and Members buttons render only icons. Tooltips do not provide a reliable accessible name, so screen readers get unlabeled controls for two primary mobile actions. Add aria-labels; if the Members button remains a toggle, expose its state with aria-pressed too.

Suggested fix
         <button
           type="button"
           onClick={() => openMobileSidebar(true)}
+          aria-label="Open navigation menu"
           className="rounded-sm p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
         >
@@
                 <button
                   type="button"
                   onClick={() => {
                     if (view) {
                       clearView()
                     } else {
                       setView({
                         type: "guild-members",
                         guildSlug: guildSlug ?? "",
                         channelId,
                       })
                     }
                   }}
+                  aria-label="Toggle members panel"
                   className="rounded-sm p-1.5 text-muted-foreground hover:bg-accent hover:text-foreground"
                 >

Also applies to: 98-119

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

In `@apps/web/src/components/chat/header.tsx` around lines 41 - 48, The Menu and
Members icon-only buttons rendered in the Chat header (conditional on isMobile
and calling openMobileSidebar/openMobileMembers or similar handlers) lack
accessible names; add aria-label attributes (e.g., aria-label="Open menu" for
the Menu button and aria-label="Members" for the Members button) to both buttons
and, if the Members button is a toggle, also expose its toggle state with
aria-pressed={isMembersOpen} (or the appropriate state variable) so screen
readers receive a meaningful name and state.

Comment on lines +107 to +119
onOpenChange(false)

if (firstChannelId) {
navigate({
to: "/$guildSlug/$channelId",
params: { guildSlug: createdGuildSlug, channelId: firstChannelId },
})
} else {
navigate({
to: "/$guildSlug",
params: { guildSlug: createdGuildSlug },
})
}
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

Close the mobile drawer before redirecting out of this dialog.

This dialog is now reachable from the mobile left sheet via GuildBar, but the success paths only call onOpenChange(false). On mobile that dismisses the dialog, not the sheet, so the destination page stays covered after create/join completes.

Suggested fix
 import { ArrowLeft, Loader2, Plus, Users } from "lucide-react"
 import { useEffect, useState } from "react"
+import { useMobileSidebar } from "@/context/mobile-sidebar-context"
 import { apiClient } from "@/lib/api-client"
@@
   const queryClient = useQueryClient()
   const navigate = useNavigate()
+  const { setOpen: setMobileSidebarOpen } = useMobileSidebar()
@@
       onOpenChange(false)
+      setMobileSidebarOpen(false)
 
       if (firstChannelId) {
@@
     onOpenChange(false)
+    setMobileSidebarOpen(false)
     await navigate({
       to: "/invite/$code",
       params: { code: inviteCode },

Also applies to: 147-151

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

In `@apps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsx` around
lines 107 - 119, The success paths only call onOpenChange(false) which closes
the dialog but not the mobile GuildBar sheet; update both success branches (the
blocks containing onOpenChange(false) followed by navigate(...) at the shown
diff and the similar block around lines 147-151) to also close the mobile left
sheet before navigation by invoking the function/prop that controls the GuildBar
mobile sheet (e.g., call closeGuildBarSheet(), setGuildBarOpen(false), or the
existing mobile-sheet close handler used elsewhere in this component/context) so
the sheet is dismissed, then perform navigate(...) as before.

Comment on lines +126 to +134
<button
type="button"
onClick={() => setCreateDialogOpen(true)}
className="group relative flex items-center justify-center px-3 py-1"
>
<div className="flex size-12 items-center justify-center rounded-[24px] bg-muted text-emerald-500 transition-all hover:rounded-2xl hover:bg-emerald-500 hover:text-white">
<Plus className="size-6" />
</div>
</div>
</button>
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

Add an accessible name to the new icon-only add-guild button.

This control only renders a Plus icon, so assistive tech will announce it as an unlabeled button. Give it an aria-label at minimum.

Suggested fix
       <button
         type="button"
         onClick={() => setCreateDialogOpen(true)}
+        aria-label="Add guild"
+        aria-haspopup="dialog"
         className="group relative flex items-center justify-center px-3 py-1"
       >
📝 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
<button
type="button"
onClick={() => setCreateDialogOpen(true)}
className="group relative flex items-center justify-center px-3 py-1"
>
<div className="flex size-12 items-center justify-center rounded-[24px] bg-muted text-emerald-500 transition-all hover:rounded-2xl hover:bg-emerald-500 hover:text-white">
<Plus className="size-6" />
</div>
</div>
</button>
<button
type="button"
onClick={() => setCreateDialogOpen(true)}
aria-label="Add guild"
aria-haspopup="dialog"
className="group relative flex items-center justify-center px-3 py-1"
>
<div className="flex size-12 items-center justify-center rounded-[24px] bg-muted text-emerald-500 transition-all hover:rounded-2xl hover:bg-emerald-500 hover:text-white">
<Plus className="size-6" />
</div>
</button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/sidebar/guild-bar/guild-bar.tsx` around lines 126 -
134, The add-guild button that calls setCreateDialogOpen and renders only the
Plus icon is missing an accessible name; update the button element in
guild-bar.tsx to provide an accessible label (e.g., add aria-label="Create
guild" or aria-label="Add guild"), or include visually-hidden text inside the
button while keeping the Plus icon, so assistive tech can announce it; ensure
the attribute is placed on the same button that wraps the Plus icon and that no
duplicate interactive elements are introduced.

Comment thread apps/web/src/components/sidebar/index.tsx
Comment thread apps/web/src/components/sidebar/index.tsx
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: 3

♻️ Duplicate comments (1)
apps/web/src/components/sidebar/index.tsx (1)

150-160: ⚠️ Potential issue | 🟠 Major

Clear the mobile right-sidebar view when the guild context disappears.

Line 153 only hides the sheet when guildSlug is falsy; it does not reset view. Because the provider keeps that state until clearView() runs, a stale guild-scoped panel can reopen unexpectedly on the next guild route.

Suggested fix
-import { useCallback, useRef, useState } from "react"
+import { useCallback, useEffect, useRef, useState } from "react"
@@
 function MobileRightPanel() {
   const { view, clearView } = useRightSidebar()
   const { guildSlug } = useParams({ strict: false })
   const open = !!view && !!guildSlug
+
+  useEffect(() => {
+    if (!guildSlug && view) {
+      clearView()
+    }
+  }, [guildSlug, view, clearView])
 
   return (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/web/src/components/sidebar/index.tsx` around lines 150 - 160, The
MobileRightPanel currently only hides the Sheet when guildSlug is falsy but
doesn’t reset the sidebar state; update MobileRightPanel so the provider state
is cleared whenever the guild context disappears by invoking clearView() when
guildSlug becomes falsy (e.g., add a useEffect watching guildSlug: if
(!guildSlug) clearView()). Reference the MobileRightPanel component,
useRightSidebar (clearView), and useParams (guildSlug) to locate where to add
the effect so stale guild-scoped panels cannot reopen.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@apps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsx`:
- Around line 64-75: getFirstChannelId and the invite-code parsing logic are
duplicated between create-guild-dialog (getFirstChannelId) and
onboarding-dialog; extract both into a shared utility (e.g., export functions
like getFirstChannelId and parseInviteCode from a new onboarding/guild helpers
module), replace the local implementations in create-guild-dialog and
onboarding-dialog with imports from that module, ensure the shared
getFirstChannelId keeps the same API (takes guildSlug and returns channel
id|null) and the parseInviteCode accepts the same input currently used in both
places and returns the same output shape, and update any references to use the
new exported names.
- Around line 77-125: The create flow can complete after the user dismisses the
dialog; fix by blocking closure while the request is in-flight and
aborting/post-checking the async flow: in CreateGuildDialog update handleCreate
to create an AbortController and (if authClient supports it) pass its signal to
authClient.organization.create, and after each await check
controller.signal.aborted (or a local `cancelled` flag) before calling setError,
queryClient.invalidateQueries, getFirstChannelId, onOpenChange, or navigate;
also prevent the UI from closing while loading by guarding onOpenChange so it
ignores attempts to close when `loading` is true (i.e., only allow
onOpenChange(false) when !loading) and disable modal/backdrop/escape-close props
while loading so the Back/dismiss buttons are blocked during the request
(references: handleCreate, authClient.organization.create, getFirstChannelId,
onOpenChange, navigate, setLoading).

In `@apps/web/src/components/sidebar/index.tsx`:
- Around line 174-187: SidebarLayout currently mounts DesktopSidebarLayout
because useIsMobile() defaults to false before it settles, causing a
desktop-to-mobile flash; instead, ensure the component waits for the mobile
detection to settle before choosing a branch: update the hook or guard in
SidebarLayout so useIsMobile() can indicate an "undetermined" state (e.g.,
undefined or isResolved flag) and if undetermined return a neutral placeholder
(or null) rather than rendering DesktopSidebarLayout; reference SidebarLayout,
useIsMobile, MobileSidebar, MobileRightPanel, DesktopSidebarLayout and children
when making the guard so mobile users are routed to the mobile tree on first
render without remounting children.

---

Duplicate comments:
In `@apps/web/src/components/sidebar/index.tsx`:
- Around line 150-160: The MobileRightPanel currently only hides the Sheet when
guildSlug is falsy but doesn’t reset the sidebar state; update MobileRightPanel
so the provider state is cleared whenever the guild context disappears by
invoking clearView() when guildSlug becomes falsy (e.g., add a useEffect
watching guildSlug: if (!guildSlug) clearView()). Reference the MobileRightPanel
component, useRightSidebar (clearView), and useParams (guildSlug) to locate
where to add the effect so stale guild-scoped panels cannot reopen.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 70b9b04f-d5b7-4f4b-ba18-39dad913093d

📥 Commits

Reviewing files that changed from the base of the PR and between 68028c7 and d62c3d9.

📒 Files selected for processing (3)
  • apps/web/src/components/onboarding/onboarding-dialog.tsx
  • apps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsx
  • apps/web/src/components/sidebar/index.tsx

Comment on lines +64 to +75
const getFirstChannelId = async (guildSlug: string) => {
const channelsRes = await apiClient.v1.guilds[":guildSlug"].channels.$get({
param: { guildSlug },
})
if (!channelsRes.ok) return null
const channels = await channelsRes.json()
if (channels.uncategorized[0]?.id) return channels.uncategorized[0].id
for (const cat of channels.categories) {
if (cat.channels[0]?.id) return cat.channels[0].id
}
return null
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Extract the shared onboarding helpers.

getFirstChannelId and invite-code parsing now duplicate logic in apps/web/src/components/onboarding/onboarding-dialog.tsx. These rules already had to be re-aligned in this PR, so please move them into a shared utility before the two flows drift again.

Also applies to: 133-139

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

In `@apps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsx` around
lines 64 - 75, getFirstChannelId and the invite-code parsing logic are
duplicated between create-guild-dialog (getFirstChannelId) and
onboarding-dialog; extract both into a shared utility (e.g., export functions
like getFirstChannelId and parseInviteCode from a new onboarding/guild helpers
module), replace the local implementations in create-guild-dialog and
onboarding-dialog with imports from that module, ensure the shared
getFirstChannelId keeps the same API (takes guildSlug and returns channel
id|null) and the parseInviteCode accepts the same input currently used in both
places and returns the same output shape, and update any references to use the
new exported names.

Comment on lines +77 to +125
const handleCreate = async (e: React.FormEvent) => {
e.preventDefault()
const normalizedSlug = sluggify(slug)
if (!name.trim() || !normalizedSlug) return
setError(null)
setLoading(true)

try {
const res = await authClient.organization.create({
name: name.trim(),
slug: normalizedSlug,
})

if (res.error) {
const message = (res.error.message ?? "Failed to create guild").replace(
/organization/gi,
"Guild"
)
setError(message)
return
}

const createdGuildSlug = res.data?.slug ?? normalizedSlug
await queryClient.invalidateQueries({ queryKey: ["guilds"] })

let firstChannelId: string | null = null
try {
firstChannelId = await getFirstChannelId(createdGuildSlug)
} catch {}

onOpenChange(false)

if (firstChannelId) {
navigate({
to: "/$guildSlug/$channelId",
params: { guildSlug: createdGuildSlug, channelId: firstChannelId },
})
} else {
navigate({
to: "/$guildSlug",
params: { guildSlug: createdGuildSlug },
})
}
} catch {
setError("Something went wrong. Please try again.")
} finally {
setLoading(false)
}
}
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

Block exit paths while the create request is in flight.

Right now the create call keeps running even if the user clicks Back or dismisses the modal. That makes cancel misleading, because the guild can still be created and the app will redirect there once the promise resolves.

Suggested fix
 export function CreateGuildDialog({
   open,
   onOpenChange,
 }: {
   open: boolean
   onOpenChange: (open: boolean) => void
 }) {
+  const handleOpenChange = (nextOpen: boolean) => {
+    if (!loading) onOpenChange(nextOpen)
+  }
+
-  return (
-    <Dialog open={open} onOpenChange={onOpenChange}>
-      <DialogContent className="sm:max-w-md">
+  return (
+    <Dialog open={open} onOpenChange={handleOpenChange}>
+      <DialogContent
+        showCloseButton={!loading}
+        onInteractOutside={loading ? (e) => e.preventDefault() : undefined}
+        onEscapeKeyDown={loading ? (e) => e.preventDefault() : undefined}
+        className="sm:max-w-md"
+      >
             <button
               type="button"
+              disabled={loading}
               onClick={() => {
                 setStep("choose")
                 setError(null)
               }}

Also applies to: 155-156, 202-212

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

In `@apps/web/src/components/sidebar/guild-bar/create-guild-dialog.tsx` around
lines 77 - 125, The create flow can complete after the user dismisses the
dialog; fix by blocking closure while the request is in-flight and
aborting/post-checking the async flow: in CreateGuildDialog update handleCreate
to create an AbortController and (if authClient supports it) pass its signal to
authClient.organization.create, and after each await check
controller.signal.aborted (or a local `cancelled` flag) before calling setError,
queryClient.invalidateQueries, getFirstChannelId, onOpenChange, or navigate;
also prevent the UI from closing while loading by guarding onOpenChange so it
ignores attempts to close when `loading` is true (i.e., only allow
onOpenChange(false) when !loading) and disable modal/backdrop/escape-close props
while loading so the Back/dismiss buttons are blocked during the request
(references: handleCreate, authClient.organization.create, getFirstChannelId,
onOpenChange, navigate, setLoading).

Comment on lines +174 to +187
function SidebarLayout({ children }: { children: React.ReactNode }) {
const isMobile = useIsMobile()

if (isMobile) {
return (
<div className="flex h-full w-full flex-col">
<MobileSidebar />
<MobileRightPanel />
{children}
</div>
)
}

return <DesktopSidebarLayout>{children}</DesktopSidebarLayout>
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

Don’t route mobile users through the desktop layout on the first render.

useIsMobile() resolves to false before its effect runs, so phones mount DesktopSidebarLayout here and then swap to the mobile tree on the next pass. That causes a desktop-to-mobile flash, remounts children, and can briefly seed desktop right-sidebar state from the channel-route effect that keys on isMobile === false.

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

In `@apps/web/src/components/sidebar/index.tsx` around lines 174 - 187,
SidebarLayout currently mounts DesktopSidebarLayout because useIsMobile()
defaults to false before it settles, causing a desktop-to-mobile flash; instead,
ensure the component waits for the mobile detection to settle before choosing a
branch: update the hook or guard in SidebarLayout so useIsMobile() can indicate
an "undetermined" state (e.g., undefined or isResolved flag) and if undetermined
return a neutral placeholder (or null) rather than rendering
DesktopSidebarLayout; reference SidebarLayout, useIsMobile, MobileSidebar,
MobileRightPanel, DesktopSidebarLayout and children when making the guard so
mobile users are routed to the mobile tree on first render without remounting
children.

@BuckyMcYolo BuckyMcYolo merged commit ea9a4b5 into main Apr 3, 2026
2 of 3 checks passed
This was referenced Apr 5, 2026
Merged
Merged
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