Skip to content

rename channel to team to channel, workpsace to organization#54

Merged
Ali1raz merged 1 commit into
mainfrom
rename
Jul 6, 2026
Merged

rename channel to team to channel, workpsace to organization#54
Ali1raz merged 1 commit into
mainfrom
rename

Conversation

@Ali1raz

@Ali1raz Ali1raz commented Jul 6, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Reworked the app around organizations and teams, including team lists, team pages, team members, invites, and organization switching.
    • Added organization-level creation, editing, leaving, and member management flows.
    • Updated real-time messaging and thread activity to work in the new team context.
  • Bug Fixes

    • Updated navigation, protected routes, and invite acceptance to send users to the correct organization pages.
    • Fixed branding across the app, including logos, product name, and page titles.

@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
team-flow Error Error Jul 6, 2026 6:10pm

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR renames the core domain model from workspace/channel to organization/team across the entire application—schemas, middleware, ORPC routers, realtime providers, and UI components—and rebrands the product from TeamFlow to TeamComms throughout copy, assets, and configuration.

Changes

TeamFlow to TeamComms Rebrand

Layer / File(s) Summary
Branding assets and copy
README.md, app/(auth)/login/page.tsx, components/general/footer.tsx, components/general/hero.tsx, components/general/navbar.tsx, components/general/user-avatar-dropdown.tsx, app/(organization)/organizations/_components/header.tsx, app/accept-invite/[id]/page.tsx, lib/app/site.ts, lib/auth-client.ts, lib/auth.ts, package.json
Logo assets, page text, cookie names, and site/package metadata are updated from TeamFlow/workspace naming to TeamComms/organization naming; dashboard links point to /organizations.

Organization/Team Domain Refactor

Layer / File(s) Summary
Schema, type, and utility contracts
app/(organization)/organizations/schema.ts, lib/schema.ts, realtime/schema.ts, lib/utils.ts
Zod schemas, inferred types, realtime event schemas, and color/slug helpers are renamed from workspace/channel to organization/team shapes.
Auth middleware and route protection
app/middlewares/member.ts, app/middlewares/organization.ts, app/router/user.ts, proxy.ts
Middleware context and lookups switch to organization scoping; protected routes now match /organizations.
Backend routers
app/router/index.ts, app/router/organization.ts, app/router/team.ts, app/router/members.ts, app/router/message.ts, app/router/ai.ts
Organization/team CRUD, member/invitation management, message CRUD/threads, and AI endpoints are re-scoped from workspace/channel to organization/team.
Realtime provider and event wiring
components/team-realtime-provider.tsx, realtime/index.ts, app/(organization)/organizations/[organizationId]/_components/realtime-provider-wrapper.tsx
Realtime provider/hook and server event validation switch to team-scoped rooms and cache keys.
Organization pages, list, switcher, and dialogs
app/(organization)/organizations/**, components/organization-switcher.tsx, components/delete-organization-dialog.tsx
Organization list/detail pages, header, switcher, and create/update/delete/invite dialogs use organization-scoped data.
Invitations, member roles, and profile wiring
app/(organization)/organizations/[organizationId]/invitations/**, components/update-member-role-dialog.tsx, components/remove-member-dialog.tsx, app/profile/**
Invitation and member-role UI, and profile references, are re-scoped to organizations.
Team pages, cards, dialogs, and sidebar
app/(organization)/organizations/[organizationId]/team/**, components/create-tem-dialog.tsx, components/update-team-dialog.tsx, components/delete-team-dialog.tsx, components/add-member-to-team.tsx, components/app-sidebar.tsx
Team list/card/detail pages, CRUD dialogs, member management, and sidebar navigation switch from channel-based to team-based data.
Message list, composer, and thread sidebar
app/(organization)/organizations/[organizationId]/_components/MessageList.tsx, .../messaeg-input.tsx, .../edit-message-form.tsx, .../delete-message-dialog.tsx, components/thread-sidebar/**
Message and thread UI switch cache keys and route params from channel/workspace to team/organization.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MessageInput
  participant MessageRouter as message router
  participant Prisma
  participant RealtimeProvider as RealtimeTeamProvider
  participant Cache as React Query Cache

  MessageInput->>MessageRouter: createMessage({teamId, content})
  MessageRouter->>Prisma: findFirst team(teamId, organizationId)
  Prisma-->>MessageRouter: team
  MessageRouter->>Prisma: create message(teamId)
  Prisma-->>MessageRouter: message
  MessageRouter-->>MessageInput: created message
  MessageInput->>RealtimeProvider: send(message:created)
  RealtimeProvider->>Cache: setQueryData(["message.list", teamId])
Loading

Possibly related PRs

  • Ali1raz/team-flow#35: Both PRs modify the realtime event schemas in realtime/index.ts/realtime/schema.ts, with this PR renaming RealtimechannelEventSchema to RealtimeTeamEventSchema.
  • Ali1raz/team-flow#36: Both PRs touch realtime message deletion behavior in delete-message-dialog.tsx and the realtime provider handling message:deleted events.
  • Ali1raz/team-flow#38: Both PRs modify app/router/user.ts and components/update-member-role-dialog.tsx, with this PR migrating that plumbing from workspace to organization scope.

Suggested labels: feature

🚥 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 reflects the main rename from channel/workspace terminology to team/organization terminology, despite awkward wording and a typo.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.
✨ 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 rename

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

@Ali1raz
Ali1raz merged commit 745c18d into main Jul 6, 2026
1 of 4 checks passed
@Ali1raz
Ali1raz deleted the rename branch July 6, 2026 18:27

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

Caution

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

⚠️ Outside diff range comments (4)
app/router/organization.ts (2)

555-572: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Make leave cleanup reliable before the operation is considered complete.

If auth.api.leaveOrganization succeeds and prisma.teamMember.deleteMany fails, the user has left the organization but stale team memberships remain. Move this cleanup into a durable hook/outbox/retry path or another atomic ownership point.

🤖 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/router/organization.ts` around lines 555 - 572, The leave flow in
organization cleanup is not reliable because `auth.api.leaveOrganization` can
succeed while the follow-up `prisma.teamMember.deleteMany` in the same handler
fails, leaving stale memberships behind. Move the cleanup out of the
`leaveOrganization` request path into a durable ownership point such as a hook,
outbox, or retryable job, and reference the `leaveOrganization` handler and
`teamMember.deleteMany` cleanup so the user is only considered fully left once
both steps are safely completed.

274-310: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Bind privileged member actions to the authorized organization.

The admin/owner check is for context.organization, but orgId can come from input.organizationId. That lets authorization and the target organization diverge.

Proposed fix pattern
-    const orgId = input.organizationId ?? context.organization.id;
+    // The role check above is scoped to the active organization, so reject caller-supplied ids from another tenant.
+    if (input.organizationId && input.organizationId !== context.organization.id) {
+      throw errors.FORBIDDEN({
+        message: "Cannot manage members outside the active organization",
+      });
+    }
+
+    const orgId = context.organization.id;

Also applies to: 489-529

🤖 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/router/organization.ts` around lines 274 - 310, The privileged member
role update flow in organization router is using a caller-supplied
organizationId while authorization is checked against context.organization,
which can let the target org differ from the authorized one. Update the
member-role handler in the organization router so the admin/owner permission
check and the member lookup/update both use the same authorized organization
source, preferably context.organization.id, and reject or ignore any mismatched
input.organizationId. Apply the same binding rule to the related member action
handler referenced by the review comment so all privileged membership operations
stay scoped to the authorized organization.
app/router/ai.ts (1)

29-34: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Require team membership before exporting thread content to AI.

The summary route now limits lookups to the organization, but not to teams the caller belongs to. That can expose another team’s thread content to the AI provider if an organization member knows the thread ID.

Representative fix
         threadId: input.threadId,
         team: {
           organizationId: context.organization.id,
+          // Prevent AI export for teams the caller is not a member of.
+          teammembers: { some: { userId: context.user.id } },
         },

Apply the same predicate to the parent lookup below.

Also applies to: 49-54

🤖 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/router/ai.ts` around lines 29 - 34, The parent message lookup in the ai
router only scopes by organization, which still allows cross-team thread access;
update the parent lookup in the thread export flow to apply the same
team-membership predicate used elsewhere, so only messages from teams the caller
belongs to can be resolved. Use the existing parent lookup in the ai route and
the thread summary query as the reference points, and make sure the
prisma.message.findFirst filter includes the caller’s team membership constraint
alongside organizationId and threadId.
app/router/message.ts (1)

52-70: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Keep message access scoped to team members.

These checks now prove the team/message belongs to the current organization, but they do not verify context.user.id is a member of that team. With team membership modeled separately, any organization member who knows a team/message ID can read or write across teams. Add a teammembers filter here or centralize this in a team-member middleware.

Representative fix
     const team = await prisma.team.findFirst({
       where: {
         id: input.teamId,
         organizationId: context.organization.id,
+        // Enforce the team membership boundary, not only the organization boundary.
+        teammembers: { some: { userId: context.user.id } },
       },
       select: {
         id: true,

Apply the same membership predicate to the parent/message lookups that currently filter only by team.organization.

Also applies to: 73-86, 155-166, 260-266, 320-326, 400-405

🤖 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/router/message.ts` around lines 52 - 70, The message/thread lookup logic
in message.ts only verifies organization ownership and does not enforce that
context.user.id belongs to the target team, so access can leak across teams.
Update the parent/message validation in the affected message routes to include a
team membership check (or route all such checks through a shared team-member
middleware) alongside the existing team.organization filter. Apply the same
membership-scoped predicate in the message lookup paths around the
parentMessage/threadId checks and the other listed message access handlers so
only team members can read or write those messages.
🧹 Nitpick comments (6)
app/(organization)/organizations/[organizationId]/page.tsx (1)

5-16: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Leftover "workspace"-era naming (w) and no dedicated single-org lookup.

.find((w) => w.id === organizationId) uses w, a naming residue from the pre-rename "workspace" domain. Since this PR's entire purpose is eliminating workspace terminology, rename to something like org for clarity. Separately, fetching the entire organizations list just to resolve one record by id is wasteful if a dedicated single-item fetch (e.g., client.organization.get) exists — worth confirming.

As per coding guidelines, **/*.{ts,tsx,js,jsx} requires that new code include "comments... explaining the reason for the code or the non-obvious decision it implements" — a short comment on why the list-then-find approach is used (vs. a direct getter) would help future readers.

♻️ Proposed naming fix
-  const { organizations } = await client.organization.list();
-  const organization = organizations.find((w) => w.id === organizationId);
+  const { organizations } = await client.organization.list();
+  // TODO: replace with a dedicated single-organization lookup if available
+  const organization = organizations.find((org) => org.id === organizationId);
🤖 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/`(organization)/organizations/[organizationId]/page.tsx around lines 5 -
16, Rename the leftover workspace-era iterator name in generateMetadata from w
to org (or similar) to match the organization domain, and verify whether a
direct single-organization lookup like client.organization.get exists instead of
listing all organizations and then finding one. If the list-then-find approach
stays, add a brief comment in generateMetadata explaining why that non-obvious
choice is used so future readers understand it.

Source: Coding guidelines

app/(organization)/organizations/[organizationId]/_components/organization-header.tsx (1)

17-28: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Sequential suspense queries create a render waterfall.

useSuspenseQuery(orpc.organization.list...) and useSuspenseQuery(orpc.team.list...) are separate hook calls in the same component. When the first suspends, React aborts the render before reaching the second hook, so team.list doesn't start fetching until organization.list resolves — a serial waterfall instead of parallel fetches, adding latency to every render of this header.

Consider useSuspenseQueries to fire both requests in parallel.

⚡ Proposed fix using useSuspenseQueries
-  const {
-    data: { currentOrganization },
-  } = useSuspenseQuery(orpc.organization.list.queryOptions());
-
-  const userData = useQuery(orpc.user.get.queryOptions());
-  const user = userData.data;
-
-  const {
-    data: { teams },
-  } = useSuspenseQuery(
-    orpc.team.list.queryOptions({ input: { organizationId: organizationId } })
-  );
+  const [
+    { data: { currentOrganization } },
+    { data: { teams } },
+  ] = useSuspenseQueries({
+    queries: [
+      orpc.organization.list.queryOptions(),
+      orpc.team.list.queryOptions({ input: { organizationId } }),
+    ],
+  });
+
+  const userData = useQuery(orpc.user.get.queryOptions());
+  const user = userData.data;
🤖 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/`(organization)/organizations/[organizationId]/_components/organization-header.tsx
around lines 17 - 28, The header component is triggering a suspense waterfall
because `useSuspenseQuery` for `orpc.organization.list.queryOptions()` and
`orpc.team.list.queryOptions()` run sequentially in `organization-header.tsx`.
Update the `OrganizationHeader` data fetching to use `useSuspenseQueries` so
both queries start in parallel, keeping the existing `currentOrganization`,
`teams`, and `userData` usage intact while removing the serialized suspend
behavior.
app/(organization)/organizations/[organizationId]/_components/messaeg-input.tsx (1)

101-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated ["message.list", teamId] key into a local constant.

The raw key literal is duplicated 5+ times in this file. Sibling files (edit-message-form.tsx, delete-message-dialog.tsx) already extract this into a single messageListKey const with a "must stay in sync" comment — following the same pattern here reduces the risk of a typo causing cache/key drift (as already happened with the chanelId field elsewhere in this PR).

♻️ Proposed refactor
+  // Raw key the infinite query is stored under; must stay in sync with MessageList.
+  const messageListKey = ["message.list", teamId] as const;
+
   const createMessageMutation = useMutation(
     orpc.message.create.mutationOptions({
       onMutate: async (variables) => {
         await queryclient.cancelQueries({
-          queryKey: ["message.list", teamId],
+          queryKey: messageListKey,
         });

         const prevData = queryclient.getQueryData<
           InfiniteData<MessagePage, string | undefined>
-        >(["message.list", teamId]);
+        >(messageListKey);

(apply similarly to the remaining occurrences at lines 101, 125, 153, 177)

Also applies to: 125-125, 147-153, 177-177

🤖 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/`(organization)/organizations/[organizationId]/_components/messaeg-input.tsx
at line 101, The query key ["message.list", teamId] is repeated multiple times
in messaeg-input.tsx, so extract it into a local messageListKey constant like in
edit-message-form.tsx and delete-message-dialog.tsx, with the same must stay in
sync comment. Update all usages in the message input flow (including the
mutation/invalidation and any related hooks in this component) to reference that
constant so the key stays consistent and typo-safe.
components/delete-team-dialog.tsx (1)

38-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Leftover "channel" naming in onSuccess param; conditional redirect lacks explanatory comment.

The mutation result is bound to ch — a leftover abbreviation from the old "channel" domain that this PR is explicitly renaming away from. Also, the redirect logic (if (teamId === team.id) router.push(...), navigating away only when the deleted team is the one currently being viewed) is non-obvious and has no comment explaining why.

As per coding guidelines, "Add comments to any new code you add, explaining the reason for the code or the non-obvious decision it implements."

♻️ Proposed fix
     orpc.team.delete.mutationOptions({
-      onSuccess: (ch) => {
+      onSuccess: (result) => {
         toast.success(`Team ${team.name} Deleted successfully!`);
         queryClient.invalidateQueries({
           queryKey: orpc.team.list.queryKey({
-            input: { organizationId: ch.organizationId },
+            input: { organizationId: result.organizationId },
           }),
         });
 
+        // Only redirect away if the user is currently viewing the team being deleted;
+        // deleting from a list elsewhere should keep the user on the current page.
         if (teamId === team.id)
-          router.push(`/organizations/${ch.organizationId}`);
+          router.push(`/organizations/${result.organizationId}`);
🤖 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 `@components/delete-team-dialog.tsx` around lines 38 - 59, The issue is the
leftover abbreviated `ch` name in `deleteTeamMutation`’s `onSuccess` handler and
the non-obvious redirect decision. Rename `ch` to a team/organization-specific
name in `useMutation(orpc.team.delete.mutationOptions(...))` and add a short
comment near the `if (teamId === team.id) router.push(...)` branch in
`onSuccess` explaining that we only navigate away when the deleted team is the
one currently being viewed. Also keep the query invalidation and dialog close
behavior unchanged.

Source: Coding guidelines

app/(organization)/organizations/[organizationId]/_components/MessageList.tsx (1)

36-36: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Document or centralize this shared cache key.

["message.list", teamId] must stay aligned with the thread sidebar’s optimistic update key. Add a shared helper or an inline comment explaining the cross-component contract to prevent cache invalidation drift. As per coding guidelines, “Add comments to any new code you add, explaining the reason for the code or the non-obvious decision it implements.”

🤖 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/`(organization)/organizations/[organizationId]/_components/MessageList.tsx
at line 36, The cache key in MessageList’s queryKey is a shared contract with
the thread sidebar’s optimistic update logic, so it should not be left as an
undocumented literal. Either centralize the key generation in a shared helper
used by both MessageList and the sidebar optimistic update code, or add a
concise inline comment near the queryKey explaining that it must remain aligned
across components. Keep the shared symbol consistent wherever the message list
cache is read or invalidated.

Source: Coding guidelines

components/app-sidebar.tsx (1)

134-146: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Finish the channel-to-team rename in the loop variable.

teams.map((ch) => ...) leaves channel terminology in the team navigation code. Rename it to team to keep this PR’s domain rename consistent.

Suggested cleanup
-                        {teams &&
-                          teams.map((ch) => (
+                        {teams &&
+                          teams.map((team) => (
                             <SidebarMenuSubItem
-                              key={ch.id}
+                              key={team.id}
                               className="flex items-center justify-center flex-row"
                             >
                               <SidebarMenuSubButton
                                 asChild
-                                isActive={teamId === ch.id}
+                                isActive={teamId === team.id}
                                 className="text-muted-foreground flex-1"
                               >
                                 <Link
-                                  href={`/organizations/${organizationId}/team/${ch.id}`}
-                                  title={ch.name}
+                                  href={`/organizations/${organizationId}/team/${team.id}`}
+                                  title={team.name}
                                 >
🤖 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 `@components/app-sidebar.tsx` around lines 134 - 146, Rename the loop variable
in the team navigation render so the channel terminology is fully removed: in
the teams.map callback inside the sidebar component, change the generic alias
from ch to team and update all references in that mapping block accordingly.
Keep the rest of the team-link rendering logic the same, using the existing
teams map and SidebarMenuSubItem/SidebarMenuSubButton structure.
🤖 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/`(organization)/organizations/[organizationId]/_components/leave-organization-dialog.tsx:
- Around line 68-75: The dialog title in leave-organization-dialog.tsx still
shows leftover copy from a delete-message flow, so update the DialogTitle in
LeaveOrganizationDialog to match the leave action. Keep the existing
DialogContent/DialogDescription structure, but replace the misleading title text
with a clear “Leave Organization” label so the confirmation dialog aligns with
its purpose.

In
`@app/`(organization)/organizations/[organizationId]/_components/realtime-provider-wrapper.tsx:
- Around line 10-12: The early return in realtime-provider-wrapper’s route check
is intentional and should be documented so it is not mistaken for dead code. Add
a short inline comment near the useParams teamId guard explaining that the
wrapper only provides realtime context on team routes and otherwise
intentionally renders children unchanged. Keep the note close to the teamId
conditional in RealtimeProviderWrapper so the scoping decision is obvious.

In
`@app/`(organization)/organizations/[organizationId]/team/[teamId]/members/_components/remov-member-dialog.tsx:
- Around line 71-75: Tighten the confirmation copy in the RemoveMemberDialog
component so the DialogDescription reads naturally; update the user-facing text
in remove-member-dialog.tsx from “Are you sure to remove” to “Are you sure you
want to remove” while keeping the rest of the memberName and team wording
intact.

In `@app/router/members.ts`:
- Around line 12-20: The invite flow in inviteMember is still trusting
client-supplied organizationId instead of the organization established by
requireOrganizationMiddleware. Update the handler to pass
context.organization.id into auth.api.createInvitation, and if input.teamId is
still accepted, verify that it belongs to the same organization before creating
the invitation. Keep the fix localized to inviteMember and the organization/team
lookup logic so the middleware-scoped organization is always the source of
truth.

In `@app/router/organization.ts`:
- Around line 107-108: The create-organization flow is logging raw tenant
metadata via the console statement in the organization router, which can leak
customer context. Remove the direct console.log from the slug creation path and
replace it with structured, redacted logging only if needed, using the
createSlug and organization creation logic as the place to update. Also apply
the same cleanup to the other referenced log site in this router.
- Around line 199-209: The organization members routes are using the
`:organizationId` path param but the handler is still relying on
`context.organization`, which can return members for the wrong organization.
Update `listOrganizationMembers` (and the related route noted in the comment) to
explicitly validate that the requested `organizationId` matches the loaded
organization in `requireOrganizationMiddleware`, and reject mismatches before
returning any members. Use the existing `organizationId` param and
`context.organization` comparison in the route handler so stale URLs cannot
fetch the wrong organization’s members.

In `@app/router/team.ts`:
- Around line 208-215: The team read endpoints are not scoped to the active
organization, so `getTeam` and `listTeamMembers` can return data for arbitrary
`teamId` values. Update the Prisma queries in `team.get` and `listTeamMembers`
to include `context.organization.id` in the lookup/filter, and ensure membership
checks also verify the requested team belongs to that organization before
returning results. Use the existing handler/context symbols to keep the fix
localized and consistent.
- Around line 149-153: The team listing handler is still querying with the
caller-supplied organizationId instead of the authorized active organization.
Update the query in the team router handler to bind the prisma.team.findMany
filter to context.organization.id, keeping requireOrganizationMiddleware as the
source of truth and avoiding organization mismatches.

---

Outside diff comments:
In `@app/router/ai.ts`:
- Around line 29-34: The parent message lookup in the ai router only scopes by
organization, which still allows cross-team thread access; update the parent
lookup in the thread export flow to apply the same team-membership predicate
used elsewhere, so only messages from teams the caller belongs to can be
resolved. Use the existing parent lookup in the ai route and the thread summary
query as the reference points, and make sure the prisma.message.findFirst filter
includes the caller’s team membership constraint alongside organizationId and
threadId.

In `@app/router/message.ts`:
- Around line 52-70: The message/thread lookup logic in message.ts only verifies
organization ownership and does not enforce that context.user.id belongs to the
target team, so access can leak across teams. Update the parent/message
validation in the affected message routes to include a team membership check (or
route all such checks through a shared team-member middleware) alongside the
existing team.organization filter. Apply the same membership-scoped predicate in
the message lookup paths around the parentMessage/threadId checks and the other
listed message access handlers so only team members can read or write those
messages.

In `@app/router/organization.ts`:
- Around line 555-572: The leave flow in organization cleanup is not reliable
because `auth.api.leaveOrganization` can succeed while the follow-up
`prisma.teamMember.deleteMany` in the same handler fails, leaving stale
memberships behind. Move the cleanup out of the `leaveOrganization` request path
into a durable ownership point such as a hook, outbox, or retryable job, and
reference the `leaveOrganization` handler and `teamMember.deleteMany` cleanup so
the user is only considered fully left once both steps are safely completed.
- Around line 274-310: The privileged member role update flow in organization
router is using a caller-supplied organizationId while authorization is checked
against context.organization, which can let the target org differ from the
authorized one. Update the member-role handler in the organization router so the
admin/owner permission check and the member lookup/update both use the same
authorized organization source, preferably context.organization.id, and reject
or ignore any mismatched input.organizationId. Apply the same binding rule to
the related member action handler referenced by the review comment so all
privileged membership operations stay scoped to the authorized organization.

---

Nitpick comments:
In
`@app/`(organization)/organizations/[organizationId]/_components/messaeg-input.tsx:
- Line 101: The query key ["message.list", teamId] is repeated multiple times in
messaeg-input.tsx, so extract it into a local messageListKey constant like in
edit-message-form.tsx and delete-message-dialog.tsx, with the same must stay in
sync comment. Update all usages in the message input flow (including the
mutation/invalidation and any related hooks in this component) to reference that
constant so the key stays consistent and typo-safe.

In
`@app/`(organization)/organizations/[organizationId]/_components/MessageList.tsx:
- Line 36: The cache key in MessageList’s queryKey is a shared contract with the
thread sidebar’s optimistic update logic, so it should not be left as an
undocumented literal. Either centralize the key generation in a shared helper
used by both MessageList and the sidebar optimistic update code, or add a
concise inline comment near the queryKey explaining that it must remain aligned
across components. Keep the shared symbol consistent wherever the message list
cache is read or invalidated.

In
`@app/`(organization)/organizations/[organizationId]/_components/organization-header.tsx:
- Around line 17-28: The header component is triggering a suspense waterfall
because `useSuspenseQuery` for `orpc.organization.list.queryOptions()` and
`orpc.team.list.queryOptions()` run sequentially in `organization-header.tsx`.
Update the `OrganizationHeader` data fetching to use `useSuspenseQueries` so
both queries start in parallel, keeping the existing `currentOrganization`,
`teams`, and `userData` usage intact while removing the serialized suspend
behavior.

In `@app/`(organization)/organizations/[organizationId]/page.tsx:
- Around line 5-16: Rename the leftover workspace-era iterator name in
generateMetadata from w to org (or similar) to match the organization domain,
and verify whether a direct single-organization lookup like
client.organization.get exists instead of listing all organizations and then
finding one. If the list-then-find approach stays, add a brief comment in
generateMetadata explaining why that non-obvious choice is used so future
readers understand it.

In `@components/app-sidebar.tsx`:
- Around line 134-146: Rename the loop variable in the team navigation render so
the channel terminology is fully removed: in the teams.map callback inside the
sidebar component, change the generic alias from ch to team and update all
references in that mapping block accordingly. Keep the rest of the team-link
rendering logic the same, using the existing teams map and
SidebarMenuSubItem/SidebarMenuSubButton structure.

In `@components/delete-team-dialog.tsx`:
- Around line 38-59: The issue is the leftover abbreviated `ch` name in
`deleteTeamMutation`’s `onSuccess` handler and the non-obvious redirect
decision. Rename `ch` to a team/organization-specific name in
`useMutation(orpc.team.delete.mutationOptions(...))` and add a short comment
near the `if (teamId === team.id) router.push(...)` branch in `onSuccess`
explaining that we only navigate away when the deleted team is the one currently
being viewed. Also keep the query invalidation and dialog close behavior
unchanged.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fdf8b5d9-e5d6-48de-a72d-0b4700163902

📥 Commits

Reviewing files that changed from the base of the PR and between 005557a and 734d702.

⛔ Files ignored due to path filters (1)
  • public/team-comms.png is excluded by !**/*.png
📒 Files selected for processing (77)
  • README.md
  • app/(auth)/login/page.tsx
  • app/(organization)/layout.tsx
  • app/(organization)/organizations/[organizationId]/_components/MessageList.tsx
  • app/(organization)/organizations/[organizationId]/_components/attachment-chip.tsx
  • app/(organization)/organizations/[organizationId]/_components/delete-message-dialog.tsx
  • app/(organization)/organizations/[organizationId]/_components/edit-message-form.tsx
  • app/(organization)/organizations/[organizationId]/_components/image-dialog.tsx
  • app/(organization)/organizations/[organizationId]/_components/leave-organization-dialog.tsx
  • app/(organization)/organizations/[organizationId]/_components/messaeg-input.tsx
  • app/(organization)/organizations/[organizationId]/_components/message-item.tsx
  • app/(organization)/organizations/[organizationId]/_components/message-omposer.tsx
  • app/(organization)/organizations/[organizationId]/_components/organization-header.tsx
  • app/(organization)/organizations/[organizationId]/_components/realtime-provider-wrapper.tsx
  • app/(organization)/organizations/[organizationId]/_components/team-card.tsx
  • app/(organization)/organizations/[organizationId]/_components/team-list.tsx
  • app/(organization)/organizations/[organizationId]/invitations/_components/cancel-invitation-dialog.tsx
  • app/(organization)/organizations/[organizationId]/invitations/_components/invitation-table-actions-dropdown.tsx
  • app/(organization)/organizations/[organizationId]/invitations/_components/invitation-table.tsx
  • app/(organization)/organizations/[organizationId]/invitations/layout.tsx
  • app/(organization)/organizations/[organizationId]/invitations/page.tsx
  • app/(organization)/organizations/[organizationId]/layout.tsx
  • app/(organization)/organizations/[organizationId]/page.tsx
  • app/(organization)/organizations/[organizationId]/team/[teamId]/loading.tsx
  • app/(organization)/organizations/[organizationId]/team/[teamId]/members/_components/remov-member-dialog.tsx
  • app/(organization)/organizations/[organizationId]/team/[teamId]/members/layout.tsx
  • app/(organization)/organizations/[organizationId]/team/[teamId]/members/page.tsx
  • app/(organization)/organizations/[organizationId]/team/[teamId]/page.tsx
  • app/(organization)/organizations/_components/create-organization-dialog.tsx
  • app/(organization)/organizations/_components/header.tsx
  • app/(organization)/organizations/_components/invite-organization-dialog.tsx
  • app/(organization)/organizations/_components/organization-list.tsx
  • app/(organization)/organizations/_components/update-organization-dialog.tsx
  • app/(organization)/organizations/layout.tsx
  • app/(organization)/organizations/page.tsx
  • app/(organization)/organizations/schema.ts
  • app/(organization)/page.tsx
  • app/(workspace)/workspaces/[workspaceId]/_components/realtime-provider-wrapper.tsx
  • app/(workspace)/workspaces/[workspaceId]/page.tsx
  • app/accept-invite/[id]/page.tsx
  • app/middlewares/member.ts
  • app/middlewares/organization.ts
  • app/profile/_components/update-profile-form.tsx
  • app/profile/layout.tsx
  • app/router/ai.ts
  • app/router/index.ts
  • app/router/members.ts
  • app/router/message.ts
  • app/router/organization.ts
  • app/router/team.ts
  • app/router/user.ts
  • components/add-member-to-team.tsx
  • components/app-sidebar.tsx
  • components/create-tem-dialog.tsx
  • components/delete-organization-dialog.tsx
  • components/delete-team-dialog.tsx
  • components/general/footer.tsx
  • components/general/hero.tsx
  • components/general/navbar.tsx
  • components/general/user-avatar-dropdown.tsx
  • components/organization-switcher.tsx
  • components/remove-member-dialog.tsx
  • components/team-realtime-provider.tsx
  • components/thread-sidebar/right-sidebar.tsx
  • components/thread-sidebar/thread-context.tsx
  • components/thread-sidebar/threads-form.tsx
  • components/update-member-role-dialog.tsx
  • components/update-team-dialog.tsx
  • lib/app/site.ts
  • lib/auth-client.ts
  • lib/auth.ts
  • lib/schema.ts
  • lib/utils.ts
  • package.json
  • proxy.ts
  • realtime/index.ts
  • realtime/schema.ts
💤 Files with no reviewable changes (2)
  • app/(workspace)/workspaces/[workspaceId]/page.tsx
  • app/(workspace)/workspaces/[workspaceId]/_components/realtime-provider-wrapper.tsx

Comment on lines 68 to 75
<DialogContent className="sm:max-w-sm">
<DialogHeader>
<DialogTitle>Delete Message</DialogTitle>
<DialogDescription>
Are you sure you want to leave this workspace? This action cannot be
undone.
Are you sure you want to leave this organization? This action cannot
be undone.
</DialogDescription>
</DialogHeader>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Wrong dialog title: "Delete Message" instead of "Leave Organization".

This appears to be leftover copy from a different dialog (delete-message). Users confirming to leave an organization will see a misleading title.

🐛 Proposed fix
-          <DialogTitle>Delete Message</DialogTitle>
+          <DialogTitle>Leave Organization</DialogTitle>
📝 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
<DialogContent className="sm:max-w-sm">
<DialogHeader>
<DialogTitle>Delete Message</DialogTitle>
<DialogDescription>
Are you sure you want to leave this workspace? This action cannot be
undone.
Are you sure you want to leave this organization? This action cannot
be undone.
</DialogDescription>
</DialogHeader>
<DialogContent className="sm:max-w-sm">
<DialogHeader>
<DialogTitle>Leave Organization</DialogTitle>
<DialogDescription>
Are you sure you want to leave this organization? This action cannot
be undone.
</DialogDescription>
</DialogHeader>
🤖 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/`(organization)/organizations/[organizationId]/_components/leave-organization-dialog.tsx
around lines 68 - 75, The dialog title in leave-organization-dialog.tsx still
shows leftover copy from a delete-message flow, so update the DialogTitle in
LeaveOrganizationDialog to match the leave action. Keep the existing
DialogContent/DialogDescription structure, but replace the misleading title text
with a clear “Leave Organization” label so the confirmation dialog aligns with
its purpose.

Comment on lines +10 to +12
const { teamId } = useParams<{ teamId?: string }>();

if (!teamId) return <>{children}</>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document why the wrapper no-ops outside team routes.

The conditional provider is intentional route scoping; add a short comment so it is not mistaken for dead code. As per coding guidelines, **/*.{ts,tsx,js,jsx}: Add comments to any new code you add, explaining the reason for the code or the non-obvious decision it implements.

Proposed comment
   const { teamId } = useParams<{ teamId?: string }>();
 
+  // Organization-level routes do not include a teamId, so avoid joining a realtime team room there.
   if (!teamId) return <>{children}</>;
📝 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 { teamId } = useParams<{ teamId?: string }>();
if (!teamId) return <>{children}</>;
const { teamId } = useParams<{ teamId?: string }>();
// Organization-level routes do not include a teamId, so avoid joining a realtime team room there.
if (!teamId) return <>{children}</>;
🤖 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/`(organization)/organizations/[organizationId]/_components/realtime-provider-wrapper.tsx
around lines 10 - 12, The early return in realtime-provider-wrapper’s route
check is intentional and should be documented so it is not mistaken for dead
code. Add a short inline comment near the useParams teamId guard explaining that
the wrapper only provides realtime context on team routes and otherwise
intentionally renders children unchanged. Keep the note close to the teamId
conditional in RealtimeProviderWrapper so the scoping decision is obvious.

Source: Coding guidelines

Comment on lines +71 to +75
<DialogTitle>Remove Member from this team</DialogTitle>
<DialogDescription>
Are you sure to remove{" "}
<span className="font-bold text-destructive">{memberName}</span> from
this channel?
this team?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Tighten the confirmation dialog copy.

“Are you sure to remove” reads awkwardly; use “Are you sure you want to remove” for clearer user-facing text.

🤖 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/`(organization)/organizations/[organizationId]/team/[teamId]/members/_components/remov-member-dialog.tsx
around lines 71 - 75, Tighten the confirmation copy in the RemoveMemberDialog
component so the DialogDescription reads naturally; update the user-facing text
in remove-member-dialog.tsx from “Are you sure to remove” to “Are you sure you
want to remove” while keeping the rest of the memberName and team wording
intact.

Comment thread app/router/members.ts
Comment on lines 12 to +20
export const inviteMember = base
.use(requireAuthMiddleware)
.use(requireworkspaceMiddleware)
.use(requireOrganizationMiddleware)
.use(standardsecurityMiddleware)
.use(heavyWritesecurityMiddleware)
.route({
method: "POST",
path: "/workspace/members/invite",
summary: "Invite members to the workspace",
path: "/organization/members/invite",
summary: "Invite members to the organization",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Bind invites to the middleware-scoped organization.

Line 14 authenticates an organization in context, but the handler still sends the client-provided input.organizationId to auth.api.createInvitation. Use context.organization.id instead, and validate input.teamId belongs to that same organization if it remains accepted.

Proposed fix
-  .handler(async ({ input, errors }) => {
+  .handler(async ({ context, input, errors }) => {
     console.log("[inviteMember]:", { input });

     try {
       await auth.api.createInvitation({
         body: {
           email: input.email,
           role: input.role,
-          organizationId: input.organizationId,
+          // Trust the organization resolved by middleware instead of a client-controlled ID.
+          organizationId: context.organization.id,
           resend: input.resend,
           teamId: input.teamId ?? undefined,
         },
🤖 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/router/members.ts` around lines 12 - 20, The invite flow in inviteMember
is still trusting client-supplied organizationId instead of the organization
established by requireOrganizationMiddleware. Update the handler to pass
context.organization.id into auth.api.createInvitation, and if input.teamId is
still accepted, verify that it belongs to the same organization before creating
the invitation. Keep the fix localized to inviteMember and the organization/team
lookup logic so the middleware-scoped organization is always the source of
truth.

Comment on lines 107 to +108
const slug = createSlug(input.name);
console.log("Creating workspace with slug:", slug);
console.log("Creating organization with slug:", slug);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove raw organization identifiers from console logs.

Organization slugs/ids are tenant metadata and can leak customer context into production logs. Prefer structured, redacted logging if this needs observability.

Proposed cleanup
-    console.log("Creating organization with slug:", slug);
@@
-    console.log("Left organization", organizationId);

Also applies to: 574-574

🤖 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/router/organization.ts` around lines 107 - 108, The create-organization
flow is logging raw tenant metadata via the console statement in the
organization router, which can leak customer context. Remove the direct
console.log from the slug creation path and replace it with structured, redacted
logging only if needed, using the createSlug and organization creation logic as
the place to update. Also apply the same cleanup to the other referenced log
site in this router.

Comment on lines +199 to 209
export const listOrganizationMembers = base
.use(requireAuthMiddleware)
.use(requireworkspaceMiddleware)
.use(requireOrganizationMiddleware)
.use(standardsecurityMiddleware)
.route({
method: "GET",
path: "/workspace/:workspaceId/members",
summary: "List all members of a workspace",
tags: ["Workspace"],
path: "/organization/:organizationId/members",
summary: "List all members of a organization",
tags: ["Organization"],
})
.input(z.void())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Validate the route organization before returning members.

The route declares :organizationId, but the handler ignores it and returns context.organization members. A stale organization URL can render the wrong member list instead of being rejected.

Proposed fix
-  .input(z.void())
+  .input(
+    z.object({
+      organizationId: z.string(),
+    })
+  )
@@
-  .handler(async ({ context, errors }) => {
+  .handler(async ({ context, input, errors }) => {
+    // Reject stale route params so this endpoint cannot return members for the wrong organization page.
+    if (input.organizationId !== context.organization.id) {
+      throw errors.BAD_REQUEST({
+        message: "You recently switched organization",
+      });
+    }
+
     let organizationmembers;

Also applies to: 223-229

🤖 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/router/organization.ts` around lines 199 - 209, The organization members
routes are using the `:organizationId` path param but the handler is still
relying on `context.organization`, which can return members for the wrong
organization. Update `listOrganizationMembers` (and the related route noted in
the comment) to explicitly validate that the requested `organizationId` matches
the loaded organization in `requireOrganizationMiddleware`, and reject
mismatches before returning any members. Use the existing `organizationId` param
and `context.organization` comparison in the route handler so stale URLs cannot
fetch the wrong organization’s members.

Comment thread app/router/team.ts
Comment on lines 149 to 153
.handler(async ({ context, input }) => {
const channels = await prisma.team.findMany({
const teams = await prisma.team.findMany({
where: {
organizationId: input.organizationId,
teammembers: {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Reject organization mismatches in team listing.

requireOrganizationMiddleware authorizes the active organization, but the query uses caller-provided input.organizationId. Keep the query bound to context.organization.id.

Proposed fix
-  .handler(async ({ context, input }) => {
+  .handler(async ({ context, input, errors }) => {
+    // Keep route state aligned with the authenticated organization context.
+    if (input.organizationId !== context.organization.id) {
+      throw errors.BAD_REQUEST({
+        message: "You recently switched organization",
+      });
+    }
+
     const teams = await prisma.team.findMany({
       where: {
-        organizationId: input.organizationId,
+        organizationId: context.organization.id,
📝 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
.handler(async ({ context, input }) => {
const channels = await prisma.team.findMany({
const teams = await prisma.team.findMany({
where: {
organizationId: input.organizationId,
teammembers: {
.handler(async ({ context, input, errors }) => {
// Keep route state aligned with the authenticated organization context.
if (input.organizationId !== context.organization.id) {
throw errors.BAD_REQUEST({
message: "You recently switched organization",
});
}
const teams = await prisma.team.findMany({
where: {
organizationId: context.organization.id,
teammembers: {
🤖 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/router/team.ts` around lines 149 - 153, The team listing handler is still
querying with the caller-supplied organizationId instead of the authorized
active organization. Update the query in the team router handler to bind the
prisma.team.findMany filter to context.organization.id, keeping
requireOrganizationMiddleware as the source of truth and avoiding organization
mismatches.

Comment thread app/router/team.ts
Comment on lines 208 to 215
.handler(async ({ input, context, errors }) => {
console.log("[channel.get]: called for", input.channelId);
const channel = await prisma.team.findUnique({
where: { id: input.channelId },
console.log("[team.get]: called for", input.teamId);
const team = await prisma.team.findUnique({
where: { id: input.teamId },
select: {
name: true,
},
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Scope direct team reads to the active organization and membership.

getTeam and listTeamMembers accept any teamId; the Prisma reads are not constrained by context.organization.id, so arbitrary team IDs can expose team/member data.

Proposed fix pattern
-    const team = await prisma.team.findUnique({
-      where: { id: input.teamId },
+    const team = await prisma.team.findFirst({
+      where: {
+        id: input.teamId,
+        // Scope by active organization and membership so arbitrary team IDs cannot be read.
+        organizationId: context.organization.id,
+        teammembers: { some: { userId: context.user.id } },
+      },
       select: {
         name: true,
       },
@@
-        where: { teamId: input.teamId },
+        where: {
+          teamId: input.teamId,
+          team: {
+            // Ensure team member lists cannot be read outside the active organization.
+            organizationId: context.organization.id,
+            teammembers: { some: { userId: context.user.id } },
+          },
+        },

Also applies to: 382-392

🤖 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/router/team.ts` around lines 208 - 215, The team read endpoints are not
scoped to the active organization, so `getTeam` and `listTeamMembers` can return
data for arbitrary `teamId` values. Update the Prisma queries in `team.get` and
`listTeamMembers` to include `context.organization.id` in the lookup/filter, and
ensure membership checks also verify the requested team belongs to that
organization before returning results. Use the existing handler/context symbols
to keep the fix localized and consistent.

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