Skip to content

feat(dashboard): Persist pagination in list pages fixes NV-6997#9702

Merged
scopsy merged 2 commits intonextfrom
cursor/NV-6997-persist-pagination-in-workflows-842f
Jan 12, 2026
Merged

feat(dashboard): Persist pagination in list pages fixes NV-6997#9702
scopsy merged 2 commits intonextfrom
cursor/NV-6997-persist-pagination-in-workflows-842f

Conversation

@scopsy
Copy link
Contributor

@scopsy scopsy commented Dec 21, 2025

What changed? Why was the change needed?

This PR introduces a new usePersistedPageSize hook to store user-selected pagination page sizes in localStorage. This hook manages a single map (novu-page-sizes) where each tableId is mapped to its selected page size.

The change was needed to persist user-selected pagination values across sessions for various paginated lists in the dashboard, improving user experience by remembering their preferences.

Affected components now using this persistence:

  • Workflows list
  • Subscribers list
  • Topics list
  • Layouts list
  • Activity table
  • HTTP Logs table
  • Contexts list

Relevant link: NV-6997

Screenshots

N/A

Expand for optional sections

Related enterprise PR

N/A

Special notes for your reviewer

N/A


Linear Issue: NV-6997

Open in Cursor Open in Web

Introduce a hook to manage and persist table page sizes, improving user experience by remembering their preferences across sessions.

Co-authored-by: dima <dima@novu.co>
@cursor
Copy link
Contributor

cursor bot commented Dec 21, 2025

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@linear
Copy link

linear bot commented Dec 21, 2025

@netlify
Copy link

netlify bot commented Dec 21, 2025

Deploy Preview for dashboard-v2-novu-staging ready!

Name Link
🔨 Latest commit 610aea8
🔍 Latest deploy log https://app.netlify.com/projects/dashboard-v2-novu-staging/deploys/6964b5235414d70008ca1ecb
😎 Deploy Preview https://deploy-preview-9702.dashboard-v2.novu-staging.co
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@scopsy scopsy changed the title Persist pagination in workflows feat(dashboard): Persist pagination in list pages fixes NV-6997 Dec 21, 2025
@scopsy
Copy link
Contributor Author

scopsy commented Jan 12, 2026

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 12, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 12, 2026

Walkthrough

This pull request introduces a persistent page size mechanism across multiple dashboard tables. A new hook use-persisted-page-size is added to store page size preferences in local storage, keyed by table identifiers. Multiple components and URL state hooks are updated to initialize and update page sizes via this persistence layer instead of relying solely on in-memory state or URL parameters. Additionally, the ActivityTable component API is expanded with new props for filters and loading states, and page size change logic now supports conditional navigation based on a feature flag.

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(dashboard): Persist pagination in list pages fixes NV-6997' accurately and specifically describes the main change in the changeset - introducing pagination persistence to multiple dashboard list pages.
Description check ✅ Passed The description clearly explains what was changed (new usePersistedPageSize hook for localStorage), why it was needed (improve UX by remembering user preferences), which components are affected, and provides a reference to the related Linear issue.

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


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
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: 0

🧹 Nitpick comments (3)
apps/dashboard/src/components/layouts/layout-list.tsx (1)

9-13: Move constant definition after all imports.

The LAYOUTS_TABLE_ID constant is defined between import statements (lines 11-12), breaking import grouping conventions. Move it after all imports for cleaner organization.

Additionally, this constant is duplicated in use-layouts-url-state.tsx. Consider exporting it from a single location to avoid potential drift.

Suggested organization
 import {
   LayoutsFilter,
   LayoutsSortableColumn,
   LayoutsUrlState,
   useLayoutsUrlState,
 } from '@/components/layouts/hooks/use-layouts-url-state';
 import { usePersistedPageSize } from '@/hooks/use-persisted-page-size';
-
-const LAYOUTS_TABLE_ID = 'layouts-list';
-
 import { LayoutListBlank } from '@/components/layouts/layout-list-blank';
 import { LayoutRow, LayoutRowSkeleton } from '@/components/layouts/layout-row';
+// ... other imports ...
+
+const LAYOUTS_TABLE_ID = 'layouts-list';

