Skip to content

refactor: initial separation of useMetaInfo - #480

Open
tianlilei-c wants to merge 5 commits into
mainfrom
feat/migrate-orgs-to-tanstack-query-refactor/split-org-project-context
Open

refactor: initial separation of useMetaInfo#480
tianlilei-c wants to merge 5 commits into
mainfrom
feat/migrate-orgs-to-tanstack-query-refactor/split-org-project-context

Conversation

@tianlilei-c

@tianlilei-c tianlilei-c commented Jun 13, 2025

Copy link
Copy Markdown
Contributor

🏷️ Ticket

[link the issue or ticket you are addressing in this PR here, or use the Development
section on the right sidebar to link the issue]

📝 Description

  • Fully migrate organization fetch logic to TanStack Query, standardizing caching (staleTime), retry, and auto-refetch strategies
  • Introduce a standalone OrgProvider to manage orgs, activeOrg, selection, and reload operations
  • Introduce a standalone ProjectProvider that depends on activeOrg and uses existing useProjects to manage projects, activeProject, selection, and reload operations
  • Remove org/project responsibilities from the original MetaInfoProvider, replacing with new Providers and useMetaInfo for backward compatibility
  • Extract LoadingGuard component to handle initial loading skeletons and local cache hydration consistently

🎥 Demo (if applicable)

📸 Screenshots (if applicable)

✅ Checklist

  • I have signed the Contributor License Agreement (CLA) and read the contributing guide (required)
  • I have linked this PR to an issue or a ticket (required)
  • I have updated the documentation related to my change if needed
  • I have updated the tests accordingly (required for a bug fix or a new feature)
  • All checks on CI passed

Summary by CodeRabbit

  • New Features

    • Introduced a loading guard component that displays a loading state while data is being fetched.
    • Added new organizational and project context providers for improved management of organizations and projects within the app.
  • Refactor

    • Replaced the previous meta information provider with separate organization and project providers for better modularity and maintainability.
    • Updated app layout to incorporate new context providers for organizations and projects.

@vercel

vercel Bot commented Jun 13, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
aci-dev-portal ✅ Ready (Inspect) Visit Preview 💬 Add feedback Jun 25, 2025 10:59am

@coderabbitai

coderabbitai Bot commented Jun 13, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This change removes the centralized MetaInfoProvider context and replaces it with two new providers: OrgProvider and ProjectProvider, each managing their respective state and logic. The useMetaInfo hook is refactored to aggregate data from new hooks. A new LoadingGuard component is introduced for loading states.

Changes

File(s) Change Summary
frontend/src/app/layout.tsx Replaces MetaInfoProvider with nested OrgProvider and ProjectProvider in the layout component.
frontend/src/components/context/loading-guard.tsx Introduces new LoadingGuard component for displaying loading skeletons and handling loading state.
frontend/src/components/context/metainfo.tsx Removes MetaInfoProvider; refactors useMetaInfo to aggregate data from new hooks and contexts.
frontend/src/components/context/org-context.tsx Adds OrgProvider and useOrgInfo hook for managing and providing organization-related state/context.
frontend/src/components/context/project-context.tsx Adds ProjectProvider and useProjectInfo hook for managing and providing project-related state/context.

Sequence Diagram(s)

sequenceDiagram
    participant App
    participant RequiredAuthProvider
    participant OrgProvider
    participant ProjectProvider
    participant SidebarProvider
    participant Children

    App->>RequiredAuthProvider: Render
    RequiredAuthProvider->>OrgProvider: Provide org context
    OrgProvider->>ProjectProvider: Provide project context
    ProjectProvider->>SidebarProvider: Provide sidebar context
    SidebarProvider->>Children: Render app content
Loading

Possibly related PRs

Suggested reviewers

  • dev-aipolabs
  • alex-aipolabs
  • jiwei-aipolabs

Poem

In the garden of code, new roots are grown,
Old meta vines are overthrown.
Orgs and Projects now stand tall,
Each with their context, answering the call.
LoadingGuard hops in, with skeletons bright,
The rabbit cheers—contexts feel just right! 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 809c207 and 4ed624d.

📒 Files selected for processing (3)
  • frontend/src/components/context/metainfo.tsx (1 hunks)
  • frontend/src/components/context/org-context.tsx (1 hunks)
  • frontend/src/components/context/project-context.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • frontend/src/components/context/org-context.tsx
  • frontend/src/components/context/project-context.tsx
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Format, Lint, and Test
  • GitHub Check: Format & Lint
  • GitHub Check: Compose Tests
✨ 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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@tianlilei-c
tianlilei-c marked this pull request as draft June 13, 2025 12:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (5)
frontend/src/components/context/org-context.tsx (3)

17-22: Allow null in the setter signature

useState<OrgMemberInfoClass | null>() returns a setter that legally accepts null.
Typing the context setter as (o: OrgMemberInfoClass) => void forbids null and forces unnecessary casts downstream.

-type OrgCtx = {
+type OrgCtx = {
   orgs: OrgMemberInfoClass[];
   activeOrg: OrgMemberInfoClass | null;
-  setActiveOrg: (o: OrgMemberInfoClass) => void;
+  setActiveOrg: (o: OrgMemberInfoClass | null) => void;
   reloadOrgs: () => Promise<void>;
 };

31-44: fetchOrgs loop may hammer the auth endpoint

With a hard-coded while loop + setTimeout(800ms) you can hit the auth endpoint 5 × quickly on a cold start.
Consider leveraging React-Query’s built-in retry/backoff instead of rolling your own polling.


93-101: Minor rerender optimisation

value={{ orgs, activeOrg, setActiveOrg, reloadOrgs }} is recreated every render causing all consumers to re-render even when nothing changed.
Wrap it in useMemo (or at least useCallback for functions) to avoid unnecessary updates.

frontend/src/components/context/project-context.tsx (1)

18-23: Setter should accept null for symmetry with state

-setActiveProject: (p: Project) => void;
+setActiveProject: (p: Project | null) => void;
frontend/src/components/context/metainfo.tsx (1)

21-30: Redundant type assertions

userClass, activeOrg, activeProject, accessToken are already typed precisely.
The as casts hide potential nulls and may mask future refactors. Prefer narrowing earlier rather than casting here.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b0b3e5c and 809c207.

📒 Files selected for processing (5)
  • frontend/src/app/layout.tsx (2 hunks)
  • frontend/src/components/context/loading-guard.tsx (1 hunks)
  • frontend/src/components/context/metainfo.tsx (1 hunks)
  • frontend/src/components/context/org-context.tsx (1 hunks)
  • frontend/src/components/context/project-context.tsx (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
frontend/src/components/context/org-context.tsx (1)
frontend/src/components/context/loading-guard.tsx (1)
  • LoadingGuard (12-31)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: Format, Lint, and Test
  • GitHub Check: Format & Lint
  • GitHub Check: Compose Tests
🔇 Additional comments (3)
frontend/src/components/context/loading-guard.tsx (1)

12-31: LGTM – simple, focused component

The guard does exactly what it says on the tin and stays client-only. No functional issues spotted.

frontend/src/components/context/org-context.tsx (1)

71-88: Edge-case: zero organisations → infinite skeleton

LoadingGuard remains active while !activeOrg.
If the user legitimately belongs to zero orgs the UI never leaves the loading state.
Handle the “no organisations” path explicitly (e.g. redirect to onboarding or render an empty-state component).

frontend/src/components/context/project-context.tsx (1)

38-43: useProjects called with undefined orgId

When activeOrg is null the hook is invoked with undefined; if useProjects does not guard against this it may fire an unnecessary request or error.
Confirm the hook’s behaviour or gate the call behind !!activeOrg.

Comment on lines 102 to +117
<QueryClientProvider client={queryClient}>
<RequiredAuthProvider authUrl={process.env.NEXT_PUBLIC_AUTH_URL!}>
<MetaInfoProvider>
<SidebarProvider>
<AppSidebar />
<SidebarInset>
<main className="w-full h-full mr-2 border rounded-lg border-gray-400 border-opacity-30 bg-white">
<Header />
{children}
<Analytics />
</main>
</SidebarInset>
</SidebarProvider>
</MetaInfoProvider>
<OrgProvider>
<ProjectProvider>
<SidebarProvider>
<AppSidebar />
<SidebarInset>
<main className="w-full h-full mr-2 border rounded-lg border-gray-400 border-opacity-30 bg-white">
<Header />
{children}
<Analytics />
</main>
</SidebarInset>
</SidebarProvider>
</ProjectProvider>
</OrgProvider>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Provider order looks correct but be wary of “permanent skeleton” states

Because OrgProviderProjectProvider each use LoadingGuard, any failure / empty list will result in an infinite skeleton with no obvious error.
Consider adding an error boundary or a timeout–based fallback so the user is not left staring at a spinner forever if the API keeps returning 0 orgs / 0 projects.

🤖 Prompt for AI Agents
In frontend/src/app/layout.tsx around lines 102 to 117, the nested OrgProvider
and ProjectProvider both use LoadingGuard, which can cause an infinite loading
spinner if the API returns no organizations or projects. To fix this, wrap these
providers or their children with an error boundary component or implement a
timeout-based fallback UI that displays an error message or alternative content
after a certain loading duration, preventing the user from being stuck on a
permanent skeleton state.

Comment on lines +58 to +68
initialData: () => {
const cache = localStorage.getItem("orgs_cache");
if (cache) {
try {
return JSON.parse(cache) as OrgMemberInfoClass[];
} catch {
return [];
}
}
return [];
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

LocalStorage cache is not namespaced by user

"orgs_cache" is shared across all logged-in users on the same browser.
A second account will momentarily see another user’s organisation list until the query resolves – a potential data-leak.

-const cache = localStorage.getItem("orgs_cache");
+const cache = localStorage.getItem(`orgs_cache_${userClass?.userId ?? "anon"}`);

Remember to write with the same key in the later useEffect.

📝 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
initialData: () => {
const cache = localStorage.getItem("orgs_cache");
if (cache) {
try {
return JSON.parse(cache) as OrgMemberInfoClass[];
} catch {
return [];
}
}
return [];
},
initialData: () => {
const cache = localStorage.getItem(`orgs_cache_${userClass?.userId ?? "anon"}`);
if (cache) {
try {
return JSON.parse(cache) as OrgMemberInfoClass[];
} catch {
return [];
}
}
return [];
},
🤖 Prompt for AI Agents
In frontend/src/components/context/org-context.tsx around lines 58 to 68, the
localStorage key "orgs_cache" is not namespaced by user, causing potential data
leaks between users on the same browser. Fix this by including a unique user
identifier (e.g., user ID or username) in the localStorage key when reading the
cache in initialData and also update the corresponding useEffect that writes to
localStorage to use the same namespaced key. This ensures each user's data is
isolated in localStorage.

Comment on lines +66 to +81
return (
<LoadingGuard
isLoading={!activeOrg || !accessToken || isFetching || !activeProject}
>
<ProjectContext.Provider
value={{
projects,
activeProject,
setActiveProject,
reloadActiveProject,
}}
>
{children}
</ProjectContext.Provider>
</LoadingGuard>
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Same infinite-skeleton risk as OrgProvider

If an organisation has zero projects the guard never resolves.
Provide an explicit empty-state instead.

🤖 Prompt for AI Agents
In frontend/src/components/context/project-context.tsx around lines 66 to 81,
the LoadingGuard causes an infinite loading state if the active organization has
zero projects because the guard condition never resolves. To fix this, add an
explicit check for the projects array length and provide a clear empty-state UI
or message when there are no projects, so the LoadingGuard can resolve and avoid
the infinite skeleton loading.

Comment on lines +46 to +61
useEffect(() => {
if (!activeOrg || !projects.length) return;
const key = `activeProject_${activeOrg.orgId}`;
const saved = localStorage.getItem(key);
setActiveProject(projects.find((p) => p.id === saved) ?? projects[0]);
}, [projects, activeOrg]);

useEffect(() => {
if (activeOrg && activeProject) {
localStorage.setItem(
`activeProject_${activeOrg.orgId}`,
activeProject.id,
);
}
}, [activeProject, activeOrg]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

LocalStorage key should include userId as well

activeProject_${orgId} is unique per org but not per user.
A shared browser profile will leak the previously selected project between accounts.

-const key = `activeProject_${activeOrg.orgId}`;
+const key = `activeProject_${activeOrg.orgId}_${userClass?.userId ?? "anon"}`;

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In frontend/src/components/context/project-context.tsx around lines 46 to 61,
the localStorage key for activeProject uses only the orgId, which can cause
project selection to leak between users sharing the same browser. Modify the key
to include the userId along with orgId to ensure it is unique per user and
organization. Update both the getItem and setItem calls to use this combined key
format.

Comment on lines 14 to 18
if (!accessToken || !activeOrg || !activeProject || !userClass) {
throw new Error(
"useMetaInfo: Context not ready - ensure you're using it within the proper providers",
);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Throwing errors for “not ready” feels harsh

Many components will call useMetaInfo unconditionally and now risk crashing the tree until data hydrates.
Instead of throwing, consider returning {ready:false} or keep the old provider-level guard to avoid propagating error boundaries everywhere.

🤖 Prompt for AI Agents
In frontend/src/components/context/metainfo.tsx around lines 14 to 18, the
current code throws an error if required context values are missing, which can
cause the component tree to crash before data is ready. Instead of throwing an
error, modify the hook to return an object indicating readiness, such as {ready:
false}, when context is not fully available. This approach avoids crashing and
allows components to handle loading states gracefully without relying on error
boundaries.

@tianlilei-c
tianlilei-c marked this pull request as ready for review June 24, 2025 10:03
while (!orgs.length && retry < 5) {
await refreshAuthInfo();
await new Promise((r) => setTimeout(r, 1000));
orgs = userClass.getOrgs();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Inconsistent null checking when calling getOrgs(). Line 34 uses optional chaining (?.) while line 39 directly accesses the method. If userClass is null during retry attempts, this will cause a runtime error. Should use optional chaining consistently: orgs = userClass?.getOrgs();

📚 Relevant Docs


React with 👍 to tell me that this comment was useful, or 👎 if not (and I'll stop posting more comments like this in the future)

@recurseml

recurseml Bot commented Jul 14, 2025

Copy link
Copy Markdown

😱 Found 1 issue. Time to roll up your sleeves! 😱

Need help? Join our Discord for support!
https://discord.gg/qEjHQk64Z9

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