Release: Chat Drawer Pagination - 22.10.2025 - #621
Conversation
## Summary - Implements cursor-based pagination for chat loading in the drawer - Adds lazy loading with infinite scroll as user scrolls to bottom - Reduces initial load from all chats to 20 chats per page - Fetches chats exclusively from remote chatflows API ## Changes ### Backend (packages/server) - **Chatflows API** (`/api/v1/chats`): Added pagination support - Accepts `limit` (default: 20, max: 100) and `cursor` query params - Uses cursor-based pagination with `createdDate` field - Validates limit to prevent abuse (1-100 range) ### API Endpoint (apps/web) - **Local API** (`/api/chats`): Proxy to chatflows API with pagination - Accepts and validates `limit` and `cursor` params - Enforces max limit of 100 items per page ### Client (packages-answers/ui) - **ChatDrawer**: Infinite scroll implementation - Replaced `useSWR` with `useSWRInfinite` - Added IntersectionObserver to detect scroll to bottom - Shows loading spinner when fetching more chats - Stops loading when no more chats available - Uses `CHATS_PAGE_SIZE` constant (20) ### Utilities (packages-answers/utils) - **getChats**: Simplified to only fetch from remote API - Removed local Prisma chat fetching - Removed merge/deduplication logic - Directly returns chatflow API results with pagination ## Test plan - [ ] Verify initial load shows first 20 chats - [ ] Scroll to bottom and verify more chats load automatically - [ ] Verify loading spinner appears while fetching - [ ] Verify loading stops when all chats are loaded - [ ] Test with accounts that have < 20, exactly 20, and > 20 chats - [ ] Verify limit validation works (requesting > 100 should cap at 100)
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const { searchParams } = new URL(req.url) | ||
| const requestedLimit = parseInt(searchParams.get('limit') || String(DEFAULT_PAGE_SIZE)) | ||
| const limit = Math.min(Math.max(1, requestedLimit), MAX_PAGE_SIZE) | ||
| const cursor = searchParams.get('cursor') || undefined |
There was a problem hiding this comment.
Sanitize
limit when parsing chat list query
The new GET handler clamps the limit query param using Math.min(Math.max(1, requestedLimit), MAX_PAGE_SIZE), but requestedLimit can be NaN when a client calls /api/chats?limit=abc or /api/chats?limit=. Because Math.min/Math.max propagate NaN, limit ends up as NaN and is forwarded to getChats, ultimately producing a limit=NaN remote request. The backend then attempts to use NaN for the SQL take clause and returns a 500. To keep the “1‑100 items per page” validation promise, the handler should detect Number.isNaN(requestedLimit) and either default to DEFAULT_PAGE_SIZE or return a 400 before calling the service.
Useful? React with 👍 / 👎.
| const requestedLimit = req.query.limit ? parseInt(req.query.limit as string, 10) : DEFAULT_PAGE_SIZE | ||
| const limit = Math.min(Math.max(1, requestedLimit), MAX_PAGE_SIZE) | ||
| const cursor = req.query.cursor as string | undefined | ||
| const apiResponse = await chatsService.getAllChats(req.user, { limit, cursor }) |
There was a problem hiding this comment.
Backend pagination accepts non-numeric
limit
The controller similarly parses req.query.limit with parseInt and clamps it, but without an isNaN guard a value like limit=foo produces limit = NaN. This NaN is passed to chatsService.getAllChats, which hands it to TypeORM’s take option. TypeORM will generate a malformed LIMIT and throw, turning a bad client parameter into a 500 instead of a controlled 400/default value. Adding an Number.isNaN check before clamping (or defaulting to DEFAULT_PAGE_SIZE) prevents arbitrary errors from the public API.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const { searchParams } = new URL(req.url) | ||
| const requestedLimit = parseInt(searchParams.get('limit') || String(DEFAULT_PAGE_SIZE)) | ||
| const limit = Math.min(Math.max(1, requestedLimit), MAX_PAGE_SIZE) | ||
| const cursor = searchParams.get('cursor') || undefined |
There was a problem hiding this comment.
Sanitize
limit when parsing chat list query
The new GET handler clamps the limit query param using Math.min(Math.max(1, requestedLimit), MAX_PAGE_SIZE), but requestedLimit can be NaN when a client calls /api/chats?limit=abc or /api/chats?limit=. Because Math.min/Math.max propagate NaN, limit ends up as NaN and is forwarded to getChats, ultimately producing a limit=NaN remote request. The backend then attempts to use NaN for the SQL take clause and returns a 500. To keep the “1‑100 items per page” validation promise, the handler should detect Number.isNaN(requestedLimit) and either default to DEFAULT_PAGE_SIZE or return a 400 before calling the service.
Useful? React with 👍 / 👎.
| const requestedLimit = req.query.limit ? parseInt(req.query.limit as string, 10) : DEFAULT_PAGE_SIZE | ||
| const limit = Math.min(Math.max(1, requestedLimit), MAX_PAGE_SIZE) | ||
| const cursor = req.query.cursor as string | undefined | ||
| const apiResponse = await chatsService.getAllChats(req.user, { limit, cursor }) |
There was a problem hiding this comment.
Backend pagination accepts non-numeric
limit
The controller similarly parses req.query.limit with parseInt and clamps it, but without an isNaN guard a value like limit=foo produces limit = NaN. This NaN is passed to chatsService.getAllChats, which hands it to TypeORM’s take option. TypeORM will generate a malformed LIMIT and throw, turning a bad client parameter into a 500 instead of a controlled 400/default value. Adding an Number.isNaN check before clamping (or defaulting to DEFAULT_PAGE_SIZE) prevents arbitrary errors from the public API.
Useful? React with 👍 / 👎.
## 📦 Release Summary This release includes updates from staging to production, bringing multiple improvements, fixes, and new features. ## 🎯 Key Changes ### Recent Updates - **HOTFIX**: Remove metadata from Stripe events - **fix**: Handle Stripe 35-day limitation for historical billing data (#624) - **chore**: Updates to fix publish automation (#623) - **feat**: Added automation for publishing aai-embed-react (#616) - **feat**: Comprehensive export/import functionality enhancement (AAI-501) (#469) ### Previous Releases Included - Billing Metadata Filtering - 22.10.2025 (#622) - Chat Drawer Pagination - 22.10.2025 (#621) - Billing Tag Self-Healing and Optimized Trace Fetching (#617) - Facebook Pixel Tracking Fixes (#608) - JLINC audit log, env overrides, and partnership page updates (#597) - Analytics tracing, billing accuracy, and admin navigation fixes (#591) ## 📊 Impact Summary - **Files changed**: 2956 files - **Additions**: 312,083 lines - **Deletions**: 25,803 lines ## ✅ Pre-Release Checklist - [ ] All tests passing on staging environment - [ ] No critical errors in staging logs - [ ] Database migrations reviewed and tested - [ ] Environment variables documented - [ ] Monitoring alerts configured - [ ] Rollback plan documented ## 🚀 Deployment Notes This is a standard release from staging to production. Please ensure all pre-release checks are completed before approving. --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: DiegoC <diecoscai@gmail.com> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: Cameron Taylor <50385537+ct3685@users.noreply.github.com> Co-authored-by: Jaime Morales <jaime.raul.morales@gmail.com>
# Release: Staging to Production - October 24, 2025 This PR releases all tested changes from staging to production. ## 🎯 Key Features ### Organizational Billing Override (#637) - **NEW**: Support for organizational billing where all users' usage consolidates to a single Stripe customer - Environment variable: `BILLING_OVERRIDE_CUSTOMER_ID=true` to enable - Users maintain individual trace attribution while billing flows to organization - Fixes P1 issue where usage events returned empty list for organizational accounts ### Langfuse API Optimization (#637) - **PERFORMANCE**: Reduced parallel API load by 85% - PAGE_BATCH_SIZE: 15 → 3 pages - TRACE_BATCH_SIZE: 15 → 5 traces - RATE_LIMIT_DELAY_MS: 1000ms → 2000ms - LOOKBACK_DAYS: 90 → 7 days (default) - Prevents "database resource limit exceeded" errors on large datasets - All settings configurable via environment variables ### Environment Variable Standardization (#637) - **BREAKING**: All billing-related env vars now use `BILLING_` prefix - `STRIPE_FREE_PRICE_ID` → `BILLING_STRIPE_FREE_PRICE_ID` - `STRIPE_CREDITS_METER_ID` → `BILLING_STRIPE_CREDITS_METER_ID` - `STRIPE_AI_TOKENS_METER_ID` → `BILLING_STRIPE_AI_TOKENS_METER_ID` - `STRIPE_MARGIN_MULTIPLIER` → `BILLING_STRIPE_MARGIN_MULTIPLIER` - Updated documentation and code references ## 🐛 Bug Fixes ### P1: Usage Events Filter for Organizational Billing - Fixed filter logic in `getUsageEvents` to handle organizational override - Users now correctly see their own traces when override is enabled - Admins continue to see all traces ### Billing Metadata and Tracking - Previous releases included billing metadata filtering improvements - Self-healing for billing tags - Enhanced analytics tracing accuracy ## 📋 Other Improvements - Chat drawer pagination enhancements - Facebook Pixel tracking fixes - JLINC audit log improvements - Admin navigation fixes - Dependencies updates (mammoth 1.10.0 → 1.11.0) ## 🔄 Migration Notes **Required Environment Variable Updates:** Update your production environment with the new `BILLING_STRIPE_*` prefixed variables. The old names will no longer work. **New Optional Variables:** - `BILLING_OVERRIDE_CUSTOMER_ID` - Set to "true" for organizational billing - `BILLING_DEFAULT_STRIPE_CUSTOMER_ID` - Organization's Stripe customer ID - `BILLING_SYNC_LOOKBACK_DAYS` - Days to look back for unprocessed traces (default: 7) - `BILLING_SYNC_PAGE_BATCH_SIZE` - Parallel page fetches (default: 3) - `BILLING_SYNC_TRACE_BATCH_SIZE` - Parallel trace fetches (default: 5) - `BILLING_SYNC_RATE_LIMIT_MS` - Delay between API calls (default: 2000) ## ✅ Testing All changes have been tested in staging environment including: - Organizational billing override functionality - Langfuse API optimization under load - Usage events filtering with override enabled - Environment variable migrations ## 📦 Included PRs - #637 - Organizational billing override and Langfuse optimization - #635 - Previous staging release - #628 - Embed package updates - #627 - Previous production release - #622 - Billing metadata filtering - #621 - Chat drawer pagination - #617 - Billing tag self-healing - And more (see commit history) --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude <noreply@anthropic.com>
Release Summary
This release adds pagination and lazy loading to the chat drawer, improving performance and user experience for users with many chats.
Changes Included
🚀 Features
🔧 Technical Improvements
Backend (Chatflows API)
limitandcursorquery paramscreatedDatefieldFrontend (Chat Drawer)
useSWRwithuseSWRInfinitefor infinite scrollAPI Layer
📊 Performance Impact
Testing Checklist
Deployment Notes