Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Other AI code review bot(s) detectedCodeRabbit 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. WalkthroughAdds 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
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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Possibly related PRs
Poem
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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.hrefwill cause hydration mismatch in SSR environments. UseusePathname()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.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
Outdated
Show resolved
Hide resolved
...board/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsx
Show resolved
Hide resolved
…/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 }); |
There was a problem hiding this comment.
Remove the debug console.log present in the NavItem component. Debug logs should not be left in production code.
| console.log("isActive", { item, isActive, pathname }); |
|
|
||
| const handleEnable = async () => { | ||
| await wait(1000); | ||
| await project.updateConfig({ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Review by RecurseML
🔍 Review performed on 7416a37..46a5272
| Severity | Location | Issue | Delete |
|---|---|---|---|
| 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); |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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.tsxdefining 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.installedsettings - 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)
21 files reviewed, 1 comment
| return user.displayName ?? user.primaryEmail ?? user.id; | ||
| } | ||
| } | ||
| const subItemsRef = useRef<any>(null); |
There was a problem hiding this comment.
style: Remove debug console.log before merging
| 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.There was a problem hiding this comment.
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 theStophook key.Renaming the hook to
no-longer-used-Stopdetaches it from Claude’s recognizedStoplifecycle event, so the typecheck/lint command will never fire. Please keep the key asStop(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
BreadcrumbItemis 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 duplicateonClickinvocation whenneedConfirmis true.The current logic calls
onClicktwice whenneedConfirmis 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.updateConfigcan 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 affordanceWith
noCloseButtonset, 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
⛔ Files ignored due to path filters (4)
apps/dashboard/public/app-bg-dark.pngis excluded by!**/*.pngapps/dashboard/public/app-bg-light.pngis excluded by!**/*.pngapps/dashboard/public/convex-logo.pngis excluded by!**/*.pngapps/dashboard/public/neon-logo.pngis 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.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/default.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page.tsxpackages/stack-ui/src/components/ui/dialog.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/components/link.tsxapps/dashboard/src/components/app-square.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/page.tsxapps/dashboard/src/lib/apps-frontend.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsxapps/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.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/default.tsxpackages/stack-shared/src/utils/urls.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page.tsxpackages/stack-ui/src/components/ui/dialog.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/components/link.tsxapps/dashboard/src/components/app-square.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/page.tsxapps/dashboard/src/lib/apps-frontend.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsxpackages/stack-shared/src/apps/apps-config.tsapps/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.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/default.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page.tsxpackages/stack-ui/src/components/ui/dialog.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/components/link.tsxapps/dashboard/src/components/app-square.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/page.tsxapps/dashboard/src/lib/apps-frontend.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsxapps/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
isChildPathcorrectly prevents false positives (e.g.,/pathwon't match/path-abc), andisChildUrlproperly validates origin, path nesting, query parameters, and hash. Test coverage is comprehensive.
| 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); | ||
| }; |
There was a problem hiding this comment.
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.
| 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.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
Show resolved
Hide resolved
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
Show resolved
Hide resolved
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
Show resolved
Hide resolved
| <ContextMenuContent> | ||
| <ContextMenuItem onClick={() => window.open(appPath, '_blank')}> | ||
| Open in new tab | ||
| </ContextMenuItem> |
There was a problem hiding this comment.
"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.
| <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).
| <div className="flex items-center gap-1"> | ||
| <ProjectAvatar displayName={currentProject?.displayName || ""} /> | ||
| <span className="truncate"> | ||
| { currentProject?.displayName } | ||
| </span> | ||
| </div> |
There was a problem hiding this comment.
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.
| <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.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
Outdated
Show resolved
Hide resolved
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
Outdated
Show resolved
Hide resolved
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
Outdated
Show resolved
Hide resolved
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
Outdated
Show resolved
Hide resolved
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
Outdated
Show resolved
Hide resolved
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (4)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (4)
10-10: Remove commented-out importStray commented import clutters the file.
-// import { UserButton, useUser } from "@stackframe/stack";
75-80: Type error:BottomItemlacksregexbut used in data and props
bottomItemsentries defineregexand 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:AppSectionmissinghrefbut provided at usageObject 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: avoidwindowduring render in NavSubItemAccessing
windowat render breaks SSR. UseusePathname()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 localBreadcrumbItemtype to avoid collision with imported componentPrevents 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
useRouteris 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 appsImproves safety when indexing
ALL_APPSand matches importedAppId.- 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
📒 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
| 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" | ||
| )} |
There was a problem hiding this comment.
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.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
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 viatoString(). 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 consistentstoreDescriptiontype.The
paymentsapp 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 AlertsAppEnabledGuard 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” navigationAppEnabledGuard 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/duplicateShow 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 nameAvoid 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 controlPer 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 accessibilityThe 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 guidelinesAlso applies to: 741-746
184-221: Throttle scroll/resize path recomputation to avoid jankupdatePath 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 lookupsproductRefs/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: scrollPrevents 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 invalidthrowErr 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 reactiveconfigconsistently overproject.configYou already call
useConfig(). Mixingproject.config.*withconfig.*may cause stale UI. Read all config fields fromconfigfor consistency.Also applies to: 270-311, 312-349, 350-374, 376-433
395-415: Simplify redundant conditionThis ternary uses the same check twice. Use
productionChecksPassingalone 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
createSvgIconfunction and logo components useanyfor 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 fornormalized === ""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
📒 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.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/api-keys/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-permissions/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsxapps/dashboard/src/components/app-square.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-settings/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/[storeId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/[endpointId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/[themeId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/workflows/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/layout.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/[templateId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-permissions/page-client.tsxapps/dashboard/src/lib/apps-frontend.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/[draftId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsxapps/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.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/api-keys/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-permissions/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsxapps/dashboard/src/components/app-square.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-settings/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/[storeId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/[endpointId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/[themeId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/workflows/page.tsxpackages/stack-shared/src/apps/apps-config.tsapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/layout.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/[templateId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-permissions/page-client.tsxapps/dashboard/src/lib/apps-frontend.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/[draftId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsxapps/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.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/api-keys/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/domains/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-permissions/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsxapps/dashboard/src/components/app-square.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-settings/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/app-enabled-guard.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/[storeId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/[endpointId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/[themeId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/workflows/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/layout.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-list-view.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/customers/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/[templateId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-permissions/page-client.tsxapps/dashboard/src/lib/apps-frontend.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/[draftId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client-catalogs-view.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsxapps/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
| const installedApps = config.apps.installed; | ||
| const isEnabled = installedApps[props.appId].enabled; | ||
|
|
There was a problem hiding this comment.
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.
| 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.
| import { CardSubtitle } from "../../../../../../../../../packages/stack-ui/dist/components/ui/card"; | ||
| import { AppEnabledGuard } from "../app-enabled-guard"; | ||
| import { PageLayout } from "../page-layout"; |
There was a problem hiding this comment.
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.
| 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.
| {storeEntries.map(([storeId, store]) => ( | ||
| <Card | ||
| key={storeId} | ||
| className="cursor-pointer hover:shadow-md transition-shadow" | ||
| onClick={() => handleStoreClick(storeId)} | ||
| > |
There was a problem hiding this comment.
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.
| {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.
| <Input | ||
| id="storeId" | ||
| placeholder="e.g., user-secrets, api-keys" | ||
| value={newStoreId} | ||
| onChange={(e) => setNewStoreId(e.target.value)} | ||
| pattern="[a-z0-9-]+" | ||
| /> |
There was a problem hiding this comment.
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.
| <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" }} | ||
| > |
There was a problem hiding this comment.
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.
| <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.
| 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> |
There was a problem hiding this comment.
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"; | |||
There was a problem hiding this comment.
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.
| 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.
| 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> | ||
| ); | ||
|
|
||
| }; |
There was a problem hiding this comment.
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.
| 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; |
There was a problem hiding this comment.
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.
| 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.
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
packages/stack-shared/src/apps/apps-config.tsapps/dashboard/src/lib/apps-frontend.tsxapps/dashboard/src/components/app-square.tsxapps/dashboard/src/components/app-store-entry.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/[appId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/(.)apps/[appId]/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/@modal/default.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/layout.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/page.tsxpackages/stack-shared/src/utils/urls.tsxpackages/stack-ui/src/components/ui/dialog.tsxapps/dashboard/src/components/link.tsxapps/dashboard/src/components/project-switcher.tsxpackages/stack-shared/src/utils/react.tsx.claude/settings.jsonapps/dashboard/public/app-bg-dark.pngapps/dashboard/public/app-bg-light.pngapps/dashboard/public/convex-logo.pngapps/dashboard/public/neon-logo.png.claude/settings.jsonpackages/stack-shared/src/utils/react.tsxSummary by CodeRabbit
New Features
Bug Fixes
Refactor
Style
Tests
Chore
Important
Introduces an apps system to the Stack Auth dashboard for managing modular features, with frontend updates for app discovery, enabling, and navigation.
apps/dashboardfor discovering, enabling, and managing features.AppSquareandAppStoreEntrycomponents for app display and interaction.sidebar-layout.tsxto dynamically show navigation based on enabled apps.page-client.tsxandpage.tsx.metrics-page.tsx.apps-config.tsandapps-frontend.tsx.apps-config.ts.layout.tsx..claude/settings.jsonunrelated to apps feature.react.tsx.This description was created by
for 4a308f5. You can customize this summary. It will automatically update as commits are pushed.