Skip to content

re-enable posthog recordings#1404

Merged
N2D4 merged 1 commit intodevfrom
chore/enable-posthog-recordings
May 4, 2026
Merged

re-enable posthog recordings#1404
N2D4 merged 1 commit intodevfrom
chore/enable-posthog-recordings

Conversation

@mantrakp04
Copy link
Copy Markdown
Collaborator

@mantrakp04 mantrakp04 commented May 4, 2026

Summary by CodeRabbit

Bug Fixes

  • Updated session recording input masking configuration to provide more granular control over data privacy during session recording.

Copilot AI review requested due to automatic review settings May 4, 2026 06:31
@vercel
Copy link
Copy Markdown

vercel Bot commented May 4, 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 4, 2026 6:37am
stack-backend Ready Ready Preview, Comment May 4, 2026 6:37am
stack-dashboard Ready Ready Preview, Comment May 4, 2026 6:37am
stack-demo Ready Ready Preview, Comment May 4, 2026 6:37am
stack-docs Ready Ready Preview, Comment May 4, 2026 6:37am
stack-preview-backend Ready Ready Preview, Comment May 4, 2026 6:37am
stack-preview-dashboard Ready Ready Preview, Comment May 4, 2026 6:37am

@mantrakp04 mantrakp04 requested a review from N2D4 May 4, 2026 06:31
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 4, 2026

📝 Walkthrough

Walkthrough

PostHog session recording configuration in the instrumentation client is updated. The change replaces disable_session_recording: true with an explicit session_recording object that enables recording while masking only password inputs, allowing other form data to be captured.

Changes

PostHog Session Recording Configuration

Layer / File(s) Summary
Configuration Update
apps/dashboard/instrumentation-client.ts
PostHog init options replace disable_session_recording: true with a session_recording object containing maskAllInputs: false and maskInputOptions.password: true to enable selective input masking.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~5 minutes

Poem

🐰 A rabbit hopped through configs with glee,
Unlocking the sessions for all to see—
Passwords masked tight, all others laid bare,
Recording the clicks floating through the air! 🎥✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description only contains the template comment and lacks any meaningful explanation of the changes, rationale, or impact of re-enabling PostHog recordings. Add a detailed description explaining why PostHog recordings are being re-enabled, what the new configuration does, and any relevant context or testing performed.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 're-enable posthog recordings' directly aligns with the main change: modifying PostHog session recording configuration from disabled to enabled with specific masking settings.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/enable-posthog-recordings

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
Review rate limit: 7/8 reviews remaining, refill in 7 minutes and 30 seconds.

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

Greptile Summary

This PR re-enables PostHog session recording by replacing disable_session_recording: true with a session_recording config block that only masks type=\"password\" inputs.

  • Known Sentry regression: The original flag was added specifically to suppress issue STACK-SERVER-1NK ("Called on script loaded before session recording is available"). Re-enabling recording without addressing the underlying race condition will likely bring that issue back.
  • Sensitive input exposure: maskAllInputs: false means PostHog will record OAuth client secrets, webhook signing secrets, API keys, and OTP codes typed into the dashboard — none of which use type=\"password\". This should be maskAllInputs: true with selective unmasking, or every sensitive field must carry data-ph-no-capture.

Confidence Score: 2/5

Not safe to merge — re-introduces a known Sentry bug and risks capturing sensitive credentials in session recordings.

Two P1 findings: a known Sentry race condition (STACK-SERVER-1NK) being re-introduced with no mitigation, and a privacy/security issue where sensitive non-password inputs (OAuth secrets, API keys, OTP codes) will be captured in PostHog recordings due to maskAllInputs: false.

apps/dashboard/instrumentation-client.ts requires attention for both the Sentry race condition and the input masking configuration.

Security Review

  • Sensitive input capture (instrumentation-client.ts): maskAllInputs: false with only password-type masking means OAuth client secrets, webhook signing secrets, API keys, and TOTP/OTP codes entered in the dashboard will be recorded by PostHog and stored in the PostHog project.

Important Files Changed

Filename Overview
apps/dashboard/instrumentation-client.ts Re-enables PostHog session recording with maskAllInputs:false — likely re-introduces a known Sentry race condition (STACK-SERVER-1NK) and may capture sensitive non-password inputs (OAuth secrets, API keys, OTP codes) in recordings.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant PostHog
    participant Sentry

    Browser->>PostHog: posthog.init() — session_recording enabled
    Browser->>Sentry: Sentry.init() + replayIntegration()
    Note over Browser,Sentry: Both session recorders now active simultaneously

    Browser->>PostHog: Records all inputs (except type=password)
    Note over Browser,PostHog: OAuth secrets, API keys, OTP codes captured
    Browser->>Sentry: Replay + posthog.sentryIntegration()

    Note over Browser,PostHog: Risk: STACK-SERVER-1NK race condition
Loading

Fix All in Claude Code Fix All in Cursor Fix All in Codex

Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
apps/dashboard/instrumentation-client.ts:17-22
**Known Sentry issue may resurface**

The previous `disable_session_recording: true` was explicitly added to prevent Sentry issue STACK-SERVER-1NK ("Called on script loaded before session recording is available"). Re-enabling session recording without addressing the root cause of that race condition means the same issue is likely to reappear. The new `session_recording` config block does not mitigate the timing problem that triggered the original bug — it only configures what gets masked.

### Issue 2 of 2
apps/dashboard/instrumentation-client.ts:17-22
**Sensitive non-password inputs will be captured in recordings**

With `maskAllInputs: false`, PostHog will record keystrokes in every input except `type="password"` fields. In the Stack Auth dashboard, users routinely enter OAuth client secrets, webhook signing secrets, API keys, and TOTP/OTP codes — none of which use `type="password"`. These values would be captured in session recordings and accessible to anyone with access to the PostHog project. Consider either setting `maskAllInputs: true` and selectively unmasking safe fields, or ensuring every sensitive input in the dashboard carries a `data-ph-no-capture` attribute.

Reviews (1): Last reviewed commit: "re-enable posthog recordings" | Re-trigger Greptile

Comment on lines +17 to +22
session_recording: {
maskAllInputs: false,
maskInputOptions: {
password: true,
},
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Known Sentry issue may resurface

The previous disable_session_recording: true was explicitly added to prevent Sentry issue STACK-SERVER-1NK ("Called on script loaded before session recording is available"). Re-enabling session recording without addressing the root cause of that race condition means the same issue is likely to reappear. The new session_recording config block does not mitigate the timing problem that triggered the original bug — it only configures what gets masked.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/dashboard/instrumentation-client.ts
Line: 17-22

Comment:
**Known Sentry issue may resurface**

The previous `disable_session_recording: true` was explicitly added to prevent Sentry issue STACK-SERVER-1NK ("Called on script loaded before session recording is available"). Re-enabling session recording without addressing the root cause of that race condition means the same issue is likely to reappear. The new `session_recording` config block does not mitigate the timing problem that triggered the original bug — it only configures what gets masked.

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

Fix in Claude Code Fix in Cursor Fix in Codex

Comment on lines +17 to +22
session_recording: {
maskAllInputs: false,
maskInputOptions: {
password: true,
},
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 security Sensitive non-password inputs will be captured in recordings

With maskAllInputs: false, PostHog will record keystrokes in every input except type="password" fields. In the Stack Auth dashboard, users routinely enter OAuth client secrets, webhook signing secrets, API keys, and TOTP/OTP codes — none of which use type="password". These values would be captured in session recordings and accessible to anyone with access to the PostHog project. Consider either setting maskAllInputs: true and selectively unmasking safe fields, or ensuring every sensitive input in the dashboard carries a data-ph-no-capture attribute.

Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/dashboard/instrumentation-client.ts
Line: 17-22

Comment:
**Sensitive non-password inputs will be captured in recordings**

With `maskAllInputs: false`, PostHog will record keystrokes in every input except `type="password"` fields. In the Stack Auth dashboard, users routinely enter OAuth client secrets, webhook signing secrets, API keys, and TOTP/OTP codes — none of which use `type="password"`. These values would be captured in session recordings and accessible to anyone with access to the PostHog project. Consider either setting `maskAllInputs: true` and selectively unmasking safe fields, or ensuring every sensitive input in the dashboard carries a `data-ph-no-capture` attribute.

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

Fix in Claude Code Fix in Cursor Fix in Codex

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR re-enables PostHog session recordings in the dashboard client instrumentation by switching from disabling session recording to configuring session_recording options during posthog.init.

Changes:

  • Removed disable_session_recording: true from PostHog initialization.
  • Added session_recording configuration (masking password inputs, leaving other inputs unmasked).

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

Comment on lines +17 to +22
session_recording: {
maskAllInputs: false,
maskInputOptions: {
password: true,
},
},
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: 1

🤖 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/dashboard/instrumentation-client.ts`:
- Around line 17-21: The session recording config sets
session_recording.maskAllInputs to false which intentionally exposes
non-password inputs; update the code by adding a clear inline comment next to
session_recording (and reference maskAllInputs and maskInputOptions) stating the
compliance rationale, the specific documented data-handling policy or consent
mechanism that authorizes this exposure, and any required user-consent or
opt-out steps; ensure the comment names the controlling policy/version and notes
any PII redaction expectations so reviewers can verify privacy coverage.
🪄 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: 972dd2d6-3347-4586-bb4c-4d7ddeadf990

📥 Commits

Reviewing files that changed from the base of the PR and between d2f2fb0 and 786562c.

📒 Files selected for processing (1)
  • apps/dashboard/instrumentation-client.ts

Comment on lines +17 to +21
session_recording: {
maskAllInputs: false,
maskInputOptions: {
password: true,
},
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

❓ Verification inconclusive

Script executed:

# First, check if the file exists and read it
if [ -f "apps/dashboard/instrumentation-client.ts" ]; then
  echo "=== File exists, reading content ==="
  wc -l apps/dashboard/instrumentation-client.ts
  cat -n apps/dashboard/instrumentation-client.ts
else
  echo "File not found at apps/dashboard/instrumentation-client.ts"
  # Try to find it
  fd "instrumentation-client" --type f
fi

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 0

stdout:

=== File exists, reading content ===
73 apps/dashboard/instrumentation-client.ts
     1	// This file configures the initialization of Sentry on the client.
     2	// The config you add here will be used whenever a users loads a page in their browser.
     3	// https://docs.sentry.io/platforms/javascript/guides/nextjs/
     4	
     5	import { getPublicEnvVar } from "@/lib/env";
     6	import * as Sentry from "@sentry/nextjs";
     7	import { getBrowserCompatibilityReport } from "@stackframe/stack-shared/dist/utils/browser-compat";
     8	import { sentryBaseConfig } from "@stackframe/stack-shared/dist/utils/sentry";
     9	import { nicify } from "@stackframe/stack-shared/dist/utils/strings";
    10	import posthog from "posthog-js";
    11	
    12	export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;
    13	
    14	const postHogKey = getPublicEnvVar('NEXT_PUBLIC_POSTHOG_KEY') ?? "phc_vIUFi0HzHo7oV26OsaZbUASqxvs8qOmap1UBYAutU4k";
    15	if (postHogKey.length > 5) {
    16	  posthog.init(postHogKey, {
    17	    session_recording: {
    18	      maskAllInputs: false,
    19	      maskInputOptions: {
    20	        password: true,
    21	      },
    22	    },
    23	    defaults: '2025-11-30',
    24	    api_host: "/consume",
    25	    ui_host: "https://eu.i.posthog.com",
    26	  });
    27	}
    28	
    29	
    30	Sentry.init({
    31	  ...sentryBaseConfig,
    32	
    33	  dsn: getPublicEnvVar('NEXT_PUBLIC_SENTRY_DSN'),
    34	
    35	  enabled: process.env.NODE_ENV !== "development" && !process.env.CI,
    36	
    37	  // You can remove this option if you're not planning to use the Sentry Session Replay feature:
    38	  integrations: [
    39	    Sentry.replayIntegration({
    40	      // Additional Replay configuration goes in here, for example:
    41	      maskAllText: false,
    42	      maskAllInputs: false,
    43	      blockAllMedia: false,
    44	    }),
    45	    posthog.sentryIntegration({
    46	      organization: "stackframe-pw",
    47	      projectId: 4507084192219136,
    48	    }),
    49	  ],
    50	
    51	  // Add exception metadata to the event
    52	  beforeSend(event, hint) {
    53	    const error = hint.originalException;
    54	    let nicified;
    55	    try {
    56	      nicified = nicify(error, { maxDepth: 8 });
    57	    } catch (e) {
    58	      nicified = `Error occurred during nicification: ${e}`;
    59	    }
    60	    if (error instanceof Error) {
    61	      event.extra = {
    62	        ...event.extra,
    63	        cause: error.cause,
    64	        errorProps: {
    65	          ...error,
    66	        },
    67	        nicifiedError: nicified,
    68	        clientBrowserCompatibility: getBrowserCompatibilityReport(),
    69	      };
    70	    }
    71	    return event;
    72	  },
    73	});


Script executed:

# Check if DESIGN-GUIDE.md exists in the dashboard directory
find apps/dashboard -name "DESIGN-GUIDE.md" -o -name "design-guide.md" -o -name "*design*" -type f | head -20

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 0

stdout:

apps/dashboard/DESIGN-GUIDE.md


Script executed:

# Search for session_recording or session recording references in the codebase
rg "session_recording|session.recording|maskAllInputs" --type ts --type tsx -i -B 2 -A 2

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 2

stderr:

unrecognized file type: tsx


Script executed:

# Try the rg search without the tsx type flag
rg "session_recording|maskAllInputs|maskInputOptions" -B 3 -A 3

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 0

stdout:

packages/template/src/lib/stack-app/apps/implementations/session-replay.ts-   *
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts-   * `@default` true
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts-   */
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts:  maskAllInputs?: boolean,
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts-  /**
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts-   * A CSS class name or RegExp. Elements with a matching class will be blocked
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts-   * (replaced with a placeholder in the recording).
--
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts-          runAsynchronously(() => this._flush({ keepalive: false }));
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts-        }
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts-      },
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts:      maskAllInputs: this._replayOptions.maskAllInputs ?? true,
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts-      ...(this._replayOptions.blockClass !== undefined ? { blockClass: this._replayOptions.blockClass } : {}),
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts-      ...(this._replayOptions.blockSelector !== undefined ? { blockSelector: this._replayOptions.blockSelector } : {}),
packages/template/src/lib/stack-app/apps/implementations/session-replay.ts-    }) ?? null;
--
docs/src/stack.ts-  analytics: {
docs/src/stack.ts-    replays: {
docs/src/stack.ts-      enabled: true,
docs/src/stack.ts:      maskAllInputs: false,
docs/src/stack.ts-    },
docs/src/stack.ts-  },
docs/src/stack.ts-});
--
docs/content/docs/(guides)/apps/analytics.mdx-    replays: {
docs/content/docs/(guides)/apps/analytics.mdx-      enabled: true,
docs/content/docs/(guides)/apps/analytics.mdx-      // Optional. Defaults to true.
docs/content/docs/(guides)/apps/analytics.mdx:      maskAllInputs: true,
docs/content/docs/(guides)/apps/analytics.mdx-    },
docs/content/docs/(guides)/apps/analytics.mdx-  },
docs/content/docs/(guides)/apps/analytics.mdx-});
docs/content/docs/(guides)/apps/analytics.mdx-```
docs/content/docs/(guides)/apps/analytics.mdx-
docs/content/docs/(guides)/apps/analytics.mdx:`maskAllInputs` defaults to `true`, so form fields are masked unless you explicitly disable it.
docs/content/docs/(guides)/apps/analytics.mdx-
docs/content/docs/(guides)/apps/analytics.mdx-## Best Practices
docs/content/docs/(guides)/apps/analytics.mdx-
docs/content/docs/(guides)/apps/analytics.mdx-1. **Use Tables for quick incident triage**: the Tables UI is the fastest way to inspect recent `events` rows without writing SQL.
docs/content/docs/(guides)/apps/analytics.mdx-2. **Use Queries for repeatable analysis**: save important SQL in folders, and scope queries with filters/`LIMIT` so they stay within result and timeout limits.
docs/content/docs/(guides)/apps/analytics.mdx-3. **Use Replays for behavioral debugging**: start from an event pattern, then inspect matching session replays to understand what users actually did.
docs/content/docs/(guides)/apps/analytics.mdx:4. **Keep replay privacy defaults on**: leave `maskAllInputs` enabled unless you have a specific reason and a data-handling policy for unmasked inputs.
--
docs-mintlify/guides/apps/analytics/overview.mdx-    replays: {
docs-mintlify/guides/apps/analytics/overview.mdx-      enabled: true,
docs-mintlify/guides/apps/analytics/overview.mdx-      // Optional. Defaults to true.
docs-mintlify/guides/apps/analytics/overview.mdx:      maskAllInputs: true,
docs-mintlify/guides/apps/analytics/overview.mdx-    },
docs-mintlify/guides/apps/analytics/overview.mdx-  },
docs-mintlify/guides/apps/analytics/overview.mdx-});
docs-mintlify/guides/apps/analytics/overview.mdx-```
docs-mintlify/guides/apps/analytics/overview.mdx-
docs-mintlify/guides/apps/analytics/overview.mdx:`maskAllInputs` defaults to `true`, so form fields are masked unless you explicitly disable it.
docs-mintlify/guides/apps/analytics/overview.mdx-
docs-mintlify/guides/apps/analytics/overview.mdx-## Best Practices
docs-mintlify/guides/apps/analytics/overview.mdx-
docs-mintlify/guides/apps/analytics/overview.mdx-1. **Use Tables for quick incident triage**: the Tables UI is the fastest way to inspect recent `events` rows without writing SQL.
docs-mintlify/guides/apps/analytics/overview.mdx-2. **Use Queries for repeatable analysis**: save important SQL in folders, and scope queries with filters/`LIMIT` so they stay within result and timeout limits.
docs-mintlify/guides/apps/analytics/overview.mdx-3. **Use Replays for behavioral debugging**: start from an event pattern, then inspect matching session replays to understand what users actually did.
docs-mintlify/guides/apps/analytics/overview.mdx:4. **Keep replay privacy defaults on**: leave `maskAllInputs` enabled unless you have a specific reason and a data-handling policy for unmasked inputs.
--
apps/dashboard/instrumentation-client.ts-const postHogKey = getPublicEnvVar('NEXT_PUBLIC_POSTHOG_KEY') ?? "phc_vIUFi0HzHo7oV26OsaZbUASqxvs8qOmap1UBYAutU4k";
apps/dashboard/instrumentation-client.ts-if (postHogKey.length > 5) {
apps/dashboard/instrumentation-client.ts-  posthog.init(postHogKey, {
apps/dashboard/instrumentation-client.ts:    session_recording: {
apps/dashboard/instrumentation-client.ts:      maskAllInputs: false,
apps/dashboard/instrumentation-client.ts:      maskInputOptions: {
apps/dashboard/instrumentation-client.ts-        password: true,
apps/dashboard/instrumentation-client.ts-      },
apps/dashboard/instrumentation-client.ts-    },
--
apps/dashboard/instrumentation-client.ts-    Sentry.replayIntegration({
apps/dashboard/instrumentation-client.ts-      // Additional Replay configuration goes in here, for example:
apps/dashboard/instrumentation-client.ts-      maskAllText: false,
apps/dashboard/instrumentation-client.ts:      maskAllInputs: false,
apps/dashboard/instrumentation-client.ts-      blockAllMedia: false,
apps/dashboard/instrumentation-client.ts-    }),
apps/dashboard/instrumentation-client.ts-    posthog.sentryIntegration({
--
apps/dashboard/src/stack.tsx-  },
apps/dashboard/src/stack.tsx-  analytics: {
apps/dashboard/src/stack.tsx-    replays: {
apps/dashboard/src/stack.tsx:      maskAllInputs: false,
apps/dashboard/src/stack.tsx-      enabled: !isPreview,
apps/dashboard/src/stack.tsx-    },
apps/dashboard/src/stack.tsx-  },
--
apps/backend/instrumentation-client.ts-    Sentry.replayIntegration({
apps/backend/instrumentation-client.ts-      // Additional Replay configuration goes in here, for example:
apps/backend/instrumentation-client.ts-      maskAllText: false,
apps/backend/instrumentation-client.ts:      maskAllInputs: false,
apps/backend/instrumentation-client.ts-      blockAllMedia: false,
apps/backend/instrumentation-client.ts-    }),
apps/backend/instrumentation-client.ts-  ],


Script executed:

# Check the DESIGN-GUIDE.md for any guidance on session recording or privacy
cat apps/dashboard/DESIGN-GUIDE.md

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 0

stdout:

# Stack Auth Dashboard Design Guide

This guide defines the source of truth for dashboard UI design and implementation.
It is intentionally written for both humans and AI agents.

If this guide conflicts with older examples in the codebase, follow this guide.

---

## 1) Core Principle (Non-Negotiable)

Always prefer components from `apps/dashboard/src/components/design-components`.

- Do not build new ad-hoc visual primitives (for example custom `GlassCard`, custom `ChartCard`, custom badge pills, custom pill toggles, custom list rows) if a design-components component exists.
- If the desired UI can be achieved by tweaking/customizing/extending a design-components component, do that instead of creating a page-local alternative.
- In all cases, default to design-components first; only use a non-design-components approach when there is absolutely no viable way to achieve the result with design-components.
- Use `@/components/ui/*` primitives only when no design-components equivalent exists, or when the design-components component intentionally wraps the primitive.
- Match existing design-components behavior:
  - hover-exit transitions (`transition-* duration-150 hover:transition-none`)
  - glassmorphic surfaces where appropriate
  - semantic variants for alerts/badges
  - async-safe click handlers via design-components primitives

---

## 2) Fast Decision Tree

Use this when implementing a new dashboard UI quickly:

1. Need a section container/card?
   - Use `DesignCard`.
   - For chart-heavy analytics surfaces (especially Recharts tooltips/overflow), use `DesignAnalyticsCard`.
2. Need user-facing status/info/warning/error message?
   - Use `DesignAlert`.
3. Need small semantic label (sent, failed, queued, active)?
   - Use `DesignBadge`.
4. Need user action button, especially async?
   - Use `DesignButton`.
5. Need text input/selectors in design-components surfaces?
   - Use `DesignInput` and `DesignSelectorDropdown`.
6. Need a segmented/pill switcher?
   - Use `DesignPillToggle`.
7. Need category tabs with count badges?
   - Use `DesignCategoryTabs`.
8. Need row/list item with action buttons/menu?
   - Use `DesignListItemRow` (or `DesignUserList` for user rows).
9. Need settings/property grid editor?
   - Use `DesignEditableGrid`.
10. Need interactive / sortable / searchable data table?
   - Use `DataGrid` + `useDataSource` + `createDefaultDataGridState` from `@stackframe/dashboard-ui-components`.
11. Need dropdown action/selector/toggle menu?
   - Use `DesignMenu`.

---

## 3) Allowed Base UI Usage

`@/components/ui/*` can still be used for primitives that do not currently have a design-components equivalent:

- dialogs/sheets/popovers (`ActionDialog`, `FormDialog`, `Sheet`, etc.)
- complex layout containers where design-components does not provide one
- highly specialized editor internals

When using a primitive directly:

- keep visual style compatible with design-components surfaces
- do not duplicate a design-components component API locally
- consider creating/extending a design-components component instead of repeating local patterns

---

## 3.1) Best Practices (Always Apply)

- Build with design-components primitives first, then add minimal page-level styling.
- Keep components composable: pass data/config via props instead of hardcoding display logic.
- Favor semantic APIs (`variant`, `color`, `gradient`) over raw class-heavy style forks.
- Use accessible defaults:
  - clear labels for icon-only controls
  - keyboard focus visibility (`focus-visible:*`)
  - semantic roles where applicable
- Keep behavior deterministic:
  - one visual language per screen
  - one status-color mapping across all routes
  - one interaction pattern per control type

---

## 3.2) Color System (Light + Dark Theme)

Use semantic tokens and design-components variants first. Avoid ad-hoc hardcoded colors unless there is a documented semantic reason.

### Theme token usage priority

1. Use component variants (`DesignAlert`, `DesignBadge`, `DesignCard` gradient, tab/toggle gradients).
2. Use semantic Tailwind tokens (`bg-background`, `text-foreground`, `text-muted-foreground`, `border-border`).
3. Use opacity layers for subtle surfaces (for example `bg-foreground/[0.03]`).

### Surface and text rules

- Primary surfaces: `bg-background` + subtle ring/border.
- Secondary/muted surfaces: low-opacity foreground overlays.
- Primary text: `text-foreground`.
- Secondary text: `text-muted-foreground`.
- Never use pure black/white hardcoded utility values for app UI text/surfaces.

### Semantic state colors

- Success: green/emerald (`DesignAlert variant="success"`, `DesignBadge color="green"`)
- Error: red (`DesignAlert variant="error"`, `DesignBadge color="red"`)
- Warning: orange/amber (`DesignAlert variant="warning"`, `DesignBadge color="orange"`)
- Info: blue/cyan (`DesignAlert variant="info"`, `DesignBadge color="blue"` or `"cyan"`)

### Light vs dark guidance

- Ensure every custom color choice has dark-mode readability.
- In dark mode, reduce high-contrast fills and rely on low-opacity tints + rings.
- Keep contrast high for text and medium for non-critical chrome.

---

## 3.3) Typography System

Keep typography concise and consistent. Prefer existing design-components/header patterns.

### Recommended scale and usage

- Page title: `text-xl sm:text-2xl font-semibold tracking-tight`
- Section heading: `text-xs font-semibold uppercase tracking-wider`
- Body/default control text: `text-sm`
- Secondary metadata: `text-xs text-muted-foreground`
- Micro labels/badges: `text-[10px]` to `text-[11px]`

### Typography rules

- Use uppercase tracking only for section labels and compact metadata headings.
- Avoid introducing new arbitrary font sizes when an existing size serves the purpose.
- Keep line-length short in cards and alerts for scanability.
- For numeric/stat values, use tabular numerals where needed.

---

## 3.4) Spacing and Layout Guidelines

Use a compact, repeatable spacing rhythm.

### Spacing rhythm

- `gap-1` (4px): tight icon/text coupling
- `gap-2` (8px): compact control spacing
- `gap-3` (12px): standard row spacing
- `gap-4` (16px): section-internal spacing
- `gap-5` (20px): larger section grouping

### Padding rhythm

- `p-2` / `px-3 py-2`: compact controls
- `p-3`: standard compact blocks
- `p-4` to `p-5`: card content/major sections

### Layout rules

- Use `rounded-2xl` for major containers/cards.
- Use `rounded-xl` for controls (inputs, toggles, small cards).
- Preserve visual hierarchy:
  - page spacing > section spacing > control spacing
- Avoid mixing unrelated spacing scales inside a single component.

---

## 3.5) Animation and Micro-Interactions

Motion should feel immediate, subtle, and informative.

### Core motion rules

- No hover-enter delay transitions.
- Use hover-exit transitions: `transition-* duration-150 hover:transition-none`.
- Keep interaction transitions short and subtle.
- Do not use large animated movement in dense admin surfaces.

### Duration guidance

- `50ms-100ms`: very small icon feedback
- `150ms`: standard hover/focus/press recovery
- `200ms-300ms`: layout/state transitions (panel collapse, sheet-like reveals)
- `>300ms`: only ambient/non-critical effects

### Micro-interaction patterns

- Hover:
  - text brightens slightly
  - ring/shadow intensifies subtly
- Press:
  - instant feedback (no delayed press animation)
- Focus:
  - visible `focus-visible` ring on all interactive elements
- Loading:
  - use design-components built-in loading states (`DesignButton`, tabs/toggles with async)
  - never freeze the UI without feedback

### Motion accessibility

- Respect `prefers-reduced-motion` for non-essential effects.
- Keep micro-interactions understandable without relying on animation alone.

---

## 3.6) Best-Practice Checklist (Visual + UX)

- [ ] Uses design-components primitives before custom wrappers.
- [ ] Uses semantic variants/colors instead of custom status styles.
- [ ] Works in both light and dark themes with readable contrast.
- [ ] Uses approved typography scale and hierarchy.
- [ ] Uses consistent spacing rhythm (`gap-2/3/4`, `p-3/4/5`).
- [ ] Uses snappy hover-exit transitions and clear focus rings.
- [ ] Provides clear loading/disabled/empty/error states.

---

## 4) Component-by-Component Contract

This section is prescriptive: use these components with these props for these scenarios.

### 4.1 `DesignCard`

File: `apps/dashboard/src/components/design-components/card.tsx`

Use for:

- page sections
- grouped controls
- analytics panels
- list containers
- glassmorphic blocks used in email/project pages

Props you should use most:

- `title`, `icon`, optional `subtitle` for section headers
- `gradient`: `"blue" | "cyan" | "purple" | "green" | "orange" | "default"`
- `glassmorphic` (optional explicit override)
- `contentClassName` for content spacing overrides

Important behavior:

- If `title` is provided, `icon` is required by type.
- Layout is auto-derived:
  - `title + subtitle` -> full header
  - `title only` -> compact header
  - no title -> body-only card
- `useGlassmorphicDefault()` makes nested components default to glassmorphic behavior.

Default recommendation:

- for dashboard sections, use `glassmorphic` style (either explicit or via nesting context)
- use `gradient="default"` unless there is semantic reason for colored tint

### 4.1.1 `DesignAnalyticsCard` (and chart helpers)

File: `apps/dashboard/src/components/design-components/analytics-card.tsx`

Use for:

- chart-heavy analytics shells on overview and metrics surfaces
- cards where chart tooltips need to escape clipping/stacking issues
- previously duplicated glass analytics wrappers (`ChartCard`, `GlassCard` clones)

Exports:

- `DesignAnalyticsCard`
- `DesignAnalyticsCardHeader`
- `DesignChartLegend`
- `useInfiniteListWindow`
- `DesignInfiniteScrollList`

`DesignAnalyticsCard` props:

- `gradient`: `"blue" | "cyan" | "purple" | "green" | "orange" | "slate"`
- `className`

Rules:

- prefer `DesignAnalyticsCard` over local chart wrappers for overview/analytics cards
- keep chart implementation local (Recharts config, data transforms), but keep shell/legend/list plumbing shared
- use `DesignChartLegend` instead of hand-rolled dot/label legend rows when layout matches
- use `useInfiniteListWindow` for incremental scrolling lists in analytics/list tabs

### 4.2 `DesignAlert`

File: `apps/dashboard/src/components/design-components/alert.tsx`

Use for:

- save success/failure
- warning states (for example SMTP/provider configuration warnings)
- informational notices

Props:

- `variant`: `"default" | "success" | "error" | "warning" | "info"`
- `title`
- `description`
- `glassmorphic` when rendered on glass surfaces

Rules:

- use semantic variant instead of custom alert class combinations
- keep title short and actionable
- put longer explanation in `description`

### 4.3 `DesignBadge`

File: `apps/dashboard/src/components/design-components/badge.tsx`

Use for:

- status chips (sent, failed, queued, draft, active)
- small semantic labels in headers and lists

Props:

- `label`
- `color`: `"blue" | "cyan" | "purple" | "green" | "orange" | "red"`
- `icon` (optional)
- `size`: `"sm" | "md"`
- `contentMode`: `"both" | "text" | "icon"`

Rules:

- choose color by meaning, not preference
- use `contentMode="icon"` only when `icon` is provided
- for icon-only badges, accessibility is already handled via `aria-label`

### 4.4 `DesignButton`

File: `apps/dashboard/src/components/design-components/button.tsx`

Use for:

- all primary/secondary actions in dashboard surfaces
- async submit/save/delete actions

Props:

- `variant`: `"default" | "destructive" | "outline" | "secondary" | "ghost" | "link" | "plain"`
- `size`: `"default" | "sm" | "lg" | "icon"`
- `onClick` (can be async)
- `loading` (optional controlled mode)
- `loadingStyle`: `"spinner" | "disabled"`
- `asChild` if composition with links/triggers is needed

Rules:

- prefer `DesignButton` over base `Button` for async behavior and consistent loading semantics
- do not hand-roll loading spinners for standard button actions

### 4.5 `DesignInput`

File: `apps/dashboard/src/components/design-components/input.tsx`

Use for:

- text fields inside design-components surfaces
- compact filter fields and inline settings inputs

Props:

- `size`: `"sm" | "md" | "lg"`
- `prefixItem` for fixed prefix UI
- `leadingIcon` for icon-leading input
- regular input props (placeholder, value, onChange, disabled, etc.)

Rules:

- use `prefixItem` for prefixed values (domains/paths/currency symbols)
- use `leadingIcon` for search or query fields

### 4.6 `DesignSelectorDropdown`

File: `apps/dashboard/src/components/design-components/select.tsx`

Use for:

- standard single-select dropdowns in dashboard settings and filters

Props:

- `value`
- `onValueChange`
- `options: { value, label, disabled? }[]`
- `placeholder`
- `size`: `"sm" | "md" | "lg"`
- `disabled`

Rules:

- prefer this instead of raw `Select` in feature pages unless custom behavior is required

### 4.7 `DesignPillToggle`

File: `apps/dashboard/src/components/design-components/pill-toggle.tsx`

Use for:

- segmented controls
- viewport switches
- compact mode switches

Props:

- `options: { id, label, icon? }[]`
- `selected`
- `onSelect`
- `size`: `"sm" | "md" | "lg"`
- `gradient`
- `showLabels` (set false for icon-only controls)
- `glassmorphic`

Rules:

- do not create custom inline pill toggle components if this fits
- use `showLabels={false}` only with clear icons and tooltip-friendly labels

### 4.8 `DesignCategoryTabs`

File: `apps/dashboard/src/components/design-components/tabs.tsx`

Use for:

- top-level category switching with optional count badges

Props:

- `categories: { id, label, count?, badgeCount? }[]`
- `selectedCategory`
- `onSelect`
- `showBadge`
- `size`: `"sm" | "md"`
- `gradient`
- `glassmorphic`

Rules:

- use for category-level navigation, not micro toggles
- if there are no category counts and control is small, `DesignPillToggle` may be better

### 4.9 `DesignMenu`

File: `apps/dashboard/src/components/design-components/menu.tsx`

Use for:

- standard row/card action menus
- selector menu (radio group)
- toggles menu (checkbox items)

Variants and required props:

- `variant="actions"` with `items`
- `variant="selector"` with `options`, `value`, `onValueChange`
- `variant="toggles"` with `options`, `onToggleChange`

Common shared props:

- `trigger`: `"button" | "icon"`
- `triggerLabel`
- `triggerIcon`
- `align`
- `label`
- `withIcons`

Rules:

- prefer this over local `DropdownMenu` wrappers for common action menus

### 4.10 `DesignListItemRow` and `DesignUserList`

File: `apps/dashboard/src/components/design-components/list.tsx`

Use for:

- structured list rows with optional per-row actions
- user activity/user list rows

`DesignListItemRow` props:

- `title`, optional `subtitle`
- `icon`
- `size`: `"sm" | "lg"`
- `buttons` (direct actions or menu actions)
- `onClick`

`DesignUserList` props:

- `users: { name, email, time, color? }[]`
- `onUserClick`
- `showAvatar`
- `gradient`: `"blue-purple" | "cyan-blue" | "none"`

Rules:

- use `size="sm"` for dense lists
- use `size="lg"` for card-like list entries
- replace custom row/card list items with this unless layout is truly unique

### 4.11 `DesignEditableGrid`

File: `apps/dashboard/src/components/design-components/editable-grid.tsx`

Use for:

- key/value settings editors
- mixed-type setting controls
- deferred save/discard patterns

Props:

- `items` (typed union: `text`, `boolean`, `dropdown`, `custom-dropdown`, `custom-button`, `custom`)
- `columns`: `1 | 2`
- `deferredSave`, `hasChanges`, `onSave`, `onDiscard`
- `externalModifiedKeys`

Rules:

- prefer this for config forms that are row-based and editable inline
- use deferred save mode when many fields should be committed together

### 4.12 `DataGrid` + `useDataSource` + `createDefaultDataGridState`

Package: `@stackframe/dashboard-ui-components`

Use for:

- interactive, sortable, searchable data tables
- any table with more than ~20 rows or that needs pagination, column visibility, quick search, or CSV export

Canonical pattern:

```tsx
import { DataGrid, useDataSource, createDefaultDataGridState, type DataGridColumnDef } from "@stackframe/dashboard-ui-components";

const columns: DataGridColumnDef<MyRow>[] = [
  { id: "name", header: "Name", accessor: "name", width: 200, type: "string" },
  { id: "status", header: "Status", accessor: "status", width: 120, type: "singleSelect",
    valueOptions: [{ value: "active", label: "Active" }, { value: "inactive", label: "Inactive" }],
    renderCell: ({ value }) => <DesignBadge label={String(value)} color={value === "active" ? "green" : "red"} size="sm" /> },
];

const [gridState, setGridState] = useState(() => createDefaultDataGridState(columns));
const gridData = useDataSource({
  data: myRows,
  columns,
  getRowId: (row) => row.id,
  sorting: gridState.sorting,
  quickSearch: gridState.quickSearch,
  pagination: gridState.pagination,
  paginationMode: "client",
});

<DataGrid
  columns={columns}
  rows={gridData.rows}
  getRowId={(row) => row.id}
  totalRowCount={gridData.totalRowCount}
  isLoading={gridData.isLoading}
  state={gridState}
  onChange={setGridState}
  toolbar={false}       // set to false to hide; omit for default toolbar
  onRowClick={(row) => handleClick(row)}
  maxHeight={400}
/>
```

Key props:

- `columns` (`DataGridColumnDef[]`): column definitions with `id`, `header`, `accessor`, `type`, optional `renderCell`, optional `cellOverflow`
- `rows` (`TRow[]`): always `gridData.rows` from `useDataSource`, NEVER your raw array
- `getRowId` (`(row) => RowId`): unique row identifier (`RowId` is `string`)
- `state` / `onChange`: fully controlled grid state (sorting, pagination, search, visibility)
- `totalRowCount`: total rows for pagination display
- `toolbar`: `false` to hide, omit for default, or render function for custom
- `onRowClick`: optional row click handler
- `maxHeight`: max pixel height before scrolling
- `rowHeight`: number (default 44) for fixed height, or `"auto"` for dynamic row measurement
- `estimatedRowHeight`: estimated row height for the virtualizer when `rowHeight="auto"` (default 44)

Cell overflow:

- `cellOverflow: "truncate"` (default): single-line with text-overflow ellipsis
- `cellOverflow: "wrap"`: content wraps naturally; rows grow when `rowHeight="auto"`
- Use `cellOverflow: "wrap"` for badge lists, permission chips, multi-line text
- Use default truncate for UUIDs, emails, dates, single-line text

```tsx
const columns: DataGridColumnDef<MyRow>[] = [
  { id: "userId", header: "User ID", width: 130 },                            // truncates (default)
  { id: "auth", header: "Auth methods", width: 150, cellOverflow: "wrap",     // badges wrap, row grows
    renderCell: ({ row }) => (
      <div className="flex flex-wrap gap-1">
        {row.authTypes.map((t) => <Badge key={t}>{t}</Badge>)}
      </div>
    ),
  },
];

<DataGrid columns={columns} rowHeight="auto" estimatedRowHeight={48} ... />
```

Rules:

- always initialize state with `createDefaultDataGridState(columns)` — never build the state object by hand
- always use `useDataSource` to process data — the grid does not sort/filter/paginate on its own
- columns must be stable across renders (define outside component or wrap in `useMemo`)
- `renderCell` must be a pure function — no React hooks inside it
- read the full JSDoc on the `DataGrid` component for iron rules and advanced usage

### 4.13 `CursorBlastEffect`

File: `apps/dashboard/src/components/design-components/cursor-blast-effect.tsx`

Use for:

- optional high-feedback interactions (playground/internal prototyping)

Props:

- `blastLifetimeMs`
- `maxActiveBlasts`
- `rageClickThreshold`
- `rageClickWindowMs`
- `rageClickRadiusPx`
- `containerRef`

Rules:

- keep as optional enhancement, not required UX
- avoid distracting overuse in production-critical flows

---

## 5) Route-Specific Guidance (Project + Email Surfaces)

Reference surfaces:

- `apps/dashboard/src/app/(main)/(protected)/projects/[projectId]`
- `apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/emails`
- `apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-drafts`
- `apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-outbox`
- `apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-templates`
- `apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/email-themes`

Current pattern in these pages often uses custom card/header/pill components. New and refactored code should standardize to design-components primitives as follows.

### 5.0 `/projects/[projectId]/(overview)` analytics surfaces

Use:

- chart/list shells: `DesignAnalyticsCard`
- compact chart headers: `DesignAnalyticsCardHeader`
- stacked chart legends: `DesignChartLegend`
- incremental list rendering: `useInfiniteListWindow` (or `DesignInfiniteScrollList` where it fits)

Avoid:

- page-local `ChartCard` wrappers
- duplicated `IntersectionObserver` list window logic per card

### 5.1 `/projects/[projectId]/emails`

Use:

- section containers: `DesignCard` (`title`, `icon`, optional `subtitle`, `gradient`)
- alerts: `DesignAlert` (`variant` by state)
- status chips: `DesignBadge` (`green` for sent, `red` for failed)
- actions: `DesignButton`
- table: `DataGrid` + `useDataSource` + `createDefaultDataGridState`

Avoid:

- custom `GlassCard`
- custom status-badge component
- raw `Alert` unless special composition is required

### 5.2 `/projects/[projectId]/email-drafts` (list)

Use:

- list container: `DesignCard`
- row items: `DesignListItemRow` (`size="lg"` for card rows or `size="sm"` for dense list)
- row menus: `DesignMenu` with `variant="actions"`
- empty state action: `DesignButton`

Avoid:

- custom `DraftCard` for standard list row behavior

### 5.3 `/projects/[projectId]/email-drafts/[draftId]` (editor)

Use:

- status/sync alerts: `DesignAlert`
- scope/status chips: `DesignBadge`
- editor side controls: `DesignButton`, `DesignSelectorDropdown`, `DesignInput` as needed

Keep:

- specialized editor layout systems if no design-components equivalent exists

### 5.4 `/projects/[projectId]/email-outbox`

Use:

- section cards: `DesignCard` (preferred for visual consistency with other email screens)
- filters: `DesignSelectorDropdown`, `DesignInput`
- status badges: `DesignBadge`
- action buttons/menus: `DesignButton`, `DesignMenu`
- data grid/list table: `DataGrid` + `useDataSource` + `createDefaultDataGridState`

Avoid:

- mixed badge systems (`Badge` in some places, custom badges elsewhere)

### 5.5 `/projects/[projectId]/email-templates`

Use:

- template item containers: `DesignCard` (`gradient` per semantic section)
- alerts/warnings: `DesignAlert`
- actions: `DesignButton`
- template row action menu: `DesignMenu`

Avoid:

- inline repeated glass class blocks for each template card

### 5.6 `/projects/[projectId]/email-templates/[templateId]`

Use:

- save/error notices: `DesignAlert`
- top actions: `DesignButton`
- state tags: `DesignBadge` where needed

### 5.7 `/projects/[projectId]/email-themes`

Use:

- section containers: `DesignCard`
- viewport/device selector: `DesignPillToggle`
- status messages: `DesignAlert`
- theme state badges: `DesignBadge`
- actions: `DesignButton`, `DesignMenu`

Avoid:

- custom `ViewportSelector` if `DesignPillToggle` supports the same behavior

### 5.8 `/projects/[projectId]/email-themes/[themeId]`

Use:

- state feedback: `DesignAlert`
- actions: `DesignButton`
- optional segmented controls: `DesignPillToggle`

---

## 6) Semantic Mapping Rules

Use consistent semantic color/variant mapping across all pages:

- Success/completed/sent -> `DesignAlert variant="success"` and `DesignBadge color="green"`
- Error/failed -> `DesignAlert variant="error"` and `DesignBadge color="red"`
- Warning/attention -> `DesignAlert variant="warning"` and `DesignBadge color="orange"`
- Info/neutral updates -> `DesignAlert variant="info"` and `DesignBadge color="blue"` or `"cyan"`

Gradient mapping for cards/tabs/toggles:

- Blue: primary navigation/state
- Cyan: analytics/activity
- Purple: templates/themes or creative tools
- Green: success/completion
- Orange: warnings/caution
- Default: neutral/system sections

---

## 7) Interaction and Motion Rules

These rules must be preserved in custom styling and overrides:

- no hover-enter delays
- use hover-exit transitions: `transition-* duration-150 hover:transition-none`
- keep controls snappy and readable
- avoid heavy animation in dense admin workflows

For async actions:

- prefer design-components primitives that already handle async/loading
- do not swallow async errors; use existing alert-aware async utilities through design-components primitives

---

## 8) AI-Readable Implementation Checklist

Use this checklist before opening a dashboard UI PR:

- [ ] Replaced ad-hoc cards with `DesignCard` where possible.
- [ ] Replaced ad-hoc alerts with `DesignAlert`.
- [ ] Replaced ad-hoc badges/status pills with `DesignBadge`.
- [ ] Replaced ad-hoc segmented controls with `DesignPillToggle` or `DesignCategoryTabs`.
- [ ] Replaced ad-hoc row/list cards with `DesignListItemRow` or `DesignUserList`.
- [ ] Used `DesignButton` for async actions.
- [ ] Used `DesignSelectorDropdown`/`DesignInput` for standard field controls.
- [ ] Used `DataGrid` + `useDataSource` + `createDefaultDataGridState` for interactive tables.
- [ ] Did not introduce duplicate local wrappers for components already in design-components.
- [ ] Kept hover/motion behavior aligned with this guide.

---

## 9) Quick Snippets (Canonical)

### Section Card

```tsx
<DesignCard
  title="Email Log"
  subtitle="View and manage email sending history"
  icon={Envelope}
  gradient="default"
>
  {/* content */}
</DesignCard>
```

### Semantic Alert

```tsx
<DesignAlert
  variant="error"
  title="Failed to send email"
  description="Please verify provider configuration and try again."
/>
```

### Status Badge

```tsx
<DesignBadge
  label="Sent"
  color="green"
  icon={CheckCircle}
  size="sm"
/>
```

### Viewport Toggle

```tsx
<DesignPillToggle
  options={[
    { id: "desktop", label: "Desktop", icon: Desktop },
    { id: "tablet", label: "Tablet", icon: DeviceTablet },
    { id: "mobile", label: "Mobile", icon: DeviceMobile },
  ]}
  selected={viewport}
  onSelect={setViewport}
  size="sm"
  gradient="default"
/>
```

### Category Tabs

```tsx
<DesignCategoryTabs
  categories={[
    { id: "all", label: "All", count: 42 },
    { id: "failed", label: "Failed", count: 3 },
  ]}
  selectedCategory={category}
  onSelect={setCategory}
  gradient="blue"
/>
```

---

## 10) Anti-Patterns (Do Not Introduce)

- Creating local `GlassCard`/`ChartCard` components instead of `DesignCard` or `DesignAnalyticsCard`.
- Creating local status pills instead of `DesignBadge`.
- Creating local segmented/pill selectors instead of `DesignPillToggle`.
- Using raw `Alert`/`Button` in standard dashboard surfaces where `DesignAlert`/`DesignButton` should be used.
- Using `DesignDataTable` or raw `DataTable` instead of `DataGrid` + `useDataSource` + `createDefaultDataGridState`. `DesignDataTable` is deprecated; all new and migrated tables use `DataGrid`.
- Repeating large inline class strings for common design-components patterns.

---

## 11) Migration Priority for Existing Email Surfaces

When touching existing email/project pages, migrate in this order:

1. Cards/surfaces (`DesignCard` / `DesignAnalyticsCard` for chart-heavy shells)
2. Alerts (`DesignAlert`)
3. Badges (`DesignBadge`)
4. Toggles/tabs (`DesignPillToggle` / `DesignCategoryTabs`)
5. Rows/lists (`DesignListItemRow`)
6. Buttons/menus (`DesignButton` / `DesignMenu`)
7. Tables/forms (`DataGrid`, `DesignInput`, `DesignSelectorDropdown`, `DesignEditableGrid`)

This order yields the biggest consistency win first.

---

## 12) Maintenance Rule

Whenever a new reusable visual pattern is introduced in dashboard features:

- add or extend a design-components component first
- then document the component contract and preferred usage here
- avoid introducing permanent page-local UI primitives that duplicate design-components behavior




Script executed:

# Search for privacy policy, consent, or related documentation
rg "privacy|consent|session.?recording|mask.?input" -i --type md | head -50

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 0

stdout:

AGENTS.md:- NEVER EVER AUTOMATICALLY COMMIT OR STAGE ANY CHANGES — DON'T MODIFY GIT WITHOUT USER CONSENT!


Script executed:

# Check git history to understand the context of this change
git log --oneline -n 30 -- apps/dashboard/instrumentation-client.ts

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 0

stdout:

786562cc re-enable posthog recordings


Script executed:

# Check if there's any comment or documentation about the intentional exposure
rg "data.?handling|privacy.?policy|replay|session.?record" apps/dashboard/ --type ts --type tsx -B 2 -A 2 | head -80

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 0

stderr:

unrecognized file type: tsx


Script executed:

# Check if there's any related issue or discussion in comments near the PostHog init
sed -n '10,30p' apps/dashboard/instrumentation-client.ts

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 0

stdout:

import posthog from "posthog-js";

export const onRouterTransitionStart = Sentry.captureRouterTransitionStart;

const postHogKey = getPublicEnvVar('NEXT_PUBLIC_POSTHOG_KEY') ?? "phc_vIUFi0HzHo7oV26OsaZbUASqxvs8qOmap1UBYAutU4k";
if (postHogKey.length > 5) {
  posthog.init(postHogKey, {
    session_recording: {
      maskAllInputs: false,
      maskInputOptions: {
        password: true,
      },
    },
    defaults: '2025-11-30',
    api_host: "/consume",
    ui_host: "https://eu.i.posthog.com",
  });
}


Sentry.init({


Script executed:

# Search for any comments about data handling, compliance, or privacy policy in config files
rg "privacy|compliance|data.?handling|policy" . --type-add "config:*.{json,env*}" --type config -i | head -20

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 0


Script executed:

# Check if there's a privacy policy or consent documentation file
find . -name "*privacy*" -o -name "*policy*" -o -name "*compliance*" -o -name "*consent*" | head -20

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 0

stdout:

./node_modules/.pnpm/next@16.2.2_@opentelemetry+api@1.9.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/next/dist/docs/01-app/02-guides/content-security-policy.md
./node_modules/.pnpm/next@16.2.2_@opentelemetry+api@1.9.0_react-dom@19.2.3_react@19.2.3__react@19.2.3/node_modules/next/dist/docs/02-pages/02-guides/content-security-policy.md
./node_modules/.pnpm/caniuse-lite@1.0.30001751/node_modules/caniuse-lite/data/features/contentsecuritypolicy.js
./node_modules/.pnpm/caniuse-lite@1.0.30001751/node_modules/caniuse-lite/data/features/contentsecuritypolicy2.js
./node_modules/.pnpm/caniuse-lite@1.0.30001751/node_modules/caniuse-lite/data/features/document-policy.js
./node_modules/.pnpm/caniuse-lite@1.0.30001751/node_modules/caniuse-lite/data/features/feature-policy.js
./node_modules/.pnpm/caniuse-lite@1.0.30001751/node_modules/caniuse-lite/data/features/permissions-policy.js
./node_modules/.pnpm/caniuse-lite@1.0.30001751/node_modules/caniuse-lite/data/features/referrer-policy.js


Script executed:

# Check root-level documentation for privacy/policy mentions
find . -maxdepth 2 -name "README*" -o -name "PRIVACY*" -o -name "POLICY*" -o -name "COMPLIANCE*" -o -name "*.md" | grep -v node_modules | head -20

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 0

stdout:

./.claude/CLAUDE-KNOWLEDGE.md
./.devcontainer/README.md
./.github/SECURITY.md
./.github/pull_request_template.md
./AGENTS.md
./CHANGELOG.md
./CLAUDE.md
./CONTRIBUTING.md
./README.md
./claude/CLAUDE-KNOWLEDGE.md
./docs-mintlify/README.md
./docs/README.md


Script executed:

# Look at git commit details to see if there's a related PR or message
git log --all --oneline --grep="recording\|privacy\|mask" -i | head -20

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 0

stdout:

786562cc re-enable posthog recordings
0207721f fix(dashboard): improve analytics replay replayer lifecycle (`#1349`)
2af2a591 Skip analytics init on apps without persistent token store (`#1336`)
7434d84a Custom Dashboards bug fix (`#1299`)
381e057c fix query route safe clickhouse error codes (`#1268`)
fa27c803 rename tabId to sessionReplaySegmentId (`#1206`)
c7ef526b session replays (`#1187`)
abc320b4 [Refactor] [Fix] Email Rendering Pipeline Refactor, Error Handling, and Bug Fixes (`#1140`)
017b43fe Add override using environment variable for freestyle.sh endpoint (`#853`)


Script executed:

# Check the specific commit for more context
git show --stat 786562cc | head -30

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 0

stdout:

commit 786562ccd8930d2b90253f0001aecfe59a783298
Author: mantrakp04 <mantrakp@gmail.com>
Date:   Sun May 3 23:30:57 2026 -0700

    re-enable posthog recordings

 .agents/skills/pr-visual-writeup/SKILL.md          |   137 +
 .../references/capture-patterns.md                 |    73 +
 .../pr-visual-writeup/references/gist-upload.md    |    56 +
 .../references/pr-body-template.md                 |    87 +
 .../pr-visual-writeup/scripts/convert_clips.sh     |    35 +
 .../pr-visual-writeup/scripts/detect_dev_server.sh |    38 +
 .../pr-visual-writeup/scripts/upload_gist.sh       |    73 +
 .changeset/config.json                             |    11 +
 .claude/CLAUDE-KNOWLEDGE.md                        |   366 +
 .claude/settings.json                              |    41 +
 .claude/skills                                     |     1 +
 .cursor/commands/pr-comments-review.md             |     1 +
 .cursor/commands/pre-push.md                       |     1 +
 .cursor/commands/repro-fix.md                      |     1 +
 .cursor/hooks.json                                 |    16 +
 .cursor/hooks/lint-file.sh                         |    19 +
 .cursor/hooks/stop-check.sh                        |    21 +
 .devcontainer/README.md                            |    23 +
 .devcontainer/devcontainer.json                    |    45 +
 .devcontainer/set-env.sh                           |    10 +
 .dockerignore                                      |   153 +
 .github/.recurseml.yaml                            |     2 +
 .github/SECURITY.md                                |    15 +
 .github/assets/account-settings.png                |   Bin 0 -> 83039 bytes


Script executed:

# Look at the PR that introduced session replays to understand the rationale
git log --all --oneline --grep="session.?replay" -i | head -10

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 0


Script executed:

# Check if there's any issue or discussion about this in the repo
rg "maskAllInputs|session.?recording" . --type md --type txt -B 3 -A 3 | grep -v node_modules | head -60

Repository: stack-auth/stack-auth


Repository: stack-auth/stack-auth
Exit code: 0


Verify this intentional exposure is covered by privacy/compliance documentation.

maskAllInputs: false leaves non-password form inputs visible in session replay (emails, names, tokens, API keys, and other PII). Documentation explicitly warns against this unless there is a documented data-handling policy. Confirm this change is intentional and add a code comment explaining the compliance rationale and any required consent mechanisms.

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

In `@apps/dashboard/instrumentation-client.ts` around lines 17 - 21, The session
recording config sets session_recording.maskAllInputs to false which
intentionally exposes non-password inputs; update the code by adding a clear
inline comment next to session_recording (and reference maskAllInputs and
maskInputOptions) stating the compliance rationale, the specific documented
data-handling policy or consent mechanism that authorizes this exposure, and any
required user-consent or opt-out steps; ensure the comment names the controlling
policy/version and notes any PII redaction expectations so reviewers can verify
privacy coverage.

@N2D4 N2D4 merged commit 9ec32ce into dev May 4, 2026
48 checks passed
@N2D4 N2D4 deleted the chore/enable-posthog-recordings branch May 4, 2026 06:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants