Skip to content

Data-grid overhaul + session-replays / team-payments dashboard surfaces#1424

Open
mantrakp04 wants to merge 14 commits into
devfrom
refactor/data-grid-and-dashboard-surfaces
Open

Data-grid overhaul + session-replays / team-payments dashboard surfaces#1424
mantrakp04 wants to merge 14 commits into
devfrom
refactor/data-grid-and-dashboard-surfaces

Conversation

@mantrakp04
Copy link
Copy Markdown
Collaborator

@mantrakp04 mantrakp04 commented May 8, 2026

Summary

Refactors the dashboard data-grid into a smaller, URL-state-aware primitive and lands several new dashboard surfaces around it: per-user session replays, team-level analytics and payments, and pagination for permission definitions. Also moves session replays out from under /analytics to a top-level surface and adds a project_user.last_active_at index that the new weekly-active metrics depend on.

Base: devHead: refactor/data-grid-and-dashboard-surfaces
Scope: 91 files, +5,644 / −1,858. Assets in this gist.

Screenshots

Captured from a local dev server (dashboard at :8101, dummy project seeded with 26 users). Standard viewport 1920×1200, widescreen 2560×1440.

Users list — data-grid overhaul in context

Light Dark
users-list-light users-list-dark

Widescreen:

Light Dark
users-list-light-wide users-list-dark-wide

User detail — new session-replays card + weekly metrics

Light Dark
user-detail-light user-detail-dark

Widescreen:

Light Dark
user-detail-light-wide user-detail-dark-wide

Session replays — moved out of /analytics

Light Dark
session-replays-light session-replays-dark

Widescreen:

Light Dark
session-replays-light-wide session-replays-dark-wide

Project permissions — new pagination

Light Dark
project-permissions-light project-permissions-dark

Widescreen:

Light Dark
project-permissions-light-wide project-permissions-dark-wide

Other migrated surfaces

Page Light Dark
Project picker projects-light projects-dark
Overview / setup overview-light overview-dark
Teams list teams-list-light teams-list-dark
Team permissions team-permissions-light team-permissions-dark
API keys api-keys-light api-keys-dark

Scroll behaviour — new data-grid on the users list

Light Dark
users-list-scroll-light users-list-scroll-dark

What's new

  • packages/dashboard-ui-components/src/components/data-grid — rewritten. Trimmed data-grid.tsx from ~1.7k LOC, split sizing logic into data-grid-sizing.ts, added use-url-state.ts for URL-synced state, and added data-grid.test.tsx.
  • Session replays moved from …/analytics/replays to …/session-replays (top-level surface). New user-session-replays.tsx card on the user detail page; new internal route.tsx to feed it.
  • Teams detail page gains team-analytics.tsx and team-payments.tsx.
  • Permissions — new shared permission-definitions-pagination.ts consumed by both project and team permission CRUD routes.
  • Backend — Prisma migration add_project_user_last_active_at_idx + a lastActiveAt index that backs the new weekly-active metrics.
  • Polisheditable-input, inline-save-discard, settings.tsx, walkthrough steps, and several data-table components touched in line with the data-grid rewrite.

