feat(sidebar): add recent conversation feed - #1041
Conversation
📝 WalkthroughWalkthroughRecent conversations can now be fetched through a paginated provider API, normalized by the sessions service, managed by the sidebar controller, and rendered with loading, error, empty, selection, and load-more states. Shared compact age formatting is used across session views. ChangesRecent conversations
Sequence Diagram(s)sequenceDiagram
participant Sidebar as SidebarRecentConversations
participant Controller as useSidebarController
participant API as api.recentConversations
participant Route as provider recent route
participant Service as sessionsService.listRecentSessions
participant Database as sessionsDb.getRecentSessionsPage
Sidebar->>Controller: reload or load more conversations
Controller->>API: request limit and offset
API->>Route: GET /api/providers/sessions/recent
Route->>Service: listRecentSessions(limit, offset)
Service->>Database: getRecentSessionsPage(limit, offset)
Database-->>Service: sessions and total
Service-->>Route: conversations and hasMore
Route-->>API: success response
API-->>Controller: paginated payload
Controller-->>Sidebar: render conversation states and rows
Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (3)
server/modules/providers/provider.routes.ts (1)
367-373: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUse strict integer parsing for query parameters.
Number.parseInttolerates floating-point values (truncating them) and trailing non-numeric characters (e.g.,'10.5'or'10abc'parses as10without failing). For stricter query validation, consider usingNumber(raw)andNumber.isInteger().♻️ Proposed refactor
- const parsed = Number.parseInt(raw, 10); - if (Number.isNaN(parsed) || parsed < minimum || parsed > maximum) { + const parsed = Number(raw); + if (!Number.isInteger(parsed) || parsed < minimum || parsed > maximum) {🤖 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 `@server/modules/providers/provider.routes.ts` around lines 367 - 373, Update the query-parameter validation around the parsed value to convert raw input with Number(raw) and reject any result that is not an integer, while preserving the existing minimum and maximum bounds and AppError details. Replace the Number.parseInt-based check so values such as decimals and trailing characters are invalid.server/modules/database/repositories/sessions.db.ts (1)
335-336: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueConsider removing
julianday()for better performance.If
updated_atandcreated_atare stored as standard ISO 8601 strings (which sort correctly lexicographically), wrapping them injulianday()is unnecessary. It forces SQLite to evaluate the function for every row during the sort phase and prevents the query engine from utilizing an index on those columns.Removing it allows for potential full-index scans if an index is added in the future.
♻️ Proposed refactor
- ORDER BY julianday(COALESCE(sessions.updated_at, sessions.created_at)) DESC, + ORDER BY COALESCE(sessions.updated_at, sessions.created_at) DESC, sessions.session_id DESC🤖 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 `@server/modules/database/repositories/sessions.db.ts` around lines 335 - 336, Update the session query’s ORDER BY clause to sort COALESCE(sessions.updated_at, sessions.created_at) directly, removing the julianday() wrapper while preserving descending timestamp order and the sessions.session_id DESC tie-breaker.server/modules/providers/services/sessions.service.ts (1)
119-129: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winLeverage the existing DB join to avoid N+1 lookups.
The
sessionsDb.getRecentSessionsPagequery already performs aLEFT JOINon theprojectstable for visibility filtering. Instead of queryingprojectsDb.getProjectPathfor each unique project in a loop, you can addprojects.project_idandprojects.custom_project_nameto theSELECTclause in the DB query.This would eliminate the N+1 lookups and simplify the mapping logic here, completely removing the need for
projectCache.🤖 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 `@server/modules/providers/services/sessions.service.ts` around lines 119 - 129, Update sessionsDb.getRecentSessionsPage to select projects.project_id and projects.custom_project_name from its existing projects LEFT JOIN, then use those joined fields directly when mapping page.sessions in the sessions service. Remove the projectsDb.getProjectPath calls, projectCache, and related per-project lookup logic while preserving the current null behavior for sessions without a project.
🤖 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 `@server/modules/database/repositories/sessions.db.ts`:
- Around line 335-336: Update the session query’s ORDER BY clause to sort
COALESCE(sessions.updated_at, sessions.created_at) directly, removing the
julianday() wrapper while preserving descending timestamp order and the
sessions.session_id DESC tie-breaker.
In `@server/modules/providers/provider.routes.ts`:
- Around line 367-373: Update the query-parameter validation around the parsed
value to convert raw input with Number(raw) and reject any result that is not an
integer, while preserving the existing minimum and maximum bounds and AppError
details. Replace the Number.parseInt-based check so values such as decimals and
trailing characters are invalid.
In `@server/modules/providers/services/sessions.service.ts`:
- Around line 119-129: Update sessionsDb.getRecentSessionsPage to select
projects.project_id and projects.custom_project_name from its existing projects
LEFT JOIN, then use those joined fields directly when mapping page.sessions in
the sessions service. Remove the projectsDb.getProjectPath calls, projectCache,
and related per-project lookup logic while preserving the current null behavior
for sessions without a project.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 23820a98-d0d5-462a-8348-df94a6076b1d
📒 Files selected for processing (13)
server/modules/database/repositories/sessions.db.tsserver/modules/database/tests/sessions.db.integration.test.tsserver/modules/providers/provider.routes.tsserver/modules/providers/services/sessions.service.tssrc/components/sidebar/hooks/useSidebarController.tssrc/components/sidebar/types/types.tssrc/components/sidebar/utils/utils.tssrc/components/sidebar/view/Sidebar.tsxsrc/components/sidebar/view/subcomponents/SidebarContent.tsxsrc/components/sidebar/view/subcomponents/SidebarRecentConversations.tsxsrc/components/sidebar/view/subcomponents/SidebarSessionItem.tsxsrc/i18n/locales/en/sidebar.jsonsrc/utils/api.js
Related to #904
What changed
The Conversations tab now opens a flat, globally ordered feed instead of repeating the project hierarchy. Project and provider information remain visible as row context, while typing two or more characters still switches to the existing full-message search.
Why
The previous tab label suggested a distinct conversation-oriented view, but selecting it still presented the same project-first structure as the Projects tab. That made the control difficult to understand and forced people to remember which project contained a recent conversation before they could resume it.
Building the feed by flattening the sessions already loaded for each project would produce incomplete or incorrectly ordered results because project histories are paginated independently. This change therefore orders and paginates visible conversations at the database boundary, where the complete history is available. Archived sessions and sessions owned by archived projects stay out of the active feed.
The project hierarchy remains unchanged in Projects. In Conversations, the project name becomes secondary metadata rather than another navigation layer, preserving orientation without obscuring the primary task: returning to recent work.
Before and after
Test plan
npm run buildnpm run typechecknpm run lint— completed with 0 errors; existing repository warnings remainnpx --no-install tsx --tsconfig server/tsconfig.json --test server/modules/database/tests/sessions.db.integration.test.ts— 4 tests passed