Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughThis PR adds a preview mode toggled by NEXT_PUBLIC_STACK_IS_PREVIEW, introduces a Walkthrough system (provider, engine, overlay, cursor, steps), moves dummy-project seeding into a new seed-dummy-data module, and adds preview-mode guards/short-circuits across backend routes and dashboard UI/flows. Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant WalkthroughProvider
participant WalkthroughEngine
participant DOM as TargetDOM
participant WalkthroughOverlay
User->>WalkthroughProvider: Page load
alt NEXT_PUBLIC_STACK_IS_PREVIEW == "true"
WalkthroughProvider->>WalkthroughEngine: mount & start
WalkthroughEngine->>DOM: query element by data-walkthrough
DOM-->>WalkthroughEngine: return element & rect
WalkthroughEngine->>WalkthroughOverlay: set step, spotlight, cursor
WalkthroughOverlay-->>User: render spotlight + cursor
loop per step
WalkthroughEngine->>WalkthroughEngine: animate cursor & monitor target
WalkthroughOverlay->>User: handle click/escape -> onStop
WalkthroughEngine->>DOM: optionally open CmdK / navigate
end
WalkthroughEngine-->>User: complete
else
WalkthroughProvider->>User: render children normally
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 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 docstrings
🧪 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 introduces a "preview mode" for the Stack Auth dashboard, enabling an embeddable, read-only demo experience gated behind Confidence Score: 5/5Safe to merge; all remaining findings are P2 style suggestions with no impact on correctness or security given the preview-only context. No P0 or P1 issues found. The preview flag is consistently checked server-side before any sensitive operation (project creation, webhooks, Stripe calls). Dummy data is fully isolated. The three P2 findings (missing postMessage origin check, non-deterministic findFirst, NEXT_PUBLIC_ prefix on a server script) are all minor best-practice improvements that do not affect correctness in the preview use case. apps/dashboard/src/components/walkthrough/walkthrough-provider.tsx — postMessage origin validation; apps/backend/src/app/api/latest/internal/preview/create-project/route.tsx — findFirst ordering. Important Files Changed
Sequence DiagramsequenceDiagram
participant Parent as Parent Page (Marketing site)
participant Dashboard as Dashboard (Preview iframe)
participant Backend as Backend API
participant DB as Database
Parent->>Dashboard: Embeds in iframe (X-Frame-Options removed)
Dashboard->>Dashboard: layout-client: auto-login with localStorage credentials
Dashboard->>Backend: signUpWithCredential / signInWithCredential
Backend-->>Dashboard: session (memory tokenStore)
Dashboard->>Dashboard: page.tsx: no owned projects → render PreviewProjectRedirect
Dashboard->>Backend: POST /internal/preview/create-project
Backend->>DB: teamMember.findFirst (user's team)
Backend->>DB: seedDummyProject(ownerTeamId)
DB-->>Backend: projectId
Backend-->>Dashboard: { project_id }
Dashboard->>Dashboard: router.push(/projects/:id)
Dashboard->>Parent: postMessage { type: stack-preview-ready } (target: '*')
Parent->>Dashboard: postMessage { type: stack-preview-visible }
Dashboard->>Dashboard: WalkthroughEngine starts (setTimeout 2s)
Dashboard->>Dashboard: Animate cursor, spotlight steps, navigate via CmdK/sidebar
Prompt To Fix All With AIThis is a comment left during a code review.
Path: apps/dashboard/src/components/walkthrough/walkthrough-provider.tsx
Line: 108-115
Comment:
**Missing `event.origin` validation in postMessage listener**
`handleMessage` starts the walkthrough for any sender without checking `event.origin`. Since `X-Frame-Options: SAMEORIGIN` is intentionally removed in preview mode (`next.config.mjs`), the app can be embedded by any origin. A third-party page could therefore send `{ type: 'stack-preview-visible' }` and trigger the animated walkthrough (which performs real DOM clicks and input typing). While all preview data is dummy data and the risk is low, validating the origin is standard practice for `postMessage` listeners.
```suggestion
const handleMessage = (event: MessageEvent) => {
if (event.data?.type === 'stack-preview-visible' && !stoppedRef.current) {
// Only accept messages from the same origin or a trusted parent
const trustedOrigins = [window.location.origin];
if (!trustedOrigins.includes(event.origin)) return;
setTimeout(() => {
if (!stoppedRef.current) {
setIsRunning(true);
}
}, 2000 * TIMING_MULTIPLIER);
}
};
```
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/backend/src/app/api/latest/internal/preview/create-project/route.tsx
Line: 48-60
Comment:
**`findFirst` without `orderBy` returns a non-deterministic team**
`teamMember.findFirst` without an `orderBy` clause returns whichever team the database engine happens to surface first. If a preview user is somehow in multiple teams (e.g. via a retry that created a second account), the resulting preview project will be owned by an arbitrary team. Adding `orderBy: { createdAt: 'asc' }` makes the selection deterministic.
```suggestion
const membership = await prisma.teamMember.findFirst({
where: {
tenancyId: auth.tenancy.id,
projectUserId: userId,
},
orderBy: { createdAt: 'asc' },
select: {
teamId: true,
},
});
```
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/backend/scripts/run-cron-jobs.ts
Line: 12-18
Comment:
**`NEXT_PUBLIC_` prefix on a server-side env var**
`NEXT_PUBLIC_STACK_IS_PREVIEW` is semantically a Next.js browser-exposed variable. Using it as a guard in a Node.js script (outside the Next.js build pipeline) works at runtime, but it may be inadvertently inlined or stripped during build-time tree-shaking in the Next.js app. Consider reading a server-only var (e.g. `STACK_IS_PREVIEW`) for server-side cron checks, or at minimum document that both the public and server names must be set consistently.
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/components/walkthrough/walkthrough-provider.tsx
Line: 119
Comment:
**`postMessage` to `'*'` leaks the ready signal to all potential parents**
`window.parent.postMessage({ type: 'stack-preview-ready' }, '*')` broadcasts to any embedding frame. Since the signal itself carries no sensitive data this is low-risk, but specifying the expected parent origin (e.g. the Stack marketing site) would prevent unintended listeners from keying off this message type. If the parent origin is known at build time, pass it in via an env var and use it as the target origin.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "Merge branch 'dev' into preview-stack-au..." | Re-trigger Greptile |
apps/backend/src/app/api/latest/internal/preview/create-project/route.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Actionable comments posted: 10
🧹 Nitpick comments (7)
apps/backend/src/lib/preview-mode.ts (1)
7-7: Add a comment explaining theany[]return type.Per coding guidelines, when using
any, explain why it's necessary. The rrweb replay event schema is complex and external, makingany[]reasonable here, but this should be documented.📝 Suggested documentation
-export function generatePreviewReplayEvents(startTs: number): any[] { +// Return type is any[] because rrweb event objects have a complex schema +// that varies by event type. These are mock events for preview mode demos. +export function generatePreviewReplayEvents(startTs: number): any[] {As per coding guidelines: "Try to avoid the
anytype. Whenever you need to useany, leave a comment explaining why you're using it."🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/backend/src/lib/preview-mode.ts` at line 7, The function generatePreviewReplayEvents currently returns any[]; add an inline comment above the function (or inline on the return type) explaining that the rrweb replay event schema is external and complex, making a strict type impractical here, and note the source/version of the rrweb schema and a TODO to replace any[] with a proper type or generated types when available; reference the generatePreviewReplayEvents function and ensure the comment mentions why any[] is necessary and how to update it in future.apps/backend/src/app/api/latest/internal/payments/method-configs/route.ts (1)
137-139: Consider separating conditions for clarity.Combining
Object.keys(body.updates).length === 0withisPreviewModeEnabled()in a singleifobscures two distinct short-circuit reasons. Separating them would make the intent clearer, though this is minor.♻️ Suggested refactor
handler: async ({ auth, body }) => { - if (Object.keys(body.updates).length === 0 || isPreviewModeEnabled()) { + if (isPreviewModeEnabled()) { + return { statusCode: 200, bodyType: "json", body: { success: true } }; + } + + if (Object.keys(body.updates).length === 0) { return { statusCode: 200, bodyType: "json", body: { success: true } }; }🤖 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/method-configs/route.ts` around lines 137 - 139, The current if-condition in route.ts lumps two separate short-circuit reasons (empty updates and preview mode) into one check, reducing clarity; split it into two explicit checks: first return the 200 success response when Object.keys(body.updates).length === 0, then separately return the same response when isPreviewModeEnabled() is true. Update the early-return logic where body.updates and isPreviewModeEnabled() are evaluated so each condition is self-contained and commented if needed to explain the short-circuit.apps/dashboard/src/app/(main)/(protected)/layout-client.tsx (1)
18-53: Effect re-runs may cause duplicate sign-in attempts.The effect depends on
user,app,isLocalEmulator, andisPreview. If sign-in/sign-up succeeds,userchanges, triggering a re-run. The earlyif (user) return;handles this, but there's a brief window whereuseris still null while auth is in progress. Consider adding a local ref to track in-flight auth.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/app/`(main)/(protected)/layout-client.tsx around lines 18 - 53, The effect in useEffect that calls autoLogin can trigger duplicate sign-in attempts because user may still be null while auth is in-flight; add a local ref (e.g., authInProgressRef) and check it at the start of autoLogin to skip if already true, set it to true before starting async auth, and set it back to false in a finally block so runAsynchronouslyWithAlert(autoLogin()) will not start concurrent sign-ins; update references inside autoLogin and ensure the ref is included in the closure (but not in the dependency array) so useEffect still depends only on user, app, isLocalEmulator, and isPreview.apps/backend/src/lib/seed-dummy-data.ts (1)
795-802:throwErrinsideresolveOptionalUserIdreturnsnever, but the function signature suggestsnullis valid.The function returns
nullfor missing email but throws for unknown email. This is correct, but thethrowErrcall inside won't returnuserId, it will throw. The logic is fine but slightly confusing.♻️ Cleaner implementation
const resolveOptionalUserId = (email?: string) => { if (!email) return null; - const userId = userEmailToId.get(email); - if (!userId) { - throwErr(`Unknown dummy project user ${email}`); - } - return userId; + return userEmailToId.get(email) ?? throwErr(`Unknown dummy project user ${email}`); };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/backend/src/lib/seed-dummy-data.ts` around lines 795 - 802, The resolveOptionalUserId function should explicitly indicate it returns string | null and make the control flow clearer around the never-returning throwErr; change the signature to resolveOptionalUserId(email?: string): string | null, keep the early return if (!email) return null, then look up userId via userEmailToId.get(email) and if userId is undefined call throwErr(`Unknown dummy project user ${email}`) (which never returns), otherwise return the userId; reference resolveOptionalUserId, userEmailToId and throwErr when making this small clarity/typing fix.apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/preview-project-redirect.tsx (1)
15-20: Use the existing guarded internals accessor instead ofas any.This reintroduces
as anyand a casted internals shape next topage-client.tsx, which already wraps the same symbol access in a runtime type guard. Reuse or extract that helper so the contract stays enforced in one place.As per coding guidelines: Do NOT use
as/any/type casts or anything else to bypass the type system unless you specifically asked the user about it.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/projects/preview-project-redirect.tsx around lines 15 - 20, Replace the ad-hoc Reflect.get + "as any" logic in the appInternals useMemo with the project's guarded internals accessor used in page-client.tsx (import the helper function that checks stackAppInternalsSymbol and validates sendRequest), call that helper to obtain the internals, and remove the manual type cast; ensure you keep the runtime check for sendRequest by using the helper so the contract is enforced in one place (refer to stackAppInternalsSymbol, appInternals, and the accessor in page-client.tsx).apps/dashboard/src/components/walkthrough/walkthrough-provider.tsx (2)
164-187: Replace styling/text selectors with explicit walkthrough hooks.This CmdK path depends on a placeholder string and presentation classes (
.rounded-2xl,.border-t button). Any copy tweak or CSS refactor will silently break preview navigation. Please add dedicateddata-walkthrough-navhooks for the search input and result row instead of querying styling details.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/walkthrough/walkthrough-provider.tsx` around lines 164 - 187, The current selector logic in the walkthrough (querying input via placeholder "Search or ask AI..." and climbing DOM via '.rounded-2xl' then '.border-t button') is brittle; update the queries in walkthrough-provider.tsx to use explicit data attributes instead: replace document.querySelector('input[placeholder="Search or ask AI..."]') with a selector for a new data attribute like '[data-walkthrough-nav="search-input"]' and replace the closest('.rounded-2xl') / querySelector('.border-t button') path with a single query for the result row like '[data-walkthrough-nav="search-result"]' (or a button within it). Update references to the variables input, cmdkContainer and resultButton accordingly, and add the matching data-walkthrough-nav attributes to the search input and result row in the component JSX so the walkthrough selectors are stable.
17-37: Useperformance.now()and cancel-aware polling inwaitForElement.
Date.now()makes the timeout sensitive to wall-clock jumps, and this loop keeps scheduling frames until timeout even afterstop()tears the walkthrough down. Switching to a monotonic clock and threading cancellation into the helper makes the wait deterministic and avoids orphaned polling.♻️ Suggested change
-function waitForElement(selector: string, timeoutMs = 3000): Promise<Element | null> { +function waitForElement( + selector: string, + cancelled: () => boolean, + timeoutMs = 3000, +): Promise<Element | null> { return new Promise((resolve) => { const existing = document.querySelector(selector); if (existing && existing.getBoundingClientRect().height > 0) { resolve(existing); return; } - const start = Date.now(); + const start = performance.now(); const check = () => { + if (cancelled()) { + resolve(null); + return; + } const el = document.querySelector(selector); if (el && el.getBoundingClientRect().height > 0) { resolve(el); - } else if (Date.now() - start > timeoutMs) { + } else if (performance.now() - start > timeoutMs) { resolve(null); } else { requestAnimationFrame(check); } };As per coding guidelines, "Don't use Date.now() for measuring elapsed (real) time, instead use performance.now()".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/walkthrough/walkthrough-provider.tsx` around lines 17 - 37, Replace Date.now() with performance.now() inside waitForElement and add cancellation support by accepting an optional AbortSignal (e.g., add parameter signal?: AbortSignal to waitForElement). Use performance.now() to compute elapsed (const start = performance.now()) and in the polling loop check signal?.aborted and resolve(null) immediately if aborted; also store the requestAnimationFrame id so you can cancel it (cancelAnimationFrame) when resolving due to success, timeout, or abort. Finally, attach an 'abort' event listener to the signal to resolve(null) and cancel any pending RAF, and remove that listener on completion to avoid orphaned polling.
🤖 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/src/app/api/latest/internal/payments/stripe-widgets/account-session/route.ts`:
- Around line 27-35: The fetchClientSecret callback (in
stripe-connect-provider.tsx) currently returns an empty client_secret when
adminApp.createStripeWidgetAccountSession() yields an empty string; add explicit
validation to throw an error if client_secret is falsy instead of returning it
silently. Update fetchClientSecret to call createStripeWidgetAccountSession(),
destructure client_secret, and if client_secret is null/empty/undefined throw
(use the project's throwErr or similar helper) with a clear message like "Stripe
client_secret is empty"; this mirrors the pattern used in
purchase/[code]/page-client.tsx and ensures failures surface loudly.
In `@apps/backend/src/app/api/latest/internal/preview/create-project/route.tsx`:
- Around line 41-58: The current code uses prisma.teamMember.findFirst to pick
"some" membership; change this to explicitly resolve the auto-created/personal
signup team before calling seedDummyProject: query for the team that was
auto-created on sign-up (e.g., via a flag or relation such as
team.autoCreatedForUserId === userId or team.isPersonal === true) using
prisma.team.findFirst/findUnique (or join teamMember with team and filter
team.autoCreatedFlag) to obtain the correct teamId, assert/throw if that exact
team isn't found, and then pass that team.id into seedDummyProject instead of
membership.teamId (referencing prisma.teamMember.findFirst, membership.teamId,
and seedDummyProject to locate and update the code).
In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/projects/preview-project-redirect.tsx:
- Around line 25-42: The code sets creating.current = true before calling
runAsynchronouslyWithAlert but never clears it on failure; wrap the async
callback passed to runAsynchronouslyWithAlert in a try/catch/finally (or add
.catch/.finally) so that creating.current is reset to false in finally, and in
the catch set an explicit error state or expose a retry action that re-invokes
the same preview creation flow (reference creating.current,
runAsynchronouslyWithAlert, appInternals.sendRequest and router.push to locate
the logic).
- Around line 24-42: The effect in useEffect will POST to create a preview
project as soon as user appears which can create duplicates; before calling
appInternals.sendRequest("/internal/preview/create-project", ...) inside
runAsynchronouslyWithAlert, re-check whether the signed-in user already owns a
preview project by calling a read endpoint (via appInternals.sendRequest) that
returns the existing preview (or by hitting the same server-side check used in
projects/page.tsx), and if an existing project_id is returned call
router.push(`/projects/${encodeURIComponent(project_id)}`) and skip the POST;
keep the creating.current guard and preserve error handling around the create
flow so the POST only runs when no existing preview is found.
In `@apps/dashboard/src/app/`(main)/(protected)/layout-client.tsx:
- Around line 45-49: Protect against corrupted or tampered localStorage by
wrapping JSON.parse(creds) in a try/catch and validating the parsed object
before using it: attempt to parse creds, verify it has string properties email
and password (non-empty), and only then call app.signInWithCredential or
app.signUpWithCredential; on parse error or invalid shape, remove the bad creds
from localStorage (or skip auth) and optionally log the issue so you don’t pass
undefined values into signInWithCredential/signUpWithCredential.
In `@apps/dashboard/src/components/walkthrough/walkthrough-overlay.tsx`:
- Around line 107-131: The overlays only handle pointer clicks (isHovering
blocks via the fixed div and the invisible catcher div) so keyboard users cannot
dismiss; add an Escape key handler and a focusable control that calls onStop:
attach a keydown listener (or use useEffect with window.addEventListener) to
call onStop when Escape is pressed while the overlay/preview is active, and make
the visible overlay include a semantic button (or add role="button" with
tabIndex={0} and handle onKeyDown for Enter/Space) that invokes onStop so it is
reachable by keyboard and screen readers; ensure the invisible catcher also
exposes a hidden focusable element (e.g., a visually-hidden button with
onClick/onKeyDown calling onStop) so non-hover state is dismissible via keyboard
as well.
- Around line 32-45: The useEffect that sequences two requestAnimationFrame
calls (watching showSpotlight and stepIndex) only cancels the outer RAF, leaving
the inner RAF to potentially run after unmount and call setAnimatedIn; update
the effect to capture both RAF ids (e.g., raf1 and raf2 or an array) and cancel
both in the cleanup, ensuring any scheduled inner callback is cancelled when
showSpotlight or stepIndex changes or the component unmounts (references:
useEffect, showSpotlight, setAnimatedIn, stepIndex).
In `@apps/dashboard/src/components/walkthrough/walkthrough-provider.tsx`:
- Line 87: currentPathRef is hard-coded to '/' and updated prematurely; instead
initialize and sync it from the live router pathname and only advance when the
router has actually reached step.path. Replace the literal initialization of
currentPathRef with a value derived from the router (e.g., useRouter().asPath or
usePathname()) and remove the premature overwrite logic around the advance code
(the block around lines 293-306). Add a useEffect that watches the router's
pathname/asPath and updates currentPathRef only when router.pathname/asPath ===
step.path (or assert/fail if it never matches), and gate advancing to the next
walkthrough step on that match so the engine never skips or races with
navigation.
- Around line 108-120: handleMessage currently processes 'stack-preview-visible'
messages from any source; restrict it by first checking that event.source ===
window.parent and (when the preview host is known) validating event.origin
against the expected host before proceeding to the existing stoppedRef and
setIsRunning flow. Update the handler used in window.addEventListener('message',
handleMessage) to early-return if event.source !== window.parent (and if
available compare event.origin to a configured PREVIEW_HOST or allowedOrigins
list) so only the parent frame can trigger the setTimeout -> setIsRunning
sequence that uses TIMING_MULTIPLIER and stoppedRef.
- Around line 362-363: Wrap the floating promise call to runWalkthrough() with
runAsynchronously so any thrown errors are handled; import runAsynchronously
from "stackframe/stack-shared/dist/utils/promises" and replace the bare
invocation (the runWalkthrough() call in walkthrough-provider) with
runAsynchronously(() => runWalkthrough()) so exceptions are captured and logged
instead of becoming unhandled rejections.
---
Nitpick comments:
In `@apps/backend/src/app/api/latest/internal/payments/method-configs/route.ts`:
- Around line 137-139: The current if-condition in route.ts lumps two separate
short-circuit reasons (empty updates and preview mode) into one check, reducing
clarity; split it into two explicit checks: first return the 200 success
response when Object.keys(body.updates).length === 0, then separately return the
same response when isPreviewModeEnabled() is true. Update the early-return logic
where body.updates and isPreviewModeEnabled() are evaluated so each condition is
self-contained and commented if needed to explain the short-circuit.
In `@apps/backend/src/lib/preview-mode.ts`:
- Line 7: The function generatePreviewReplayEvents currently returns any[]; add
an inline comment above the function (or inline on the return type) explaining
that the rrweb replay event schema is external and complex, making a strict type
impractical here, and note the source/version of the rrweb schema and a TODO to
replace any[] with a proper type or generated types when available; reference
the generatePreviewReplayEvents function and ensure the comment mentions why
any[] is necessary and how to update it in future.
In `@apps/backend/src/lib/seed-dummy-data.ts`:
- Around line 795-802: The resolveOptionalUserId function should explicitly
indicate it returns string | null and make the control flow clearer around the
never-returning throwErr; change the signature to resolveOptionalUserId(email?:
string): string | null, keep the early return if (!email) return null, then look
up userId via userEmailToId.get(email) and if userId is undefined call
throwErr(`Unknown dummy project user ${email}`) (which never returns), otherwise
return the userId; reference resolveOptionalUserId, userEmailToId and throwErr
when making this small clarity/typing fix.
In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/projects/preview-project-redirect.tsx:
- Around line 15-20: Replace the ad-hoc Reflect.get + "as any" logic in the
appInternals useMemo with the project's guarded internals accessor used in
page-client.tsx (import the helper function that checks stackAppInternalsSymbol
and validates sendRequest), call that helper to obtain the internals, and remove
the manual type cast; ensure you keep the runtime check for sendRequest by using
the helper so the contract is enforced in one place (refer to
stackAppInternalsSymbol, appInternals, and the accessor in page-client.tsx).
In `@apps/dashboard/src/app/`(main)/(protected)/layout-client.tsx:
- Around line 18-53: The effect in useEffect that calls autoLogin can trigger
duplicate sign-in attempts because user may still be null while auth is
in-flight; add a local ref (e.g., authInProgressRef) and check it at the start
of autoLogin to skip if already true, set it to true before starting async auth,
and set it back to false in a finally block so
runAsynchronouslyWithAlert(autoLogin()) will not start concurrent sign-ins;
update references inside autoLogin and ensure the ref is included in the closure
(but not in the dependency array) so useEffect still depends only on user, app,
isLocalEmulator, and isPreview.
In `@apps/dashboard/src/components/walkthrough/walkthrough-provider.tsx`:
- Around line 164-187: The current selector logic in the walkthrough (querying
input via placeholder "Search or ask AI..." and climbing DOM via '.rounded-2xl'
then '.border-t button') is brittle; update the queries in
walkthrough-provider.tsx to use explicit data attributes instead: replace
document.querySelector('input[placeholder="Search or ask AI..."]') with a
selector for a new data attribute like '[data-walkthrough-nav="search-input"]'
and replace the closest('.rounded-2xl') / querySelector('.border-t button') path
with a single query for the result row like
'[data-walkthrough-nav="search-result"]' (or a button within it). Update
references to the variables input, cmdkContainer and resultButton accordingly,
and add the matching data-walkthrough-nav attributes to the search input and
result row in the component JSX so the walkthrough selectors are stable.
- Around line 17-37: Replace Date.now() with performance.now() inside
waitForElement and add cancellation support by accepting an optional AbortSignal
(e.g., add parameter signal?: AbortSignal to waitForElement). Use
performance.now() to compute elapsed (const start = performance.now()) and in
the polling loop check signal?.aborted and resolve(null) immediately if aborted;
also store the requestAnimationFrame id so you can cancel it
(cancelAnimationFrame) when resolving due to success, timeout, or abort.
Finally, attach an 'abort' event listener to the signal to resolve(null) and
cancel any pending RAF, and remove that listener on completion to avoid orphaned
polling.
🪄 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: ba2f8c20-a4bd-4cd0-a66b-01b251962146
📒 Files selected for processing (37)
apps/backend/prisma/seed.tsapps/backend/scripts/run-cron-jobs.tsapps/backend/src/app/api/latest/emails/render-email/route.tsxapps/backend/src/app/api/latest/internal/payments/method-configs/route.tsapps/backend/src/app/api/latest/internal/payments/stripe-widgets/account-session/route.tsapps/backend/src/app/api/latest/internal/payments/stripe/account-info/route.tsapps/backend/src/app/api/latest/internal/preview/create-project/route.tsxapps/backend/src/app/api/latest/internal/session-replays/[session_replay_id]/events/route.tsxapps/backend/src/app/api/latest/webhooks/svix-token/route.tsxapps/backend/src/lib/preview-mode.tsapps/backend/src/lib/seed-dummy-data.tsapps/backend/src/lib/webhooks.tsxapps/dashboard/next.config.mjsapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsxapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page.tsxapps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/preview-project-redirect.tsxapps/dashboard/src/app/(main)/(protected)/layout-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/replays/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-sent/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/layout.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/payouts/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsxapps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsxapps/dashboard/src/app/layout.tsxapps/dashboard/src/components/cmdk-search.tsxapps/dashboard/src/components/payments/stripe-connect-provider.tsxapps/dashboard/src/components/walkthrough/mock-cursor.tsxapps/dashboard/src/components/walkthrough/walkthrough-overlay.tsxapps/dashboard/src/components/walkthrough/walkthrough-provider.tsxapps/dashboard/src/components/walkthrough/walkthrough-steps.tsapps/dashboard/src/lib/env.tsxapps/dashboard/src/lib/theme.tsxapps/dashboard/src/stack.tsx
Summary by CodeRabbit
New Features
New Features — Walkthrough
Style