Skip to content

Release: Chat Drawer Pagination - 22.10.2025 - #621

Merged
maxtechera merged 1 commit into
productionfrom
staging
Oct 22, 2025
Merged

Release: Chat Drawer Pagination - 22.10.2025#621
maxtechera merged 1 commit into
productionfrom
staging

Conversation

@maxtechera

Copy link
Copy Markdown
Collaborator

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

  • Chat Drawer Pagination (Add pagination and lazy loading to chat drawer #620)
    • Implements cursor-based pagination for chat loading
    • Adds lazy loading with infinite scroll
    • Reduces initial load from all chats to 20 chats per page
    • Fetches chats exclusively from remote chatflows API

🔧 Technical Improvements

  • Backend (Chatflows API)

    • Added pagination support with limit and cursor query params
    • Implemented cursor-based pagination using createdDate field
    • Added validation to prevent abuse (1-100 items per page)
    • Optimized TypeORM queries by using direct column IDs (eliminated unnecessary JOINs)
  • Frontend (Chat Drawer)

    • Replaced useSWR with useSWRInfinite for infinite scroll
    • Added IntersectionObserver to detect scroll to bottom
    • Shows loading spinner when fetching more chats
    • Automatically stops loading when all chats are loaded
  • API Layer

    • Added limit validation in both local and chatflows APIs
    • Enforces max limit of 100 items per page
    • Added cursor date validation to prevent errors

📊 Performance Impact

  • Before: Loading all chats on initial page load (could be 100s of chats)
  • After: Loading 20 chats initially, then 20 more on demand
  • Query Optimization: Removed 2 unnecessary JOINs from database queries

Testing Checklist

  • 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)

Deployment Notes

  • No database migrations required
  • No environment variable changes needed
  • Backwards compatible with existing chat data

## 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)
@vercel

vercel Bot commented Oct 22, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
answerai-docs Building Building Preview Oct 22, 2025 4:35pm
the-answerai Building Building Preview Oct 22, 2025 4:35pm

@maxtechera
maxtechera merged commit 47d77c7 into production Oct 22, 2025
8 of 9 checks passed

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +18 to +21
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +14 to +17
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 })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 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".

Comment on lines +18 to +21
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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

Comment on lines +14 to +17
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 })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge 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 👍 / 👎.

maxtechera added a commit that referenced this pull request Oct 23, 2025
## 📦 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>
maxtechera added a commit that referenced this pull request Oct 24, 2025
# 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>
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.

1 participant