Skip to content

Sidebar performance improvements#95

Merged
gml-pz merged 8 commits into
mainfrom
perf/sidebar
Dec 4, 2025
Merged

Sidebar performance improvements#95
gml-pz merged 8 commits into
mainfrom
perf/sidebar

Conversation

@marinofranz

@marinofranz marinofranz commented Dec 4, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

Release Notes

  • New Features

    • Added beta indicators to experimental features (Policies and Compliance widgets).
  • Improvements

    • Optimized workspace navigation with dynamic routing for faster, more reliable access.
    • Consolidated settings configuration fetching for improved performance.
    • Enhanced caching mechanisms for better responsiveness.
  • Chores

    • Updated package version to 2.1.6beta10.

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

@coderabbitai

coderabbitai Bot commented Dec 4, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

This PR consolidates workspace configuration fetching by creating a unified /api/workspace/[id]/settings/general/configuration endpoint, converts getThumbnail from async to synchronous, refactors the sidebar to use dynamic workspace URLs, removes the duplicate /api/workspace/[id]/index.ts route, and adds beta badge UI for widgets.

Changes

Cohort / File(s) Summary
Sidebar & UI Components
components/sidebar.tsx
Replaced hard-coded workspace href templates with dynamic routes using workspace.groupId, updated icon selection logic to compare current route with page.href, changed URL construction from placeholder replacement to direct URL push, consolidated feature flag fetching via single /api/workspace/{groupId}/settings/general/configuration call, removed multiple granular fetch effects in favor of a single effect deriving all flags from the new config response.
New Consolidated Settings Endpoint
pages/api/workspace/[id]/settings/general/configuration.ts
New GET-only API route that fetches six configuration sections in parallel (guides, allies, sessions, notices, leaderboard, policies) via getConfig and aggregates them into a single response object, wrapping the handler with withSessionRoute for session enforcement.
Core @me API Endpoint
pages/api/@me.ts
Introduced periodic cache cleanup for in-memory user cache, parallelized user fetches via Promise.all for dbuser, username, displayname, reworked roles/workspaces retrieval with parallel Promise.all per-role fetches and fallback error handling, changed data type casing from groupthumbnail/groupname to groupThumbnail/groupName, added async cache persistence via setImmediate block.
Workspace API Consolidation
pages/api/workspace/[id].ts
Parallelized workspace validation and data fetches via Promise.all, introduced batch configuration fetch consolidating six config sections, added group logo retrieval for groupThumbnail, expanded permissions mapping with "manage_policies", updated response to derive settings flags from newly fetched config blocks, removed the previously separate /api/workspace/[id]/index.ts route (functionality consolidated here).
getThumbnail Function Change
utils/userinfoEngine.ts
Changed getThumbnail from async function returning Promise<string> to synchronous function returning string.
API Endpoints Removing await on getThumbnail
pages/api/activity/session.ts, pages/api/auth/checkOwner.ts, pages/api/auth/login.ts, pages/api/setupworkspace.ts, pages/api/workspace/[id]/profile/[uid].ts, pages/api/workspace/[id]/staff/search/[username].ts
Removed await when calling getThumbnail(...), changing picture/avatar/thumbnail fields from resolved strings to direct function return values.
Server-Side Props Thumbnail Updates
pages/workspace/[id]/alliances/index.tsx, pages/workspace/[id]/alliances/manage/[aid].tsx, pages/workspace/[id]/profile/[uid].tsx, pages/workspace/[id]/views.tsx
Removed await on getThumbnail(...) calls in getServerSideProps data assembly, affecting thumbnail/picture/avatar field assignments.
Permissions Manager & Utilities
utils/permissionsManager.ts
Removed debug logging of user data and roles in withPermissionCheck, replaced thumbnail retrieval with direct getThumbnail(member.userId) calls without awaiting or intermediate variable storage.
Widget Beta Badges
pages/workspace/[id]/index.tsx
Added optional beta?: boolean field to WidgetConfig interface, marked Policies and Compliance widgets as beta by setting beta: true and removing "(BETA)" suffix from descriptions, updated rendering to conditionally display a BETA badge when flag is set.
Version Bump
package.json
Updated version from 2.1.6beta7 to 2.1.6beta10.

Sequence Diagram

sequenceDiagram
    participant Sidebar as Sidebar Component
    participant Router as Next Router
    participant NewAPI as /api/workspace/[id]/<br/>settings/general/config
    participant OldAPIs as /api/workspace/<br/>[id]/(multiple)
    
    rect rgb(240, 248, 255)
    Note over Sidebar,OldAPIs: OLD BEHAVIOR (Before PR)
    Sidebar->>OldAPIs: fetch guides config
    OldAPIs-->>Sidebar: guides
    Sidebar->>OldAPIs: fetch allies config
    OldAPIs-->>Sidebar: allies
    Sidebar->>OldAPIs: fetch sessions config
    OldAPIs-->>Sidebar: sessions
    Sidebar->>OldAPIs: fetch notices config
    OldAPIs-->>Sidebar: notices
    Sidebar->>OldAPIs: fetch leaderboard config
    OldAPIs-->>Sidebar: leaderboard
    Sidebar->>OldAPIs: fetch policies config
    OldAPIs-->>Sidebar: policies
    Sidebar->>Sidebar: Render with flags
    end
    
    rect rgb(240, 255, 240)
    Note over Sidebar,NewAPI: NEW BEHAVIOR (After PR)
    Sidebar->>Router: Compute workspace groupId
    Sidebar->>NewAPI: fetch all configs in parallel<br/>(guides, allies, sessions,<br/>notices, leaderboard, policies)
    par Parallel Config Fetches
        NewAPI->>NewAPI: getConfig(guides)
        NewAPI->>NewAPI: getConfig(allies)
        NewAPI->>NewAPI: getConfig(sessions)
        NewAPI->>NewAPI: getConfig(notices)
        NewAPI->>NewAPI: getConfig(leaderboard)
        NewAPI->>NewAPI: getConfig(policies)
    end
    NewAPI-->>Sidebar: { guides, allies, sessions,<br/>notices, leaderboard, policies }
    Sidebar->>Sidebar: Render with consolidated flags
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • getThumbnail sync conversion impact: Review all 8+ files where getThumbnail calls now return values without awaiting—verify that type expectations align and no downstream code expects Promises to be resolved
  • Sidebar refactoring logic: Verify URL construction with dynamic workspace.groupId is correct, icon selection comparison logic with router.pathname, and new effect dependency tracking
  • Consolidated settings API correctness: Ensure /api/workspace/[id]/settings/general/configuration correctly parallelize fetches and properly aggregate all six config sections
  • Type casing changes: Confirm groupthumbnail/groupnamegroupThumbnail/groupName changes propagate correctly through response payloads and aren't breaking consumers
  • Route consolidation verification: Ensure /api/workspace/[id].ts now handles all responsibilities previously in the deleted /api/workspace/[id]/index.ts and that permission mappings are complete
  • Cache cleanup in @me endpoint: Verify the periodic cache eviction logic and async persistence via setImmediate don't introduce race conditions

Possibly related PRs

  • General Bug Fixes & Live Servers #89: Modifies sidebar feature-flag fetching and rendering with similar patterns for gating feature items via configuration, directly related to the consolidated settings endpoint approach in this PR.

Suggested reviewers

  • gml-pz
  • brennanpeters

Poem

🐰 Hop, hop—the thumbnails sync align,
Settings consolidated, so divine!
One endpoint fetches all the flags so true,
Beta badges gleam in polish new.
The sidebar dances with dynamic grace—
Efficiency blooms in this refactored space!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The pull request makes extensive changes across 18 files including sidebar refactoring, API consolidation, and function signature changes. While sidebar performance is improved through component optimization and consolidated API calls, the changes extend far beyond sidebar-specific improvements to include significant refactoring of authentication endpoints, workspace configuration handling, and core utility functions like getThumbnail, making the title overly narrow. Revise title to reflect the broader scope: consider 'Refactor API architecture and optimize sidebar performance' or 'Consolidate workspace configuration fetching and improve sidebar performance'.
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch perf/sidebar

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

@gml-pz
gml-pz self-requested a review December 4, 2025 01:29
@gml-pz gml-pz added the enhancement New feature or request label Dec 4, 2025

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

Caution

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

⚠️ Outside diff range comments (3)
pages/api/activity/session.ts (1)

72-76: The [id] placeholder issue affects database storage here.

The picture field is set to getThumbnail(userid) and then stored in the database via upsert (line 76). This will persist the literal [id] placeholder in the user's picture field.

components/sidebar.tsx (1)

333-341: Dead code: .replace("[id]", ...) is no longer needed.

Since page.href now contains the actual workspace ID (e.g., /workspace/123/wall), the .replace("[id]", workspace.groupId.toString()) calls are redundant and will have no effect.

-router.asPath === page.href.replace("[id]", workspace.groupId.toString())
+router.asPath === page.href

This applies to both line 333 and line 341.

pages/api/@me.ts (1)

23-27: Type definition does not match returned data structure.

The Data type defines groupthumbnail and groupname (lowercase), but the actual return at lines 89-93 uses groupThumbnail and groupName (camelCase). This mismatch will cause TypeScript to not catch property access errors in consuming code.

 workspaces?: { 
   groupId: number
-  groupthumbnail: string
-  groupname: string
+  groupThumbnail: string
+  groupName: string
 }[]
🧹 Nitpick comments (6)
pages/workspace/[id]/index.tsx (1)

264-269: Beta badge rendering implemented correctly.

The conditional rendering of the beta badge is well-implemented with appropriate styling. The badge will only display when widgetConfig.beta is true.

Minor: Consider consistent indentation.

Lines 265-269 use tabs while the rest of the file uses spaces. Consider using consistent indentation throughout.

-						</div>
+            </div>
pages/api/workspace/[id]/staff/search/[username].ts (1)

32-37: The async and Promise.all are now redundant.

Since getThumbnail is now synchronous (returns a string directly), the async callback and Promise.all wrapper are unnecessary overhead. Consider simplifying:

-const infoUsers = await Promise.all(users.map(async (user: any) => {
-  return {
-    username: user.username,
-    thumbnail: getThumbnail(user.userid)
-  }
-}))
+const infoUsers = users.map((user: any) => ({
+  username: user.username,
+  thumbnail: getThumbnail(user.userid)
+}))
pages/workspace/[id]/alliances/manage/[aid].tsx (1)

75-83: The async callback is now redundant.

Since getThumbnail is synchronous, the async keyword in the map callback serves no purpose. The same applies to lines 102-111 and 130-140.

-const infoUsers: any = await Promise.all(
-  users.map(async (user: any) => {
-    return {
-      ...user,
-      userid: Number(user.userid),
-      thumbnail: getThumbnail(user.userid),
-    };
-  })
-);
+const infoUsers: any = users.map((user: any) => ({
+  ...user,
+  userid: Number(user.userid),
+  thumbnail: getThumbnail(user.userid),
+}));
pages/workspace/[id]/alliances/index.tsx (1)

71-79: Redundant async wrapper can be simplified.

Same as other files - the async and Promise.all are unnecessary for synchronous getThumbnail.

pages/api/workspace/[id]/settings/general/configuration.ts (1)

20-44: Add error handling for robustness.

The endpoint lacks try-catch error handling. If getConfig or parseInt fails, the response will be a 500 without useful context.

 async function handler(
   req: NextApiRequest,
   res: NextApiResponse<Data>
 ) {
 	if (req.method !== "GET") return res.status(405).json({ success: false, error: "Method not allowed" });
 	if(!req.session.userid) return res.status(401).json({ success: false, error: "Not logged in" });

+	const groupId = parseInt(req.query.id as string);
+	if (isNaN(groupId)) return res.status(400).json({ success: false, error: "Invalid workspace ID" });
+
+	try {
 	const configuration = await Promise.all([
-		getConfig("guides", parseInt(req.query.id as string)),
-		getConfig("allies", parseInt(req.query.id as string)),
-		getConfig("sessions", parseInt(req.query.id as string)),
-		getConfig("notices", parseInt(req.query.id as string)),
-		getConfig("leaderboard", parseInt(req.query.id as string)),
-		getConfig("policies", parseInt(req.query.id as string)),
+		getConfig("guides", groupId),
+		getConfig("allies", groupId),
+		getConfig("sessions", groupId),
+		getConfig("notices", groupId),
+		getConfig("leaderboard", groupId),
+		getConfig("policies", groupId),
 	])

 	const keys = ["guides", "allies", "sessions", "notices", "leaderboard", "policies"];
 	return res.status(200).json({ 
 		success: true, 
 		value: configuration.reduce((acc, curr, index) => {
 			acc[keys[index]] = curr;
 			return acc;
 		}, {} as Record<string, any>) 
 	})
+	} catch (error) {
+		console.error("Error fetching configuration:", error);
+		return res.status(500).json({ success: false, error: "Failed to fetch configuration" });
+	}
 }
pages/api/@me.ts (1)

34-41: Consider cleanup for hot-reload scenarios.

In development with hot module replacement, this interval can accumulate across reloads. Consider adding a guard or cleanup mechanism:

+let cleanupInterval: NodeJS.Timeout | null = null;
+if (!cleanupInterval) {
-setInterval(() => {
+  cleanupInterval = setInterval(() => {
    const now = Date.now();
    for (const [key, value] of userCache.entries()) {
      if (now - value.timestamp > CACHE_DURATION) {
        userCache.delete(key);
      }
    }
  }, 60000);
+}

Alternatively, for serverless deployments, this pattern may not be necessary since the in-memory cache lifetime is limited anyway.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 02c2d4d and 29c7dbf.

📒 Files selected for processing (19)
  • components/sidebar.tsx (2 hunks)
  • package.json (1 hunks)
  • pages/api/@me.ts (3 hunks)
  • pages/api/activity/session.ts (1 hunks)
  • pages/api/auth/checkOwner.ts (0 hunks)
  • pages/api/auth/login.ts (1 hunks)
  • pages/api/setupworkspace.ts (1 hunks)
  • pages/api/workspace/[id].ts (2 hunks)
  • pages/api/workspace/[id]/index.ts (0 hunks)
  • pages/api/workspace/[id]/profile/[uid].ts (1 hunks)
  • pages/api/workspace/[id]/settings/general/configuration.ts (1 hunks)
  • pages/api/workspace/[id]/staff/search/[username].ts (1 hunks)
  • pages/workspace/[id]/alliances/index.tsx (2 hunks)
  • pages/workspace/[id]/alliances/manage/[aid].tsx (3 hunks)
  • pages/workspace/[id]/index.tsx (3 hunks)
  • pages/workspace/[id]/profile/[uid].tsx (1 hunks)
  • pages/workspace/[id]/views.tsx (1 hunks)
  • utils/permissionsManager.ts (1 hunks)
  • utils/userinfoEngine.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • pages/api/auth/checkOwner.ts
  • pages/api/workspace/[id]/index.ts
🧰 Additional context used
🧬 Code graph analysis (13)
pages/api/setupworkspace.ts (1)
utils/userinfoEngine.ts (1)
  • getThumbnail (19-21)
pages/workspace/[id]/profile/[uid].tsx (1)
utils/userinfoEngine.ts (1)
  • getThumbnail (19-21)
utils/permissionsManager.ts (1)
utils/userinfoEngine.ts (1)
  • getThumbnail (19-21)
pages/api/auth/login.ts (1)
utils/userinfoEngine.ts (1)
  • getThumbnail (19-21)
pages/api/workspace/[id]/staff/search/[username].ts (1)
utils/userinfoEngine.ts (1)
  • getThumbnail (19-21)
pages/api/activity/session.ts (1)
utils/userinfoEngine.ts (1)
  • getThumbnail (19-21)
pages/api/workspace/[id].ts (1)
utils/configEngine.ts (1)
  • getConfig (7-24)
pages/workspace/[id]/alliances/manage/[aid].tsx (1)
utils/userinfoEngine.ts (1)
  • getThumbnail (19-21)
pages/api/workspace/[id]/settings/general/configuration.ts (2)
utils/configEngine.ts (1)
  • getConfig (7-24)
lib/withSession.ts (1)
  • withSessionRoute (41-47)
pages/workspace/[id]/alliances/index.tsx (1)
utils/userinfoEngine.ts (1)
  • getThumbnail (19-21)
pages/workspace/[id]/views.tsx (1)
utils/userinfoEngine.ts (1)
  • getThumbnail (19-21)
pages/api/workspace/[id]/profile/[uid].ts (1)
utils/userinfoEngine.ts (1)
  • getThumbnail (19-21)
pages/api/@me.ts (3)
utils/userinfoEngine.ts (3)
  • getUsername (8-17)
  • getDisplayName (23-32)
  • getThumbnail (19-21)
utils/database.ts (2)
  • user (14-14)
  • role (14-14)
types/index.d.ts (1)
  • User (13-18)
🔇 Additional comments (12)
package.json (1)

3-3: LGTM! Version bump looks appropriate.

The version increment from 2.1.6beta7 to 2.1.6beta10 aligns with the beta release cadence for this PR's changes.

pages/workspace/[id]/index.tsx (2)

44-44: LGTM! Beta flag properly added to interface.

The optional beta?: boolean field is correctly typed and will support the new beta badge feature.


87-101: Beta badge implementation looks good.

The policies and compliance widgets are appropriately marked as beta, and the descriptions have been cleaned up (removed "(BETA)" suffix). The implementation aligns with the PR objectives.

pages/api/setupworkspace.ts (1)

297-303: LGTM with the same caveat about thumbnail URL.

The change aligns with the synchronous getThumbnail refactor. The returned URL contains a literal [id] placeholder which should be resolved on the client side or by the routing layer.

pages/workspace/[id]/alliances/manage/[aid].tsx (1)

102-111: Consider simplifying the nested async pattern.

The infoReps mapping still uses await getUsername() which requires async, but you could separate the sync and async operations for clarity. However, the current implementation is functional.

pages/workspace/[id]/alliances/index.tsx (1)

89-107: LGTM - async is still needed here.

The getUsername call on line 96 is async, so Promise.all and async are correctly retained in this mapping.

pages/api/workspace/[id].ts (2)

61-108: Good optimization with parallel data fetching.

The Promise.all batch fetch significantly improves performance by parallelizing multiple database and API calls. This aligns well with the PR's performance improvement goals.


134-151: LGTM - Settings response structure is clean.

The response properly aggregates all fetched config values with sensible defaults using optional chaining and nullish coalescing.

components/sidebar.tsx (1)

128-163: Good refactor to dynamic workspace URLs.

The hardcoded [id] placeholders are replaced with actual workspace.groupId values, making the URLs directly usable without runtime replacement.

pages/workspace/[id]/views.tsx (1)

320-320: No action needed. The getThumbnail function correctly returns /api/workspace/[id]/avatar/${userId}. The avatar API route at pages/api/workspace/[id]/avatar/[userid]/index.ts only uses the [userid] parameter and ignores the [id] placeholder, which is the intended design. The literal [id] string in the URL is not problematic; Next.js routes it to the correct handler, which successfully serves avatars based on the userId. This pattern is used consistently throughout the codebase and functions as designed.

Likely an incorrect or invalid review comment.

pages/api/@me.ts (2)

61-68: LGTM!

Good use of Promise.all to parallelize independent async operations, improving response latency.


83-95: Good error handling for external API calls.

The per-role error handling with .catch() fallbacks prevents a single failed group lookup from failing the entire request. The parallel fetching is well-structured.

Comment thread components/sidebar.tsx
Comment thread pages/api/@me.ts
Comment on lines +109 to 111
picture: getThumbnail(userId),
username: await getUsername(userId),
registered: true

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 | 🟠 Major

Redundant getUsername call; reuse the existing variable.

username was already fetched at line 66 and is still in scope. Calling getUsername(userId) again is wasteful.

 data: {
   picture: getThumbnail(userId),
-  username: await getUsername(userId),
+  username,
   registered: true
 }
📝 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
picture: getThumbnail(userId),
username: await getUsername(userId),
registered: true
picture: getThumbnail(userId),
username,
registered: true
🤖 Prompt for AI Agents
In pages/api/@me.ts around lines 109 to 111, the code calls await
getUsername(userId) again even though username was already fetched at line 66
and remains in scope; replace that redundant call with the existing username
variable (ensure the variable name is correct and in scope) to avoid an
unnecessary async call and duplicate work.

Comment thread pages/api/@me.ts
Comment thread pages/api/auth/login.ts
Comment thread pages/api/workspace/[id].ts
).length;

const avatar = await getThumbnail(user.userid);
const avatar = getThumbnail(user.userid);

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 | 🔴 Critical

The [id] placeholder issue affects the API response.

The avatar field (line 243) will contain the literal [id] placeholder. However, this route has access to the actual workspace ID via workspaceGroupId (line 17), so the URL could be corrected here.

Consider replacing the placeholder:

-    const avatar = getThumbnail(user.userid);
+    const avatar = `/api/workspace/${workspaceGroupId}/avatar/${user.userid}`;
📝 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 avatar = getThumbnail(user.userid);
const avatar = `/api/workspace/${workspaceGroupId}/avatar/${user.userid}`;
🤖 Prompt for AI Agents
In pages/api/workspace/[id]/profile/[uid].ts around line 199,
getThumbnail(user.userid) is producing an avatar URL that still contains the
literal `[id]` placeholder; use the actual workspace ID available as
workspaceGroupId (declared around line 17) when building the thumbnail URL.
Replace the call to getThumbnail so it receives or constructs a URL that
substitutes workspaceGroupId for `[id]` (e.g., pass workspaceGroupId into
getThumbnail or build the corrected path before calling) so the returned avatar
contains the real workspace ID instead of the placeholder.

Comment thread pages/workspace/[id]/profile/[uid].tsx
Comment thread utils/permissionsManager.ts
Comment thread utils/userinfoEngine.ts

@gml-pz gml-pz 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.

lgtm

@gml-pz
gml-pz merged commit dea598e into main Dec 4, 2025
4 checks passed
@gml-pz
gml-pz deleted the perf/sidebar branch December 4, 2025 01:33
@coderabbitai coderabbitai Bot mentioned this pull request Dec 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants