Conversation
…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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThis 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
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]
Estimated code review effort🎯 4 (Complex) | ⏱️ ~55 minutes Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ❌ 3❌ Failed checks (2 warnings, 1 inconclusive)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR redesigns the new-project onboarding experience into a multi-step animated wizard ( Confidence Score: 5/5
Important Files Changed
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
|
There was a problem hiding this comment.
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
OnboardingPagelayout with a new progress indicator and cascade animation. - Adds a reusable
StripeWordmarkSVG component and updates the payments onboarding step to use it. - Introduces a dedicated CSS token override (
.auth-preview-host-theme) to make embeddedAuthPagepreviews 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.
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsx (1)
312-319: Addhover:transition-noneto progress dot buttons.The
transition-all duration-300applies a hover-enter transition when hovering over completed steps (thehover:bg-foreground/60change 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
📒 Files selected for processing (4)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsxapps/dashboard/src/app/globals.cssapps/dashboard/src/components/stripe-wordmark.tsxclaude/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.
There was a problem hiding this comment.
🧹 Nitpick comments (4)
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts (1)
566-585: Add a regression test for thetokenStore: nullstartup path.This is the constructor branch that prevents browser-created
tokenStore: nullapps from reachinggetAnalyticsSession(). A browser-like test that instantiates the app withtokenStore: nulland 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.
getStepIndexreturns-1when 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 (
handleTimelineStepClickat line 146 in wizard) check fortargetIndex < 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)afterrouter.pushappears 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
📒 Files selected for processing (11)
apps/backend/src/app/api/latest/internal/payments/setup/route.tsapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/components.tsxapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/content.tsxapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/project-onboarding-wizard.test.tsxapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/project-onboarding-wizard.tsxapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/shared.tsapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.test.tsxapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client.tsxapps/dashboard/src/app/globals.cssclaude/CLAUDE-KNOWLEDGE.mdpackages/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
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (2)
packages/stack-cli/src/lib/auth.ts (1)
30-32: Redundant intersection with{ projectId: string }.Both
ProjectAuthWithRefreshTokenandProjectAuthWithSecretServerKeyalready includeprojectId: 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.
isConfigOverrideassertsvalue is EnvironmentConfigOverrideOverridebut only checks whethervalueis 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:
- Renaming to
isPlainObjectto accurately reflect what it checks, or- 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (18)
apps/backend/prisma/migrations/20260415000000_add_welcome_onboarding_status/migration.sqlapps/backend/prisma/migrations/20260415000000_add_welcome_onboarding_status/tests/welcome-status-allowed.tsapps/backend/prisma/migrations/20260415000001_validate_welcome_onboarding_status/migration.sqlapps/backend/prisma/migrations/20260415000001_validate_welcome_onboarding_status/tests/constraint-is-validated.tsapps/backend/src/app/api/latest/internal/config/override/[level]/route.tsxapps/dashboard/package.jsonapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/components.tsxapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/content.tsxapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/link-existing-onboarding.tsxapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/project-onboarding-wizard.test.tsxapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/project-onboarding-wizard.tsxapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/shared.tsclaude/CLAUDE-KNOWLEDGE.mdpackages/stack-cli/src/commands/config-file.tspackages/stack-cli/src/commands/exec.tspackages/stack-cli/src/lib/app.tspackages/stack-cli/src/lib/auth.tspackages/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
| 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; | ||
| } |
There was a problem hiding this comment.
🧩 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 100Repository: 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 -20Repository: 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.tsxRepository: 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.tsxRepository: 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 -30Repository: 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 -200Repository: 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 -500Repository: 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.tsxRepository: 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.tsxRepository: 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.tsxRepository: 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 -20Repository: 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 -5Repository: 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 -100Repository: 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 15Repository: 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 20Repository: 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.
| 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]); |
There was a problem hiding this comment.
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.
Summary by CodeRabbit
New Features
Tests