Skip to content

Apps frontend#945

Merged
N2D4 merged 27 commits intodevfrom
apps-frontend
Oct 17, 2025
Merged

Apps frontend#945
N2D4 merged 27 commits intodevfrom
apps-frontend

Conversation

@N2D4
Copy link
Contributor

@N2D4 N2D4 commented Oct 11, 2025

High-level PR Summary

This PR introduces a comprehensive apps system to the Stack Auth dashboard, implementing an app store-like interface where users can discover, enable, and manage modular features. The changes include creating an apps infrastructure with metadata definitions, frontend routing and navigation, visual app cards with icons and screenshots, a dedicated app browsing page, modal-based app details views, and integration of installed apps into the project sidebar. The overview page now displays installed and suggested apps, and the sidebar has been completely refactored to dynamically show navigation items based on enabled apps rather than static menu items.

⏱️ Estimated Review Time: 30-90 minutes

💡 Review Order Suggestion
Order File Path
1 packages/stack-shared/src/apps/apps-config.ts
2 apps/dashboard/src/lib/apps-frontend.tsx
3 apps/dashboard/src/components/app-square.tsx
4 apps/dashboard/src/components/app-store-entry.tsx
5 apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page.tsx
6 apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx
7 apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page.tsx
8 apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
9 apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page.tsx
10 apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsx
11 apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/default.tsx
12 apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsx
13 apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
14 apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
15 apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/page.tsx
16 packages/stack-shared/src/utils/urls.tsx
17 packages/stack-ui/src/components/ui/dialog.tsx
18 apps/dashboard/src/components/link.tsx
19 apps/dashboard/src/components/project-switcher.tsx
20 packages/stack-shared/src/utils/react.tsx
21 .claude/settings.json
22 apps/dashboard/public/app-bg-dark.png
23 apps/dashboard/public/app-bg-light.png
24 apps/dashboard/public/convex-logo.png
25 apps/dashboard/public/neon-logo.png
⚠️ Inconsistent Changes Detected
File Path Warning
.claude/settings.json Configuration change to Claude settings appears unrelated to the apps frontend feature
packages/stack-shared/src/utils/react.tsx Removal of console.log statements is a cleanup task unrelated to the apps frontend feature

Need help? Join our Discord

Analyze latest changes

Summary by CodeRabbit

  • New Features

    • Apps overview with Installed and Suggested sections, app tiles, details pages and modal; store entries with screenshots and enable/disable actions.
    • Launch Checklist page and payments onboarding (welcome/catalogs) flow.
  • Bug Fixes

    • Pages now respect app enablement (UI gated when an app is disabled).
  • Refactor

    • Sidebar navigation driven by installed apps; layout supports an optional modal region.
  • Style

    • Project switcher groups avatar and name.
  • Tests

    • URL/path relationship utilities added with tests.
  • Chore

    • Public hook key renamed (non-functional).

Important

Introduces an apps system to the Stack Auth dashboard for managing modular features, with frontend updates for app discovery, enabling, and navigation.

  • Behavior:
    • Introduces an apps system in apps/dashboard for discovering, enabling, and managing features.
    • Adds AppSquare and AppStoreEntry components for app display and interaction.
    • Updates sidebar-layout.tsx to dynamically show navigation based on enabled apps.
    • Adds modal-based app details views in page-client.tsx and page.tsx.
    • Displays installed and suggested apps in metrics-page.tsx.
  • Models:
    • Defines app metadata in apps-config.ts and apps-frontend.tsx.
    • Adds app tags and stages in apps-config.ts.
  • Misc:
    • Refactors layout to support optional modal region in layout.tsx.
    • Changes in .claude/settings.json unrelated to apps feature.
    • Removes console.log statements in react.tsx.

This description was created by Ellipsis for 4a308f5. You can customize this summary. It will automatically update as commits are pushed.

Copilot AI review requested due to automatic review settings October 11, 2025 01:33
@vercel
Copy link

vercel bot commented Oct 11, 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 Oct 17, 2025 3:28am
stack-dashboard Ready Ready Preview Comment Oct 17, 2025 3:28am
stack-demo Ready Ready Preview Comment Oct 17, 2025 3:28am
stack-docs Ready Ready Preview Comment Oct 17, 2025 3:28am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 11, 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 an apps ecosystem to the dashboard (apps index, per-app pages, modal, UI components, frontend registry, routing helpers), refactors sidebar to drive navigation from apps, introduces AppEnabledGuard used across many pages, updates shared apps config/tags and URL utilities, tweaks dialog/link APIs, and renames a .claude hook key.

Changes

Cohort / File(s) Change summary
Apps pages & routes
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page.tsx, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page.tsx, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page.tsx, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsx, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/default.tsx, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
Adds Apps index, per‑app detail pages and modal, renders installed/suggested app grids, and implements enable flow that updates project config then navigates to the app route.
Sidebar & navigation refactor
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
Replaces static navigation with dynamic app-driven sections, NavItem/NavSubItem components, expansion state, breadcrumb resolution helpers, and bottom actions (Explore Apps, Settings).
App UI components
apps/dashboard/src/components/app-square.tsx, apps/dashboard/src/components/app-store-entry.tsx
New AppSquare/AppIcon (sizing constants, gradient icons, context menu, enable/disable actions) and AppStoreEntry (header, screenshots gallery, description, enable button).
Frontend registry & routing helpers
apps/dashboard/src/lib/apps-frontend.tsx
Adds AppFrontend type, ALL_APPS_FRONTEND registry, getAppPath/getItemPath/testAppPath/testItemPath helpers, SVG icon utilities, screenshots and navigation metadata.
App enablement guard (client)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx
New AppEnabledGuard component that checks project config for app enablement and redirects or shows a not-enabled message.
Pages wrapped by guard
Multiple files under apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/* (e.g., api-keys/page-client.tsx, auth-methods/page-client.tsx, data-vault/**, email-*/*, teams/**, payments/**, rbac/**, workflows/**, webhooks/**, etc.)
Many existing pages are refactored to render inside AppEnabledGuard for their associated app id (guarding access and reflowing some UI into the guarded subtree).
Payments onboarding & drafts
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client.tsx, .../page-client-catalogs-view.tsx, .../page-client-list-view.tsx
Introduces multi-view (welcome, catalogs, list), welcome onboarding and draft provisioning props/flow; CatalogView/PageClient signatures expanded to accept draft-related props; removes WelcomeScreen in one variant and consolidates flows.
Launch Checklist
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx, .../page.tsx
New Launch Checklist page and client UI with tasks, progress, provider guides, and actions (AppEnabledGuard used).
Layout changes
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsx, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/page.tsx
Layout accepts optional modal prop and renders it; overview metadata annotated; some page components made async to await params and redirect to absolute project paths.
Components: Link & project-switcher
apps/dashboard/src/components/link.tsx, apps/dashboard/src/components/project-switcher.tsx
Link converted to forwardRef, adds optional scroll prop and ref handling; project-switcher minor DOM restructuring and unused hook removals.
Apps frontend & shared config/types
packages/stack-shared/src/apps/apps-config.ts, packages/stack-shared/src/apps/*
Adds AppTag type and ALL_APP_TAGS, constrains app.tags to tag keys, exports AppId type, removes prior per-app type field, and updates ALL_APPS entries/tags/stages.
URL & react utilities
packages/stack-shared/src/utils/urls.tsx, packages/stack-shared/src/utils/react.tsx
Adds isChildUrl and isChildPath utilities (with tests); removes debug console.log calls from useRefState.
UI dialog API
packages/stack-ui/src/components/ui/dialog.tsx
DialogContent gains optional noCloseButton?: boolean prop; close button rendered conditionally.
Small/formatting edits
Various (e.g., payments/customers/import ordering, trailing newline removal)
Import reordering and minor formatting tweaks without behavior changes.
Internal config
.claude/settings.json
Renamed hook key "Stop""no-longer-used-Stop" under hooks without changing its value.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor U as User
  participant AppsPage as Apps Index
  participant Tile as AppSquare / AppStoreEntry
  participant Config as Project Config
  participant Router as Router

  U->>AppsPage: Open Apps index
  AppsPage->>Tile: Render installed & suggested tiles
  U->>Tile: Open app details (page or modal)
  Tile-->>U: Show details + Enable button
  U->>Tile: Click "Enable"
  Tile->>Config: update apps.installed.{appId}.enabled = true (async)
  Config-->>Tile: update confirmed
  Tile->>Router: router.push(getAppPath(projectId, appFrontend))
  Router-->>U: navigated to app route
Loading
sequenceDiagram
  autonumber
  actor U as User
  participant Page as Any guarded Page
  participant Guard as AppEnabledGuard
  participant Router as Router

  U->>Page: Request page URL
  Page->>Guard: Check isEnabled from project config
  alt enabled
    Guard-->>Page: render children (app UI)
  else disabled
    Guard->>Router: router.push(app overview or app page)
    Guard-->>Page: render "App not enabled" message inside PageLayout
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

Poem

I’m a rabbit in the code, I hop through tiles and trees,
I nudge a config, flip a switch, and scatter breadcrumbs with ease.
Modals open, guards stand watch, the sidebar learns to play,
Apps bloom on the dashboard — I hop off, glad today. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The PR title "Apps frontend" is vague and generic, lacking specificity about what was actually changed. While it correctly references "apps," which is the main subject matter of the changeset, it does not clarify whether this is introducing, adding, refactoring, or modifying the apps frontend. The title reads similarly to non-descriptive terms like "misc updates" in that it provides minimal information about the nature of the change—a reader scanning the PR history would not understand from this title alone that the PR introduces a comprehensive app store interface, app discovery, feature management, or dynamic navigation infrastructure. Consider revising the PR title to be more specific and descriptive. A title like "Introduce comprehensive apps system for discovering and managing dashboard features" or "Add app store interface with app discovery, enablement, and dynamic navigation" would clearly communicate the main intent and scope of the changeset. This would help reviewers and future maintainers quickly understand the primary contribution without needing to inspect the detailed changes.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed The PR description exceeds the minimal requirements of the repository's template, which only asks authors to confirm they have read CONTRIBUTING.md. The provided description includes a high-level summary explaining the apps system and its purpose, detailed sections on behavior, models, and miscellaneous changes, a suggested review order with a prioritized file list, notes on inconsistent changes, and extensive breakdown of what was added or modified. While some sections appear to be auto-generated by the Ellipsis tool, the human author has explicitly included this comprehensive information in the PR description, making it informative and well-structured overall.
✨ 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 apps-frontend

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.

Copy link

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 adds a comprehensive app store frontend for the Stack authentication platform, enabling users to browse, view details, and install applications. It introduces an app-based architecture with navigation improvements and modal dialogs for app management.

  • Implementation of app store UI with categorized browsing, app details modal, and installation workflows
  • Enhanced navigation sidebar with app-based sections and improved layout responsiveness
  • New utility functions for URL handling and React component patterns

Reviewed Changes

Copilot reviewed 21 out of 25 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/stack-ui/src/components/ui/dialog.tsx Adds optional noCloseButton prop to disable dialog close button
packages/stack-shared/src/utils/urls.tsx Adds URL and path validation utilities for nested routing
packages/stack-shared/src/utils/react.tsx Removes debug console.log statements
packages/stack-shared/src/apps/apps-config.ts Refactors app configuration with new tag system and updated metadata
apps/dashboard/src/lib/apps-frontend.tsx Creates frontend configuration mapping for apps with icons and navigation
apps/dashboard/src/components/project-switcher.tsx Adds visual wrapper div and removes unused imports
apps/dashboard/src/components/link.tsx Converts to forwardRef pattern for better ref handling
apps/dashboard/src/components/app-store-entry.tsx Implements app store entry component with screenshots and installation
apps/dashboard/src/components/app-square.tsx Creates app icon and square components for app grid display
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx Major refactor to app-based navigation with collapsible sections
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsx Adds modal slot for parallel routing support
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page.tsx App store listing page
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx Client component for app store with tag-based categorization
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page.tsx Individual app details page
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx Client component for app details view
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/default.tsx Default modal slot component
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page.tsx Modal intercept route for app details
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsx Modal version of app details component
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/page.tsx Adds metadata type and JSX wrapper
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx Integrates app squares into overview dashboard
.claude/settings.json Configuration file change
Comments suppressed due to low confidence (1)

apps/dashboard/src/components/app-square.tsx:1

  • Direct access to window.location.href will cause hydration mismatch in SSR environments. Use usePathname() hook from Next.js navigation instead.
import { useAdminApp } from "@/app/(main)/(protected)/projects/[projectId]/use-admin-app";

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

…/sidebar-layout.tsx

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
return template.displayName;
}
const isActive = "type" in item && item.regex.test(pathname);
console.log("isActive", { item, isActive, pathname });
Copy link
Contributor

Choose a reason for hiding this comment

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

Remove the debug console.log present in the NavItem component. Debug logs should not be left in production code.

Suggested change
console.log("isActive", { item, isActive, pathname });


const handleEnable = async () => {
await wait(1000);
await project.updateConfig({
Copy link
Contributor

Choose a reason for hiding this comment

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

The async function handleEnable does not include error handling. Consider wrapping the updateConfig call in a try-catch block to gracefully handle potential failures and inform the user if enabling the app fails.

Copy link

@recurseml recurseml bot left a comment

Choose a reason for hiding this comment

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

Review by RecurseML

🔍 Review performed on 7416a37..46a5272

  Severity     Location     Issue     Delete  
Medium apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx:61 <- Click for details
✅ Files analyzed, no issues (20)

.claude/settings.json
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/page.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/default.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsx
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
apps/dashboard/src/components/app-square.tsx
apps/dashboard/src/components/app-store-entry.tsx
apps/dashboard/src/components/link.tsx
apps/dashboard/src/components/project-switcher.tsx
apps/dashboard/src/lib/apps-frontend.tsx
packages/stack-shared/src/apps/apps-config.ts
packages/stack-shared/src/utils/react.tsx
packages/stack-shared/src/utils/urls.tsx
packages/stack-ui/src/components/ui/dialog.tsx

const suggestedApps = typedEntries(ALL_APPS)
.filter(([_, app]) => app.stage === "stable")
.map(([appId]) => appId)
.filter((appId) => !config.apps.installed[appId].enabled);
Copy link

Choose a reason for hiding this comment

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

Runtime error: Cannot read property 'enabled' of undefined. The code attempts to access config.apps.installed[appId].enabled for all appIds in ALL_APPS. However, according to the schema (packages/stack-shared/src/config/schema.ts line 469), config.apps.installed is initialized with entries for all apps in ALL_APPS with enabled: false by default. This means that all apps should have an entry in config.apps.installed, making this safe.

Wait, let me reconsider. The code in line 469 shows:

installed: typedFromEntries(appIds.map(appId => [appId, { enabled: false }]))

where appIds is defined as Object.keys(ALL_APPS) (line 92). This means config.apps.installed should always have an entry for every appId in ALL_APPS with at minimum { enabled: false }. Therefore, accessing config.apps.installed[appId].enabled should be safe, as every appId will have an entry.

Actually, looking more closely at the backend code (apps/backend/src/lib/projects.tsx lines 227-230), when creating a project, only authentication and emails apps are set to enabled. But the schema default at line 469 ensures ALL apps have entries with enabled: false by default.

So this should be safe. Let me look for other issues.


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

Copy link
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.

Greptile Overview

Summary

This PR implements a comprehensive app store frontend with dynamic navigation. It adds an apps explorer page where users can browse, enable, and manage apps organized by tags.

Key Changes:

  • Created new apps-frontend.tsx defining frontend configuration for all apps with navigation items, icons, and routing logic
  • Refactored sidebar navigation from static hardcoded items to dynamic app-based sections that expand/collapse
  • Added apps explorer page (/apps) with app grid grouped by categories (Authentication, Integration, etc.)
  • Implemented app detail modals for viewing and enabling apps
  • Added URL hierarchy utilities (isChildUrl, isChildPath) for matching navigation paths
  • Refactored app configuration to remove redundant type field and reorganize tags

Implementation Details:

  • Apps are now dynamically loaded based on project config's apps.installed settings
  • Sidebar builds collapsible sections for each enabled app with their navigation items
  • Context menu on app squares allows opening in new tab or toggling enabled state
  • Modal flow for enabling disabled apps with descriptions and screenshots

Confidence Score: 4/5

  • This PR is safe to merge after removing the debug console.log statement
  • The implementation is well-structured with proper use of Maps/Sets for dynamic keys (following custom rules), comprehensive test coverage for URL utilities, and safe URL construction from hardcoded configs. The only issue is a debug console.log that should be removed before merging.
  • sidebar-layout.tsx:139 - Remove console.log statement

Important Files Changed

File Analysis

Filename Score Overview
apps/dashboard/src/lib/apps-frontend.tsx 5/5 New file defining frontend configuration for apps with hardcoded navigation paths and icons
packages/stack-shared/src/apps/apps-config.ts 5/5 Refactored app configuration to remove type field and update tag categorizations
packages/stack-shared/src/utils/urls.tsx 5/5 Added URL hierarchy checking utilities with comprehensive test coverage
apps/dashboard/src/components/app-square.tsx 5/5 New app square component with icon rendering, enable/disable functionality, and context menu
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx 4/5 Major refactor of sidebar to dynamic app-based navigation with collapsible sections

Sequence Diagram

sequenceDiagram
    participant User
    participant AppsPage as Apps Page
    participant AppSquare as App Square
    participant Modal as App Modal
    participant Config as Project Config
    participant Sidebar as Dynamic Sidebar

    User->>AppsPage: Navigate to /apps
    AppsPage->>Config: Load installed apps
    Config-->>AppsPage: Return enabled apps
    AppsPage->>AppsPage: Group apps by tags
    AppsPage->>User: Display app grid

    User->>AppSquare: Click disabled app
    AppSquare->>Modal: Open app details modal
    Modal->>User: Show app info & enable button

    User->>Modal: Click Enable
    Modal->>Config: updateConfig(apps.installed.{appId}.enabled = true)
    Config-->>Modal: Config updated
    Modal->>User: Close modal & navigate back

    User->>Sidebar: View sidebar
    Sidebar->>Config: Load enabled apps
    Config-->>Sidebar: Return app list
    Sidebar->>Sidebar: Build dynamic nav sections
    Sidebar->>User: Display collapsible app sections

    User->>AppSquare: Right-click enabled app
    AppSquare->>User: Show context menu
    User->>AppSquare: Select "Open in new tab"
    AppSquare->>User: window.open(appPath)
Loading

21 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

return user.displayName ?? user.primaryEmail ?? user.id;
}
}
const subItemsRef = useRef<any>(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

style: Remove debug console.log before merging

Suggested change
const subItemsRef = useRef<any>(null);
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
Line: 139:139

Comment:
**style:** Remove debug console.log before merging

```suggestion
```

How can I resolve this? If you propose a fix, please make it concise.

cursor[bot]

This comment was marked as outdated.

Copy link
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: 7

Caution

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

⚠️ Outside diff range comments (2)
.claude/settings.json (1)

40-49: Restore the Stop hook key.

Renaming the hook to no-longer-used-Stop detaches it from Claude’s recognized Stop lifecycle event, so the typecheck/lint command will never fire. Please keep the key as Stop (or remove the block entirely if it’s truly obsolete).

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

49-55: Rename the local breadcrumb type to avoid redeclaration errors

BreadcrumbItem is already imported from @stackframe/stack-ui. Redeclaring it here shadows the import and Biome rightfully fails the build. Rename the local type to keep both definitions usable.

-type BreadcrumbItem = { item: React.ReactNode, href: string };
+type BreadcrumbEntry = { item: React.ReactNode, href: string };
♻️ Duplicate comments (3)
apps/dashboard/src/components/link.tsx (1)

28-35: Fix duplicate onClick invocation when needConfirm is true.

The current logic calls onClick twice when needConfirm is true: once inside the conditional block (line 31) and again unconditionally (line 34). This causes side effects to execute twice.

Apply this diff to fix:

     onClick={(e: React.MouseEvent<HTMLAnchorElement>) => {
       if (needConfirm) {
         e.preventDefault();
         onClick?.();
         router.push(href);
+        return;
       }
       onClick?.();
     }}
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsx (2)

16-22: Handle enable failures gracefully

project.updateConfig can reject (network/server issues). Right now the rejection bubbles into an unhandled promise and the user never learns the app failed to enable. Please guard the call and surface an alert so the flow degrades safely.

   const handleEnable = async () => {
-    await wait(1000);
-    await project.updateConfig({
-      [`apps.installed.${appId}.enabled`]: true,
-    });
-    router.back();
+    try {
+      await wait(1000);
+      await project.updateConfig({
+        [`apps.installed.${appId}.enabled`]: true,
+      });
+      router.back();
+    } catch (error) {
+      console.error("Failed to enable app", error);
+      alert("Failed to enable the app. Please try again.");
+    }
   };

25-31: Restore an accessible close affordance

With noCloseButton set, the dialog has no visible/focusable control to dismiss it—keyboard and assistive tech users are stranded unless they discover Escape/back. Please restore the built-in close button (or add an equivalent control inside the content) so the modal remains accessible.

-    <Dialog open={true} onOpenChange={(open) => !open && router.back()} modal>
-      <DialogContent className="max-w-4xl max-h-[90vh] overflow-hidden p-0" noCloseButton>
+    <Dialog open={true} onOpenChange={(open) => !open && router.back()} modal>
+      <DialogContent className="max-w-4xl max-h-[90vh] overflow-hidden p-0">
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7416a37 and 2712e09.

⛔ Files ignored due to path filters (4)
  • apps/dashboard/public/app-bg-dark.png is excluded by !**/*.png
  • apps/dashboard/public/app-bg-light.png is excluded by !**/*.png
  • apps/dashboard/public/convex-logo.png is excluded by !**/*.png
  • apps/dashboard/public/neon-logo.png is excluded by !**/*.png
📒 Files selected for processing (21)
  • .claude/settings.json (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/page.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/default.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/[appId]/page.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]/apps/page.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (9 hunks)
  • apps/dashboard/src/components/app-square.tsx (1 hunks)
  • apps/dashboard/src/components/app-store-entry.tsx (1 hunks)
  • apps/dashboard/src/components/link.tsx (2 hunks)
  • apps/dashboard/src/components/project-switcher.tsx (2 hunks)
  • apps/dashboard/src/lib/apps-frontend.tsx (1 hunks)
  • packages/stack-shared/src/apps/apps-config.ts (1 hunks)
  • packages/stack-shared/src/utils/react.tsx (0 hunks)
  • packages/stack-shared/src/utils/urls.tsx (1 hunks)
  • packages/stack-ui/src/components/ui/dialog.tsx (2 hunks)
💤 Files with no reviewable changes (1)
  • packages/stack-shared/src/utils/react.tsx
🧰 Additional context used
📓 Path-based instructions (3)
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

For blocking alerts and errors in UI, do not use toast notifications; use alerts instead

Files:

  • apps/dashboard/src/components/app-store-entry.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/default.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page.tsx
  • packages/stack-ui/src/components/ui/dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/components/link.tsx
  • apps/dashboard/src/components/app-square.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/page.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
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • apps/dashboard/src/components/app-store-entry.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/default.tsx
  • packages/stack-shared/src/utils/urls.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page.tsx
  • packages/stack-ui/src/components/ui/dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/components/link.tsx
  • apps/dashboard/src/components/app-square.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/page.tsx
  • apps/dashboard/src/lib/apps-frontend.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
  • packages/stack-shared/src/apps/apps-config.ts
  • apps/dashboard/src/components/project-switcher.tsx
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Keep hover/click animations snappy; avoid pre-transition delays on hover and apply transitions after the action (e.g., fade-out on hover end)

Files:

  • apps/dashboard/src/components/app-store-entry.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/default.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page.tsx
  • packages/stack-ui/src/components/ui/dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/components/link.tsx
  • apps/dashboard/src/components/app-square.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/page.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
🧬 Code graph analysis (14)
apps/dashboard/src/components/app-store-entry.tsx (3)
packages/stack-shared/src/apps/apps-config.ts (3)
  • AppId (48-48)
  • ALL_APPS (50-141)
  • ALL_APP_TAGS (5-39)
apps/dashboard/src/lib/apps-frontend.tsx (1)
  • ALL_APPS_FRONTEND (59-213)
apps/dashboard/src/components/app-square.tsx (1)
  • AppIcon (12-98)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page.tsx (2)
packages/stack-shared/src/apps/apps-config.ts (1)
  • AppId (48-48)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsx (1)
  • AppDetailsModalPageClient (10-35)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsx (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • AdminAppProvider (10-25)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (1)
  • SidebarLayout (425-500)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page.tsx (2)
packages/stack-shared/src/apps/apps-config.ts (1)
  • AppId (48-48)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx (1)
  • AppDetailsPageClient (11-34)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsx (5)
packages/stack-shared/src/apps/apps-config.ts (1)
  • AppId (48-48)
apps/dashboard/src/components/router.tsx (1)
  • useRouter (15-33)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (27-34)
packages/stack-shared/src/utils/promises.tsx (1)
  • wait (260-268)
apps/dashboard/src/components/app-store-entry.tsx (1)
  • AppStoreEntry (10-95)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (27-34)
packages/stack-shared/src/apps/apps-config.ts (3)
  • AppId (48-48)
  • ALL_APPS (50-141)
  • ALL_APP_TAGS (5-39)
apps/dashboard/src/components/app-square.tsx (1)
  • AppSquare (100-170)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx (6)
packages/stack-shared/src/apps/apps-config.ts (1)
  • AppId (48-48)
apps/dashboard/src/components/router.tsx (1)
  • useRouter (15-33)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (27-34)
packages/stack-shared/src/utils/promises.tsx (1)
  • wait (260-268)
apps/dashboard/src/lib/apps-frontend.tsx (2)
  • getAppPath (32-35)
  • ALL_APPS_FRONTEND (59-213)
apps/dashboard/src/components/app-store-entry.tsx (1)
  • AppStoreEntry (10-95)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx (1)
  • PageClient (8-62)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (8)
packages/stack-shared/src/apps/apps-config.ts (2)
  • AppId (48-48)
  • ALL_APPS (50-141)
packages/stack-shared/src/hooks/use-hover.tsx (1)
  • useHover (4-88)
apps/dashboard/src/components/link.tsx (1)
  • Link (20-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (27-34)
apps/dashboard/src/components/router.tsx (1)
  • useRouter (15-33)
packages/stack-shared/src/utils/objects.tsx (1)
  • typedEntries (263-265)
apps/dashboard/src/lib/apps-frontend.tsx (5)
  • getItemPath (37-40)
  • testItemPath (52-57)
  • getAppPath (32-35)
  • AppFrontend (10-30)
  • testAppPath (42-50)
apps/dashboard/src/components/logo.tsx (1)
  • Logo (10-29)
apps/dashboard/src/components/link.tsx (1)
apps/dashboard/src/components/router.tsx (2)
  • useRouter (15-33)
  • useRouterConfirm (35-37)
apps/dashboard/src/components/app-square.tsx (6)
packages/stack-shared/src/apps/apps-config.ts (2)
  • AppId (48-48)
  • ALL_APPS (50-141)
apps/dashboard/src/lib/apps-frontend.tsx (3)
  • AppFrontend (10-30)
  • ALL_APPS_FRONTEND (59-213)
  • getAppPath (32-35)
packages/stack-shared/src/utils/arrays.tsx (1)
  • typedIncludes (3-5)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (27-34)
apps/dashboard/src/components/link.tsx (1)
  • Link (20-38)
packages/stack-shared/src/utils/promises.tsx (1)
  • runAsynchronouslyWithAlert (312-328)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/page.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/page-client.tsx (1)
  • PageClient (9-22)
apps/dashboard/src/lib/apps-frontend.tsx (2)
packages/stack-shared/src/utils/urls.tsx (3)
  • url (278-280)
  • getRelativePart (256-258)
  • isChildUrl (339-344)
packages/stack-shared/src/apps/apps-config.ts (1)
  • AppId (48-48)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx (6)
packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts (1)
  • stackAppInternalsSymbol (440-449)
packages/stack-shared/src/apps/apps-config.ts (2)
  • AppId (48-48)
  • ALL_APPS (50-141)
packages/stack-shared/src/utils/objects.tsx (1)
  • typedEntries (263-265)
apps/dashboard/src/components/app-square.tsx (3)
  • appSquareWidthExpression (9-9)
  • AppSquare (100-170)
  • appSquarePaddingExpression (10-10)
apps/dashboard/src/components/link.tsx (1)
  • Link (20-38)
packages/stack-shared/src/utils/urls.tsx (1)
  • urlString (314-316)
🪛 Biome (2.1.2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx

[error] 49-49: Shouldn't redeclare 'BreadcrumbItem'. Consider to delete it or rename it.

'BreadcrumbItem' is defined here:

(lint/suspicious/noRedeclare)


[error] 80-80: expected , but instead found :

Remove :

(parse)


[error] 80-80: expected , but instead found URL

Remove URL

(parse)

⏰ 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: lint_and_build (latest)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: build (22.x)
  • GitHub Check: docker
  • GitHub Check: build (22.x)
  • GitHub Check: setup-tests
  • GitHub Check: all-good
  • GitHub Check: docker
  • GitHub Check: restart-dev-and-test
  • GitHub Check: Security Check
🔇 Additional comments (2)
apps/dashboard/src/components/project-switcher.tsx (1)

6-6: LGTM: Cleanup of unused imports.

Removing unused React hooks (useEffect, useRef, useState) is a good practice that reduces bundle size and improves code clarity.

packages/stack-shared/src/utils/urls.tsx (1)

339-380: LGTM!

The URL/path relationship checking logic is sound. The trailing-slash normalization in isChildPath correctly prevents false positives (e.g., /path won't match /path-abc), and isChildUrl properly validates origin, path nesting, query parameters, and hash. Test coverage is comprehensive.

Comment on lines +17 to +24
const handleEnable = async () => {
await wait(1000);
await project.updateConfig({
[`apps.installed.${appId}.enabled`]: true,
});
const path = getAppPath(project.id, ALL_APPS_FRONTEND[appId]);
router.push(path);
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard the enable flow against failures

If project.updateConfig throws, we silently fail and keep the user stuck on the details page. Wrap the operation in try/catch and show an alert so errors are surfaced and React doesn’t log an unhandled rejection.

   const handleEnable = async () => {
-    await wait(1000);
-    await project.updateConfig({
-      [`apps.installed.${appId}.enabled`]: true,
-    });
-    const path = getAppPath(project.id, ALL_APPS_FRONTEND[appId]);
-    router.push(path);
+    try {
+      await wait(1000);
+      await project.updateConfig({
+        [`apps.installed.${appId}.enabled`]: true,
+      });
+      const path = getAppPath(project.id, ALL_APPS_FRONTEND[appId]);
+      router.push(path);
+    } catch (error) {
+      console.error("Failed to enable app", error);
+      alert("Failed to enable the app. Please try again.");
+    }
   };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const handleEnable = async () => {
await wait(1000);
await project.updateConfig({
[`apps.installed.${appId}.enabled`]: true,
});
const path = getAppPath(project.id, ALL_APPS_FRONTEND[appId]);
router.push(path);
};
const handleEnable = async () => {
try {
await wait(1000);
await project.updateConfig({
[`apps.installed.${appId}.enabled`]: true,
});
const path = getAppPath(project.id, ALL_APPS_FRONTEND[appId]);
router.push(path);
} catch (error) {
console.error("Failed to enable app", error);
alert("Failed to enable the app. Please try again.");
}
};
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsx
around lines 17 to 24, the async enable flow calls project.updateConfig without
error handling which can cause unhandled rejections and leaves the user stuck;
wrap the updateConfig call and subsequent router.push inside a try/catch, await
project.updateConfig in the try block, only call router.push on success, and in
the catch block call alert(error.message || 'Failed to enable app') and
console.error(error) so the user is notified and React won’t log an unhandled
rejection.

Comment on lines +159 to +162
<ContextMenuContent>
<ContextMenuItem onClick={() => window.open(appPath, '_blank')}>
Open in new tab
</ContextMenuItem>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

"Open in new tab" should respect enabled state.

The context menu action always opens appPath, but when the app is disabled, the main Link (line 125) navigates to appDetailsPath. For consistency, the "Open in new tab" action should also open appDetailsPath when the app is disabled.

Apply this diff:

       <ContextMenuContent>
-        <ContextMenuItem onClick={() => window.open(appPath, '_blank')}>
+        <ContextMenuItem onClick={() => window.open(isEnabled ? appPath : appDetailsPath, '_blank')}>
           Open in new tab
         </ContextMenuItem>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<ContextMenuContent>
<ContextMenuItem onClick={() => window.open(appPath, '_blank')}>
Open in new tab
</ContextMenuItem>
<ContextMenuContent>
<ContextMenuItem onClick={() => window.open(isEnabled ? appPath : appDetailsPath, '_blank')}>
Open in new tab
</ContextMenuItem>
🤖 Prompt for AI Agents
In apps/dashboard/src/components/app-square.tsx around lines 159 to 162, the
ContextMenuItem always opens appPath even when the app is disabled; change its
onClick to respect the app's enabled state by opening appDetailsPath when
disabled (i.e., compute target = enabled ? appPath : appDetailsPath and call
window.open(target, '_blank')). Ensure you reference the same enabled flag used
by the main Link (line ~125).

Comment on lines +35 to +40
<div className="flex items-center gap-1">
<ProjectAvatar displayName={currentProject?.displayName || ""} />
<span className="truncate">
{ currentProject?.displayName }
</span>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Gap inconsistency between trigger and dropdown items.

The SelectValue wrapper uses gap-1 (line 35), but the SelectItem entries use gap-2 (line 46). This creates a visual mismatch between the selected item displayed in the trigger and how it appears in the dropdown.

Consider standardizing the gap value for visual consistency:

-          <div className="flex items-center gap-1">
+          <div className="flex items-center gap-2">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<div className="flex items-center gap-1">
<ProjectAvatar displayName={currentProject?.displayName || ""} />
<span className="truncate">
{ currentProject?.displayName }
</span>
</div>
<div className="flex items-center gap-2">
<ProjectAvatar displayName={currentProject?.displayName || ""} />
<span className="truncate">
{ currentProject?.displayName }
</span>
</div>
🤖 Prompt for AI Agents
In apps/dashboard/src/components/project-switcher.tsx around lines 35 to 40, the
SelectValue/trigger uses className "gap-1" while dropdown SelectItem entries use
"gap-2", causing a visual mismatch; update one of them so both use the same gap
value (pick either gap-1 or gap-2) for consistent spacing across trigger and
dropdown, e.g., change the trigger's className to match the items or vice versa
so both sides use the identical gap utility.

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

cursor[bot]

This comment was marked as outdated.

Copy link
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 (4)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (4)

10-10: Remove commented-out import

Stray commented import clutters the file.

-// import { UserButton, useUser } from "@stackframe/stack";

75-80: Type error: BottomItem lacks regex but used in data and props

bottomItems entries define regex and it’s forwarded later; add it to the type.

 type BottomItem = {
   name: string,
   href: string,
   icon: LucideIcon,
+  regex: RegExp,
   external?: boolean,
 };

64-73: Type error: AppSection missing href but provided at usage

Object passed at Line 273 includes href; the type must declare it.

 type AppSection = {
   appId: AppId,
   name: string,
   icon: React.FunctionComponent<React.SVGProps<SVGSVGElement>>,
+  href: string,
   items: {
     name: string,
     href: string,
     match: (fullUrl: URL) => boolean,
   }[],
 };

197-205: Fix SSR crash: avoid window during render in NavSubItem

Accessing window at render breaks SSR. Use usePathname() and construct a URL instead.

   const ref = useRef<any>(null);
   const hover = useHover(ref);
-  const isActive = item.match(new URL(window.location.href));
+  const pathname = usePathname();
+  const isActive = item.match(new URL(pathname, "https://example.com"));
🧹 Nitpick comments (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (3)

41-41: Rename local BreadcrumbItem type to avoid collision with imported component

Prevents linter “redeclare” warning and confusion with the JSX component.

-type BreadcrumbItem = { item: React.ReactNode, href: string };
+type Crumb = { item: React.ReactNode, href: string };

Also update other references in this file:

  • type Hidden.name: BreadcrumbItem[] | ((...) => BreadcrumbItem[])Crumb[] | ((...) => Crumb[])
  • const breadcrumbItems: BreadcrumbItem[] = ...Crumb[]

Based on static analysis hints.


11-12: Remove unused router import/variable

useRouter is imported and instantiated but never used.

-import { useRouter } from "@/components/router";
-  const router = useRouter();
-

Also applies to: 222-223


220-226: Tighten types for app IDs and enabled apps

Improves safety when indexing ALL_APPS and matches imported AppId.

-  const [expandedSections, setExpandedSections] = useState<Set<string>>(new Set(["authentication"]));
+  const [expandedSections, setExpandedSections] = useState<Set<AppId>>(new Set<AppId>(["authentication" as AppId]));

-  const enabledApps = typedEntries(config.apps.installed).filter(([_, appConfig]) => appConfig.enabled).map(([appId]) => appId);
+  const enabledApps: AppId[] = typedEntries(config.apps.installed)
+    .filter(([_, appConfig]) => appConfig.enabled)
+    .map(([appId]) => appId as AppId);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b8f0f6 and 4a308f5.

📒 Files selected for processing (1)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (8 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

For blocking alerts and errors in UI, do not use toast notifications; use alerts instead

Files:

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

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Keep hover/click animations snappy; avoid pre-transition delays on hover and apply transitions after the action (e.g., fade-out on hover end)

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
🧬 Code graph analysis (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (8)
packages/stack-shared/src/apps/apps-config.ts (2)
  • AppId (48-48)
  • ALL_APPS (50-141)
packages/stack-shared/src/hooks/use-hover.tsx (1)
  • useHover (4-88)
apps/dashboard/src/components/link.tsx (1)
  • Link (20-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (27-34)
apps/dashboard/src/components/router.tsx (1)
  • useRouter (15-33)
packages/stack-shared/src/utils/objects.tsx (1)
  • typedEntries (263-265)
apps/dashboard/src/lib/apps-frontend.tsx (6)
  • ALL_APPS_FRONTEND (59-213)
  • getItemPath (37-40)
  • testItemPath (52-57)
  • getAppPath (32-35)
  • AppFrontend (10-30)
  • testAppPath (42-50)
apps/dashboard/src/components/logo.tsx (1)
  • Logo (10-29)
🪛 Biome (2.1.2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx

[error] 41-41: Shouldn't redeclare 'BreadcrumbItem'. Consider to delete it or rename it.

'BreadcrumbItem' is defined here:

(lint/suspicious/noRedeclare)

⏰ 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: restart-dev-and-test
  • 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: docker
  • GitHub Check: Security Check

Comment on lines 144 to 151
ref={ref}
{...(isSection ? { onClick: onToggle } : { href })}
className={cn(
"flex items-center w-full py-1.5 px-4 text-left",
isHovered && "bg-foreground/5",
isActive && "bg-foreground/5",
isSection && "cursor-default"
)}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Mobile sheet doesn’t close after navigation: wire onClick correctly

NavItem does not forward onClick to Link; and app sections pass onNavigate which NavItem doesn’t use. Forward onClick and pass it at the callsite.

-        {...(isSection ? { onClick: onToggle } : { href })}
+        {...(isSection ? { onClick: onToggle } : { href, onClick })}
-              onNavigate={onNavigate}
+              onClick={onNavigate}

Optional follow‑up: remove the unused onNavigate prop from NavItem’s signature.

Also applies to: 276-280

🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
around lines 144-151 (and similarly lines 276-280), NavItem is not forwarding
the onClick handler to the underlying Link so the mobile sheet stays open after
navigation; also callsites pass onNavigate which NavItem ignores. Update NavItem
to accept and forward an onClick prop into the rendered Link (or anchor)
element, remove or stop using the unused onNavigate prop at the NavItem
signature/callsite, and update all places that currently pass onNavigate to pass
onClick instead so the mobile sheet can be closed after navigation.

Copy link
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: 9

Caution

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

⚠️ Outside diff range comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx (1)

204-208: Use project.useConfig() for reactive state instead of reading project.config directly.

Ensures UI re-renders when config changes (consistent with other pages).

   const stackAdminApp = useAdminApp();
   const project = stackAdminApp.useProject();
-  const oauthProviders = project.config.oauthProviders;
+  const config = project.useConfig();
+  const oauthProviders = config.oauthProviders;
-              checked={project.config.credentialEnabled}
+              checked={config.credentialEnabled}

Apply similarly: magicLinkEnabled, passkeyEnabled, signUpEnabled, oauthAccountMergeStrategy, clientUserDeletionEnabled, and the AuthPage mockProject spread:

-                            ...project.config,
+                            ...config,

Also applies to: 207-208, 228-236

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

159-161: “Open in new tab” should respect enabled state.

Open appDetailsPath when disabled; appPath when enabled. Same rule as the main Link.

-        <ContextMenuItem onClick={() => window.open(appPath, '_blank')}>
+        <ContextMenuItem onClick={() => window.open(isEnabled ? appPath : appDetailsPath, '_blank')}>
           Open in new tab
         </ContextMenuItem>
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (1)

225-231: Forward onClick to Link so the mobile sheet closes after navigation.

Currently only href is passed; onClick from props is ignored for non‑sections.

-      <ButtonComponent
-        {...(isSection ? { onClick: onToggle } : { href })}
+      <ButtonComponent
+        {...(isSection ? { onClick: onToggle } : { href, onClick })}
apps/dashboard/src/lib/apps-frontend.tsx (2)

41-44: Make the URL construction explicit.

The implicit string coercion in new URL(item.href, new URL(...) + "/") is unclear. When + is applied to a URL object, JavaScript converts it to a string via toString(). While this works correctly, it's not idiomatic and makes the code harder to understand.

Apply this diff to make the trailing-slash handling explicit:

 export function getItemPath(projectId: string, appFrontend: AppFrontend, item: AppFrontend["navigationItems"][number]) {
-  const url = new URL(item.href, new URL(appFrontend.href, `${DUMMY_ORIGIN}/projects/${projectId}/`) + "/");
+  const appUrl = new URL(appFrontend.href, `${DUMMY_ORIGIN}/projects/${projectId}/`);
+  const baseWithSlash = appUrl.href + (appUrl.href.endsWith('/') ? '' : '/');
+  const url = new URL(item.href, baseWithSlash);
   return getRelativePart(url);
 }

116-116: Use consistent storeDescription type.

The payments app uses an empty string "" while all other apps use an empty fragment <></>. This causes different fallback behavior in the UI component: the empty string is falsy and triggers a "No additional information available" message, whereas the empty fragment is truthy and renders no content.

Apply this diff for consistency:

-    storeDescription: "",
+    storeDescription: <></>,
🧹 Nitpick comments (27)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/workflows/[workflowId]/page.tsx (3)

41-43: Use inline Alert for errors instead of toast (per guidelines).

Replace error toasts with an inline Alert or surfaced error state within the page.

As per coding guidelines

Example approach:

  • Add: const [errorMsg, setErrorMsg] = useState<string | null>(null);
  • Set on failure: setErrorMsg("Failed to save workflow") / setErrorMsg("Failed to toggle workflow");
  • Render near top inside PageLayout: {errorMsg && {errorMsg}}

Also applies to: 60-61


72-72: Avoid JSON.stringify for IDs in UI.

Render as string: {String(workflowId)} to avoid quotes.

-            <p className="text-muted-foreground mb-4">The workflow {JSON.stringify(workflowId)} was not found.</p>
+            <p className="text-muted-foreground mb-4">The workflow {String(workflowId)} was not found.</p>

103-103: Prefer overflow: auto over scroll.

Prevents always‑visible scrollbars and shows only when needed.

-        <div className="flex gap-6 flex-1" style={{ flexBasis: "0px", overflow: "scroll" }}>
+        <div className="flex gap-6 flex-1" style={{ flexBasis: "0px", overflow: "auto" }}>
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx (4)

1107-1119: Prefer Map over Record for groups prop.

To align with guideline, accept groups as Map<string, {displayName?: string}> (caller can convert from object).

As per coding guidelines

-type CatalogViewProps = {
-  groupedProducts: Map<string | undefined, Array<{ id: string, product: Product }>>,
-  groups: Record<string, { displayName?: string }>,
+type CatalogViewProps = {
+  groupedProducts: Map<string | undefined, Array<{ id: string, product: Product }>>,
+  groups: Map<string, { displayName?: string }>,

And adapt reads: groups.get(catalogId)?.displayName


528-533: Replace blocking error toasts with inline validation.

  • “Item already included” and “Price ID already exists” are blocking errors; avoid toast per guidelines. Surface inline near the control or disable the conflicting option with a hint.

As per coding guidelines
Example:

  • Track a local error state for the row and render … adjacent to the control.
  • For the item picker, set disabled with a helper text instead of firing a toast.

Also applies to: 785-786


1296-1308: Avoid window.alert; show inline validation.

Use an inline Alert or field error state for catalog ID validation (required, format, uniqueness) instead of blocking browser alerts.

As per coding guidelines

Example:

  • const [newCatalogError, setNewCatalogError] = useState<string | null>(null);
  • On validation failure, setNewCatalogError("…");
  • Render {newCatalogError && {newCatalogError}} below the input.

1513-1517: Remove unused handler.

handleCreateProduct is defined but unused; delete to reduce noise.

-  // Check if there are no products and no items
-  // Handler for create product button
-  const handleCreateProduct = () => {
-    setShowProductDialog(true);
-  };
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/[themeId]/page-client.tsx (1)

49-79: Guarding the theme editor is correct; consider replacing blocking error toasts with inline Alerts

AppEnabledGuard usage looks good here.

As per coding guidelines, for blocking save errors, prefer an inline Alert in the editor area over a toast. Example pattern (outside this hunk): track a local error and render an Alert near the Save button.

// add state
const [saveError, setSaveError] = useState<string | null>(null);

// update handleSaveTheme
try {
  await stackAdminApp.updateEmailTheme(themeId, currentCode);
  setSaveError(null);
  // success toast can remain if desired (non-blocking), or show inline success banner
} catch (error) {
  if (error instanceof KnownErrors.EmailRenderingError) {
    setSaveError(error.message);
    return;
  }
  throw error;
}

// render near the Save action
{saveError && (
  <Alert variant="destructive">
    Failed to save theme: {saveError}
  </Alert>
)}

As per coding guidelines

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

23-35: Guard and shared-server UX are solid; add a null-check around “Edit Templates Anyway” navigation

AppEnabledGuard usage and warnings look good.

Add a defensive check to avoid pushing with a null id in rare cases:

- okButton={{
-   label: "Edit Templates Anyway", onClick: async () => {
-     router.push(`email-templates/${sharedSmtpWarningDialogOpen}`);
-   }
- }}
+ okButton={{
+   label: "Edit Templates Anyway",
+   onClick: async () => {
+     if (!sharedSmtpWarningDialogOpen) return "prevent-close";
+     router.push(`email-templates/${sharedSmtpWarningDialogOpen}`);
+   },
+ }}

Also applies to: 36-58, 60-79, 80-81

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

120-165: Prefer inline validation over blocking dialogs; disable “Create” when invalid/duplicate

Show validation near inputs and prevent submission rather than using blocking alerts. Also disable the Create button when invalid, empty, or duplicate.

As per coding guidelines

               <div className="space-y-2">
                 <Label htmlFor="storeId">Store ID</Label>
                 <Input
                   id="storeId"
                   placeholder="e.g., user-secrets, api-keys"
                   value={newStoreId}
                   onChange={(e) => setNewStoreId(e.target.value)}
                   pattern="[a-z0-9-]+"
                 />
                 <p className="text-xs text-muted-foreground">
                   Lowercase letters, numbers, and hyphens only
                 </p>
+                {newStoreId && !/^[a-z0-9-]+$/.test(newStoreId.trim()) && (
+                  <p className="text-xs text-destructive">
+                    Invalid ID. Use lowercase letters, numbers, and hyphens only.
+                  </p>
+                )}
+                {newStoreId && (newStoreId in stores) && (
+                  <p className="text-xs text-destructive">
+                    A store with this ID already exists.
+                  </p>
+                )}
               </div>
...
-                  <Button onClick={handleCreateStore} disabled={isCreating}>
+                  <Button
+                    onClick={handleCreateStore}
+                    disabled={
+                      isCreating ||
+                      !newStoreId.trim() ||
+                      !/^[a-z0-9-]+$/.test(newStoreId.trim()) ||
+                      (newStoreId in stores)
+                    }
+                  >
                     {isCreating ? "Creating..." : "Create Store"}
                   </Button>

Optional: catch update errors and show an inline Alert in the dialog instead of a toast or window.alert. As per coding guidelines

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

13-17: Filter installed to known apps and sort by display name

Avoid unsafe cast by intersecting with ALL_APPS and provide consistent ordering.

-  const installedApps = Object.entries(config.apps.installed)
-    .filter(([_, appConfig]) => appConfig.enabled)
-    .map(([appId]) => appId as AppId);
+  const installedApps = Object.entries(config.apps.installed)
+    .filter(([appId, appConfig]) => appConfig.enabled && (appId in ALL_APPS))
+    .map(([appId]) => appId as AppId)
+    .sort((a, b) => ALL_APPS[a].displayName.localeCompare(ALL_APPS[b].displayName));

18-29: Render tags in a deterministic order (use ALL_APP_TAGS keys)

Current Map iteration depends on encounter order. Use ALL_APP_TAGS to drive section order.

   // Group apps by tag
   const appsByTag = new Map<string, AppId[]>();
 
   Object.entries(ALL_APPS).forEach(([appId, app]) => {
     app.tags.forEach(tag => {
       if (!appsByTag.has(tag)) {
         appsByTag.set(tag, []);
       }
       appsByTag.get(tag)?.push(appId as AppId);
     });
   });
+
+  const orderedTags = Object.keys(ALL_APP_TAGS) as (keyof typeof ALL_APP_TAGS)[];
...
-      {Array.from(appsByTag.entries()).filter(([_, apps]) => apps.some(appId => ALL_APPS[appId].stage !== "alpha")).map(([tag, apps]) => (
-        <section key={tag}>
-          <h2 className="font-bold my-4">{ALL_APP_TAGS[tag as keyof typeof ALL_APP_TAGS].displayName}</h2>
-          <div className="flex gap-1 lg:gap-8 flex-wrap">
-            {apps.filter(appId => ALL_APPS[appId].stage !== "alpha").map(appId => (
-              <AppSquare key={appId} appId={appId} />
-            ))}
-          </div>
-        </section>
-      ))}
+      {orderedTags.map((tag) => {
+        const apps = appsByTag.get(tag) ?? [];
+        const visible = apps.some(appId => ALL_APPS[appId].stage !== "alpha");
+        if (!visible) return null;
+        return (
+          <section key={tag}>
+            <h2 className="font-bold my-4">{ALL_APP_TAGS[tag].displayName}</h2>
+            <div className="flex gap-1 lg:gap-8 flex-wrap">
+              {apps
+                .filter(appId => ALL_APPS[appId].stage !== "alpha")
+                .sort((a, b) => ALL_APPS[a].displayName.localeCompare(ALL_APPS[b].displayName))
+                .map(appId => (
+                  <AppSquare key={appId} appId={appId} />
+                ))}
+            </div>
+          </section>
+        );
+      })}

Also applies to: 49-59

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx (5)

701-711: Avoid toast for error states; surface inline Alert near the control

Per coding guidelines, prefer an inline Alert for errors (not toast). When test mode update fails, render a small Alert by the switch and preserve tactile feedback.

As per coding guidelines

Apply this diff to adjust the handler:

   const handleToggleTestMode = async (enabled: boolean) => {
     setIsUpdatingTestMode(true);
     try {
       await project.updateConfig({ "payments.testMode": enabled });
       toast({ title: enabled ? "Test mode enabled" : "Test mode disabled" });
-    } catch (_error) {
-      toast({ title: "Failed to update test mode", variant: "destructive" });
+    } catch (_error) {
+      setTestModeError("Failed to update test mode");
     } finally {
       setIsUpdatingTestMode(false);
     }
   };

And render the Alert next to the switch:

         <div className="flex items-center gap-2">
           <Label htmlFor={testModeSwitchId}>Test mode</Label>
           <Switch
             id={testModeSwitchId}
             checked={paymentsConfig.testMode === true}
             disabled={isUpdatingTestMode}
             onCheckedChange={(checked) => void handleToggleTestMode(checked)}
           />
+          {testModeError && (
+            <Alert variant="destructive" className="ml-2">
+              <AlertTitle>Update failed</AlertTitle>
+              <AlertDescription>{testModeError}</AlertDescription>
+            </Alert>
+          )}
         </div>

Add state and imports (outside the shown range):

// imports at top
import { Alert, AlertDescription, AlertTitle } from "@stackframe/stack-ui";
// state near other useState hooks
const [testModeError, setTestModeError] = useState<string | null>(null);

Also applies to: 727-749


733-736: Switch used as navigation “view toggle” harms accessibility

The switch is always checked and acts as a navigation trigger. This is confusing for assistive tech. Prefer a segmented control or two buttons/links indicating the current view.

Example (conceptual):

  • Replace the Switch with two Buttons: “Pricing table” and “List”. Style the current view as selected. Clicking “Pricing table” calls onViewChange("catalogs").
    As per coding guidelines

Also applies to: 741-746


184-221: Throttle scroll/resize path recomputation to avoid jank

updatePath runs on every scroll/resize and calls setState; this can cause layout thrash on large lists. Throttle via requestAnimationFrame or a small debounce.

Example:

useEffect(() => {
  if (!fromRef.current || !toRef.current || !containerRef.current) return;
  let raf = 0;
  const updatePath = () => {
    cancelAnimationFrame(raf);
    raf = requestAnimationFrame(() => {
      // ...existing path calculations...
      setPath(pathStr);
      setMidpoint({ x: midX, y: midY });
    });
  };
  updatePath();
  window.addEventListener('resize', updatePath);
  window.addEventListener('scroll', updatePath, true);
  return () => {
    cancelAnimationFrame(raf);
    window.removeEventListener('resize', updatePath);
    window.removeEventListener('scroll', updatePath, true);
  };
}, [fromRef, toRef, containerRef]);

558-565: Prefer Map for refs, and guard lookups

productRefs/itemRefs are Record maps. Use Map for key–value collections and guard lookups before passing to ConnectionLine.

As per coding guidelines

Example:

const productRefs = useMemo(() => {
  const m = new Map<string, React.RefObject<HTMLDivElement>>();
  Object.keys(paymentsConfig.products).forEach(id => m.set(id, React.createRef<HTMLDivElement>()));
  return m;
}, [paymentsConfig.products]);

// usage
fromRef={productRefs.get(hoveredProductId)!}

Also guard to ensure ref exists before rendering a ConnectionLine to avoid undefined refs at boundary cases.

Also applies to: 566-573, 818-839


779-783: Use overflow-auto instead of overflow: scroll

Prevents always-visible scrollbars and improves UX.

- style={{ flexBasis: "0px", overflow: "scroll" }}
+ style={{ flexBasis: "0px" }}
+ className="flex gap-6 flex-1 overflow-auto"
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/page-client.tsx (1)

42-44: Graceful fallback if active theme ID is invalid

throwErr will crash the page if config is stale. Consider a safe fallback (first theme) plus an inline Alert to inform the user, instead of hard-failing.

Example:

const selectedThemeData = themes.find(t => t.id === activeTheme) ?? themes[0];
const invalidActive = !themes.some(t => t.id === activeTheme);

// Render an Alert above EmailPreview when invalidActive is true.

Also applies to: 55-80

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

259-269: Prefer reactive config consistently over project.config

You already call useConfig(). Mixing project.config.* with config.* may cause stale UI. Read all config fields from config for consistency.

Also applies to: 270-311, 312-349, 350-374, 376-433


395-415: Simplify redundant condition

This ternary uses the same check twice. Use productionChecksPassing alone for clarity.

- detail:
-   productionChecksPassing || productionModeErrors.length === 0 ? (
+ detail: productionChecksPassing ? (
    <Typography variant="secondary" className="text-xs">
      All checks are passing.
    </Typography>
  ) : (
    ...
  )
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client.tsx (2)

14-16: Prefer crypto.randomUUID() for trigger IDs (fallback to current).

More robust and collision‑resistant; keeps current fallback for older browsers.

-function generateTriggerId() {
-  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
-}
+function generateTriggerId() {
+  return typeof crypto !== "undefined" && typeof (crypto as any).randomUUID === "function"
+    ? (crypto as any).randomUUID()
+    : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
+}

20-20: Title consistency: use “Products” to match the route.

This page is under payments/products; showing “Payments” here but “Products” elsewhere can confuse users.

-    <PageLayout title="Payments" description="Set up your pricing table by creating products and items.">
+    <PageLayout title="Products" description="Set up your pricing table by creating products and items.">
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/[storeId]/page-client.tsx (1)

54-55: Minor cleanup and resilience: remove dead var; handle clipboard errors.

  • remainingStores is computed but unused.
  • Clipboard API can fail (permissions/HTTP); handle errors.
-  const { [storeId]: _, ...remainingStores } = config.dataVault.stores;
+  // removed unused destructure of remainingStores

   await project.updateConfig({
     [`dataVault.stores.${storeId}`]: null,
   });
-  const copyToClipboard = async (text: string) => {
-    await navigator.clipboard.writeText(text);
-    toast({ title: "Copied to clipboard" });
-  };
+  const copyToClipboard = async (text: string) => {
+    try {
+      await navigator.clipboard.writeText(text);
+      toast({ title: "Copied to clipboard" });
+    } catch {
+      alert("Copy failed. Please copy manually.");
+    }
+  };

Also applies to: 84-87

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

40-40: Rename local Breadcrumb type to avoid shadowing UI component.

Prevents noRedeclare warning and confusion with the imported BreadcrumbItem component.

-type BreadcrumbItem = { item: React.ReactNode, href: string };
+type ResolvedBreadcrumb = { item: React.ReactNode, href: string };
-}): Promise<BreadcrumbItem[]> {
+}): Promise<ResolvedBreadcrumb[]> {
-  const [breadcrumbItems, setBreadcrumbItems] = useState<BreadcrumbItem[]>([]);
+  const [breadcrumbItems, setBreadcrumbItems] = useState<ResolvedBreadcrumb[]>([]);

Also applies to: 120-129, 406-407

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

229-236: Optional: wrap updates with runAsynchronouslyWithAlert for uniform error UX.

Matches the shared pattern and shows alerts on failures.

Example:

-                await project.update({
+                await runAsynchronouslyWithAlert(project.update({
                   config: {
                     credentialEnabled: checked,
                   },
-                });
+                }));

Also applies to: 244-251, 260-267, 345-354, 366-373

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

223-242: Consider stronger prop types.

The createSvgIcon function and logo components use any for props, which reduces type safety and IDE autocomplete support.

Consider using explicit SVG prop types:

-function createSvgIcon(ChildrenComponent: () => React.ReactNode): (props: any) => React.ReactNode {
-  const Result = (props: any) => (
+function createSvgIcon(ChildrenComponent: () => React.ReactNode): React.FC<React.SVGProps<SVGSVGElement>> {
+  const Result: React.FC<React.SVGProps<SVGSVGElement>> = (props) => (
     <svg
       width="24"

Similarly for logo components:

-    logo: (props: any) => <Image src={NeonLogo} alt="Neon logo" {...props} />,
+    logo: ({ className }: { className?: string }) => <Image src={NeonLogo} alt="Neon logo" className={className} />,

244-249: Redundant empty string check after normalization.

Line 245 normalizes relativePart || "/", converting empty strings to "/". The subsequent check on line 247 for normalized === "" is therefore unreachable.

Simplify the condition:

 async function getEmailTemplatesBreadcrumbItems(stackAdminApp: StackAdminApp<false>, relativePart: string) {
   const normalized = relativePart || "/";
   const baseCrumbs = [{ item: "Templates", href: "." }];
-  if (normalized === "/" || normalized === "") {
+  if (normalized === "/") {
     return baseCrumbs;
   }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2ff0fc and 194fc09.

📒 Files selected for processing (39)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/api-keys/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.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/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/page.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/[storeId]/page-client.tsx (3 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/[draftId]/page-client.tsx (3 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/[templateId]/page-client.tsx (3 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/[themeId]/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.tsx (3 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/page.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/layout.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx (5 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client.tsx (1 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/transactions/page-client.tsx (0 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-permissions/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (7 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-permissions/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-settings/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/[endpointId]/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/workflows/[workflowId]/page.tsx (2 hunks)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/workflows/page.tsx (3 hunks)
  • apps/dashboard/src/components/app-square.tsx (1 hunks)
  • apps/dashboard/src/lib/apps-frontend.tsx (1 hunks)
  • packages/stack-shared/src/apps/apps-config.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/transactions/page-client.tsx
🧰 Additional context used
📓 Path-based instructions (3)
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

For blocking alerts and errors in UI, do not use toast notifications; use alerts instead

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/workflows/[workflowId]/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/api-keys/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-permissions/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx
  • apps/dashboard/src/components/app-square.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-settings/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/[storeId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/[endpointId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/[themeId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/workflows/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/[templateId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-permissions/page-client.tsx
  • apps/dashboard/src/lib/apps-frontend.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/[draftId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Prefer ES6 Map over Record when representing key–value collections

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/workflows/[workflowId]/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/api-keys/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-permissions/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx
  • apps/dashboard/src/components/app-square.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-settings/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/[storeId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/[endpointId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/[themeId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/workflows/page.tsx
  • packages/stack-shared/src/apps/apps-config.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/[templateId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-permissions/page-client.tsx
  • apps/dashboard/src/lib/apps-frontend.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/[draftId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx
{apps/dashboard,apps/dev-launchpad,packages/stack-ui,packages/react}/**/*.{tsx,jsx,css}

📄 CodeRabbit inference engine (AGENTS.md)

Keep hover/click animations snappy; avoid pre-transition delays on hover and apply transitions after the action (e.g., fade-out on hover end)

Files:

  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/workflows/[workflowId]/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/api-keys/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-permissions/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx
  • apps/dashboard/src/components/app-square.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-settings/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/[storeId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/[endpointId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/[themeId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/workflows/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/[templateId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-permissions/page-client.tsx
  • apps/dashboard/src/lib/apps-frontend.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/[draftId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx
🧬 Code graph analysis (35)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/workflows/[workflowId]/page.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/api-keys/page-client.tsx (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx (1)
  • PageLayout (3-42)
apps/dashboard/src/components/data-table/api-key-table.tsx (1)
  • InternalApiKeyTable (104-126)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/page-client.tsx (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/components/data-table/team-member-table.tsx (1)
  • TeamMemberTable (133-195)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsx (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx (1)
  • PageLayout (3-42)
apps/dashboard/src/components/data-table/team-table.tsx (1)
  • TeamTable (130-144)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx (1)
  • PageClient (249-630)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsx (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx (1)
  • PageLayout (3-42)
apps/dashboard/src/components/settings.tsx (2)
  • SettingCard (11-48)
  • SettingSwitch (50-84)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-permissions/page-client.tsx (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx (1)
  • PageLayout (3-42)
apps/dashboard/src/components/data-table/permission-table.tsx (1)
  • PermissionTable (185-198)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/components/app-square.tsx (7)
packages/stack-shared/src/apps/apps-config.ts (2)
  • AppId (48-48)
  • ALL_APPS (50-141)
apps/dashboard/src/lib/apps-frontend.tsx (3)
  • AppFrontend (25-34)
  • ALL_APPS_FRONTEND (63-221)
  • getAppPath (36-39)
packages/stack-shared/src/utils/arrays.tsx (1)
  • typedIncludes (3-5)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (28-43)
packages/stack-ui/src/components/ui/context-menu.tsx (5)
  • ContextMenu (190-190)
  • ContextMenuTrigger (191-191)
  • ContextMenuContent (192-192)
  • ContextMenuItem (193-193)
  • ContextMenuSeparator (197-197)
apps/dashboard/src/components/link.tsx (1)
  • Link (21-42)
packages/stack-shared/src/utils/promises.tsx (1)
  • runAsynchronouslyWithAlert (312-328)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-settings/page-client.tsx (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx (1)
  • PageLayout (3-42)
apps/dashboard/src/components/settings.tsx (2)
  • SettingCard (11-48)
  • SettingSwitch (50-84)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client.tsx (3)
apps/dashboard/src/components/illustrated-info.tsx (1)
  • IllustratedInfo (4-31)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx (1)
  • PageClient (1418-1654)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (28-43)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx (1)
  • PageLayout (3-42)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (3)
packages/stack-shared/src/apps/apps-config.ts (1)
  • AppId (48-48)
apps/dashboard/src/components/router.tsx (1)
  • useRouter (15-33)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (28-43)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx (4)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (28-43)
apps/dashboard/src/components/router.tsx (1)
  • useRouter (15-33)
apps/dashboard/src/components/settings.tsx (1)
  • SettingSwitch (50-84)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/[storeId]/page-client.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/[endpointId]/page-client.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx (4)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/components/settings.tsx (3)
  • SettingCard (11-48)
  • SettingSwitch (50-84)
  • SettingSelect (131-174)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx (1)
  • ProviderIcon (13-22)
packages/stack-ui/src/components/browser-frame/index.tsx (1)
  • BrowserFrame (11-51)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/[themeId]/page-client.tsx (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/components/vibe-coding/chat-adapters.ts (2)
  • createChatAdapter (15-61)
  • createHistoryAdapter (63-76)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/workflows/page.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/layout.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/page-client.tsx (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
packages/stack-ui/src/components/action-dialog.tsx (1)
  • ActionDialog (31-135)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/page.tsx (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/page.tsx (1)
  • Page (3-6)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page.tsx (1)
  • Page (11-15)
apps/dashboard/src/app/(main)/purchase/[code]/page.tsx (1)
  • Page (7-12)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/page-client.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/[templateId]/page-client.tsx (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/components/vibe-coding/vibe-code-layout.tsx (1)
  • VibeCodeLayout (13-37)
apps/dashboard/src/components/email-theme-selector.tsx (1)
  • EmailThemeSelector (17-39)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/page.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/page.tsx (1)
  • Page (3-6)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-permissions/page-client.tsx (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/components/data-table/permission-table.tsx (1)
  • PermissionTable (185-198)
apps/dashboard/src/lib/apps-frontend.tsx (2)
packages/stack-shared/src/utils/urls.tsx (3)
  • url (278-280)
  • getRelativePart (256-258)
  • isChildUrl (339-344)
packages/stack-shared/src/apps/apps-config.ts (1)
  • AppId (48-48)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/[draftId]/page-client.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx (3)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (28-43)
packages/stack-shared/src/apps/apps-config.ts (3)
  • AppId (48-48)
  • ALL_APPS (50-141)
  • ALL_APP_TAGS (5-39)
apps/dashboard/src/components/app-square.tsx (1)
  • AppSquare (100-169)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsx (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client.tsx (1)
  • PageClient (71-115)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/page-client.tsx (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx (1)
  • PageLayout (3-42)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (7)
packages/stack-shared/src/apps/apps-config.ts (2)
  • AppId (48-48)
  • ALL_APPS (50-141)
apps/dashboard/src/lib/apps-frontend.tsx (7)
  • DUMMY_ORIGIN (11-11)
  • ALL_APPS_FRONTEND (63-221)
  • testAppPath (46-54)
  • AppFrontend (25-34)
  • getAppPath (36-39)
  • testItemPath (56-61)
  • getItemPath (41-44)
packages/stack-shared/src/utils/promises.tsx (1)
  • runAsynchronously (343-366)
packages/stack-shared/src/utils/urls.tsx (1)
  • getRelativePart (256-258)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/use-admin-app.tsx (1)
  • useAdminApp (28-43)
packages/stack-shared/src/utils/objects.tsx (1)
  • typedEntries (263-265)
apps/dashboard/src/components/link.tsx (1)
  • Link (21-42)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
packages/stack-ui/src/components/action-dialog.tsx (1)
  • ActionDialog (31-135)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx (2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx (1)
  • AppEnabledGuard (11-38)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx (1)
  • PageLayout (3-42)
🪛 Biome (2.1.2)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx

[error] 40-40: Shouldn't redeclare 'BreadcrumbItem'. Consider to delete it or rename it.

'BreadcrumbItem' is defined here:

(lint/suspicious/noRedeclare)

⏰ 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). (13)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: Vercel Agent Review
  • GitHub Check: Cursor Bugbot
  • GitHub Check: setup-tests
  • GitHub Check: build (22.x)
  • GitHub Check: check_prisma_migrations (22.x)
  • GitHub Check: lint_and_build (latest)
  • GitHub Check: docker
  • GitHub Check: docker
  • GitHub Check: build (22.x)
  • GitHub Check: all-good
  • GitHub Check: restart-dev-and-test
  • GitHub Check: Security Check

Comment on lines +16 to +18
const installedApps = config.apps.installed;
const isEnabled = installedApps[props.appId].enabled;

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Potential crash when app isn’t present in config.apps.installed

installedApps[appId] can be undefined; accessing .enabled will throw. Default to false via optional chaining.

Apply this diff:

-  const installedApps = config.apps.installed;
-  const isEnabled = installedApps[props.appId].enabled;
+  const installedApps = config.apps.installed ?? {};
+  const isEnabled = !!installedApps?.[props.appId]?.enabled;

Optional: to avoid a brief flash before redirect, you can return null when !isEnabled instead of rendering PageLayout.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const installedApps = config.apps.installed;
const isEnabled = installedApps[props.appId].enabled;
const installedApps = config.apps.installed ?? {};
const isEnabled = !!installedApps?.[props.appId]?.enabled;
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsx
around lines 16 to 18, the code accesses installedApps[props.appId].enabled
which will throw if installedApps[props.appId] is undefined; change the check to
use optional chaining with a default false (e.g., const isEnabled =
installedApps[props.appId]?.enabled ?? false) so missing entries don't crash,
and optionally return null immediately when !isEnabled to avoid a brief flash
before redirect instead of rendering PageLayout.

Comment on lines 9 to 11
import { CardSubtitle } from "../../../../../../../../../packages/stack-ui/dist/components/ui/card";
import { AppEnabledGuard } from "../app-enabled-guard";
import { PageLayout } from "../page-layout";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Avoid deep imports into dist; use the package entrypoint.

Deep paths are brittle across builds/publishes.

-import { CardSubtitle } from "../../../../../../../../../packages/stack-ui/dist/components/ui/card";
+import { CardSubtitle } from "@stackframe/stack-ui";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { CardSubtitle } from "../../../../../../../../../packages/stack-ui/dist/components/ui/card";
import { AppEnabledGuard } from "../app-enabled-guard";
import { PageLayout } from "../page-layout";
import { CardSubtitle } from "@stackframe/stack-ui";
import { AppEnabledGuard } from "../app-enabled-guard";
import { PageLayout } from "../page-layout";
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx
around lines 9 to 11 you are doing a deep import from the package build output
which is brittle; replace the deep path import
(../../../../../../../../../packages/stack-ui/dist/components/ui/card) with the
package entrypoint import (e.g. import { CardSubtitle } from "stack-ui") and, if
the component is not exported from the package root, add a re-export from the
package's index (or barrel) so CardSubtitle is exposed via the package
entrypoint.

Comment on lines +96 to 101
{storeEntries.map(([storeId, store]) => (
<Card
key={storeId}
className="cursor-pointer hover:shadow-md transition-shadow"
onClick={() => handleStoreClick(storeId)}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make the clickable Card keyboard-accessible (role/tabIndex + Enter/Space)

Div with onClick isn’t reachable via keyboard. Add role, tabIndex, and onKeyDown or wrap with a Link.

Apply this diff:

-                <Card
+                <Card
                   key={storeId}
-                  className="cursor-pointer hover:shadow-md transition-shadow"
-                  onClick={() => handleStoreClick(storeId)}
+                  className="cursor-pointer hover:shadow-md transition-shadow"
+                  role="button"
+                  tabIndex={0}
+                  onClick={() => handleStoreClick(storeId)}
+                  onKeyDown={(e) => {
+                    if (e.key === 'Enter' || e.key === ' ') {
+                      e.preventDefault();
+                      handleStoreClick(storeId);
+                    }
+                  }}
                 >
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{storeEntries.map(([storeId, store]) => (
<Card
key={storeId}
className="cursor-pointer hover:shadow-md transition-shadow"
onClick={() => handleStoreClick(storeId)}
>
{storeEntries.map(([storeId, store]) => (
<Card
key={storeId}
className="cursor-pointer hover:shadow-md transition-shadow"
role="button"
tabIndex={0}
onClick={() => handleStoreClick(storeId)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleStoreClick(storeId);
}
}}
>
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/page-client.tsx
around lines 96 to 101, the Card elements are clickable via mouse but not
keyboard; make them accessible by adding role="button", tabIndex={0}, and an
onKeyDown handler that invokes handleStoreClick(storeId) when Enter or Space is
pressed (or alternatively wrap the Card with a semantic Link/button component).
Ensure the handler prevents default for Space to avoid page scroll and that the
key checks cover both "Enter" and " " or "Spacebar" for cross-browser support.

Comment on lines +131 to +137
<Input
id="storeId"
placeholder="e.g., user-secrets, api-keys"
value={newStoreId}
onChange={(e) => setNewStoreId(e.target.value)}
pattern="[a-z0-9-]+"
/>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Note: pattern attribute isn’t enforced without a form submit

pattern only triggers on native form submission. Since you call a handler directly, keep the JS regex check (and inline error), don’t rely on pattern alone.

🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/page-client.tsx
around lines 131–137, the input's pattern attribute isn't enforced because you
call the submit handler directly; add a JS regex validation (e.g. const valid =
/^[a-z0-9-]+$/.test(newStoreId.trim())) inside the handler before proceeding,
and if invalid set an inline error state/message and return to prevent creating
the store; keep the pattern attribute for native forms but rely on the JS check
for programmatic submissions and ensure the error message is rendered next to
the Input.

Comment on lines +61 to +71
<ActionDialog
open={sharedSmtpWarningDialogOpen !== null}
onClose={() => setSharedSmtpWarningDialogOpen(null)}
title="Shared Email Server"
okButton={{
label: "Open Draft Anyway", onClick: async () => {
router.push(`email-drafts/${sharedSmtpWarningDialogOpen}`);
}
}}
cancelButton={{ label: "Cancel" }}
>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Possible null passed to router.push

sharedSmtpWarningDialogOpen is string | null; ensure non-null when used.

 okButton={{
-  label: "Open Draft Anyway", onClick: async () => {
-    router.push(`email-drafts/${sharedSmtpWarningDialogOpen}`);
-  }
+  label: "Open Draft Anyway",
+  onClick: async () => {
+    if (!sharedSmtpWarningDialogOpen) return "prevent-close";
+    router.push(`email-drafts/${sharedSmtpWarningDialogOpen}`);
+  }
 }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<ActionDialog
open={sharedSmtpWarningDialogOpen !== null}
onClose={() => setSharedSmtpWarningDialogOpen(null)}
title="Shared Email Server"
okButton={{
label: "Open Draft Anyway", onClick: async () => {
router.push(`email-drafts/${sharedSmtpWarningDialogOpen}`);
}
}}
cancelButton={{ label: "Cancel" }}
>
<ActionDialog
open={sharedSmtpWarningDialogOpen !== null}
onClose={() => setSharedSmtpWarningDialogOpen(null)}
title="Shared Email Server"
okButton={{
label: "Open Draft Anyway",
onClick: async () => {
if (!sharedSmtpWarningDialogOpen) return "prevent-close";
router.push(`email-drafts/${sharedSmtpWarningDialogOpen}`);
}
}}
cancelButton={{ label: "Cancel" }}
>
🤖 Prompt for AI Agents
In
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx
around lines 61 to 71, the onClick handler passes sharedSmtpWarningDialogOpen
(type string | null) to router.push without guaranteeing it's non-null; update
the handler to guard or narrow the type before calling router.push (for example,
check if sharedSmtpWarningDialogOpen is truthy and only then call router.push
with the value, or otherwise bail out), or explicitly convert to a non-null
string after verifying it, so router.push never receives null.

Comment on lines +525 to +537
const productionChildren = (
<div className="rounded-lg border border-dashed border-border bg-background p-3">
<SettingSwitch
label="Enable production mode"
checked={project.isProductionMode}
disabled={
!project.isProductionMode && productionModeErrors.length > 0
}
onCheckedChange={async (checked) => {
await project.update({ isProductionMode: checked });
}}
/>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle update failures and revert UI for production mode toggle

onCheckedChange updates server state but doesn’t handle failures; UI may drift from truth.

Suggestion:

  • Wrap update in try/catch; on error, revert the local toggle (SettingSwitch keeps internal state) and show an inline Alert near the switch (not a toast), or return "prevent-close" pattern if you gate via a dialog.
    Example:
onCheckedChange={async (checked) => {
  try {
    await project.update({ isProductionMode: checked });
  } catch {
    // revert UI and surface inline error state nearby
    setShowProdError(true);
  }
}}

As per coding guidelines

Also applies to: 540-557

@@ -0,0 +1,169 @@
import { useAdminApp } from "@/app/(main)/(protected)/projects/[projectId]/use-admin-app";
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Missing "use client" — this component uses hooks.

Without it Next.js will treat this as a server component and fail at runtime.

+'use client';
+
 import { useAdminApp } from "@/app/(main)/(protected)/projects/[projectId]/use-admin-app";
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { useAdminApp } from "@/app/(main)/(protected)/projects/[projectId]/use-admin-app";
'use client';
import { useAdminApp } from "@/app/(main)/(protected)/projects/[projectId]/use-admin-app";
🤖 Prompt for AI Agents
In apps/dashboard/src/components/app-square.tsx around line 1, the file is
missing the required "use client" directive for components that use React hooks;
add the exact string "use client" as the very first line of the file (before any
imports) so Next.js treats this component as a client component and then keep
the existing imports and hook usage unchanged.

Comment on lines +18 to +33
const svgGradients = (gradients: Record<string, string[]>) => {
return (
<svg width="0" height="0">
{Object.entries(gradients).map(([id, gradient]) => {
return (
<linearGradient key={id} id={id} x1="100%" y1="100%" x2="0%" y2="0%">
{gradient.map((color, index) => {
return <stop key={index} stopColor={color} offset={`${index * 100 / (gradient.length - 1)}%`} />;
})}
</linearGradient>
);
})}
</svg>
);

};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix gradient definitions: return instead of a nested .

Current code renders inside another . Define gradients in so they are resolvable by the icon.

-  const svgGradients = (gradients: Record<string, string[]>) => {
-    return (
-      <svg width="0" height="0">
-        {Object.entries(gradients).map(([id, gradient]) => {
-          return (
-            <linearGradient key={id} id={id} x1="100%" y1="100%" x2="0%" y2="0%">
-              {gradient.map((color, index) => {
-                return <stop key={index} stopColor={color} offset={`${index * 100 / (gradient.length - 1)}%`} />;
-              })}
-            </linearGradient>
-          );
-        })}
-      </svg>
-    );
-  };
+  const svgGradients = (gradients: Record<string, string[]>) => {
+    return (
+      <defs>
+        {Object.entries(gradients).map(([id, gradient]) => (
+          <linearGradient key={id} id={id} x1="100%" y1="100%" x2="0%" y2="0%">
+            {gradient.map((color, index) => (
+              <stop key={index} stopColor={color} offset={`${(index * 100) / (gradient.length - 1)}%`} />
+            ))}
+          </linearGradient>
+        ))}
+      </defs>
+    );
+  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const svgGradients = (gradients: Record<string, string[]>) => {
return (
<svg width="0" height="0">
{Object.entries(gradients).map(([id, gradient]) => {
return (
<linearGradient key={id} id={id} x1="100%" y1="100%" x2="0%" y2="0%">
{gradient.map((color, index) => {
return <stop key={index} stopColor={color} offset={`${index * 100 / (gradient.length - 1)}%`} />;
})}
</linearGradient>
);
})}
</svg>
);
};
const svgGradients = (gradients: Record<string, string[]>) => {
return (
<defs>
{Object.entries(gradients).map(([id, gradient]) => (
<linearGradient key={id} id={id} x1="100%" y1="100%" x2="0%" y2="0%">
{gradient.map((color, index) => (
<stop key={index} stopColor={color} offset={`${(index * 100) / (gradient.length - 1)}%`} />
))}
</linearGradient>
))}
</defs>
);
};
🤖 Prompt for AI Agents
In apps/dashboard/src/components/app-square.tsx around lines 18 to 33, the
helper currently returns a nested <svg> which leads to an <svg> inside another
<svg>; change it to return a <defs> element containing the <linearGradient>
entries (so the parent SVG can include these defs instead of nesting). Keep the
same map logic and keys for gradients/stops, but wrap the generated
<linearGradient> elements in a single <defs> root and ensure the stop offset
calculation avoids division by zero when gradient.length === 1 (e.g. offset
"0%").

const project = adminApp.useProject();
const config = project.useConfig();

const isEnabled = config.apps.installed[appId].enabled;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Guard config access to avoid crashes when app entry is absent.

If an app isn’t present under apps.installed, this throws.

-  const isEnabled = config.apps.installed[appId].enabled;
+  const isEnabled = config.apps.installed?.[appId]?.enabled === true;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const isEnabled = config.apps.installed[appId].enabled;
const isEnabled = config.apps.installed?.[appId]?.enabled === true;
🤖 Prompt for AI Agents
In apps/dashboard/src/components/app-square.tsx around line 110, accessing
config.apps.installed[appId].enabled can throw if the app entry is missing;
guard this access by checking that config.apps and config.apps.installed and
config.apps.installed[appId] exist before reading enabled (or use optional
chaining with a safe default like false), e.g. compute isEnabled =
!!(config?.apps?.installed?.[appId]?.enabled) or add an if/early-return when the
entry is absent so the component doesn't crash.

@N2D4 N2D4 merged commit 542d444 into dev Oct 17, 2025
31 checks passed
@N2D4 N2D4 deleted the apps-frontend branch October 17, 2025 03:54
@coderabbitai coderabbitai bot mentioned this pull request Oct 25, 2025
This was referenced Dec 3, 2025
@coderabbitai coderabbitai bot mentioned this pull request Jan 9, 2026
This was referenced Jan 28, 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.

2 participants