Or better, export the constant from use-layouts-url-state.tsx and import it here.

apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx (1)

4-6: Consider consolidating the LAYOUTS_TABLE_ID constant.

This constant is also defined in layout-list.tsx. Having the same constant in two files risks them drifting apart. Consider exporting it from this file so layout-list.tsx can import it:

-const LAYOUTS_TABLE_ID = 'layouts-list';
+export const LAYOUTS_TABLE_ID = 'layouts-list';

Also applies to: 18-24

apps/dashboard/src/hooks/use-persisted-page-size.ts (1)

36-43: Consider simplifying the useEffect dependency.

The useEffect includes pageSize in its dependency array, but its purpose is primarily to sync state when tableId changes. Including pageSize causes the effect to run on every page size change, even though the condition prevents unnecessary state updates.

♻️ Optional: Remove pageSize from dependencies
  useEffect(() => {
    const map = getPageSizeMap();
    const stored = map[tableId];

-   if (stored !== undefined && stored !== pageSize) {
+   if (stored !== undefined) {
      setPageSizeState(stored);
    }
- }, [tableId, pageSize]);
+ }, [tableId]);

This ensures the effect only runs when tableId changes, which is the primary use case. When setPageSize is called, it updates both state and storage synchronously, so no additional sync is needed.

📜 Review details

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cd1191a and 610aea8.

📒 Files selected for processing (10)
  • apps/dashboard/src/components/activity/activity-table.tsx
  • apps/dashboard/src/components/contexts/hooks/use-contexts-url-state.ts
  • apps/dashboard/src/components/http-logs/logs-table.tsx
  • apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx
  • apps/dashboard/src/components/layouts/layout-list.tsx
  • apps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.ts
  • apps/dashboard/src/components/topics/hooks/use-topics-url-state.ts
  • apps/dashboard/src/hooks/use-logs-url-state.ts
  • apps/dashboard/src/hooks/use-persisted-page-size.ts
  • apps/dashboard/src/pages/workflows.tsx
🧰 Additional context used
📓 Path-based instructions (10)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Write concise, technical TypeScript code with accurate examples
Use descriptive variable names with auxiliary verbs (isLoading, hasError)
Add blank lines before return statements
Import motion components from 'motion/react' instead of 'motion-react'

**/*.{ts,tsx}: Write concise, technical TypeScript code with accurate examples
Use functional and declarative programming patterns; avoid classes
Prefer iteration and modularization over code duplication, minimize code duplication as possible
Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError)
Structure files: exported component, subcomponents, helpers, static content, types
Don't leave comments in code, unless they explain something complex and not trivial
Don't use nested ternaries
Favor named exports for components
Use TypeScript for all code; prefer interfaces over types
In front end code, use types over interfaces
Use functional components with TypeScript types
Use the "function" keyword for pure functions
Avoid unnecessary curly braces in conditionals; use concise syntax for simple statements
Add blank lines before return statements
When importing "motion-react" package, import it from "motion/react"

Files:

  • apps/dashboard/src/components/http-logs/logs-table.tsx
  • apps/dashboard/src/components/contexts/hooks/use-contexts-url-state.ts
  • apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx
  • apps/dashboard/src/components/layouts/layout-list.tsx
  • apps/dashboard/src/components/activity/activity-table.tsx
  • apps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.ts
  • apps/dashboard/src/pages/workflows.tsx
  • apps/dashboard/src/components/topics/hooks/use-topics-url-state.ts
  • apps/dashboard/src/hooks/use-persisted-page-size.ts
  • apps/dashboard/src/hooks/use-logs-url-state.ts
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use functional and declarative programming patterns; avoid classes

Files:

  • apps/dashboard/src/components/http-logs/logs-table.tsx
  • apps/dashboard/src/components/contexts/hooks/use-contexts-url-state.ts
  • apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx
  • apps/dashboard/src/components/layouts/layout-list.tsx
  • apps/dashboard/src/components/activity/activity-table.tsx
  • apps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.ts
  • apps/dashboard/src/pages/workflows.tsx
  • apps/dashboard/src/components/topics/hooks/use-topics-url-state.ts
  • apps/dashboard/src/hooks/use-persisted-page-size.ts
  • apps/dashboard/src/hooks/use-logs-url-state.ts
**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Favor named exports for components

Files:

  • apps/dashboard/src/components/http-logs/logs-table.tsx
  • apps/dashboard/src/components/contexts/hooks/use-contexts-url-state.ts
  • apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx
  • apps/dashboard/src/components/layouts/layout-list.tsx
  • apps/dashboard/src/components/activity/activity-table.tsx
  • apps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.ts
  • apps/dashboard/src/pages/workflows.tsx
  • apps/dashboard/src/components/topics/hooks/use-topics-url-state.ts
  • apps/dashboard/src/hooks/use-persisted-page-size.ts
  • apps/dashboard/src/hooks/use-logs-url-state.ts
apps/{dashboard,web}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Prefer types over interfaces in frontend code

Files:

  • apps/dashboard/src/components/http-logs/logs-table.tsx
  • apps/dashboard/src/components/contexts/hooks/use-contexts-url-state.ts
  • apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx
  • apps/dashboard/src/components/layouts/layout-list.tsx
  • apps/dashboard/src/components/activity/activity-table.tsx
  • apps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.ts
  • apps/dashboard/src/pages/workflows.tsx
  • apps/dashboard/src/components/topics/hooks/use-topics-url-state.ts
  • apps/dashboard/src/hooks/use-persisted-page-size.ts
  • apps/dashboard/src/hooks/use-logs-url-state.ts
apps/dashboard/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use lowercase with dashes for directories and files in dashboard (e.g., components/auth-wizard)

Files:

  • apps/dashboard/src/components/http-logs/logs-table.tsx
  • apps/dashboard/src/components/contexts/hooks/use-contexts-url-state.ts
  • apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx
  • apps/dashboard/src/components/layouts/layout-list.tsx
  • apps/dashboard/src/components/activity/activity-table.tsx
  • apps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.ts
  • apps/dashboard/src/pages/workflows.tsx
  • apps/dashboard/src/components/topics/hooks/use-topics-url-state.ts
  • apps/dashboard/src/hooks/use-persisted-page-size.ts
  • apps/dashboard/src/hooks/use-logs-url-state.ts
apps/dashboard/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Favor named exports for components in dashboard

Files:

  • apps/dashboard/src/components/http-logs/logs-table.tsx
  • apps/dashboard/src/components/contexts/hooks/use-contexts-url-state.ts
  • apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx
  • apps/dashboard/src/components/layouts/layout-list.tsx
  • apps/dashboard/src/components/activity/activity-table.tsx
  • apps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.ts
  • apps/dashboard/src/pages/workflows.tsx
  • apps/dashboard/src/components/topics/hooks/use-topics-url-state.ts
  • apps/dashboard/src/hooks/use-persisted-page-size.ts
  • apps/dashboard/src/hooks/use-logs-url-state.ts
apps/dashboard/**/*

📄 CodeRabbit inference engine (.cursor/rules/figma.mdc)

apps/dashboard/**/*: Use the assets endpoint from Figma Dev Mode MCP Server to serve image and SVG assets
If the Figma Dev Mode MCP Server returns a localhost source for an image or SVG, use that image or SVG source directly without modification
Do not import or add new icon packages; all assets should come from the Figma payload
Do not use or create placeholders if a localhost source is provided by the Figma Dev Mode MCP Server

Use lowercase with dashes for directory and file names (e.g., components/auth-wizard)

Files:

  • apps/dashboard/src/components/http-logs/logs-table.tsx
  • apps/dashboard/src/components/contexts/hooks/use-contexts-url-state.ts
  • apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx
  • apps/dashboard/src/components/layouts/layout-list.tsx
  • apps/dashboard/src/components/activity/activity-table.tsx
  • apps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.ts
  • apps/dashboard/src/pages/workflows.tsx
  • apps/dashboard/src/components/topics/hooks/use-topics-url-state.ts
  • apps/dashboard/src/hooks/use-persisted-page-size.ts
  • apps/dashboard/src/hooks/use-logs-url-state.ts
