Skip to content

stack auth preview mode#1307

Open
BilalG1 wants to merge 5 commits intodevfrom
preview-stack-auth
Open

stack auth preview mode#1307
BilalG1 wants to merge 5 commits intodevfrom
preview-stack-auth

Conversation

@BilalG1
Copy link
Copy Markdown
Collaborator

@BilalG1 BilalG1 commented Apr 3, 2026

Summary by CodeRabbit

  • New Features

    • Preview mode: safe sandbox with mock projects, placeholder data, and disabled external integrations (payments, webhooks, email rendering, session replays).
    • One-click preview project creation and automatic preview sign-in to explore the dashboard.
  • New Features — Walkthrough

    • Interactive guided walkthroughs with spotlight, animated cursor, and step-driven navigation to showcase dashboard features.
  • Style

    • UI tweaks for preview (theme behavior, conditional banners/alerts, walkthrough attributes and overlays).

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 3, 2026

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

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

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f5512df6-d8a8-4c42-a7cb-8f9f6298e3c3

📥 Commits

Reviewing files that changed from the base of the PR and between 0d23a62 and 0a86750.

📒 Files selected for processing (1)
  • apps/dashboard/src/app/(main)/(protected)/layout-client.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/dashboard/src/app/(main)/(protected)/layout-client.tsx

📝 Walkthrough

Walkthrough

This 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

Cohort / File(s) Summary
Backend Preview Guards
apps/backend/src/app/api/latest/emails/render-email/route.tsx, apps/backend/src/app/api/latest/internal/payments/method-configs/route.ts, apps/backend/src/app/api/latest/internal/payments/stripe-widgets/account-session/route.ts, apps/backend/src/app/api/latest/internal/payments/stripe/account-info/route.ts, apps/backend/src/app/api/latest/internal/session-replays/.../events/route.tsx, apps/backend/src/app/api/latest/webhooks/svix-token/route.tsx
Added isPreviewModeEnabled() checks; handlers short-circuit with mock responses when preview is enabled (email placeholders, fixed payment config, empty Stripe secrets, preview replay events, svix token stub).
Seed Data Infrastructure
apps/backend/prisma/seed.ts, apps/backend/src/lib/seed-dummy-data.ts
Removed inline dummy-project seeding from seed.ts and delegated to new seedDummyProject in seed-dummy-data.ts; new module implements full dummy project provisioning (teams, users, payments, emails, analytics, session replays).
Preview Utilities & Webhooks
apps/backend/src/lib/preview-mode.ts, apps/backend/src/lib/webhooks.tsx
Added isPreviewModeEnabled() and generatePreviewReplayEvents(); webhooks sending now no-ops in preview mode.
Preview Project API
apps/backend/src/app/api/latest/internal/preview/create-project/route.tsx
New internal POST route that verifies preview mode and team membership, then calls seedDummyProject to create/return a preview project id.
Cron Process Behavior
apps/backend/scripts/run-cron-jobs.ts
When preview is enabled, main() logs preview mode and keeps the process alive (setInterval) without starting cron jobs.
Session Replay Worker
apps/backend/src/app/api/latest/internal/session-replays/[session_replay_id]/events/route.tsx
Chunk worker now generates preview replay events for preview:// S3 keys under preview mode; otherwise retains S3 download + decompress + parse flow.
Email Rendering Guard
apps/backend/src/app/api/latest/emails/render-email/route.tsx
Added preview-mode short-circuit returning placeholder html/subject and skipping editable-marker computation and template rendering.
Dashboard Env & Theme
apps/dashboard/src/lib/env.tsx, apps/dashboard/src/lib/theme.tsx, apps/dashboard/src/app/layout.tsx, apps/dashboard/next.config.mjs
Registered NEXT_PUBLIC_STACK_IS_PREVIEW; theme defaults/fallbacks adjusted for preview; inline theme script omits prefers-color fallback in preview; X-Frame-Options header omitted in preview.
Stack App Config
apps/dashboard/src/stack.tsx
stackServerApp constructed without explicit generics; token store set to memory in preview; replay analytics disabled in preview.
Dashboard Auth / Auto-login
apps/dashboard/src/app/(main)/(protected)/layout-client.tsx
Reworked auto-login: emulator uses fixed credentials; preview generates UUID-based creds, attempts sign-in, falls back to signup; loading gating updated.
Projects Redirect & Preview Create Flow
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page.tsx, .../page-client.tsx, .../preview-project-redirect.tsx
Preview branch: page uses server getUser without redirect and renders PreviewProjectRedirect which POSTs to /internal/preview/create-project and navigates to created project.
Payments / Payouts UI
apps/dashboard/src/app/.../payments/layout.tsx, .../payments/payouts/page-client.tsx
Connect banner hidden in preview; payouts page shows alert instead of Stripe UI when preview enabled.
Webhooks UI
apps/dashboard/src/app/.../webhooks/page-client.tsx
Shows informational alert in preview mode and hides Svix portal/provider UI.
Walkthrough Provider & Wrapper
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx, apps/dashboard/src/components/walkthrough/walkthrough-provider.tsx
Wrapped project sidebar layout with WalkthroughProvider; provider mounts WalkthroughEngine only in preview mode to coordinate steps and interactions.
Walkthrough Overlay & Cursor
apps/dashboard/src/components/walkthrough/walkthrough-overlay.tsx, apps/dashboard/src/components/walkthrough/mock-cursor.tsx
Added overlay component (portal, spotlight, tooltip, animated cursor, interactions) and a MockCursor SVG component.
Walkthrough Steps & Types
apps/dashboard/src/components/walkthrough/walkthrough-steps.ts
Added WalkthroughStep/SpotlightRect types and WALKTHROUGH_STEPS configuration array mapping steps to routes, labels, queries, and padding.
DOM Walkthrough Targets
apps/dashboard/src/app/.../metrics-page.tsx, .../analytics/replays/page-client.tsx, .../email-sent/page-client.tsx, .../payments/products/page-client.tsx, .../teams/page-client.tsx, .../users/page-client.tsx, apps/dashboard/src/components/cmdk-search.tsx
Added data-walkthrough or data-walkthrough-nav attributes to key UI elements to enable targeted walkthrough steps.
Misc UI Adjustments
apps/dashboard/src/components/payments/stripe-connect-provider.tsx, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/replays/page-client.tsx, others
Preview gating in Stripe connect provider (bypass ConnectComponents in preview), minor DOM attribute additions for walkthrough, small layout/flag 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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • seed script dummy project #1018: Refactors apps/backend/prisma/seed.ts and dummy-project seeding—directly related to the new seed-dummy-data extraction and changed seed invocation.
  • session replays #1187: Implements session replay core APIs and S3 chunking—related to preview replay event generation and session-replay route changes.
  • Fixed. #1046: Modifies sidebar/layout behavior—likely to overlap with sidebar-layout wrapping by WalkthroughProvider.

Suggested reviewers

  • N2D4

Poem

🐰 A preview hop, a gentle nudge,
Spotlight gleams and cursors trudge,
Dummy projects spawn with cheer,
Mock replies keep real things clear,
Follow the trail—the tour’s begun, hooray! ✨

🚥 Pre-merge checks | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is empty except for an HTML comment referencing CONTRIBUTING.md guidelines, providing no explanation of changes, objectives, testing approach, or any other substantive information. Add a comprehensive description explaining the preview mode feature, its purpose, affected components, testing steps, and any configuration requirements.
Docstring Coverage ⚠️ Warning Docstring coverage is 11.90% 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 'stack auth preview mode' is vague and generic, using non-descriptive language that does not clearly convey the specific changes made in this large, multi-faceted pull request. Replace with a more specific title that captures the main objective, such as 'Add preview mode support for Stack Auth' or 'Implement preview environment for onboarding walkthrough'.

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

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

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

Greptile Summary

This PR introduces a "preview mode" for the Stack Auth dashboard, enabling an embeddable, read-only demo experience gated behind NEXT_PUBLIC_STACK_IS_PREVIEW=true. It covers the full stack: auto-login with ephemeral localStorage credentials, a new /internal/preview/create-project endpoint that seeds dummy data, stub responses for Stripe/Svix/email routes, and an animated walkthrough overlay that simulates user interactions to showcase the dashboard to prospective customers.

Confidence Score: 5/5

Safe 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

