X Tutup
Skip to content

Update dashboard components (Except for major 4)#1205

Open
Developing-Gamer wants to merge 100 commits intodevfrom
update-dashboard-components
Open

Update dashboard components (Except for major 4)#1205
Developing-Gamer wants to merge 100 commits intodevfrom
update-dashboard-components

Conversation

@Developing-Gamer
Copy link
Contributor

@Developing-Gamer Developing-Gamer commented Feb 16, 2026

Summary by CodeRabbit

  • New Features

    • Full Playground with multi-component live previews and generated TSX snippets.
    • New reusable Design data table and richer editable-grid interfaces.
  • Refactor

    • Replaced legacy UI primitives across many pages with unified Design components (cards, buttons, alerts, badges, tabs).
  • Improvements

    • PageLayout supports optional content overflow.
    • Enhanced inline editing, copy-to-clipboard, async toggles with confirmation dialogs, dialog/form default value handling, and consistent alerts/UX.

Developing-Gamer and others added 30 commits December 4, 2025 20:49
…email logs. Updated sender email formatting to exclude non-alphanumeric characters. Added tests for email template deletion scenarios, including handling of shared email configurations.
…and user feedback. Added state management for delete errors and updated the confirmation dialog to display error messages when deletion fails.
…g. Enhance mobile layout in VibeCodeLayout with responsive design and chat functionality.
… effects and transitions. Added accent hover tint for better visual feedback.
…Removed redundant try-catch block and streamlined message validation process for improved readability and maintainability.
…lobe component to fix flickering bug, improved culling.
@Developing-Gamer Developing-Gamer self-assigned this Feb 16, 2026
Copilot AI review requested due to automatic review settings February 16, 2026 22:42
@vercel
Copy link

vercel bot commented Feb 16, 2026

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

Project Deployment Actions Updated (UTC)
stack-backend Ready Ready Preview, Comment Mar 10, 2026 3:54am
stack-dashboard Ready Ready Preview, Comment Mar 10, 2026 3:54am
stack-demo Ready Ready Preview, Comment Mar 10, 2026 3:54am
stack-docs Ready Ready Preview, Comment Mar 10, 2026 3:54am

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 16, 2026

Too many files changed for review. (123 files found, 100 file limit)

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a large client-side Playground page and migrates many dashboard pages from legacy UI primitives to new Design-prefixed components; introduces DesignDataTable, updates SmartForm/SmartFormDialog defaults, and adds PageLayout.allowContentOverflow prop.

Changes

Cohort / File(s) Summary
Playground
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/playground/page-client.tsx
New ~1700-line client Playground: component selector, live preview, per-component controls, demo data, and TSX code generation for many Design components.
Design Data Table Component
apps/dashboard/src/components/design-components/table.tsx
New DesignDataTable wrapper around DataTable with glassmorphic mode, optional DesignCard wrapping, toolbar placement, and onTableReady handling.
DataTable Migrations
apps/dashboard/src/components/data-table/* (api-key-table.tsx, permission-table.tsx, team-table.tsx)
Replaced DataTable with DesignDataTable, added glassmorphic prop and inlined/adjusted toolbar rendering.
Design-component swaps (projects area)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/* (e.g., setup-page.tsx, apps/page-client.tsx, api-keys-app/page-client.tsx, project-settings/page-client.tsx, project-permissions/page-client.tsx, project-keys/page-client.tsx, team-permissions/page-client.tsx, team-settings/page-client.tsx, vercel/page-client.tsx, webhooks/*, launch-checklist/page-client.tsx, onboarding/page-client.tsx, data-vault/stores/*, email-templates/page-client.tsx)
Systematic replacement of legacy UI primitives (Button, Card, Alert, Badge, Tabs, etc.) with Design-prefixed components (DesignButton, DesignCard, DesignAlert, DesignBadge, DesignCategoryTabs, DesignEditableGrid, DesignInput, DesignListItemRow, etc.), plus dialog/flow adjustments (ActionDialog, SmartFormDialog) and UI refinements.
PageLayout API
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/page-layout.tsx
Added optional allowContentOverflow?: boolean and adjusted content container classes to allow overflow when true.
SmartForm / Dialog defaults
apps/dashboard/src/components/smart-form.tsx, apps/dashboard/src/components/form-dialog.tsx
SmartForm gains defaultValues? and isOpen? with reset-on-open behavior; SmartFormDialog now accepts defaultValues and forwards isOpen.
Dropdown / DataTable base / Faceted filter tweaks
apps/dashboard/src/components/ui/dropdown-menu.tsx, apps/dashboard/src/components/ui/data-table/data-table.tsx, apps/dashboard/src/components/ui/data-table/faceted-filter.tsx
DropdownMenuItem async handling improved (Promise detection/loading/auto-close); DataTableBase guards onTableReady to call once; faceted filter styling and interaction refinements.
Webhooks & Apps pages
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/*, apps/page-client.tsx
Migrated endpoint tables to DesignDataTable, switched status mapping to Map, and replaced alerts/buttons/badges with Design variants; refactored dialogs/details rendering.
Design editable grid & settings migrations
multiple files under projects (api-keys, team-settings, data-vault, launch-checklist)
Reworked inline edit/save flows to DesignEditableGrid and ActionDialog, centralizing modified-keys tracking and simplifying modal flows.
Misc & docs/backend notes
claude/CLAUDE-KNOWLEDGE.md, apps/backend/.../email-templates/[templateId]/route.tsx
Added linting Q&A; clarified dynamic override typing/comments for PATCH/DELETE handlers; minor dialog return-value signaling in email-templates page-client.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Launch checklist redesign #1009: Edits the same launch-checklist page and TaskCard-related props/status logic—strongly related to the UI/component swaps in this PR.

Suggested reviewers

  • nams1570

Poem

🐰 I hopped through code with nimble feet,

Replaced old buttons with designs so neat,
A playground of previews, snippets that gleam,
Widgets and dialogs stitched into a dream,
🥕 Hooray — I nibble on bugs and ship a new theme!

🚥 Pre-merge checks | ✅ 1 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is entirely empty except for a template reminder, providing no context about the extensive refactoring across multiple dashboard pages and components. Add a comprehensive description explaining the design-system migration, affected areas, and any breaking changes or coordination needed.
Docstring Coverage ⚠️ Warning Docstring coverage is 19.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and overly broad, using non-specific terms like 'Update dashboard components' that don't convey meaningful information about the primary change across 25+ modified files. Consider a more specific title that highlights the main design system refactor (e.g., 'Migrate dashboard UI components to design-system components') or group updates by major themes.
✅ Passed checks (1 passed)
Check name Status Explanation
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into dev

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch update-dashboard-components

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR updates dashboard UI components with a new design system, excluding four major components. It introduces a comprehensive set of design components with glassmorphic styling, improved visual consistency, and enhanced user experience across the dashboard.

Changes:

  • Introduces new design component library (design-components) with glassmorphic styling and consistent visual patterns
  • Updates email editing interface with WYSIWYG capabilities and improved code editor theming
  • Enhances theme toggle with animated view transitions and cursor blast effects
  • Refactors numerous page layouts and data tables to use new design system components

Reviewed changes

Copilot reviewed 106 out of 123 changed files in this pull request and generated no comments.

Show a summary per file
File Description
apps/e2e/tests/backend/endpoints/api/v1/internal/email-templates-create.test.ts Adds tests for email template creation validation with shared/custom email configs
apps/e2e/tests/backend/endpoints/api/v1/internal/email-drafts.test.ts Adds tests for email draft deletion functionality
apps/e2e/tests/backend/endpoints/api/v1/emails/email-queue.test.ts Updates email HTML snapshots with new theme styling including margin and shadow properties
apps/e2e/tests/backend/endpoints/api/v1/email-themes.test.ts Updates default email theme to use Section component and improved responsive layout
apps/dashboard/src/lib/prefetch/url-prefetcher.tsx Fixes incorrect array transformation that was returning nested arrays
apps/dashboard/src/components/vibe-coding/index.ts Exports additional types for viewport mode and WYSIWYG debug info
apps/dashboard/src/components/vibe-coding/code-editor.tsx Enhances code editor with custom themes, improved font settings, and better scrollbar styling
apps/dashboard/src/components/vibe-coding/chat-adapters.ts Improves message formatting by separating tool calls from content
apps/dashboard/src/components/vibe-coding/assistant-chat.tsx Restructures chat layout with border and off-white light mode support
apps/dashboard/src/components/ui/simple-tooltip.tsx Adds pointer events to tooltip content and adjusts delay duration
apps/dashboard/src/components/ui/select.tsx Updates select components with new styling including rounded borders and glassmorphic effects
apps/dashboard/src/components/ui/input.tsx Enhances input component with size variants, leading icon support, and glassmorphic styling
apps/dashboard/src/components/ui/dropdown-menu.tsx Refactors dropdown menu with improved keyboard navigation and item activation logic
apps/dashboard/src/components/ui/data-table/* Updates data table components with new styling and improved filter UI
apps/dashboard/src/components/ui/button.tsx Updates button outline variant with glassmorphic background
apps/dashboard/src/components/ui/alert.tsx Updates alert styling with increased border radius
apps/dashboard/src/components/theme-toggle.tsx Implements animated theme transitions using View Transition API
apps/dashboard/src/components/theme-provider.tsx Reformats theme provider component
apps/dashboard/src/components/stack-companion.tsx Updates companion UI with improved color scheme for light/dark modes
apps/dashboard/src/components/search-bar.tsx Changes search bar to use Input component props
apps/dashboard/src/components/router.tsx Replaces window.confirm with custom navigation dialog for unsaved changes
apps/dashboard/src/components/resizable-container.tsx Improves resize handles with rounded styling and better visual feedback
apps/dashboard/src/components/navbar.tsx Updates navbar with improved border styling and integrated theme toggle
apps/dashboard/src/components/inline-save-discard.tsx Wraps save handler with error alert utility
apps/dashboard/src/components/email-theme-selector.tsx Adds responsive width styling to theme selector
apps/dashboard/src/components/editable-input.tsx Refactors to use design components with improved button styling
apps/dashboard/src/components/design-components/* Introduces comprehensive design component library with tabs, tables, inputs, cards, badges, alerts, lists, menus, and more
apps/dashboard/src/components/data-table/* Updates data tables to use new design system components
apps/dashboard/src/app/globals.css Adds ambient background gradients, view transition styles, and updated color tokens
apps/dashboard/src/app/(main)/handler/[...stack]/page.tsx Wraps Stack handler in container with data attribute for styling
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/* Migrates multiple project pages to use new design components
apps/backend/src/lib/email-rendering.tsx Adds WYSIWYG editing support with JSX transpilation and editable region markers
apps/backend/src/lib/email-rendering.test.tsx Adds tests for editable markers functionality
apps/backend/src/lib/ai-chat/* Improves AI adapter system prompts with detailed design principles
apps/backend/src/app/api/latest/internal/wysiwyg-edit/route.tsx Implements WYSIWYG text editing endpoint using AI with mock mode fallback
apps/backend/src/app/api/latest/internal/email-themes/cud.tsx Adds guard to prevent deletion of active email theme
apps/backend/src/app/api/latest/internal/email-templates/route.tsx Adds validation for shared email server and uses default template source
apps/backend/src/app/api/latest/internal/email-templates/[templateId]/route.tsx Adds DELETE endpoint and improves PATCH handling
apps/backend/src/app/api/latest/internal/email-drafts/[id]/route.tsx Adds DELETE endpoint and improves error handling
apps/backend/src/app/api/latest/internal/ai-chat/[threadId]/route.tsx Adds mock mode, timeout handling, and switches to OpenRouter
apps/backend/src/app/api/latest/emails/render-email/route.tsx Adds support for editable markers in email rendering
apps/backend/.env.development Adds mock OpenRouter API key configuration
AGENTS.md Adds guideline to update Playground page when modifying design components
.vscode/settings.json Adds "glassmorphic" to spell checker dictionary

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 17

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (16)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/globe.tsx (1)

197-217: 🛠️ Refactor suggestion | 🟠 Major

hexSelectedCountry state is dead code.

After this refactor, hexSelectedCountry is written (lines 197, 213, 215, 422) but never read. The tooltip is driven by polygonSelectedCountry (line 200), and the hex color/altitude accessors read selectedCountryRef.current, which points to polygonSelectedCountry (line 208). The entire useEffect (lines 211–217) and the hexSelectedCountry state can be removed.

♻️ Suggested cleanup
-  const [hexSelectedCountry, setHexSelectedCountry] = useState<{ code: string, name: string } | null>(null);
   const [polygonSelectedCountry, setPolygonSelectedCountry] = useState<{ code: string, name: string } | null>(null);
   // Use polygon for tooltip (no gaps), hex just for visual highlighting
   const selectedCountry = polygonSelectedCountry;
-  // Sync hex highlighting with polygon hover (for visual consistency)
-  useEffect(() => {
-    if (polygonSelectedCountry) {
-      setHexSelectedCountry(polygonSelectedCountry);
-    } else {
-      setHexSelectedCountry(null);
-    }
-  }, [polygonSelectedCountry]);

And in onMouseLeave:

              onMouseLeave={() => {
                // Only clear when leaving the entire globe area
-               setHexSelectedCountry(null);
                setPolygonSelectedCountry(null);
#!/bin/bash
# Verify hexSelectedCountry is never read (only written via set*)
echo "=== All references to hexSelectedCountry ==="
rg -n 'hexSelectedCountry' apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/\(overview\)/globe.tsx
echo ""
echo "=== Read usages (excluding setState calls and declarations) ==="
rg -n 'hexSelectedCountry' apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/\(overview\)/globe.tsx | grep -v 'setHexSelectedCountry' | grep -v 'useState'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/(overview)/globe.tsx
around lines 197 - 217, The hexSelectedCountry state is dead code: remove the
declaration const [hexSelectedCountry, setHexSelectedCountry] = useState... and
the useEffect that syncs it to polygonSelectedCountry, and delete any
setHexSelectedCountry(...) calls (e.g., in onMouseLeave or other handlers);
ensure color/altitude accessors and tooltip logic continue to use
selectedCountryRef / polygonSelectedCountry so behavior remains unchanged (check
functions that currently read selectedCountryRef.current to confirm no further
changes are needed).
apps/dashboard/src/components/ui/dropdown-menu.tsx (1)

18-42: ⚠️ Potential issue | 🟡 Minor

Context state ignores defaultOpen, causing desync.

Line 22 initializes internal state with !!props.open, but if a consumer passes defaultOpen={true} (forwarded to Radix via ...props), Radix will open the menu while DropdownMenuContext reports open: false. This would break any context consumer (e.g., DropdownMenuItem's setOpen).

Proposed fix
-  const [open, setOpen] = React.useState(!!props.open);
+  const [open, setOpen] = React.useState(props.open ?? props.defaultOpen ?? false);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/ui/dropdown-menu.tsx` around lines 18 - 42, The
internal open state is initialized from props.open only, ignoring
props.defaultOpen and causing desync when Radix is uncontrolled; update the
DropdownMenu component to initialize state with the uncontrolled initial value
by using React.useState(() => props.open ?? props.defaultOpen ?? false) (or
Boolean(props.open ?? props.defaultOpen)) so DropdownMenuContext (open and
setOpen) reflects Radix's defaultOpen on first render; keep the existing
onOpenChange handlers and the prop forwarding to DropdownMenuPrimitive.Root.
apps/backend/src/lib/ai-chat/email-template-adapter.ts (1)

41-85: ⚠️ Potential issue | 🔴 Critical

currentEmailTemplate can be undefined — unguarded property access on line 83.

context.tenancy.config.emails.templates[context.threadId] will be undefined if threadId doesn't match any template key. Accessing .tsxSource on undefined will throw a TypeError at runtime.

Proposed fix
 const CREATE_EMAIL_TEMPLATE_TOOL_DESCRIPTION = (context: ChatAdapterContext) => {
   const currentEmailTemplate = context.tenancy.config.emails.templates[context.threadId];
+  if (!currentEmailTemplate) {
+    throw new Error(`No email template found for thread ID: ${context.threadId}`);
+  }
 
   return `

As per coding guidelines, "Code defensively. Prefer ?? throwErr(...) over non-null assertions, with good error messages explicitly stating the assumption."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/lib/ai-chat/email-template-adapter.ts` around lines 41 - 85,
The template builder accesses
context.tenancy.config.emails.templates[context.threadId] without guarding for
undefined, so CREATE_EMAIL_TEMPLATE_TOOL_DESCRIPTION may dereference
currentEmailTemplate.tsxSource and throw; update the code to retrieve the
template into currentEmailTemplate and replace the unguarded access with a
defensive check using the project's throwErr pattern (e.g. const
currentEmailTemplate = context.tenancy.config.emails.templates[context.threadId]
?? throwErr("...")) or an explicit if/throw, and use that variable when
interpolating ${currentEmailTemplate.tsxSource} so a clear, descriptive error is
thrown when threadId has no matching template.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/apps/page-client.tsx (1)

29-29: 🛠️ Refactor suggestion | 🟠 Major

Prefer ?? throwErr(...) over non-null assertion.

useAdminApp()! uses a non-null assertion. Per coding guidelines, use ?? throwErr("...") with a descriptive message instead.

Fix
-  const adminApp = useAdminApp()!;
+  const adminApp = useAdminApp() ?? throwErr("useAdminApp returned null — expected to be inside AdminInterfaceProvider");

As per coding guidelines: "Code defensively. Prefer ?? throwErr(...) over non-null assertions, with good error messages explicitly stating the assumption".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/apps/page-client.tsx
at line 29, Replace the non-null assertion used when assigning adminApp (const
adminApp = useAdminApp()!) with a defensive null-check using the nullish
coalescing operator: const adminApp = useAdminApp() ?? throwErr("Expected admin
app from useAdminApp but received null/undefined"); ensure throwErr is imported
(or implemented) in the module and provide a clear, descriptive message
referencing the assumption that an admin app must be present; update the
assignment site (adminApp) and any related imports accordingly.
apps/dashboard/src/components/search-bar.tsx (1)

7-15: ⚠️ Potential issue | 🟡 Minor

Potential double-icon overlap when leadingIcon is passed through.

Since React.ComponentProps<typeof Input> now includes leadingIcon, callers can pass it to SearchBar, where it spreads into <Input>. This would render both the SearchBar's own MagnifyingGlassIcon (Line 13) and the Input's leadingIcon layout, causing overlapping icons and doubled pl-8 padding.

Consider either:

  1. Excluding leadingIcon from the forwarded props, or
  2. Refactoring SearchBar to use Input's leadingIcon prop directly instead of manual icon placement.
Option 2: Use Input's leadingIcon prop (preferred)
 export const SearchBar = forwardRefIfNeeded<
   HTMLInputElement,
   React.ComponentProps<typeof Input>
->((props, ref) => (
-  <div className="relative">
-    <Input ref={ref} className="pl-8" {...props} />
-    <MagnifyingGlassIcon className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
-  </div>
+>((props, ref) => (
+  <Input
+    ref={ref}
+    leadingIcon={<MagnifyingGlassIcon className="h-4 w-4" />}
+    {...props}
+  />
 ));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/search-bar.tsx` around lines 7 - 15, The
SearchBar currently always renders MagnifyingGlassIcon while also forwarding
React.ComponentProps<typeof Input> (including leadingIcon), which can cause
duplicate/overlapping icons; update SearchBar (the forwardRefIfNeeded wrapper)
to stop manually placing MagnifyingGlassIcon and instead pass a leadingIcon prop
into <Input> (or alternatively strip leadingIcon from the spread props) so only
one icon/layout path is used; specifically modify the component around
SearchBar, Input, leadingIcon and MagnifyingGlassIcon to either remove
leadingIcon from props before {...props} or set
leadingIcon={<MagnifyingGlassIcon .../>} on <Input> and remove the separate
absolute icon element.
apps/dashboard/src/components/ui/input.tsx (1)

28-41: ⚠️ Potential issue | 🟡 Minor

prefixItem border radius mismatch with new rounded-xl base.

The prefix container uses rounded-l-md (Line 31) while the new base classes use rounded-xl. The input in this branch also applies rounded-l-none, so the left side of the composite input will have a md radius while the right side has xl. Consider updating the prefix container to rounded-l-xl to stay visually consistent.

🔧 Suggested fix
-          <div className={'flex self-stretch justify-center items-center text-muted-foreground pl-3 select-none bg-muted/70 pr-3 border-r border-input rounded-l-md'}>
+          <div className={'flex self-stretch justify-center items-center text-muted-foreground pl-3 select-none bg-muted/70 pr-3 border-r border-input rounded-l-xl'}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/ui/input.tsx` around lines 28 - 41, The
prefixItem branch renders a left-side container with class 'rounded-l-md' which
conflicts with the new base input radius ('rounded-xl') and causes inconsistent
corners; update the prefix container's border-radius class from 'rounded-l-md'
to 'rounded-l-xl' (the JSX block that renders prefixItem containing the div with
classes including 'rounded-l-md') so it matches the input which uses baseClasses
and 'rounded-l-none' for consistent visual rounding.
apps/dashboard/src/components/editable-input.tsx (1)

138-150: ⚠️ Potential issue | 🟠 Major

Double-wrapping with runAsynchronouslyWithAlert breaks DesignButton's loading state.

DesignButton already wraps onClick with useAsyncCallback + runAsynchronouslyWithAlert internally (see button.tsx Lines 68-79). Wrapping the handler here in another runAsynchronouslyWithAlert makes the call fire-and-forget from DesignButton's perspective — await onClick?.(e) resolves immediately, so isLoading never reflects the actual async work, and the button won't stay disabled during the update.

Return the Promise directly instead:

Suggested fix
-          onClick={() => runAsynchronouslyWithAlert(async () => {
+          onClick={async () => {
             try {
               forceAllowBlur.current = true;
               inputRef.current?.blur();
               if (action === "accept") {
                 await handleUpdate(editValue ?? throwErr("No value to update"));
               }
               setEditValue(null);
               setHasChanged(false);
             } finally {
               forceAllowBlur.current = false;
             }
-          })}
+          }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/editable-input.tsx` around lines 138 - 150, The
click handler is double-wrapped with runAsynchronouslyWithAlert which prevents
DesignButton's internal useAsyncCallback from seeing the real Promise; remove
the outer runAsynchronouslyWithAlert wrapper and return the async operation's
Promise directly so DesignButton can await it. Specifically, in the onClick
handler that references forceAllowBlur.current, inputRef.current?.blur(),
handleUpdate(editValue ?? throwErr("No value to update")), setEditValue(null)
and setHasChanged(false), make the function itself async (or return the async
function) instead of calling runAsynchronouslyWithAlert around it so
DesignButton's internal useAsyncCallback/runAsynchronouslyWithAlert can manage
isLoading correctly.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (1)

366-366: 🛠️ Refactor suggestion | 🟠 Major

URL built with plain string interpolation — use urlString or encodeURIComponent.

If projectId or user.id contain special characters, this could produce a malformed URL.

-                    onClick={() => router.push(`/projects/${projectId}/users/${user.id}`)}
+                    onClick={() => router.push(urlString`/projects/${projectId}/users/${user.id}`)}

As per coding guidelines: "Use urlString`` or encodeURIComponent() instead of normal string interpolation for URLs, for consistency".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx
at line 366, The onClick handler builds a URL with plain string interpolation
which can break if projectId or user.id contain special characters; update the
router.push call in the onClick handler (where router.push is invoked) to
construct the path using the project's urlString`` tagged template or by
encoding components with encodeURIComponent(projectId) and
encodeURIComponent(user.id) so the final path is safe and consistent with coding
guidelines.
apps/dashboard/src/components/assistant-ui/thread.tsx (1)

182-196: 🛠️ Refactor suggestion | 🟠 Major

Hover-enter transition on action bars violates coding guidelines.

Lines 187 and 276 use transition-opacity duration-150 which animates the fade-in on hover-enter. Per guidelines, hover-enter should be instant and only the exit should be transitioned. Apply the established pattern:

-      className="flex items-center gap-0.5 self-center opacity-0 group-hover:opacity-100 transition-opacity duration-150"
+      className="flex items-center gap-0.5 self-center opacity-0 group-hover:opacity-100 transition-opacity duration-150 group-hover:transition-none"

The same applies to AssistantActionBar at Line 276.

As per coding guidelines: "WHENEVER you create hover transitions, avoid hover-enter transitions, and just use hover-exit transitions (e.g., transition-colors hover:transition-none)".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/assistant-ui/thread.tsx` around lines 182 -
196, UserActionBar and AssistantActionBar currently apply a transition on
hover-enter via "transition-opacity duration-150" which violates the guideline;
update the className on both components to disable transitions while hovered so
only hover-exit is animated—e.g., keep "transition-opacity duration-150" but add
"group-hover:transition-none" (or "hover:transition-none" if the element itself
is hovered) to the className string so the opacity change on hover-enter is
instant and only the exit uses the transition; locate the className props on the
UserActionBar and AssistantActionBar JSX and insert the group-hover/hover
transition-none modifier accordingly.
apps/dashboard/src/components/editable-grid.tsx (1)

159-181: ⚠️ Potential issue | 🟠 Major

Wrap the async function in a lambda so the helper owns the async flow

Lines 180 and 242 call runAsynchronouslyWithAlert(handleChange(v)), which executes the promise before the utility can establish proper error handling. Instead, pass a function so the utility controls the entire async lifecycle:

Suggested change
- onValueChange={(v) => runAsynchronouslyWithAlert(handleChange(v))}
+ onValueChange={(v) => runAsynchronouslyWithAlert(() => handleChange(v))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/editable-grid.tsx` around lines 159 - 181,
handleChange is being invoked before runAsynchronouslyWithAlert can manage its
lifecycle; update the Select onValueChange (and any other similar calls) to pass
a function reference that calls handleChange instead of calling it directly. For
example, replace runAsynchronouslyWithAlert(handleChange(v)) with
runAsynchronouslyWithAlert(() => handleChange(v)) so runAsynchronouslyWithAlert
controls the async execution and error handling for handleChange.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/page-client.tsx (1)

61-63: ⚠️ Potential issue | 🟡 Minor

URL constructed via template literal instead of urlString.

storeId could contain characters that need encoding. Use urlString for consistency and safety.

♻️ Suggested fix

Add the import:

import { urlString } from "@stackframe/stack-shared/dist/utils/urls";
  const handleStoreClick = (storeId: string) => {
-   router.push(`/projects/${project.id}/data-vault/stores/${storeId}`);
+   router.push(urlString`/projects/${project.id}/data-vault/stores/${storeId}`);
  };

As per coding guidelines, "Use urlString`` or encodeURIComponent() instead of normal string interpolation for URLs, for consistency".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/data-vault/stores/page-client.tsx
around lines 61 - 63, The handleStoreClick function constructs a URL with a
template literal that can be unsafe for special characters; update
handleStoreClick to build the path using urlString (imported from
"@stackframe/stack-shared/dist/utils/urls") instead of direct string
interpolation and pass the result to router.push, ensuring you reference
project.id and the storeId parameter via urlString so storeId is properly
encoded.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx (1)

854-862: ⚠️ Potential issue | 🟠 Major

Error notification uses toast instead of an alert.

"No recipients selected" at line 856 is a blocking user error that prevents form submission. Per the coding guidelines, this should use an alert component rather than a toast, which is easily missed.

As per coding guidelines: "For blocking alerts and errors, never use toast, as they are easily missed by the user. Instead, use alerts."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/emails/page-client.tsx
around lines 854 - 862, The current handleNext function uses toast for the
blocking "No recipients selected" error; replace that toast call with the alert
component used elsewhere in the app to surface blocking errors (show the same
title "No recipients selected" and description "Please select at least one
recipient to send the email." and use the destructive/negative styling variant).
Update the logic inside handleNext so the alert is rendered/shown when
selectedUsers.length === 0 and still returns "prevent-close" to stop submission;
reference the handleNext function to locate and change the toast invocation to
an alert-based pattern consistent with other alert usages in the codebase.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx (1)

411-414: 🛠️ Refactor suggestion | 🟠 Major

as any cast without explanation.

Line 412 casts appFrontend to any for a truthiness check. This bypasses the type system. If the intent is to guard against undefined, use a proper == null check on the original type, or add a comment explaining why the cast is needed.

Suggested fix
-      if (!(appFrontend as any)) {
+      if (appFrontend == null) {

As per coding guidelines: "Do NOT use as/any/type casts or anything else like that to bypass the type system unless you specifically asked the user about it."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
around lines 411 - 414, The code uses an unsafe cast "(appFrontend as any)" to
perform a truthiness check; remove the cast and perform a proper null/undefined
guard on the real value returned from ALL_APPS_FRONTEND[enabledApp] (the
appFrontend variable) — e.g. replace the check with "if (appFrontend == null)
continue;" or "if (typeof appFrontend === 'undefined') continue;" (or add a
short comment if a more specific type-narrowing reason exists) so the type
system is not bypassed while keeping the same control flow.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx (1)

126-144: ⚠️ Potential issue | 🔴 Critical

Use runAsynchronouslyWithAlert to handle errors from async operations instead of try/finally without catch.

handleTransfer wraps transferProject in try/finally but lacks a catch block. If the operation throws, the error propagates unhandled through ActionDialog's onClick handler (which is a bare async function with no error handling), and the dialog closes without user feedback. Per the codebase guidelines, use runAsynchronouslyWithAlert for async button handlers to automatically show errors and handle promise rejections properly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
around lines 126 - 144, Replace the current try/finally in handleTransfer with
runAsynchronouslyWithAlert so promise rejections show an alert instead of
bubbling unhandled; specifically, inside handleTransfer call
runAsynchronouslyWithAlert(async () => { setIsTransferring(true); await
user.transferProject(project.id, selectedTeamId); toast({ title: 'Project
transferred successfully', variant: 'success' }); window.location.reload(); } ,
/* optional message */) and ensure setIsTransferring(false) is executed in a
finally block inside that async callback (use setIsTransferring and
isTransferring as before); remove the outer try/finally so errors are routed
through runAsynchronouslyWithAlert.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx (2)

267-276: ⚠️ Potential issue | 🟡 Minor

Use performance.now() instead of Date.now() for elapsed-time measurement.

The confetti animation loop compares Date.now() against a deadline to measure elapsed time. Date.now() can drift with system clock adjustments; performance.now() is the correct choice here.

🛡️ Proposed fix
-      const animationEnd = Date.now() + duration;
+      const animationEnd = performance.now() + duration;
       const defaults = { startVelocity: 30, spread: 360, ticks: 60, zIndex: 9999 };
 
       function randomInRange(min: number, max: number) {
         return Math.random() * (max - min) + min;
       }
 
       const interval = setInterval(() => {
-        const timeLeft = animationEnd - Date.now();
+        const timeLeft = animationEnd - performance.now();

As per coding guidelines, "Don't use Date.now() for measuring elapsed (real) time, instead use performance.now()".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/vercel/page-client.tsx
around lines 267 - 276, The timing uses Date.now() for the confetti loop
(variables duration, animationEnd and the interval callback) which can drift;
change to performance.now() by computing animationEnd = performance.now() +
duration and replace Date.now() with performance.now() inside the interval
callback (and anywhere else using Date.now() to measure elapsed time, e.g. in
the interval computation of timeLeft) so the elapsed-time measurement uses the
high-resolution, monotonic timer.

86-87: ⚠️ Potential issue | 🟡 Minor

Use ?? throwErr(...) instead of non-null assertions.

Both publishableClientKey and secretServerKey can be null at runtime. Replace ! with a defensive fallback that surfaces a clear error.

🛡️ Proposed fix
-          publishableClientKey: newKey.publishableClientKey!,
-          secretServerKey: newKey.secretServerKey!,
+          publishableClientKey: newKey.publishableClientKey ?? throwErr("Expected publishableClientKey to be defined after creating an API key with hasPublishableClientKey: true"),
+          secretServerKey: newKey.secretServerKey ?? throwErr("Expected secretServerKey to be defined after creating an API key with hasSecretServerKey: true"),

As per coding guidelines, "Code defensively. Prefer ?? throwErr(...) over non-null assertions, with good error messages explicitly stating the assumption".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/vercel/page-client.tsx
around lines 86 - 87, Replace the non-null assertions on
newKey.publishableClientKey and newKey.secretServerKey with defensive fallbacks
using the project's throwErr helper so a clear error is thrown if either is
null; specifically, change the assignments for publishableClientKey and
secretServerKey in page-client.tsx from using "newKey.publishableClientKey!" /
"newKey.secretServerKey!" to "newKey.publishableClientKey ?? throwErr('Expected
publishableClientKey from createNewKey')" and "newKey.secretServerKey ??
throwErr('Expected secretServerKey from createNewKey')" (or similar descriptive
messages) to surface a clear runtime error instead of using non-null assertions.
🟡 Minor comments (17)
apps/dashboard/src/components/resizable-container.tsx-210-215 (1)

210-215: ⚠️ Potential issue | 🟡 Minor

Hardcoded bg-white won't adapt to dark mode.

Line 212 uses bg-white for the container background. Given that the ring already has dark-mode support (dark:ring-white/[0.08]), the background should likely also respect dark mode.

♻️ Suggested fix
-          "bg-white shadow-2xl",
+          "bg-white dark:bg-zinc-900 shadow-2xl",

Adjust the dark color to match your design system's surface token.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/resizable-container.tsx` around lines 210 -
215, The container currently hardcodes "bg-white" inside the className passed to
cn (the JSX in ResizableContainer / resizable-container.tsx); update that class
string to use your design-system surface token and add a dark-mode variant (for
example replace "bg-white" with the surface token and "dark:..." counterpart
such as "bg-surface dark:bg-surface-<dark-token>" or whatever token names your
design system uses) so the background follows dark mode while keeping the
existing ring classes and other utilities untouched.
apps/backend/src/app/api/latest/internal/wysiwyg-edit/route.tsx-151-151 (1)

151-151: ⚠️ Potential issue | 🟡 Minor

Mock-mode // comment may break non-JS source types.

The source_type field accepts "template", "theme", and "draft". If "theme" represents CSS or a non-JS format, prepending a // NOTE: ... comment produces invalid syntax. Consider using a format-appropriate comment or skipping the note for non-JS types.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/app/api/latest/internal/wysiwyg-edit/route.tsx` at line 151,
The current construction of updatedSource unconditionally prepends a // comment
which can invalidate non-JS sources; update the logic in route.tsx around the
updatedSource creation (the updatedSource variable and where source_type is
read) to choose a source-type-appropriate comment or omit the note: implement a
small helper (e.g., getCommentPrefixForSourceType(source_type)) that returns
'//' for JS-like types, '/* ... */' or '/*...*/\n' for CSS/theme, '<!-- ... -->'
for HTML templates, or an empty string for unknown binary formats, then prepend
that prefix + note only when non-empty before ${replacedSource}; ensure the
helper covers "template", "theme", and "draft" cases and falls back to omitting
the note.
apps/backend/src/app/api/latest/internal/wysiwyg-edit/route.tsx-27-27 (1)

27-27: ⚠️ Potential issue | 🟡 Minor

Typo in system prompt.

Line 31: "without massively the rest of the source code" → should be "without massively changing the rest of the source code".

Fix
-8. Context: The user is editing the text in a WYSIWYG editor. They expect that the change they made will be reflected as-is, without massively the rest of the source code. However, in most cases, the user don't actually care about the rest of the source code, so in the rare cases where things are complex and you would have to change a bit more than just the text node, you should make the changes that sound reasonable from a UX perspective.
+8. Context: The user is editing the text in a WYSIWYG editor. They expect that the change they made will be reflected as-is, without massively changing the rest of the source code. However, in most cases, the user doesn't actually care about the rest of the source code, so in the rare cases where things are complex and you would have to change a bit more than just the text node, you should make the changes that sound reasonable from a UX perspective.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/app/api/latest/internal/wysiwyg-edit/route.tsx` at line 27,
There's a typo in the system prompt text in route.tsx: replace the phrase
"without massively the rest of the source code" with "without massively changing
the rest of the source code" so the prompt reads correctly; locate the system
prompt string (used in the WYSIWYG edit route handler in
apps/backend/src/app/api/latest/internal/wysiwyg-edit/route.tsx) and update that
literal accordingly.
apps/dashboard/src/components/design-components/button.tsx-58-58 (1)

58-58: ⚠️ Potential issue | 🟡 Minor

Duplicate displayName — both inner and outer button are "DesignButton".

DesignOriginalButton has displayName = "DesignButton" (Line 58) and DesignButton also has displayName = "DesignButton" (Line 91). This makes the two components indistinguishable in React DevTools.

Suggested fix
-DesignOriginalButton.displayName = "DesignButton";
+DesignOriginalButton.displayName = "DesignOriginalButton";
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/design-components/button.tsx` at line 58, Both
components share the same displayName; change DesignOriginalButton.displayName
to a unique name so React DevTools can distinguish them. Locate the displayName
assignments for DesignOriginalButton and DesignButton and set them to distinct
identifiers (e.g., DesignOriginalButton.displayName = "DesignOriginalButton" and
keep DesignButton.displayName = "DesignButton") so each component has a unique
displayName.
apps/dashboard/src/components/design-components/tabs.tsx-96-102 (1)

96-102: ⚠️ Potential issue | 🟡 Minor

!value in getMapValueOrThrow fails for falsy values like 0, "", or false.

Map.get returns undefined for missing keys, so the check should be value === undefined to avoid incorrectly throwing for legitimately stored falsy values. While current callers use object values, the helper is generic.

Suggested fix
 function getMapValueOrThrow<TKey, TValue>(map: Map<TKey, TValue>, key: TKey, mapName: string) {
-  const value = map.get(key);
-  if (!value) {
+  if (!map.has(key)) {
     throw new Error(`Missing ${mapName} entry for key "${String(key)}"`);
   }
-  return value;
+  return map.get(key) as TValue;
 }

Or keep the get + explicit undefined check:

   const value = map.get(key);
-  if (!value) {
+  if (value === undefined) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/design-components/tabs.tsx` around lines 96 -
102, The check in getMapValueOrThrow incorrectly treats falsy stored values (0,
"", false) as missing; update the existence test to only treat undefined as
missing by checking value === undefined (or value === undefined || map.has(key)
=== false pattern), so replace the current if (!value) throw with an explicit
undefined check while keeping the generic signature of getMapValueOrThrow and
using map.get/key/mapName to build the error message.
apps/dashboard/src/components/email-preview.tsx-623-635 (1)

623-635: ⚠️ Potential issue | 🟡 Minor

Fix editableRegions type at the source instead of casting at usage

Line 631 casts editableRegions[id] to EditableMetadata to work around the hook returning Record<string, unknown>. Update useEmailPreviewWithEditableMarkers in packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts (line 682) to return Record<string, EditableMetadata> instead of Record<string, unknown>, eliminating the need for the cast.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/email-preview.tsx` around lines 623 - 635, The
hook useEmailPreviewWithEditableMarkers currently returns Record<string,
unknown>, forcing callers to cast editableRegions[id] to EditableMetadata;
update the hook implementation (useEmailPreviewWithEditableMarkers) to return
Record<string, EditableMetadata> (or a typed interface) so editableRegions is
correctly typed at the source, adjust any internal logic/returns inside that
function to construct and return EditableMetadata values, and update its
signature/export so callers like the component using editableRegions no longer
need to cast.
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/playground/page-client.tsx-327-341 (1)

327-341: ⚠️ Potential issue | 🟡 Minor

Replace row.getValue() with row.original to eliminate unnecessary type casts

Lines 331 and 339 use as type assertions on row.getValue(...). Since the column definition is typed as ColumnDef<DemoProduct>[], row.original is already properly typed as DemoProduct and eliminates the need for casts.

Suggested change
- cell: ({ row }) => (
-   <span className="text-sm text-muted-foreground">
-     ${(row.getValue("price") as number).toFixed(2)}
-   </span>
- ),
+ cell: ({ row }) => (
+   <span className="text-sm text-muted-foreground">
+     ${row.original.price.toFixed(2)}
+   </span>
+ ),
...
- const s = row.getValue("status") as DemoProduct["status"];
- return <DesignBadge label={STATUS_BADGE[s].label} color={STATUS_BADGE[s].color} size="sm" />;
+ const status = row.original.status;
+ return <DesignBadge label={STATUS_BADGE[status].label} color={STATUS_BADGE[status].color} size="sm" />;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/playground/page-client.tsx
around lines 327 - 341, Replace unnecessary type assertions by using the typed
row.original property: in the price column cell (currently using
(row.getValue("price") as number).toFixed(2)) use the typed value from
row.original.price, and in the status column cell (currently using
row.getValue("status") as DemoProduct["status"]) use row.original.status to feed
STATUS_BADGE and DesignBadge; this leverages the ColumnDef<DemoProduct>[] typing
and removes the need for casts on row.getValue.
apps/dashboard/src/components/editable-grid.tsx-438-442 (1)

438-442: ⚠️ Potential issue | 🟡 Minor

Use a stable key for grid items

Line 440 uses key={index}; if items reorder, React can reuse state incorrectly. Since itemKey is available in all item types (via BaseItemProps), use that with a fallback for stability.

Suggested change
- key={index}
+ key={item.itemKey ?? index}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/editable-grid.tsx` around lines 438 - 442, The
map currently uses an unstable index key (key={index}) for GridItemContent which
can cause incorrect state reuse when items reorder; change it to use the item's
stable identifier (item.itemKey) as the key and fall back to a stable derived
value only if item.itemKey is missing so keys remain consistent; update the
items.map call that renders <GridItemContent ...> to use item.itemKey (with a
deterministic fallback) and ensure any code referencing externalModifiedKeys
still uses item.itemKey to check modification status.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates/[templateId]/page-client.tsx-98-105 (1)

98-105: ⚠️ Potential issue | 🟡 Minor

Use explicit check instead of !currentCode for string state.

Line 100 uses !currentCode which is a boolean check on a string. An empty template source ("") would be falsy, which means the sync would trigger even if the user had cleared the editor. Consider an explicit check that better represents the intent, e.g. tracking whether the template has been initially loaded.

As per coding guidelines, "prefer explicit null/undefinedness checks over boolean checks, eg. foo == null instead of !foo".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/email-templates/[templateId]/page-client.tsx
around lines 98 - 105, The useEffect currently checks `!currentCode`, which
treats an empty string as unset; change that to an explicit null/undefined check
(e.g., `currentCode == null`) so only truly uninitialized state triggers the
sync; update the conditional in the useEffect that references
`templateFromHook`, `currentCode`, `hasSyncedTemplateFromHook`, and the calls to
`setCurrentCode`/`setSelectedThemeId` to use the explicit null check for
`currentCode` while keeping the existing `hasSyncedTemplateFromHook` guard to
ensure initial-load-only behavior.
apps/backend/src/app/api/latest/emails/render-email/route.tsx-82-83 (1)

82-83: ⚠️ Potential issue | 🟡 Minor

Avoid the as type cast — use a fallback or explicit check instead.

Line 83 uses as 'template' | 'theme' | 'both' to bypass the type system. Since yup validates the value via .oneOf(...), the runtime value is guaranteed, but the cast hides this from TypeScript. Consider a safer pattern:

Proposed fix
-    const editableSource = ('editable_source' in body ? body.editable_source : 'template') as 'template' | 'theme' | 'both';
+    const rawEditableSource = 'editable_source' in body ? body.editable_source : undefined;
+    const editableSource: 'template' | 'theme' | 'both' =
+      rawEditableSource === 'template' || rawEditableSource === 'theme' || rawEditableSource === 'both'
+        ? rawEditableSource
+        : 'template';

As per coding guidelines, "Do NOT use as/any/type casts or anything else like that to bypass the type system unless you specifically asked the user about it".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/app/api/latest/emails/render-email/route.tsx` around lines
82 - 83, The code uses a type cast on editableSource to silence TypeScript;
instead add a runtime check or type guard so TypeScript can infer the union
without "as". Replace the cast on editableSource by validating
body.editable_source with a small predicate or includes check (e.g. an
isEditableSource(value): value is 'template'|'theme'|'both' or
['template','theme','both'].includes(value)) and then set const editableSource =
isEditableSource(body.editable_source) ? body.editable_source : 'template';
ensure you reference the existing editableSource variable and
body.editable_source so the type is narrowed safely without using as.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx-350-355 (1)

350-355: ⚠️ Potential issue | 🟡 Minor

Yup schema allows empty string for draft name.

yup.string().defined() permits empty strings. While the required HTML attribute on the InputField (line 362) provides browser-level validation, the schema itself would pass "". Consider adding .required() to the yup schema for robust validation.

♻️ Suggested fix
       formSchema={yup.object({
-        name: yup.string().defined().label("Draft name"),
+        name: yup.string().required().label("Draft name"),
       })}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/email-drafts/page-client.tsx
around lines 350 - 355, The yup schema for the form (the formSchema passed to
the component) currently uses yup.string().defined() for the "name" field which
allows empty strings; update the schema for the name field in the formSchema
(the yup.object used alongside handleCreateNewDraft) to enforce non-empty values
by chaining .required() (optionally also .trim().min(1) to reject
whitespace-only names) so the schema itself will reject empty/blank draft names
instead of relying only on the InputField HTML required attribute.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx-395-438 (1)

395-438: ⚠️ Potential issue | 🟡 Minor

Column definitions are re-created on every render; also, status is always hardcoded to "Active".

Two observations:

  1. columns is defined inside the else branch without useMemo, so it's re-created on every render. This can cause unnecessary table re-renders.

  2. The "Status" column (lines 414-423) always renders a green "Active" badge regardless of the actual endpoint state. If Svix endpoints can be disabled or paused, this misrepresents their status.

♻️ Consider memoizing the columns

Move the column definitions into a useMemo or extract them outside the component (since they depend on props).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx
around lines 395 - 438, The columns array (columns) is being re-created on every
render and the "Status" column always shows a hardcoded "Active" badge; wrap the
column definitions in a useMemo so they are memoized based on dependencies
(e.g., props.updateFn, props.onTestRequested) and update the status column cell
to read the endpoint state from row.original (e.g., row.original.status or
row.original.enabled) and render DesignBadge with the appropriate label and
color instead of the hardcoded "Active"; ensure ActionMenu still receives
endpoint={row.original} and the memo includes any props it uses.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/team-settings/page-client.tsx-37-42 (1)

37-42: ⚠️ Potential issue | 🟡 Minor

useEffect resets selection on every defaultPermissions reference change while dialog is open.

defaultPermissions is an object derived from config, so its reference may change on every render (or whenever config re-fetches). If this happens while the dialog is open, the user's in-progress checkbox changes get silently discarded. Consider memoizing defaultPermissions or comparing by value (e.g., JSON.stringify) to avoid spurious resets.

♻️ Suggested approach
+  const defaultPermissionsKey = JSON.stringify(defaultPermissions);
+
   // Reset selection when dialog opens
   useEffect(() => {
     if (open) {
       setSelectedIds(Object.keys(defaultPermissions).filter(id => defaultPermissions[id]));
     }
-  }, [open, defaultPermissions]);
+  // eslint-disable-next-line react-hooks/exhaustive-deps -- compare by value, not reference
+  }, [open, defaultPermissionsKey]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/team-settings/page-client.tsx
around lines 37 - 42, The useEffect that resets selection when the dialog opens
is using the mutable reference defaultPermissions, causing selection to be reset
whenever defaultPermissions' reference changes while open; update the code so
the effect only depends on stable values—either memoize defaultPermissions where
it's created (so the reference is stable) or change the effect to compare values
(e.g., derive a stringifiedKey = JSON.stringify(defaultPermissions) and depend
on that) and keep the current conditions (open) and update via
setSelectedIds(Object.keys(defaultPermissions).filter(id =>
defaultPermissions[id])) only when the actual permissions value changes rather
than on reference equality; ensure you adjust dependencies to use the memoized
variable or the stringified value in the useEffect signature.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails/page-client.tsx-709-719 (1)

709-719: ⚠️ Potential issue | 🟡 Minor

Boolean checks for missing fields: port 0 would be falsely flagged as missing.

!emailServerConfig.port is falsy when the port is 0, which is a valid (though uncommon) port number and is explicitly allowed by the schema (yup.number().min(0, ...)). Use explicit null/undefined checks instead.

Suggested fix
-      if (!emailServerConfig.host) missingFields.push("host");
-      if (!emailServerConfig.port) missingFields.push("port");
-      if (!emailServerConfig.username) missingFields.push("username");
-      if (!emailServerConfig.password) missingFields.push("password");
-      if (!emailServerConfig.senderName) missingFields.push("sender name");
-      if (!emailServerConfig.senderEmail) missingFields.push("sender email");
+      if (emailServerConfig.host == null) missingFields.push("host");
+      if (emailServerConfig.port == null) missingFields.push("port");
+      if (emailServerConfig.username == null) missingFields.push("username");
+      if (emailServerConfig.password == null) missingFields.push("password");
+      if (emailServerConfig.senderName == null) missingFields.push("sender name");
+      if (emailServerConfig.senderEmail == null) missingFields.push("sender email");

As per coding guidelines: "Unless very clearly equivalent from types, prefer explicit null/undefinedness checks over boolean checks, eg. foo == null instead of !foo."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/emails/page-client.tsx
around lines 709 - 719, The null/undefined check logic currently uses boolean
falsiness (e.g., !emailServerConfig.port) which incorrectly treats valid falsy
values like 0 as missing; update the checks to use explicit null/undefined
checks—for example replace the port check with emailServerConfig.port == null
(or === null || === undefined) before pushing "port" to missingFields; apply the
same explicit null/undefined style for any other fields where falsy but valid
values may exist, then keep the setError and return "prevent-close" behavior
unchanged.
apps/dashboard/src/components/design-components/list.tsx-197-206 (1)

197-206: ⚠️ Potential issue | 🟡 Minor

Empty name produces an invisible avatar initial.

If name is an empty string, name.charAt(0) returns "", leaving the avatar circle blank with no visual fallback.

Suggested fix
-      {name.charAt(0)}
+      {name.charAt(0) || "?"}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/design-components/list.tsx` around lines 197 -
206, UserAvatar currently renders name.charAt(0) which produces an empty string
for an empty or whitespace-only name and leaves the avatar blank; update the
rendering in the UserAvatar component to compute a safe initial (for example:
take name?.trim().charAt(0)?.toUpperCase() and fall back to a default like "?"
when that result is falsy) so the avatar always shows a visible character;
reference the UserAvatar function and its name prop to locate where to replace
name.charAt(0) with this guarded fallback.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts/[draftId]/page-client.tsx-279-287 (1)

279-287: ⚠️ Potential issue | 🟡 Minor

getErrorMessage is defined but never used.

This function at the bottom of the file is unreferenced — it appears to be dead code left over from refactoring.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/email-drafts/[draftId]/page-client.tsx
around lines 279 - 287, The function getErrorMessage is dead code and should be
removed; delete the getErrorMessage function (and its dependency on
KnownErrors.EmailRenderingError if that import is only used by it) from
page-client.tsx to clean up unused code, or if its logic is still needed,
replace inline error handling in the surrounding components/methods with calls
to getErrorMessage (e.g., where errors are rendered) and ensure
KnownErrors.EmailRenderingError remains imported; locate the symbol
getErrorMessage to remove or replace and adjust imports accordingly.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes/page-client.tsx-275-282 (1)

275-282: ⚠️ Potential issue | 🟡 Minor

Preview senderEmail derivation could produce an odd address for edge-case project names.

If project.displayName is empty or all-special-characters, the email becomes noreply@.com. This is only for a preview so the impact is cosmetic, but a fallback would be cleaner:

- senderEmail={`noreply@${project.displayName.toLowerCase().replace(/[^a-z0-9]/g, '')}.com`}
+ senderEmail={`noreply@${project.displayName.toLowerCase().replace(/[^a-z0-9]/g, '') || 'example'}.com`}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/email-themes/page-client.tsx
around lines 275 - 282, The preview senderEmail can become "noreply@.com" when
project.displayName is empty or fully sanitized away; update the code that
supplies the senderEmail prop for EmailPreview to compute a sanitized local-part
(e.g., lowercased project.displayName with non-alphanumerics removed) and if
that result is empty use a safe fallback (like project.id, "example", or
"default") so senderEmail is always a valid-looking address (e.g.,
`noreply@{sanitizedOrFallback}.com`); adjust where senderEmail is constructed
near the EmailPreview usage that references selectedThemeData and
project.displayName.

Copy link

@vercel vercel bot left a comment

Choose a reason for hiding this comment

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

Additional Suggestion:

Delete dialog closes immediately on error, hiding the error message from the user

Fix on Vercel

- Added dynamic string handling for email template configuration overrides.
- Updated line-chart component to use urlString for routing.
- Refactored data vault store configuration to directly set displayName.
- Improved error handling in email template deletion process.
- Enhanced launch checklist with new asynchronous alert functionality.
- Updated Vercel page to ensure proper key assignment with error handling.
- Modified dropdown menu state initialization for better default handling.
- Improved data table component to prevent multiple calls on table readiness.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

Caution

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

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

150-156: ⚠️ Potential issue | 🔴 Critical

Bug: "prevent-close" return value from handleDelete is silently discarded.

The onClick handler awaits handleDelete but never returns its result, so ActionDialog always sees undefined and closes the dialog — even when the delete fails. This means the error alert on lines 163-170 is still never visible, despite the return "prevent-close" added on line 36.

Proposed fix
         okButton={{
           label: "Delete",
           onClick: async () => {
             if (deleteDialogOpen) {
-              await handleDelete(deleteDialogOpen);
+              return await handleDelete(deleteDialogOpen);
             }
           },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/email-templates/page-client.tsx
around lines 150 - 156, The okButton onClick awaits
handleDelete(deleteDialogOpen) but does not return its result, so the
"prevent-close" sentinel is discarded and ActionDialog always closes; update the
okButton.onClick handler in the component (the okButton block where
deleteDialogOpen is checked) to return the awaited result of
handleDelete(deleteDialogOpen) (i.e., use "return await
handleDelete(deleteDialogOpen);") so ActionDialog receives the "prevent-close"
value and can keep the dialog open on failure.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx (1)

436-457: ⚠️ Potential issue | 🔴 Critical

Bug: error list never renders — condition is always true.

productionChecksPassing is defined as productionModeErrors.length === 0 (line 418), so the guard on line 437 (productionChecksPassing || productionModeErrors.length === 0) is tautologically true. The else branch (lines 441–456) showing the list of production-mode errors is dead code — users will never see what they need to fix.

The intent was likely !productionChecksPassing as the first operand (or simply checking productionModeErrors.length === 0 alone):

Proposed fix
-          productionChecksPassing || productionModeErrors.length === 0 ? (
+          productionModeErrors.length === 0 ? (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx
around lines 436 - 457, The conditional always evaluates true because
productionChecksPassing is defined as productionModeErrors.length === 0; update
the JSX guard so the error list renders when checks fail by using the negation
(e.g., !productionChecksPassing) or directly checking
productionModeErrors.length > 0 in the ternary condition where
productionChecksPassing || productionModeErrors.length === 0 is currently used;
modify the conditional around productionChecksPassing/productionModeErrors in
the JSX block that renders the "All checks are passing." vs the error list so
the else branch with productionModeErrors.map(...) can execute when there are
errors.
apps/dashboard/src/components/ui/dropdown-menu.tsx (1)

119-152: ⚠️ Potential issue | 🔴 Critical

onClick leaks through the {...props} spread, still causing double execution on mouse clicks.

onClick is not destructured out of ...props on line 119, so {...props} on line 150 passes it directly to DropdownMenuPrimitive.Item. On a mouse click, the native onClick fires props.onClick directly and onSelect fires handleItemAction which calls props.onClick again — the same double-execution bug from the previous review, just via a different path.

Destructure onClick out of the rest props so it doesn't leak to the primitive:

Proposed fix
->(({ className, inset, icon, ...props }, ref) => {
+>(({ className, inset, icon, onClick, ...props }, ref) => {
   const { setOpen } = React.useContext(DropdownMenuContext) ?? throwErr("No DropdownMenuContext found");
   const [isLoading, setIsLoading] = React.useState(false);

   // Share activation logic so keyboard onSelect matches mouse clicks.
   const handleItemAction = (event: { preventDefault: () => void, stopPropagation: () => void }) => {
     event.preventDefault();
     event.stopPropagation();
-    const result = props.onClick?.(event as unknown as React.MouseEvent<HTMLDivElement, MouseEvent>);
+    const result = onClick?.(event as unknown as React.MouseEvent<HTMLDivElement, MouseEvent>);
     if (result instanceof Promise) {
       setIsLoading(true);
       runAsynchronouslyWithAlert(
         result.finally(() => {
           setIsLoading(false);
           setOpen(false);
         })
       );
     } else {
       setOpen(false);
     }
   };
   ...
     {...props}
     disabled={isLoading || props.disabled}
-    onSelect={props.onClick ? handleItemAction : undefined}
+    onSelect={onClick ? handleItemAction : undefined}
   >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/ui/dropdown-menu.tsx` around lines 119 - 152,
The onClick handler is leaking through the {...props} spread and causing double
execution (native onClick plus onSelect -> handleItemAction -> props.onClick);
fix by destructuring onClick from the component args (e.g., change ({ className,
inset, icon, ...props }, ref) to extract onClick: const { onClick, ...rest } or
destructure directly) and use rest in the JSX spread so
DropdownMenuPrimitive.Item does not receive onClick, then reference the
extracted onClick inside handleItemAction and set onSelect={onClick ?
handleItemAction : undefined} and disabled based on isLoading || rest.disabled.
Ensure you update all uses of props to rest and props.onClick to the extracted
onClick (handleItemAction should call the extracted onClick).
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx (1)

63-63: ⚠️ Potential issue | 🟡 Minor

Dead code: error state is declared but never set.

setError (line 63) is never called anywhere — the old catch block that presumably called setError(...) appears to have been removed when runAsynchronouslyWithAlert was adopted. As a result, props.error in StepCard is always null and the DesignAlert on lines 591-595 will never render. Either remove the dead error state and related UI, or restore error-setting logic if per-field error display is still desired alongside the global alert.

Option A: Remove dead code
- const [error, setError] = useState<string | null>(null);

And remove the error prop from StepCard usage and its rendering logic.

Also applies to: 590-595

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/vercel/page-client.tsx
at line 63, The local state error declared as `const [error, setError] =
useState<string | null>(null)` is dead because `setError` is never called after
switching to `runAsynchronouslyWithAlert`; either remove this state and all uses
(remove the `error` prop passed into `StepCard` and the `DesignAlert` rendering
around lines where `props.error` is referenced), or restore error-setting where
failures are handled by calling `setError(...)` inside the async handlers
invoked by `runAsynchronouslyWithAlert` so `StepCard` and `DesignAlert` receive
and display per-field errors; update the `StepCard` prop usage and any
conditional rendering accordingly (refer to `error`, `setError`,
`runAsynchronouslyWithAlert`, `StepCard`, and `DesignAlert` to locate the
changes).
🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx:
- Around line 186-218: Hoist the static mapping const designCardGradients out of
the component and into module scope so it isn't recreated on every render: move
the existing designCardGradients declaration to the top-level of the file (above
the component) and leave all references (designCardGradients[gradientColor])
inside the component unchanged; ensure the identifier name remains the same and
adjust any imports/exports only if you intend to reuse it across files.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/data-vault/stores/[storeId]/page-client.tsx:
- Line 47: Replace the plain template-literal URL construction used in the
DesignButton onClick (router.push(`/projects/${project.id}/data-vault/stores`))
and the other router.push that uses
`/projects/${project.id}/data-vault/stores/${storeId}` with the urlString
template tag (e.g., urlString`/projects/${project.id}/data-vault/stores` and
urlString`/projects/${project.id}/data-vault/stores/${storeId}`) and add the
missing import for urlString at the top of the file so special characters in
project.id or storeId are properly encoded; update the two router.push call
sites (the DesignButton onClick and the other redirect using storeId) to use
urlString instead of plain interpolation.
- Around line 110-117: The onClick currently calls copyToClipboard(storeId) and
drops the returned promise; wrap that async call with the project's async error
handler so clipboard failures aren't swallowed—replace the arrow handler on the
DesignButton with a call to runAsynchronously or runAsynchronouslyWithAlert
(import it if missing) and pass a callback that invokes
copyToClipboard(storeId); keep DesignButton, copyToClipboard, and the chosen
runAsynchronously/runAsynchronouslyWithAlert identifiers to locate and update
the handler.
- Around line 36-54: The code reads `store = config.dataVault.stores[storeId]`
before checking existence and declares `modifiedKeys` with `useMemo`, which can
leave `store` typed as possibly undefined later; fix by either moving the
existence guard (the `if (!(storeId in config.dataVault.stores))` block) above
the `store` and `useMemo` declarations so hooks remain in the same order across
returns, or keep the current order and add an explicit defensive narrowing
immediately after the guard (e.g., throw via a utility if `store` is undefined)
so usages later (references to `store`, `modifiedKeys`, `useMemo`) see a
non-undefined `store`; ensure you do not introduce conditional hooks when
reordering.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/email-templates/page-client.tsx:
- Around line 150-156: The okButton onClick awaits
handleDelete(deleteDialogOpen) but does not return its result, so the
"prevent-close" sentinel is discarded and ActionDialog always closes; update the
okButton.onClick handler in the component (the okButton block where
deleteDialogOpen is checked) to return the awaited result of
handleDelete(deleteDialogOpen) (i.e., use "return await
handleDelete(deleteDialogOpen);") so ActionDialog receives the "prevent-close"
value and can keep the dialog open on failure.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx:
- Around line 539-548: The confetti timer uses Date.now() for elapsed-time
calculations; update the logic to use performance.now() instead by setting
animationEnd based on performance.now() + duration (replace the current
animationEnd = Date.now() + duration) and compute timeLeft as animationEnd -
performance.now() inside the setInterval callback (keep the rest of the confetti
logic including defaults, randomInRange, and interval unchanged). Ensure all
references to Date.now() in this block are replaced so elapsed-time measurement
consistently uses performance.now().
- Around line 149-158: The cardClass values for the checklist states (keys done,
action, blocked) use hover-enter classes like "transition-all duration-300
hover:shadow-lg"; update each cardClass in page-client.tsx to follow the
dashboard hover-exit pattern by moving the transition to the base state and
removing hover-enter effects—specifically, keep a base transition (e.g.,
transition-[property] and duration) and replace hover:shadow-lg with
hover:transition-none (and remove or neutralize any shadow-on-hover) so the
visual transition occurs on hover-exit rather than on hover-enter; update the
cardClass strings for done, action, and blocked accordingly.
- Around line 142-161: STATUS_META currently defines identical cardClass strings
for all LaunchTaskStatus keys (done, action, blocked); either deduplicate by
extracting a shared constant and reusing it in STATUS_META or assign distinct
cardClass values per status if visual differentiation is intended. Update the
STATUS_META declaration (and the LaunchTaskStatus usages) to reference a single
constant like DEFAULT_CARD_CLASS for the common string, or replace the three
identical cardClass values with unique style strings for done/action/blocked so
the per-status lookup becomes meaningful.
- Around line 436-457: The conditional always evaluates true because
productionChecksPassing is defined as productionModeErrors.length === 0; update
the JSX guard so the error list renders when checks fail by using the negation
(e.g., !productionChecksPassing) or directly checking
productionModeErrors.length > 0 in the ternary condition where
productionChecksPassing || productionModeErrors.length === 0 is currently used;
modify the conditional around productionChecksPassing/productionModeErrors in
the JSX block that renders the "All checks are passing." vs the error list so
the else branch with productionModeErrors.map(...) can execute when there are
errors.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/vercel/page-client.tsx:
- Around line 403-414: The subtitle prop on the DesignCard is using the HTML
entity &apos; which renders literally in JSX attributes; update the subtitle
value (on the DesignCard component instance) to use a real apostrophe or a JS
string expression instead (e.g. subtitle="See Vercel's documentation on
environment variables for more details." or subtitle={'See Vercel\\'s
documentation on environment variables for more details.'}) so the apostrophe
displays correctly.
- Around line 492-496: The DesignButton/StyledLink combination currently uses
"transition-all duration-150" which triggers hover-enter transitions; replace
that with a hover-exit pattern such as "transition-colors duration-150
hover:transition-none" (keeping other classes like "font-medium border
border-border shadow-sm active:scale-95 dark:..." intact) for the instance
inside page-client.tsx and the matching button(s) later in the file to follow
the dashboard guideline.
- Line 63: The local state error declared as `const [error, setError] =
useState<string | null>(null)` is dead because `setError` is never called after
switching to `runAsynchronouslyWithAlert`; either remove this state and all uses
(remove the `error` prop passed into `StepCard` and the `DesignAlert` rendering
around lines where `props.error` is referenced), or restore error-setting where
failures are handled by calling `setError(...)` inside the async handlers
invoked by `runAsynchronouslyWithAlert` so `StepCard` and `DesignAlert` receive
and display per-field errors; update the `StepCard` prop usage and any
conditional rendering accordingly (refer to `error`, `setError`,
`runAsynchronouslyWithAlert`, `StepCard`, and `DesignAlert` to locate the
changes).

In `@apps/dashboard/src/components/ui/dropdown-menu.tsx`:
- Around line 119-152: The onClick handler is leaking through the {...props}
spread and causing double execution (native onClick plus onSelect ->
handleItemAction -> props.onClick); fix by destructuring onClick from the
component args (e.g., change ({ className, inset, icon, ...props }, ref) to
extract onClick: const { onClick, ...rest } or destructure directly) and use
rest in the JSX spread so DropdownMenuPrimitive.Item does not receive onClick,
then reference the extracted onClick inside handleItemAction and set
onSelect={onClick ? handleItemAction : undefined} and disabled based on
isLoading || rest.disabled. Ensure you update all uses of props to rest and
props.onClick to the extracted onClick (handleItemAction should call the
extracted onClick).
🧹 Nitpick comments (6)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx:
- Around line 186-218: Hoist the static mapping const designCardGradients out of
the component and into module scope so it isn't recreated on every render: move
the existing designCardGradients declaration to the top-level of the file (above
the component) and leave all references (designCardGradients[gradientColor])
inside the component unchanged; ensure the identifier name remains the same and
adjust any imports/exports only if you intend to reuse it across files.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/data-vault/stores/[storeId]/page-client.tsx:
- Around line 36-54: The code reads `store = config.dataVault.stores[storeId]`
before checking existence and declares `modifiedKeys` with `useMemo`, which can
leave `store` typed as possibly undefined later; fix by either moving the
existence guard (the `if (!(storeId in config.dataVault.stores))` block) above
the `store` and `useMemo` declarations so hooks remain in the same order across
returns, or keep the current order and add an explicit defensive narrowing
immediately after the guard (e.g., throw via a utility if `store` is undefined)
so usages later (references to `store`, `modifiedKeys`, `useMemo`) see a
non-undefined `store`; ensure you do not introduce conditional hooks when
reordering.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx:
- Around line 539-548: The confetti timer uses Date.now() for elapsed-time
calculations; update the logic to use performance.now() instead by setting
animationEnd based on performance.now() + duration (replace the current
animationEnd = Date.now() + duration) and compute timeLeft as animationEnd -
performance.now() inside the setInterval callback (keep the rest of the confetti
logic including defaults, randomInRange, and interval unchanged). Ensure all
references to Date.now() in this block are replaced so elapsed-time measurement
consistently uses performance.now().
- Around line 149-158: The cardClass values for the checklist states (keys done,
action, blocked) use hover-enter classes like "transition-all duration-300
hover:shadow-lg"; update each cardClass in page-client.tsx to follow the
dashboard hover-exit pattern by moving the transition to the base state and
removing hover-enter effects—specifically, keep a base transition (e.g.,
transition-[property] and duration) and replace hover:shadow-lg with
hover:transition-none (and remove or neutralize any shadow-on-hover) so the
visual transition occurs on hover-exit rather than on hover-enter; update the
cardClass strings for done, action, and blocked accordingly.
- Around line 142-161: STATUS_META currently defines identical cardClass strings
for all LaunchTaskStatus keys (done, action, blocked); either deduplicate by
extracting a shared constant and reusing it in STATUS_META or assign distinct
cardClass values per status if visual differentiation is intended. Update the
STATUS_META declaration (and the LaunchTaskStatus usages) to reference a single
constant like DEFAULT_CARD_CLASS for the common string, or replace the three
identical cardClass values with unique style strings for done/action/blocked so
the per-status lookup becomes meaningful.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/vercel/page-client.tsx:
- Around line 492-496: The DesignButton/StyledLink combination currently uses
"transition-all duration-150" which triggers hover-enter transitions; replace
that with a hover-exit pattern such as "transition-colors duration-150
hover:transition-none" (keeping other classes like "font-medium border
border-border shadow-sm active:scale-95 dark:..." intact) for the instance
inside page-client.tsx and the matching button(s) later in the file to follow
the dashboard guideline.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx (3)

539-548: Use performance.now() instead of Date.now() for elapsed-time measurement.

The confetti animation interval computes remaining time via Date.now(). Per coding guidelines, performance.now() should be used for measuring elapsed (real) time.

Proposed fix
-      const animationEnd = Date.now() + duration;
+      const animationEnd = performance.now() + duration;
       ...
-        const timeLeft = animationEnd - Date.now();
+        const timeLeft = animationEnd - performance.now();

As per coding guidelines: "Use performance.now() instead of Date.now() for measuring elapsed (real) time."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx
around lines 539 - 548, The confetti timer uses Date.now() for elapsed-time
calculations; update the logic to use performance.now() instead by setting
animationEnd based on performance.now() + duration (replace the current
animationEnd = Date.now() + duration) and compute timeLeft as animationEnd -
performance.now() inside the setInterval callback (keep the rest of the confetti
logic including defaults, randomInRange, and interval unchanged). Ensure all
references to Date.now() in this block are replaced so elapsed-time measurement
consistently uses performance.now().

149-158: Consider hover-exit transition pattern per dashboard guidelines.

Classes like transition-all duration-300 hover:shadow-lg are hover-enter transitions. The dashboard guideline recommends the inverse: apply transitions on exit, not entry (e.g., transition-all hover:transition-none), so the visual effect feels snappier.

As per coding guidelines: "Use hover-exit transitions instead of hover-enter transitions. For example, transition-colors hover:transition-none."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx
around lines 149 - 158, The cardClass values for the checklist states (keys
done, action, blocked) use hover-enter classes like "transition-all duration-300
hover:shadow-lg"; update each cardClass in page-client.tsx to follow the
dashboard hover-exit pattern by moving the transition to the base state and
removing hover-enter effects—specifically, keep a base transition (e.g.,
transition-[property] and duration) and replace hover:shadow-lg with
hover:transition-none (and remove or neutralize any shadow-on-hover) so the
visual transition occurs on hover-exit rather than on hover-enter; update the
cardClass strings for done, action, and blocked accordingly.

142-161: All three STATUS_META entries share the same cardClass string.

The done, action, and blocked statuses have identical cardClass values, which makes the per-status lookup pointless. If differentiation is planned, consider adding distinct styles; otherwise, extract a single constant to avoid the repetition.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/launch-checklist/page-client.tsx
around lines 142 - 161, STATUS_META currently defines identical cardClass
strings for all LaunchTaskStatus keys (done, action, blocked); either
deduplicate by extracting a shared constant and reusing it in STATUS_META or
assign distinct cardClass values per status if visual differentiation is
intended. Update the STATUS_META declaration (and the LaunchTaskStatus usages)
to reference a single constant like DEFAULT_CARD_CLASS for the common string, or
replace the three identical cardClass values with unique style strings for
done/action/blocked so the per-status lookup becomes meaningful.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/vercel/page-client.tsx (1)

492-496: Prefer hover-exit transitions per dashboard guidelines.

The buttons use transition-all duration-150 which applies transitions on hover-enter. The coding guideline states: "Use hover-exit transitions instead of hover-enter transitions. For example, transition-colors hover:transition-none."

Proposed fix
- className="font-medium border border-border shadow-sm transition-all duration-150 hover:bg-accent active:scale-95 ..."
+ className="font-medium border border-border shadow-sm transition-all duration-150 hover:transition-none hover:bg-accent active:scale-95 ..."

Also applies to: 501-508

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/vercel/page-client.tsx
around lines 492 - 496, The DesignButton/StyledLink combination currently uses
"transition-all duration-150" which triggers hover-enter transitions; replace
that with a hover-exit pattern such as "transition-colors duration-150
hover:transition-none" (keeping other classes like "font-medium border
border-border shadow-sm active:scale-95 dark:..." intact) for the instance
inside page-client.tsx and the matching button(s) later in the file to follow
the dashboard guideline.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/data-vault/stores/[storeId]/page-client.tsx (1)

36-54: store is read before the existence guard — consider reordering or adding a defensive throw.

store is assigned on line 36 but may be undefined if storeId isn't present. The early return on line 41 prevents further usage at runtime, but TypeScript may not narrow the type, leaving store typed as potentially undefined when used later (lines 57, 65, 129). A small reorder makes the intent explicit:

♻️ Suggested reorder
  const config = project.useConfig();
- const store = config.dataVault.stores[storeId];
- const modifiedKeys = useMemo(() => new Set([
-   ...(localDisplayName !== undefined ? ["display-name"] : []),
- ]), [localDisplayName]);

  if (!(storeId in config.dataVault.stores)) {
    return (
      ...
    );
  }

+ const store = config.dataVault.stores[storeId] ?? throwErr(`Store ${storeId} not found despite passing existence check`);
+ const modifiedKeys = useMemo(() => new Set([
+   ...(localDisplayName !== undefined ? ["display-name"] : []),
+ ]), [localDisplayName]);

Note: this requires that no React hooks are called between the early return and the rest of the component (hooks must not be called conditionally). Since useMemo is currently before the guard, moving it after the guard would violate the rules of hooks if the guard triggers a different return path. This needs careful handling — you may instead keep the current structure and just add an explicit narrowing assertion after the guard.

As per coding guidelines, 'Code defensively. Prefer ?? throwErr(...) over non-null assertions, with good error messages explicitly stating the assumption that must've been violated'.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/data-vault/stores/[storeId]/page-client.tsx
around lines 36 - 54, The code reads `store = config.dataVault.stores[storeId]`
before checking existence and declares `modifiedKeys` with `useMemo`, which can
leave `store` typed as possibly undefined later; fix by either moving the
existence guard (the `if (!(storeId in config.dataVault.stores))` block) above
the `store` and `useMemo` declarations so hooks remain in the same order across
returns, or keep the current order and add an explicit defensive narrowing
immediately after the guard (e.g., throw via a utility if `store` is undefined)
so usages later (references to `store`, `modifiedKeys`, `useMemo`) see a
non-undefined `store`; ensure you do not introduce conditional hooks when
reordering.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx (1)

186-218: Consider hoisting designCardGradients to module scope.

This static mapping doesn't depend on props or state, yet it's recreated on every render of ChartCard. Moving it outside the component is a small clarity and allocation win.

♻️ Proposed change
+const designCardGradients: Record<GradientColor, "blue" | "cyan" | "purple" | "green" | "orange" | "default"> = {
+  blue: "blue",
+  purple: "purple",
+  green: "green",
+  orange: "orange",
+  slate: "default",
+  cyan: "cyan",
+};
+
 export function ChartCard({
   children,
   className,
   gradientColor = "blue"
 }: {
   children: React.ReactNode,
   className?: string,
   gradientColor?: GradientColor,
 }) {
-  const designCardGradients: Record<GradientColor, "blue" | "cyan" | "purple" | "green" | "orange" | "default"> = {
-    blue: "blue",
-    purple: "purple",
-    green: "green",
-    orange: "orange",
-    slate: "default",
-    cyan: "cyan",
-  };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/(overview)/line-chart.tsx
around lines 186 - 218, Hoist the static mapping const designCardGradients out
of the component and into module scope so it isn't recreated on every render:
move the existing designCardGradients declaration to the top-level of the file
(above the component) and leave all references
(designCardGradients[gradientColor]) inside the component unchanged; ensure the
identifier name remains the same and adjust any imports/exports only if you
intend to reuse it across files.

- Removed default values from the project details dialog in the project settings page.
- Added a globe icon to the endpoints design card in the webhooks page for improved UI clarity.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

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]/project-settings/page-client.tsx (1)

88-101: 🛠️ Refactor suggestion | 🟠 Major

URL constructed via plain string interpolation — use urlString or encodeURIComponent.

project.id is interpolated directly into the URL without encoding. Per coding guidelines, URLs should use urlString\`orencodeURIComponent()` to prevent issues with special characters.

Proposed fix
   const jwksUrl = useMemo(
-    () => `${baseApiUrl}/api/v1/projects/${project.id}/.well-known/jwks.json`,
+    () => `${baseApiUrl}/api/v1/projects/${encodeURIComponent(project.id)}/.well-known/jwks.json`,
     [baseApiUrl, project.id]
   );

As per coding guidelines: "Use urlString`` or encodeURIComponent() instead of normal string interpolation for URLs".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
around lines 88 - 101, The URL construction currently interpolates project.id
directly in jwksUrl (and then used by restrictedJwksUrl and allJwksUrl); update
these to encode the project id or use the urlString template helper: build
jwksUrl using either urlString`...${encodeURIComponent(project.id)}...` or
urlString`...${project.id}...` (where urlString handles encoding), then derive
restrictedJwksUrl and allJwksUrl from that encoded jwksUrl to ensure special
characters in project.id are safely encoded.
🤖 Fix all issues with AI agents
Verify each finding against the current code and only fix it if needed.


In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx:
- Around line 270-278: SmartFormDialog is missing the defaultValues prop so the
form opens blank; pass the current project values as defaultValues (e.g. map
project.displayName and project.description to the schema field names) when
rendering SmartFormDialog, ensuring you use the same field keys used in
projectInformationSchema and update the defaults when the project data changes
(useMemo or derive from project before passing into SmartFormDialog). Keep the
existing props: open={isProjectDetailsDialogOpen},
onOpenChange={setIsProjectDetailsDialogOpen},
formSchema={projectInformationSchema}, and onSubmit={handleProjectDetailsSubmit}
while adding defaultValues to pre-populate the form.
- Line 263: The rendering uses a boolean fallback that treats empty strings as
missing; change the fallback for project.description to an explicit nullish
check (e.g., use the null/undefined check on project.description or the nullish
coalescing behavior) so that an empty string remains displayed while only
null/undefined becomes "-"; update the JSX that references project.description
in page-client.tsx (the <p> that shows project.description) to use an explicit
check like project.description == null ? "-" : project.description or the
equivalent nullish-coalescing approach.
- Line 30: Replace the three uses of `any` with proper types or a short
justification comment: for the `TeamMemberItem` prop `member` use the SDK type
(e.g., `TeamMember`) instead of `any`; for the `team` variable use the SDK's
`Team` type; for the form `values` replace `any` with `yup.InferType<typeof
projectInformationSchema>` (or the explicit inferred type) — if a precise type
truly cannot be expressed, add a one-line comment next to each usage explaining
why `any` is required and reference the limitation (e.g., runtime shape,
third‑party SDK looseness) so the rationale is recorded.
- Around line 303-321: The dark-mode LogoUpload handlers are inline async
lambdas while the light-mode handlers use useCallback (handleLogoChange,
handleFullLogoChange); make the dark-mode handlers consistent by creating
memoized callbacks (e.g., handleLogoDarkChange and handleFullLogoDarkChange)
using useCallback that call project.update({ logoDarkModeUrl }) and
project.update({ logoFullDarkModeUrl }) respectively, and pass those memoized
functions to the LogoUpload onValueChange props so all four handlers are
implemented consistently.
- Around line 88-101: The URL construction currently interpolates project.id
directly in jwksUrl (and then used by restrictedJwksUrl and allJwksUrl); update
these to encode the project id or use the urlString template helper: build
jwksUrl using either urlString`...${encodeURIComponent(project.id)}...` or
urlString`...${project.id}...` (where urlString handles encoding), then derive
restrictedJwksUrl and allJwksUrl from that encoded jwksUrl to ensure special
characters in project.id are safely encoded.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx:
- Around line 415-425: The status column currently hardcodes "Active"; update
the Endpoint type (the type alias used for rows, extending Svix's EndpointOut)
to include a disabled: boolean field, then change the column with id "status"
(the cell renderer) to read the row's disabled value (e.g.,
row.original.disabled) and render DesignBadge with label "Disabled" when true
and "Active" when false, using an appropriate color (e.g., red for disabled,
green for active) and keeping size "sm".
- Around line 396-439: The columns array (ColumnDef<Endpoint> assigned to
columns) is recreated on every render and closes over props.updateFn and
props.onTestRequested causing unnecessary re-renders of DesignDataTable; fix by
moving the columns definition above the early return and wrapping it in useMemo,
e.g. useMemo(() => [ ...columns... ], [props.updateFn, props.onTestRequested])
so ActionMenu receives stable updateFn/onTestEndpoint references and the table
won’t re-render unnecessarily.
- Around line 344-350: The conditional rendering uses a truthy check for the
variable errorMessage; change it to an explicit null/undefined check (e.g.,
errorMessage != null) so the condition only triggers when errorMessage is not
null/undefined; update the JSX around the status, errorMessage, and DesignAlert
usage in the page-client component to use the explicit check.
🧹 Nitpick comments (5)
🤖 Fix all nitpicks with AI agents
Verify each finding against the current code and only fix it if needed.


In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx:
- Line 263: The rendering uses a boolean fallback that treats empty strings as
missing; change the fallback for project.description to an explicit nullish
check (e.g., use the null/undefined check on project.description or the nullish
coalescing behavior) so that an empty string remains displayed while only
null/undefined becomes "-"; update the JSX that references project.description
in page-client.tsx (the <p> that shows project.description) to use an explicit
check like project.description == null ? "-" : project.description or the
equivalent nullish-coalescing approach.
- Line 30: Replace the three uses of `any` with proper types or a short
justification comment: for the `TeamMemberItem` prop `member` use the SDK type
(e.g., `TeamMember`) instead of `any`; for the `team` variable use the SDK's
`Team` type; for the form `values` replace `any` with `yup.InferType<typeof
projectInformationSchema>` (or the explicit inferred type) — if a precise type
truly cannot be expressed, add a one-line comment next to each usage explaining
why `any` is required and reference the limitation (e.g., runtime shape,
third‑party SDK looseness) so the rationale is recorded.
- Around line 303-321: The dark-mode LogoUpload handlers are inline async
lambdas while the light-mode handlers use useCallback (handleLogoChange,
handleFullLogoChange); make the dark-mode handlers consistent by creating
memoized callbacks (e.g., handleLogoDarkChange and handleFullLogoDarkChange)
using useCallback that call project.update({ logoDarkModeUrl }) and
project.update({ logoFullDarkModeUrl }) respectively, and pass those memoized
functions to the LogoUpload onValueChange props so all four handlers are
implemented consistently.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx:
- Around line 396-439: The columns array (ColumnDef<Endpoint> assigned to
columns) is recreated on every render and closes over props.updateFn and
props.onTestRequested causing unnecessary re-renders of DesignDataTable; fix by
moving the columns definition above the early return and wrapping it in useMemo,
e.g. useMemo(() => [ ...columns... ], [props.updateFn, props.onTestRequested])
so ActionMenu receives stable updateFn/onTestEndpoint references and the table
won’t re-render unnecessarily.
- Around line 344-350: The conditional rendering uses a truthy check for the
variable errorMessage; change it to an explicit null/undefined check (e.g.,
errorMessage != null) so the condition only triggers when errorMessage is not
null/undefined; update the JSX around the status, errorMessage, and DesignAlert
usage in the page-client component to use the explicit check.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx (3)

263-263: Boolean check on project.description hides empty strings.

project.description || "-" will display "-" for both null/undefined and an empty string "". If an empty description is a valid state distinct from "no description", prefer an explicit nullish check.

-            <p className="text-sm text-foreground/80">{project.description || "-"}</p>
+            <p className="text-sm text-foreground/80">{project.description ?? "-"}</p>

As per coding guidelines: "Prefer explicit null/undefinedness checks over boolean checks, eg. foo == null instead of !foo".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
at line 263, The rendering uses a boolean fallback that treats empty strings as
missing; change the fallback for project.description to an explicit nullish
check (e.g., use the null/undefined check on project.description or the nullish
coalescing behavior) so that an empty string remains displayed while only
null/undefined becomes "-"; update the JSX that references project.description
in page-client.tsx (the <p> that shows project.description) to use an explicit
check like project.description == null ? "-" : project.description or the
equivalent nullish-coalescing approach.

30-30: any types used without justification comments.

Three places use any without explaining why the type system can't express the correct type:

  • Line 30: member: any
  • Line 162: team: any
  • Line 167: values: any

Per coding guidelines, when any is necessary, a comment should explain why. Ideally, type these properly (e.g., use the SDK's TeamMember/Team types and yup.InferType<typeof projectInformationSchema> for values).

As per coding guidelines: "Try to avoid the any type. When necessary, leave a comment explaining why you're using it and why the type system fails there".

Also applies to: 162-164, 167-169

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
at line 30, Replace the three uses of `any` with proper types or a short
justification comment: for the `TeamMemberItem` prop `member` use the SDK type
(e.g., `TeamMember`) instead of `any`; for the `team` variable use the SDK's
`Team` type; for the form `values` replace `any` with `yup.InferType<typeof
projectInformationSchema>` (or the explicit inferred type) — if a precise type
truly cannot be expressed, add a one-line comment next to each usage explaining
why `any` is required and reference the limitation (e.g., runtime shape,
third‑party SDK looseness) so the rationale is recorded.

303-321: Dark-mode logo callbacks are inline while light-mode equivalents are memoized.

handleLogoChange and handleFullLogoChange are wrapped in useCallback (lines 148–154), but the dark-mode variants at lines 306–308 and 316–318 use inline async lambdas. This is an inconsistency — either memoize all four or none.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
around lines 303 - 321, The dark-mode LogoUpload handlers are inline async
lambdas while the light-mode handlers use useCallback (handleLogoChange,
handleFullLogoChange); make the dark-mode handlers consistent by creating
memoized callbacks (e.g., handleLogoDarkChange and handleFullLogoDarkChange)
using useCallback that call project.update({ logoDarkModeUrl }) and
project.update({ logoFullDarkModeUrl }) respectively, and pass those memoized
functions to the LogoUpload onValueChange props so all four handlers are
implemented consistently.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx (2)

396-439: Column definitions are recreated on every render.

columns is defined inline in the render path and captures props.updateFn / props.onTestRequested via closures. Consider wrapping it in useMemo to avoid unnecessary re-renders of DesignDataTable.

Wrap in useMemo

Move the column definition before the early return and memoize it:

 function Endpoints(props: { updateFn: () => void, onTestRequested: (endpoint: Endpoint) => void }) {
   const endpoints = getSvixResult(useEndpoints({ limit: 100 }));
+
+  const columns: ColumnDef<Endpoint>[] = useMemo(() => [
+    // ...column defs unchanged...
+  ], [props.updateFn, props.onTestRequested]);

   if (!endpoints.loaded) {
     return endpoints.rendered;
-  } else {
-    const columns: ColumnDef<Endpoint>[] = [
-      // ...
-    ];
-    return ( ... );
-  }
+  }
+
+  return ( ... );
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx
around lines 396 - 439, The columns array (ColumnDef<Endpoint> assigned to
columns) is recreated on every render and closes over props.updateFn and
props.onTestRequested causing unnecessary re-renders of DesignDataTable; fix by
moving the columns definition above the early return and wrapping it in useMemo,
e.g. useMemo(() => [ ...columns... ], [props.updateFn, props.onTestRequested])
so ActionMenu receives stable updateFn/onTestEndpoint references and the table
won’t re-render unnecessarily.

344-350: Prefer explicit null check for errorMessage.

Line 344 uses a truthy check on errorMessage (a string | null). Per project guidelines, prefer explicit null/undefinedness checks.

-        {status === 'error' && errorMessage && (
+        {status === 'error' && errorMessage != null && (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx
around lines 344 - 350, The conditional rendering uses a truthy check for the
variable errorMessage; change it to an explicit null/undefined check (e.g.,
errorMessage != null) so the condition only triggers when errorMessage is not
null/undefined; update the JSX around the status, errorMessage, and DesignAlert
usage in the page-client component to use the explicit check.

- Introduced default values for project details in the project settings dialog.
- Added a 'disabled' state to webhooks endpoints, improving status visibility in the UI.
- Updated the SmartForm component to support default values and handle form resets more effectively.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

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

289-300: ⚠️ Potential issue | 🟠 Major

sendTestEvent can throw unhandled if sendTestWebhook rejects.

If stackAdminApp.sendTestWebhook throws a network error or unexpected exception, the status will remain stuck at 'sending' and the user will see no feedback. Consider wrapping in try/catch to set the error state on unexpected failures.

Suggested fix
   const sendTestEvent = async () => {
     setStatus('sending');
     setErrorMessage(null);
 
+    try {
       const result = await stackAdminApp.sendTestWebhook({ endpointId: props.endpoint.id });
       if (result.status === 'ok') {
         setStatus('success');
         return;
       }
       setStatus('error');
       setErrorMessage(result.error.errorMessage);
+    } catch (e) {
+      setStatus('error');
+      setErrorMessage(e instanceof Error ? e.message : 'An unexpected error occurred');
+    }
   };

Note: the outer ActionDialog onClick may catch and display the error, but the local state (status) would still be stuck at 'sending', leaving the Cancel button hidden (Line 319) and the UI in a broken state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx
around lines 289 - 300, sendTestEvent can throw if stackAdminApp.sendTestWebhook
rejects; wrap the await call in a try/catch so any thrown error sets local UI
state (call setStatus('error') and setErrorMessage with the caught error message
or a fallback) and avoid leaving the status stuck at 'sending'. In the catch
block reference stackAdminApp.sendTestWebhook failure, call setStatus('error'),
setErrorMessage(error?.message ?? 'Unexpected error'), and return (or ensure
finally behavior) so the Cancel button and UI are restored.

381-381: ⚠️ Potential issue | 🟡 Minor

Import and use urlString template tag for the route construction.

Per coding guidelines, prefer urlString or encodeURIComponent() over plain template literals for URL interpolation. The urlString utility is available from @stackframe/stack-shared/dist/utils/urls and is already used throughout the dashboard for similar navigation patterns.

Suggested change
+import { urlString } from "@stackframe/stack-shared/dist/utils/urls";
+
-          { item: "View Details", onClick: () => router.push(`/projects/${project.id}/webhooks/${props.endpoint.id}`) },
+          { item: "View Details", onClick: () => router.push(urlString`/projects/${project.id}/webhooks/${props.endpoint.id}`) },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx
at line 381, The route is built with a plain template literal; import the
urlString template tag from `@stackframe/stack-shared/dist/utils/urls` and use it
when calling router.push so path segments are properly encoded. Replace
router.push(`/projects/${project.id}/webhooks/${props.endpoint.id}`) with
router.push(urlString`/projects/${project.id}/webhooks/${props.endpoint.id}`)
and add the corresponding import for urlString at the top of the module;
reference symbols: router.push, project.id, props.endpoint.id, and urlString.
🧹 Nitpick comments (8)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx (2)

463-468: Inline empty-array literals ([]) create new references each render.

defaultColumnFilters={[]} and defaultSorting={[]} produce fresh arrays every render. If DesignDataTable uses these in dependency arrays or passes them to useReactTable options, this can trigger unnecessary re-computations. Hoist them to module-level constants.

Suggested fix

At the module level (e.g. near the type definition):

const EMPTY_COLUMN_FILTERS: never[] = [];
const EMPTY_SORTING: never[] = [];

Then reference them:

-        defaultColumnFilters={[]}
-        defaultSorting={[]}
+        defaultColumnFilters={EMPTY_COLUMN_FILTERS}
+        defaultSorting={EMPTY_SORTING}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx
around lines 463 - 468, The inline empty-array literals passed to
DesignDataTable as defaultColumnFilters and defaultSorting create new references
each render and can cause unnecessary recomputations; fix by hoisting them to
module-level constants (e.g., EMPTY_COLUMN_FILTERS and EMPTY_SORTING) and then
passing those constants to the DesignDataTable props defaultColumnFilters and
defaultSorting instead of [] so the same reference is reused across renders.

398-447: Column definitions and row data are re-created on every render — consider memoizing.

columns and endpointRows are both defined inline in the else branch without useMemo, so they produce new references on each render. This can cause DesignDataTable (and its internal useReactTable) to reset or re-render unnecessarily.

Additionally, Line 445 (description: endpoint.description) is redundant after the ...endpoint spread.

Suggested refactor

Move the column/row definitions above the conditional, wrapping them with useMemo:

 function Endpoints(props: { updateFn: () => void, onTestRequested: (endpoint: Endpoint) => void }) {
   const endpoints = getSvixResult(useEndpoints({ limit: 100 }));
 
+  const columns: ColumnDef<Endpoint>[] = useMemo(() => [
+    {
+      accessorKey: "url",
+      header: "Endpoint URL",
+      cell: ({ row }) => (
+        <span className="text-sm font-medium text-foreground">{row.original.url}</span>
+      ),
+    },
+    {
+      accessorKey: "description",
+      header: "Description",
+      cell: ({ row }) => (
+        row.original.description ? (
+          <span className="text-sm text-foreground/80">{row.original.description}</span>
+        ) : (
+          <span className="text-sm text-muted-foreground">-</span>
+        )
+      ),
+    },
+    {
+      id: "status",
+      header: "Status",
+      cell: ({ row }) => (
+        <DesignBadge
+          label={row.original.disabled ? "Disabled" : "Active"}
+          color={row.original.disabled ? "red" : "green"}
+          size="sm"
+        />
+      ),
+    },
+    {
+      id: "actions",
+      header: "",
+      cell: ({ row }) => (
+        <div className="flex justify-end">
+          <ActionMenu
+            endpoint={row.original}
+            updateFn={props.updateFn}
+            onTestEndpoint={props.onTestRequested}
+          />
+        </div>
+      ),
+    },
+  ], [props.updateFn, props.onTestRequested]);
+
+  const endpointRows: Endpoint[] = useMemo(() =>
+    endpoints.loaded
+      ? endpoints.data.map((endpoint) => ({
+          ...endpoint,
+          disabled: endpoint.disabled ?? false,
+        }))
+      : [],
+    [endpoints.loaded, endpoints.data]
+  );
+
   if (!endpoints.loaded) {
     return endpoints.rendered;
-  } else {
-    const columns: ColumnDef<Endpoint>[] = [ ... ];
-    const endpointRows: Endpoint[] = endpoints.data.map((endpoint) => ({
-      ...endpoint,
-      description: endpoint.description,
-      disabled: endpoint.disabled ?? false,
-    }));
-    return ( ... );
+  }
+
+  return (
+    <DesignCard ...>
+      <DesignDataTable
+        data={endpointRows}
+        columns={columns}
+        defaultColumnFilters={[]}
+        defaultSorting={[]}
+      />
+    </DesignCard>
+  );
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx
around lines 398 - 447, The columns and endpointRows are recreated on every
render which causes unnecessary re-renders of DesignDataTable/useReactTable;
wrap the columns array and the endpointRows mapping in React.useMemo to memoize
them (e.g., const columns = useMemo(() => [...], [props.updateFn,
props.onTestRequested]) and const endpointRows = useMemo(() =>
endpoints.data.map(e => ({ ...e, disabled: e.disabled ?? false })),
[endpoints.data]) ), remove the redundant description: endpoint.description
after the spread, and ensure dependencies include any props or values referenced
by the memoized callbacks (like props.updateFn, props.onTestRequested and
endpoints.data).
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx (4)

171-174: || used instead of explicit nullish check for description.

project.description || undefined treats empty strings as falsy and converts them to undefined. If this is intentional (empty string = no description), a comment would help clarify the intent. Otherwise, project.description ?? undefined is the guideline-preferred pattern.

As per coding guidelines, "Prefer explicit null/undefinedness checks over boolean checks, eg. foo == null instead of !foo."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
around lines 171 - 174, The use of a boolean-falsy fallback incorrectly converts
empty strings to undefined in projectDetailsDefaultValues; update the expression
to use a nullish check so only null/undefined become undefined (replace
project.description || undefined with a nullish pattern such as
project.description ?? undefined), or if empty-string-to-undefined is
intentional, add a clarifying comment next to projectDetailsDefaultValues
explaining that empty strings should be treated as missing descriptions.

309-327: Dark-mode logo callbacks are inline while their light-mode counterparts are memoized.

handleLogoChange and handleFullLogoChange (lines 148-154) are extracted as useCallback memos, but the dark-mode variants on lines 312-314 and 322-324 are defined inline. This inconsistency means the dark-mode LogoUpload components receive a new function reference every render. Consider extracting them for consistency.

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

Then use onValueChange={handleLogoDarkModeChange} and onValueChange={handleLogoFullDarkModeChange} in the JSX.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
around lines 309 - 327, The dark-mode LogoUpload callbacks are defined inline
causing new function refs each render; extract them as memoized callbacks like
the existing handleLogoChange and handleFullLogoChange by creating useCallback
hooks (e.g., handleLogoDarkModeChange and handleLogoFullDarkModeChange) that
call project.update({ logoDarkModeUrl }) and project.update({
logoFullDarkModeUrl }) respectively, then pass those handlers as onValueChange
to the dark-mode LogoUpload components so the props remain stable across
renders.

88-101: URL construction uses raw string interpolation — guideline requires encodeURIComponent or urlString.

project.id is interpolated directly into the URL template. While project IDs are likely safe identifiers, the coding guideline explicitly requires using urlString tagged templates or encodeURIComponent() for URL construction.

♻️ Suggested fix
   const jwksUrl = useMemo(
-    () => `${baseApiUrl}/api/v1/projects/${project.id}/.well-known/jwks.json`,
+    () => `${baseApiUrl}/api/v1/projects/${encodeURIComponent(project.id)}/.well-known/jwks.json`,
     [baseApiUrl, project.id]
   );

Or use urlString if it's available in the project.

As per coding guidelines, "Use urlString`` or encodeURIComponent() instead of normal string interpolation for URLs".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
around lines 88 - 101, The three URL builders jwksUrl, restrictedJwksUrl and
allJwksUrl currently interpolate project.id directly; update them to encode the
project id (or use the project's urlString tagged template if available) so
unsafe characters are escaped: replace `${project.id}` with an encoded value via
encodeURIComponent(project.id) or use urlString`...${project.id}...` and keep
the same dependency arrays ([baseApiUrl, project.id] and [jwksUrl]) so the
useMemo hooks still update correctly.

30-30: Multiple any types without explanatory comments.

  • member: any (line 30)
  • team: any (line 162)
  • values: any (line 167)

Per guidelines, the any type should be avoided, and when necessary a comment should explain why. For values, yup.InferType<typeof projectInformationSchema> would be more precise and provide type safety on the project.update(values) call. For member and team, consider using the actual SDK types.

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

As per coding guidelines, "Try to avoid the any type. When necessary, leave a comment explaining why you're using it and why the type system fails there."

Also applies to: 162-163, 167-168

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx
at line 30, The code uses several any types (member in TeamMemberItem, team, and
values) which reduces type safety; replace member and team with the correct SDK
types from your project/team SDK (e.g., TeamMember or User types) by updating
the TeamMemberItem prop signature and the team variable's type, and change
values to a precise type using yup.InferType<typeof projectInformationSchema> so
the call to project.update(values) is type-safe; if an SDK type is missing, add
a short comment explaining why any is used and reference the intended type.
apps/dashboard/src/components/smart-form.tsx (2)

39-52: Dependency array includes props — callback recreated every render.

props as a whole is a new object reference every render, so including it in the useCallback deps makes the memoization effectively useless. Consider destructuring the specific props used (props.onChangeIsSubmitting, props.onSubmit) and listing them individually.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/smart-form.tsx` around lines 39 - 52,
handleSubmit is being recreated each render because the entire props object is
in the useCallback dependency array; instead, destructure the specific callbacks
you use (onChangeIsSubmitting and onSubmit) from props and reference those
individual variables in the dependency list along with form and
resolvedDefaultValues. Update the component to pull const {
onChangeIsSubmitting, onSubmit } = props (or function params) and replace
props.onChangeIsSubmitting/props.onSubmit calls inside handleSubmit, then change
the useCallback deps to [onChangeIsSubmitting, onSubmit, form,
resolvedDefaultValues] to restore proper memoization.

54-61: resolvedDefaultValues is a new object reference every render, causing the effect to fire unnecessarily.

resolvedDefaultValues (line 32) is recomputed as a new object each render, so listing it in the dependency array means this effect runs every render. The guard condition (currentOpen && !prevOpen.current) prevents spurious resets, so this is not a correctness bug, but it's wasteful. Memoizing or stabilizing the defaults would make the intent clearer.

♻️ Suggested stabilization

Wrap resolvedDefaultValues in useMemo so the effect (and handleSubmit callback) only re-run when the values actually change:

-  const resolvedDefaultValues = props.defaultValues ?? props.formSchema.getDefault();
+  const resolvedDefaultValues = useMemo(
+    () => props.defaultValues ?? props.formSchema.getDefault(),
+    [props.defaultValues, props.formSchema]
+  );

Note: this still won't deep-stabilize if props.defaultValues is a new object with the same content each render (the caller should memoize). But it avoids the unconditional recompute of getDefault() and signals intent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/src/components/smart-form.tsx` around lines 54 - 61, The
effect depends on resolvedDefaultValues which is recomputed each render;
stabilize it by wrapping the resolvedDefaultValues computation (the variable
assigned at line 32) in useMemo so it only changes when its true inputs change
(e.g., props.defaultValues or getDefault inputs), and then keep that memoized
resolvedDefaultValues in the useEffect dependency array that calls form.reset;
also ensure the handleSubmit callback (which uses resolvedDefaultValues) uses
the memoized value so both the effect and callback only re-run when the actual
defaults change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx:
- Around line 346-351: The conditional rendering for the error alert currently
checks errorMessage truthiness which hides empty-string errors; update the JSX
condition to explicitly check for null/undefined (e.g., errorMessage != null or
errorMessage !== null && errorMessage !== undefined) so that when status ===
'error' and errorMessage is an empty string the DesignAlert (title "Unable to
send event", component DesignAlert) still renders using
result.error.errorMessage; adjust the expression near the existing status ===
'error' && errorMessage to status === 'error' && errorMessage != null.

---

Outside diff comments:
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx:
- Around line 289-300: sendTestEvent can throw if stackAdminApp.sendTestWebhook
rejects; wrap the await call in a try/catch so any thrown error sets local UI
state (call setStatus('error') and setErrorMessage with the caught error message
or a fallback) and avoid leaving the status stuck at 'sending'. In the catch
block reference stackAdminApp.sendTestWebhook failure, call setStatus('error'),
setErrorMessage(error?.message ?? 'Unexpected error'), and return (or ensure
finally behavior) so the Cancel button and UI are restored.
- Line 381: The route is built with a plain template literal; import the
urlString template tag from `@stackframe/stack-shared/dist/utils/urls` and use it
when calling router.push so path segments are properly encoded. Replace
router.push(`/projects/${project.id}/webhooks/${props.endpoint.id}`) with
router.push(urlString`/projects/${project.id}/webhooks/${props.endpoint.id}`)
and add the corresponding import for urlString at the top of the module;
reference symbols: router.push, project.id, props.endpoint.id, and urlString.

---

Duplicate comments:
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx:
- Around line 275-284: The default values are correctly wired:
projectDetailsDefaultValues (memo defined earlier) is being passed into
SmartFormDialog's defaultValues prop and pre-populates the form; no code change
required—approve the change as-is and ensure SmartFormDialog,
projectDetailsDefaultValues, and handleProjectDetailsSubmit remain unchanged.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx:
- Around line 417-427: Status column was updated to remove the hardcoded
"Active" label; ensure the column definition with id "status" uses
row.original.disabled to conditionally set DesignBadge's label ("Disabled" vs
"Active") and color ("red" vs "green"). Locate the column object (id: "status")
and confirm the cell renderer returns <DesignBadge label={row.original.disabled
? "Disabled" : "Active"} color={row.original.disabled ? "red" : "green"}
size="sm" /> so the UI reflects the disabled field correctly.

---

Nitpick comments:
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/project-settings/page-client.tsx:
- Around line 171-174: The use of a boolean-falsy fallback incorrectly converts
empty strings to undefined in projectDetailsDefaultValues; update the expression
to use a nullish check so only null/undefined become undefined (replace
project.description || undefined with a nullish pattern such as
project.description ?? undefined), or if empty-string-to-undefined is
intentional, add a clarifying comment next to projectDetailsDefaultValues
explaining that empty strings should be treated as missing descriptions.
- Around line 309-327: The dark-mode LogoUpload callbacks are defined inline
causing new function refs each render; extract them as memoized callbacks like
the existing handleLogoChange and handleFullLogoChange by creating useCallback
hooks (e.g., handleLogoDarkModeChange and handleLogoFullDarkModeChange) that
call project.update({ logoDarkModeUrl }) and project.update({
logoFullDarkModeUrl }) respectively, then pass those handlers as onValueChange
to the dark-mode LogoUpload components so the props remain stable across
renders.
- Around line 88-101: The three URL builders jwksUrl, restrictedJwksUrl and
allJwksUrl currently interpolate project.id directly; update them to encode the
project id (or use the project's urlString tagged template if available) so
unsafe characters are escaped: replace `${project.id}` with an encoded value via
encodeURIComponent(project.id) or use urlString`...${project.id}...` and keep
the same dependency arrays ([baseApiUrl, project.id] and [jwksUrl]) so the
useMemo hooks still update correctly.
- Line 30: The code uses several any types (member in TeamMemberItem, team, and
values) which reduces type safety; replace member and team with the correct SDK
types from your project/team SDK (e.g., TeamMember or User types) by updating
the TeamMemberItem prop signature and the team variable's type, and change
values to a precise type using yup.InferType<typeof projectInformationSchema> so
the call to project.update(values) is type-safe; if an SDK type is missing, add
a short comment explaining why any is used and reference the intended type.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx:
- Around line 463-468: The inline empty-array literals passed to DesignDataTable
as defaultColumnFilters and defaultSorting create new references each render and
can cause unnecessary recomputations; fix by hoisting them to module-level
constants (e.g., EMPTY_COLUMN_FILTERS and EMPTY_SORTING) and then passing those
constants to the DesignDataTable props defaultColumnFilters and defaultSorting
instead of [] so the same reference is reused across renders.
- Around line 398-447: The columns and endpointRows are recreated on every
render which causes unnecessary re-renders of DesignDataTable/useReactTable;
wrap the columns array and the endpointRows mapping in React.useMemo to memoize
them (e.g., const columns = useMemo(() => [...], [props.updateFn,
props.onTestRequested]) and const endpointRows = useMemo(() =>
endpoints.data.map(e => ({ ...e, disabled: e.disabled ?? false })),
[endpoints.data]) ), remove the redundant description: endpoint.description
after the spread, and ensure dependencies include any props or values referenced
by the memoized callbacks (like props.updateFn, props.onTestRequested and
endpoints.data).

In `@apps/dashboard/src/components/smart-form.tsx`:
- Around line 39-52: handleSubmit is being recreated each render because the
entire props object is in the useCallback dependency array; instead, destructure
the specific callbacks you use (onChangeIsSubmitting and onSubmit) from props
and reference those individual variables in the dependency list along with form
and resolvedDefaultValues. Update the component to pull const {
onChangeIsSubmitting, onSubmit } = props (or function params) and replace
props.onChangeIsSubmitting/props.onSubmit calls inside handleSubmit, then change
the useCallback deps to [onChangeIsSubmitting, onSubmit, form,
resolvedDefaultValues] to restore proper memoization.
- Around line 54-61: The effect depends on resolvedDefaultValues which is
recomputed each render; stabilize it by wrapping the resolvedDefaultValues
computation (the variable assigned at line 32) in useMemo so it only changes
when its true inputs change (e.g., props.defaultValues or getDefault inputs),
and then keep that memoized resolvedDefaultValues in the useEffect dependency
array that calls form.reset; also ensure the handleSubmit callback (which uses
resolvedDefaultValues) uses the memoized value so both the effect and callback
only re-run when the actual defaults change.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

X Tutup