apps/dashboard/**/components/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/dashboard.mdc)

Favor named exports for components

Files:

  • apps/dashboard/src/components/http-logs/logs-table.tsx
  • apps/dashboard/src/components/contexts/hooks/use-contexts-url-state.ts
  • apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx
  • apps/dashboard/src/components/layouts/layout-list.tsx
  • apps/dashboard/src/components/activity/activity-table.tsx
  • apps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.ts
  • apps/dashboard/src/components/topics/hooks/use-topics-url-state.ts
**

📄 CodeRabbit inference engine (.cursor/rules/novu.mdc)

Use lowercase with dashes for directories and files (e.g., components/auth-wizard)

Files:

  • apps/dashboard/src/components/http-logs/logs-table.tsx
  • apps/dashboard/src/components/contexts/hooks/use-contexts-url-state.ts
  • apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx
  • apps/dashboard/src/components/layouts/layout-list.tsx
  • apps/dashboard/src/components/activity/activity-table.tsx
  • apps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.ts
  • apps/dashboard/src/pages/workflows.tsx
  • apps/dashboard/src/components/topics/hooks/use-topics-url-state.ts
  • apps/dashboard/src/hooks/use-persisted-page-size.ts
  • apps/dashboard/src/hooks/use-logs-url-state.ts
**/*.tsx

📄 CodeRabbit inference engine (.cursor/rules/novu.mdc)

**/*.tsx: Use declarative JSX
Wrap client components in Suspense with fallback
Use dynamic loading for non-critical components

Files:

  • apps/dashboard/src/components/http-logs/logs-table.tsx
  • apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx
  • apps/dashboard/src/components/layouts/layout-list.tsx
  • apps/dashboard/src/components/activity/activity-table.tsx
  • apps/dashboard/src/pages/workflows.tsx
🧠 Learnings (2)
📚 Learning: 2025-11-25T11:29:52.304Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-11-25T11:29:52.304Z
Learning: Applies to **/*.{ts,tsx} : Import motion components from 'motion/react' instead of 'motion-react'

Applied to files:

  • apps/dashboard/src/components/http-logs/logs-table.tsx
  • apps/dashboard/src/components/activity/activity-table.tsx
📚 Learning: 2026-01-07T13:09:45.904Z
Learnt from: CR
Repo: novuhq/novu PR: 0
File: .cursor/rules/novu.mdc:0-0
Timestamp: 2026-01-07T13:09:45.904Z
Learning: Applies to **/*.{ts,tsx} : When importing "motion-react" package, import it from "motion/react"

Applied to files:

  • apps/dashboard/src/components/http-logs/logs-table.tsx
🧬 Code graph analysis (6)
apps/dashboard/src/components/contexts/hooks/use-contexts-url-state.ts (1)
apps/dashboard/src/hooks/use-persisted-page-size.ts (2)
  • getPersistedPageSize (61-65)
  • usePersistedPageSize (27-59)
apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx (1)
apps/dashboard/src/hooks/use-persisted-page-size.ts (1)
  • getPersistedPageSize (61-65)
apps/dashboard/src/components/activity/activity-table.tsx (1)
apps/dashboard/src/hooks/use-persisted-page-size.ts (1)
  • usePersistedPageSize (27-59)
apps/dashboard/src/pages/workflows.tsx (1)
apps/dashboard/src/hooks/use-persisted-page-size.ts (2)
  • getPersistedPageSize (61-65)
  • usePersistedPageSize (27-59)
apps/dashboard/src/hooks/use-persisted-page-size.ts (1)
apps/dashboard/src/utils/local-storage.ts (2)
  • loadFromStorage (19-43)
  • saveToStorage (5-17)
apps/dashboard/src/hooks/use-logs-url-state.ts (1)
apps/dashboard/src/hooks/use-persisted-page-size.ts (2)
  • usePersistedPageSize (27-59)
  • getPersistedPageSize (61-65)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
  • GitHub Check: E2E test API / Test E2E
  • GitHub Check: Redirect rules - dashboard-v2-novu-staging
  • GitHub Check: Header rules - dashboard-v2-novu-staging
  • GitHub Check: Pages changed - dashboard-v2-novu-staging
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Analyze (typescript)
🔇 Additional comments (14)
apps/dashboard/src/components/layouts/layout-list.tsx (1)

133-136: LGTM!

The usePersistedPageSize hook is correctly integrated. The handlePageSizeChange function properly persists the new page size before updating filters and correctly resets offset to 0 to avoid showing an empty page.

Also applies to: 165-171

apps/dashboard/src/components/http-logs/logs-table.tsx (1)

30-43: LGTM!

Clean refactor that properly delegates page size management to the useLogsUrlState hook. The component is now simpler and follows better separation of concerns.

apps/dashboard/src/components/layouts/hooks/use-layouts-url-state.tsx (1)

23-23: LGTM on the persistence integration.

The getPersistedPageSize call correctly provides the persisted page size as the default limit, falling back to 10 if no value is stored.

apps/dashboard/src/pages/workflows.tsx (2)

43-44: LGTM!

The persisted page size integration follows the established pattern correctly:

  • WORKFLOWS_TABLE_ID constant for consistent identification
  • getPersistedPageSize at module level for default
  • usePersistedPageSize hook in component for updates

Also applies to: 51-51, 57-60


328-336: LGTM!

The page size change handler correctly persists the new size and resets pagination by removing the offset parameter. This keeps the URL clean while ensuring users start from page 1 after changing page size.

apps/dashboard/src/components/contexts/hooks/use-contexts-url-state.ts (2)

4-6: LGTM!

The persisted page size integration is correctly implemented:

  • Module-level getPersistedPageSize for default limit
  • Hook-level usePersistedPageSize for state management
  • Consistent CONTEXTS_TABLE_ID identifier

Also applies to: 32-32, 38-41


174-186: LGTM!

The handlePageSizeChange correctly handles cursor-based pagination by clearing both after and before cursors when page size changes, ensuring users return to the first page. The dependency array properly includes setPersistedPageSize.

apps/dashboard/src/components/subscribers/hooks/use-subscribers-url-state.ts (1)

5-10: LGTM!

The persisted page size integration is well-implemented. The pattern correctly:

  • Initializes the default limit from persisted storage
  • Persists the page size before updating URL params
  • Includes setPersistedPageSize in the dependency array

Also applies to: 29-29, 59-62, 241-250

apps/dashboard/src/components/topics/hooks/use-topics-url-state.ts (1)

4-7: LGTM!

Consistent implementation with the subscribers hook. The page size persistence is correctly wired up with proper initialization, setter usage, and dependency tracking.

Also applies to: 34-34, 40-43, 184-198

apps/dashboard/src/hooks/use-logs-url-state.ts (1)

5-9: LGTM!

The logs URL state hook correctly integrates persisted page sizes with a sensible default of 20 for logs. The handlePageSizeChange implementation properly:

  • Persists the new size
  • Updates the URL limit parameter
  • Resets pagination by deleting the page param

Also applies to: 25-25, 37-40, 66-69, 93-104

apps/dashboard/src/components/activity/activity-table.tsx (2)

19-19: LGTM!

The persisted page size integration is correctly implemented with the ACTIVITY_TABLE_ID constant and the usePersistedPageSize hook.

Also applies to: 25-26, 49-52


121-128: Feature-flag-aware page size change handling looks good.

The branching logic correctly handles both cursor-based (new) and page-based (legacy) pagination when the page size changes, ensuring users are navigated to the first page in both cases.

apps/dashboard/src/hooks/use-persisted-page-size.ts (2)

1-15: LGTM!

Clean setup with appropriate storage key constants and helper functions. The PageSizeMap type alias and helper functions follow the coding guidelines (types over interfaces, functional patterns).


61-65: LGTM!

The getPersistedPageSize helper provides a clean synchronous API for initializing default values outside of React components or during module initialization.

@scopsy scopsy marked this pull request as ready for review January 12, 2026 09:47
@scopsy scopsy merged commit 2bd63ab into next Jan 12, 2026
29 checks passed
@scopsy scopsy deleted the cursor/NV-6997-persist-pagination-in-workflows-842f branch January 12, 2026 09:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants