Skip to content

Onboarding redo#1308

Merged
N2D4 merged 16 commits intodevfrom
onboarding-redo
Apr 15, 2026
Merged

Onboarding redo#1308
N2D4 merged 16 commits intodevfrom
onboarding-redo

Conversation

@Developing-Gamer
Copy link
Copy Markdown
Contributor

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

Summary by CodeRabbit

  • New Features

    • Added a full multi-step project onboarding flow including a new Welcome slide, configurable timeline, app selection, auth setup, domain/email theme preview, and payments (country selection + Stripe connect).
    • New "Link existing" option to configure via GitHub Actions or local CLI.
    • Visual/UX: onboarding cascade animations and an embedded email theme preview; new Stripe wordmark SVG.
  • Tests

    • Added UI tests covering onboarding components and flows.

…page layout

- Introduced a new CSS class for the AuthPage preview to ensure consistent theming with neutral tokens.
- Updated the onboarding page component to improve layout and user experience, including a shared animated stage wrapper and sticky action buttons for better navigation.
- Added a new Stripe wordmark component for branding consistency.
@Developing-Gamer Developing-Gamer self-assigned this Apr 4, 2026
@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 4, 2026

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

Project Deployment Actions Updated (UTC)
3 Building Building Preview, Comment Apr 15, 2026 4:48am
3-1776188452445-5Jus Canceled Canceled Apr 15, 2026 4:48am
stack-auth-hosted-components Ready Ready Preview, Comment Apr 15, 2026 4:48am
stack-backend Ready Ready Preview, Comment Apr 15, 2026 4:48am
stack-dashboard Ready Ready Preview, Comment Apr 15, 2026 4:48am
stack-demo Ready Ready Preview, Comment Apr 15, 2026 4:48am
stack-docs Ready Ready Preview, Comment Apr 15, 2026 4:48am
stack-preview-backend Ready Ready Preview, Comment Apr 15, 2026 4:48am
stack-preview-dashboard Ready Ready Preview, Comment Apr 15, 2026 4:48am

@Developing-Gamer Developing-Gamer requested a review from N2D4 April 4, 2026 21:23
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 4, 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

This PR splits the monolithic new-project PageClient into modular parts, adds a full multi-step onboarding wizard (including link-existing/GitHub flow), updates schema/migrations to add a "welcome" onboarding status, adjusts CLI auth modes, adds CSS/branding components, and includes tests and client-side GitHub secret handling.

Changes

Cohort / File(s) Summary
PageClient entry
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx
Replaced inline PageClient with re-exports and a default PageClient imported from page-client-parts/content.
Page client tests
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.test.tsx
Added tests for pending-action helpers, OnboardingPage progress/dot behavior, OnboardingAppCard interaction rules, and DomainSetupTransitionState actions.
Onboarding components
apps/dashboard/src/app/.../new-project/page-client-parts/components.tsx
New UI components: OnboardingPage, OnboardingAppCard, DomainSetupTransitionState, WelcomeSlide, OnboardingEmailThemePreview, ModeNotImplementedCard and related types.
Onboarding shared utils
apps/dashboard/src/app/.../new-project/page-client-parts/shared.ts
New shared utilities: sign-in method constants, app ID groupings, timeline builders, pending-action helpers, normalization/validation helpers, and initial-state derivation functions.
Page content & wizard
apps/dashboard/src/app/.../new-project/page-client-parts/content.tsx, .../project-onboarding-wizard.tsx, .../project-onboarding-wizard.test.tsx
New PageClient content and ProjectOnboardingWizard implementing multi-step flow, status-driven transitions, config persistence, API PATCHing, and tests verifying transition behavior.
Link-existing / GitHub flow
apps/dashboard/src/app/.../new-project/page-client-parts/link-existing-onboarding.tsx
Large new component implementing linking via GitHub Actions or local push: repo/branch selection, suggestions, workflow secret encryption (libsodium), commit/dispatch, run monitoring, and polling.
Styling & animation
apps/dashboard/src/app/globals.css
Added .auth-preview-host-theme CSS variables, dark-mode overrides, onboarding-cascade animation, and reduced-motion handling.
Brand SVG
apps/dashboard/src/components/stripe-wordmark.tsx
New accessible Stripe wordmark SVG component using currentColor.
Backend: payments route
apps/backend/src/app/api/latest/internal/payments/setup/route.ts
Return URL now builds /new-project?project_id=... when onboardingStatus is payments_setup; includes dashboard base URL handling.
Backend: config override auth
apps/backend/src/app/api/latest/internal/config/override/[level]/route.tsx
Switched to serverOrHigherAuthTypeSchema and added assertServerAccessAllowed to forbid server auth for non-branch levels.
DB migrations & tests
apps/backend/prisma/migrations/.../migration.sql, .../tests/*.ts
Added welcome to onboardingStatus CHECK constraint (NOT VALID) and validation migration; added pre/post-migration tests ensuring welcome allowed and constraint validated.
CLI: auth & config commands
packages/stack-cli/src/lib/auth.ts, .../lib/app.ts, .../commands/config-file.ts, .../commands/exec.ts
Refactored ProjectAuth into discriminated union (refresh-token vs secret-server-key), updated getAdminProject typing, required refresh-token in several flows, added branch-config-source construction and secret-server-key direct API push path.
Shared schema
packages/stack-shared/src/schema-fields.ts
Added "welcome" to projectOnboardingStatusValues and the derived ProjectOnboardingStatus union.
Docs / knowledge
claude/CLAUDE-KNOWLEDGE.md
Appended onboarding- and GitHub-related Q&A entries and troubleshooting notes.
Dependency
apps/dashboard/package.json
Added libsodium-wrappers@^0.8.2 for client-side encryption of GitHub secrets.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant PageClient
    participant Wizard as ProjectOnboardingWizard
    participant API as Backend API
    participant GitHub
    participant Stripe

    User->>PageClient: Open /new-project (or submit create)
    PageClient->>API: GET /internal/projects (build statuses)
    PageClient->>Wizard: Render wizard with project & status
    loop onboarding steps
        User->>Wizard: Interact (choose apps, auth, email theme, payments)
        Wizard->>API: PATCH /internal/projects/current (set status / update config)
        API-->>Wizard: Ack
        alt Link-existing → GitHub path
            Wizard->>GitHub: Fetch repos/branches, suggest config paths
            Wizard->>GitHub: Upload workflow & secrets (encrypt via libsodium)
            GitHub-->>Wizard: Dispatch run / return run URL
            loop monitor run
                Wizard->>GitHub: Poll run/jobs -> update UI log
            end
        end
        alt Payments
            Wizard->>Stripe: project.app.setupPayments()
            Stripe-->>Wizard: Redirect URL
            Wizard->>User: Navigate to Stripe onboarding
        end
    end
    Wizard->>API: PATCH status=completed
    API-->>Wizard: Completed
    Wizard->>PageClient: onComplete() -> redirect to /projects/[id]
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

Suggested reviewers

  • BilalG1

"🐰
I hopped through code and stitched the trail,
New steps, new flows, a welcome tale.
Apps, auth, emails, payments take flight,
Secrets encrypted, onboarding's bright.
Hop on, dear devs — the project's right!"

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is empty except for an HTML comment directing contributors to read CONTRIBUTING.md; it provides no explanation of the changes, objectives, or implementation details. Add a comprehensive description explaining the refactoring scope, new onboarding components, key behavioral changes, and any migration notes or testing guidance.
Docstring Coverage ⚠️ Warning Docstring coverage is 1.32% 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 'Onboarding redo' is vague and generic; it does not clearly convey the specific changes or scope of the substantial refactoring work. Use a more descriptive title that specifies the main change, such as 'Refactor new-project onboarding wizard into modular components' or 'Extract onboarding UI into page-client-parts modules'.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch onboarding-redo

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 14, 2026

Greptile Summary

This PR redesigns the new-project onboarding experience into a multi-step animated wizard (config_choice → apps_selection → auth_setup → email_theme_setup → [payments_setup] → completed), adds a new Stripe wordmark component, and extends globals.css with ambient-background and Pacifica surface utilities. All remaining findings are P2.

Confidence Score: 5/5

  • Safe to merge; all findings are non-blocking style and UX hardening suggestions.
  • No P0 or P1 issues found. The three P2 findings (static CSS in render, brief double-submit window, stuck spinner on domain_setup failure) are minor quality improvements that do not affect correctness or data integrity.
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx — review inline style and loading-state ordering.

Important Files Changed

Filename Overview
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx Complete redesign of the project onboarding wizard with multi-step animated flow, app/auth/email-theme/payments steps, and new project creation dialog. A few minor issues: inline style block in render, setCreatingProject/setCreatingTeam state set after async entry, and the domain_setup auto-advance has no error-recovery UI.
apps/dashboard/src/app/globals.css Adds --page-ambient CSS variable, onboarding cascade animation, and [data-pacifica-*] surface/border utility classes. Straightforward CSS additions with no issues.
apps/dashboard/src/components/stripe-wordmark.tsx New lightweight SVG component for the Stripe wordmark. Uses currentColor for theme-aware tinting, includes proper ARIA role/label. No issues.
claude/CLAUDE-KNOWLEDGE.md Appends several Q&A entries documenting decisions made during the onboarding redo (spinner-only loading state, useEffect redirect, status validation, domain_setup skip). Documentation-only, no code issues.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A([User visits /new-project]) --> B{selectedProjectId?}
    B -- No --> C[Create Project Dialog]
    C --> D[user.createProject onboardingStatus=config_choice]
    D --> E{redirect_to_neon / confirm?}
    E -- Yes --> F[Navigate to integration confirm]
    E -- No --> G[updateSearchParams project_id=newProject.id]

    B -- Yes --> H{loadingStatuses?}
    H -- Yes --> I([Spinner])
    H -- No --> J{selectedProjectStatus}

    J -- completed --> K([Spinner + useEffect router.replace dashboard])
    J -- null --> L([throw Error])
    J -- other --> M[ProjectOnboardingWizard]

    M --> N{status}
    N -- config_choice --> O[Config step: Create New / Link Existing]
    O -- Create New --> P[apps_selection]
    O -- Link Existing --> Q[ModeNotImplementedCard]

    P --> R[auth_setup]
    R --> S[email_theme_setup]
    S --> T{includePayments?}
    T -- Yes --> U[payments_setup]
    T -- No --> V[setStatus completed + onComplete]

    U --> W{Connect Stripe / Do Later}
    W -- Connect Stripe --> X[setupPayments + window.location redirect]
    W -- Do Later --> V

    V --> Y([Navigate to /projects/id])

    N -- domain_setup --> Z([Spinner + useEffect auto-advance to email_theme_setup])
    Z --> S
Loading

Comments Outside Diff (2)

  1. apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx, line 1518-1530 (link)

    P2 Loading guard set after synchronous validation, leaving a brief double-submit window

    setCreatingProject(true) is only called after the validation checks succeed. Between the initial click and the React state update that disables the button, a second click can pass validation and start a second user.createProject(...) call, potentially creating duplicate projects.

    Moving the state setter before runAsynchronouslyWithAlert (or before the validation) closes this window:

    The same pattern applies to setCreatingTeam(true) at the team creation handler (line ~1620).

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx
    Line: 1518-1530
    
    Comment:
    **Loading guard set after synchronous validation, leaving a brief double-submit window**
    
    `setCreatingProject(true)` is only called after the validation checks succeed. Between the initial click and the React state update that disables the button, a second click can pass validation and start a second `user.createProject(...)` call, potentially creating duplicate projects.
    
    Moving the state setter before `runAsynchronouslyWithAlert` (or before the validation) closes this window:
    
    
    
    The same pattern applies to `setCreatingTeam(true)` at the team creation handler (line ~1620).
    
    How can I resolve this? If you propose a fix, please make it concise.
  2. apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx, line 1011-1017 (link)

    P2 Stuck spinner on domain_setup auto-advance failure

    When status === "domain_setup", the component renders a full-screen spinner and a useEffect fires setStatus("email_theme_setup"). If that PATCH request fails (network error, server error), runAsynchronouslyWithAlert will surface an alert — but the component continues to show the spinner with no way for the user to retry or navigate away. Consider rendering an error state or adding a retry button when the auto-advance fails.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx
    Line: 1011-1017
    
    Comment:
    **Stuck spinner on `domain_setup` auto-advance failure**
    
    When `status === "domain_setup"`, the component renders a full-screen spinner and a `useEffect` fires `setStatus("email_theme_setup")`. If that PATCH request fails (network error, server error), `runAsynchronouslyWithAlert` will surface an alert — but the component continues to show the spinner with no way for the user to retry or navigate away. Consider rendering an error state or adding a retry button when the auto-advance fails.
    
    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)/(outside-dashboard)/new-project/page-client.tsx
Line: 327-349

Comment:
**Static keyframe CSS injected on every render**

The `<style>` block inside `OnboardingPage` contains static `@keyframes` and `.onboarding-cascade` rules that are identical on every render. React re-inserts this element on each mount, and the CSS text stays in memory alongside each component instance. These rules belong in `globals.css` (or a separate stylesheet import) where they are parsed once and deduped by the browser.

```suggestion
      `}</style>
    </div>
  );
}
```

Move the keyframe and `.onboarding-cascade` rules to `globals.css`, keeping only the `@media (prefers-reduced-motion)` override if needed.

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)/(outside-dashboard)/new-project/page-client.tsx
Line: 1518-1530

Comment:
**Loading guard set after synchronous validation, leaving a brief double-submit window**

`setCreatingProject(true)` is only called after the validation checks succeed. Between the initial click and the React state update that disables the button, a second click can pass validation and start a second `user.createProject(...)` call, potentially creating duplicate projects.

Moving the state setter before `runAsynchronouslyWithAlert` (or before the validation) closes this window:

```suggestion
              onClick={() => {
                setCreatingProject(true);
                runAsynchronouslyWithAlert(async () => {
```

The same pattern applies to `setCreatingTeam(true)` at the team creation handler (line ~1620).

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)/(outside-dashboard)/new-project/page-client.tsx
Line: 1011-1017

Comment:
**Stuck spinner on `domain_setup` auto-advance failure**

When `status === "domain_setup"`, the component renders a full-screen spinner and a `useEffect` fires `setStatus("email_theme_setup")`. If that PATCH request fails (network error, server error), `runAsynchronouslyWithAlert` will surface an alert — but the component continues to show the spinner with no way for the user to retry or navigate away. Consider rendering an error state or adding a retry button when the auto-advance fails.

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

Reviews (1): Last reviewed commit: "Merge branch 'dev' into onboarding-redo" | 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

Redesigns the dashboard’s “new project” onboarding experience to use a calmer, more centered step layout and improved embedded previews, while updating supporting UI/theming assets.

Changes:

  • Refactors the onboarding wizard UI into a shared OnboardingPage layout with a new progress indicator and cascade animation.
  • Adds a reusable StripeWordmark SVG component and updates the payments onboarding step to use it.
  • Introduces a dedicated CSS token override (.auth-preview-host-theme) to make embedded AuthPage previews match hosted handler styling.

Reviewed changes

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

File Description
claude/CLAUDE-KNOWLEDGE.md Adds onboarding UI guidance notes (currently slightly out of sync with implementation).
apps/dashboard/src/components/stripe-wordmark.tsx New Stripe wordmark SVG component for consistent branding in UI.
apps/dashboard/src/app/globals.css Adds .auth-preview-host-theme token overrides for embedded AuthPage previews (light/dark).
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx Major onboarding wizard UI refactor + new auth preview theming wrapper + payments step UI updates.

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

Comment thread claude/CLAUDE-KNOWLEDGE.md Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx (1)

312-319: Add hover:transition-none to progress dot buttons.

The transition-all duration-300 applies a hover-enter transition when hovering over completed steps (the hover:bg-foreground/60 change will animate in). Per coding guidelines, avoid hover-enter transitions and use only hover-exit transitions.

♻️ Suggested fix
 className={cn(
-  "rounded-full transition-all duration-300",
+  "rounded-full transition-all duration-300 hover:transition-none",
   isCurrent
     ? "h-[6px] w-5 bg-foreground"
     : isComplete
       ? "h-[6px] w-[6px] cursor-pointer bg-foreground/40 hover:bg-foreground/60"
       : "h-[6px] w-[6px] cursor-default bg-foreground/20",
 )}

As per coding guidelines: "When creating hover transitions, avoid hover-enter transitions and use only hover-exit 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)/(outside-dashboard)/new-project/page-client.tsx
around lines 312 - 319, The progress-dot class currently uses "transition-all
duration-300" which causes a hover-enter animation; update the className for the
progress dot JSX (the element that uses isCurrent and isComplete to choose
classes) to use a transition that only animates exits and disable hover enter by
adding "hover:transition-none" (e.g., use transition-colors duration-300 plus
hover:transition-none) so the hover:bg-foreground/60 change does not animate on
enter but still transitions on exit.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx:
- Around line 312-319: The progress-dot class currently uses "transition-all
duration-300" which causes a hover-enter animation; update the className for the
progress dot JSX (the element that uses isCurrent and isComplete to choose
classes) to use a transition that only animates exits and disable hover enter by
adding "hover:transition-none" (e.g., use transition-colors duration-300 plus
hover:transition-none) so the hover:bg-foreground/60 change does not animate on
enter but still transitions on exit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e5edf16c-1463-4cdd-a083-bd10e991f4ef

📥 Commits

Reviewing files that changed from the base of the PR and between c7b6b59 and cf00aa2.

📒 Files selected for processing (4)
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx
  • apps/dashboard/src/app/globals.css
  • apps/dashboard/src/components/stripe-wordmark.tsx
  • claude/CLAUDE-KNOWLEDGE.md

- Split the `page-client.tsx` into multiple files for better organization, including `content.tsx`, `components.tsx`, `project-onboarding-wizard.tsx`, and `shared.ts`.
- Introduced a new `OnboardingPage` component to manage the onboarding steps and UI.
- Enhanced the `ProjectOnboardingWizard` to handle project setup more effectively.
- Updated shared utilities for onboarding processes, including sign-in methods and app management.
- Improved code readability and maintainability by modularizing the onboarding logic.
…nalize onboarding when Stripe setup is successful.

- Added logic to handle the onboarding status and Stripe account information more effectively.
- Introduced a new test file for the `ProjectOnboardingWizard` to ensure proper functionality during the onboarding process.
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.

🧹 Nitpick comments (4)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)

566-585: Add a regression test for the tokenStore: null startup path.

This is the constructor branch that prevents browser-created tokenStore: null apps from reaching getAnalyticsSession(). A browser-like test that instantiates the app with tokenStore: null and asserts both trackers stay disabled would make this much harder to regress.

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

In `@packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts`
around lines 566 - 585, Add a regression test that ensures the constructor path
for browser-created apps with tokenStore: null does not create or start
trackers: instantiate the client app (the class in client-app-impl.ts / its
constructor) in a browser-like test environment (mock isBrowserLike() to true)
with options tokenStore: null and analyticsOptions.replays.enabled set to true,
then assert that this._sessionRecorder and this._eventTracker are not
created/started (or that _hasPersistentTokenStore() is false) to prevent calling
getAnalyticsSession()—this verifies the guarded branches around SessionRecorder
and EventTracker remain disabled for tokenStore: null.
apps/backend/src/app/api/latest/internal/payments/setup/route.ts (1)

35-41: Minor indentation inconsistency in URL construction.

Line 38 has extra indentation compared to the surrounding IIFE structure. This appears to be a formatting oversight.

🔧 Suggested fix
     const returnToUrl = project?.onboardingStatus === "payments_setup"
       ? (() => {
         const onboardingUrl = new URL("/new-project", dashboardBaseUrl);
-          onboardingUrl.searchParams.set("project_id", auth.project.id);
+        onboardingUrl.searchParams.set("project_id", auth.project.id);
           return onboardingUrl.toString();
       })()
       : new URL(`/projects/${encodeURIComponent(auth.project.id)}/payments`, dashboardBaseUrl).toString();
🤖 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/payments/setup/route.ts` around
lines 35 - 41, The returnToUrl construction has inconsistent indentation inside
the IIFE creating onboardingUrl; fix the formatting in the block that builds
onboardingUrl (the IIFE used when project?.onboardingStatus ===
"payments_setup") so the onboardingUrl variable and its searchParams.set line
match the surrounding indentation style used for the IIFE and the alternate
branch; locate the returnToUrl declaration and normalize indentation for
onboardingUrl, onboardingUrl.searchParams.set("project_id", auth.project.id),
and return onboardingUrl.toString() to align with the opening of the IIFE and
the ternary expression using dashboardBaseUrl.
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/shared.ts (1)

201-203: Consider handling the case when step is not found.

getStepIndex returns -1 when the step ID is not found in the timeline. Callers should handle this case to avoid potential issues with negative index usage.

The current implementation is acceptable since callers (handleTimelineStepClick at line 146 in wizard) check for targetIndex < 0, but consider documenting this behavior or providing a more explicit return type.

🤖 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)/new-project/page-client-parts/shared.ts
around lines 201 - 203, getStepIndex currently returns -1 when a step isn't
found which is implicit and error-prone; change its signature from returning
number to number | null and return null instead of -1 when no TimelineStep with
the given ProjectOnboardingStatus is found, then update callers (e.g.
handleTimelineStepClick) to check for null (targetIndex === null) rather than <
0; also update any types/usages that assume a plain number to handle the
nullable return.
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/content.tsx (1)

213-219: Consider removing hardcoded wait after navigation.

The wait(2000) after router.push appears to be a workaround. If the intent is to prevent double-clicks during navigation, the button's loading state or disabled prop would be more appropriate.

🔧 Suggested simplification
             <Button onClick={async () => {
               router.push("/projects");
-              await wait(2000);
             }}>
               Go to Projects
             </Button>

If double-click prevention is needed, consider disabling the button after the first click instead.

🤖 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)/new-project/page-client-parts/content.tsx
around lines 213 - 219, Remove the hardcoded wait(2000) in the Button onClick
handler and replace the workaround with a proper navigation-state guard: track a
local boolean (e.g., isNavigating) in the component, set isNavigating = true
when the Button's onClick calls router.push("/projects"), and pass that boolean
to the Button's disabled or loading prop to prevent double-clicks; ensure you
still call router.push from the same onClick and clear/reset state only if
needed after navigation completes or on error. Reference: the Button component,
the router.push("/projects") call, and the wait function in the current onClick
handler.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@apps/backend/src/app/api/latest/internal/payments/setup/route.ts`:
- Around line 35-41: The returnToUrl construction has inconsistent indentation
inside the IIFE creating onboardingUrl; fix the formatting in the block that
builds onboardingUrl (the IIFE used when project?.onboardingStatus ===
"payments_setup") so the onboardingUrl variable and its searchParams.set line
match the surrounding indentation style used for the IIFE and the alternate
branch; locate the returnToUrl declaration and normalize indentation for
onboardingUrl, onboardingUrl.searchParams.set("project_id", auth.project.id),
and return onboardingUrl.toString() to align with the opening of the IIFE and
the ternary expression using dashboardBaseUrl.

In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/content.tsx:
- Around line 213-219: Remove the hardcoded wait(2000) in the Button onClick
handler and replace the workaround with a proper navigation-state guard: track a
local boolean (e.g., isNavigating) in the component, set isNavigating = true
when the Button's onClick calls router.push("/projects"), and pass that boolean
to the Button's disabled or loading prop to prevent double-clicks; ensure you
still call router.push from the same onClick and clear/reset state only if
needed after navigation completes or on error. Reference: the Button component,
the router.push("/projects") call, and the wait function in the current onClick
handler.

In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/shared.ts:
- Around line 201-203: getStepIndex currently returns -1 when a step isn't found
which is implicit and error-prone; change its signature from returning number to
number | null and return null instead of -1 when no TimelineStep with the given
ProjectOnboardingStatus is found, then update callers (e.g.
handleTimelineStepClick) to check for null (targetIndex === null) rather than <
0; also update any types/usages that assume a plain number to handle the
nullable return.

In `@packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts`:
- Around line 566-585: Add a regression test that ensures the constructor path
for browser-created apps with tokenStore: null does not create or start
trackers: instantiate the client app (the class in client-app-impl.ts / its
constructor) in a browser-like test environment (mock isBrowserLike() to true)
with options tokenStore: null and analyticsOptions.replays.enabled set to true,
then assert that this._sessionRecorder and this._eventTracker are not
created/started (or that _hasPersistentTokenStore() is false) to prevent calling
getAnalyticsSession()—this verifies the guarded branches around SessionRecorder
and EventTracker remain disabled for tokenStore: null.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: deb64f5e-744e-4228-896d-37ca10b831a0

📥 Commits

Reviewing files that changed from the base of the PR and between d45afdd and 63c14c2.

📒 Files selected for processing (11)
  • apps/backend/src/app/api/latest/internal/payments/setup/route.ts
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/components.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/content.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/project-onboarding-wizard.test.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/project-onboarding-wizard.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/shared.ts
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.test.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx
  • apps/dashboard/src/app/globals.css
  • claude/CLAUDE-KNOWLEDGE.md
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • claude/CLAUDE-KNOWLEDGE.md
  • apps/dashboard/src/app/globals.css

Copy link
Copy Markdown

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

TypeScript build failure due to calling potentially undefined reset() and accessing potentially undefined error in ErrorBoundary's errorComponent callback

Fix on Vercel

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
packages/stack-cli/src/lib/auth.ts (1)

30-32: Redundant intersection with { projectId: string }.

Both ProjectAuthWithRefreshToken and ProjectAuthWithSecretServerKey already include projectId: string, so the intersection is unnecessary.

♻️ Suggested simplification
-export type ProjectAuth = (ProjectAuthWithRefreshToken | ProjectAuthWithSecretServerKey) & {
-  projectId: string,
-};
+export type ProjectAuth = ProjectAuthWithRefreshToken | ProjectAuthWithSecretServerKey;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/stack-cli/src/lib/auth.ts` around lines 30 - 32, The type alias
ProjectAuth redundantly intersects { projectId: string } even though both
constituent types ProjectAuthWithRefreshToken and ProjectAuthWithSecretServerKey
already contain projectId; remove the trailing intersection so ProjectAuth is
simply the union of ProjectAuthWithRefreshToken | ProjectAuthWithSecretServerKey
(retain the export and exact name ProjectAuth) to simplify the type without
changing semantics.
packages/stack-cli/src/commands/config-file.ts (1)

10-16: Type guard only validates plain object structure, not schema compliance.

isConfigOverride asserts value is EnvironmentConfigOverrideOverride but only checks whether value is a plain object. It doesn't validate that the object conforms to the actual config override schema, which could allow invalid config structures to pass through with incorrect type narrowing.

Consider either:

  1. Renaming to isPlainObject to accurately reflect what it checks, or
  2. Adding actual schema validation if runtime type safety is needed
♻️ Option 1: Rename to reflect actual behavior
-function isConfigOverride(value: unknown): value is EnvironmentConfigOverrideOverride {
+function isPlainObject(value: unknown): value is Record<string, unknown> {
   if (value === null || typeof value !== "object" || Array.isArray(value)) {
     return false;
   }
   const prototype = Object.getPrototypeOf(value);
   return prototype === Object.prototype || prototype === null;
 }

Then cast explicitly at the usage site if the caller assumes the schema is correct.

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

In `@packages/stack-cli/src/commands/config-file.ts` around lines 10 - 16, The
function isConfigOverride currently only checks for a plain object (using
Object.getPrototypeOf and Array.isArray) but claims the type guard
EnvironmentConfigOverrideOverride; either rename isConfigOverride to
isPlainObject to reflect its actual behavior and remove the misleading type
assertion (then cast at call sites where schema is assumed), or implement real
runtime schema validation inside isConfigOverride (e.g., check required keys and
value types of the EnvironmentConfigOverrideOverride shape) so the type guard
truly guarantees the config shape; update call sites that rely on
isConfigOverride accordingly (functions/methods referencing isConfigOverride or
EnvironmentConfigOverrideOverride).
🤖 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/backend/prisma/migrations/20260415000000_add_welcome_onboarding_status/tests/welcome-status-allowed.ts`:
- Around line 24-44: The postMigration test currently only updates an existing
Project to onboardingStatus 'welcome' but needs to also verify the INSERT path;
in postMigration (using the same Sql and ctx from preMigration) add an INSERT
into "Project" setting onboardingStatus = 'welcome' (use ctx.projectId or insert
a new row as appropriate) and assert the row exists and onboardingStatus is
'welcome', then add a separate INSERT attempt with onboardingStatus =
'invalid_status' and assert it rejects/toThrow with the same
Project_onboardingStatus_valid constraint regex; keep the checks consistent with
the existing update tests (expect length/field checks and rejects.toThrow).

In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/link-existing-onboarding.tsx:
- Around line 399-424: The buildWorkflowYaml function currently injects
unescaped user inputs (branch, configPath) directly into YAML and a shell run
line; fix by JSON-encoding those scalars when embedding into the YAML (use
JSON.stringify-equivalent to produce safe YAML scalar quoting for branch and
configPath) and stop interpolating configPath into the run string: instead pass
the config path into the GitHub Action step via an env entry (e.g. set
CONFIG_PATH to the encoded configPath) and reference that env variable in the
run command (e.g. use "$CONFIG_PATH"); update the references to
WORKFLOW_FILE_PATH and secret names unchanged but ensure you replace direct
"${branch}" and "${configPath}" insertions in buildWorkflowYaml with the encoded
values.
- Around line 1552-1570: The code currently starts pollers during render; move
the local and GitHub auto-poller initialization into two separate useEffect
hooks: one that watches [step, project.id] and uses localAutoMonitoringKeyRef
and runAsynchronouslyWithAlert(startLocalPushMonitoring) to start/avoid
duplicate runs, and returns a cleanup that increments the run-id/cancellation
ref (localAutoMonitoringKeyRef) to stop the poller; and another effect that
watches [step, selectedRepositoryFullName, selectedBranch] and similarly uses
githubLogsAutoPollingKeyRef with
runAsynchronouslyWithAlert(startGithubLogsMonitoring) and a cleanup that
increments/nulls the githubLogsAutoPollingKeyRef to cancel. Ensure each effect
only starts the poller when the matching step is active and dependency arrays
include the referenced values and functions.

In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/project-onboarding-wizard.tsx:
- Around line 104-139: The early-return guard inside useEffect (the if
(previousProjectId.current === project.id) { return; } check) prevents the
effect from running when project.id is unchanged, causing updates from
completeConfig or status to be ignored; remove that early return so the effect
always runs when its deps change (keep previousProjectId.current = project.id
but do not return), or alternatively split initialization into two effects (one
that runs only on project.id changes and one that runs on completeConfig/status)
so deriveInitialApps, deriveInitialSignInMethods, setSelectedApps,
setSignInMethods, setSelectedEmailThemeId, etc., are refreshed for the same
project when completeConfig or status update.

---

Nitpick comments:
In `@packages/stack-cli/src/commands/config-file.ts`:
- Around line 10-16: The function isConfigOverride currently only checks for a
plain object (using Object.getPrototypeOf and Array.isArray) but claims the type
guard EnvironmentConfigOverrideOverride; either rename isConfigOverride to
isPlainObject to reflect its actual behavior and remove the misleading type
assertion (then cast at call sites where schema is assumed), or implement real
runtime schema validation inside isConfigOverride (e.g., check required keys and
value types of the EnvironmentConfigOverrideOverride shape) so the type guard
truly guarantees the config shape; update call sites that rely on
isConfigOverride accordingly (functions/methods referencing isConfigOverride or
EnvironmentConfigOverrideOverride).

In `@packages/stack-cli/src/lib/auth.ts`:
- Around line 30-32: The type alias ProjectAuth redundantly intersects {
projectId: string } even though both constituent types
ProjectAuthWithRefreshToken and ProjectAuthWithSecretServerKey already contain
projectId; remove the trailing intersection so ProjectAuth is simply the union
of ProjectAuthWithRefreshToken | ProjectAuthWithSecretServerKey (retain the
export and exact name ProjectAuth) to simplify the type without changing
semantics.
🪄 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: dc115899-5b60-4a7a-82f2-4fcb6f08f468

📥 Commits

Reviewing files that changed from the base of the PR and between 63c14c2 and 07c07a2.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (18)
  • apps/backend/prisma/migrations/20260415000000_add_welcome_onboarding_status/migration.sql
  • apps/backend/prisma/migrations/20260415000000_add_welcome_onboarding_status/tests/welcome-status-allowed.ts
  • apps/backend/prisma/migrations/20260415000001_validate_welcome_onboarding_status/migration.sql
  • apps/backend/prisma/migrations/20260415000001_validate_welcome_onboarding_status/tests/constraint-is-validated.ts
  • apps/backend/src/app/api/latest/internal/config/override/[level]/route.tsx
  • apps/dashboard/package.json
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/components.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/content.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/link-existing-onboarding.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/project-onboarding-wizard.test.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/project-onboarding-wizard.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/shared.ts
  • claude/CLAUDE-KNOWLEDGE.md
  • packages/stack-cli/src/commands/config-file.ts
  • packages/stack-cli/src/commands/exec.ts
  • packages/stack-cli/src/lib/app.ts
  • packages/stack-cli/src/lib/auth.ts
  • packages/stack-shared/src/schema-fields.ts
✅ Files skipped from review due to trivial changes (4)
  • apps/backend/prisma/migrations/20260415000001_validate_welcome_onboarding_status/migration.sql
  • apps/dashboard/package.json
  • apps/backend/prisma/migrations/20260415000001_validate_welcome_onboarding_status/tests/constraint-is-validated.ts
  • apps/backend/prisma/migrations/20260415000000_add_welcome_onboarding_status/migration.sql
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/project-onboarding-wizard.test.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/shared.ts

Comment on lines +1552 to +1570
if (step === "local") {
const localMonitoringKey = project.id;
if (localAutoMonitoringKeyRef.current !== localMonitoringKey) {
localAutoMonitoringKeyRef.current = localMonitoringKey;
runAsynchronouslyWithAlert(startLocalPushMonitoring);
}
} else if (localAutoMonitoringKeyRef.current !== null) {
localAutoMonitoringKeyRef.current = null;
}

if (step === "github-logs") {
const nextAutoPollingKey = `${selectedRepositoryFullName}:${selectedBranch}`;
if (githubLogsAutoPollingKeyRef.current !== nextAutoPollingKey) {
githubLogsAutoPollingKeyRef.current = nextAutoPollingKey;
runAsynchronouslyWithAlert(startGithubLogsMonitoring);
}
} else if (githubLogsAutoPollingKeyRef.current !== null) {
githubLogsAutoPollingKeyRef.current = null;
}
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

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the file and the specific lines mentioned
cat -n apps/dashboard/src/app/\(main\)/\(protected\)/\(outside-dashboard\)/new-project/page-client-parts/link-existing-onboarding.tsx | sed -n '1540,1580p'

Repository: stack-auth/stack-auth

Length of output: 1695


🏁 Script executed:

# Get more context - look at the beginning of this component function
# First find where this component function starts
head -n 1551 apps/dashboard/src/app/\(main\)/\(protected\)/\(outside-dashboard\)/new-project/page-client-parts/link-existing-onboarding.tsx | tail -n 100

Repository: stack-auth/stack-auth

Length of output: 4059


🏁 Script executed:

# Let's search for the function signature and structure
# Find the component function definition
rg -n "export.*function\|const.*=\s*\(" apps/dashboard/src/app/\(main\)/\(protected\)/\(outside-dashboard\)/new-project/page-client-parts/link-existing-onboarding.tsx | head -20

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

# Let's look at the beginning of the file to understand structure
head -n 150 apps/dashboard/src/app/\(main\)/\(protected\)/\(outside-dashboard\)/new-project/page-client-parts/link-existing-onboarding.tsx

Repository: stack-auth/stack-auth

Length of output: 4882


🏁 Script executed:

# Let's search for useEffect in the file to see if it's already being used
rg -n "useEffect" apps/dashboard/src/app/\(main\)/\(protected\)/\(outside-dashboard\)/new-project/page-client-parts/link-existing-onboarding.tsx

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

# Let's find where the main component function starts by looking for export statements
# and searching for where this code block appears
rg -n "export\|function LinkExisting\|const.*=" apps/dashboard/src/app/\(main\)/\(protected\)/\(outside-dashboard\)/new-project/page-client-parts/link-existing-onboarding.tsx | grep -E "^[0-9]+:" | head -30

Repository: stack-auth/stack-auth

Length of output: 47


🏁 Script executed:

# Let's look at a wider context around the problem lines to understand function scope
sed -n '1400,1600p' apps/dashboard/src/app/\(main\)/\(protected\)/\(outside-dashboard\)/new-project/page-client-parts/link-existing-onboarding.tsx | head -200

Repository: stack-auth/stack-auth

Length of output: 7433


🏁 Script executed:

# Let's find where the main component function starts and its state declarations
# Search backwards from line 1552 to find the function definition
sed -n '200,1000p' apps/dashboard/src/app/\(main\)/\(protected\)/\(outside-dashboard\)/new-project/page-client-parts/link-existing-onboarding.tsx | tail -500

Repository: stack-auth/stack-auth

Length of output: 18821


🏁 Script executed:

# Find the component function definition by searching for "export" or "function"
# Look around line 200-400 where main component likely starts
sed -n '180,350p' apps/dashboard/src/app/\(main\)/\(protected\)/\(outside-dashboard\)/new-project/page-client-parts/link-existing-onboarding.tsx

Repository: stack-auth/stack-auth

Length of output: 4699


🏁 Script executed:

# Search for the main component export
rg -n "^export|^function LinkExisting|^const LinkExisting" apps/dashboard/src/app/\(main\)/\(protected\)/\(outside-dashboard\)/new-project/page-client-parts/link-existing-onboarding.tsx

Repository: stack-auth/stack-auth

Length of output: 123


🏁 Script executed:

# Look at the component function starting at line 448
sed -n '448,650p' apps/dashboard/src/app/\(main\)/\(protected\)/\(outside-dashboard\)/new-project/page-client-parts/link-existing-onboarding.tsx

Repository: stack-auth/stack-auth

Length of output: 9698


🏁 Script executed:

# Let's check if runAsynchronouslyWithAlert has any built-in cancellation or cleanup
rg -n "runAsynchronouslyWithAlert|runAsynchronously" apps/dashboard/src/app/\(main\)/\(protected\)/\(outside-dashboard\)/new-project/page-client-parts/link-existing-onboarding.tsx | head -20

Repository: stack-auth/stack-auth

Length of output: 840


🏁 Script executed:

# Search for the definition of runAsynchronouslyWithAlert
find . -name "*.ts" -o -name "*.tsx" | xargs grep -l "export.*runAsynchronouslyWithAlert\|function runAsynchronouslyWithAlert" | head -5

Repository: stack-auth/stack-auth

Length of output: 111


🏁 Script executed:

# Check the implementation of runAsynchronouslyWithAlert
cat -n ./packages/stack-shared/src/utils/promises.tsx | head -100

Repository: stack-auth/stack-auth

Length of output: 4301


🏁 Script executed:

# Search for runAsynchronouslyWithAlert definition
rg -n "runAsynchronouslyWithAlert" ./packages/stack-shared/src/utils/promises.tsx -A 15

Repository: stack-auth/stack-auth

Length of output: 1919


🏁 Script executed:

# Find startLocalPushMonitoring and startGithubLogsMonitoring
rg -n "const startLocalPushMonitoring|const startGithubLogsMonitoring" apps/dashboard/src/app/\(main\)/\(protected\)/\(outside-dashboard\)/new-project/page-client-parts/link-existing-onboarding.tsx -A 20

Repository: stack-auth/stack-auth

Length of output: 1304


Move auto-poller initialization to useEffect with proper dependency arrays and cleanup.

Lines 1552-1570 call runAsynchronouslyWithAlert(...) during render, which violates React's rules of hooks and causes issues in React Strict Mode. During double-mount in development, this can launch duplicate pollers. While the code has ref-based cancellation logic, it should not rely on that—side effects must be in useEffect.

Move the monitoring logic into two separate useEffect hooks (one for local monitoring, one for GitHub logs monitoring) with appropriate dependencies and cleanup functions that increment the run ID refs to signal cancellation.

🤖 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)/new-project/page-client-parts/link-existing-onboarding.tsx
around lines 1552 - 1570, The code currently starts pollers during render; move
the local and GitHub auto-poller initialization into two separate useEffect
hooks: one that watches [step, project.id] and uses localAutoMonitoringKeyRef
and runAsynchronouslyWithAlert(startLocalPushMonitoring) to start/avoid
duplicate runs, and returns a cleanup that increments the run-id/cancellation
ref (localAutoMonitoringKeyRef) to stop the poller; and another effect that
watches [step, selectedRepositoryFullName, selectedBranch] and similarly uses
githubLogsAutoPollingKeyRef with
runAsynchronouslyWithAlert(startGithubLogsMonitoring) and a cleanup that
increments/nulls the githubLogsAutoPollingKeyRef to cancel. Ensure each effect
only starts the poller when the matching step is active and dependency arrays
include the referenced values and functions.

Comment on lines +104 to +139
useEffect(() => {
if (previousProjectId.current === project.id) {
return;
}
previousProjectId.current = project.id;

setSelectedApps(deriveInitialApps(completeConfig, status));
setSignInMethods(deriveInitialSignInMethods(project, status));

const trustedDomains = Object.values(completeConfig.domains.trustedDomains)
.filter((entry) => entry.baseUrl != null)
.map((entry) => ({ baseUrl: entry.baseUrl, handlerPath: entry.handlerPath }));

if (trustedDomains[0]) {
const trustedDomainEntry = trustedDomains[0];
if (trustedDomainEntry.baseUrl == null) {
throw new Error("Invariant violated: trusted domain entry is missing a baseUrl.");
}
setTrustedDomain(trustedDomainEntry.baseUrl);
setDomainHandlerPath(trustedDomainEntry.handlerPath);
} else {
setTrustedDomain("");
setDomainHandlerPath("/handler");
}

const serverConfig = completeConfig.emails.server;
setManagedSubdomain(serverConfig.managedSubdomain ?? "");
setManagedSenderLocalPart(serverConfig.managedSenderLocalPart ?? "");
setSelectedEmailThemeId(completeConfig.emails.selectedThemeId);
setManagedDomainSetupStatus(null);
setSelectedConfigChoice("create-new");
setAuthSetupMobileTab("methods");
setDomainSetupAutoAdvanceError(null);
setDomainSetupAutoAdvancing(false);
paymentsAutoCompletingRef.current = false;
}, [completeConfig, project, project.id, status]);
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

This rehydration guard freezes same-project updates.

Line 105 exits whenever project.id is unchanged, so later project.useConfig() or status updates for the same project never refresh selectedApps, signInMethods, or selectedEmailThemeId. Resuming onboarding can therefore show stale choices and overwrite newer config.

🤖 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)/new-project/page-client-parts/project-onboarding-wizard.tsx
around lines 104 - 139, The early-return guard inside useEffect (the if
(previousProjectId.current === project.id) { return; } check) prevents the
effect from running when project.id is unchanged, causing updates from
completeConfig or status to be ignored; remove that early return so the effect
always runs when its deps change (keep previousProjectId.current = project.id
but do not return), or alternatively split initialization into two effects (one
that runs only on project.id changes and one that runs on completeConfig/status)
so deriveInitialApps, deriveInitialSignInMethods, setSelectedApps,
setSignInMethods, setSelectedEmailThemeId, etc., are refreshed for the same
project when completeConfig or status update.

@N2D4 N2D4 merged commit 654c97c into dev Apr 15, 2026
29 of 32 checks passed
@N2D4 N2D4 deleted the onboarding-redo branch April 15, 2026 16:35
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.

3 participants