Notes for reviewers

  • The data-grid rewrite changes the shape of state (now URL-synced), not just internals. Consumers in apps/dashboard/src/components/data-table/* were updated to match — please scan those for any missed knobs.
  • The analytics/replayssession-replays rename is git-tracked as renames; diffs should be small in those files.
  • New SDK surface in packages/template/src/lib/stack-app/session-replays/index.ts and additions in admin-app-impl.ts / server-app-impl.ts mean OpenAPI specs (docs-mintlify/openapi/{admin,client}.json) regenerate; the diff is mostly mechanical.

Test plan

  • pnpm typecheck clean
  • pnpm lint clean
  • Data-grid unit tests pass (packages/dashboard-ui-components)
  • Manual: users list — column resize, sort, filter, paginate; URL state reflects each change and survives reload
  • Manual: user detail — session-replays card lists replays; weekly-metrics card renders without lastActiveAt index migration applied (i.e. on a fresh DB) and after applying it
  • Manual: project + team permissions — pagination cursor advances and stays consistent under search
  • Manual: session-replays top-level page loads; old /analytics/replays/... URL path is no longer expected to be linked anywhere

Summary by CodeRabbit

  • New Features

    • Session Replays app (embedded mode, search, sorting, share links)
    • Tabbed Team pages with Team Analytics and Team Payments dashboards
    • Server-backed cursor pagination, debounced search, and infinite-scroll for teams/users/permissions
  • UX

    • Permission and member tables refresh after edits; permission creation triggers table refresh
    • Users list supports sorting by last-active
  • Performance

    • Index added to speed ProjectUser last-active queries
  • Documentation

    • API/SDK docs updated for pagination and new query params
    • Contributor guidance: explicit git-safety rules added (no destructive git ops without consent)
  • Tests

    • Added e2e tests for pagination and filtering on list endpoints

…ents surfaces

- Rewrite data-grid with URL-synced state, new sizing logic, tests
- Move analytics/replays → session-replays; add per-user session replays card
- Add team-analytics and team-payments to team detail page
- Add project_user.last_active_at index + permission-definitions pagination
- Various editable-input / inline-save-discard / settings polish
@vercel
Copy link
Copy Markdown

vercel Bot commented May 8, 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 11, 2026 8:28pm
stack-auth-mcp Ready Ready Preview, Comment May 11, 2026 8:28pm
stack-backend Ready Ready Preview, Comment May 11, 2026 8:28pm
stack-dashboard Ready Ready Preview, Comment May 11, 2026 8:28pm
stack-demo Ready Ready Preview, Comment May 11, 2026 8:28pm
stack-docs Ready Ready Preview, Comment May 11, 2026 8:28pm
stack-preview-backend Ready Ready Preview, Comment May 11, 2026 8:28pm
stack-preview-dashboard Ready Ready Preview, Comment May 11, 2026 8:28pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 8, 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

Adds backend pagination/search and session-replays sorting, extends shared interfaces/SDK and template/server implementations for paginated listings, refactors DataGrid with URL-state and TanStack, converts tables to server-backed infinite sources, adds team analytics/payments and user session-replays UI, updates OpenAPI/docs/registry, adds a DB index, E2E tests, dependency, and AGENTS git-safety guidance.

Changes

Main change DAG

Layer / File(s) Summary
DB Schema / Migration
apps/backend/prisma/*
Adds composite index on ProjectUser(tenancyId,isAnonymous,lastActiveAt) and migration creating it concurrently.
Backend: Permission Pagination
apps/backend/src/app/api/latest/permission-definitions-pagination.ts, project-permission-definitions/*, team-permission-definitions/*
Adds permission-definitions pagination schema/logic and wires list handlers to return cursor-based paginated results.
Backend: Teams/Users/Session-Replays
apps/backend/src/app/api/latest/teams/crud.tsx, users/crud.tsx, internal/session-replays/route.tsx
Teams list: order_by/desc/limit/cursor/query + cursor pagination; Users list: allow order_by last_active_at; Session-replays: accept sort_direction + q, escape ILIKE patterns, validate cursor row against active filters, flip cursor/ORDER BY by direction.
Shared Interfaces & SDK Types
packages/stack-shared/*, packages/template/src/lib/stack-app/*
Adds paginated listing methods/types for teams/users and permission definitions; extends session-replays options with sort/query; updates StackServerApp/StackAdminApp interfaces and app registry.
Template implementations & server caching
packages/template/src/lib/stack-app/apps/implementations/*
Implements paginated methods, maps paginated interface results, forwards replay query/sort, and reworks server-app caches with paginated caching and invalidation.
OpenAPI / Docs / Registry
docs-mintlify/*, docs/content/*, docs/src/components/*, apps/dashboard/src/lib/apps-frontend.tsx, packages/stack-shared/src/apps/apps-config.ts
OpenAPI updated with new query params and stricter enums/integers; SDK/docs updated for paginated APIs; adds session-replays app entry, icon, and walkthrough step rename.
E2E & Tooling
apps/e2e/tests/backend/endpoints/*, turbo.json
Adds teams and permission-definitions pagination e2e tests and a persistent dev:tui turbo task.
Repository guidance
AGENTS.md
Adds git-safety guidance forbidding destructive/working-tree-mutating git operations without explicit user permission.

UI / DataGrid and Tables

Layer / File(s) Summary
DataGrid Core & Dependency
packages/dashboard-ui-components/src/components/data-grid/*, packages/dashboard-ui-components/package.json
Refactors DataGrid to use TanStack Table (selection, sorting, resizing), CSS-variable sizing, ResizeObserver layout tracking, CSV export confirmation; adds @tanstack/react-table dependency.
Sizing & URL-state
packages/dashboard-ui-components/src/components/data-grid/data-grid-sizing.ts, use-url-state.ts, use-url-state.test.tsx
Simplifies sizing helpers, exports DEFAULT_COL_WIDTH/MAX, implements useDataGridUrlState to persist column widths/visibility to URL with parse/serialize and popstate handling (with tests).
Barrel exports & tests
packages/dashboard-ui-components/src/components/data-grid/index.ts, data-grid.test.tsx
Adds toolbar and URL-state hook to exports, expands sizing helpers, narrows some state re-exports, and adds horizontal-scrolling test coverage.
Tables → server-backed
apps/dashboard/src/components/data-table/*, export-users-dialog.tsx
PermissionTable, TeamTable, TeamMemberTable, UserTable, and search tables converted to server-backed infinite/generator data sources, debounced search, refetch contexts, updated columns/sorting, and export adjusted to paginated APIs.
Minor UI tweaks
various apps/dashboard/src/components/*
Styling/spacing tweaks: editable-input ghost styles, SettingCard border, inline-save-discard margin, walkthrough step rename, icon mapping, import reorderings.

Pages / Features

Layer / File(s) Summary
Team pages
apps/dashboard/src/app/.../teams/[teamId]/**
New tabbed TeamPage with TeamHeader; Members/Payments/Analytics/Metadata tabs; adds TeamAnalyticsSection (ClickHouse queries) and TeamPaymentsSection.
User pages / analytics / session replays
apps/dashboard/src/app/.../users/[userId]/*, session-replays/*
User page: URL-driven tabs, activity sidebar, day-filter analytics, paginated recent events, metric card deltas/sparklines, UserSessionReplaysSection; session-replays page: embedded lockedUserId mode and related UI adjustments.
Page wiring updates
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/**
Pages updated to use paginated hooks, table versioning for refresh, pass version into PermissionTable, and minor layout changes.
Component API changes
PermissionTable, TeamMemberTable, TeamTable, UserTable
Several component signatures changed to server-backed patterns (e.g., PermissionTable now accepts permissionType/version; TeamTable no longer accepts teams prop).

Estimated code review effort:
🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

Suggested reviewers

  • aadesh18
  • N2D4
  • nams1570

Poem

I hop through patches, tail a-flutter,
Cursors find their rightful gutter.
Grids reborn and replays sing,
Docs align and tests take wing.
A little carrot for this merge! 🐇✨

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/data-grid-and-dashboard-surfaces

…sion handling

- Introduced pagination for teams in the dashboard, allowing for efficient data retrieval and display.
- Updated permission definitions to include cursor-based pagination, improving user experience when navigating through permissions.
- Refactored various components to utilize the new paginated API, ensuring consistency across the application.
- Added error handling for pagination and improved loading states in user session replays and team member tables.
- Enhanced the data grid to support URL-synced state for column widths and visibility, improving user customization options.

This update significantly enhances the dashboard's performance and usability, particularly for users managing large teams and permissions.
- Updated the ProjectUser model to simplify indexing on lastActiveAt.
- Refactored permission definitions pagination to utilize a unified schema, improving consistency across project and team permissions.
- Enhanced error handling in pagination logic for better user feedback.
- Improved loading states and data retrieval efficiency in various components, including session replays and team permissions.

These changes streamline the permission management experience and optimize data handling in the dashboard.
…election logic

- Renamed cursor comparator variable for clarity in session replay queries.
- Enhanced search query handling to include escape character for better SQL compatibility.
- Introduced error handling for asynchronous queries in the Team Analytics section, providing default values for failed queries.
- Refactored data grid selection logic to support flexible row selection modes and improved column width distribution.

These changes enhance the robustness and usability of the dashboard components, particularly in data retrieval and user interaction.
@mantrakp04 mantrakp04 marked this pull request as ready for review May 9, 2026 00:02
Copilot AI review requested due to automatic review settings May 9, 2026 00:02
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 9, 2026

Greptile Summary

This PR refactors the dashboard data-grid into a TanStack Table-backed primitive with URL-synced column state, adds per-user session replays and weekly-active metrics to the user detail page, promotes session replays to a top-level route, and lands team analytics/payments tabs. Backend changes introduce cursor+search pagination on the team list and session-replay list endpoints, a shared paginatePermissionDefinitions utility, and a CONCURRENTLY index on ProjectUser.lastActiveAt.

  • Data-grid rewrite: data-grid.tsx migrated to TanStack Table with a new use-url-state.ts hook for URL-persisted column widths/visibility; custom resize and selection logic replaced by library built-ins.
  • New dashboard surfaces: Team detail gains Members/Payments/Analytics/Metadata tabs; user detail gains a session-replays card; session replays move to a top-level route.
  • Backend pagination: Team list, session-replay list, and permission definition list all gain cursor+search+sort parameters; the ProjectUser_lastActiveAt index added via a non-blocking CONCURRENTLY migration.

Confidence Score: 5/5

Safe to merge — the pagination endpoints and data-grid rewrite are structurally sound, with only edge-case cursor misuse footguns that normal dashboard usage will not trigger.

The cursor-direction mismatch would only produce wrong pages if a caller reuses a cursor across a sort-direction or filter change; the dashboard resets pagination on such changes. All SQL is parameterized, the index migration is non-blocking, and async handlers follow the runAsynchronouslyWithAlert convention.

apps/backend/src/app/api/latest/internal/session-replays/route.tsx and apps/backend/src/app/api/latest/teams/crud.tsx — revisit cursor design before promoting these endpoints to the public API surface.

Important Files Changed

Filename Overview
apps/backend/src/app/api/latest/internal/session-replays/route.tsx Adds sort_direction and free-text search parameters; cursor pagination is direction-aware in the WHERE clause but cursors are not validated against the active sort direction, which can silently return wrong pages.
apps/backend/src/app/api/latest/teams/crud.tsx Adds cursor pagination, free-text search, ordering, and direction; cursor validated for existence but not for compatibility with the active search filter or sort direction.
apps/backend/src/app/api/latest/permission-definitions-pagination.ts New shared in-memory cursor+search pagination utility; correctly handles edge cases and is consistent with the team endpoint fail-fast cursor policy.
packages/dashboard-ui-components/src/components/data-grid/use-url-state.ts New hook persisting column widths and visibility to URL params; debounced writes and popstate listener correctly wired; URL keys validated against known column IDs.
packages/dashboard-ui-components/src/components/data-grid/data-grid.tsx Major rewrite migrating to TanStack Table; resize, flex distribution, and shift-range selection are clean implementations.
apps/backend/prisma/migrations/20260507000000_add_project_user_last_active_at_idx/migration.sql CREATE INDEX CONCURRENTLY IF NOT EXISTS with RUN_OUTSIDE_TRANSACTION_SENTINEL — correct non-blocking approach.
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/page-client.tsx Team detail refactored to tab-based surface; async handlers correctly use runAsynchronouslyWithAlert; tab state managed via URL search params.
apps/dashboard/src/components/data-table/team-member-table.tsx Converted to server-side cursor pagination with debounced search; Promise.allSettled replaced with Promise.all in permission dialog.

Sequence Diagram

sequenceDiagram
    participant Dashboard
    participant TeamsAPI as GET /teams
    participant SessionAPI as GET /session-replays
    participant PermAPI as GET /permission-definitions

    Dashboard->>TeamsAPI: "?limit=10&query=acme&order_by=created_at"
    TeamsAPI-->>Dashboard: items + next_cursor

    Dashboard->>TeamsAPI: "?limit=10&cursor=prev&query=acme"
    Note over TeamsAPI: findUnique validates cursor exists
    TeamsAPI-->>Dashboard: items + next_cursor

    Dashboard->>SessionAPI: "?limit=50&q=alice&sort_direction=desc"
    SessionAPI-->>Dashboard: items + next_cursor

    Dashboard->>SessionAPI: "?limit=50&cursor=prev&sort_direction=asc"
    Note over SessionAPI: direction mismatch - wrong page boundary
    SessionAPI-->>Dashboard: incorrect page (no error)

    Dashboard->>PermAPI: "?limit=20&query=read&cursor=perm-id"
    Note over PermAPI: in-memory filter+sort+slice
    PermAPI-->>Dashboard: items + next_cursor
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/backend/src/app/api/latest/internal/session-replays/route.tsx:204-215
**Cursor is not direction-aware — wrong page returned on direction change**

The cursor pivot only stores `lastEventAt` and `id`, with no record of which `sort_direction` it was created under. If a caller issues page 1 with `sort_direction=desc` and then fetches page 2 with the same cursor but `sort_direction=asc`, the `AND (lastEventAt > cursorPivot.lastEventAt …)` condition skips to a completely different position in the ascending order, silently returning an incorrect page rather than surfacing an error. Consider embedding the sort direction in the opaque cursor token, or rejecting cursor requests whose inferred direction doesn't match `sort_direction`.

### Issue 2 of 2
apps/backend/src/app/api/latest/teams/crud.tsx:282-355
**Cursor is not search-filter-aware — Prisma may silently skip matching teams**

The cursor row is validated for existence via `findUnique`, but not for whether it satisfies the active `queryFilter`. Per Prisma's cursor behaviour, when the cursor row does not match the `where` clause, Prisma still positions at that row in the sort order and returns the first `take` rows after it that do match, silently missing every matching team before the cursor position. The OpenAPI description should at minimum warn that cursors are filter- and direction-specific.

Reviews (2): Last reviewed commit: "fix(api): improve pagination error handl..." | Re-trigger Greptile

Comment thread packages/dashboard-ui-components/src/components/data-grid/use-url-state.ts Outdated
Comment thread packages/dashboard-ui-components/src/components/data-grid/use-url-state.ts Outdated
Comment thread apps/backend/src/app/api/latest/teams/crud.tsx
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/backend/src/app/api/latest/internal/session-replays/route.tsx (1)

200-203: ⚡ Quick win

Leading-wildcard ILIKE won't use a B-tree index — consider a trigram index for pu."displayName" and sr."id"::text.

%${q}% precludes index usage on a regular B-tree, so search latency will scale linearly with the filtered set. For projects with many users/replays this can become a noticeable hot path. If/when this matters in production, a pg_trgm GIN index (e.g., CREATE INDEX ... USING gin (lower("displayName") gin_trgm_ops)) is the standard remedy. Capping q.length server-side (e.g., reject >128 chars) is also worth adding to bound worst-case work and prevent abuse via huge patterns.

🤖 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/backend/src/app/api/latest/internal/session-replays/route.tsx` around
lines 200 - 203, The leading-wildcard ILIKE pattern built in the Prisma.sql
block (searchQuery via escapeLikePattern used against pu."displayName" and
sr."id"::text) prevents B-tree index use and will be slow at scale; switch to
recommending and documenting creation of pg_trgm trigram GIN/GiST indexes (e.g.,
on lower("displayName") and on sr.id::text) and change queries to use lower(...)
for indexable trigram searches, and also add a server-side cap on searchQuery
length (e.g., reject >128 chars) to bound work and avoid abuse; update any
relevant query code paths using Prisma.sql and escapeLikePattern to reflect
lowercasing and trigram-ready patterns.
🤖 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/backend/src/app/api/latest/internal/session-replays/route.tsx`:
- Around line 200-203: The leading-wildcard ILIKE pattern built in the
Prisma.sql block (searchQuery via escapeLikePattern used against
pu."displayName" and sr."id"::text) prevents B-tree index use and will be slow
at scale; switch to recommending and documenting creation of pg_trgm trigram
GIN/GiST indexes (e.g., on lower("displayName") and on sr.id::text) and change
queries to use lower(...) for indexable trigram searches, and also add a
server-side cap on searchQuery length (e.g., reject >128 chars) to bound work
and avoid abuse; update any relevant query code paths using Prisma.sql and
escapeLikePattern to reflect lowercasing and trigram-ready patterns.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 35947dcf-fe8d-4a9e-9b2e-afbe9ac9c2dc

📥 Commits

Reviewing files that changed from the base of the PR and between b0d290e and 9a55c2a.

📒 Files selected for processing (13)
  • apps/backend/src/app/api/latest/internal/session-replays/route.tsx
  • apps/backend/src/app/api/latest/teams/crud.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx
  • apps/dashboard/src/components/data-table/team-member-search-table.tsx
  • apps/dashboard/src/components/data-table/team-member-table.tsx
  • apps/dashboard/src/components/data-table/user-search-picker.tsx
  • apps/dashboard/src/components/data-table/user-table.tsx
  • apps/dashboard/src/components/export-users-dialog.tsx
  • packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts
  • packages/template/src/lib/stack-app/apps/interfaces/server-app.ts
  • packages/template/src/lib/stack-app/teams/index.ts
  • packages/template/src/lib/stack-app/users/index.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • apps/dashboard/src/components/data-table/user-search-picker.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/page-client.tsx
  • apps/dashboard/src/components/data-table/team-member-search-table.tsx
  • apps/dashboard/src/components/data-table/user-table.tsx
  • apps/dashboard/src/components/export-users-dialog.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx
  • apps/dashboard/src/components/data-table/team-member-table.tsx

- Enhanced the `paginatePermissionDefinitions` function to include sorting by ID using a string comparison utility.
- Improved error handling for cursor validation, throwing a `StatusError` when the cursor is not found, ensuring better user feedback.
- Updated related components to maintain consistency in pagination behavior across the application.

These changes optimize the user experience when navigating permission definitions and enhance data retrieval reliability.
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: 3

🤖 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.

Inline comments:
In `@apps/backend/src/app/api/latest/permission-definitions-pagination.ts`:
- Around line 24-25: Change the truthy cursor checks to explicit null/undefined
checks so empty string cursors aren't treated as absent: replace uses of `if
(query.cursor)` (and the other occurrence at lines 41-42) with an explicit
presence check such as `query.cursor != null` (or `query.cursor !== undefined &&
query.cursor !== null`), and if you require non-empty cursors validate
`query.cursor !== ""` as well; keep the existing logic around `query.limit` and
the StatusError thrown when `query.cursor` is present but `query.limit` is
undefined.

In `@docs/content/docs/sdk/types/user.mdx`:
- Line 1948: The anchor link for the React-hook description points to the wrong
anchor `#serveruserlistteamspaginatedoptions`; update the link target to
`#serveruserlistteamspaginated` so it matches the actual heading anchor for
listTeamsPaginated; locate the sentence mentioning listTeamsPaginated (the hook
description) and replace the anchor suffix accordingly to restore the correct
internal link.
- Around line 1716-1718: The TOC anchor IDs and the internal doc link
incorrectly include the "[options]" suffix; update the anchor fragments for
listTeamsPaginated and useTeamsPaginated to follow the established pattern by
removing the "options" suffix: change any occurrences of
"#serveruserlistteamspaginatedoptions" to "#serveruserlistteamspaginated" and
"#serveruseruseteamspaginatedoptions" to "#serveruseruseteamspaginated" (this
affects the TOC entries for the methods listTeamsPaginated and useTeamsPaginated
and the referenced internal documentation link for the same methods).
🪄 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: 7b62d283-a819-46ef-aee4-e6ed2a88dda0

📥 Commits

Reviewing files that changed from the base of the PR and between 9a55c2a and 99f94e3.

📒 Files selected for processing (12)
  • apps/backend/src/app/api/latest/permission-definitions-pagination.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx
  • apps/dashboard/src/components/data-table/team-table.tsx
  • docs-mintlify/sdk/objects/stack-app.mdx
  • docs-mintlify/sdk/types/user.mdx
  • docs/content/docs/sdk/objects/stack-app.mdx
  • docs/content/docs/sdk/types/user.mdx
  • packages/dashboard-ui-components/src/components/data-grid/state.ts
  • packages/dashboard-ui-components/src/components/data-grid/use-url-state.ts
  • packages/template/src/dev-tool/index.ts
  • packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts
🚧 Files skipped from review as they are similar to previous changes (10)
  • packages/template/src/dev-tool/index.ts
  • apps/dashboard/src/components/data-table/team-table.tsx
  • docs-mintlify/sdk/objects/stack-app.mdx
  • docs/content/docs/sdk/objects/stack-app.mdx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx
  • packages/dashboard-ui-components/src/components/data-grid/use-url-state.ts
  • packages/dashboard-ui-components/src/components/data-grid/state.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/page-client.tsx
  • packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts
  • docs-mintlify/sdk/types/user.mdx

Comment thread apps/backend/src/app/api/latest/permission-definitions-pagination.ts Outdated
Comment thread docs/content/docs/sdk/types/user.mdx Outdated
Comment thread docs/content/docs/sdk/types/user.mdx Outdated
- Improved cursor-based pagination logic to ensure that the cursor matches the current filter set, preventing issues when filters are swapped between requests.
- Updated error handling to throw a `KnownErrors.ItemNotFound` when the cursor is invalid or does not meet duration constraints.
- Refactored related components to maintain consistency in pagination behavior across the application.

These changes enhance the reliability of data retrieval and improve user experience when navigating session replays and team permissions.
- Updated the user permissions type to allow for null values, indicating permission fetch failures distinctly from empty arrays.
- Improved loading state management in the TeamMemberTable and EditPermissionDialog components to provide better user feedback during permission retrieval.
- Refactored permission-related logic to ensure consistent handling of permissions across the dashboard.

These changes enhance the user experience by clearly indicating permission loading issues and improving the overall reliability of permission management.
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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
examples/e-commerce/src/app/edit-shop/page.tsx (1)

54-61: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Validate FormData input before persisting to metadata.

The code extracts formData values with type casts (as string, Number()) but performs no validation:

  • If a product name field is missing, formData.get(...) returns null, which when cast as string becomes the literal string "null" or causes unexpected behavior.
  • If a price field is missing or contains non-numeric text, Number(...) produces NaN, corrupting the product's dollarPrice.

Per coding guidelines, avoid type casts and fail early with explicit errors. Per the retrieved learning, user input (FormData) should be validated before persisting to serverMetadata.

🛡️ Proposed validation fix
 const products: Product[] = [];
 for (let i = 0; i < oldShop.products.length; i++) {
+  const name = formData.get(`product${i}Name`);
+  const priceStr = formData.get(`product${i}DollarPrice`);
+  if (typeof name !== 'string' || !name.trim()) {
+    throw new Error(`Product ${i} name is required`);
+  }
+  const dollarPrice = Number(priceStr);
+  if (!Number.isFinite(dollarPrice) || dollarPrice < 0) {
+    throw new Error(`Product ${i} price must be a non-negative number`);
+  }
   products.push({
     ...oldShop.products[i],
-    name: formData.get(`product${i}Name`) as string,
-    dollarPrice: Number(formData.get(`product${i}DollarPrice`)),
+    name: name.trim(),
+    dollarPrice,
   });
 }
🤖 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 `@examples/e-commerce/src/app/edit-shop/page.tsx` around lines 54 - 61, The
loop building products from oldShop.products must validate FormData values
before persisting: replace the unchecked casts in the products construction (the
block using formData.get(`product${i}Name`) and
formData.get(`product${i}DollarPrice`) inside the for loop that fills products)
with explicit checks—ensure product name exists and is a non-empty string
(reject if formData.get(...) is null or empty) and parse the price with
Number/parseFloat and verify !isNaN(value) and value >= 0; if validation fails,
throw/return an explicit error rather than writing bad values into dollarPrice
or serverMetadata. Keep the rest of the product shape (spread of
oldShop.products[i]) but fail early on invalid input so serverMetadata persists
only validated product.name and product.dollarPrice.
🧹 Nitpick comments (2)
docs-mintlify/openapi/admin.json (1)

5883-5890: ⚡ Quick win

Encode limit caps in the schema, not only the description.

These parameters document “capped at 200” but the schema omits explicit bounds (maximum, and ideally minimum: 1), so generated clients/validators can’t enforce the contract early.

Based on learnings: For generated OpenAPI artifacts in docs-mintlify/openapi/*.json, do not make manual edits; update generator metadata/logic and regenerate schemas.

Also applies to: 6454-6461, 8861-8868

🤖 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 `@docs-mintlify/openapi/admin.json` around lines 5883 - 5890, The OpenAPI
parameter "limit" currently only documents "capped at 200" in its description
but lacks schema bounds; update the generator metadata/logic that produces the
docs-mintlify/openapi JSON so the "limit" parameter schema includes "maximum":
200 and "minimum": 1 (and ensure this change is applied wherever the same param
is emitted - e.g., the other occurrences referenced) then regenerate the OpenAPI
artifacts rather than manually editing docs-mintlify/openapi/*.json so generated
clients/validators will enforce the contract.
docs-mintlify/openapi/client.json (1)

3277-3283: ⚡ Quick win

Constrain AI message roles in the read schema too.

These request schemas now narrow messages[].role to "user" | "assistant", but GET /internal/ai-conversations/{conversationId} still exposes messages[].role as plain string at Line 3378. That leaves the regenerated SDK with asymmetric types for the same model and weakens exhaustiveness checks on consumers of the new SDK surface.

Based on learnings: “For the generated OpenAPI artifacts in docs-mintlify/openapi/*.json, do not make manual edits. If the schema needs to change, update the schema generator logic (Yup/OpenAPI field metadata) and rerun the ‘Regenerate OpenAPI schemas’ workflow so committed JSON matches the generator output; manual changes will be reverted by regeneration.”

Also applies to: 3513-3519

🤖 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 `@docs-mintlify/openapi/client.json` around lines 3277 - 3283, The read schema
for GET /internal/ai-conversations/{conversationId} has messages[].role typed as
a plain string while request schemas constrain it to "user" | "assistant"; fix
this by updating the schema generator (the Yup/OpenAPI field metadata that
defines messages.role) so the generated OpenAPI output sets messages[].role to
an enum ["user","assistant"] for the conversation response, then rerun the
"Regenerate OpenAPI schemas" workflow to produce matching JSON; do not make a
manual edit to the generated client.json since regenerating will overwrite it.
🤖 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.

Inline comments:
In `@apps/backend/src/app/api/latest/internal/session-replays/route.tsx`:
- Around line 183-202: The cursor validation in prisma.sessionReplay.findFirst
is dropping the lower bound because lastEventAtFrom and lastEventAtTo are spread
into separate lastEventAt keys so the second overwrites the first; update the
where clause in route.tsx (the prisma.sessionReplay.findFirst call that uses
cursorId and auth.tenancy.id) to build a single lastEventAt object that
conditionally includes gte: lastEventAtFrom and lte: lastEventAtTo (or omits the
entire lastEventAt filter if neither bound exists), preserving both bounds when
both are provided so cursor validation matches the main query.

In `@docs-mintlify/openapi/server.json`:
- Around line 4921-4924: The OpenAPI change turned the common response field
"success" into a string enum ("maybe, only if user with e-mail exists"); revert
"success" back to a boolean and instead add a separate "message" or "status"
string field (or an enum) to convey account-enumeration-safe text; make this
change in the schema generator (Yup/OpenAPI field metadata) that produces the
JSON (not by hand-editing the generated JSON), then rerun the "Regenerate
OpenAPI schemas" workflow so the committed docs-mintlify/openapi JSON matches
the generator output.

In `@examples/e-commerce/src/app/edit-shop/page.tsx`:
- Line 17: The three server actions that currently spread ...user.serverMetadata
should guard against undefined before spreading; update each place where
user.serverMetadata is spread (the occurrences spreading ...user.serverMetadata)
to use a null-safe pattern that falls back to an empty object when undefined
(e.g., replace spreading user.serverMetadata with spreading (user.serverMetadata
|| {}) or equivalent), ensuring the optional chaining usage
(user.serverMetadata?.eCommerceExample?.shop) remains unchanged and no runtime
errors occur when serverMetadata is undefined.

---

Outside diff comments:
In `@examples/e-commerce/src/app/edit-shop/page.tsx`:
- Around line 54-61: The loop building products from oldShop.products must
validate FormData values before persisting: replace the unchecked casts in the
products construction (the block using formData.get(`product${i}Name`) and
formData.get(`product${i}DollarPrice`) inside the for loop that fills products)
with explicit checks—ensure product name exists and is a non-empty string
(reject if formData.get(...) is null or empty) and parse the price with
Number/parseFloat and verify !isNaN(value) and value >= 0; if validation fails,
throw/return an explicit error rather than writing bad values into dollarPrice
or serverMetadata. Keep the rest of the product shape (spread of
oldShop.products[i]) but fail early on invalid input so serverMetadata persists
only validated product.name and product.dollarPrice.

---

Nitpick comments:
In `@docs-mintlify/openapi/admin.json`:
- Around line 5883-5890: The OpenAPI parameter "limit" currently only documents
"capped at 200" in its description but lacks schema bounds; update the generator
metadata/logic that produces the docs-mintlify/openapi JSON so the "limit"
parameter schema includes "maximum": 200 and "minimum": 1 (and ensure this
change is applied wherever the same param is emitted - e.g., the other
occurrences referenced) then regenerate the OpenAPI artifacts rather than
manually editing docs-mintlify/openapi/*.json so generated clients/validators
will enforce the contract.

In `@docs-mintlify/openapi/client.json`:
- Around line 3277-3283: The read schema for GET
/internal/ai-conversations/{conversationId} has messages[].role typed as a plain
string while request schemas constrain it to "user" | "assistant"; fix this by
updating the schema generator (the Yup/OpenAPI field metadata that defines
messages.role) so the generated OpenAPI output sets messages[].role to an enum
["user","assistant"] for the conversation response, then rerun the "Regenerate
OpenAPI schemas" workflow to produce matching JSON; do not make a manual edit to
the generated client.json since regenerating will overwrite it.
🪄 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: 8c6274a1-02aa-4c78-b89f-704af1a806bb

📥 Commits

Reviewing files that changed from the base of the PR and between 99f94e3 and bc9b1e5.

📒 Files selected for processing (14)
  • apps/backend/src/app/api/latest/internal/session-replays/route.tsx
  • apps/backend/src/app/api/latest/teams/crud.tsx
  • apps/backend/src/lib/openapi.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/session-replays/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/team-analytics.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx
  • apps/dashboard/src/components/data-table/team-member-table.tsx
  • apps/e2e/tests/backend/endpoints/api/v1/project-permission-definitions.test.ts
  • apps/e2e/tests/backend/endpoints/api/v1/team-permission-definitions.test.ts
  • docs-mintlify/openapi/admin.json
  • docs-mintlify/openapi/client.json
  • docs-mintlify/openapi/server.json
  • docs-mintlify/openapi/webhooks.json
  • examples/e-commerce/src/app/edit-shop/page.tsx
🚧 Files skipped from review as they are similar to previous changes (4)
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/teams/[teamId]/team-analytics.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/session-replays/page-client.tsx
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/page-client.tsx
  • apps/dashboard/src/components/data-table/team-member-table.tsx

Comment on lines +183 to 202
const row = await prisma.sessionReplay.findFirst({
where: {
tenancyId: auth.tenancy.id,
id: cursorId,
...userIdsFilter.length > 0 ? { projectUserId: { in: userIdsFilter } } : {},
...lastEventAtFrom ? { lastEventAt: { gte: lastEventAtFrom } } : {},
...lastEventAtTo ? { lastEventAt: { lte: lastEventAtTo } } : {},
...teamIdsFilter.length > 0 ? {
projectUser: {
teamMembers: {
some: {
tenancyId: auth.tenancy.id,
teamId: { in: teamIdsFilter },
},
},
},
} : {},
},
select: { id: true, lastEventAt: true, startedAt: 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 | ⚡ Quick win

lastEventAtFrom filter is silently dropped from cursor validation when both bounds are set.

Two separate object spreads target the same lastEventAt key:

...lastEventAtFrom ? { lastEventAt: { gte: lastEventAtFrom } } : {},
...lastEventAtTo   ? { lastEventAt: { lte: lastEventAtTo   } } : {},

When both bounds are provided, the second spread overwrites the first, so findFirst only enforces lte and the gte lower bound is lost. This means a cursor whose lastEventAt is before lastEventAtFrom will still pass validation (no ItemNotFound), even though the main query at lines 218–219 correctly excludes such rows. The result is a cursor that anchors paging outside the active filter window — defeating the whole purpose of the validation block called out in the PR commit ("ensure the cursor matches the current filter set").

Note the teamIdsFilter and userIdsFilter are also keyed on tenancyId/teamId but those collisions happen to merge cleanly via Prisma nested types; only the lastEventAt pair self-collides.

🐛 Proposed fix: combine into a single `lastEventAt` clause
       const row = await prisma.sessionReplay.findFirst({
         where: {
           tenancyId: auth.tenancy.id,
           id: cursorId,
           ...userIdsFilter.length > 0 ? { projectUserId: { in: userIdsFilter } } : {},
-          ...lastEventAtFrom ? { lastEventAt: { gte: lastEventAtFrom } } : {},
-          ...lastEventAtTo ? { lastEventAt: { lte: lastEventAtTo } } : {},
+          ...(lastEventAtFrom || lastEventAtTo) ? {
+            lastEventAt: {
+              ...lastEventAtFrom ? { gte: lastEventAtFrom } : {},
+              ...lastEventAtTo ? { lte: lastEventAtTo } : {},
+            },
+          } : {},
           ...teamIdsFilter.length > 0 ? {
🤖 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/backend/src/app/api/latest/internal/session-replays/route.tsx` around
lines 183 - 202, The cursor validation in prisma.sessionReplay.findFirst is
dropping the lower bound because lastEventAtFrom and lastEventAtTo are spread
into separate lastEventAt keys so the second overwrites the first; update the
where clause in route.tsx (the prisma.sessionReplay.findFirst call that uses
cursorId and auth.tenancy.id) to build a single lastEventAt object that
conditionally includes gte: lastEventAtFrom and lte: lastEventAtTo (or omits the
entire lastEventAt filter if neither bound exists), preserving both bounds when
both are provided so cursor validation matches the main query.

Comment on lines +4921 to +4924
"type": "string",
"enum": [
"maybe, only if user with e-mail exists"
]
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 | ⚡ Quick win

Don't overload success with a status string.

Changing this endpoint to return success as a literal string makes one of the most common response fields in the spec stop being boolean, and the regenerated SDK will expose that breaking shape. If this endpoint needs an enumeration-safe message for account-enumeration protection, keep success boolean and add a separate message or status field in the generator source.

Based on learnings: For the generated OpenAPI artifacts in docs-mintlify/openapi/*.json, do not make manual edits. If the schema needs to change, update the schema generator logic (Yup/OpenAPI field metadata) and rerun the “Regenerate OpenAPI schemas” workflow so committed JSON matches the generator output; manual changes will be reverted by regeneration.

🤖 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 `@docs-mintlify/openapi/server.json` around lines 4921 - 4924, The OpenAPI
change turned the common response field "success" into a string enum ("maybe,
only if user with e-mail exists"); revert "success" back to a boolean and
instead add a separate "message" or "status" string field (or an enum) to convey
account-enumeration-safe text; make this change in the schema generator
(Yup/OpenAPI field metadata) that produces the JSON (not by hand-editing the
generated JSON), then rerun the "Regenerate OpenAPI schemas" workflow so the
committed docs-mintlify/openapi JSON matches the generator output.

await (await stackServerApp.listUsers()).find(u => u.id === user.id)!.setServerMetadata({
await user.setServerMetadata({
// TODO this should be more like a transaction
...user.serverMetadata,
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 | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check if serverMetadata can be undefined in the CurrentServerUser type

# Search for CurrentServerUser or similar type definitions related to getUser
ast-grep --pattern 'interface $_ {
  $$$
  serverMetadata$$$
  $$$
}'

# Search for getUser return type in stackServerApp
rg -nP -A5 'getUser.*\{' --type=ts -g '**/stack*' -g '!**/node_modules/**'

Repository: hexclave/stack-auth

Length of output: 3766


🏁 Script executed:

# Search for CurrentServerUser type definition
rg -n "type CurrentServerUser|interface CurrentServerUser" --type=ts --type=tsx

Repository: hexclave/stack-auth

Length of output: 91


🏁 Script executed:

# Read the file being reviewed to understand context
cat -n examples/e-commerce/src/app/edit-shop/page.tsx | head -80

Repository: hexclave/stack-auth

Length of output: 2786


🏁 Script executed:

# Search for serverMetadata property definition in types
rg -n "serverMetadata" --type=ts --type=tsx -B2 -A2 | head -100

Repository: hexclave/stack-auth

Length of output: 91


🏁 Script executed:

# Search for CurrentServerUser type definition
rg -n "type CurrentServerUser|interface CurrentServerUser" -A10 --type ts

Repository: hexclave/stack-auth

Length of output: 1032


🏁 Script executed:

# Alternative: search in the stack package since getUser comes from stackServerApp
find . -name "*.ts" -path "*/stack*" -type f | head -20

Repository: hexclave/stack-auth

Length of output: 888


🏁 Script executed:

# Search for setServerMetadata to understand the expected type
rg -n "setServerMetadata" -B5 -A5 --type ts | head -80

Repository: hexclave/stack-auth

Length of output: 6879


Ensure user.serverMetadata is defined before spreading it.

All three server actions spread ...user.serverMetadata without checking if it exists. Line 11 uses optional chaining (user.serverMetadata?.eCommerceExample?.shop), indicating the developer recognizes it could be undefined. Despite being typed as any, spreading undefined at runtime throws an error. Per coding guidelines, code defensively and explicitly handle undefined cases.

🛡️ Proposed null-safe spread
 await user.setServerMetadata({
-  ...user.serverMetadata,
+  ...(user.serverMetadata ?? {}),
   eCommerceExample: {

Also applies to: 39-39, 64-64

🤖 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 `@examples/e-commerce/src/app/edit-shop/page.tsx` at line 17, The three server
actions that currently spread ...user.serverMetadata should guard against
undefined before spreading; update each place where user.serverMetadata is
spread (the occurrences spreading ...user.serverMetadata) to use a null-safe
pattern that falls back to an empty object when undefined (e.g., replace
spreading user.serverMetadata with spreading (user.serverMetadata || {}) or
equivalent), ensuring the optional chaining usage
(user.serverMetadata?.eCommerceExample?.shop) remains unchanged and no runtime
errors occur when serverMetadata is undefined.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/user-analytics.tsx (1)

669-691: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Base the chart empty state on the chart window, not the summary window.

When a day filter is active, the chart is built from the expanded daily range (±15 days), but hasAnyEvent still comes from summary.total_events, which only counts the selected day. Filtering to a zero-event day will therefore hide surrounding activity and show the global “No events recorded” empty state instead.

🤖 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)/projects/[projectId]/users/[userId]/user-analytics.tsx
around lines 669 - 691, The empty-state check currently uses
data.summary.total_events which reflects only the summary window; change it to
derive from the chart window data (the densified daily series) so the empty
state matches the displayed chart. Move or recreate hasAnyEvent after the dense
computation and set it to something like: hasAnyEvent = dense.some(d =>
(d.total_events ?? 0) > 0) (or sum dense totals > 0), ensuring the variable
depends on dense (and dayFilter) rather than data.summary.total_events so the UI
reflects densifyDaily/daily and dayFilter logic.
🤖 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.

Inline comments:
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/users/[userId]/user-analytics.tsx:
- Around line 433-439: The page currently treats any non-empty dayFilter string
as a “filtered” state even when parseDayFilterRange(dayFilter) returns null; fix
by computing a single validated activeDayFilter/validatedFilterRange (call
parseDayFilterRange once, e.g. const activeDayFilter =
parseDayFilterRange(dayFilter)) and use that value everywhere you currently
check dayFilter or filterRange for rendering and querying (banner,
deltas/sparklines, description rewrite); if parseDayFilterRange returns null
clear/reset the URL param or treat the page as unfiltered so the UI and queries
stay consistent (update all usages of filterRange/dayFilter in
user-analytics.tsx including the sections around the parseDayFilterRange call
and the other affected blocks).
- Around line 753-755: The ActivityChart is being hidden at extra-large
viewports because it's wrapped in a div with the "xl:hidden" utility; to fix,
remove or change that responsive class so the chart remains visible on xl
screens—locate the wrapper around the ActivityChart (the div containing
<ActivityChart daily={dense} hasAnyEvent={hasAnyEvent}
description={chartDescription} />) and either delete the "xl:hidden" class or
replace it with the correct responsive utility (e.g., no hide or "hidden
xl:block" if you need it hidden on small screens but shown on xl).

---

Outside diff comments:
In
`@apps/dashboard/src/app/`(main)/(protected)/projects/[projectId]/users/[userId]/user-analytics.tsx:
- Around line 669-691: The empty-state check currently uses
data.summary.total_events which reflects only the summary window; change it to
derive from the chart window data (the densified daily series) so the empty
state matches the displayed chart. Move or recreate hasAnyEvent after the dense
computation and set it to something like: hasAnyEvent = dense.some(d =>
(d.total_events ?? 0) > 0) (or sum dense totals > 0), ensuring the variable
depends on dense (and dayFilter) rather than data.summary.total_events so the UI
reflects densifyDaily/daily and dayFilter logic.
🪄 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: 0dfc9c9f-afbf-417c-9090-dab32256f568

📥 Commits

Reviewing files that changed from the base of the PR and between bc9b1e5 and 37ead67.

📒 Files selected for processing (6)
  • apps/backend/src/app/api/latest/permission-definitions-pagination.ts
  • apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/users/[userId]/user-analytics.tsx
  • apps/dashboard/src/components/data-table/team-member-table.tsx
  • apps/dashboard/src/lib/apps-frontend.tsx
  • docs/content/docs/sdk/types/user.mdx
  • packages/template/src/dev-tool/index.ts
🚧 Files skipped from review as they are similar to previous changes (5)
  • apps/backend/src/app/api/latest/permission-definitions-pagination.ts
  • apps/dashboard/src/lib/apps-frontend.tsx
  • packages/template/src/dev-tool/index.ts
  • docs/content/docs/sdk/types/user.mdx
  • apps/dashboard/src/components/data-table/team-member-table.tsx

Comment on lines +433 to +439
function parseDayFilterRange(dayFilter: string): { since: Date, until: Date } | null {
const parts = dayFilter.split("-").map(Number);
if (parts.length !== 3 || parts.some((p) => !Number.isFinite(p))) return null;
const since = new Date(Date.UTC(parts[0], parts[1] - 1, parts[2]));
const until = new Date(since);
until.setUTCDate(until.getUTCDate() + 1);
return { since, until };
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 | ⚡ Quick win

Validate dayFilter before treating the page as filtered.

Right now malformed values fall back to the unfiltered queries (filterRange === null), but the render path still shows the filter banner, disables deltas/sparklines, and rewrites descriptions whenever dayFilter is a non-empty string. A bad URL state will therefore show filtered UI around unfiltered analytics. Please derive one validated activeDayFilter and use that for both querying and rendering, or clear invalid values immediately.

Also applies to: 448-451, 586-599

🤖 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)/projects/[projectId]/users/[userId]/user-analytics.tsx
around lines 433 - 439, The page currently treats any non-empty dayFilter string
as a “filtered” state even when parseDayFilterRange(dayFilter) returns null; fix
by computing a single validated activeDayFilter/validatedFilterRange (call
parseDayFilterRange once, e.g. const activeDayFilter =
parseDayFilterRange(dayFilter)) and use that value everywhere you currently
check dayFilter or filterRange for rendering and querying (banner,
deltas/sparklines, description rewrite); if parseDayFilterRange returns null
clear/reset the URL param or treat the page as unfiltered so the UI and queries
stay consistent (update all usages of filterRange/dayFilter in
user-analytics.tsx including the sections around the parseDayFilterRange call
and the other affected blocks).

Comment on lines +753 to +755
<div className="xl:hidden">
<ActivityChart daily={dense} hasAnyEvent={hasAnyEvent} description={chartDescription} />
</div>
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 | ⚡ Quick win

The daily activity chart disappears on xl screens.

This is the only ActivityChart render in the component, so wrapping it in xl:hidden removes the chart entirely for extra-large viewports.

♻️ Minimal fix
-      <div className="xl:hidden">
-        <ActivityChart daily={dense} hasAnyEvent={hasAnyEvent} description={chartDescription} />
-      </div>
+      <ActivityChart daily={dense} hasAnyEvent={hasAnyEvent} description={chartDescription} />
🤖 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)/projects/[projectId]/users/[userId]/user-analytics.tsx
around lines 753 - 755, The ActivityChart is being hidden at extra-large
viewports because it's wrapped in a div with the "xl:hidden" utility; to fix,
remove or change that responsive class so the chart remains visible on xl
screens—locate the wrapper around the ActivityChart (the div containing
<ActivityChart daily={dense} hasAnyEvent={hasAnyEvent}
description={chartDescription} />) and either delete the "xl:hidden" class or
replace it with the correct responsive utility (e.g., no hide or "hidden
xl:block" if you need it hidden on small screens but shown on xl).

- Replaced the use of `createDefaultDataGridState` with `useDataGridUrlState` across multiple components to improve state persistence and URL synchronization.
- Updated pagination logic in various tables to ensure consistent handling of grid states and improve user experience during data retrieval.
- Refactored components to utilize the new user picker table for better user selection functionality.

These changes enhance the overall reliability and usability of the dashboard's data grid features.
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