Skip to content

Dashboard redesign#1029

Merged
N2D4 merged 27 commits intodevfrom
dashboard-redesign
Nov 28, 2025
Merged

Dashboard redesign#1029
N2D4 merged 27 commits intodevfrom
dashboard-redesign

Conversation

@Developing-Gamer
Copy link
Copy Markdown
Contributor

@Developing-Gamer Developing-Gamer commented Nov 25, 2025

Summary by CodeRabbit

  • New Features

    • Configurable dashboard widgets with persistent layout, time-range controls, new chart types (donut/filtered ranges), enhanced globe visuals, and improved list views.
    • Redesigned App Store with searchable/category tabs, hero app pages, compact app list, warning modal for alpha/beta apps, and progress/checklist UI with animated completion.
    • New copyable inline text component and streamlined project switcher.
  • Documentation

    • Published Dashboard Design Guide covering patterns, theming, and accessibility.
  • Accessibility

    • Keyboard/ARIA improvements for interactive lists and checklist rows.

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


Note

Major dashboard overhaul: widget-based metrics with globe and time ranges, new collapsible sidebar with resizable companion drawer, revamped app explorer/store and settings, plus a comprehensive design guide and UI utilities.

  • Dashboard UX/UI:
    • Metrics Page: Widgetized layout (globe, total users overlay, quick app access, DAU and sign-ups with tabbed chart/list views, time-range toggle), responsive grid, and local config loading.
    • Charts: New ChartCard, TimeRangeToggle, TabbedMetricsCard; improved tooltips, weekend styling, and revamped auth methods donut.
    • Globe: Performance-friendly loading, dynamic sizing/borders, tooltips, theme-aware coloring, and error recovery.
    • Layout: New PageLayout header/actions; collapsible left sidebar with tooltips and polished nav; theme toggle styling.
    • Companion: Resizable right-side drawer (split-screen/overlay) with Docs, Feature Requests, Changelog, Support; shared context hook.
  • Apps:
    • Explorer: Search, category tabs with counts, installed-first sorting, improved grid.
    • Store Entry: Hero, screenshots preview, feature highlights, stage warnings.
    • App Tiles: New glowing AppIcon, enable/disable with alpha/beta warning modal, installed badges.
  • Settings:
    • Project settings refinements (copyable JWKS/ID, team transfer UX, production mode handling); new SettingCopyableText and memoized setting components.
  • Core/Utils:
    • Link click handling (passes event), use-wait-for-idle hook, phosphor icons for app frontend, threejs typings.
  • Docs:
    • Added apps/dashboard/DESIGN_GUIDE.md; updated AGENTS.md (hover-exit transition guidance).
  • Deps:
    • Added @phosphor-icons/react, three/@types/three; minor dependency/version updates.

Written by Cursor Bugbot for commit bc1ed3f. This will update automatically on new commits. Configure here.

@vercel
Copy link
Copy Markdown

vercel Bot commented Nov 25, 2025

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

Project Deployment Preview Comments Updated (UTC)
stack-backend Ready Ready Preview Comment Nov 28, 2025 9:17pm
stack-dashboard Ready Ready Preview Comment Nov 28, 2025 9:17pm
stack-demo Ready Ready Preview Comment Nov 28, 2025 9:17pm
stack-docs Ready Ready Preview Comment Nov 28, 2025 9:17pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Nov 25, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Adds a widget-driven, persistent metrics dashboard with time-range-aware charts and an idle-gated Suspense globe; large UI rewrites across apps management, sidebar/drawer, settings, shared app icon/ui, new hooks, components, types, and runtime dependencies (three, phosphor-icons).

Changes

Cohort / File(s) Summary
Design & Docs
apps/dashboard/DESIGN_GUIDE.md, AGENTS.md
New Dashboard Design Guide and minor AGENTS.md text addition.
Metrics & Overview
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/{metrics-page.tsx,charts-section-with-data.tsx,line-chart.tsx}
Adds widget-configurable dashboard (persisted), TimeRange concept, new chart primitives (ChartCard, TimeRangeToggle, TabbedMetricsCard, DonutChartDisplay), and overview/layout/data routing updates.
Globe Visualization
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx, apps/dashboard/src/hooks/use-wait-for-idle.tsx
Idle-gated globe rendering with Suspense fallback, camera/size math, tooltip/selection state, dynamic border visuals, and new useWaitForIdle hook.
Apps UI & Frontend
apps/dashboard/src/components/{app-square.tsx,app-store-entry.tsx,app-warning-modal.tsx}, apps/dashboard/src/lib/apps-frontend.tsx, packages/stack-shared/src/apps/apps-ui.tsx
Redesigned AppSquare/AppStoreEntry, new AppWarningModal, AppIcon enabled prop + glow filter, AppId type added, icon set migrated to phosphor-icons.
Apps Pages
apps/dashboard/src/app/.../apps/{page-client.tsx,[appId]/page-client.tsx}
Tabbed/category-filtered apps explorer with search and counts; safer enable flow with frontend existence guard and alert-wrapped async enable.
Sidebar, Layout & Dev UX
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/{sidebar-layout.tsx,page-layout.tsx,layout.tsx}
Collapsed/expanded sidebar refactor (firstItemHref, AppNavItem, isCollapsed), mobile Sheet integration, PageLayout spacing changes, and dev-only script/port display tweaks.
Settings & Small Components
apps/dashboard/src/components/{settings.tsx,copyable-text.tsx,project-switcher.tsx,stack-companion.tsx,theme-toggle.tsx,link.tsx}
Memoized/refactored settings primitives (SettingCopyableText), new CopyableText, simplified ProjectSwitcher, StackCompanion rewritten to drawer/split-screen with useStackCompanion(), Link props/onClick update, theme-toggle styling tweak.
Project Settings
apps/dashboard/src/app/.../project-settings/page-client.tsx
Copyable fields, TeamMemberItem, memoized handlers, and success toast on project transfer; various handler refactors.
Vercel / Onboarding
apps/dashboard/src/app/.../vercel/page-client.tsx
Onboarding converted to step cards with checklist rows, per-item statuses, animated progress/confetti, keyboard-accessible checklist rows, and async key generation via alert wrapper.
Shared & Template
packages/template/src/components/{credential-sign-up.tsx,user-button.tsx}
Added explicit React event typing; adjusted UserButton trigger/layout classes.
Deps & Hooks
apps/dashboard/package.json, apps/dashboard/src/hooks/use-wait-for-idle.tsx
Adds @phosphor-icons/react, three, @types/three; introduces useWaitForIdle hook.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant User
  participant MetricsPage
  participant LocalStorage
  participant MetricsContent
  participant Widget
  participant MetricsAPI

  User->>MetricsPage: open metrics page
  MetricsPage->>LocalStorage: loadConfig(STORAGE_KEY)
  LocalStorage-->>MetricsPage: dashboardConfig
  MetricsPage->>MetricsContent: render(dashboardConfig, timeRange)
  MetricsContent->>Widget: mount (per enabled widget)
  Widget->>MetricsAPI: fetch data (timeRange)
  MetricsAPI-->>Widget: return data
  Widget-->>MetricsContent: render visuals
  User->>Widget: interact (change timeRange / click)
  Widget->>MetricsAPI: additional requests or navigation
  MetricsContent->>LocalStorage: saveConfig(updatedLayout)
  LocalStorage-->>MetricsContent: ack
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Potential hotspots to inspect:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/globe.tsx — 3D sizing, camera math, requestIdleCallback/Suspense, tooltip anchoring.
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/{metrics-page.tsx,charts-section-with-data.tsx,line-chart.tsx} — widget persistence/merge, TimeRange propagation, new chart primitives and BRAND_CONFIG map.
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx & apps/dashboard/src/components/stack-companion.tsx — collapsed navigation, drag-to-resize, context/hook behavior, mobile Sheet integration.
  • apps/dashboard/src/components/settings.tsx & FormSettingCard — memoization, typing, async submit/reset flows.
  • apps/dashboard/src/components/app-square.tsx & app-store-entry.tsx & packages/stack-shared/src/apps/apps-ui.tsx — enable/disable flows, AppIcon enabled behavior, iconset migration.

Possibly related PRs

Suggested reviewers

  • N2D4
  • BilalG1

Poem

🐇
I hopped through pixels, chewed some code,
Widgets planted down the road.
A globe that waits while charts align,
Sidebars fold and icons shine.
Hop in — the dashboard's set to show!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.46% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Dashboard redesign' accurately reflects the main change—a comprehensive redesign of the dashboard with new widgets, layout, and components.
Description check ✅ Passed The PR description is comprehensive and well-structured, detailing all major changes across dashboard UX/UI, charts, globe, layout, apps, settings, and dependencies.
✨ 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 dashboard-redesign

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.

@N2D4 N2D4 closed this Nov 25, 2025
@N2D4 N2D4 deleted the dashboard-redesign branch November 25, 2025 18:20
@N2D4 N2D4 restored the dashboard-redesign branch November 25, 2025 18:25
@N2D4 N2D4 reopened this Nov 26, 2025
@Developing-Gamer Developing-Gamer marked this pull request as ready for review November 26, 2025 21:50
Copilot AI review requested due to automatic review settings November 26, 2025 21:50
@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Nov 26, 2025

Claude encountered an error —— View job


I'll analyze this and get back to you.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR implements a comprehensive dashboard redesign with significant UI/UX improvements across the entire application. The changes include new components, layout redesigns, performance optimizations, and a new design guide to ensure consistency.

Key Changes:

  • Complete redesign of the dashboard layout with collapsible sidebar and improved navigation
  • New component patterns with React.memo for performance optimization
  • Enhanced dark mode support with theme-aware colors throughout
  • New interactive elements including app warning modals, copyable text components, and redesigned app cards
  • Major refactor of metrics/analytics pages with new chart components and improved data visualization

Reviewed changes

Copilot reviewed 26 out of 26 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
DESIGN_GUIDE.md New comprehensive design guide documenting patterns, colors, typography, and dark mode requirements
user-button.tsx Removed unused imports (DropdownMenuLabel, DropdownMenuSeparator, SunMoon) and color mode toggle feature
credential-sign-up.tsx Added React import and explicit type annotations for event handlers
apps-ui.tsx Complete redesign of AppIcon component with simplified styling and enabled state
apps-config.ts Added shortName field to all apps for compact display
theme-toggle.tsx Updated button styling to use ghost variant and consistent hover states
stack-companion.tsx Complete redesign as floating drawer with improved UX and touch support
settings.tsx Major refactor with React.memo optimization and improved component structure
project-switcher.tsx Redesigned with compact display and removed SelectValue import
link.tsx Updated onClick signature to receive MouseEvent parameter
copyable-text.tsx New component for displaying copyable text with visual feedback
app-warning-modal.tsx New modal component for alpha/beta app warnings
app-store-entry.tsx Complete redesign with hero section, feature grid, and improved layout
app-square.tsx Major refactor with new variants, hover states, and modal integration
vercel/page-client.tsx Complete redesign with collapsible steps, progress tracking, and confetti animation
sidebar-layout.tsx Major layout overhaul with collapsible sidebar, tooltips, and improved navigation
project-settings/page-client.tsx Performance optimization with useCallback/useMemo and new SettingCopyableText component
page-layout.tsx Updated with improved typography and responsive spacing
auth-methods/providers.tsx Changed Link to anchor tag for external documentation
apps/page-client.tsx Complete redesign with search, categories, and grid layout
apps/[appId]/page-client.tsx Updated error handling and removed wait function
metrics-page.tsx Major redesign with widget system, flexible layout, and globe integration
line-chart.tsx Complete refactor with tabbed views, user lists, and time range support
globe.tsx Redesigned with improved positioning, tooltips, and auto-rotate
charts-section-with-data.tsx Updated layout and improved card designs
Comments suppressed due to low confidence (1)

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx:1

  • Using as any bypasses TypeScript's type safety. This should be properly typed to catch potential type errors at compile time.
'use client';

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread apps/dashboard/src/components/settings.tsx
Comment thread apps/dashboard/src/components/stack-companion.tsx
Comment thread apps/dashboard/src/components/stack-companion.tsx Outdated
Copy link
Copy Markdown
Contributor

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

Caution

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

⚠️ Outside diff range comments (2)
packages/template/src/components/user-button.tsx (1)

83-131: colorModeToggle prop is unused and not rendered in the menu; consumers pass it but it has no effect

Confirmed: UserButtonProps exposes colorModeToggle at line 24, but the prop is never used in UserButtonInnerInner's render logic (lines 83–131). Despite this, multiple call sites actively pass the prop:

  • examples/demo/src/components/header.tsx:33
  • apps/dashboard/src/components/navbar.tsx:26
  • examples/docs-examples/src/app/components/user-button/page.tsx:9

This means the prop exists in the public API, consumers provide callbacks, but those callbacks are never invoked since no menu item renders them. Either:

  • Wire colorModeToggle back into the menu as a conditional Item (safer, non-breaking), or
  • Remove the prop from UserButtonProps and all call sites (breaking change, but cleaner).
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (1)

35-40: Remove unused globeWindowSize and sectionContainerSize variables; globeWindowRef is actually used at line 290.

The review comment conflates two different variables. globeWindowRef is correctly assigned to a div at line 290, so it should not be removed. However, globeWindowSize (defined at line 36) is genuinely unused and can be safely removed. Additionally, sectionContainerSize (defined at line 40) is also unused and should be removed.

🧹 Nitpick comments (27)
packages/template/src/components/user-button.tsx (1)

72-83: Ensure visible focus styling when using outline-none on the trigger

Adding outline-none on DropdownMenuTrigger risks removing the native focus indicator for keyboard users unless the underlying component or other classes add a custom :focus-visible style (e.g., focus-visible:ring-*). Please confirm that a visible focus state is still present, and if not, add one to keep the trigger keyboard‑accessible.

Example adjustment (if needed):

-      <DropdownMenuTrigger className="outline-none stack-scope rounded-lg hover:bg-muted/50 transition-colors p-1 -m-1">
+      <DropdownMenuTrigger className="outline-none stack-scope rounded-lg hover:bg-muted/50 transition-colors p-1 -m-1 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2">
apps/dashboard/src/components/project-switcher.tsx (1)

30-35: Add consistent fallback for undefined currentProject.

When currentProject is undefined, line 31 shows an empty avatar (first letter of empty string) while line 33 renders nothing. This creates an inconsistent UI state.

Consider either hiding the entire block or showing a fallback message like "Select a project".

Apply this diff:

       <div className="flex items-center gap-1">
-        <ProjectAvatar displayName={currentProject?.displayName || ""} />
-        <span className="truncate text-sm font-medium">
-          {currentProject?.displayName}
-        </span>
+        {currentProject ? (
+          <>
+            <ProjectAvatar displayName={currentProject.displayName} />
+            <span className="truncate text-sm font-medium">
+              {currentProject.displayName}
+            </span>
+          </>
+        ) : (
+          <span className="truncate text-sm font-medium text-muted-foreground">
+            Select a project
+          </span>
+        )}
       </div>
packages/template/src/components/credential-sign-up.tsx (2)

8-8: React import for event typing is correct; consider type-only import (optional).

Importing React to use React.ChangeEvent is fine and should typecheck cleanly. If you want to keep the value import surface minimal, you could instead use a type-only import:

-import React, { useState } from "react";
+import React, { useState, type ChangeEvent } from "react";

and then switch the handlers to ChangeEvent<HTMLInputElement> (see next comment). This is purely an optional cleanliness tweak.


73-77: Explicit ChangeEvent typing on password fields looks good; small optional cleanups.

The explicit React.ChangeEvent<HTMLInputElement> annotations on both PasswordInput handlers are correct and align with how registerPassword.onChange / registerPasswordRepeat.onChange are typically typed. Using runAsynchronously around these calls also matches the repo’s async-handling pattern (and avoids voiding promises), which is consistent with prior learnings. Based on learnings, this is the intended pattern.

If you’d like, you could make two small optional refinements:

  1. Use the imported type alias instead of the namespace-qualified type:
-onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+onChange={(e: ChangeEvent<HTMLInputElement>) => {
  1. Since the two handlers are nearly identical, you could factor out a tiny helper to reduce duplication, but that’s non-essential given the current size.

Also applies to: 87-91

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx (3)

6-6: Consider also importing runAsynchronouslyWithAlert for async button handlers.

Based on learnings, runAsynchronouslyWithAlert should be used for async button click handlers to automatically handle errors and show alerts to users. This will be needed for handleGenerateKeys.

-import { runAsynchronously } from "@stackframe/stack-shared/dist/utils/promises";
+import { runAsynchronously, runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";

309-312: Unused itemId parameter.

The itemId parameter is declared but never used in the function body.

-  const handleItemClick = (stepId: StepId, itemId: string) => {
-    // Toggle the step completion when clicking on an item
-    toggleStepCompletion(stepId);
+  const handleItemClick = (stepId: StepId, _itemId: string) => {
+    toggleStepCompletion(stepId);
   };

518-528: Overly restrictive type annotation on keyboard event.

The inline type annotation (e: { key: string, preventDefault: () => void }) loses React's full keyboard event type. Consider using React.KeyboardEvent or letting TypeScript infer the type.

-        onKeyDown={(e: { key: string, preventDefault: () => void }) => {
+        onKeyDown={(e) => {
           if (e.key === "Enter" || e.key === " ") {
             e.preventDefault();
             props.onToggle();
           }
         }}
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx (1)

178-186: Switching to a plain <a> for external docs link looks good

Using a regular anchor for the external documentation URL avoids router-specific behaviors while keeping the existing styling and target behavior; this is appropriate here. As an optional hardening step, you could extend rel to "noopener noreferrer" to also suppress referrer leakage, but the current setup is already safe from window.opener issues.

apps/dashboard/src/components/app-warning-modal.tsx (1)

7-13: Consider async support for onConfirm.

The onConfirm callback is typed as () => void, but enabling an app may involve async operations (API calls). If callers pass async functions, they won't be awaited, and the modal will close immediately without waiting for completion or handling errors.

Consider:

  1. Updating the type to () => void | Promise<void>
  2. Adding loading state while confirmation is in progress
  3. Using runAsynchronouslyWithAlert for error handling (as per coding guidelines)

Based on learnings, use runAsynchronouslyWithAlert for async operations instead of manual try/catch blocks.

Apply this pattern:

+import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";

 type AppWarningModalProps = {
   isOpen: boolean,
   onClose: () => void,
-  onConfirm: () => void,
+  onConfirm: () => void | Promise<void>,
   appName: string,
   stage: "alpha" | "beta",
 };

Then update the button handler:

+  const [isConfirming, setIsConfirming] = useState(false);
+
   // ... in the Enable Anyway button:
   <button
-    onClick={() => {
-      onConfirm();
-      onClose();
+    onClick={() => runAsynchronouslyWithAlert(async () => {
+      setIsConfirming(true);
+      try {
+        await onConfirm();
+        onClose();
+      } finally {
+        setIsConfirming(false);
+      }
     })}
+    disabled={isConfirming}
apps/dashboard/src/components/app-store-entry.tsx (2)

100-106: Consider explicit error handling for async button action.

The onEnable prop is async (() => Promise<void>), and while the Button component may support async callbacks with built-in loading state, it's best practice to explicitly wrap async handlers with runAsynchronouslyWithAlert for consistent error handling across the dashboard.

Based on learnings, use runAsynchronouslyWithAlert for async button handlers instead of relying on implicit behavior.

+import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";

 <Button
-  onClick={onEnable}
+  onClick={() => runAsynchronouslyWithAlert(onEnable)}
   size="lg"
   className="px-8 bg-blue-600 hover:bg-blue-700 dark:bg-blue-500 dark:hover:bg-blue-600 text-white font-medium shadow-lg shadow-blue-500/20"
 >
   Enable App
 </Button>

23-28: Consider making features configurable per app.

The features array is currently hardcoded with generic items. As the comment on line 23 suggests, this could be customized per app in the future for more relevant feature highlights.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx (1)

33-33: Redundant async wrapper.

The async keyword is unnecessary here since runAsynchronouslyWithAlert already handles the promise. The current code works correctly, but can be simplified.

-        onEnable={async () => runAsynchronouslyWithAlert(handleEnable())}
+        onEnable={() => runAsynchronouslyWithAlert(handleEnable())}
apps/dashboard/src/components/copyable-text.tsx (1)

22-29: Avoid try-catch with console.error for silent failures.

Per coding guidelines, avoid .catch(console.error) or similar patterns. The clipboard write failure is silently logged without user feedback. Consider using runAsynchronouslyWithAlert or providing visual feedback on failure.

+import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
+
 const handleCopy = useCallback(async () => {
-  try {
-    await navigator.clipboard.writeText(props.value);
-    setCopied(true);
-  } catch (err) {
-    console.error("Failed to copy:", err);
-  }
+  await navigator.clipboard.writeText(props.value);
+  setCopied(true);
 }, [props.value]);

Then update the Button's onClick to:

-onClick={handleCopy}
+onClick={() => runAsynchronouslyWithAlert(handleCopy())}

Based on coding guidelines: "never use try-catch-all... use runAsynchronouslyWithAlert for error handling."

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx (3)

21-21: Add proper typing for member parameter.

Using any loses type safety. Consider defining or importing the proper team member type.

-function TeamMemberItem({ member }: { member: any }) {
+function TeamMemberItem({ member }: { member: { id: string; teamProfile: { displayName?: string; profileImageUrl?: string | null } } }) {

138-141: Remove unnecessary async from synchronous callback.

handleTeamSwitcherChange only calls setSelectedTeamId, which is synchronous. The async keyword is unnecessary.

-const handleTeamSwitcherChange = useCallback(async (team: any) => {
+const handleTeamSwitcherChange = useCallback((team: { id: string }) => {
   setSelectedTeamId(team.id);
 }, []);

230-245: Inconsistent memoization for logo change handlers.

The handlers for logoUrl and fullLogoUrl (lines 125-131) are memoized with useCallback, but the handlers for logoDarkModeUrl and logoFullDarkModeUrl are inline arrow functions. Consider memoizing all logo handlers for consistency.

+const handleLogoDarkModeChange = useCallback(async (logoDarkModeUrl: string | null) => {
+  await project.update({ logoDarkModeUrl });
+}, [project]);
+
+const handleLogoFullDarkModeChange = useCallback(async (logoFullDarkModeUrl: string | null) => {
+  await project.update({ logoFullDarkModeUrl });
+}, [project]);

Then use these in the onValueChange props.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (2)

172-172: Remove empty style object.

The empty style={{}} prop has no effect and can be removed.

-<div
-  className='relative w-full h-full'
-  style={{}}
-  onMouseEnter={() => {
+<div
+  className='relative w-full h-full'
+  onMouseEnter={() => {

45-61: Consider extracting magic numbers as named constants.

The camera distance calculation uses several magic numbers (484, 0.35, 355, 360). Consider extracting these as named constants for better maintainability and self-documentation.

+const GLOBE_MIN_WIDTH = 350;
+const GLOBE_ZOOM_THRESHOLD = 355;
+const ZOOM_BASE = 484;
+const ZOOM_SLOPE = 0.35;
+const ZOOM_DEFAULT = 360;
+
 const canvasWidth = globeContainerSize?.width ?? 0;
-const GLOBE_MIN_WIDTH = 350;
 const shouldShowGlobe = canvasWidth >= GLOBE_MIN_WIDTH;

-const cameraDistance = canvasWidth >= 355 
-  ? 484 - 0.35 * canvasWidth 
-  : 360;
+const cameraDistance = canvasWidth >= GLOBE_ZOOM_THRESHOLD 
+  ? ZOOM_BASE - ZOOM_SLOPE * canvasWidth 
+  : ZOOM_DEFAULT;
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx (2)

55-57: Use installedAppsSet for O(1) lookup instead of Array.includes().

You've already created installedAppsSet for O(1) lookups at line 44, but here you're using installedApps.includes(appId) which is O(n).

     // Apply category filter
     if (selectedCategory === "installed") {
-      apps = apps.filter(appId => installedApps.includes(appId));
+      apps = apps.filter(appId => installedAppsSet.has(appId));
     } else if (selectedCategory !== "all") {

88-102: Consider memoizing getCategoryCount to avoid redundant recalculations.

This function is called for each category tab on every render. While the computation isn't expensive for a small number of apps, wrapping it with useMemo or useCallback would be more consistent with the optimization approach already applied elsewhere in this component.

-  // Get count for each category
-  const getCategoryCount = (categoryId: string) => {
+  // Get count for each category - memoized to prevent recalculation
+  const getCategoryCount = useCallback((categoryId: string) => {
     if (categoryId === "installed") return installedApps.length;
     if (categoryId === "all") return Object.keys(ALL_APPS).filter(appId =>
       process.env.NODE_ENV === "development" || ALL_APPS[appId as AppId].stage !== "alpha"
     ).length;

     const category = CATEGORIES.find(c => c.id === categoryId);
     if (!category) return 0;

     return (Object.entries(ALL_APPS) as [AppId, typeof ALL_APPS[AppId]][]).filter(([appId, app]) => {
       if (process.env.NODE_ENV !== "development" && app.stage === "alpha") return false;
       return app.tags.some((tag: string) => category.tags.includes(tag));
     }).length;
-  };
+  }, [installedApps.length]);
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (3)

80-88: Unused saveConfig function with TODO comment.

This function is defined but never called. The TODO suggests it will be used when widget configuration UI is implemented. Consider removing it until it's needed, or if you want to keep it, ensure it's actually wired up.

Would you like me to help implement the widget configuration UI, or should this function be removed for now to avoid dead code?


122-129: Improve type safety in calculateTotalUsers.

The reduce callback uses any for both parameters. Consider adding proper types to avoid potential runtime issues.

     const calculateTotalUsers = () => {
       if (timeRange === 'all') {
         return data.total_users || 0;
       }
       const dailyUsers = data.daily_users || [];
       const filteredData = timeRange === '7d' ? dailyUsers.slice(-7) : dailyUsers.slice(-30);
-      return filteredData.reduce((sum: any, point: { activity: any }) => sum + point.activity, 0);
+      return filteredData.reduce((sum: number, point: { activity: number }) => sum + point.activity, 0);
     };

393-400: Hardcoded gap value assumes 16px root font size.

The calculation uses 20 for gap-5 (1.25rem), which assumes a 16px root font size. While this is typically correct, consider using a CSS variable or calculating this dynamically for robustness.

apps/dashboard/src/components/app-square.tsx (1)

84-98: Consider using runAsynchronouslyWithAlert instead of try-catch with manual alert.

Per coding guidelines, runAsynchronouslyWithAlert is preferred for async operations that need error handling. This would simplify the error handling and ensure consistent behavior.

+import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";

   const performToggle = async () => {
     setIsProcessing(true);
-
-    try {
-      await project.updateConfig({
-        [`apps.installed.${appId}.enabled`]: !isEnabled,
-      });
-      onToggleEnabled?.(!isEnabled);
-    } catch (error) {
-      console.error(`Failed to ${isEnabled ? 'disable' : 'enable'} app:`, error);
-      alert(`Failed to ${isEnabled ? 'disable' : 'enable'} ${app.displayName}. Please try again.`);
-    } finally {
-      setIsProcessing(false);
-    }
+    await runAsynchronouslyWithAlert(async () => {
+      await project.updateConfig({
+        [`apps.installed.${appId}.enabled`]: !isEnabled,
+      });
+      onToggleEnabled?.(!isEnabled);
+    });
+    setIsProcessing(false);
   };

Note: If you need to preserve the setIsProcessing(false) call even on error, you may need to adjust the pattern slightly. Based on learnings, runAsynchronouslyWithAlert is the preferred approach for async button handlers.

apps/dashboard/src/components/stack-companion.tsx (3)

170-173: Unreachable code path.

This branch can never execute. The guard at line 121 (if (!activeItem) return;) prevents isResizing from being set to true when there's no active item, so handleMouseUp will never run with !activeItem being true.

Consider removing this dead code:

        } else {
-         // Keep current width but ensure item is active
-         if (!activeItem) {
-            // If dragged open from closed state without clicking specific item, default to docs
-            setActiveItem('docs'); 
-         }
+         // Keep current width, item is guaranteed to be active
        }

183-184: Consider adding preventDefault() for touch drag.

The touchmove listener uses { passive: false } but doesn't call e.preventDefault(). Without it, the page may still scroll on touch devices during drawer resize, causing a jarring UX.

    const handleMouseMove = (e: MouseEvent | TouchEvent) => {
+     if ('touches' in e) {
+       e.preventDefault(); // Prevent page scroll during resize
+     }
      const clientX = 'touches' in e ? e.touches[0].clientX : e.clientX;

218-221: Consider removing empty comment and unnecessary fragment.

The backdrop comment block is empty, and the outer fragment (<>...</>) is unnecessary since there's only one child element (the main container div).

  return (
-   <>
-     {/* Backdrop - transparent but blocks interaction with content if dragging? 
-         User asked for "work along side it", so NO backdrop pointer-events.
-         We only use it for closing on outside click if we really wanted, but requested behavior implies modeless.
-     */}
-     
-     {/* Main Container - Fixed Right Edge, Flex Reverse to push handle left */}
-     <div className="fixed inset-y-0 right-0 z-50 flex flex-row-reverse items-center pointer-events-none">
+   <div className="fixed inset-y-0 right-0 z-50 flex flex-row-reverse items-center pointer-events-none">
      ...
-     </div>
-   </>
+   </div>
  );
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a6bf1e8 and 4f94451.

📒 Files selected for processing (26)
  • apps/dashboard/DESIGN_GUIDE.md (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx (3 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (4 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx (10 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (7 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx (5 hunks)
  • apps/dashboard/src/components/app-square.tsx (2 hunks)
  • apps/dashboard/src/components/app-store-entry.tsx (2 hunks)
  • apps/dashboard/src/components/app-warning-modal.tsx (1 hunks)
  • apps/dashboard/src/components/copyable-text.tsx (1 hunks)
  • apps/dashboard/src/components/link.tsx (2 hunks)
  • apps/dashboard/src/components/project-switcher.tsx (2 hunks)
  • apps/dashboard/src/components/settings.tsx (5 hunks)
  • apps/dashboard/src/components/stack-companion.tsx (1 hunks)
  • apps/dashboard/src/components/theme-toggle.tsx (1 hunks)
  • apps/dashboard/src/lib/apps-frontend.tsx (3 hunks)
  • packages/stack-shared/src/apps/apps-config.ts (5 hunks)
  • packages/stack-shared/src/apps/apps-ui.tsx (3 hunks)
  • packages/template/src/components/credential-sign-up.tsx (3 hunks)
  • packages/template/src/components/user-button.tsx (3 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Ensure code passes pnpm typecheck for TypeScript type validation
Never use toast for blocking alerts and errors; use alerts instead as they are more visible to users
Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI
Never use try-catch-all, never void a promise, never use .catch(console.error) or similar; use runAsynchronously or runAsynchronouslyWithAlert for error handling instead of try-catch blocks
Use loading indicators instead of try-catch blocks for asynchronous UI operations; button components like support async callbacks with built-in loading state
Use ES6 maps instead of records wherever possible

Files:

  • packages/template/src/components/credential-sign-up.tsx
  • apps/dashboard/src/components/app-store-entry.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx
  • apps/dashboard/src/components/copyable-text.tsx
  • apps/dashboard/src/components/app-warning-modal.tsx
  • apps/dashboard/src/lib/apps-frontend.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
  • apps/dashboard/src/components/project-switcher.tsx
  • apps/dashboard/src/components/stack-companion.tsx
  • packages/stack-shared/src/apps/apps-ui.tsx
  • packages/template/src/components/user-button.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx
  • apps/dashboard/src/components/link.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
  • apps/dashboard/src/components/app-square.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx
  • apps/dashboard/src/components/theme-toggle.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx
  • packages/stack-shared/src/apps/apps-config.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/components/settings.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer client components and dynamic functions like usePathname over Next.js dynamic functions to keep pages static; avoid using await params or similar dynamic patterns

Files:

  • apps/dashboard/src/components/app-store-entry.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx
  • apps/dashboard/src/components/copyable-text.tsx
  • apps/dashboard/src/components/app-warning-modal.tsx
  • apps/dashboard/src/lib/apps-frontend.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
  • apps/dashboard/src/components/project-switcher.tsx
  • apps/dashboard/src/components/stack-companion.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx
  • apps/dashboard/src/components/link.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
  • apps/dashboard/src/components/app-square.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx
  • apps/dashboard/src/components/theme-toggle.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/components/settings.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
**/apps-{frontend,config}.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

To update available apps, edit apps-frontend.tsx and apps-config.ts

Files:

  • apps/dashboard/src/lib/apps-frontend.tsx
  • packages/stack-shared/src/apps/apps-config.ts
🧠 Learnings (8)
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.

Applied to files:

  • packages/template/src/components/credential-sign-up.tsx
  • apps/dashboard/src/components/app-warning-modal.tsx
  • apps/dashboard/src/components/project-switcher.tsx
  • packages/template/src/components/user-button.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
  • apps/dashboard/src/components/app-square.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx
  • apps/dashboard/src/components/settings.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/apps-{frontend,config}.{ts,tsx} : To update available apps, edit `apps-frontend.tsx` and `apps-config.ts`

Applied to files:

  • apps/dashboard/src/components/app-store-entry.tsx
  • apps/dashboard/src/lib/apps-frontend.tsx
  • packages/stack-shared/src/apps/apps-ui.tsx
  • apps/dashboard/src/components/app-square.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx
  • packages/stack-shared/src/apps/apps-config.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/components/settings.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to apps/**/*.{ts,tsx} : Prefer client components and dynamic functions like `usePathname` over Next.js dynamic functions to keep pages static; avoid using `await params` or similar dynamic patterns

Applied to files:

  • apps/dashboard/src/components/app-store-entry.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/*.{ts,tsx} : Never use `toast` for blocking alerts and errors; use alerts instead as they are more visible to users

Applied to files:

  • apps/dashboard/src/components/app-warning-modal.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: When making dashboard changes, provide deep links to the dashboard page in the format `http://localhost:<$NEXT_PUBLIC_STACK_PORT_PREFIX>01/projects/-selector-/...` (or use a.localhost, b.localhost, c.localhost for port prefixes 91, 92, 93)

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/*.{ts,tsx} : Never use try-catch-all, never void a promise, never use .catch(console.error) or similar; use `runAsynchronously` or `runAsynchronouslyWithAlert` for error handling instead of try-catch blocks

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to packages/stack-shared/src/config/** : When making backwards-incompatible changes to the config schema, update the migration functions in `packages/stack-shared/src/config/schema.ts`

Applied to files:

  • packages/stack-shared/src/apps/apps-config.ts
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to apps/e2e/**/*.{ts,tsx} : Always add new E2E tests when you change the API or SDK interface

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
🧬 Code graph analysis (11)
apps/dashboard/src/components/app-store-entry.tsx (3)
docs/src/components/icons.tsx (2)
  • Zap (345-347)
  • Check (163-165)
apps/dashboard/src/components/app-square.tsx (1)
  • AppIcon (16-43)
packages/stack-shared/src/apps/apps-config.ts (1)
  • ALL_APP_TAGS (5-39)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx (2)
apps/dashboard/src/lib/utils.tsx (1)
  • cn (7-9)
packages/stack-ui/src/index.ts (1)
  • cn (53-53)
apps/dashboard/src/components/copyable-text.tsx (1)
docs/src/components/icons.tsx (2)
  • Check (163-165)
  • Copy (185-205)
apps/dashboard/src/lib/apps-frontend.tsx (1)
packages/stack-shared/src/apps/apps-config.ts (2)
  • AppId (49-49)
  • ALL_APPS (51-152)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx (5)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (3)
  • LineChartDisplayConfig (14-18)
  • LineChartDisplay (432-485)
  • DonutChartDisplay (517-644)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (2)
  • useAdminApp (29-44)
  • useProjectId (46-53)
apps/dashboard/src/components/router.tsx (1)
  • useRouter (15-33)
packages/stack-ui/src/components/ui/typography.tsx (1)
  • Typography (47-47)
packages/stack-shared/src/utils/dates.tsx (1)
  • fromNow (28-30)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (3)
packages/stack-shared/src/utils/react.tsx (2)
  • current (133-139)
  • current (187-193)
apps/e2e/tests/helpers.ts (1)
  • value (82-86)
packages/stack-shared/src/utils/unicode.tsx (1)
  • getFlagEmoji (3-10)
apps/dashboard/src/components/app-square.tsx (8)
packages/stack-shared/src/apps/apps-ui.tsx (1)
  • AppIcon (33-79)
apps/dashboard/src/lib/apps-frontend.tsx (3)
  • AppId (13-13)
  • ALL_APPS_FRONTEND (67-228)
  • getAppPath (40-43)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (29-44)
docs/src/middleware.ts (1)
  • config (58-63)
packages/stack-shared/src/utils/promises.tsx (1)
  • runAsynchronously (343-366)
docs/src/components/icons.tsx (1)
  • Check (163-165)
apps/dashboard/src/components/app-warning-modal.tsx (1)
  • AppWarningModal (15-201)
apps/dashboard/src/components/link.tsx (1)
  • Link (22-43)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx (2)
apps/dashboard/src/components/env-keys.tsx (1)
  • EnvKeys (24-62)
packages/stack-shared/src/utils/promises.tsx (1)
  • runAsynchronously (343-366)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx (2)
packages/stack-ui/src/lib/utils.tsx (1)
  • cn (4-6)
packages/stack-ui/src/components/ui/typography.tsx (1)
  • Typography (47-47)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (4)
apps/dashboard/src/lib/apps-frontend.tsx (6)
  • DUMMY_ORIGIN (15-15)
  • AppId (13-13)
  • getItemPath (45-48)
  • testItemPath (60-65)
  • getAppPath (40-43)
  • testAppPath (50-58)
packages/stack-shared/src/apps/apps-config.ts (2)
  • AppId (49-49)
  • ALL_APPS (51-152)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (2)
  • useAdminApp (29-44)
  • useProjectId (46-53)
packages/stack-shared/src/utils/objects.tsx (1)
  • typedEntries (263-265)
apps/dashboard/src/components/settings.tsx (3)
packages/stack-shared/src/utils/react.tsx (1)
  • forwardRefIfNeeded (47-57)
packages/stack-shared/src/utils/promises.tsx (2)
  • runAsynchronouslyWithAlert (312-328)
  • runAsynchronously (343-366)
apps/dashboard/src/components/copyable-text.tsx (1)
  • CopyableText (8-49)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (8)
  • GitHub Check: CodeQL analysis (javascript-typescript)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: setup-tests
  • GitHub Check: all-good
  • GitHub Check: restart-dev-and-test
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: check_prisma_migrations (22.x)
🔇 Additional comments (40)
packages/template/src/components/user-button.tsx (1)

4-5: Imports and async utilities align with project conventions

The updated imports are minimal and all used, and the pattern of routing menu actions through runAsynchronouslyWithAlert in Item matches the repo’s async‑handling guidance for UI interactions. No issues from a type‑checking or guideline perspective here.
Based on learnings, this correctly uses runAsynchronouslyWithAlert for menu actions.

apps/dashboard/src/components/project-switcher.tsx (1)

36-40: Based on my verification of the codebase, I can now provide the rewritten review comment.

The original concerns are not valid issues given the codebase context.

The SelectTrigger component from @stackframe/stack-ui explicitly accepts optional children and renders just the caret icon when empty. The empty SelectTrigger with compact styling (h-6 w-6 p-0) is an intentional icon-only design pattern. Additionally, using defaultValue with Next.js app router is correct here—when projectId changes via URL navigation, the component re-mounts and re-evaluates defaultValue with the new currentProjectId.

However, there is an actual issue on line 33: currentProject?.displayName could render as undefined when currentProject is not found. Add a fallback like line 31 does.

{currentProject?.displayName || ""}
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx (3)

42-61: All status types share identical cardClass values.

The cardClass property is identical for done, action, and blocked statuses, making the differentiation ineffective. Either this is intentional (and the cardClass property could be removed from the mapping), or distinct styling was intended but not implemented.


260-303: Confetti animation handles async correctly.

Good use of runAsynchronously with { noErrorLogging: true } for the optional confetti animation, and proper cleanup via clearInterval in the effect return.


447-460: Clickable list item accessibility is implemented well.

Good implementation of keyboard navigation with onKeyDown, proper role="button", and tabIndex attributes for clickable items.

apps/dashboard/src/components/link.tsx (1)

10-20: Type narrowing of LinkProps is safe and intentional; no existing usages depend on NextLink-only props.

Verification confirms that while LinkProps was narrowed from React.ComponentProps<typeof NextLink> to a custom type (excluding replace, shallow, locale, legacyBehavior, passHref), there are no existing <Link> or <StyledLink> usages in the codebase that pass these NextLink-specific props. All sampled usage patterns (20+ import sites across the dashboard) only use the supported properties: href, children, className, target, onClick, style, scroll, prefetch, and title. The narrowing is type-safe and consistent.

apps/dashboard/src/components/theme-toggle.tsx (1)

9-11: LGTM! Theme-aware styling update.

The change from outline to ghost variant with the hover styling (hover:bg-muted/50) provides a more subtle, theme-aware appearance that aligns well with the dashboard redesign.

apps/dashboard/src/components/app-warning-modal.tsx (1)

24-30: Animation pattern is acceptable.

The setTimeout with a 10ms delay is a common pattern for triggering CSS transitions after initial render. This ensures the transition actually plays rather than the element appearing instantly in its final state.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx (1)

1-46: LGTM! Responsive layout improvements.

The refactored layout enhances responsiveness with proper breakpoints (sm:) and improved header structure. The use of cn utility and theme-aware classes aligns well with the dashboard redesign goals.

packages/stack-shared/src/apps/apps-ui.tsx (1)

18-79: LGTM! Streamlined icon rendering with state support.

The refactored AppIcon component simplifies rendering by removing complex gradients and adds enabled state support with appropriate visual indicators (green stroke for enabled, grayscale for disabled). The theme-aware classes ensure dark mode compatibility.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (2)

514-515: Excellent use of ES6 Map!

Using Map for BRAND_CONFIG_MAP follows the coding guidelines to prefer ES6 Maps over records for efficient lookups. This provides better performance for the brand color lookups used throughout the chart components.

As per coding guidelines, "Use ES6 maps instead of records wherever possible."


12-644: LGTM! Well-architected chart component system.

The refactored chart components introduce a clean, modular architecture with:

  • Reusable building blocks (ChartCard, TimeRangeToggle, ActivityBarChart)
  • Type-safe interfaces (TimeRange, DataPoint, UserListItem)
  • Theme-aware styling with proper dark mode support
  • Performance optimizations (isAnimationActive={false})
  • Weekend highlighting with brand-specific colors

The components maintain consistency with the design guide and follow best practices.

apps/dashboard/src/lib/apps-frontend.tsx (2)

3-13: LGTM! Type-safe AppId derivation.

Deriving AppId as keyof typeof ALL_APPS ensures type safety and single source of truth. This aligns with the coding guideline that apps should be updated in apps-frontend.tsx and apps-config.ts.

As per coding guidelines, "To update available apps, edit apps-frontend.tsx and apps-config.ts."


218-220: LGTM! Improved Vercel logo rendering.

The updated container properly centers the logo with flex utilities and applies appropriate styling (object-contain, p-2) for consistent presentation.

apps/dashboard/DESIGN_GUIDE.md (1)

1-814: Excellent comprehensive design guide!

This design guide provides clear, actionable guidance for maintaining visual and functional consistency across the dashboard. The emphasis on theme-aware colors, dark mode support, WCAG accessibility standards, and concrete code examples will help ensure high-quality implementations.

Key strengths:

  • Explicit WCAG AA contrast requirements (4.5:1 normal text, 3:1 large text)
  • Theme-aware color patterns with dark mode variants
  • Comprehensive verification checklist
  • Concrete code examples for common patterns
  • Best practices section

This documentation significantly reduces the risk of accessibility and consistency issues in future dashboard development.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx (2)

21-26: Good defensive guard for app lookup.

The explicit check with StackAssertionError provides clear error messaging if the app frontend is not found. This follows best practices for handling unexpected states.


14-14: Non-null assertion is safe; parent layout wraps with AdminAppProvider.

The AdminAppProvider context provider wraps all children in the [projectId]/layout.tsx (line 10), ensuring useAdminApp() always receives a non-null value from context. When called without parameters, useAdminApp() either returns the provided app or throws an error (line 42 of use-admin-app.tsx)—it never returns null. The non-null assertion is therefore guaranteed not to fail at runtime in normal usage.

apps/dashboard/src/components/copyable-text.tsx (1)

15-20: Good cleanup pattern for timeout.

The useEffect correctly cleans up the timeout on unmount or when copied changes, preventing memory leaks and stale state updates.

packages/stack-shared/src/apps/apps-config.ts (1)

41-47: Clean addition of optional shortName field.

The optional shortName property is well-structured and provides useful display abbreviations for apps. The type definition is backward-compatible.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx (1)

103-122: Good use of runAsynchronouslyWithAlert for transfer action.

The transfer handler correctly relies on runAsynchronouslyWithAlert at the call site (line 328) to handle errors, following the coding guidelines. The try-finally ensures isTransferring is reset regardless of outcome.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (2)

101-109: Camera controls set in both useEffect and onGlobeReady.

The camera distance and control limits are set in both the useEffect (lines 101-109) and onGlobeReady (lines 206-214). This is intentional—onGlobeReady handles initial setup while useEffect handles resize updates—but ensure the duplication is necessary. The useEffect dependency on shouldShowGlobe is good for skipping updates when the globe is hidden.

Also applies to: 206-214


294-325: Fixed-position tooltip follows cursor well.

The tooltip implementation with fixed positioning and mouse tracking provides a smooth UX. The transform calculation for positioning above the cursor is appropriate.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx (1)

189-215: Clean implementation of empty state and grid rendering.

The conditional rendering for empty state with contextual messaging (search vs. category) and the responsive grid layout are well-implemented.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (1)

253-304: Widget components are well-structured.

The DailyActiveUsersWidget and DailySignUpsWidget components provide clean abstractions for the metrics cards. The data: any typing is consistent with the internal API usage pattern.

apps/dashboard/src/components/app-square.tsx (2)

203-279: AppListItem provides a clean compact list variant.

The component correctly handles enabled/disabled states and provides appropriate navigation paths. The stage badge styling is consistent with AppSquare.


100-200: Well-structured hover interaction and modal flow.

The hover-triggered enable/disable button with the warning modal for non-stable apps is a nice UX pattern. The runAsynchronously wrapper for click handlers follows the coding guidelines.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx (2)

47-67: Good use of useProjectId hook for static page compatibility.

Using useProjectId() from the client-side hook rather than await params keeps the page static, which aligns with the coding guidelines for apps directory. The chart rendering with theme-aware colors is well-implemented.


86-127: Clean implementation of scrollable user list with proper height constraints.

The max-h-[340px] with overflow-y-auto and pr-1 for scrollbar padding creates a good scrollable container. The empty state handling is also well done.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (3)

328-375: Well-memoized AppNavItem component.

The useMemo for navItemData prevents unnecessary re-renders of NavItem when the parent re-renders. The dependency array correctly includes all relevant values.


562-583: Clean responsive layout with sticky sidebar.

The flex-based layout with sticky positioning for the sidebar and proper overflow handling provides a solid foundation. The transition animation for the collapsed state is smooth.


169-175: Duplicate transition-colors class.

The caretClasses string contains both transition-colors and transition-transform. The first transition-colors appears to be a copy-paste error.

   const caretClasses = cn(
-    "h-4 w-4 flex-shrink-0 transition-colors transition-transform duration-[50ms]",
+    "h-4 w-4 flex-shrink-0 transition-transform duration-[50ms]",
     isHighlighted
       ? "text-blue-600 dark:text-blue-400"
       : "text-muted-foreground group-hover:text-foreground",
     isSection && isExpanded && "rotate-180"
   );
⛔ Skipped due to learnings
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/*.{ts,tsx} : Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI
apps/dashboard/src/components/settings.tsx (3)

114-148: LGTM! Excellent error handling.

The component correctly uses runAsynchronouslyWithAlert to handle async operations in the change handler, following the coding guidelines.


173-192: LGTM! Well-structured new component.

The new SettingCopyableText component follows consistent patterns with other Setting components and properly delegates to the CopyableText utility component.


194-249: LGTM! Proper async handling.

The component correctly uses runAsynchronouslyWithAlert in handleValueChange to handle async operations, following the coding guidelines.

apps/dashboard/src/components/stack-companion.tsx (6)

14-56: LGTM!

The type definition and constants are well-organized. The threshold values (100px for close, 400px min, 800px max) provide sensible boundaries for the drawer behavior.


58-82: LGTM!

State initialization and version check effect are properly structured with cleanup.


103-114: LGTM!

The click vs drag distinction using dragThresholdRef is a clean pattern, and the three-way branching logic correctly handles all interaction states.


194-207: LGTM!

Proper cleanup of body styles both when isDragging becomes false and on unmount.


209-216: LGTM!

The content opacity calculation provides a smooth fade-out effect as the drawer approaches the close threshold—nice touch for UX polish.


296-321: LGTM!

The sidebar item buttons with tooltips, per-item hover colors, and active state styling are well implemented. The 50ms transition duration keeps interactions snappy per the coding guidelines.

Comment thread apps/dashboard/DESIGN_GUIDE.md
Comment thread apps/dashboard/src/components/link.tsx
Comment thread apps/dashboard/src/components/settings.tsx Outdated
Comment thread apps/dashboard/src/components/settings.tsx Outdated
Comment thread apps/dashboard/src/components/stack-companion.tsx
Comment thread apps/dashboard/src/components/link.tsx Outdated
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Nov 26, 2025

Greptile Overview

Greptile Summary

This PR implements a comprehensive dashboard redesign with significant improvements to user experience and visual consistency. The changes introduce a collapsible sidebar navigation, a widget-based customizable dashboard with time-range filtering, an app discovery interface with category tabs, a resizable companion drawer, and an interactive Vercel integration checklist.

Key Changes:

  • Design System: Added comprehensive DESIGN_GUIDE.md documenting patterns for dark mode support, WCAG contrast requirements, and consistent styling
  • Collapsible Sidebar: Enhanced navigation with expand/collapse functionality, tooltips, and smooth animations
  • Dashboard Widgets: Implemented configurable widget system with globe visualization, metrics charts, and quick app access
  • Stack Companion: Redesigned as resizable right-edge drawer with drag-to-resize functionality (400-800px range)
  • Vercel Integration: Interactive multi-step checklist with progress tracking and confetti celebration on completion
  • App Discovery: New tabbed interface with search, category filtering, and installation status badges
  • Copyable Text Component: New reusable component for displaying copyable credentials with visual feedback

Code Quality:

  • Proper cleanup patterns with useEffect return functions to prevent memory leaks
  • Memoized components and callbacks for performance optimization
  • Accessibility features including ARIA labels, keyboard navigation, and focus management
  • Responsive design with mobile-first approach

Minor Observations:

  • The copyable-text.tsx component uses async onClick without runAsynchronouslyWithAlert, but the clipboard API doesn't throw user-facing errors that need alerts—errors are only logged to console, which is acceptable
  • The app-square.tsx uses manual try/catch with alert() rather than runAsynchronouslyWithAlert, but given this is for app installation (not a form submission), the current approach is reasonable
  • No redirect URL validation issues found, as URLs are internal navigation paths

Confidence Score: 5/5

  • This PR is safe to merge with excellent code quality and comprehensive design improvements
  • Score reflects well-structured code with proper cleanup patterns, accessibility features, performance optimizations, and comprehensive design documentation. The redesign maintains consistency across all dashboard pages while introducing significant UX improvements.
  • No files require special attention

Important Files Changed

File Analysis

Filename Score Overview
apps/dashboard/DESIGN_GUIDE.md 5/5 New comprehensive design guide documenting dashboard patterns, emphasizing dark mode support and WCAG contrast requirements
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx 5/5 Redesigned dashboard with widget-based layout, time-range filtering, and responsive grid system
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx 5/5 Collapsible sidebar with improved navigation, tooltip support, and theme-aware styling
apps/dashboard/src/components/stack-companion.tsx 5/5 Resizable companion drawer with drag-to-resize functionality and smooth animations
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx 5/5 Interactive Vercel integration with multi-step checklist, progress tracking, and confetti celebration
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx 5/5 App discovery interface with category tabs, search functionality, and improved filtering

Sequence Diagram

sequenceDiagram
    participant User
    participant Browser
    participant MetricsPage
    participant SidebarLayout
    participant StackCompanion
    participant AdminApp
    participant API

    User->>Browser: Navigate to dashboard
    Browser->>SidebarLayout: Render layout
    SidebarLayout->>SidebarLayout: Load collapsed state
    SidebarLayout->>AdminApp: useProject()
    AdminApp->>API: Fetch project config
    API-->>AdminApp: Return config with installed apps
    AdminApp-->>SidebarLayout: Config data
    SidebarLayout->>SidebarLayout: Filter enabled apps
    SidebarLayout->>Browser: Display collapsible sidebar

    Browser->>MetricsPage: Render metrics dashboard
    MetricsPage->>MetricsPage: loadConfig() from localStorage
    MetricsPage->>AdminApp: useMetrics()
    AdminApp->>API: Fetch metrics data
    API-->>AdminApp: Return user metrics
    AdminApp-->>MetricsPage: Metrics data
    MetricsPage->>MetricsPage: Filter widgets by config
    MetricsPage->>Browser: Display widgets (globe, charts, apps)

    User->>SidebarLayout: Click sidebar toggle
    SidebarLayout->>SidebarLayout: Toggle collapsed state
    SidebarLayout->>Browser: Animate sidebar width

    User->>StackCompanion: Click companion icon
    StackCompanion->>StackCompanion: openDrawer()
    StackCompanion->>Browser: Animate drawer to DEFAULT_DRAWER_WIDTH

    User->>StackCompanion: Drag resize handle
    StackCompanion->>StackCompanion: handleMouseMove()
    StackCompanion->>StackCompanion: Calculate newWidth
    StackCompanion->>Browser: Update drawer width dynamically

    User->>StackCompanion: Drag below threshold
    StackCompanion->>StackCompanion: handleMouseUp()
    StackCompanion->>StackCompanion: closeDrawer()
    StackCompanion->>Browser: Animate drawer to 0 width

    User->>MetricsPage: Change time range
    MetricsPage->>MetricsPage: setTimeRange()
    MetricsPage->>AdminApp: useMetrics() with new filter
    AdminApp-->>MetricsPage: Filtered metrics
    MetricsPage->>Browser: Update charts and totals
Loading

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

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

26 files reviewed, no comments

Edit Code Review Agent Settings | Greptile

Copy link
Copy Markdown
Contributor

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

♻️ Duplicate comments (2)
apps/dashboard/src/components/stack-companion.tsx (1)

114-131: Missing cleanup for animation timeouts.

The setTimeout calls in openDrawer and closeDrawer are not cleaned up if the component unmounts during animation. This could cause state updates on unmounted components.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (1)

284-325: NavSubItem structure looks good.

The component follows the same URL matching pattern as NavItem. The try-catch concern was already flagged in the previous comment.

🧹 Nitpick comments (3)
apps/dashboard/src/components/stack-companion.tsx (2)

186-209: Dead code: unreachable branch.

The check if (!activeItem) at lines 200-203 is unreachable. Since handleMouseDown returns early when !activeItem (line 151), dragging can only start when activeItem is truthy. Additionally, if drawerWidth < CLOSE_THRESHOLD, closeDrawer() is called before reaching this branch.

        if (drawerWidth < CLOSE_THRESHOLD) {
          closeDrawer();
        } else if (drawerWidth < MIN_DRAWER_WIDTH) {
          setIsAnimating(true);
          setDrawerWidth(MIN_DRAWER_WIDTH);
          setTimeout(() => setIsAnimating(false), 200);
-       } else {
-         // Keep current width but ensure item is active
-         if (!activeItem) {
-            // If dragged open from closed state without clicking specific item, default to docs
-            setActiveItem('docs'); 
-         }
        }

343-343: Memoize context value to prevent unnecessary re-renders.

Creating a new object on every render causes all useStackCompanion() consumers to re-render even when the values haven't changed.

- const contextValue = { drawerWidth, isSplitScreenMode };
+ const contextValue = useMemo(() => ({ drawerWidth, isSplitScreenMode }), [drawerWidth, isSplitScreenMode]);
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (1)

483-594: SidebarLayout implementation is solid with good responsive design.

The layout properly handles:

  • Mobile (Sheet-based menu) vs desktop (collapsible sidebar) navigation
  • TooltipProvider wrapper for collapsed mode tooltips
  • Responsive breakpoints and smooth transitions

The component is quite large (~110 lines). Consider extracting Header and Sidebar into separate components if the file grows further, but the current organization is acceptable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f94451 and cb37ed5.

📒 Files selected for processing (2)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (7 hunks)
  • apps/dashboard/src/components/stack-companion.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Ensure code passes pnpm typecheck for TypeScript type validation
Never use toast for blocking alerts and errors; use alerts instead as they are more visible to users
Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI
Never use try-catch-all, never void a promise, never use .catch(console.error) or similar; use runAsynchronously or runAsynchronouslyWithAlert for error handling instead of try-catch blocks
Use loading indicators instead of try-catch blocks for asynchronous UI operations; button components like support async callbacks with built-in loading state
Use ES6 maps instead of records wherever possible

Files:

  • apps/dashboard/src/components/stack-companion.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer client components and dynamic functions like usePathname over Next.js dynamic functions to keep pages static; avoid using await params or similar dynamic patterns

Files:

  • apps/dashboard/src/components/stack-companion.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
🧠 Learnings (3)
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/*.{ts,tsx} : Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI

Applied to files:

  • apps/dashboard/src/components/stack-companion.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/apps-{frontend,config}.{ts,tsx} : To update available apps, edit `apps-frontend.tsx` and `apps-config.ts`

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to apps/**/*.{ts,tsx} : Prefer client components and dynamic functions like `usePathname` over Next.js dynamic functions to keep pages static; avoid using `await params` or similar dynamic patterns

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: all-good
  • GitHub Check: restart-dev-and-test
  • GitHub Check: restart-dev-and-test-with-custom-base-port
🔇 Additional comments (9)
apps/dashboard/src/components/stack-companion.tsx (5)

1-21: LGTM!

Imports and type definitions are well-organized. The SidebarItem type properly captures the new hoverBg field for per-item hover styling.


22-51: LGTM!

The sidebar items configuration is well-structured with consistent styling patterns across all items.


53-74: LGTM!

Constants are well-documented with the breakpoint comment, and the context/hook pattern is correctly implemented.


224-237: LGTM!

The body style manipulation for drag state is properly implemented with correct cleanup.


345-399: LGTM!

The dual rendering modes (split-screen vs overlay) are cleanly implemented with proper positioning strategies and transition controls.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (4)

40-97: LGTM! Type definitions and constants are well-structured.

The addition of firstItemHref to the AppSection type cleanly supports the collapsed navigation behavior where clicking a collapsed section navigates to its first item.


119-140: Verify the try-catch pattern for URL parsing.

The coding guidelines state "never use try-catch-all" for error handling. While these try-catch blocks are for synchronous URL parsing (not async operations), silently returning null or false on error could mask issues if the pathname is malformed.

Consider whether:

  1. URL parsing failures indicate a deeper routing issue that should be reported
  2. A more explicit error boundary or validation approach would be better
  3. This defensive pattern is acceptable for URL parsing specifically

Based on coding guidelines, which prohibit try-catch-all patterns.


327-375: Excellent use of memoization to prevent unnecessary re-renders.

The AppNavItem component properly memoizes the navigation item data with correct dependencies, which will improve performance when app configuration changes.


377-481: SidebarContent is well-structured with good memoization.

The component properly uses useMemo and useCallback to optimize performance. The getDefaultExpandedSections callback includes pathname in dependencies, which changes frequently, but since it's only used for useState initialization (which runs once), this is acceptable.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 26 out of 26 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Copy Markdown
Contributor

@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

🧹 Nitpick comments (5)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx (5)

20-20: Consider adding proper type for member parameter.

Using any type loses TypeScript's type safety benefits. Consider defining a proper type or using the actual member type from the SDK.

-function TeamMemberItem({ member }: { member: any }) {
+function TeamMemberItem({ member }: { member: { id: string; teamProfile: { displayName?: string | null; profileImageUrl?: string | null } } }) {

102-121: Error handling is incomplete in the transfer flow.

If user.transferProject throws an error, the user won't see any feedback since the toast and reload are only reached on success. Per coding guidelines, consider using runAsynchronouslyWithAlert for proper error handling, or ensure errors are surfaced to the user.

+ import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
...
  const handleTransfer = useCallback(async () => {
    if (!selectedTeamId || selectedTeamId === project.ownerTeamId) return;
    if (isTransferring) return;

    setIsTransferring(true);
-   try {
-     await user.transferProject(project.id, selectedTeamId);
-
-     toast({
-       title: 'Project transferred successfully',
-       variant: 'success'
-     });
-
-     // Reload the page to reflect changes
-     // we don't actually need this, but it's a nicer UX as it clearly indicates to the user that a "big" change was made
-     window.location.reload();
-   } finally {
-     setIsTransferring(false);
-   }
+   await user.transferProject(project.id, selectedTeamId);
+   toast({
+     title: 'Project transferred successfully',
+     variant: 'success'
+   });
+   setIsTransferring(false);
+   window.location.reload();
  }, [selectedTeamId, project.ownerTeamId, project.id, user, toast, isTransferring]);

Note: The Button component used as the trigger could handle the async operation with built-in loading state if refactored. Based on learnings, runAsynchronouslyWithAlert is the preferred pattern for async handlers.


138-145: Replace any types with proper type definitions.

Both handleTeamSwitcherChange and handleProjectDetailsSubmit use any types, losing type safety. Consider using the proper types:

-  const handleTeamSwitcherChange = useCallback(async (team: any) => {
+  const handleTeamSwitcherChange = useCallback(async (team: { id: string }) => {
    setSelectedTeamId(team.id);
  }, []);

-  const handleProjectDetailsSubmit = useCallback(async (values: any) => {
+  const handleProjectDetailsSubmit = useCallback(async (values: yup.InferType<typeof projectInformationSchema>) => {
    await project.update(values);
  }, [project]);

229-241: Inconsistent memoization - dark mode logo handlers are inline while others are memoized.

handleLogoChange and handleFullLogoChange are memoized, but the dark mode variants use inline async callbacks. For consistency and to avoid unnecessary re-renders, memoize these as well:

+  const handleLogoDarkModeChange = useCallback(async (logoDarkModeUrl: string | null) => {
+    await project.update({ logoDarkModeUrl });
+  }, [project]);
+
+  const handleFullLogoDarkModeChange = useCallback(async (logoFullDarkModeUrl: string | null) => {
+    await project.update({ logoFullDarkModeUrl });
+  }, [project]);

Then update the JSX:

  <LogoUpload
    label="Logo (Dark Mode)"
    value={project.logoDarkModeUrl}
-   onValueChange={async (logoDarkModeUrl) => {
-     await project.update({ logoDarkModeUrl });
-   }}
+   onValueChange={handleLogoDarkModeChange}
    ...
  />

  <LogoUpload
    label="Full Logo (Dark Mode)"
    value={project.logoFullDarkModeUrl}
-   onValueChange={async (logoFullDarkModeUrl) => {
-     await project.update({ logoFullDarkModeUrl });
-   }}
+   onValueChange={handleFullLogoDarkModeChange}
    ...
  />

69-77: The renderInfoLabel callback has an empty dependency array but doesn't need memoization.

This function doesn't close over any component state or props, so useCallback with an empty dependency array is equivalent to defining it outside the component. Consider moving it outside PageClient to avoid the hook overhead entirely.

+const renderInfoLabel = (label: string, tooltip: string) => (
+  <div className="flex items-center gap-2">
+    <span>{label}</span>
+    <SimpleTooltip type="info" tooltip={tooltip}>
+      <span className="sr-only">{`More info about ${label}`}</span>
+    </SimpleTooltip>
+  </div>
+);
+
 export default function PageClient() {
   ...
-  // Memoize renderInfoLabel callback
-  const renderInfoLabel = useCallback((label: string, tooltip: string) => (
-    <div className="flex items-center gap-2">
-      <span>{label}</span>
-      <SimpleTooltip type="info" tooltip={tooltip}>
-        <span className="sr-only">{`More info about ${label}`}</span>
-      </SimpleTooltip>
-    </div>
-  ), []);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb37ed5 and 0f3cb66.

📒 Files selected for processing (1)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx (10 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Ensure code passes pnpm typecheck for TypeScript type validation
Never use toast for blocking alerts and errors; use alerts instead as they are more visible to users
Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI
Never use try-catch-all, never void a promise, never use .catch(console.error) or similar; use runAsynchronously or runAsynchronouslyWithAlert for error handling instead of try-catch blocks
Use loading indicators instead of try-catch blocks for asynchronous UI operations; button components like support async callbacks with built-in loading state
Use ES6 maps instead of records wherever possible

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer client components and dynamic functions like usePathname over Next.js dynamic functions to keep pages static; avoid using await params or similar dynamic patterns

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
🧠 Learnings (2)
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to apps/**/*.{ts,tsx} : Prefer client components and dynamic functions like `usePathname` over Next.js dynamic functions to keep pages static; avoid using `await params` or similar dynamic patterns

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: CodeQL analysis (javascript-typescript)
  • GitHub Check: setup-tests
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: restart-dev-and-test
  • GitHub Check: all-good
  • GitHub Check: Vercel Agent Review
  • GitHub Check: docker
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: build (22.x)
🔇 Additional comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx (1)

46-54: Good use of memoization for derived values and callbacks.

The extensive use of useMemo for computed URLs and useCallback for handlers follows React best practices for performance optimization. The integration of useToast for success notifications is appropriate here since it's for non-blocking success feedback (not errors/blocking alerts per coding guidelines).

Copy link
Copy Markdown
Contributor

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

♻️ Duplicate comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (1)

116-120: Using internal DO-NOT-USE API is fragile and should be replaced.

This issue was previously flagged but remains unresolved. Accessing stackAppInternalsSymbol and using (adminApp as any)[stackAppInternalsSymbol].useMetrics() bypasses the public API and may break with library updates.

Consider exposing a proper public API method like adminApp.useMetrics(includeAnonymous) in the Stack library, or work with the library maintainers to provide an official way to access metrics data.

Also applies to: 362-362

🧹 Nitpick comments (10)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (2)

43-62: Extract magic numbers as named constants.

The sizing and zoom calculations use several magic numbers (484, 0.35, 360, 350, 355) that make the logic harder to understand and maintain. Consider extracting these as named constants to document their purpose.

Apply this diff:

+  // Globe sizing constants
+  const GLOBE_DEFAULT_SIZE = 400;
+  const GLOBE_MIN_WIDTH = 350;
+  const GLOBE_MIN_VISIBLE_WIDTH = 355;
+  const CAMERA_BASE_DISTANCE = 484;
+  const CAMERA_DISTANCE_SLOPE = 0.35;
+  const CAMERA_DISTANCE_FALLBACK = 360;
+
   // Simplified sizing for the new layout - only use width
-  const globeSize = globeContainerSize?.width ?? 400;
+  const globeSize = globeContainerSize?.width ?? GLOBE_DEFAULT_SIZE;
 
   // Calculate camera distance (zoom) based on canvas width
   // Linear interpolation: zoom decreases as width increases (less aggressive slope)
   // Lower zoom values = larger globe size
-  // - Canvas width 350: Hide globe
-  // - Canvas width 355: zoom = 360
-  // - Canvas width 500: zoom = 309
-  // Formula: zoom = 484 - 0.35 * width (for width >= 355)
+  // Formula: zoom = CAMERA_BASE_DISTANCE - CAMERA_DISTANCE_SLOPE * width (for width >= GLOBE_MIN_VISIBLE_WIDTH)
   const canvasWidth = globeContainerSize?.width ?? 0;
-  const GLOBE_MIN_WIDTH = 350;
   const shouldShowGlobe = canvasWidth >= GLOBE_MIN_WIDTH;
 
   // Calculate zoom based on width
-  // For widths >= 355, use linear formula: zoom = 484 - 0.35 * width
-  // For widths between 350-355, use 360 (same as at 355px)
-  const cameraDistance = canvasWidth >= 355
-    ? 484 - 0.35 * canvasWidth
-    : 360; // For 350-355 range, use 360
+  // For widths >= GLOBE_MIN_VISIBLE_WIDTH, use linear formula
+  // For widths between GLOBE_MIN_WIDTH and GLOBE_MIN_VISIBLE_WIDTH, use fallback
+  const cameraDistance = canvasWidth >= GLOBE_MIN_VISIBLE_WIDTH
+    ? CAMERA_BASE_DISTANCE - CAMERA_DISTANCE_SLOPE * canvasWidth
+    : CAMERA_DISTANCE_FALLBACK;

234-241: Consider defining types for Globe event handlers.

Multiple event handlers use any type for parameters (e.g., d: any, country: any, polygon: any). While this might be due to react-globe.gl's type definitions, consider defining interfaces for better type safety:

interface CountryFeature {
  properties: {
    ISO_A2_EH: string;
    NAME: string;
    POP_EST: number;
  };
  // Add other properties as needed
}

Then use CountryFeature instead of any in the callbacks.

Also applies to: 246-250, 273-280, 284-297

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (3)

80-88: Implementation ready but not yet wired up.

The saveConfig function is complete but marked as TODO since the widget configuration UI isn't implemented yet. This is fine for now but ensure it's used when the configuration UI is added.

Would you like me to help draft the widget configuration UI to connect this persistence layer?


128-128: Define proper types for metrics data structures.

Multiple locations use any type, which bypasses TypeScript's type checking. Consider defining interfaces for the metrics data structure:

interface DailyMetricPoint {
  activity: number;
  date?: string;
  // Add other properties as needed
}

interface MetricsData {
  total_users: number;
  daily_users: DailyMetricPoint[];
  daily_active_users: DailyMetricPoint[];
  recently_active: any[]; // Define proper type
  recently_registered: any[]; // Define proper type
}

Then use these types instead of any in function parameters and reduce callbacks.

Also applies to: 256-256, 282-282


390-430: Extract magic numbers for grid calculations.

The grid sizing logic uses several magic numbers (352.5, 180, 1.75, 1024, 20) that could be extracted as named constants for better maintainability:

const GRID_GAP_LG = 20; // gap-5 in pixels
const GRID_COLUMNS = 12;
const GLOBE_COLUMNS = 5;
const GLOBE_MIN_WIDTH = 352.5;
const VIEW_HEIGHT_OFFSET = 180; // Header/padding offset
const GLOBE_MAX_HEIGHT_RATIO = 1.75;
const LG_BREAKPOINT = 1024; // Tailwind lg breakpoint

This makes the calculations more self-documenting and easier to maintain if design requirements change.

packages/stack-shared/src/apps/apps-ui.tsx (1)

18-25: Clarify the semantic difference between enabled and disabled props.

The enabled and disabled props control different visual aspects but their semantics may overlap in consumer understanding. Consider documenting when to use each:

  • enabled: Controls "active/installed" visual state (green icon, full opacity logo)
  • disabled: Controls "interaction disabled" state (dashed border, reduced opacity)

This is clear in the implementation but could benefit from a brief note in the docstrings about their independent use cases.

apps/dashboard/src/components/app-store-entry.tsx (1)

23-28: Move static features array outside the component.

The features array is static and doesn't depend on props or state. Moving it outside the component avoids recreating the array on every render.

+// Feature highlights (can be customized per app in the future)
+const features = [
+  { icon: Shield, label: "Secure & Compliant" },
+  { icon: Zap, label: "Quick Setup" },
+  { icon: Check, label: "Production Ready" },
+];
+
 export function AppStoreEntry({
   appId,
   onEnable,
   titleComponent: TitleComponent = "h1",
 }: {
   appId: AppId,
   onEnable: () => Promise<void>,
   titleComponent?: FunctionComponent<any> | string,
 }) {
   const app = ALL_APPS[appId];
   const appFrontend = ALL_APPS_FRONTEND[appId];
-
-  // Feature highlights (can be customized per app in the future)
-  const features = [
-    { icon: Shield, label: "Secure & Compliant" },
-    { icon: Zap, label: "Quick Setup" },
-    { icon: Check, label: "Production Ready" },
-  ];
apps/dashboard/src/components/app-square.tsx (2)

186-191: Use async button callback instead of runAsynchronously wrapper.

For the warning modal confirm action, consider making onConfirm accept an async callback and using a Button component with loading state for consistency with the CTA patterns used elsewhere.


197-272: AppListItem: Consider extracting shared hook logic.

AppListItem and AppSquare both call useAdminApp()!, project.useProject(), and project.useConfig() with the same pattern. Consider extracting this into a shared hook like useAppInstallState(appId) to reduce duplication.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (1)

400-411: getDefaultExpandedSections as useCallback is unnecessary.

The function is only used as the useState initializer and never called again. The useCallback wrapper adds overhead without benefit. Use a plain function or inline the logic.

-  // Memoize getDefaultExpandedSections to prevent recreating the function
-  const getDefaultExpandedSections = useCallback((): Set<AppId> => {
+  const [expandedSections, setExpandedSections] = useState<Set<AppId>>(() => {
     const currentUrl = new URL(pathname, DUMMY_ORIGIN);
     for (const enabledApp of enabledApps) {
       const appFrontend = ALL_APPS_FRONTEND[enabledApp];
       if (testAppPath(projectId, appFrontend, currentUrl)) {
         return new Set([enabledApp]);
       }
     }
     return new Set(["authentication"]);
-  }, [enabledApps, pathname, projectId]);
-
-  const [expandedSections, setExpandedSections] = useState<Set<AppId>>(() => getDefaultExpandedSections());
+  });

Note: This changes behavior slightly since the current code captures enabledApps at initialization time, but since useState initializer only runs once, this is equivalent.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f3cb66 and 0e12380.

📒 Files selected for processing (7)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (4 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (7 hunks)
  • apps/dashboard/src/app/layout.tsx (1 hunks)
  • apps/dashboard/src/components/app-square.tsx (2 hunks)
  • apps/dashboard/src/components/app-store-entry.tsx (2 hunks)
  • packages/stack-shared/src/apps/apps-ui.tsx (3 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/dashboard/src/app/layout.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Ensure code passes pnpm typecheck for TypeScript type validation
Never use toast for blocking alerts and errors; use alerts instead as they are more visible to users
Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI
Never use try-catch-all, never void a promise, never use .catch(console.error) or similar; use runAsynchronously or runAsynchronouslyWithAlert for error handling instead of try-catch blocks
Use loading indicators instead of try-catch blocks for asynchronous UI operations; button components like support async callbacks with built-in loading state
Use ES6 maps instead of records wherever possible

Files:

  • apps/dashboard/src/components/app-store-entry.tsx
  • packages/stack-shared/src/apps/apps-ui.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/components/app-square.tsx
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer client components and dynamic functions like usePathname over Next.js dynamic functions to keep pages static; avoid using await params or similar dynamic patterns

Files:

  • apps/dashboard/src/components/app-store-entry.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/components/app-square.tsx
🧠 Learnings (3)
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/apps-{frontend,config}.{ts,tsx} : To update available apps, edit `apps-frontend.tsx` and `apps-config.ts`

Applied to files:

  • apps/dashboard/src/components/app-store-entry.tsx
  • packages/stack-shared/src/apps/apps-ui.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/components/app-square.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to apps/**/*.{ts,tsx} : Prefer client components and dynamic functions like `usePathname` over Next.js dynamic functions to keep pages static; avoid using `await params` or similar dynamic patterns

Applied to files:

  • apps/dashboard/src/components/app-store-entry.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.

Applied to files:

  • apps/dashboard/src/components/app-square.tsx
🧬 Code graph analysis (3)
apps/dashboard/src/components/app-store-entry.tsx (4)
docs/src/components/icons.tsx (2)
  • Zap (345-347)
  • Check (163-165)
apps/dashboard/src/components/app-square.tsx (1)
  • AppIcon (16-39)
packages/stack-shared/src/apps/apps-ui.tsx (1)
  • AppIcon (33-79)
packages/stack-shared/src/apps/apps-config.ts (1)
  • ALL_APP_TAGS (5-39)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (2)
examples/lovable-react-18-example/src/lib/utils.ts (1)
  • cn (4-6)
packages/stack-shared/src/utils/unicode.tsx (1)
  • getFlagEmoji (3-10)
apps/dashboard/src/components/app-square.tsx (9)
packages/stack-shared/src/apps/apps-ui.tsx (1)
  • AppIcon (33-79)
packages/stack-shared/src/apps/apps-config.ts (2)
  • AppId (48-48)
  • ALL_APPS (50-141)
apps/dashboard/src/lib/apps-frontend.tsx (2)
  • AppId (13-13)
  • ALL_APPS_FRONTEND (67-228)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (29-44)
docs/src/middleware.ts (1)
  • config (58-63)
packages/stack-shared/src/utils/promises.tsx (1)
  • runAsynchronously (343-366)
docs/src/components/icons.tsx (1)
  • Check (163-165)
apps/dashboard/src/components/app-warning-modal.tsx (1)
  • AppWarningModal (15-201)
apps/dashboard/src/components/link.tsx (1)
  • Link (22-43)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Cursor Bugbot
  • GitHub Check: all-good
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: docker
  • GitHub Check: build (22.x)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: restart-dev-and-test-with-custom-base-port
🔇 Additional comments (17)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (3)

67-74: Nice UX improvement!

The previousSelectedCountry tracking allows the tooltip to persist with the last selected country data even when not hovering, providing a smoother user experience.


110-118: Verify camera update performance.

The effect depends on cameraDistance, which is recalculated on every render when globeContainerSize changes. This will trigger camera updates during window resizes. While this appears intentional for responsive behavior, verify that the frequent camera position updates don't cause performance issues or visual jitter during continuous resizing.


303-334: Tooltip implementation looks good.

The tooltip positioning and transition logic is well-implemented:

  • Uses transform for smooth positioning updates
  • Applies transition-opacity only on fade-out (when selectedCountry becomes null), keeping the UI snappy as per coding guidelines
  • Displays flag emoji with proper validation
  • Clean data presentation with proper formatting
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (3)

146-248: Well-implemented responsive widget.

The AppsWidget implementation handles dynamic grid layout effectively:

  • Responsive item sizing with proper resize observation
  • Smart overflow handling with expand/collapse
  • Hover transitions applied correctly (post-action fade, not pre-action delay) per coding guidelines
  • Clean empty state handling

432-462: Widget rendering looks clean.

The renderWidget function provides a clean abstraction for widget rendering with proper type safety via the switch statement. The grouping of widgets into chart and stat categories (lines 465-466) is logical and supports the grid layout effectively.


556-564: Good mobile UX with informative notice.

The mobile globe notice provides clear feedback to users on smaller screens, explaining why the globe visualization isn't visible. This is good UX design.

packages/stack-shared/src/apps/apps-ui.tsx (1)

53-75: LGTM - Conditional rendering logic is correct.

The rendering logic correctly handles both LogoComponent and IconComponent cases with appropriate styling based on the enabled and disabled states. The conditional grayscale/opacity for logos and stroke colors for icons provide clear visual feedback.

apps/dashboard/src/components/app-store-entry.tsx (3)

98-104: LGTM - Button correctly handles async callback.

The Button component with onClick={onEnable} correctly leverages the async callback support with built-in loading state per the coding guidelines.


149-176: LGTM - Screenshots section with proper conditional rendering.

The screenshots gallery correctly checks for non-empty array before rendering and uses appropriate ScrollArea for horizontal scrolling. Image alt text is descriptive.


66-78: I'll verify the type assertion concern about app.tags and ALL_APP_TAGS. Let me start by exploring the relevant code.
<function_calls>

#!/bin/bash

Find the definition of ALL_APP_TAGS

rg -n "ALL_APP_TAGS" apps/dashboard/src/components/app-store-entry.tsx -A 3 -B 3


</function_calls>

#!/bin/bash

Find where ALL_APP_TAGS is defined (likely imported or defined elsewhere)

rg -n "export.*ALL_APP_TAGS|const ALL_APP_TAGS|const ALL_APP_TAGS" --type ts --type tsx


</function_calls>

#!/bin/bash

Check the file structure to find imports in app-store-entry.tsx

head -30 apps/dashboard/src/components/app-store-entry.tsx


</function_calls>

apps/dashboard/src/components/app-square.tsx (2)

143-146: Simplify async handling - handleToggleEnabled is already async.

The handleToggleEnabled function is already async and returns a Promise<void>. Wrapping it with runAsynchronously is correct for button click handlers per the guidelines.


58-58: I'll help you verify this review comment about the non-null assertion on useAdminApp()!. Let me investigate the codebase to understand the context and usage patterns.
<function_calls>
<invoke_name>shell

#!/bin/bash

First, let's explore the file structure and find the file in question

fd "app-square.tsx" --type f

</invoke_name>
</function_calls>

Now let me examine the file and understand the context:
<function_calls>
<invoke_name>shell

#!/bin/bash

Read the app-square.tsx file to see the full context

cat -n "apps/dashboard/src/components/app-square.tsx"

</invoke_name>
</function_calls>

Now let me search for where AppSquare is imported and used:
<function_calls>
<invoke_name>shell

#!/bin/bash

Search for AppSquare imports and usages

rg -n "AppSquare" --type=tsx -A 3 -B 3

</invoke_name>
</function_calls>

Let me also check the useAdminApp hook implementation:
<function_calls>
<invoke_name>shell

#!/bin/bash

Search for useAdminApp hook definition

rg -n "useAdminApp" --type=ts --type=tsx -A 5 -B 2

</invoke_name>
</function_calls>

Let me search for AdminApp provider to understand the context setup:
<function_calls>
<invoke_name>shell

#!/bin/bash

Search for AdminApp context/provider

rg -n "AdminApp" --type=tsx --type=ts -A 3 -B 3 | head -100

</invoke_name>
</function_calls>

<budget_exceeded />

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (5)

119-125: LGTM - Defensive URL parsing with graceful fallback.

The try-catch for URL parsing is appropriate here since pathname could theoretically be invalid, and returning null allows for safe downstream handling.


327-375: Good use of memoization for AppNavItem.

The useMemo for navItemData correctly memoizes the navigation item object to prevent unnecessary re-renders of NavItem. The dependency array is complete.


391-397: LGTM - Memoized enabledApps filter.

Memoizing the filtered enabled apps list with proper dependencies prevents unnecessary recalculations on every render.


177-225: LGTM - Collapsed navigation with tooltip support.

The collapsed sidebar navigation correctly renders icon-only buttons with tooltips for accessibility. The conditional href handling for sections vs items is correct.


565-575: Verify sidebar transition doesn't cause layout shift.

The sidebar width transition uses transition-[width] duration-200 ease-in-out which could cause content reflow. Consider using transform or ensuring the main content area handles the width change gracefully.

Comment thread apps/dashboard/src/components/app-square.tsx
Comment thread apps/dashboard/src/components/app-square.tsx Outdated
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

This is the final PR Bugbot will review for you during this billing cycle

Your free Bugbot reviews will reset on December 10

Details

You are on the Bugbot Free tier. On this plan, Bugbot will review limited PRs each billing cycle.

To receive Bugbot reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Comment thread apps/dashboard/src/app/layout.tsx Outdated
Copy link
Copy Markdown
Contributor

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

♻️ Duplicate comments (5)
apps/dashboard/DESIGN_GUIDE.md (2)

529-534: Hardcoded bg-white/40 conflicts with dark mode guidance.

This example uses bg-white/40 which contradicts the guide's own dark mode principles documented in lines 90-219. Consider using a theme-aware alternative like bg-muted/40 or bg-background/40.


547-558: Progress Summary Section also uses hardcoded bg-white/40.

Line 558 repeats the same bg-white/40 pattern that conflicts with the dark mode guidance. For consistency with the guide's principles, this should also use a theme-aware token.

-  <Progress value={progress} className="mt-4 h-2 bg-white/40" />
+  <Progress value={progress} className="mt-4 h-2 bg-muted/40" />
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (2)

34-39: Duplicate isWeekend function.

This function duplicates logic from isWeekend in @stackframe/stack-shared/dist/utils/dates, which is already imported in this file (line 4 imports fromNow from the same module).

Consider refactoring to reuse the shared function:

+import { fromNow, isWeekend as isWeekendShared } from '@stackframe/stack-shared/dist/utils/dates';
-import { fromNow } from '@stackframe/stack-shared/dist/utils/dates';

-const isWeekend = (dateString: string): boolean => {
+const isWeekend = (dateString: string): boolean => {
   const date = new Date(dateString);
-  if (isNaN(date.getTime())) return false;
-  const dayOfWeek = date.getDay();
-  return dayOfWeek === 0 || dayOfWeek === 6;
+  return isNaN(date.getTime()) ? false : isWeekendShared(date);
 };

627-627: Critical: Unsafe property access will crash on unknown auth methods.

This line accesses BRAND_CONFIG[item.method as keyof typeof BRAND_CONFIG].color without checking if the key exists. Since item.method is typed as string (line 488), if the backend returns an auth method not in BRAND_CONFIG, this throws "Cannot read property 'color' of undefined". The type cast provides no runtime protection, and the ?? "var(--color-other)" fallback won't prevent the exception.

Line 630 demonstrates the correct safe pattern using BRAND_CONFIG_MAP.get() with optional chaining. Apply the same pattern here:

-                      style={{ backgroundColor: BRAND_CONFIG[item.method as keyof typeof BRAND_CONFIG].color ?? "var(--color-other)" }}
+                      style={{ backgroundColor: BRAND_CONFIG_MAP.get(item.method)?.color ?? "var(--color-other)" }}
apps/dashboard/src/components/stack-companion.tsx (1)

114-131: Clean up animation timeouts and rAF to avoid state updates after unmount.

openDrawer, closeDrawer, and handleMouseUp schedule requestAnimationFrame and multiple setTimeout callbacks that can still fire after the component unmounts, causing React “state update on unmounted component” warnings and minor leaks. Track these IDs in refs, clear them in a cleanup effect, and route all timeouts through a helper instead of raw setTimeout.

Example refactor:

@@
-  const startXRef = useRef(0);
-  const startWidthRef = useRef(0);
-  const dragThresholdRef = useRef(false);
+  const startXRef = useRef(0);
+  const startWidthRef = useRef(0);
+  const dragThresholdRef = useRef(false);
+  const animationFrameRef = useRef<number | null>(null);
+  const timeoutsRef = useRef<Array<ReturnType<typeof setTimeout>>>([]);
+
+  const registerTimeout = useCallback((cb: () => void, delay: number) => {
+    const id = window.setTimeout(cb, delay);
+    timeoutsRef.current.push(id);
+    return id;
+  }, []);
@@
-  const openDrawer = useCallback((itemId: string) => {
-    setActiveItem(itemId);
-    setIsAnimating(true);
-    // Start animation
-    requestAnimationFrame(() => {
-      setDrawerWidth(DEFAULT_DRAWER_WIDTH);
-      setTimeout(() => setIsAnimating(false), 300);
-    });
-  }, []);
+  const openDrawer = useCallback((itemId: string) => {
+    setActiveItem(itemId);
+    setIsAnimating(true);
+    animationFrameRef.current = requestAnimationFrame(() => {
+      setDrawerWidth(DEFAULT_DRAWER_WIDTH);
+      registerTimeout(() => setIsAnimating(false), 300);
+    });
+  }, [registerTimeout]);
@@
-  const closeDrawer = useCallback(() => {
-    setIsAnimating(true);
-    setDrawerWidth(0);
-    setTimeout(() => {
-      setActiveItem(null);
-      setIsAnimating(false);
-    }, 300);
-  }, []);
+  const closeDrawer = useCallback(() => {
+    setIsAnimating(true);
+    setDrawerWidth(0);
+    registerTimeout(() => {
+      setActiveItem(null);
+      setIsAnimating(false);
+    }, 300);
+  }, [registerTimeout]);
@@
-    const handleMouseUp = () => {
-      setIsResizing(false);
-      setTimeout(() => setIsDragging(false), 0);
+    const handleMouseUp = () => {
+      setIsResizing(false);
+      registerTimeout(() => setIsDragging(false), 0);
@@
-        } else if (drawerWidth < MIN_DRAWER_WIDTH) {
-          setIsAnimating(true);
-          setDrawerWidth(MIN_DRAWER_WIDTH);
-          setTimeout(() => setIsAnimating(false), 200);
+        } else if (drawerWidth < MIN_DRAWER_WIDTH) {
+          setIsAnimating(true);
+          setDrawerWidth(MIN_DRAWER_WIDTH);
+          registerTimeout(() => setIsAnimating(false), 200);
@@
-    };
+    };
@@
-    document.addEventListener('touchend', handleMouseUp);
+    document.addEventListener('touchend', handleMouseUp);
@@
-      document.removeEventListener('touchend', handleMouseUp);
-    };
-  }, [isResizing, drawerWidth, closeDrawer, activeItem]);
+      document.removeEventListener('touchend', handleMouseUp);
+    };
+  }, [isResizing, drawerWidth, closeDrawer, activeItem, registerTimeout]);
+
+  useEffect(() => {
+    return () => {
+      if (animationFrameRef.current !== null) {
+        cancelAnimationFrame(animationFrameRef.current);
+      }
+      timeoutsRef.current.forEach(clearTimeout);
+      timeoutsRef.current = [];
+    };
+  }, []);

Please verify with your existing ESLint/hooks rules and run-time behavior in dev to ensure no new warnings.

Also applies to: 161-205

🧹 Nitpick comments (9)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx (1)

33-33: Remove unnecessary async wrapper.

The async keyword is unnecessary here since runAsynchronouslyWithAlert already handles the promise returned by handleEnable().

Apply this diff:

-        onEnable={async () => runAsynchronouslyWithAlert(handleEnable())}
+        onEnable={() => runAsynchronouslyWithAlert(handleEnable())}
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (2)

119-125: Silent try-catch suppresses errors without handling.

Per coding guidelines, avoid try-catch-all patterns. This catch block silently returns null without logging or handling the error. If URL parsing fails, this could mask underlying issues.

Consider using a safer URL validation approach or at minimum log unexpected errors:

 const currentUrl = useMemo(() => {
   try {
     return new URL(pathname, DUMMY_ORIGIN);
-  } catch {
-    return null;
+  } catch (e) {
+    console.warn('Failed to parse pathname as URL:', pathname, e);
+    return null;
   }
 }, [pathname]);

131-140: Silent error suppression in matchesCurrentUrl.

This try-catch-all pattern suppresses all errors from sectionItem.match(). If match throws due to a bug (not just invalid URLs), this will silently return false and hide the issue.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx (1)

95-96: Consider typing user data instead of any.

The user objects from data.recently_registered and data.recently_active are typed as any. If a shared type exists for these user objects, applying it would improve type safety.

Also applies to: 159-160

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (2)

154-158: Avoid blocking alert() even in development mode.

Per coding guidelines, never use toast for blocking alerts. While this alert() is for development debugging only, consider using console.error or a non-blocking notification mechanism instead.

         if (process.env.NODE_ENV === "development") {
-          setTimeout(() => {
-            alert("Globe rendering error — it has now been refreshed. TODO let's fix this");
-          }, 1000);
+          console.error("Globe rendering error — it has now been refreshed. TODO: fix this root cause");
         }

177-199: Consider extracting mouse event handlers.

The inline event handlers (onMouseEnter, onMouseMoveCapture, onMouseLeave) could be extracted to named callbacks to improve readability and potentially reduce re-renders, though the performance impact is likely minimal for a globe component.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (2)

492-515: Consolidate to use BRAND_CONFIG_MAP consistently for runtime lookups.

The file defines both BRAND_CONFIG (record) and BRAND_CONFIG_MAP (Map), but uses them inconsistently. Line 630 uses the safe pattern BRAND_CONFIG_MAP.get(item.method)?.label, while lines 574 and 627 use unsafe bracket notation on BRAND_CONFIG. This inconsistency led to the critical bug at line 627.

Apply the safe Map pattern consistently for all runtime lookups:

Line 574:

-                        const brandConfig = key ? BRAND_CONFIG[key as keyof typeof BRAND_CONFIG] : undefined;
+                        const brandConfig = key ? BRAND_CONFIG_MAP.get(key) : undefined;

Note: BRAND_CONFIG is still needed for the ChartContainer config prop (line 560), so both can coexist, but runtime lookups should use the Map exclusively.

Also applies to: 574-574, 627-627, 630-630


514-515: Clarify "Memoized" comment.

The comment states "Memoized Map" but this is a module-level constant, not a memoized value. While it's efficiently created once at module load time, "memoized" typically refers to React's useMemo or similar caching patterns.

Consider updating the comment for clarity:

-// Memoized Map for efficient lookups
+// Map for efficient runtime lookups
 const BRAND_CONFIG_MAP = new Map(Object.entries(BRAND_CONFIG));
apps/dashboard/src/components/stack-companion.tsx (1)

146-159: Unreachable “drag to open from closed” path—consider simplifying or enabling the behavior.

handleMouseDown bails out when !activeItem, so the branch in handleMouseUp that sets a default 'docs' item when !activeItem and dragThresholdRef.current is true can never be hit. Either allow drag-resize from a closed state (and keep the default-docs logic), or remove that branch/comment to avoid dead code and confusion.

Also applies to: 186-205

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0e12380 and 3faa4fb.

📒 Files selected for processing (8)
  • apps/dashboard/DESIGN_GUIDE.md (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx (3 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (4 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (7 hunks)
  • apps/dashboard/src/components/stack-companion.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Ensure code passes pnpm typecheck for TypeScript type validation
Never use toast for blocking alerts and errors; use alerts instead as they are more visible to users
Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI
Never use try-catch-all, never void a promise, never use .catch(console.error) or similar; use runAsynchronously or runAsynchronouslyWithAlert for error handling instead of try-catch blocks
Use loading indicators instead of try-catch blocks for asynchronous UI operations; button components like support async callbacks with built-in loading state
Use ES6 maps instead of records wherever possible

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
  • apps/dashboard/src/components/stack-companion.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer client components and dynamic functions like usePathname over Next.js dynamic functions to keep pages static; avoid using await params or similar dynamic patterns

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
  • apps/dashboard/src/components/stack-companion.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx
🧠 Learnings (4)
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/apps-{frontend,config}.{ts,tsx} : To update available apps, edit `apps-frontend.tsx` and `apps-config.ts`

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to apps/**/*.{ts,tsx} : Prefer client components and dynamic functions like `usePathname` over Next.js dynamic functions to keep pages static; avoid using `await params` or similar dynamic patterns

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/*.{ts,tsx} : Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI

Applied to files:

  • apps/dashboard/src/components/stack-companion.tsx
🧬 Code graph analysis (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (2)
examples/lovable-react-18-example/src/lib/utils.ts (1)
  • cn (4-6)
packages/stack-shared/src/utils/unicode.tsx (1)
  • getFlagEmoji (3-10)
apps/dashboard/src/components/stack-companion.tsx (5)
apps/dashboard/src/lib/version-check.ts (2)
  • VersionCheckResult (5-8)
  • checkVersion (44-109)
apps/dashboard/src/components/stack-companion/unified-docs-widget.tsx (1)
  • UnifiedDocsWidget (159-404)
apps/dashboard/src/components/stack-companion/feature-request-board.tsx (1)
  • FeatureRequestBoard (50-487)
apps/dashboard/src/components/stack-companion/changelog-widget.tsx (1)
  • ChangelogWidget (24-285)
apps/dashboard/src/components/feedback-form.tsx (1)
  • FeedbackForm (11-177)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (2)
packages/stack-shared/src/utils/dates.tsx (2)
  • isWeekend (5-7)
  • fromNow (28-30)
apps/dashboard/src/components/router.tsx (1)
  • useRouter (15-33)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: restart-dev-and-test
  • GitHub Check: setup-tests
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: all-good
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Cursor Bugbot
  • GitHub Check: docker
  • GitHub Check: check_prisma_migrations (22.x)
🔇 Additional comments (16)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx (2)

6-8: LGTM: Error handling utilities properly imported.

The addition of StackAssertionError and runAsynchronouslyWithAlert aligns with the coding guidelines for proper error handling.


14-14: The non-null assertion is safe; useAdminApp() cannot return null.

The useAdminApp() hook returns either a StackAdminApp<false> value or calls throwErr() (which has return type never, meaning it always throws and never returns). TypeScript correctly infers this cannot produce a null value, making the non-null assertion ! technically redundant but not unsafe.

apps/dashboard/DESIGN_GUIDE.md (1)

1-814: Solid design guide establishing consistent patterns.

This comprehensive documentation provides valuable guidance for dashboard consistency, dark mode support, and accessibility. The patterns and examples will help maintain design coherence across the dashboard.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (4)

348-363: Well-structured memoization for navigation items.

The navItemData memoization with proper dependencies prevents unnecessary re-renders of the navigation structure. Good use of useMemo here.


391-397: Good use of memoization and callbacks for performance.

The enabledApps, getDefaultExpandedSections, and toggleSection are properly memoized with appropriate dependency arrays, preventing unnecessary recalculations on re-renders.

Also applies to: 400-409, 413-423


483-593: Clean layout implementation with responsive design.

The new header/sidebar/content structure with collapsible sidebar, TooltipProvider wrapper, and responsive breakpoints is well-implemented. The transition animations and sticky positioning provide a polished UX.


105-106: I'll help you verify this review comment about the unused onExpandSidebar parameter. Let me search the codebase to understand how this parameter is used.
<function_calls>

#!/bin/bash

Search for onExpandSidebar usage across the codebase

rg -n "onExpandSidebar" --type=ts --type=tsx -C3


</function_calls>
shell

Let me also read the specific file to understand the component structure and parameter declarations:
<function_calls>

#!/bin/bash

Get line count first

wc -l apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx


</function_calls>
shell

<function_calls>

#!/bin/bash

Read the sidebar-layout.tsx file

cat -n "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx"


</function_calls>
shell

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx (1)

1-49: Clean responsive layout implementation.

The refactored PageLayout properly handles responsive design with appropriate breakpoints, maintains a consistent header structure with optional actions, and uses the cn utility correctly for className composition.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/charts-section-with-data.tsx (2)

19-45: Good theme-aware color configuration.

The chart configs properly use theme objects with separate light/dark values, ensuring charts display correctly in both modes.


86-127: Well-structured interactive user list with empty state.

The Recent Sign Ups section handles empty state gracefully and provides clickable navigation to user details. Good use of responsive styling and accessibility with proper button semantics.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (4)

46-62: Well-documented camera distance calculation.

The inline comments clearly explain the linear interpolation formula for camera distance based on canvas width. This makes the magic numbers understandable and maintainable.


67-74: Good pattern for tooltip fade-out.

Tracking previousSelectedCountry separately allows the tooltip to fade out smoothly rather than disappearing abruptly when the user moves away from a country.


250-272: Theme-aware hex polygon coloring.

The color calculation properly handles both light and dark themes with appropriate HSL values, ensuring the globe is visually appealing in both modes.


302-334: Clean tooltip implementation with theme-aware styling.

The tooltip uses bg-background/95, text-foreground, and text-muted-foreground tokens ensuring proper dark mode support. The flag emoji fallback to 🌍 handles edge cases gracefully.

apps/dashboard/src/components/stack-companion.tsx (2)

14-51: Context, sidebar config, and version-check wiring look solid.

SidebarItem’s new hoverBg field, the per-item color/hover classes, drawer sizing constants, the StackCompanionContext/useStackCompanion API, the split-screen breakpoint effect, and the delayed, silent checkVersion call are all cohesive and type-safe. I don’t see logical issues here.

Also applies to: 53-75, 94-103, 105-112


247-341: Handle UI, content container, and split-screen/overlay layouts look coherent and performant.

The shared drawerContent and handleComponent reuse is clean, width transitions are short (150–300ms) and applied post-action, tooltip/item hover effects are snappy, and the split-screen vs overlay layouts integrate well with the new context. This aligns with the design guide’s emphasis on fast interactions and feels maintainable.

Based on learnings, the brief transition durations and action-first behavior match the DESIGN_GUIDE guidance.

Also applies to: 343-399

Copy link
Copy Markdown
Contributor

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

♻️ Duplicate comments (5)
apps/dashboard/src/app/layout.tsx (1)

74-78: Clarification on production guard + development concerns.

The previous review flagged this as loading in production, but the process.env.NODE_ENV === 'development' guard should prevent that—Next.js evaluates this at build time, so production builds won't include this script.

However, there are still concerns for the development environment:

  1. No version pinning: The unpkg URL lacks a version specifier, meaning different developers could pull different versions of react-scan, leading to inconsistent behavior.
  2. Synchronous script: Loading this synchronously in the head blocks rendering, even in development.
  3. Protocol-relative URL: The // prefix is outdated; prefer https://.

Consider pinning to a specific version and documenting why this tool is needed:

-        {/* eslint-disable-next-line @next/next/no-sync-scripts */}
-        {process.env.NODE_ENV === 'development' && <script
-          crossOrigin="anonymous"
-          src="//unpkg.com/react-scan/dist/auto.global.js"
-        />}
+        {/* React performance profiling in development */}
+        {process.env.NODE_ENV === 'development' && <script
+          async
+          crossOrigin="anonymous"
+          src="https://unpkg.com/react-scan@0.0.37/dist/auto.global.js"
+        />}
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (2)

34-39: Local isWeekend differs from shared utility.

The shared isWeekend in packages/stack-shared/src/utils/dates.tsx takes a Date object, while this local version takes a date string and handles invalid dates. If you need string handling, this is reasonable, but consider extracting to a shared utility or using the existing one with proper date parsing upstream.


631-636: Unsafe property access will crash on unknown auth methods.

Line 633 accesses BRAND_CONFIG[item.method as keyof typeof BRAND_CONFIG].color without checking if the key exists. If the backend returns an auth method not in BRAND_CONFIG (e.g., magiclink, passkey, otp), this throws "Cannot read property 'color' of undefined". Line 636 correctly uses BRAND_CONFIG_MAP.get() with a fallback—apply the same pattern to line 633.

                     <span
                       className="h-2 w-2 rounded-full shrink-0"
-                      style={{ backgroundColor: BRAND_CONFIG[item.method as keyof typeof BRAND_CONFIG].color ?? "var(--color-other)" }}
+                      style={{ backgroundColor: BRAND_CONFIG_MAP.get(item.method)?.color ?? "var(--color-other)" }}
                     />
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (1)

116-120: Using internal symbols is fragile and may break.

This accesses an internal API marked with "DO-NOT-USE-OR-YOU-WILL-BE-FIRED". While it works now, this pattern is brittle and could break without notice on library updates. Consider exposing a proper public API for accessing metrics data.

apps/dashboard/src/components/stack-companion.tsx (1)

114-131: Critical: Animation timeouts still lack cleanup (previously reported).

This issue was already flagged in a previous review but remains unfixed. The requestAnimationFrame (line 118) and setTimeout calls (lines 120, 127-130) can execute after the component unmounts, causing state updates on an unmounted component.

As suggested in the previous review, track timeout and animation frame IDs in refs and clear them on unmount:

+ const animationTimeoutRef = useRef<NodeJS.Timeout | null>(null);
+ const animationFrameRef = useRef<number | null>(null);
+
+ useEffect(() => {
+   return () => {
+     if (animationTimeoutRef.current) {
+       clearTimeout(animationTimeoutRef.current);
+     }
+     if (animationFrameRef.current) {
+       cancelAnimationFrame(animationFrameRef.current);
+     }
+   };
+ }, []);

  const openDrawer = useCallback((itemId: string) => {
    setActiveItem(itemId);
    setIsAnimating(true);
-   requestAnimationFrame(() => {
+   animationFrameRef.current = requestAnimationFrame(() => {
      setDrawerWidth(DEFAULT_DRAWER_WIDTH);
-     setTimeout(() => setIsAnimating(false), 300);
+     animationTimeoutRef.current = setTimeout(() => setIsAnimating(false), 300);
    });
  }, []);

  const closeDrawer = useCallback(() => {
    setIsAnimating(true);
    setDrawerWidth(0);
-   setTimeout(() => {
+   animationTimeoutRef.current = setTimeout(() => {
      setActiveItem(null);
      setIsAnimating(false);
    }, 300);
  }, []);
🧹 Nitpick comments (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (3)

51-78: Consider using structured error handling instead of console.error.

Per coding guidelines, avoid try-catch-all patterns with console.error. For localStorage operations that can fail (quota exceeded, private browsing), consider using runAsynchronously or at minimum, ensure the error is surfaced appropriately rather than silently logged.

 function loadConfig(): DashboardConfig {
   if (typeof window === 'undefined') return DEFAULT_CONFIG;
-  try {
-    const stored = localStorage.getItem(STORAGE_KEY);
-    if (stored) {
-      const parsed = JSON.parse(stored);
-      // Validate and merge with defaults for any new widgets
-      const validWidgetIds = new Set(AVAILABLE_WIDGETS.map(w => w.id));
-      const enabledWidgets = (parsed.enabledWidgets || []).filter((id: string) => validWidgetIds.has(id as WidgetId));
-      const widgetOrder = (parsed.widgetOrder || []).filter((id: string) => validWidgetIds.has(id as WidgetId));
-
-      // Add any new widgets that aren't in the stored config
-      for (const widget of AVAILABLE_WIDGETS) {
-        if (!widgetOrder.includes(widget.id)) {
-          widgetOrder.push(widget.id);
-          if (widget.defaultEnabled) {
-            enabledWidgets.push(widget.id);
-          }
-        }
-      }
-
-      return { enabledWidgets, widgetOrder };
-    }
-  } catch (e) {
-    console.error('Failed to load dashboard config:', e);
+  const stored = localStorage.getItem(STORAGE_KEY);
+  if (!stored) return DEFAULT_CONFIG;
+  
+  const parsed = JSON.parse(stored) as Partial<DashboardConfig> | null;
+  if (!parsed) return DEFAULT_CONFIG;
+  
+  // Validate and merge with defaults for any new widgets
+  const validWidgetIds = new Set(AVAILABLE_WIDGETS.map(w => w.id));
+  const enabledWidgets = (parsed.enabledWidgets || []).filter((id: string) => validWidgetIds.has(id as WidgetId));
+  const widgetOrder = (parsed.widgetOrder || []).filter((id: string) => validWidgetIds.has(id as WidgetId));
+
+  // Add any new widgets that aren't in the stored config
+  for (const widget of AVAILABLE_WIDGETS) {
+    if (!widgetOrder.includes(widget.id)) {
+      widgetOrder.push(widget.id);
+      if (widget.defaultEnabled) {
+        enabledWidgets.push(widget.id);
+      }
+    }
   }
+
+  return { enabledWidgets, widgetOrder };
   return DEFAULT_CONFIG;
 }

Alternatively, if you need to handle malformed JSON gracefully, extract the JSON parsing into a helper that returns null on failure rather than using try-catch at this level.


390-397: Grid width calculation assumes fixed gap value.

The calculation hardcodes gap = 20 for gap-5, but the comment mentions sm:gap-5 which means on smaller screens (below sm breakpoint), gap-4 (16px) applies. Since this calculation only runs on lg screens (isLgScreen), the value is correct, but consider adding a comment clarifying this.

   const calculateGlobeColumnWidth = () => {
     if (!gridContainerSize?.width) return 0;
-    const gap = 20; // gap-5 = 1.25rem = 20px on lg screens (sm:gap-5 applies)
+    const gap = 20; // gap-5 = 1.25rem = 20px; this calculation only runs on lg screens where sm:gap-5 is active
     const totalGaps = gap * 11; // 11 gaps between 12 columns

409-424: Multiple resize event listeners could be consolidated.

The component uses both useResizeObserver (line 385) and a manual resize event listener for viewport height. Consider using a single approach or extracting this into a custom hook for consistency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3faa4fb and 4f4f4bb.

📒 Files selected for processing (7)
  • AGENTS.md (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (4 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (7 hunks)
  • apps/dashboard/src/app/layout.tsx (1 hunks)
  • apps/dashboard/src/components/stack-companion.tsx (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • AGENTS.md
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Ensure code passes pnpm typecheck for TypeScript type validation
Never use toast for blocking alerts and errors; use alerts instead as they are more visible to users
Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI
Never use try-catch-all, never void a promise, never use .catch(console.error) or similar; use runAsynchronously or runAsynchronouslyWithAlert for error handling instead of try-catch blocks
Use loading indicators instead of try-catch blocks for asynchronous UI operations; button components like support async callbacks with built-in loading state
Use ES6 maps instead of records wherever possible

Files:

  • apps/dashboard/src/app/layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
  • apps/dashboard/src/components/stack-companion.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer client components and dynamic functions like usePathname over Next.js dynamic functions to keep pages static; avoid using await params or similar dynamic patterns

Files:

  • apps/dashboard/src/app/layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
  • apps/dashboard/src/components/stack-companion.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
🧠 Learnings (4)
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/*.{ts,tsx} : Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI

Applied to files:

  • apps/dashboard/src/components/stack-companion.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: When making dashboard changes, provide deep links to the dashboard page in the format `http://localhost:<$NEXT_PUBLIC_STACK_PORT_PREFIX>01/projects/-selector-/...` (or use a.localhost, b.localhost, c.localhost for port prefixes 91, 92, 93)

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/apps-{frontend,config}.{ts,tsx} : To update available apps, edit `apps-frontend.tsx` and `apps-config.ts`

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to apps/**/*.{ts,tsx} : Prefer client components and dynamic functions like `usePathname` over Next.js dynamic functions to keep pages static; avoid using `await params` or similar dynamic patterns

Applied to files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
🧬 Code graph analysis (4)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (2)
examples/lovable-react-18-example/src/lib/utils.ts (1)
  • cn (4-6)
packages/stack-shared/src/utils/unicode.tsx (1)
  • getFlagEmoji (3-10)
apps/dashboard/src/components/stack-companion.tsx (1)
apps/dashboard/src/lib/version-check.ts (2)
  • VersionCheckResult (5-8)
  • checkVersion (44-109)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (3)
packages/stack-shared/src/utils/dates.tsx (2)
  • isWeekend (5-7)
  • fromNow (28-30)
apps/dashboard/src/components/router.tsx (1)
  • useRouter (15-33)
packages/stack-ui/src/components/ui/typography.tsx (1)
  • Typography (47-47)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (4)
apps/dashboard/src/lib/apps-frontend.tsx (6)
  • DUMMY_ORIGIN (15-15)
  • ALL_APPS_FRONTEND (67-228)
  • getItemPath (45-48)
  • testItemPath (60-65)
  • getAppPath (40-43)
  • testAppPath (50-58)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (29-44)
packages/stack-shared/src/utils/objects.tsx (1)
  • typedEntries (263-265)
apps/dashboard/src/lib/env.tsx (1)
  • getPublicEnvVar (69-79)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Cursor Bugbot
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: build (22.x)
  • GitHub Check: all-good
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: docker
🔇 Additional comments (11)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (4)

42-62: Sizing and camera distance logic looks good.

The width-based globe sizing with linear interpolation for camera distance is well-documented with clear comments explaining the formula and thresholds. The GLOBE_MIN_WIDTH constant at 350px provides a sensible cutoff.


67-74: Good UX pattern for tooltip persistence.

Tracking previousSelectedCountry allows the tooltip to fade out gracefully rather than disappearing abruptly when the user moves away from a country. The effect correctly updates only when there's an active selection.


111-119: Verify camera controls access timing.

The effect correctly updates camera distance on resize. However, consider adding a null check for controls() return value, as the globe may not have fully initialized its controls in all scenarios.

   useEffect(() => {
     if (!globeRef.current || !shouldShowGlobe) return;
 
     const controls = globeRef.current.controls();
+    if (!controls) return;
     controls.maxDistance = cameraDistance;
     controls.minDistance = cameraDistance;
     globeRef.current.camera().position.z = cameraDistance;
   }, [cameraDistance, shouldShowGlobe]);

304-335: Tooltip implementation is clean.

The tooltip correctly uses lastSelectedCountry for content while using selectedCountry for opacity transitions. The flag emoji rendering with regex validation is a nice defensive touch. The fixed positioning with transform-based mouse tracking works well for this use case.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (1)

146-248: AppsWidget implementation is well-structured.

The responsive grid calculation with itemsPerRow based on container width is a good approach. The expand/collapse logic with "See all" / "Less" buttons provides good UX for many installed apps.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (4)

50-82: CustomTooltip is clean and well-formatted.

Good defensive handling for invalid dates with the isNaN check before formatting.


96-186: ActivityBarChart reduces duplication effectively.

Good extraction of the shared bar chart logic. The weekend coloring per chart name and compact mode support are well implemented.


517-518: Good use of ES6 Map for efficient lookups.

Using a Map for BRAND_CONFIG_MAP aligns with the coding guideline to prefer ES6 Maps over records where possible.


260-433: TabbedMetricsCard is well-structured with good tab state management.

The component handles chart/list view switching cleanly, with proper empty states and time range filtering. The color theming system with gradientColor prop provides good flexibility.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (2)

391-479: SidebarContent memoization and section expansion logic look solid.

The enabledApps memo, URL-based getDefaultExpandedSections, and Set<AppId>-backed expand/collapse logic are clean and should scale well with more apps. Using typedEntries and usePathname aligns with the apps/* guidelines to avoid dynamic route handlers.


483-593: Overall layout composition and responsive behavior look good.

The header/sheet + sticky sidebar + main content + StackCompanion split-screen arrangement is cohesive. Mobile vs desktop behavior via Sheet and lg/xl breakpoints, plus the PanelLeft-driven collapse, all read cleanly and follow the client-component + usePathname guidance.

Comment thread apps/dashboard/src/components/stack-companion.tsx
Comment thread apps/dashboard/src/components/stack-companion.tsx
Copy link
Copy Markdown
Contributor

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

♻️ Duplicate comments (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (2)

34-48: Consider reusing the shared isWeekend utility instead of duplicating logic

You’re reimplementing isWeekend locally even though there’s already a shared helper in packages/stack-shared/src/utils/dates.tsx (and you’re importing fromNow from the same module). To keep a single source of truth and avoid subtle divergence over time, consider delegating to the shared function:

-import { fromNow } from '@stackframe/stack-shared/dist/utils/dates';
+import { fromNow, isWeekend as isWeekendDate } from '@stackframe/stack-shared/dist/utils/dates';
@@
-const isWeekend = (dateString: string): boolean => {
-  const date = new Date(dateString);
-  if (isNaN(date.getTime())) return false;
-  const dayOfWeek = date.getDay();
-  return dayOfWeek === 0 || dayOfWeek === 6; // Sunday (0) or Saturday (6)
-};
+const isWeekend = (dateString: string): boolean => {
+  const date = new Date(dateString);
+  if (isNaN(date.getTime())) return false;
+  return isWeekendDate(date);
+};

This keeps the string-parsing behavior local while centralizing the “weekend” definition.


490-494: Fix unsafe BRAND_CONFIG access for auth methods and harden legend mapping

In the auth‑methods donut, AuthMethodDatapoint.method is now typed as string, so it can legitimately contain values that aren’t keys in BRAND_CONFIG. The legend color currently does:

style={{ backgroundColor: BRAND_CONFIG[item.method as keyof typeof BRAND_CONFIG].color ?? "var(--color-other)" }}

If item.method is not defined in BRAND_CONFIG, BRAND_CONFIG[item.method] is undefined, and accessing .color throws at runtime. This is the same issue that was previously flagged. You already have BRAND_CONFIG_MAP for safe lookups; you can use it (with a fallback) for both color and label:

-            <div className={cn("flex w-full flex-wrap justify-center gap-2 shrink-0", compact ? "mt-3" : "mt-4")}>
-              {datapoints.map((item) => {
-                const percentage = total > 0 ? ((item.count / total) * 100).toFixed(0) : 0;
-                return (
+            <div className={cn("flex w-full flex-wrap justify-center gap-2 shrink-0", compact ? "mt-3" : "mt-4")}>
+              {datapoints.map((item) => {
+                const percentage = total > 0 ? ((item.count / total) * 100).toFixed(0) : 0;
+                const brandConfig = BRAND_CONFIG_MAP.get(item.method) ?? BRAND_CONFIG.other;
+                return (
@@
-                    <span
-                      className="h-2 w-2 rounded-full shrink-0"
-                      style={{ backgroundColor: BRAND_CONFIG[item.method as keyof typeof BRAND_CONFIG].color ?? "var(--color-other)" }}
-                    />
-                    <span className="font-medium text-foreground">
-                      {BRAND_CONFIG_MAP.get(item.method)?.label ?? item.method}
-                    </span>
+                    <span
+                      className="h-2 w-2 rounded-full shrink-0"
+                      style={{ backgroundColor: brandConfig.color ?? "var(--color-other)" }}
+                    />
+                    <span className="font-medium text-foreground">
+                      {brandConfig.label ?? item.method}
+                    </span>

This removes the crash for unknown methods and keeps color/label logic centralized. Optionally, if upstream data can emit arbitrary new methods, you might also normalize them to an 'other' bucket earlier so colors and labels stay fully consistent with BRAND_CONFIG.

Also applies to: 495-519, 533-647

🧹 Nitpick comments (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (3)

50-82: Time‑range filtering and bar chart rendering look solid; be aware of ordering assumptions

CustomTooltip, filterDatapointsByTimeRange, and ActivityBarChart are well-structured: date parsing is defensive, empty data is handled, and weekend coloring is cleanly isolated.

One small assumption to keep in mind: filterDatapointsByTimeRange uses slice(-7) / slice(-30), so it relies on datapoints being chronologically ordered and roughly one point per day. If upstream data can be unsorted or have gaps, you may eventually want to switch to a true date-based filter rather than “last N points.”

Also applies to: 84-93, 95-186


226-258: Time range toggle is clear; consider minor accessibility semantics

The 7d / 30d / All toggle is visually clear and easy to use. For keyboard and screen‑reader users, you might consider adding tab/segment semantics, e.g. role="tablist" on the wrapper and role="tab" + aria-selected or aria-pressed on the buttons, to better convey the current selection state. This is a nice‑to‑have, not blocking.


260-432: Avoid coupling behavior to config.name === 'Daily Active Users'

Using the display name string to drive behavior:

config.name === 'Daily Active Users'
  ? user.last_active_at_millis ...
  : user.signed_up_at_millis ...

makes the component fragile to copy changes—if the name is reworded, the logic silently flips branches. Consider adding a stable field to LineChartDisplayConfig (e.g. kind: 'dau' | 'signups') or a boolean like usesLastActive and branching on that instead, keeping name purely presentational. The rest of the component (time‑range totals, empty states, routing) looks good.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4f4f4bb and 166e3af.

📒 Files selected for processing (2)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (2 hunks)
  • apps/dashboard/src/app/layout.tsx (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/dashboard/src/app/layout.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Ensure code passes pnpm typecheck for TypeScript type validation
Never use toast for blocking alerts and errors; use alerts instead as they are more visible to users
Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI
Never use try-catch-all, never void a promise, never use .catch(console.error) or similar; use runAsynchronously or runAsynchronouslyWithAlert for error handling instead of try-catch blocks
Use loading indicators instead of try-catch blocks for asynchronous UI operations; button components like support async callbacks with built-in loading state
Use ES6 maps instead of records wherever possible

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer client components and dynamic functions like usePathname over Next.js dynamic functions to keep pages static; avoid using await params or similar dynamic patterns

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx
🧬 Code graph analysis (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (3)
packages/stack-shared/src/utils/dates.tsx (2)
  • isWeekend (5-7)
  • fromNow (28-30)
apps/dashboard/src/components/router.tsx (1)
  • useRouter (15-33)
packages/stack-ui/src/components/ui/typography.tsx (1)
  • Typography (47-47)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Cursor Bugbot
  • GitHub Check: build (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: docker
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: setup-tests
  • GitHub Check: all-good
  • GitHub Check: build (22.x)
  • GitHub Check: build (22.x)
🔇 Additional comments (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (2)

188-224: ChartCard implementation matches the visual/interaction guidelines

The glassmorphic layering, subtle hover tint, and short duration-150 transitions (with hover:transition-none) fit the “snappy, non-blocking transitions” guideline and provide a nice reusable shell for charts. No changes needed here.


435-488: LineChartDisplay refactor reuses shared pieces cleanly

The refactored LineChartDisplay nicely reuses ActivityBarChart, centralizes the time‑range filtering, and provides reasonable empty‑state messaging. The required timeRange prop keeps the behavior explicit. No issues from this component on its own.

Copy link
Copy Markdown
Contributor

@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

♻️ Duplicate comments (3)
apps/dashboard/src/components/app-square.tsx (2)

79-89: Error handling remains incomplete—no user feedback on failure.

While console.error and alert were removed per the previous review, errors thrown by project.updateConfig still propagate without being caught. When called from line 183 with void performToggle(), these errors are silently swallowed, leaving users with no feedback.

Per coding guidelines and learnings, either:

  1. Let the Button component handle async operations with its built-in error handling (if it uses runAsynchronouslyWithAlert internally), or
  2. Wrap the call in runAsynchronouslyWithAlert to show alerts on failure.

Consider simplifying by removing manual isProcessing state if the Button component's loading prop is driven by its internal async handling:

+import { runAsynchronouslyWithAlert } from "@stackframe/stack-shared/dist/utils/promises";
+
-  const performToggle = async () => {
-    setIsProcessing(true);
-    try {
-      await project.updateConfig({
-        [`apps.installed.${appId}.enabled`]: !isEnabled,
-      });
-      onToggleEnabled?.(!isEnabled);
-    } finally {
-      setIsProcessing(false);
-    }
+  const performToggle = async () => {
+    await project.updateConfig({
+      [`apps.installed.${appId}.enabled`]: !isEnabled,
+    });
+    onToggleEnabled?.(!isEnabled);
   };

Then update the Button at lines 138-153 to use runAsynchronouslyWithAlert:

           <Button
-            onClick={handleToggleEnabled}
-            loading={isProcessing}
+            onClick={(e) => {
+              e.preventDefault();
+              e.stopPropagation();
+              if (!isEnabled && app.stage !== "stable") {
+                setShowWarningModal(true);
+                return;
+              }
+              runAsynchronouslyWithAlert(performToggle);
+            }}

Based on learnings, the Button component supports async callbacks with built-in loading state.


214-227: Remove pointless try-catch-rethrow pattern (regression).

The catch block only re-throws, making the try-catch semantically useless. This was previously flagged and marked as addressed in commit a8b2dff, but the pattern has reappeared.

Per coding guidelines: "Use loading indicators instead of try-catch blocks for asynchronous UI operations; button components like support async callbacks with built-in loading state."

Simplify to:

 const handleEnable = async (e: React.MouseEvent) => {
   e.preventDefault();
   e.stopPropagation();
-
-  setIsLoading(true);
-  try {
-    await onEnable?.();
-  } catch (error) {
-    // Re-throw to let Button component handle it via runAsynchronouslyWithAlert
-    throw error;
-  } finally {
-    setIsLoading(false);
-  }
+  return onEnable?.();
 };

If the Button component internally uses runAsynchronouslyWithAlert for its onClick handler, you can also remove the manual isLoading state and loading prop. Otherwise, wrap the button's onClick with runAsynchronouslyWithAlert as shown in the earlier comment.

Based on learnings, the Button component should handle async operations and loading state automatically.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (1)

568-599: Handle unknown auth methods safely and avoid potential crashes in tooltip.

The tooltip formatter has three robustness issues that can cause crashes or incorrect rendering:

  1. Missing optional chaining on item (Line 569): The code accesses item.payload without checking if item exists. If Recharts passes undefined, this will crash.

  2. Unsafe property access (Line 570): Using BRAND_CONFIG[key as keyof typeof BRAND_CONFIG] provides no runtime protection when key is an unknown method. The type cast only satisfies TypeScript.

  3. CSS variable without fallback (Lines 581, 598): Using var(--color-${key}) and var(--color-${x.method}) assumes CSS variables exist for all methods. Unknown methods from the backend will render incorrectly.

Your legend (lines 623, 626) already demonstrates the correct pattern: BRAND_CONFIG_MAP.get(item.method)?.color ?? "var(--color-other)".

Apply these changes to use the same safe pattern throughout:

                      formatter={(value, _name, item) => {
-                        const key = (item.payload as AuthMethodDatapoint | undefined)?.method;
-                        const brandConfig = key ? BRAND_CONFIG[key as keyof typeof BRAND_CONFIG] : undefined;
+                        const key = (item?.payload as AuthMethodDatapoint | undefined)?.method;
+                        const brandConfig = key ? BRAND_CONFIG_MAP.get(key) : undefined;
                        const label = brandConfig?.label || _name;

                        if (typeof value !== "number" || !key) {
                          return null;
                        }

                        return (
                          <div className="flex items-center gap-2.5">
                            <span
                              className="h-2 w-2 rounded-full ring-2 ring-white/20"
-                              style={{ backgroundColor: `var(--color-${key})` }}
+                              style={{ backgroundColor: brandConfig?.color ?? BRAND_CONFIG.other.color }}
                            />

And for the pie segments:

                <Pie
-                  data={datapoints.map(x => ({
+                  data={datapoints.map((x) => {
+                    const brand = BRAND_CONFIG_MAP.get(x.method);
+                    return {
                    ...x,
-                    fill: `var(--color-${x.method})`
-                  }))}
+                    fill: brand?.color ?? BRAND_CONFIG.other.color,
+                    };
+                  })}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 893c014 and 4d90b1f.

📒 Files selected for processing (2)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (2 hunks)
  • apps/dashboard/src/components/app-square.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Ensure code passes pnpm typecheck for TypeScript type validation
Never use toast for blocking alerts and errors; use alerts instead as they are more visible to users
Keep hover/click transitions snappy and fast; apply transitions after the action (e.g., smooth fade-out on hover end) rather than delaying actions with pre-transitions to avoid sluggish UI
Never use try-catch-all, never void a promise, never use .catch(console.error) or similar; use runAsynchronously or runAsynchronouslyWithAlert for error handling instead of try-catch blocks
Use loading indicators instead of try-catch blocks for asynchronous UI operations; button components like support async callbacks with built-in loading state
Use ES6 maps instead of records wherever possible

Files:

  • apps/dashboard/src/components/app-square.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx
apps/**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer client components and dynamic functions like usePathname over Next.js dynamic functions to keep pages static; avoid using await params or similar dynamic patterns

Files:

  • apps/dashboard/src/components/app-square.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx
🧠 Learnings (5)
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/apps-{frontend,config}.{ts,tsx} : To update available apps, edit `apps-frontend.tsx` and `apps-config.ts`

Applied to files:

  • apps/dashboard/src/components/app-square.tsx
📚 Learning: 2025-10-11T04:13:19.308Z
Learnt from: N2D4
Repo: stack-auth/stack-auth PR: 943
File: examples/convex/app/action/page.tsx:23-28
Timestamp: 2025-10-11T04:13:19.308Z
Learning: In the stack-auth codebase, use `runAsynchronouslyWithAlert` from `stackframe/stack-shared/dist/utils/promises` for async button click handlers and form submissions instead of manual try/catch blocks. This utility automatically handles errors and shows alerts to users.

Applied to files:

  • apps/dashboard/src/components/app-square.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/*.{ts,tsx} : Never use try-catch-all, never void a promise, never use .catch(console.error) or similar; use `runAsynchronously` or `runAsynchronouslyWithAlert` for error handling instead of try-catch blocks

Applied to files:

  • apps/dashboard/src/components/app-square.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/*.{ts,tsx} : Never use `toast` for blocking alerts and errors; use alerts instead as they are more visible to users

Applied to files:

  • apps/dashboard/src/components/app-square.tsx
📚 Learning: 2025-11-25T02:09:03.104Z
Learnt from: CR
Repo: stack-auth/stack-auth PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-11-25T02:09:03.104Z
Learning: Applies to **/*.{ts,tsx} : Use loading indicators instead of try-catch blocks for asynchronous UI operations; button components like <Button> support async callbacks with built-in loading state

Applied to files:

  • apps/dashboard/src/components/app-square.tsx
🧬 Code graph analysis (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (3)
packages/stack-shared/src/utils/dates.tsx (2)
  • isWeekend (5-7)
  • fromNow (28-30)
apps/dashboard/src/components/router.tsx (1)
  • useRouter (15-33)
packages/stack-ui/src/components/ui/typography.tsx (1)
  • Typography (47-47)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: Vercel Agent Review
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: restart-dev-and-test
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: docker
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: build (22.x)
  • GitHub Check: all-good
  • GitHub Check: restart-dev-and-test-with-custom-base-port
  • GitHub Check: build (22.x)

Comment thread apps/dashboard/src/components/app-square.tsx
@N2D4 N2D4 merged commit c2992f3 into dev Nov 28, 2025
20 of 22 checks passed
@N2D4 N2D4 deleted the dashboard-redesign branch November 28, 2025 21:21
This was referenced Feb 3, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Feb 24, 2026
@coderabbitai coderabbitai Bot mentioned this pull request Apr 3, 2026
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.

4 participants