Skip to content

Conversation

@drifter089
Copy link
Owner

@drifter089 drifter089 commented Jan 10, 2026

Summary

Fixes a race condition in role creation that caused the role node to briefly show blank/default data after closing the dialog.

Changes

  • Reorder operations in onSuccess handler to update TanStack Query cache before updating the canvas node
  • This ensures RoleCard finds the new role by its real ID when it re-renders
  • Move cache invalidation to run last for background refresh

Summary by CodeRabbit

  • Bug Fixes
    • Improved data consistency when creating roles by optimizing cache updates and implementing background refresh invalidation.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link

vercel bot commented Jan 10, 2026

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

Project Deployment Review Updated (UTC)
org-os Ready Ready Preview, Comment Jan 10, 2026 4:04pm

@coderabbitai
Copy link

coderabbitai bot commented Jan 10, 2026

📝 Walkthrough

Walkthrough

This PR refactors optimistic cache handling in the role creation hook. Instead of immediately replacing temp roles with real roles in the cache, it now maps and swaps role IDs before render, then triggers cache invalidation after success to maintain background consistency.

Changes

Cohort / File(s) Summary
Optimistic Cache Update Logic
src/app/teams/[teamId]/hooks/use-create-role.tsx
Changed onMutate/onSuccess flow: replaces direct in-cache replacement with pre-render mapping that swaps tempRoleId with newRole; removes duplicate cache update block; adds cache invalidation for team and role data after success

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 With a hop and a flap of my ears so long,
This cache now sings a stronger song!
No duplicates here, just clean and bright,
Pre-render swaps make everything right! ✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: prevent blank flash when creating role in canvas' directly and specifically describes the main change - fixing a race condition that prevented a blank flash UI issue when creating roles.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings

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.

@claude
Copy link
Contributor

claude bot commented Jan 10, 2026

Code Review

Summary

This is a well-identified and correctly-fixed race condition. The fix properly aligns with the established cache management patterns in the codebase.

What This Fixes

The race condition occurred when:

  1. User creates a role → dialog closes
  2. Canvas updates node with newRole.id
  3. RoleCard re-renders and looks up newRole.id in TanStack Query cache
  4. Problem: Cache still had tempRoleId, causing a brief "not found" state → blank display

Code Quality ✅

Strengths:

  • Correct implementation: Follows the exact pattern documented in CLAUDE.md ("Cache Pipeline: Role Mutations")
  • Matches existing patterns: Aligns with use-optimistic-role-update.ts:173-181 which does the same cache-first, invalidate-later approach
  • Good comments: The inline comment clearly explains the "why" behind the ordering
  • No breaking changes: The reordering is logically sound and maintains all existing functionality

Architecture Consistency ✅

This change follows the established pattern from CLAUDE.md:

onSuccess:
  1. setData(updatedRole)  ← Use server response (prevents stale data)
  2. invalidate()          ← Background refresh

The PR correctly implements this for the create flow, matching the update flow in use-optimistic-role-update.ts.

Performance Considerations ✅

  • No performance regression: The operations are just reordered, not added
  • Invalidation is now void-called: Properly non-blocking for background refresh
  • Cache coherence improved: Reduces unnecessary re-renders from the race condition

Potential Issues / Suggestions

Minor: Comment Clarity
The comment on line 198 could be more explicit about what happens without this ordering:

// Update cache BEFORE node so RoleCard finds newRole.id on re-render
// Without this ordering: node updates → RoleCard re-renders → cache still has tempRoleId → blank flash

Question: Test Coverage
Given this is a timing-sensitive race condition:

  • Are there E2E tests that verify role creation doesn't show blank states?
  • Consider adding a Playwright test that:
    1. Creates a role from canvas
    2. Takes screenshot immediately after dialog closes
    3. Asserts role card displays correct title/color

Security ✅

No security concerns. This is purely a client-side cache management change.

Bugs ✅

No new bugs introduced. The fix resolves the race condition without side effects.


Verdict: LGTM

This is a textbook fix for a cache synchronization race condition. The change is minimal, well-reasoned, and follows established patterns in the codebase. The only suggestion is to enhance the comment for future maintainers, but this is optional.

Recommend merging as-is.

@drifter089 drifter089 merged commit a4eb0db into main Jan 10, 2026
4 of 5 checks passed
Copy link

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

🤖 Fix all issues with AI agents
In @src/app/teams/[teamId]/hooks/use-create-role.tsx:
- Around line 226-228: Add dashboard cache invalidation after the existing
team/role invalidations in use-create-role (where utils.team.getById.invalidate
and utils.role.getByTeamId.invalidate are called): call
utils.dashboard.getDashboardCharts.invalidate({ teamId }) so dashboard charts
refresh when a role is created with a metricId, placing it immediately after the
two existing invalidate calls to match other mutations like use-auto-save.tsx
and use-optimistic-role-update.ts.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7dd0aad and e248ee7.

📒 Files selected for processing (1)
  • src/app/teams/[teamId]/hooks/use-create-role.tsx
🧰 Additional context used
📓 Path-based instructions (6)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (GEMINI.md)

Use TypeScript 5.9 with strict type checking for all frontend and backend code

Files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
src/**/*.tsx

📄 CodeRabbit inference engine (GEMINI.md)

Prefer Server Components for initial data fetching; use Client Components ('use client') only for interactivity

Files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
src/**/*/*.tsx

📄 CodeRabbit inference engine (GEMINI.md)

Client Components must use import { api } from '@/trpc/react' for standard HTTP/Hooks wrapper

Files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
**/*.tsx

📄 CodeRabbit inference engine (GEMINI.md)

Use Tailwind CSS 4 for styling with shadcn/ui and Radix UI primitive components

Files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
src/app/**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

src/app/**/*.tsx: Use the dual tRPC API pattern: direct calls in Server Components (api.team.getById) for 10x faster performance, and React hooks in Client Components (api.team.getById.useQuery)
Use getUserDisplayName(userId, members) utility (client-side sync) from @/lib/helpers/get-user-name for displaying user names in components

Files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use @trivago/prettier-plugin-sort-imports with inline type imports for import organization

Files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T21:27:04.176Z
Learning: Applies to src/app/teams/[teamId]/_components/**/*.tsx : Implement cache-first node pattern for role nodes: store only roleId in node data, fetch display data from TanStack Query cache using useRoleData hook
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T12:52:42.935Z
Learning: Applies to src/hooks/use-optimistic-role-update.ts : The `use-optimistic-role-update` hook provides shared logic for all role mutations with proper cache layer handling for TanStack Query and Prisma Accelerate. Use this hook in canvas-specific wrappers to add markDirty side effects.
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-12-20T22:12:00.576Z
Learning: Applies to src/app/teams/[teamId]/**/*.tsx : React Flow nodes must store minimal data (e.g., just `roleId`); fetch full Role data from TanStack Query cache in the Node component to keep canvas and sidebars in sync
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T12:52:42.935Z
Learning: Applies to src/app/teams/[teamId]/**/*.{ts,tsx} : Cache-first node system: Role nodes store only `roleId` in node.data. Display data must be fetched from TanStack Query cache using the `useRoleData()` hook which queries the `role.getByTeamId` procedure.
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T21:27:04.176Z
Learning: Applies to src/hooks/**/*.ts : For role-metric cache updates, optimistically update both role cache (role.getByTeamId) and dashboard cache (dashboard.getDashboardCharts) to maintain UI consistency
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T12:52:42.935Z
Learning: Applies to src/app/**/*role*mutation*.{ts,tsx} : For role mutations, use cache pipeline with two operations: (1) onMutate for optimistic update, (2) onSuccess with both setData(updatedRole) using server response AND invalidate() for background refresh. Always use server response data in setData before invalidate to ensure Prisma Accelerate cache consistency.
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T12:52:42.935Z
Learning: Applies to src/app/dashboard/[teamId]/**/*.{ts,tsx} : Dashboard cache updates for role-metric assignments must update both `role.getByTeamId` and `dashboard.getDashboardCharts` caches during mutations. Use onMutate for optimistic updates on both caches, then invalidate both on success.
📚 Learning: 2025-12-29T12:52:42.935Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T12:52:42.935Z
Learning: Applies to src/hooks/use-optimistic-role-update.ts : The `use-optimistic-role-update` hook provides shared logic for all role mutations with proper cache layer handling for TanStack Query and Prisma Accelerate. Use this hook in canvas-specific wrappers to add markDirty side effects.

Applied to files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
📚 Learning: 2025-12-29T21:27:04.176Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T21:27:04.176Z
Learning: Applies to src/app/teams/[teamId]/_components/**/*.tsx : Implement cache-first node pattern for role nodes: store only roleId in node data, fetch display data from TanStack Query cache using useRoleData hook

Applied to files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
📚 Learning: 2025-12-29T12:52:42.935Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T12:52:42.935Z
Learning: Applies to src/app/**/*role*mutation*.{ts,tsx} : For role mutations, use cache pipeline with two operations: (1) onMutate for optimistic update, (2) onSuccess with both setData(updatedRole) using server response AND invalidate() for background refresh. Always use server response data in setData before invalidate to ensure Prisma Accelerate cache consistency.

Applied to files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
📚 Learning: 2025-12-29T21:27:04.176Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T21:27:04.176Z
Learning: Applies to src/hooks/**/*.ts : For role-metric cache updates, optimistically update both role cache (role.getByTeamId) and dashboard cache (dashboard.getDashboardCharts) to maintain UI consistency

Applied to files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
📚 Learning: 2025-12-29T12:52:42.935Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T12:52:42.935Z
Learning: Applies to src/app/dashboard/[teamId]/**/*.{ts,tsx} : Dashboard cache updates for role-metric assignments must update both `role.getByTeamId` and `dashboard.getDashboardCharts` caches during mutations. Use onMutate for optimistic updates on both caches, then invalidate both on success.

Applied to files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
📚 Learning: 2025-12-29T12:52:42.935Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T12:52:42.935Z
Learning: Applies to src/app/teams/[teamId]/**/*.{ts,tsx} : Cache-first node system: Role nodes store only `roleId` in node.data. Display data must be fetched from TanStack Query cache using the `useRoleData()` hook which queries the `role.getByTeamId` procedure.

Applied to files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
📚 Learning: 2025-12-20T22:12:00.576Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-12-20T22:12:00.576Z
Learning: Applies to src/app/teams/[teamId]/**/*.tsx : React Flow nodes must store minimal data (e.g., just `roleId`); fetch full Role data from TanStack Query cache in the Node component to keep canvas and sidebars in sync

Applied to files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
📚 Learning: 2025-12-20T22:12:00.576Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: GEMINI.md:0-0
Timestamp: 2025-12-20T22:12:00.576Z
Learning: Applies to src/app/teams/[teamId]/**/*.tsx : Avoid modifying `enrichNodesWithRoleData` flow without understanding the complete canvas serialization logic for saving/loading React Flow nodes to the database

Applied to files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
📚 Learning: 2025-12-29T21:27:04.176Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T21:27:04.176Z
Learning: Applies to src/hooks/**/*.ts : Use optimistic updates with setData before invalidate in TanStack Query mutations to ensure correct data when Prisma Accelerate cache hasn't propagated

Applied to files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
📚 Learning: 2025-12-29T12:52:42.935Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T12:52:42.935Z
Learning: Applies to src/app/teams/[teamId]/_components/role-node.tsx,src/app/teams/[teamId]/_components/public-role-node.tsx : These role node components are 75% identical and should be consolidated. Extract shared `RoleNodeTemplate` component with `isEditable` prop to DRY up the code.

Applied to files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
📚 Learning: 2025-12-29T21:27:04.176Z
Learnt from: CR
Repo: drifter089/orgOS PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-29T21:27:04.176Z
Learning: Applies to src/server/api/**/*.ts : Use enrichRolesWithUserNames() for flat role arrays and enrichChartRolesWithUserNames() for nested chart roles from src/server/api/utils/organization-members.ts

Applied to files:

  • src/app/teams/[teamId]/hooks/use-create-role.tsx
🔇 Additional comments (1)
src/app/teams/[teamId]/hooks/use-create-role.tsx (1)

198-224: LGTM! The reordering correctly fixes the race condition.

Updating the cache before the node ensures RoleCard (which reads from the TanStack Query cache via useRoleData) finds newRole by its real ID when it re-renders. This aligns with the cache-first node pattern documented in the learnings.

Comment on lines +226 to +228
// Invalidate caches for background refresh
void utils.team.getById.invalidate({ id: teamId });
void utils.role.getByTeamId.invalidate({ teamId });
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/app/teams/[teamId]/hooks/use-create-role.tsx | head -250 | tail -50

Repository: drifter089/orgOS

Length of output: 1968


🏁 Script executed:

# Check the full CreateRoleVariables type definition
rg -n "type CreateRoleVariables|interface CreateRoleVariables" -A 10 src/app/teams/[teamId]/hooks/use-create-role.tsx

Repository: drifter089/orgOS

Length of output: 322


🏁 Script executed:

# Search for invalidateDashboardCache utility and its usage
rg -n "invalidateDashboardCache" --type=ts --type=tsx -B 2 -A 2

Repository: drifter089/orgOS

Length of output: 87


🏁 Script executed:

# Look for other role-metric mutation patterns
rg -n "metricId" --type=ts --type=tsx src/app/teams/ -B 2 -A 2 | head -100

Repository: drifter089/orgOS

Length of output: 87


🏁 Script executed:

# Search for invalidateDashboardCache without strict file type
rg -n "invalidateDashboardCache" -B 2 -A 2

Repository: drifter089/orgOS

Length of output: 8673


🏁 Script executed:

# Search for dashboard.getDashboardCharts invalidation
rg -n "dashboard.getDashboardCharts.invalidate" -B 2 -A 2

Repository: drifter089/orgOS

Length of output: 5361


🏁 Script executed:

# Check other role mutation hooks
fd -e ts -e tsx "use-.*role" src/app/teams/ | head -20

Repository: drifter089/orgOS

Length of output: 259


🏁 Script executed:

# Look for metric assignment patterns in mutations
rg -n "metricId" src/app/teams/[teamId]/hooks/ -B 3 -A 3

Repository: drifter089/orgOS

Length of output: 9557


Add dashboard cache invalidation to maintain UI consistency with role creation.

The cache invalidation is missing utils.dashboard.getDashboardCharts.invalidate({ teamId }). This is required to ensure dashboard charts reflect metric assignments when a role is created with a metricId. Other role mutations in the codebase follow this pattern (see use-auto-save.tsx, use-optimistic-role-update.ts, and role-card.tsx).

Add the following line after the existing invalidations:

void utils.dashboard.getDashboardCharts.invalidate({ teamId });
🤖 Prompt for AI Agents
In @src/app/teams/[teamId]/hooks/use-create-role.tsx around lines 226 - 228, Add
dashboard cache invalidation after the existing team/role invalidations in
use-create-role (where utils.team.getById.invalidate and
utils.role.getByTeamId.invalidate are called): call
utils.dashboard.getDashboardCharts.invalidate({ teamId }) so dashboard charts
refresh when a role is created with a metricId, placing it immediately after the
two existing invalidate calls to match other mutations like use-auto-save.tsx
and use-optimistic-role-update.ts.

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.

2 participants