Filename Overview
apps/dashboard/src/components/walkthrough/walkthrough-provider.tsx New animated walkthrough engine rendered only in preview mode; postMessage listener lacks event.origin validation, and the outgoing postMessage targets '*'.
apps/backend/src/app/api/latest/internal/preview/create-project/route.tsx New POST endpoint creates a dummy project for preview users; uses SmartRouteHandler and is preview-gated, but teamMember.findFirst lacks orderBy, making team selection non-deterministic.
apps/backend/src/lib/preview-mode.ts New utility exposing isPreviewModeEnabled() flag and generatePreviewReplayEvents() for canned session-replay data; straightforward and correctly gated.
apps/dashboard/src/app/(main)/(protected)/layout-client.tsx Adds auto-login for preview mode using a UUID-based credential persisted in localStorage, with graceful fallback when localStorage is blocked in cross-origin iframes.
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/preview-project-redirect.tsx New client component that calls the create-project endpoint and redirects to the new project; uses useRef guard to prevent double-firing and runAsynchronouslyWithAlert for error handling.
apps/backend/src/lib/seed-dummy-data.ts Existing seed logic extracted from seed.ts into a shared module; adds optional projectId, excludeAlphaApps, and skipGithubConfigSource parameters for preview mode use.
apps/dashboard/next.config.mjs Removes X-Frame-Options: SAMEORIGIN header in preview mode to allow iframe embedding; intentional and clearly conditioned on the preview flag.
apps/backend/scripts/run-cron-jobs.ts Skips all cron jobs in preview mode and keeps the process alive with an infinite interval; uses NEXT_PUBLIC_ prefix for a server-side env var, which is a minor conceptual mismatch.
apps/backend/src/lib/webhooks.tsx Adds an early return in sendWebhooks when preview mode is enabled, preventing real webhook deliveries during preview sessions.
apps/dashboard/src/stack.tsx Switches token store to 'memory' in preview mode so sessions are not persisted to cookies, and disables analytics replays for preview users.
apps/backend/src/app/api/latest/internal/session-replays/[session_replay_id]/events/route.tsx Serves canned preview replay events when the S3 key starts with 'preview://' in preview mode; falls through to real S3 download for other chunks, which is intentional.

Sequence Diagram

sequenceDiagram
    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
Loading
Prompt To Fix All With AI
This 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

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

🧹 Nitpick comments (7)
apps/backend/src/lib/preview-mode.ts (1)

7-7: Add a comment explaining the any[] return type.

Per coding guidelines, when using any, explain why it's necessary. The rrweb replay event schema is complex and external, making any[] 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 any type. Whenever you need to use any, leave a comment explaining why you're using it."

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

In `@apps/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 === 0 with isPreviewModeEnabled() in a single if obscures 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, and isPreview. If sign-in/sign-up succeeds, user changes, triggering a re-run. The early if (user) return; handles this, but there's a brief window where user is 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: throwErr inside resolveOptionalUserId returns never, but the function signature suggests null is valid.

The function returns null for missing email but throws for unknown email. This is correct, but the throwErr call inside won't return userId, 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 of as any.

This reintroduces as any and a casted internals shape next to page-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 dedicated data-walkthrough-nav hooks 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: Use performance.now() and cancel-aware polling in waitForElement.

Date.now() makes the timeout sensitive to wall-clock jumps, and this loop keeps scheduling frames until timeout even after stop() 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

📥 Commits

Reviewing files that changed from the base of the PR and between a9ff924 and 0d23a62.

📒 Files selected for processing (37)
  • apps/backend/prisma/seed.ts
  • apps/backend/scripts/run-cron-jobs.ts
  • apps/backend/src/app/api/latest/emails/render-email/route.tsx
  • apps/backend/src/app/api/latest/internal/payments/method-configs/route.ts
  • apps/backend/src/app/api/latest/internal/payments/stripe-widgets/account-session/route.ts
  • apps/backend/src/app/api/latest/internal/payments/stripe/account-info/route.ts
  • apps/backend/src/app/api/latest/internal/preview/create-project/route.tsx
  • apps/backend/src/app/api/latest/internal/session-replays/[session_replay_id]/events/route.tsx
  • apps/backend/src/app/api/latest/webhooks/svix-token/route.tsx
  • apps/backend/src/lib/preview-mode.ts
  • apps/backend/src/lib/seed-dummy-data.ts
  • apps/backend/src/lib/webhooks.tsx
  • apps/dashboard/next.config.mjs
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/page.tsx
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/preview-project-redirect.tsx
  • apps/dashboard/src/app/(main)/(protected)/layout-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/analytics/replays/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-sent/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/payouts/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/payments/products/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/sidebar-layout.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/webhooks/page-client.tsx
  • apps/dashboard/src/app/layout.tsx
  • apps/dashboard/src/components/cmdk-search.tsx
  • apps/dashboard/src/components/payments/stripe-connect-provider.tsx
  • apps/dashboard/src/components/walkthrough/mock-cursor.tsx
  • apps/dashboard/src/components/walkthrough/walkthrough-overlay.tsx
  • apps/dashboard/src/components/walkthrough/walkthrough-provider.tsx
  • apps/dashboard/src/components/walkthrough/walkthrough-steps.ts
  • apps/dashboard/src/lib/env.tsx
  • apps/dashboard/src/lib/theme.tsx
  • apps/dashboard/src/stack.tsx

@BilalG1 BilalG1 requested a review from N2D4 April 4, 2026 00:05
@BilalG1 BilalG1 assigned N2D4 and unassigned BilalG1 Apr 4, 2026
@github-actions github-actions bot assigned BilalG1 and unassigned N2D4 Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants