Skip to content

Dev#5

Merged
BuckyMcYolo merged 4 commits intomainfrom
dev
Feb 16, 2026
Merged

Dev#5
BuckyMcYolo merged 4 commits intomainfrom
dev

Conversation

@BuckyMcYolo
Copy link
Copy Markdown
Owner

@BuckyMcYolo BuckyMcYolo commented Feb 16, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Channels are now organized into categories within the sidebar, with uncategorized channels displayed at the top.
    • Added guild selection and navigation with visual indicators for the currently active guild.
  • Bug Fixes

    • Fixed authentication credential handling for API requests.

- Update channels API to return channels grouped by category with
  uncategorized channels separate, both sorted by position
- Wire up channel list component to fetch real data via useQuery
- Add credentials: "include" to API client for auth cookies
- Add seed script for populating test channels (apps/api/scripts/)
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Feb 16, 2026

📝 Walkthrough

Walkthrough

This PR replaces mock data with dynamic API-driven functionality across the application. The API adds a channel seeding script and modifies channel handlers to group channels by categories. The frontend refactors sidebar components to fetch and display guilds and channels from the API, introduces new authenticated guild routes, and includes credentials in HTTP requests.

Changes

Cohort / File(s) Summary
Channel Seeding & Organization
apps/api/scripts/seed-channels.ts
New script that seeds predefined channels organized into categories for a guild via Drizzle ORM operations, validating arguments and logging progress.
API Routing & Type Exposure
apps/api/src/app.ts
Refactored route mounting to use chained approach; updated exported AppType from aggregated array to single routes type definition.
Channel Response Structure
apps/api/src/routes/v1/channels/handlers.ts, apps/api/src/routes/v1/channels/schema.ts
Added channel ordering by position; restructured response to group channels into uncategorized and category-nested collections via new categoryWithChannelsSchema.
Web Sidebar Refactoring
apps/web/src/components/sidebar/channel-list.tsx, apps/web/src/components/sidebar/channel-panel/channel-list.tsx, apps/web/src/components/sidebar/channel-panel/guild-header.tsx, apps/web/src/components/sidebar/guild-bar.tsx, apps/web/src/components/sidebar/guild-bar/guild-bar.tsx, apps/web/src/components/sidebar/guild-header.tsx
Replaced static channel/guild lists with API-driven components; removed old hardcoded implementations and introduced new nested component structure with dynamic data fetching via React Query.
Supporting Web Updates
apps/web/src/components/sidebar/channel-panel/user-bar.tsx, apps/web/src/components/sidebar/index.tsx, apps/web/src/lib/api-client.ts, apps/web/src/routes/_authenticated.tsx
Updated import paths, added credentials inclusion in API client, and adjusted module resolution for restructured sidebar components.
Guild Route Structure
apps/web/src/routes/_authenticated/$guildSlug.tsx, apps/web/src/routes/_authenticated/$guildSlug/index.tsx
Added new authenticated routes for guild-specific pages with layout component that fetches and manages guild state via React Query.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Hark! The channels now bloom in ordered rows,
From mock data shed to API flows,
Guilds and categories dance with care,
Dynamic data floats through the air! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 3

❌ Failed checks (2 warnings, 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.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (5 files):

⚔️ apps/api/src/app.ts (content)
⚔️ apps/api/src/routes/v1/channels/handlers.ts (content)
⚔️ apps/api/src/routes/v1/channels/schema.ts (content)
⚔️ apps/web/src/lib/api-client.ts (content)
⚔️ apps/web/src/routes/_authenticated.tsx (content)

These conflicts must be resolved before merging into main.
Resolve conflicts locally and push changes to this branch.
Title check ❓ Inconclusive The title 'Dev' is vague and generic, providing no meaningful information about the substantial changes in this pull request. Use a descriptive title that summarizes the main changes, such as 'Restructure sidebar components and add channel seeding script' or 'Add guild and channel routing with restructured sidebar'.
✅ 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
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch dev
  • Create stacked PR with resolved conflicts
  • Post resolved changes as copyable diffs in a comment

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

🤖 Fix all issues with AI agents
In `@apps/web/src/components/sidebar/channel-panel/channel-list.tsx`:
- Around line 87-112: ChannelItem currently renders a <button> but has no click
handler so channel clicks do nothing; update ChannelItem to accept an onClick
prop (e.g., onClick?: () => void or onSelect channelName) and forward it to the
button element, or alternatively accept a href and use Next.js Link/useRouter to
navigate to the channel route inside ChannelItem; ensure the prop signature is
added to the function parameters (ChannelItem) and that the button uses that
handler (and add a TODO comment if this is intentionally WIP).
- Around line 28-39: The channels query uses useQuery with queryKey ["channels",
guildSlug] but calls apiClient.v1.channels.$get() without passing guild context,
which can return channels for the previous guild; fix by making the request
include the guild identifier (e.g., pass guildSlug or guildId as a query
param/body to apiClient.v1.channels.$get or use a dedicated endpoint like
apiClient.v1.channels._guild(guildSlug).$get) so the server returns the correct
guild's channels, or alternatively gate the query with the layout's setActive
completion (ensure setActive resolves before enabling the useQuery) by wiring
the enabled flag to the setActive promise resolution; reference useQuery,
queryKey ["channels", guildSlug], apiClient.v1.channels.$get(), and setActive in
the layout to locate the code to change.

In `@apps/web/src/components/sidebar/channel-panel/guild-header.tsx`:
- Around line 9-15: The queryKey includes guildSlug but the queryFn calls
authClient.organization.getFullOrganization() which ignores the slug, causing
stale/mismatched cache entries; fix by either (A) setting the active org before
fetching—call authClient.organization.setActive({ slug: guildSlug }) (e.g., in
an async queryFn or a prefetch/side-effect) then call getFullOrganization(), or
(B) replace getFullOrganization() with an API method that accepts the slug
(e.g., organization.getBySlug(guildSlug) or similar) so the queryFn uses
guildSlug directly; update the useQuery queryFn accordingly to ensure the
returned data corresponds to the queryKey.

In `@apps/web/src/routes/_authenticated/`$guildSlug.tsx:
- Around line 13-26: The query functions for useQuery (queryFn for keys
["guilds"] and ["active-guild", guildSlug]) assume res.data exists; instead
check the API response and surface errors to React Query by throwing when data
is missing or res.error is present. Update the queryFn for
authClient.organization.list() and authClient.organization.getFullOrganization()
to validate the response (e.g., if (!res.data) throw new Error(...) including
res.error/message/status) so that guilds and activeOrg are never silently
undefined and React Query will set an error state.
- Around line 33-37: The useEffect that calls authClient.organization.setActive
inside the component currently treats setActive as fire-and-forget (useEffect
referencing guild and activeOrg?.id); update it to await the async
authClient.organization.setActive({ organizationId: guild.id }) call,
handle/rethrow or log errors, and after it completes explicitly invalidate or
refetch downstream queries that depend on the active organization (e.g.,
channels and active-guild queries) so state is consistent with activeOrg; ensure
the effect is async-safe (avoid unmounted updates) and only runs when guild
exists and activeOrg?.id !== guild.id.
🧹 Nitpick comments (4)
apps/api/src/app.ts (1)

35-49: Remove commented-out dead code.

The old loop-based routing is superseded by the chained approach on Line 31. Keeping ~15 lines of commented code adds noise; it's preserved in git history if needed.

apps/api/scripts/seed-channels.ts (1)

56-107: Wrap the seed in a transaction to avoid partial state on failure.

If an insert fails mid-way, the guild is left with its channels already deleted but only partially re-seeded. Wrapping in db.transaction(async (tx) => { ... }) and using tx for all operations prevents this.

Also, uncategorized channels and category rows both start position at 0. If the API sorts all top-level entries (where parentId is null) by position, these will interleave unpredictably. Consider offsetting category positions:

-        position: catIdx,
+        position: uncategorized.length + catIdx,
apps/web/src/components/sidebar/guild-bar/guild-bar.tsx (2)

61-74: Consider extracting shared guild/activeOrg queries into a custom hook.

The same two queries (["guilds"] and ["active-guild", guildSlug]) with identical queryFn implementations exist in both this component and $guildSlug.tsx. While React Query deduplicates at runtime, the duplicated code will drift over time. A shared hook like useGuilds() / useActiveOrg() would keep them in sync.

Also, res.data is returned without checking for errors — same concern as noted in the layout file.


18-22: Edge case: w[0] is undefined for empty-string segments.

If name contains consecutive spaces (e.g. "Foo Bar"), split(" ") produces empty strings and w[0] yields undefined. The result still works (joins as "undefinedF" etc.) but produces unexpected initials.

A safer approach:

  const initials = name
    .split(" ")
+   .filter(Boolean)
    .map((w) => w[0])
    .join("")
    .slice(0, 2)

Comment on lines +28 to +39
const { data } = useQuery({
queryKey: ["channels", guildSlug],
queryFn: async () => {
const res = await apiClient.v1.channels.$get()
if (!res.ok) {
throw new Error("Failed to fetch channels")
}
const json = await res.json()
return json.data
},
enabled: !!guildSlug,
})
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

Channel query depends on server-side active org but doesn't pass guild context explicitly.

apiClient.v1.channels.$get() doesn't include guildSlug (or guild ID) in the request. The query key includes guildSlug, so navigating between guilds triggers a refetch, but the server determines which guild's channels to return based on session state. Combined with the fire-and-forget setActive call in the layout, there's a window where this query fetches channels for the previous guild.

Consider either passing the guild ID as a query parameter, or ensuring setActive completes before this query is enabled.

🤖 Prompt for AI Agents
In `@apps/web/src/components/sidebar/channel-panel/channel-list.tsx` around lines
28 - 39, The channels query uses useQuery with queryKey ["channels", guildSlug]
but calls apiClient.v1.channels.$get() without passing guild context, which can
return channels for the previous guild; fix by making the request include the
guild identifier (e.g., pass guildSlug or guildId as a query param/body to
apiClient.v1.channels.$get or use a dedicated endpoint like
apiClient.v1.channels._guild(guildSlug).$get) so the server returns the correct
guild's channels, or alternatively gate the query with the layout's setActive
completion (ensure setActive resolves before enabling the useQuery) by wiring
the enabled flag to the setActive promise resolution; reference useQuery,
queryKey ["channels", guildSlug], apiClient.v1.channels.$get(), and setActive in
the layout to locate the code to change.

Comment on lines +87 to +112
function ChannelItem({
name,
type,
active = false,
}: {
name: string
type: string
active?: boolean
}) {
return (
<button
type="button"
className={cn(
"relative flex w-full items-center gap-2 rounded-lg px-2 py-[6px] text-[14px] hover:bg-foreground/[0.06]",
active
? "bg-foreground/[0.06] font-medium text-foreground"
: "text-muted-foreground"
)}
>
{active && (
<div className="absolute left-0 top-1/2 h-4 w-[3px] -translate-y-1/2 rounded-r-full bg-primary" />
)}
<ChannelIcon type={type} />
<span className="truncate">{name}</span>
</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 | 🟡 Minor

ChannelItem buttons have no click handler — channels aren't navigable.

The items render as <button> elements but have no onClick, so clicking a channel does nothing. If this is intentional WIP, consider adding a TODO comment. Otherwise, wire up navigation to a channel route.

Would you like me to help scaffold the channel navigation logic?

🤖 Prompt for AI Agents
In `@apps/web/src/components/sidebar/channel-panel/channel-list.tsx` around lines
87 - 112, ChannelItem currently renders a <button> but has no click handler so
channel clicks do nothing; update ChannelItem to accept an onClick prop (e.g.,
onClick?: () => void or onSelect channelName) and forward it to the button
element, or alternatively accept a href and use Next.js Link/useRouter to
navigate to the channel route inside ChannelItem; ensure the prop signature is
added to the function parameters (ChannelItem) and that the button uses that
handler (and add a TODO comment if this is intentionally WIP).

Comment on lines +9 to +15
const { data: activeOrg } = useQuery({
queryKey: ["active-guild", guildSlug],
queryFn: async () => {
const res = await authClient.organization.getFullOrganization()
return res.data
},
})
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

queryFn doesn't use guildSlug, but queryKey implies it should.

The query key includes guildSlug, so React Query treats each slug as a distinct cache entry. However, getFullOrganization() fetches whatever org is currently active on the auth client session — it doesn't accept a slug parameter. If the active org isn't switched before this component renders with a new guildSlug, you'll get stale/incorrect data cached under the new key.

Consider either:

  1. Setting the active organization (e.g., authClient.organization.setActive({ slug: guildSlug })) before or within the query, or
  2. Passing the slug to an API that resolves the org by slug.
🤖 Prompt for AI Agents
In `@apps/web/src/components/sidebar/channel-panel/guild-header.tsx` around lines
9 - 15, The queryKey includes guildSlug but the queryFn calls
authClient.organization.getFullOrganization() which ignores the slug, causing
stale/mismatched cache entries; fix by either (A) setting the active org before
fetching—call authClient.organization.setActive({ slug: guildSlug }) (e.g., in
an async queryFn or a prefetch/side-effect) then call getFullOrganization(), or
(B) replace getFullOrganization() with an API method that accepts the slug
(e.g., organization.getBySlug(guildSlug) or similar) so the queryFn uses
guildSlug directly; update the useQuery queryFn accordingly to ensure the
returned data corresponds to the queryKey.

Comment on lines +13 to +26
const { data: guilds, isPending: guildsLoading } = useQuery({
queryKey: ["guilds"],
queryFn: async () => {
const res = await authClient.organization.list()
return res.data
},
})
const { data: activeOrg } = useQuery({
queryKey: ["active-guild", guildSlug],
queryFn: async () => {
const res = await authClient.organization.getFullOrganization()
return res.data
},
})
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

No error handling for API calls — res.data may be undefined.

Both authClient.organization.list() and authClient.organization.getFullOrganization() can return error responses where res.data is undefined. Returning undefined silently from queryFn means guilds could be undefined even after loading completes (isPending is false), yet the code assumes it's an array on line 29.

Consider checking res.error or throwing so React Query surfaces the error state.

🤖 Prompt for AI Agents
In `@apps/web/src/routes/_authenticated/`$guildSlug.tsx around lines 13 - 26, The
query functions for useQuery (queryFn for keys ["guilds"] and ["active-guild",
guildSlug]) assume res.data exists; instead check the API response and surface
errors to React Query by throwing when data is missing or res.error is present.
Update the queryFn for authClient.organization.list() and
authClient.organization.getFullOrganization() to validate the response (e.g., if
(!res.data) throw new Error(...) including res.error/message/status) so that
guilds and activeOrg are never silently undefined and React Query will set an
error state.

Comment on lines +33 to +37
useEffect(() => {
if (!guild) return
if (activeOrg?.id === guild.id) return
authClient.organization.setActive({ organizationId: guild.id })
}, [guild, activeOrg?.id])
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

setActive is fire-and-forget — errors are silently swallowed and dependent queries aren't invalidated.

When the active organization is switched, downstream queries (channels, active-guild) depend on that state being committed server-side. Without awaiting setActive and invalidating queries afterward, there's a race where activeOrg and channel data may reflect the previous guild.

Proposed fix
+ import { useQueryClient } from "@tanstack/react-query"
  ...
  function GuildLayout() {
+   const queryClient = useQueryClient()
    ...
    useEffect(() => {
      if (!guild) return
      if (activeOrg?.id === guild.id) return
-     authClient.organization.setActive({ organizationId: guild.id })
+     authClient.organization.setActive({ organizationId: guild.id }).then(() => {
+       queryClient.invalidateQueries({ queryKey: ["active-guild"] })
+       queryClient.invalidateQueries({ queryKey: ["channels"] })
+     })
    }, [guild, activeOrg?.id])
🤖 Prompt for AI Agents
In `@apps/web/src/routes/_authenticated/`$guildSlug.tsx around lines 33 - 37, The
useEffect that calls authClient.organization.setActive inside the component
currently treats setActive as fire-and-forget (useEffect referencing guild and
activeOrg?.id); update it to await the async authClient.organization.setActive({
organizationId: guild.id }) call, handle/rethrow or log errors, and after it
completes explicitly invalidate or refetch downstream queries that depend on the
active organization (e.g., channels and active-guild queries) so state is
consistent with activeOrg; ensure the effect is async-safe (avoid unmounted
updates) and only runs when guild exists and activeOrg?.id !== guild.id.

@BuckyMcYolo BuckyMcYolo merged commit eb89ebf into main Feb 16, 2026
1 check passed
This was referenced Feb 18, 2026
Merged
Merged
@coderabbitai coderabbitai Bot mentioned this pull request Apr 3, 2026
Merged
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