Skip to content

Auth app redesign#1367

Open
Developing-Gamer wants to merge 11 commits intodevfrom
auth-app-redesign
Open

Auth app redesign#1367
Developing-Gamer wants to merge 11 commits intodevfrom
auth-app-redesign

Conversation

@Developing-Gamer
Copy link
Copy Markdown
Contributor

@Developing-Gamer Developing-Gamer commented Apr 22, 2026

Summary by CodeRabbit

  • New Features

    • Added a new modal dialog component system with configurable sizes, variants, and styling options for building consistent modal interfaces.
  • Documentation

    • Expanded design guide with comprehensive dialog usage patterns and best practices.
  • Improvements

    • Updated dashboard UI components with the new design system for consistency.
    • Enhanced playground with interactive dialog configuration preview and code generation.
    • Refreshed auth methods and sign-up rules interfaces with improved components.

Introduce a canonical dialog surface with structured header/body/footer slots and styling controls so feature pages can reuse a consistent modal foundation.

Made-with: Cursor
Expose the new dialog component and related helper types from the package root so dashboard pages can import the shared modal API consistently.

Made-with: Cursor
Add guidance for when and how to use DesignDialog so modal redesign work follows a single documented pattern across dashboard routes.

Made-with: Cursor
Replace the hand-wired trigger history dialog chrome with the shared DesignDialog wrapper while preserving the existing summary header and trigger list behavior.

Made-with: Cursor
Show confirmation, rich-header, tester, and parity examples so agents and developers can copy the shared modal patterns directly.

Made-with: Cursor
Introduce a dedicated dialog playground entry with shape presets and generated snippets to make modal experimentation and reuse easier.

Made-with: Cursor
@Developing-Gamer Developing-Gamer self-assigned this Apr 22, 2026
Copilot AI review requested due to automatic review settings April 22, 2026 06:01
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 22, 2026

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

Project Deployment Actions Updated (UTC)
stack-auth-hosted-components Ready Ready Preview, Comment Apr 22, 2026 6:04am
stack-backend Ready Ready Preview, Comment Apr 22, 2026 6:04am
stack-dashboard Ready Ready Preview, Comment Apr 22, 2026 6:04am
stack-demo Ready Ready Preview, Comment Apr 22, 2026 6:04am
stack-docs Ready Ready Preview, Comment Apr 22, 2026 6:04am
stack-preview-backend Ready Ready Preview, Comment Apr 22, 2026 6:04am
stack-preview-dashboard Ready Ready Preview, Comment Apr 22, 2026 6:04am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 22, 2026

📝 Walkthrough

Walkthrough

Introduces a new DesignDialog component as the canonical design-system modal surface, including comprehensive design documentation, playground integration, and adoption across auth-methods, auth-method providers, and sign-up-rules pages. Establishes standardized dialog composition with configurable sizing, variants, icons, headers, footers, and content regions.

Changes

Cohort / File(s) Summary
DesignDialog Component & Exports
packages/dashboard-ui-components/src/components/dialog.tsx, packages/dashboard-ui-components/src/index.ts
New client-side DesignDialog component wrapping @stackframe/stack-ui Dialog primitives with typed props for size/variant/trigger/icon/title/description/headerContent/footer and optional class overrides for overlay/header/body/footer regions. Conditional rendering for header (icon/title/description/headerContent) and footer. Exports re-exports of Dialog, DialogTrigger, DialogClose, DialogTitle, DialogDescription.
Design Documentation & Examples
apps/dashboard/DESIGN-GUIDE.md, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/design-language/page-client.tsx
Updated DESIGN-GUIDE with new DesignDialog section covering usage (focus-trapping modals, replacing stack-ui Dialog composition), key props, behavioral rules, and common patterns (confirmation dialogs, rich modals, tester/form dialogs). Added design-language page with multiple ComponentDemo examples showing different dialog configurations and props table.
Playground Dialog Preview
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/playground/page-client.tsx
Extended playground with new "dialog" component selection, introducing dialog state (shape, size, variant, title, description, footer/icon visibility, close toggle), dynamic DesignDialog preview with variant trigger labels/icons/headerContent/body/footer, and TSX code generation for selected dialog props.
Auth Methods Page Refactor
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx
Replaced SettingCard/SettingSwitch/SettingSelect primitives with Design UI components (DesignCard\*, DesignInput, DesignButton, DesignMenu, DesignAlert, DesignBadge). Refactored email-verification toggle with preview-affected-users flow using ActionDialog. Replaced OAuth provider dropdown menus with DesignMenu. Added MethodToggleRow shared component and new "Live preview" card rendering AuthPage with current settings.
Auth Methods Providers Refactor
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx
Added size prop to ProviderIcon. Replaced inlined provider settings modal with composed OAuthProviderSettingsForm. Switched shared-keys UI from SwitchField to DesignPillToggle. Updated TurnOffProviderDialog with provider icon/title and DesignAlert block. Replaced provider settings card from clickable div to DesignCardTint with explicit button. Replaced Badge with DesignBadge.
Sign-up Rules Page Major Refactor
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sign-up-rules/page-client.tsx
Replaced Alert/Button/Input/Select/Dialog primitives with Design components (DesignAlert, DesignButton, DesignInput, DesignSelectorDropdown, DesignMenu, DesignDialog). Refactored rule trigger history UI with new helper components (TriggerStatTile, TriggerHistoryChart, TriggerRow) and Activity chart. Split monolithic RuleEditor into state hook and presentational components. Replaced edit/delete icon buttons with DesignMenu kebab. Added DefaultActionRow, EmptyState, and TestRulesPanel using Design components. Introduced PageBody component for page rendering.
Action Dialog Enhancement
apps/dashboard/src/components/ui/action-dialog.tsx
Extended ActionDialogProps with keepOpenOnOutsideInteraction?: boolean to prevent outside-interaction dismissal and contentClassName?: string to apply additional CSS classes. Updated dismissal handlers (onInteractOutside, onPointerDownOutside, onFocusOutside) to respect the new blockDismissOnOutside flag.
Skills & Configuration
.agents/skills/roids/SKILL.md, skills-lock.json
Added new roids skill documentation file defining metadata (name, description, version) and usage instructions for SKILL.txt contract. Added skills-lock.json manifest with version schema and roids skill registry entry (GitHub source, computedHash pin).

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Trigger
    participant DesignDialog
    participant DialogContent
    participant DialogBody
    participant DialogFooter

    User->>Trigger: Click/Interact
    Trigger->>DesignDialog: Render with open state
    DesignDialog->>DialogContent: Apply size/variant/overlay styles
    DialogContent->>DialogContent: Render header (icon/title/description)
    DialogContent->>DialogBody: Conditionally render children
    DialogContent->>DialogFooter: Conditionally render footer actions
    User->>DialogFooter: Click action (confirm/cancel)
    DialogFooter->>DesignDialog: Trigger close callback
    DesignDialog->>User: Dialog closes
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • N2D4

Poem

🐰 Hops with glee at dialogs so fine,
DesignDialog paints each modal line,
Variants dance, sizes align with grace,
From auth to rules, design finds its place!
A rabbit salutes this modal dream,
Now every dialog's part of the team! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is essentially empty, containing only the CONTRIBUTING.md template reminder with no actual narrative, rationale, change summary, or testing notes. Provide a complete description including: what was changed (DesignDialog component, auth pages redesign, design language updates), why these changes were made, any breaking changes, and testing notes.
Docstring Coverage ⚠️ Warning Docstring coverage is 32.73% 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 'Auth app redesign' is overly broad and vague, covering multiple unrelated features (roids skill, DesignDialog component, playground, design language updates) without clearly specifying the primary change. Use a more specific title such as 'Introduce DesignDialog component and redesign auth pages' to better convey the main technical contributions.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch auth-app-redesign

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

❤️ Share

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

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 22, 2026

Greptile Summary

This PR introduces DesignDialog — a new canonical glassmorphic modal surface for the dashboard — and migrates the auth-methods and sign-up-rules pages to use it alongside other design-system components (DesignCard, DesignMenu, DesignBadge, etc.). The playground and design-language pages gain a fully interactive dialog section with code snippet generation.

Confidence Score: 5/5

Safe to merge — all findings are P2 style/accessibility suggestions with no functional regressions.

The new DesignDialog component is well-structured (Map-based class lookup, correct a11y wiring, clean prop API). Auth-methods and sign-up-rules redesigns are pure UI refactors with preserved logic. The two flagged items are an as any cast copied from an existing component (so the runtime behavior is already proven) and a missing htmlFor association on a Radix Select wrapper — neither blocks correctness.

apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx — as any cast on previewAffectedUsersByOnboardingChange and Label/htmlFor gap.

Important Files Changed

Filename Overview
packages/dashboard-ui-components/src/components/dialog.tsx New DesignDialog component: correctly uses Map for size/variant/overlay classes, proper a11y wiring, well-structured header/body/footer regions.
packages/dashboard-ui-components/src/index.ts Exports DesignDialog and its re-exports (DesignDialogClose, DesignDialogTitle, etc.) — complete and consistent with the component's public API.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx Major auth-methods redesign; introduces useEmailVerificationToggle that casts stackAdminApp as any to call previewAffectedUsersByOnboardingChange; the Label wrapping DesignSelectorDropdown for merge-strategy has no htmlFor.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx Provider settings dialog refactored into composable sub-components; ProviderIcon now accepts a size prop; ProviderSettingDialog gains contentClassName for chrome override. No logic regressions identified.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sign-up-rules/page-client.tsx Migrated RuleTriggerHistoryDialog from hand-built Dialog primitives to DesignDialog; rule editor refactored into composable atoms with useRuleEditorState; action/size/overlay classes use design-system tokens throughout.
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/playground/page-client.tsx Adds a full interactive Dialog section to the playground with shape/size/variant controls and a code snippet generator — consistent with existing playground patterns.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/design-language/page-client.tsx Adds Dialog documentation section with three ComponentDemo blocks (confirmation, rich header, wide tester) and a PropsTable; no logic changes.
apps/dashboard/DESIGN-GUIDE.md Documents DesignDialog in §4.14 with props, rules, and common code shapes; updates quick-ref and primitives-allowed list to reflect the new canonical dialog surface.
apps/dashboard/src/components/ui/action-dialog.tsx Unchanged logic; adds contentClassName prop forwarded to DialogContent to support variant chrome overrides from call-sites like ProviderSettingDialog.

Sequence Diagram

sequenceDiagram
    participant User
    participant AuthMethodsPage
    participant useEmailVerificationToggle
    participant stackAdminApp
    participant ActionDialog
    participant updateConfig

    User->>AuthMethodsPage: Toggle "Require email verification"
    AuthMethodsPage->>useEmailVerificationToggle: onCheckedChange(true)
    useEmailVerificationToggle->>stackAdminApp: previewAffectedUsersByOnboardingChange (as any cast)
    stackAdminApp-->>useEmailVerificationToggle: { affectedUsers, totalAffectedCount }
    alt totalAffectedCount > 0
        useEmailVerificationToggle->>ActionDialog: setPendingChange → open dialog
        User->>ActionDialog: Click "Apply Change"
        ActionDialog->>updateConfig: onConfirm() → configUpdate
        updateConfig-->>AuthMethodsPage: config saved
    else no affected users
        useEmailVerificationToggle->>updateConfig: configUpdate directly
        updateConfig-->>AuthMethodsPage: config saved
    end
Loading

Comments Outside Diff (2)

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

    P2 as any cast for previewAffectedUsersByOnboardingChange

    (stackAdminApp as any).previewAffectedUsersByOnboardingChange(...) suppresses all TypeScript checking on the call: wrong argument shape, renamed method, or removed method will all silently pass the type-checker and only surface as a runtime error in the email-verification confirmation flow. The comment says this was copied from EmailVerificationSetting — if the existing component already has a typed version of this call, expose the method in the admin-app type rather than duplicating the as any cast here.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx
    Line: 811-814
    
    Comment:
    **`as any` cast for `previewAffectedUsersByOnboardingChange`**
    
    `(stackAdminApp as any).previewAffectedUsersByOnboardingChange(...)` suppresses all TypeScript checking on the call: wrong argument shape, renamed method, or removed method will all silently pass the type-checker and only surface as a runtime error in the email-verification confirmation flow. The comment says this was copied from `EmailVerificationSetting` — if the existing component already has a typed version of this call, expose the method in the admin-app type rather than duplicating the `as any` cast here.
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx, line 1150-1199 (link)

    P2 Label without htmlFor wrapping a custom dropdown

    The <Label> for "Multi-provider sign-up mode" has no htmlFor and no id wired to the DesignSelectorDropdown inside it. Because DesignSelectorDropdown renders a Radix Select (not a native <select>) the label click won't focus or activate the control, and screen-readers won't announce the association. Either add a matching id/htmlFor pair if the dropdown exposes a native trigger, or replace Label with a styled div since it is only serving as a layout container here.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx
    Line: 1150-1199
    
    Comment:
    **`Label` without `htmlFor` wrapping a custom dropdown**
    
    The `<Label>` for "Multi-provider sign-up mode" has no `htmlFor` and no `id` wired to the `DesignSelectorDropdown` inside it. Because `DesignSelectorDropdown` renders a Radix `Select` (not a native `<select>`) the label click won't focus or activate the control, and screen-readers won't announce the association. Either add a matching `id`/`htmlFor` pair if the dropdown exposes a native trigger, or replace `Label` with a styled `div` since it is only serving as a layout container here.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx
Line: 811-814

Comment:
**`as any` cast for `previewAffectedUsersByOnboardingChange`**

`(stackAdminApp as any).previewAffectedUsersByOnboardingChange(...)` suppresses all TypeScript checking on the call: wrong argument shape, renamed method, or removed method will all silently pass the type-checker and only surface as a runtime error in the email-verification confirmation flow. The comment says this was copied from `EmailVerificationSetting` — if the existing component already has a typed version of this call, expose the method in the admin-app type rather than duplicating the `as any` cast here.

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

---

This is a comment left during a code review.
Path: apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx
Line: 1150-1199

Comment:
**`Label` without `htmlFor` wrapping a custom dropdown**

The `<Label>` for "Multi-provider sign-up mode" has no `htmlFor` and no `id` wired to the `DesignSelectorDropdown` inside it. Because `DesignSelectorDropdown` renders a Radix `Select` (not a native `<select>`) the label click won't focus or activate the control, and screen-readers won't announce the association. Either add a matching `id`/`htmlFor` pair if the dropdown exposes a native trigger, or replace `Label` with a styled `div` since it is only serving as a layout container here.

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

Reviews (1): Last reviewed commit: "add interactive DesignDialog controls in..." | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Introduces a redesigned “Auth” experience across the dashboard, including a new canonical dialog surface and a visual refresh of sign-up rules and auth-methods pages.

Changes:

  • Added DesignDialog to @stackframe/dashboard-ui-components and documented/demoed it in the design language and playground.
  • Refactored sign-up rules UI (trigger-history dialog, editor layout, menus, empty states) to use design-components patterns.
  • Redesigned auth-methods and provider configuration UIs (cards, menus, new email verification toggle flow).

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
skills-lock.json Adds skills lock entry for the roids agent skill.
packages/dashboard-ui-components/src/index.ts Exposes the new dialog component and types from the UI components package.
packages/dashboard-ui-components/src/components/dialog.tsx Implements DesignDialog wrapper with size/variant presets and structured header/body/footer.
apps/dashboard/src/components/ui/action-dialog.tsx Extends ActionDialog with outside-interaction behavior control and content surface class customization.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sign-up-rules/page-client.tsx Major UI redesign: new dialog usage, editor atoms, trigger-history UX, and restructured page composition.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/design-language/page-client.tsx Adds a Design Language section documenting and showcasing DesignDialog.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx Redesigns provider settings dialog content and provider tiles; adds new chrome customization.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx Redesigns auth methods screen layout, introduces reusable toggle rows, provider rows, live preview, and email verification toggle.
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/playground/page-client.tsx Adds playground demos/controls/snippets for DesignDialog.
apps/dashboard/DESIGN-GUIDE.md Updates the design guide to recommend DesignDialog as the canonical modal surface.
.agents/skills/roids/SKILL.md Adds documentation for the roids agent skill.

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

Comment on lines +305 to +317
<Label
htmlFor={id}
className={`flex items-center gap-3 cursor-pointer ${innerRing} ${padding}`}
>
<div className="p-2 rounded-lg bg-foreground/[0.06] dark:bg-foreground/[0.04] shrink-0">
<Icon size={iconSize} className="text-foreground/70 dark:text-muted-foreground" aria-hidden="true" />
</div>
<div className="flex-1 min-w-0">
<div className="text-sm font-medium text-foreground truncate">{label}</div>
{hint && <div className="text-xs text-muted-foreground mt-0.5">{hint}</div>}
</div>
<Switch id={id} checked={checked} onCheckedChange={onCheckedChange} />
</Label>
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

MethodToggleRow wraps a Radix Switch (renders a <button>) inside a <label htmlFor=...>. Because the switch isn’t a labelable form control, clicking the row/label text won’t toggle the switch (and nesting interactive controls inside a label is also problematic for a11y). Consider changing the wrapper to a non-label element (e.g., div) and either: (1) keep a separate Label for the text only, or (2) add an explicit row onClick that calls onCheckedChange(!checked) while keeping the Switch clickable/focusable.

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +95
function ProviderHeader({ providerId }: { providerId: string }) {
return (
<div className="flex items-center gap-3">
<ProviderIcon id={providerId} size="sm" />
<div className="flex flex-col min-w-0 flex-1">
<div className="flex items-center gap-2 flex-wrap">
<span className="text-sm font-semibold text-foreground">{toTitle(providerId)}</span>
<DesignBadge label="OAuth 2.0" color="blue" size="sm" />
</div>
<span className="text-[11px] text-muted-foreground">Configure credentials for this provider</span>
</div>
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

ProviderHeader (and other new UI here) uses the file-local toTitle() helper, which duplicates BrandIcons.toTitle() but can return undefined for unknown provider IDs. To prevent silent UI regressions when providers change, prefer calling BrandIcons.toTitle(providerId) directly, or ensure toTitle() has an explicit fallback/throw for unknown IDs.

Copilot uses AI. Check for mistakes.
<div className="pt-4 border-t border-black/[0.12] dark:border-white/[0.06]">
<Typography type="label" className="font-semibold mb-3">Props</Typography>
<PropsTable props={[
{ name: "trigger", type: "ReactNode", description: "Element wrapped in a DialogTrigger. Optional when controlling externally via open/onOpenChange." },
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

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

Props table lists trigger as ReactNode, but DesignDialog’s actual prop type is ReactElement (so it can be safely wrapped with DialogTrigger asChild). Suggest updating this row to ReactElement (or ReactElement | null) to match the exported type and avoid implying that strings/fragments are supported.

Suggested change
{ name: "trigger", type: "ReactNode", description: "Element wrapped in a DialogTrigger. Optional when controlling externally via open/onOpenChange." },
{ name: "trigger", type: "ReactElement | null", description: "Element wrapped in a DialogTrigger. Optional when controlling externally via open/onOpenChange." },

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

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]/sign-up-rules/page-client.tsx (2)

1509-1538: ⚠️ Potential issue | 🟠 Major

Reset analytics loading state on failures.

If /internal/sign-up-rules-stats fails, setIsLoading(false) is never reached, leaving every rule sparkline stuck in a loading state after the alert.

Proposed fix
     const fetchAnalytics = async () => {
-      const response = await (stackAdminApp as any)[stackAppInternalsSymbol].sendRequest(
-        '/internal/sign-up-rules-stats',
-        { method: 'GET' },
-        'admin'
-      );
-      if (cancelled) return;
-
-      if (!response.ok) {
-        throw new StackAssertionError(`Failed to fetch sign-up rules stats: ${response.status} ${response.statusText}`);
-      }
-
-      const data = await response.json();
-      setTimespanHours(data.analytics_hours);
-
-      const analyticsMap = new Map<string, RuleAnalytics>();
-      for (const trigger of data.rule_triggers ?? []) {
-        analyticsMap.set(trigger.rule_id, {
-          ruleId: trigger.rule_id,
-          countInTimespan: trigger.total_count,
-          allTimeCount: trigger.all_time_count,
-          hourlyCounts: trigger.hourly_counts ?? [],
-        });
-      }
-
-      setAnalytics(analyticsMap);
-      setIsLoading(false);
+      try {
+        const response = await (stackAdminApp as any)[stackAppInternalsSymbol].sendRequest(
+          '/internal/sign-up-rules-stats',
+          { method: 'GET' },
+          'admin'
+        );
+        if (cancelled) return;
+
+        if (!response.ok) {
+          throw new StackAssertionError(`Failed to fetch sign-up rules stats: ${response.status} ${response.statusText}`);
+        }
+
+        const data = await response.json();
+        setTimespanHours(data.analytics_hours);
+
+        const analyticsMap = new Map<string, RuleAnalytics>();
+        for (const trigger of data.rule_triggers ?? []) {
+          analyticsMap.set(trigger.rule_id, {
+            ruleId: trigger.rule_id,
+            countInTimespan: trigger.total_count,
+            allTimeCount: trigger.all_time_count,
+            hourlyCounts: trigger.hourly_counts ?? [],
+          });
+        }
+
+        setAnalytics(analyticsMap);
+      } finally {
+        if (!cancelled) {
+          setIsLoading(false);
+        }
+      }
     };
🤖 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]/sign-up-rules/page-client.tsx
around lines 1509 - 1538, The fetchAnalytics function can throw and never clears
the loading state; wrap the body of fetchAnalytics in a try/catch/finally (or
add a finally block) so that setIsLoading(false) is always called regardless of
success or failure, and in the catch call the existing alert path (or rethrow)
before letting finally clear loading. Update the fetchAnalytics function
referenced by runAsynchronouslyWithAlert to ensure setIsLoading(false) is
invoked in the finally block (use the function name fetchAnalytics and state
setter setIsLoading to locate and modify the code).

400-449: ⚠️ Potential issue | 🟠 Major

Remove the unsafe return in finally block to prevent stale loading state.

The return statement at line 437 in the finally block can leave isLoadingMore and isInitialLoading stuck in loading state if a request is superseded (e.g., dialog closes or a reset is triggered). Wrap the state updates in a conditional instead:

Proposed fix
    if (reset) {
      setIsInitialLoading(true);
+     setIsLoadingMore(false);
      setLoadingError(null);
      setHasMore(true);
      setTriggers([]);
    } else {
      setIsLoadingMore(true);
    }
    
    try {
      // ... fetch logic ...
    } catch (error) {
      // ... error handling ...
    } finally {
-     if (nextRequestId !== latestRequestIdRef.current) return;
-     if (reset) setIsInitialLoading(false);
-     else setIsLoadingMore(false);
+     if (nextRequestId === latestRequestIdRef.current) {
+       if (reset) setIsInitialLoading(false);
+       else setIsLoadingMore(false);
+     }
    }

Also clear both flags when the dialog closes:

   const handleOpenChange = (nextOpen: boolean) => {
     setOpen(nextOpen);
     if (!nextOpen) {
       latestRequestIdRef.current += 1;
+      setIsInitialLoading(false);
+      setIsLoadingMore(false);
       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]/sign-up-rules/page-client.tsx
around lines 400 - 449, The finally block in fetchTriggerPage should not return
early on a stale request because that can leave loading flags stuck; instead,
remove the final "if (nextRequestId !== latestRequestIdRef.current) return;" and
wrap the state updates so they only run when the request is still current (check
nextRequestId === latestRequestIdRef.current before mutating state), then always
clear loading flags when the dialog closes in handleOpenChange by calling
setIsInitialLoading(false) and setIsLoadingMore(false) (and optionally clear
setLoadingError) after incrementing latestRequestIdRef.current so the UI never
remains stuck.
🧹 Nitpick comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx (1)

601-606: Replace Record<string, any> with a typed config-update value.

This update only writes booleans and possibly an OAuthAccountMergeStrategy, so any is avoidable here.

♻️ Proposed fix
-    const configUpdate: Record<string, any> = { 'auth.allowSignUp': newAllowSignUp };
+    const configUpdate: Record<string, boolean | OAuthAccountMergeStrategy> = { 'auth.allowSignUp': newAllowSignUp };

As per coding guidelines, “Try to avoid the any type. Whenever you need to use any, leave a comment explaining why you're using 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]/auth-methods/page-client.tsx
around lines 601 - 606, The configUpdate in handleSignUpConfirmed currently uses
Record<string, any>; change it to a typed shape that only allows the two keys
being written: 'auth.allowSignUp' (boolean) and optionally
'auth.oauth.accountMergeStrategy' (OAuthAccountMergeStrategy | undefined).
Update the declaration for configUpdate to use a narrow type (e.g., a specific
interface or Partial<Record<'auth.allowSignUp' |
'auth.oauth.accountMergeStrategy', boolean | OAuthAccountMergeStrategy>>) and
remove any usage; ensure localMergeStrategy is typed as
OAuthAccountMergeStrategy so the value assigned to
'auth.oauth.accountMergeStrategy' matches the new type before calling
updateConfig(stackAdminApp, configUpdate, ...).
🤖 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/DESIGN-GUIDE.md`:
- Line 67: Update the stale section reference in the DesignDialog guidance:
locate the sentence that says "see §22 below" in the paragraph mentioning
DesignDialog (the one referencing DesignDialog / Dialog + DialogContent +
DialogHeader) and replace "§22 below" with "§4.14" so the pointer correctly
lands on the new DesignDialog section.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx:
- Around line 412-417: Add a brief inline comment explaining why the any cast is
required where handleChange calls (stackAdminApp as
any).previewAffectedUsersByOnboardingChange: state that
previewAffectedUsersByOnboardingChange is a dynamically-typed admin API method
not exposed in the current TypeScript types, so the any cast is used to bypass
typing for runtime-only admin methods (mirror the comment pattern used in
onboarding/page-client.tsx).
- Around line 652-655: Remove the unsafe type cast in onMergeStrategyChange and
validate the incoming string against the known OAuthAccountMergeStrategy set
before calling setLocalMergeStrategy: implement a type-guard or check (e.g.,
compare the value to Object.values(OAuthAccountMergeStrategy) or a hard-coded
allowed array) to confirm the value is a valid OAuthAccountMergeStrategy, then
call setLocalMergeStrategy(next === config.auth.oauth.accountMergeStrategy ?
undefined : next) using the validated next; if the value is invalid, treat it as
no-selection (undefined) or ignore it.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx:
- Around line 140-146: The RedirectInline and DocsTextLink components currently
interpolate providerId directly into the callback URL string; update both to use
the urlString template tag (or encodeURIComponent(providerId)) when constructing
the URL so the providerId is properly encoded—e.g., replace
`${getPublicEnvVar('NEXT_PUBLIC_STACK_API_URL')}/api/v1/auth/oauth/callback/${providerId}`
with the urlString version that encodes providerId (and do the same in
DocsTextLink), keeping the surrounding getPublicEnvVar and InlineCode/anchor
usage intact.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/design-language/page-client.tsx:
- Around line 1237-1245: Update the props table to match the actual exported API
types and names: change the "trigger" type to React.ReactElement to match
DesignDialogProps, ensure the open/onOpenChange/defaultOpen entry matches the
controlled/uncontrolled API used by DesignDialog (use the exact names open,
onOpenChange, defaultOpen), and update any mentions of DialogClose to reference
the exported DesignDialogClose component for close buttons; verify other entries
(size, variant, icon, title/description, headerContent, customHeader, footer)
use the same names and TypeScript types as in the DesignDialogProps/type
declarations.

In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/sign-up-rules/page-client.tsx:
- Around line 1698-1703: serverRules currently maps
configWithRules.auth.signUpRules without using each rule's priority and will
crash when signUpRules is undefined; update serverRules (the value used to
render rules) to first default signUpRules to an empty object, then use
typedEntries(configWithRules.auth.signUpRules ?? {}) and sort the resulting
[{id, rule}] array by rule.priority (descending or ascending to match
handleSaveOrder semantics) before returning it; keep defaultAction as is
(configWithRules.auth.signUpRulesDefaultAction ?? 'allow') so empty-state
rendering is safe.
- Around line 817-823: The Switch control (switchControl / <Switch
checked={isEnabled} onCheckedChange={props.onToggleEnabled} />) lacks an
accessible name; update the Switch element to include an accessible name by
adding either aria-label or aria-labelledby that references the rule's visible
name (e.g., use a stable id for the rule title like ruleNameId =
`rule-name-${projectId || ruleId}` and set aria-labelledby={ruleNameId}), or
pass a descriptive aria-label using the rule text from props (e.g.,
`aria-label={`Enable rule ${props.ruleName}`}`) so screen readers announce which
rule is being toggled while keeping the existing event.stopPropagation handlers.
- Around line 658-666: The dropdown onValueChange handlers (e.g., in
ActionDropdown with state.setActionType) currently cast incoming values (v as
ActionType / as 'allow' | 'reject') without runtime checks; update these
handlers to perform defensive validation against the allowed option set before
updating state—e.g., check that v is one of the entries in
ACTION_DROPDOWN_OPTIONS (or the explicit string union for the allow/reject
dropdown) and only call state.setActionType or state.setAllowReject when the
value is valid, otherwise ignore the change or log a warning; apply the same
pattern to the similar dropdown handler at the other location (lines ~920-930)
so invalid/unexpected values cannot be injected via the dropdown component.

---

Outside diff comments:
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/sign-up-rules/page-client.tsx:
- Around line 1509-1538: The fetchAnalytics function can throw and never clears
the loading state; wrap the body of fetchAnalytics in a try/catch/finally (or
add a finally block) so that setIsLoading(false) is always called regardless of
success or failure, and in the catch call the existing alert path (or rethrow)
before letting finally clear loading. Update the fetchAnalytics function
referenced by runAsynchronouslyWithAlert to ensure setIsLoading(false) is
invoked in the finally block (use the function name fetchAnalytics and state
setter setIsLoading to locate and modify the code).
- Around line 400-449: The finally block in fetchTriggerPage should not return
early on a stale request because that can leave loading flags stuck; instead,
remove the final "if (nextRequestId !== latestRequestIdRef.current) return;" and
wrap the state updates so they only run when the request is still current (check
nextRequestId === latestRequestIdRef.current before mutating state), then always
clear loading flags when the dialog closes in handleOpenChange by calling
setIsInitialLoading(false) and setIsLoadingMore(false) (and optionally clear
setLoadingError) after incrementing latestRequestIdRef.current so the UI never
remains stuck.

---

Nitpick comments:
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx:
- Around line 601-606: The configUpdate in handleSignUpConfirmed currently uses
Record<string, any>; change it to a typed shape that only allows the two keys
being written: 'auth.allowSignUp' (boolean) and optionally
'auth.oauth.accountMergeStrategy' (OAuthAccountMergeStrategy | undefined).
Update the declaration for configUpdate to use a narrow type (e.g., a specific
interface or Partial<Record<'auth.allowSignUp' |
'auth.oauth.accountMergeStrategy', boolean | OAuthAccountMergeStrategy>>) and
remove any usage; ensure localMergeStrategy is typed as
OAuthAccountMergeStrategy so the value assigned to
'auth.oauth.accountMergeStrategy' matches the new type before calling
updateConfig(stackAdminApp, configUpdate, ...).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cd36aa67-14a4-453b-bb35-40d8870d1178

📥 Commits

Reviewing files that changed from the base of the PR and between f89b97b and d23fbbd.

📒 Files selected for processing (11)
  • .agents/skills/roids/SKILL.md
  • apps/dashboard/DESIGN-GUIDE.md
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/playground/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/providers.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/design-language/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sign-up-rules/page-client.tsx
  • apps/dashboard/src/components/ui/action-dialog.tsx
  • packages/dashboard-ui-components/src/components/dialog.tsx
  • packages/dashboard-ui-components/src/index.ts
  • skills-lock.json

- complex layout containers where design-components does not provide one
- highly specialized editor internals

For any general-purpose modal surface (rich detail dialogs, tester surfaces, data dialogs, settings popovers presented as modals), use `DesignDialog` instead of wiring `Dialog` + `DialogContent` + `DialogHeader` etc. by hand. `DesignDialog` is the canonical glassmorphic dialog surface for the dashboard — see §22 below.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix the stale section reference.

§22 below should point to §4.14 so readers land on the new DesignDialog section.

📝 Proposed fix
-For any general-purpose modal surface (rich detail dialogs, tester surfaces, data dialogs, settings popovers presented as modals), use `DesignDialog` instead of wiring `Dialog` + `DialogContent` + `DialogHeader` etc. by hand. `DesignDialog` is the canonical glassmorphic dialog surface for the dashboard — see §22 below.
+For any general-purpose modal surface (rich detail dialogs, tester surfaces, data dialogs, settings popovers presented as modals), use `DesignDialog` instead of wiring `Dialog` + `DialogContent` + `DialogHeader` etc. by hand. `DesignDialog` is the canonical glassmorphic dialog surface for the dashboard — see §4.14 below.
📝 Committable suggestion

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

Suggested change
For any general-purpose modal surface (rich detail dialogs, tester surfaces, data dialogs, settings popovers presented as modals), use `DesignDialog` instead of wiring `Dialog` + `DialogContent` + `DialogHeader` etc. by hand. `DesignDialog` is the canonical glassmorphic dialog surface for the dashboard — see §22 below.
For any general-purpose modal surface (rich detail dialogs, tester surfaces, data dialogs, settings popovers presented as modals), use `DesignDialog` instead of wiring `Dialog` + `DialogContent` + `DialogHeader` etc. by hand. `DesignDialog` is the canonical glassmorphic dialog surface for the dashboard — see §4.14 below.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/dashboard/DESIGN-GUIDE.md` at line 67, Update the stale section
reference in the DesignDialog guidance: locate the sentence that says "see §22
below" in the paragraph mentioning DesignDialog (the one referencing
DesignDialog / Dialog + DialogContent + DialogHeader) and replace "§22 below"
with "§4.14" so the pointer correctly lands on the new DesignDialog section.

Comment on lines +412 to +417
const handleChange = async (next: boolean) => {
if (next && !projectConfig.onboarding.requireEmailVerification) {
const preview = await (stackAdminApp as any).previewAffectedUsersByOnboardingChange(
{ requireEmailVerification: true },
10,
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find runtime implementations and type declarations for previewAffectedUsersByOnboardingChange.
rg -n -C3 '\bpreviewAffectedUsersByOnboardingChange\b'

Repository: stack-auth/stack-auth

Length of output: 5065


Add a comment explaining why the any cast is necessary for stackAdminApp.previewAffectedUsersByOnboardingChange.

The same pattern is already documented in onboarding/page-client.tsx (line 44): // any cast needed: previewAffectedUsersByOnboardingChange is a dynamically-typed admin API method. Add a similar comment here to explain the type bypass, per the guideline: "Whenever you need to use any, leave a comment explaining why you're using 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]/auth-methods/page-client.tsx
around lines 412 - 417, Add a brief inline comment explaining why the any cast
is required where handleChange calls (stackAdminApp as
any).previewAffectedUsersByOnboardingChange: state that
previewAffectedUsersByOnboardingChange is a dynamically-typed admin API method
not exposed in the current TypeScript types, so the any cast is used to bypass
typing for runtime-only admin methods (mirror the comment pattern used in
onboarding/page-client.tsx).

Comment on lines +652 to +655
const onMergeStrategyChange = (value: string) => {
const next = value as OAuthAccountMergeStrategy;
setLocalMergeStrategy(next === config.auth.oauth.accountMergeStrategy ? undefined : next);
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

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

Repository: stack-auth/stack-auth

Length of output: 160


🏁 Script executed:

sed -n '650,660p' "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx"

Repository: stack-auth/stack-auth

Length of output: 590


🏁 Script executed:

# Search for OAuthAccountMergeStrategy type definition
rg "OAuthAccountMergeStrategy" --type ts --type tsx -B 2 -A 2

Repository: stack-auth/stack-auth

Length of output: 92


🏁 Script executed:

# Look for similar validation patterns in the file for dropdowns
rg "as.*Strategy\|type.*Strategy" "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx" -B 2 -A 2

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

# Search for OAuthAccountMergeStrategy type definition
rg "OAuthAccountMergeStrategy" --type ts -B 2 -A 5

Repository: stack-auth/stack-auth

Length of output: 4457


🏁 Script executed:

# Look for the dropdown component configuration for merge strategy
rg "accountMergeStrategy\|merge.*strategy" "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx" -B 3 -A 3

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

# Find where this dropdown is defined and what options it has
sed -n '1,700p' "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx" | grep -n -B 10 -A 10 "onMergeStrategyChange"

Repository: stack-auth/stack-auth

Length of output: 1033


🏁 Script executed:

# Check for similar validation patterns in the same file
sed -n '600,750p' "apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/auth-methods/page-client.tsx"

Repository: stack-auth/stack-auth

Length of output: 7241


Remove the type cast and validate the merge strategy value.

The code uses value as OAuthAccountMergeStrategy to bypass the type system. Since the dropdown options are defined separately from the type definition, invalid values could persist if they drift. Add explicit validation instead:

  const onMergeStrategyChange = (value: string) => {
-   const next = value as OAuthAccountMergeStrategy;
+   if (value !== "link_method" && value !== "raise_error" && value !== "allow_duplicates") {
+     throw new Error(`Unknown OAuth account merge strategy "${value}"`);
+   }
+   const next = value;
    setLocalMergeStrategy(next === config.auth.oauth.accountMergeStrategy ? undefined : next);
  };

This follows the coding guideline: "Do NOT use as/any/type casts or anything else like that to bypass the type system."

🤖 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]/auth-methods/page-client.tsx
around lines 652 - 655, Remove the unsafe type cast in onMergeStrategyChange and
validate the incoming string against the known OAuthAccountMergeStrategy set
before calling setLocalMergeStrategy: implement a type-guard or check (e.g.,
compare the value to Object.values(OAuthAccountMergeStrategy) or a hard-coded
allowed array) to confirm the value is a valid OAuthAccountMergeStrategy, then
call setLocalMergeStrategy(next === config.auth.oauth.accountMergeStrategy ?
undefined : next) using the validated next; if the value is invalid, treat it as
no-selection (undefined) or ignore it.

Comment on lines +140 to +146
function RedirectInline({ providerId }: { providerId: string }) {
return (
<div className="flex flex-col gap-1">
<span className="text-[11px] uppercase tracking-wider text-muted-foreground">Redirect URL</span>
<Typography type="footnote" className="break-all">
<InlineCode>{`${getPublicEnvVar('NEXT_PUBLIC_STACK_API_URL')}/api/v1/auth/oauth/callback/${providerId}`}</InlineCode>
</Typography>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and get its line count
wc -l apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/auth-methods/providers.tsx

Repository: stack-auth/stack-auth

Length of output: 158


🏁 Script executed:

# Read the specific lines mentioned in the review comment (140-146 and 246-250)
sed -n '140,146p; 246,250p' apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/auth-methods/providers.tsx

Repository: stack-auth/stack-auth

Length of output: 717


🏁 Script executed:

# Search for any URL helper functions or urlString utilities in the codebase
rg -t ts -t tsx "urlString|encodeURIComponent" apps/dashboard --max-count=10

Repository: stack-auth/stack-auth

Length of output: 92


🏁 Script executed:

# Check if there's a URL utility or helper file commonly used in this project
fd -t f -i "url" apps/dashboard/src --type f | head -20

Repository: stack-auth/stack-auth

Length of output: 183


🏁 Script executed:

# Search for urlString helper function/utility in the entire codebase
rg "urlString" --type ts --type js -i apps/ -B 2 -A 2

Repository: stack-auth/stack-auth

Length of output: 36131


🏁 Script executed:

# Check how provider IDs are used elsewhere - look for any encoding patterns
rg "providerId" apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/auth-methods/providers.tsx -B 2 -A 2

Repository: stack-auth/stack-auth

Length of output: 4107


🏁 Script executed:

# Verify the exact context around lines 140-150 to see the full function
sed -n '135,160p' apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/auth-methods/providers.tsx

Repository: stack-auth/stack-auth

Length of output: 1009


🏁 Script executed:

# Check the context around lines 240-260 for the second location
sed -n '240,260p' apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/auth-methods/providers.tsx

Repository: stack-auth/stack-auth

Length of output: 735


🏁 Script executed:

# Check the imports at the top of the providers.tsx file
head -30 apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/auth-methods/providers.tsx

Repository: stack-auth/stack-auth

Length of output: 1576


🏁 Script executed:

# Check if urlString is imported anywhere in the file
grep -n "urlString\|encodeURIComponent" apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/auth-methods/providers.tsx

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

# Check the urlString implementation to understand how it works
rg -A 10 "export.*function urlString|export const urlString" --type ts --type js

Repository: stack-auth/stack-auth

Length of output: 1034


🏁 Script executed:

# Look for where urlString is defined
fd "urls.ts" -o "urls.js" | head -5

Repository: stack-auth/stack-auth

Length of output: 198


Use urlString template tag to encode provider IDs in URLs.

Both RedirectInline (lines 140–146) and DocsTextLink (lines 246–250) interpolate providerId directly into URL paths using backtick literals. Per coding guidelines, use urlString or encodeURIComponent() for consistency. The urlString helper automatically encodes all interpolated values:

🛡️ Proposed fix
+import { urlString } from "@stackframe/stack-shared/dist/utils/urls";

 function RedirectInline({ providerId }: { providerId: string }) {
   return (
     <div className="flex flex-col gap-1">
       <span className="text-[11px] uppercase tracking-wider text-muted-foreground">Redirect URL</span>
       <Typography type="footnote" className="break-all">
-        <InlineCode>{`${getPublicEnvVar('NEXT_PUBLIC_STACK_API_URL')}/api/v1/auth/oauth/callback/${providerId}`}</InlineCode>
+        <InlineCode>{urlString`${getPublicEnvVar('NEXT_PUBLIC_STACK_API_URL')}/api/v1/auth/oauth/callback/${providerId}`}</InlineCode>
       </Typography>
     </div>
   );
 }

 function DocsTextLink({ providerId }: { providerId: string }) {
   return (
     <Link
-      href={`https://docs.stack-auth.com/docs/concepts/auth-providers/${providerId === "x" ? "x-twitter" : providerId}`}
+      href={urlString`https://docs.stack-auth.com/docs/concepts/auth-providers/${providerId === "x" ? "x-twitter" : providerId}`}
🤖 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]/auth-methods/providers.tsx
around lines 140 - 146, The RedirectInline and DocsTextLink components currently
interpolate providerId directly into the callback URL string; update both to use
the urlString template tag (or encodeURIComponent(providerId)) when constructing
the URL so the providerId is properly encoded—e.g., replace
`${getPublicEnvVar('NEXT_PUBLIC_STACK_API_URL')}/api/v1/auth/oauth/callback/${providerId}`
with the urlString version that encodes providerId (and do the same in
DocsTextLink), keeping the surrounding getPublicEnvVar and InlineCode/anchor
usage intact.

Comment on lines +1237 to +1245
{ name: "trigger", type: "ReactNode", description: "Element wrapped in a DialogTrigger. Optional when controlling externally via open/onOpenChange." },
{ name: "open / onOpenChange / defaultOpen", type: "boolean / fn / boolean", description: "Standard controlled or uncontrolled state for the dialog." },
{ name: "size", type: "'sm' | 'md' | 'lg' | 'xl' | '2xl' | ... | '7xl' | 'full'", default: "'lg'", description: "Width preset for the modal surface." },
{ name: "variant", type: "'glassmorphic' | 'plain'", default: "'glassmorphic'", description: "Visual style for the surface and overlay." },
{ name: "icon", type: "ElementType | null", description: "Icon component for the header chip. Pass null to skip the chip." },
{ name: "title / description", type: "ReactNode", description: "Standard header text. `title` is wired into DialogTitle for a11y." },
{ name: "headerContent", type: "ReactNode", description: "Rich content rendered below the icon/title block (summary cards, badges, etc)." },
{ name: "customHeader", type: "ReactNode", description: "Override the entire header. You become responsible for rendering DialogTitle." },
{ name: "footer", type: "ReactNode", description: "Footer node. Renders in a styled bottom bar. Wrap close buttons in DialogClose asChild." },
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Align the props table with the exported API names and types.

trigger is typed as React.ReactElement in DesignDialogProps, and close buttons should reference DesignDialogClose rather than raw DialogClose.

📝 Proposed fix
-              { name: "trigger", type: "ReactNode", description: "Element wrapped in a DialogTrigger. Optional when controlling externally via open/onOpenChange." },
+              { name: "trigger", type: "ReactElement", description: "Element wrapped in a DesignDialogTrigger. Optional when controlling externally via open/onOpenChange." },
...
-              { name: "footer", type: "ReactNode", description: "Footer node. Renders in a styled bottom bar. Wrap close buttons in DialogClose asChild." },
+              { name: "footer", type: "ReactNode", description: "Footer node. Renders in a styled bottom bar. Wrap close buttons in DesignDialogClose asChild." },
📝 Committable suggestion

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

Suggested change
{ name: "trigger", type: "ReactNode", description: "Element wrapped in a DialogTrigger. Optional when controlling externally via open/onOpenChange." },
{ name: "open / onOpenChange / defaultOpen", type: "boolean / fn / boolean", description: "Standard controlled or uncontrolled state for the dialog." },
{ name: "size", type: "'sm' | 'md' | 'lg' | 'xl' | '2xl' | ... | '7xl' | 'full'", default: "'lg'", description: "Width preset for the modal surface." },
{ name: "variant", type: "'glassmorphic' | 'plain'", default: "'glassmorphic'", description: "Visual style for the surface and overlay." },
{ name: "icon", type: "ElementType | null", description: "Icon component for the header chip. Pass null to skip the chip." },
{ name: "title / description", type: "ReactNode", description: "Standard header text. `title` is wired into DialogTitle for a11y." },
{ name: "headerContent", type: "ReactNode", description: "Rich content rendered below the icon/title block (summary cards, badges, etc)." },
{ name: "customHeader", type: "ReactNode", description: "Override the entire header. You become responsible for rendering DialogTitle." },
{ name: "footer", type: "ReactNode", description: "Footer node. Renders in a styled bottom bar. Wrap close buttons in DialogClose asChild." },
{ name: "trigger", type: "ReactElement", description: "Element wrapped in a DesignDialogTrigger. Optional when controlling externally via open/onOpenChange." },
{ name: "open / onOpenChange / defaultOpen", type: "boolean / fn / boolean", description: "Standard controlled or uncontrolled state for the dialog." },
{ name: "size", type: "'sm' | 'md' | 'lg' | 'xl' | '2xl' | ... | '7xl' | 'full'", default: "'lg'", description: "Width preset for the modal surface." },
{ name: "variant", type: "'glassmorphic' | 'plain'", default: "'glassmorphic'", description: "Visual style for the surface and overlay." },
{ name: "icon", type: "ElementType | null", description: "Icon component for the header chip. Pass null to skip the chip." },
{ name: "title / description", type: "ReactNode", description: "Standard header text. `title` is wired into DialogTitle for a11y." },
{ name: "headerContent", type: "ReactNode", description: "Rich content rendered below the icon/title block (summary cards, badges, etc)." },
{ name: "customHeader", type: "ReactNode", description: "Override the entire header. You become responsible for rendering DialogTitle." },
{ name: "footer", type: "ReactNode", description: "Footer node. Renders in a styled bottom bar. Wrap close buttons in DesignDialogClose asChild." },
🤖 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]/design-language/page-client.tsx
around lines 1237 - 1245, Update the props table to match the actual exported
API types and names: change the "trigger" type to React.ReactElement to match
DesignDialogProps, ensure the open/onOpenChange/defaultOpen entry matches the
controlled/uncontrolled API used by DesignDialog (use the exact names open,
onOpenChange, defaultOpen), and update any mentions of DialogClose to reference
the exported DesignDialogClose component for close buttons; verify other entries
(size, variant, icon, title/description, headerContent, customHeader, footer)
use the same names and TypeScript types as in the DesignDialogProps/type
declarations.

Comment on lines +658 to +666
function ActionDropdown({ state, size = "sm", className }: { state: RuleEditorState, size?: "sm" | "md" | "lg", className?: string }) {
return (
<div className={cn(ruleCardClassName, "p-4 ring-primary/50 ring-2 transition-all duration-150 hover:transition-none")}>
<div className="flex items-start gap-3">
{/* Enabled toggle on the left */}
<div className="pt-2">
<Switch checked={enabled} onCheckedChange={setEnabled} />
</div>
<DesignSelectorDropdown
value={state.actionType}
onValueChange={(v) => state.setActionType(v as ActionType)}
size={size}
className={className ?? "w-40"}
options={ACTION_DROPDOWN_OPTIONS}
/>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Type-check the guarded dropdown value changes.
pnpm typecheck

Repository: stack-auth/stack-auth

Length of output: 10190


🏁 Script executed:

#!/bin/bash
# Check file size and read the relevant sections
wc -l apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/sign-up-rules/page-client.tsx

Repository: stack-auth/stack-auth

Length of output: 162


🏁 Script executed:

#!/bin/bash
# Read lines around 658-666 and 920-930
sed -n '650,675p' apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/sign-up-rules/page-client.tsx

Repository: stack-auth/stack-auth

Length of output: 875


🏁 Script executed:

#!/bin/bash
# Read lines around 920-930
sed -n '910,940p' apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/sign-up-rules/page-client.tsx

Repository: stack-auth/stack-auth

Length of output: 1233


🏁 Script executed:

#!/bin/bash
# Search for ACTION_DROPDOWN_OPTIONS and ActionType definition
rg "ACTION_DROPDOWN_OPTIONS|type ActionType|enum ActionType" apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/sign-up-rules/page-client.tsx -A 5

Repository: stack-auth/stack-auth

Length of output: 703


🏁 Script executed:

#!/bin/bash
# Find SignUpRuleAction definition
rg "type SignUpRuleAction|interface SignUpRuleAction" apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/sign-up-rules/page-client.tsx -B 2 -A 10

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Check if SignUpRuleAction is imported
rg "SignUpRuleAction|import.*SignUpRule" apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/sign-up-rules/page-client.tsx | head -20

Repository: stack-auth/stack-auth

Length of output: 220


🏁 Script executed:

#!/bin/bash
# Search for SignUpRuleAction in the entire codebase
rg "type SignUpRuleAction|interface SignUpRuleAction" apps/dashboard/src/ -A 10

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Find the SignUpRuleAction definition in stack-shared
find . -path "*/stack-shared/dist/interface/crud/sign-up-rules*" -type f

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Search more broadly for SignUpRuleAction
rg "SignUpRuleAction" packages/stack-shared/src/ | head -20

Repository: stack-auth/stack-auth

Length of output: 241


🏁 Script executed:

#!/bin/bash
# List the interface/crud directory
ls -la packages/stack-shared/src/interface/crud/ | grep -i sign

Repository: stack-auth/stack-auth

Length of output: 131


🏁 Script executed:

#!/bin/bash
# Search in the entire packages directory
rg "export.*SignUpRuleAction|type SignUpRuleAction" packages/ | head -10

Repository: stack-auth/stack-auth

Length of output: 155


🏁 Script executed:

#!/bin/bash
# Read the entire sign-up-rules.ts file
cat packages/stack-shared/src/interface/crud/sign-up-rules.ts

Repository: stack-auth/stack-auth

Length of output: 336


🏁 Script executed:

#!/bin/bash
# Find and read the useRuleEditorState hook
rg "function useRuleEditorState|const useRuleEditorState" apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/sign-up-rules/page-client.tsx -A 50 | head -70

Repository: stack-auth/stack-auth

Length of output: 1855


🏁 Script executed:

#!/bin/bash
# Check what setActionType does
rg "setActionType|setState.*actionType" apps/dashboard/src/app/\(main\)/\(protected\)/projects/\[projectId\]/sign-up-rules/page-client.tsx -B 3 -A 3 | head -40

Repository: stack-auth/stack-auth

Length of output: 855


Replace unchecked dropdown casts with defensive guards.

The onValueChange callbacks use type assertions (as ActionType and as 'allow' | 'reject') to accept dropdown values without runtime validation. While the dropdowns are currently hardcoded with correct options, this bypasses type safety and could allow invalid values to enter state if the dropdown component changes or receives unexpected input.

Proposed fix
 const ACTION_DROPDOWN_OPTIONS: { value: ActionType, label: string }[] = [
   { value: "allow", label: "Allow" },
   { value: "reject", label: "Reject" },
   { value: "restrict", label: "Restrict" },
   { value: "log", label: "Log only" },
 ];
+
+function isActionType(value: string): value is ActionType {
+  return ACTION_DROPDOWN_OPTIONS.some((option) => option.value === value);
+}
+
+function isDefaultAction(value: string): value is 'allow' | 'reject' {
+  return value === 'allow' || value === 'reject';
+}
@@
     <DesignSelectorDropdown
       value={state.actionType}
-      onValueChange={(v) => state.setActionType(v as ActionType)}
+      onValueChange={(v) => {
+        if (!isActionType(v)) {
+          throw new StackAssertionError(`Unexpected sign-up rule action type: ${v}`);
+        }
+        state.setActionType(v);
+      }}
       size={size}
       className={className ?? "w-40"}
       options={ACTION_DROPDOWN_OPTIONS}
@@
         triggerLabel={value === 'allow' ? 'Allow' : 'Reject'}
         value={value}
-        onValueChange={(v) => onChange(v as 'allow' | 'reject')}
+        onValueChange={(v) => {
+          if (!isDefaultAction(v)) {
+            throw new StackAssertionError(`Unexpected default sign-up rule action: ${v}`);
+          }
+          onChange(v);
+        }}
         options={[

Also applies to: line 920-930

🤖 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]/sign-up-rules/page-client.tsx
around lines 658 - 666, The dropdown onValueChange handlers (e.g., in
ActionDropdown with state.setActionType) currently cast incoming values (v as
ActionType / as 'allow' | 'reject') without runtime checks; update these
handlers to perform defensive validation against the allowed option set before
updating state—e.g., check that v is one of the entries in
ACTION_DROPDOWN_OPTIONS (or the explicit string union for the allow/reject
dropdown) and only call state.setActionType or state.setAllowReject when the
value is valid, otherwise ignore the change or log a warning; apply the same
pattern to the similar dropdown handler at the other location (lines ~920-930)
so invalid/unexpected values cannot be injected via the dropdown component.

Comment on lines +817 to +823
const switchControl = (
<div
onClick={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
>
<Switch checked={isEnabled} onCheckedChange={props.onToggleEnabled} />
</div>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add an accessible name to the rule enabled switch.

The row switch is otherwise announced without context, making it unclear which rule is being toggled.

Proposed fix
-      <Switch checked={isEnabled} onCheckedChange={props.onToggleEnabled} />
+      <Switch
+        checked={isEnabled}
+        onCheckedChange={props.onToggleEnabled}
+        aria-label={`${isEnabled ? "Disable" : "Enable"} rule ${ruleName}`}
+      />
📝 Committable suggestion

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

Suggested change
const switchControl = (
<div
onClick={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
>
<Switch checked={isEnabled} onCheckedChange={props.onToggleEnabled} />
</div>
const switchControl = (
<div
onClick={(e) => e.stopPropagation()}
onPointerDown={(e) => e.stopPropagation()}
>
<Switch
checked={isEnabled}
onCheckedChange={props.onToggleEnabled}
aria-label={`${isEnabled ? "Disable" : "Enable"} rule ${ruleName}`}
/>
</div>
🤖 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]/sign-up-rules/page-client.tsx
around lines 817 - 823, The Switch control (switchControl / <Switch
checked={isEnabled} onCheckedChange={props.onToggleEnabled} />) lacks an
accessible name; update the Switch element to include an accessible name by
adding either aria-label or aria-labelledby that references the rule's visible
name (e.g., use a stable id for the rule title like ruleNameId =
`rule-name-${projectId || ruleId}` and set aria-labelledby={ruleNameId}), or
pass a descriptive aria-label using the rule text from props (e.g.,
`aria-label={`Enable rule ${props.ruleName}`}`) so screen readers announce which
rule is being toggled while keeping the existing event.stopPropagation handlers.

Comment on lines 1698 to 1703
const serverRules = useMemo(() =>
typedEntries(configWithRules.auth.signUpRules).map(([id, rule]) => ({ id, rule })),
[configWithRules.auth.signUpRules]
);
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- TypeScript may not see these as optional due to type assertion
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
const defaultAction = configWithRules.auth.signUpRulesDefaultAction ?? 'allow';
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Sort persisted rules by priority before rendering.

handleSaveOrder persists order by updating each rule’s priority, but serverRules ignores priority, so a reload/config refresh can show a different order than the one actually evaluated. Also, signUpRules is optional in the local type, so the empty-state path can crash before rendering.

Proposed fix
   const serverRules = useMemo(() =>
-    typedEntries(configWithRules.auth.signUpRules).map(([id, rule]) => ({ id, rule })),
+    typedEntries(configWithRules.auth.signUpRules ?? {})
+      .map(([id, rule]) => ({ id, rule }))
+      .sort((a, b) => (b.rule.priority ?? 0) - (a.rule.priority ?? 0)),
     [configWithRules.auth.signUpRules]
   );
🤖 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]/sign-up-rules/page-client.tsx
around lines 1698 - 1703, serverRules currently maps
configWithRules.auth.signUpRules without using each rule's priority and will
crash when signUpRules is undefined; update serverRules (the value used to
render rules) to first default signUpRules to an empty object, then use
typedEntries(configWithRules.auth.signUpRules ?? {}) and sort the resulting
[{id, rule}] array by rule.priority (descending or ascending to match
handleSaveOrder semantics) before returning it; keep defaultAction as is
(configWithRules.auth.signUpRulesDefaultAction ?? 'allow') so empty-state
rendering is safe.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants