Skip to content

Speed up dummy-project seeding (preview create-project ~15s → ~1.3s)#1437

Open
BilalG1 wants to merge 12 commits into
devfrom
worktree-benchmark-preview-create
Open

Speed up dummy-project seeding (preview create-project ~15s → ~1.3s)#1437
BilalG1 wants to merge 12 commits into
devfrom
worktree-benchmark-preview-create

Conversation

@BilalG1
Copy link
Copy Markdown
Collaborator

@BilalG1 BilalG1 commented May 16, 2026

Summary

The internal preview/create-project endpoint was taking ~15s because seedDummyProject created its dummy users one at a time through the full usersCrudHandlers.adminCreate CRUD pipeline (one DB transaction + config render per user, ~86 users). This reworks the seeding path to use bulk inserts.

End-to-end, the endpoint's server-side handler time drops from ~15,100ms → ~1,300ms (~11× faster).

Seeding changes (seed-dummy-data.ts)

  • seedDummyUsers — bulk insert. Build every row (ProjectUser, ContactChannel, AuthMethod, ProjectUserOAuthAccount, OAuthAuthMethod, default permissions) up front with pre-generated UUIDs, then insert via one createMany per table inside a single transaction — replacing ~86 sequential adminCreate transactions. Named-user team memberships are bulk-inserted the same way (TeamMember + TeamMemberDirectPermission). Idempotency is preserved with a single up-front email lookup, so re-runs against an existing project still skip existing users.
  • Native randomUUID. The seed paths now use node:crypto's randomUUID() instead of stack-shared's generateUuid(). The browser-safe polyfill calls crypto.getRandomValues ~31× per UUID (once per template char, each with a fresh Uint8Array(1)); generating thousands of seed UUIDs made that ~800ms of pure CPU in the activity-event build alone.
  • seedBulkSignupsAndActivity. Skip the redundant back-date UPDATE for freshly-inserted users (createMany already writes correct createdAt/signedUpAt), and flush ClickHouse events in larger, parallel batches.
  • seedDummyProject. Run seedBulkSignupsAndActivity concurrently with the lighter remaining steps, and fold seedDummyTransactions into the emails/activity/replays Promise.all.
  • Removed the now-unused syncSeedUserOauthProviders helper.

The bulk path produces the same rows as the CRUD-handler path (verified row-count equality during development). Webhooks / soft-limit checks are intentionally not fired for seed data, consistent with the rest of the seed.

Also in this PR — preview-mode 404 fix (preview-project-redirect.tsx)

While testing the above, the dashboard 404'd right after a preview project was created. In preview mode the /projects page renders PreviewProjectRedirect, which POSTs /internal/preview/create-project and then router.push()es to /projects/<new-id> — but it never refreshed the client-side owned-projects cache, so the [projectId] route's useAdminApp() read a stale list, failed to find the just-created project, and called notFound().

Fixed by refreshing the owned-projects cache before navigating, matching what the normal create-project flow in page-client.tsx already does. (Pre-existing bug, not caused by the seeding change — but it surfaces the seeding path, so it's bundled here.)

Testing

pnpm typecheck and pnpm lint pass for both backend and dashboard. The preview endpoint was exercised repeatedly during development (HTTP 200, projects created and populated correctly).

Summary by CodeRabbit

  • Performance

    • Much faster bulk user and event seeding via larger, parallelized batches and optimized backfilling.
  • Refactor

    • Dummy data seeding redesigned to be idempotent, deterministic, and bulk-oriented; seeding tasks now overlap where safe.
  • Bug Fixes

    • Preview project flow validates client capabilities and refreshes the local project list to avoid stale navigation.
    • Auto-login guarded to run only once to prevent duplicate sign-ins.
  • UI/UX

    • Walkthrough steps and sidebar behavior improved; walkthrough labels and search keywords updated.
  • Chore

    • CLI identity command now resolves session authentication more reliably.

Review Change Stack

Replace the per-user CRUD-handler calls in seedDummyUsers with bulk
createMany inserts, plus several smaller wins on the seeding path.
End-to-end the preview create-project endpoint drops from ~15s to ~1.3s.

- seedDummyUsers: build every row up front and insert via one createMany
  per table inside a single transaction, instead of ~86 sequential
  adminCreate transactions. Named-user team memberships are bulk-inserted
  the same way. Idempotency is preserved via a single up-front email
  lookup, so re-runs against an existing project still skip existing
  users.
- Use node:crypto randomUUID instead of stack-shared generateUuid in the
  seed paths. The browser-safe polyfill calls crypto.getRandomValues ~31x
  per UUID, which dominated CPU time when generating thousands of seed
  UUIDs (~800ms in the activity-event build alone).
- seedBulkSignupsAndActivity: skip the redundant back-date UPDATE for
  freshly-inserted users (createMany already writes correct timestamps),
  and flush ClickHouse events in larger, parallel batches.
- seedDummyProject: run seedBulkSignupsAndActivity concurrently with the
  lighter remaining steps, and fold seedDummyTransactions into the
  emails/activity/replays Promise.all.
- Remove the now-unused syncSeedUserOauthProviders helper.
@vercel
Copy link
Copy Markdown

vercel Bot commented May 16, 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 May 20, 2026 2:23am
stack-auth-mcp Ready Ready Preview, Comment May 20, 2026 2:23am
stack-auth-skills Ready Ready Preview, Comment May 20, 2026 2:23am
stack-backend Ready Ready Preview, Comment May 20, 2026 2:23am
stack-dashboard Ready Ready Preview, Comment May 20, 2026 2:23am
stack-demo Ready Ready Preview, Comment May 20, 2026 2:23am
stack-docs Ready Ready Preview, Comment May 20, 2026 2:23am
stack-preview-backend Ready Ready Preview, Comment May 20, 2026 2:23am
stack-preview-dashboard Ready Ready Preview, Comment May 20, 2026 2:23am

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 16, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Refactors backend seed scripts for deterministic bulk user creation and idempotent bulk inserts, increases ClickHouse batch sizes and concurrency, narrows backdating to existing users, overlaps bulk activity seeding with project setup, validates preview internals and refreshes owned-projects before navigation, guards layout auto-login with a ref, and adjusts CLI whoami auth resolution.

Changes

Seed Data Refactoring & Performance Optimization

Layer / File(s) Summary
Type System & Import Updates
apps/backend/src/lib/seed-dummy-data.ts
Prisma imports updated to tenancy-scoped TenancyPrismaClient. Added node:crypto imports (createHash, randomUUID).
Deterministic Bulk User Seeding
apps/backend/src/lib/seed-dummy-data.ts
Two-phase deterministic user spec generation from seeded PRNG, prefetch existing emails, UUID generation for new rows, and idempotent bulk createMany across ProjectUser, ContactChannel, OAuth auth-method/account tables, direct permissions, and team memberships. Removed per-user OAuth sync helper.
Activity Batching & Targeted Backdating
apps/backend/src/lib/seed-dummy-data.ts
seedDummySessionActivityEvents and seedBulkSignupsAndActivity build 10,000-row ClickHouse batches and insert them concurrently via Promise.all. Introduced usersToBackdate to update timestamps only for pre-existing users. Switched $token-refresh refresh_token_id generation to randomUUID().
Project Orchestration & Overlap
apps/backend/src/lib/seed-dummy-data.ts
seedDummyProject uses randomUUID() for generated project IDs when not provided, starts seedBulkSignupsAndActivity as an overlapping promise after user seeding, includes seedDummyTransactions in the concurrency group, and awaits the bulk-signups promise before completion.

Preview Project Redirect Validation

Layer / File(s) Summary
Runtime Internals Validation & Cache Refresh
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/preview-project-redirect.tsx
Validate that appInternals includes sendRequest and refreshOwnedProjects; call appInternals.refreshOwnedProjects() after creating the preview project and before router.push to refresh the owned-projects cache.

Layout Client Auto-login Guard

Layer / File(s) Summary
useRef Guard for Auto-login
apps/dashboard/src/app/(main)/(protected)/layout-client.tsx
Add autoLoginStarted ref and update the auto-login effect to return early when the ref is set or user exists; set the ref before starting the async auto-login.

Walkthrough UI Wiring & Labels

Layer / File(s) Summary
Sidebar Navigation & Runner
apps/dashboard/src/components/walkthrough/walkthrough-provider.tsx
Fix expand-button selection for collapsed sidebar sections and change runWalkthrough to a repeated while(!isCancelled()) loop across steps, retaining cancellation guards across phases.
Walkthrough Step Metadata & Metrics Attributes
apps/dashboard/src/components/walkthrough/walkthrough-steps.ts, apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/metrics-page.tsx
Update WALKTHROUGH_STEPS labels/keywords for overview, users, emails, payments, and replays steps; add data-walkthrough="overview-globe" and data-walkthrough="overview-metrics" attributes to overview UI containers.

CLI whoami

Layer / File(s) Summary
resolveSessionAuth() update
packages/stack-cli/src/commands/whoami.ts
Resolve session authentication by calling resolveSessionAuth() with no arguments instead of passing CLI flags.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • hexclave/stack-auth#1424: Overlapping changes to walkthrough step definitions around replays/session-replays in walkthrough-steps.ts.

Suggested reviewers

  • nams1570
  • N2D4

Poem

🐰 I seed in bulk, not one-by-one delight,
Tenancy Prisma hums through day and night,
ClickHouse batches leap in parallel flight,
A preview refresh and guard keep routes right,
Tiny ref, new UUIDs — the garden’s in sight.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and specifically describes the main performance optimization achieved—seeding speed improvement from ~15s to ~1.3s—which is the primary focus of the changeset.
Description check ✅ Passed The description comprehensively covers the seeding refactoring (bulk inserts, UUID optimization, concurrency), the preview-mode 404 fix, testing approach, and impact metrics.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch worktree-benchmark-preview-create

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 May 16, 2026

Greptile Summary

This PR replaces per-user sequential adminCreate calls in seedDummyProject with batched createMany inserts, cutting the /preview/create-project server-side time from ~15 s to ~1.3 s. It also swaps generateUuid() (a browser-safe polyfill) for node:crypto's randomUUID(), parallelises ClickHouse event flushes, and folds seedBulkSignupsAndActivity into a concurrent execution window instead of running it after all other seeds.

  • seedDummyUsers now pre-builds all ProjectUser, ContactChannel, AuthMethod, ProjectUserOAuthAccount, OAuthAuthMethod, and ProjectUserDirectPermission rows in memory and inserts them in a single retryTransaction, replacing ~86 sequential transactions.
  • seedBulkSignupsAndActivity skips the redundant UPDATE for freshly-created rows (since createMany already writes the correct createdAt/signedUpAt) and flushes ClickHouse events in larger parallel batches (10 000-row vs 500-row sequential).
  • Concurrency model in seedDummyProject fires bulkSignupsPromise immediately after user seeding and awaits it only at the very end, overlapping it with lighter config and email seeds.

Confidence Score: 3/5

The bulk-insert path produces correct data under the happy path, but the floated bulkSignupsPromise can become an unhandled rejection if either intermediate Promise.all block throws, potentially leaving the database in a partially-seeded state.

The new concurrent execution model fires bulkSignupsPromise and then runs two separate await Promise.all(...) blocks before finally awaiting it. If either of those blocks throws, bulkSignupsPromise is never awaited: background database writes continue silently, and a subsequent rejection from that promise has no handler. On modern Node.js runtimes an unhandled rejection terminates the process.

apps/backend/src/lib/seed-dummy-data.ts — specifically the bulkSignupsPromise lifetime and the two Promise.all blocks that precede its final await.

Important Files Changed

Filename Overview
apps/backend/src/lib/seed-dummy-data.ts Bulk-insert rewrite of seedDummyUsers/seedBulkSignupsAndActivity for ~11× speedup; introduces a floated promise that can become an unhandled rejection if an intermediate Promise.all block throws.

Sequence Diagram

sequenceDiagram
    participant C as seedDummyProject
    participant PU as seedDummyUsers (bulk)
    participant BSA as seedBulkSignupsAndActivity
    participant PA1 as Promise.all #1 (config overrides)
    participant PA2 as Promise.all #2 (emails/txns/replays)
    participant CH as ClickHouse

    C->>PU: await (sequential — heavy Postgres)
    PU-->>C: userEmailToId Map

    C->>BSA: fire (Promise, not awaited yet)
    activate BSA

    C->>PA1: await Promise.all [overrideBranchConfig, overrideEnvConfig, ...]
    PA1-->>C: done (or throws ⚠️ — bulkSignupsPromise abandoned)

    C->>PA2: await Promise.all [seedDummyTransactions, seedDummyEmails, ...]
    PA2-->>C: done (or throws ⚠️ — bulkSignupsPromise abandoned)

    BSA->>BSA: createMany ProjectUsers
    BSA->>CH: Promise.all batch inserts (10 000-row batches, parallel)
    CH-->>BSA: ack
    BSA-->>C: resolves

    C->>C: await bulkSignupsPromise (line 2092)
Loading

Comments Outside Diff (1)

  1. apps/backend/src/lib/seed-dummy-data.ts, line 1978-1992 (link)

    P1 Unhandled rejection / stray background write if Promise.all throws

    bulkSignupsPromise is started at line 1978 and only awaited at line 2092, but there are two await Promise.all(...) calls between those points (lines 1983 and 2067). If either of those Promise.all blocks rejects (e.g. a Postgres error in overrideBranchConfigOverride or seedDummyTransactions), the function unwinds before reaching await bulkSignupsPromise. The promise is then abandoned: if it later resolves, the database is left in a partially-seeded state without the caller knowing; if it rejects, Node.js emits an unhandled-rejection warning (and in newer runtimes, terminates the process). A try/finally that aborts or awaits bulkSignupsPromise before re-throwing would prevent this.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/backend/src/lib/seed-dummy-data.ts
    Line: 1978-1992
    
    Comment:
    **Unhandled rejection / stray background write if `Promise.all` throws**
    
    `bulkSignupsPromise` is started at line 1978 and only awaited at line 2092, but there are two `await Promise.all(...)` calls between those points (lines 1983 and 2067). If either of those `Promise.all` blocks rejects (e.g. a Postgres error in `overrideBranchConfigOverride` or `seedDummyTransactions`), the function unwinds before reaching `await bulkSignupsPromise`. The promise is then abandoned: if it later resolves, the database is left in a partially-seeded state without the caller knowing; if it rejects, Node.js emits an unhandled-rejection warning (and in newer runtimes, terminates the process). A `try/finally` that aborts or awaits `bulkSignupsPromise` before re-throwing would prevent this.
    
    How can I resolve this? If you propose a fix, please make it concise.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
apps/backend/src/lib/seed-dummy-data.ts:1978-1992
**Unhandled rejection / stray background write if `Promise.all` throws**

`bulkSignupsPromise` is started at line 1978 and only awaited at line 2092, but there are two `await Promise.all(...)` calls between those points (lines 1983 and 2067). If either of those `Promise.all` blocks rejects (e.g. a Postgres error in `overrideBranchConfigOverride` or `seedDummyTransactions`), the function unwinds before reaching `await bulkSignupsPromise`. The promise is then abandoned: if it later resolves, the database is left in a partially-seeded state without the caller knowing; if it rejects, Node.js emits an unhandled-rejection warning (and in newer runtimes, terminates the process). A `try/finally` that aborts or awaits `bulkSignupsPromise` before re-throwing would prevent this.

Reviews (1): Last reviewed commit: "Speed up dummy-project seeding (preview ..." | Re-trigger Greptile

Copy link
Copy Markdown

@vercel vercel Bot left a comment

Choose a reason for hiding this comment

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

Additional Suggestion:

seedBulkSignupsAndActivity function creates ProjectUser and ContactChannel rows for new users but does not create corresponding ProjectUserDirectPermission rows with default permissions, breaking expected permission behavior for bulk-seeded users.

Fix on Vercel

In preview mode the dashboard's /projects page renders PreviewProjectRedirect,
which POSTs /internal/preview/create-project and then router.push()es to
/projects/<new-id>. It never refreshed the client-side owned-projects cache,
so the [projectId] route's useAdminApp() read a stale list, failed to find the
just-created project, and called notFound() — showing a 404.

Refresh the owned-projects cache before navigating, matching what the normal
create-project flow in page-client.tsx already does.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/preview-project-redirect.tsx (1)

16-27: ⚡ Quick win

Avoid any/as for internals extraction; use a type guard instead.

Line 16 and Line 24-27 currently bypass the type system. Please narrow from unknown via a runtime type guard and return the narrowed value directly.

Proposed refactor
+type AppInternals = {
+  sendRequest: (path: string, options: RequestInit, type: string) => Promise<Response>;
+  refreshOwnedProjects: () => Promise<void>;
+};
+
+function isAppInternals(value: unknown): value is AppInternals {
+  return (
+    typeof value === "object" &&
+    value !== null &&
+    "sendRequest" in value &&
+    typeof (value as { sendRequest?: unknown }).sendRequest === "function" &&
+    "refreshOwnedProjects" in value &&
+    typeof (value as { refreshOwnedProjects?: unknown }).refreshOwnedProjects === "function"
+  );
+}
+
   const appInternals = useMemo(() => {
-    const internals = Reflect.get(app as any, stackAppInternalsSymbol);
-    if (
-      !internals ||
-      typeof internals.sendRequest !== "function" ||
-      typeof internals.refreshOwnedProjects !== "function"
-    ) {
+    const internals = Reflect.get(app, stackAppInternalsSymbol);
+    if (!isAppInternals(internals)) {
       throw new Error("The Stack client app cannot send internal requests.");
     }
-    return internals as {
-      sendRequest: (path: string, options: RequestInit, type: string) => Promise<Response>,
-      refreshOwnedProjects: () => Promise<void>,
-    };
+    return internals;
   }, [app]);

As per coding guidelines: "**/*.{ts,tsx}: Do NOT use as/any/type casts ... unless you specifically asked the user about it."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/projects/preview-project-redirect.tsx
around lines 16 - 27, Replace the unchecked cast around Reflect.get(app as any,
stackAppInternalsSymbol) by treating the result as unknown and validating it
with a runtime type guard: implement a function isStackAppInternals(value:
unknown): value is { sendRequest: (path: string, options: RequestInit, type:
string) => Promise<Response>; refreshOwnedProjects: () => Promise<void>; } that
checks value is an object and both sendRequest and refreshOwnedProjects are
functions, then call Reflect.get(...) into a const internals: unknown, run the
guard and throw the same error if it returns false, and finally return the
narrowed internals (no use of any/as) so the return type is inferred from the
type predicate; reference symbols: stackAppInternalsSymbol, internals,
isStackAppInternals, sendRequest, refreshOwnedProjects.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@apps/dashboard/src/app/`(main)/(protected)/(outside-dashboard)/projects/preview-project-redirect.tsx:
- Around line 16-27: Replace the unchecked cast around Reflect.get(app as any,
stackAppInternalsSymbol) by treating the result as unknown and validating it
with a runtime type guard: implement a function isStackAppInternals(value:
unknown): value is { sendRequest: (path: string, options: RequestInit, type:
string) => Promise<Response>; refreshOwnedProjects: () => Promise<void>; } that
checks value is an object and both sendRequest and refreshOwnedProjects are
functions, then call Reflect.get(...) into a const internals: unknown, run the
guard and throw the same error if it returns false, and finally return the
narrowed internals (no use of any/as) so the return type is inferred from the
type predicate; reference symbols: stackAppInternalsSymbol, internals,
isStackAppInternals, sendRequest, refreshOwnedProjects.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 46e10b70-cafa-45a2-958a-9199f2c52675

📥 Commits

Reviewing files that changed from the base of the PR and between 8d400f5 and 2b0393b.

📒 Files selected for processing (1)
  • apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/projects/preview-project-redirect.tsx

LayoutClient's auto-login effect had no run-once guard. React StrictMode
(on by default in Next dev) double-invokes effects, and both invocations
ran while `user` was still null — so in preview mode each generated a
fresh `preview-*@Preview.stack-auth.com` email and signed up a *separate*
preview user.

With two users created per load, the session settles on one while the
preview project may have been created for the other, so the project page
renders a 404 ("does not have access to it").

Guard the effect with a ref so the auto-login (and preview-user creation)
runs at most once per mount.
BilalG1 added 2 commits May 19, 2026 16:30
Conflict in apps/dashboard/src/app/(main)/(protected)/layout-client.tsx
resolved by combining both sides: keep dev's `useUser({ or: "anonymous-if-exists[deprecated]" })`
option and remote-development-environment early return, plus this branch's
`autoLoginStarted` ref guard that prevents StrictMode from minting duplicate
preview users. Dropped dev's redundant `if (user) return;` inside autoLogin
since the existing `if (user || autoLoginStarted.current) return;` covers it.
…hoami

`resolveSessionAuth` takes no arguments; the trailing `flags` was breaking
typecheck.
The 7-step walkthrough tour shown in preview mode (NEXT_PUBLIC_STACK_IS_PREVIEW)
had several silently-skipping steps after recent dashboard changes:

- Overview revamp (#1238) dropped the data-walkthrough hooks for the globe and
  metrics widgets — re-attach them to the new GlobeSectionWithData column and
  the AnalyticsChartWidget column in metrics-page.tsx.
- Session-replays/data-grid PR (#1424) renamed the anchor to "analytics-replays"
  and the sidebar nav was relabeled from "Session Replays" to "Replays" (under
  a collapsed Analytics group) — update the step id and sidebarNavLabel.
- The sidebar's collapsible-group expand button is now a <button aria-expanded>
  *nested inside* the prev-sibling header div, not the prev sibling itself —
  navigateViaSidebar's expansion logic only matched the prevSibling directly,
  so step 7 silently skipped. Look inside prevSibling for the chevron too.
- The cmdkSearch strings "Authentication" / "Emails" / "Payments" were
  matching the wrong CmdK result first (auth-methods / email-themes /
  payments/products/new) — tighten them to "Users" / "Emails sent" /
  "Products" so they land on the pages that actually have the spotlight
  anchors.
- Loop the walkthrough back to step 1 after step 7 instead of stopping; add
  sidebarNavLabel "Overview" to step 1 so the loop can navigate back from
  /session-replays.

Verified end-to-end in a real browser: all 7 spotlights render and the loop
continues indefinitely across iterations.
Comment thread apps/dashboard/src/components/walkthrough/walkthrough-provider.tsx
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