close
Skip to content

feat: add an opt-in all-sessions time sort mode#659

Open
apple-ouyang wants to merge 2 commits intositeboon:mainfrom
apple-ouyang:feat/session-sort-toggle
Open

feat: add an opt-in all-sessions time sort mode#659
apple-ouyang wants to merge 2 commits intositeboon:mainfrom
apple-ouyang:feat/session-sort-toggle

Conversation

@apple-ouyang
Copy link
Copy Markdown

@apple-ouyang apple-ouyang commented Apr 15, 2026

Summary by CodeRabbit

  • New Features

    • New quick-setting: "Sort all sessions by time" — view a combined, time-sorted timeline of all sessions across providers.
    • Multilingual label added (EN/DE/JA/KO/RU/ZH‑CN).
  • Improvements

    • Sidebar, preferences, and session lists now honor the new timeline view and preserve provider attribution.
    • Server/session indexing warmed and kept up-to-date to improve timeline freshness and deletion coverage.

原因:
- 项目侧边栏默认只取每个 provider 的少量会话,无法按真实最近时间展示完整的 Codex 历史
- 需要把这个行为做成可选配置,避免直接改默认值带来性能回退

改动:
- 新增 showAllSessionsSortedByTime UI 偏好,并在快速设置里暴露开关与多语言文案
- /api/projects 支持 includeAllSessions 查询参数,按开关返回 Claude、Cursor、Codex、Gemini 的完整会话列表
- projects_updated 改为按当前本地偏好重新拉取项目数据,避免后台更新把 UI 退回到截断结果
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 15, 2026

📝 Walkthrough

Walkthrough

Adds a UI preference and request flags to return all sessions sorted by time. The flag is threaded from quick-settings through hooks and API calls to server routes, which now support timeline-style session aggregation, Codex index warming/dirtying, and timeline-based pagination.

Changes

Cohort / File(s) Summary
Server: routes & codex index
server/index.js, server/projects.js
API handlers now accept sortAllSessionsByTime/includeAllSessions flags; /api/projects/:projectName/sessions can return a timeline via getProjectSessionTimeline; Codex session index warm/dirty controls added and warmed on startup; getProjects accepts options to produce timelineSessions.
Server: session providers & helpers
server/projects.js
Added timeline merging across providers, getGeminiProjectSessions, expanded getCursorSessions/getCodexSessions behaviors (includeMeta/includeSubagents), pagination semantics adjusted, and exports markCodexSessionsIndexDirty / warmCodexSessionsIndex / getProjectSessionTimeline.
Frontend: preference toggle & i18n
src/components/quick-settings-panel/constants.ts, src/components/quick-settings-panel/types.ts, src/i18n/locales/*/settings.json
New quick-settings toggle showAllSessionsSortedByTime added and localized across en/de/ja/ko/ru/zh-CN; type union and constants updated.
Frontend: preferences state
src/hooks/useUiPreferences.ts, src/components/quick-settings-panel/view/QuickSettingsPanelView.tsx
Added showAllSessionsSortedByTime to UiPreferences and defaults; QuickSettingsPanelView includes the new preference in derived props.
Frontend: project/session state & API wiring
src/hooks/useProjectsState.ts, src/components/sidebar/hooks/useSidebarController.ts, src/utils/api.js
Preference threaded into api.projects() and api.sessions() calls; project-sync and socket handling updated to fetch with sortAllSessionsByTime; session aggregation/lookup logic updated to handle timelineSessions; api signatures extended to accept options.
Frontend: session types & utilities
src/components/sidebar/types/types.ts, src/components/sidebar/utils/utils.ts, src/types/app.ts
Session types updated to include __provider (SessionWithProvider), AdditionalSessionsByProject adjusted, getAllSessions/sorting utilities accept useTimeline to build/normalize timeline sessions, and Project gains optional timelineSessions.

Sequence Diagram

sequenceDiagram
    participant User
    participant UI as QuickSettingsPanel
    participant Pref as useUiPreferences
    participant Hook as useProjectsState / SidebarController
    participant API as src/utils/api.js
    participant Server as server/index.js
    participant Projects as server/projects.js

    User->>UI: Toggle "showAllSessionsSortedByTime"
    UI->>Pref: set preference
    Pref-->>Hook: preference changed (effect)
    Hook->>API: api.projects({ sortAllSessionsByTime: true })
    API->>Server: GET /api/projects?sortAllSessionsByTime=true
    Server->>Projects: getProjects(progressCallback, { sortAllSessionsByTime: true })
    Projects->>Projects: aggregate timelineSessions across providers (Claude/Cursor/Codex/Gemini), sort, paginate
    Projects-->>Server: projects payload with timelineSessions
    Server-->>API: HTTP 200 with projects
    API-->>Hook: resolved projects
    Hook->>UI: update state -> render timeline sessions
Loading

Possibly related PRs

Suggested reviewers

  • viper151
  • blackmammoth

Poem

🐇 I hopped through code with a joyful chime,
Now sessions all sparkle, ordered by time.
From toggle to server the pathways align,
A rabbit's small dance for a timeline so fine! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.48% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add an opt-in all-sessions time sort mode' accurately describes the main change: introducing a new optional feature to sort all sessions chronologically.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch feat/session-sort-toggle

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

Caution

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

⚠️ Outside diff range comments (1)
server/projects.js (1)

489-493: ⚠️ Potential issue | 🟠 Major

Sort merged Gemini sessions before applying sessionLimit.

This concatenates in-memory Gemini sessions ahead of CLI sessions and then truncates, so the returned list is not actually the newest N sessions by time. That breaks the new time-sorted mode and can hide newer CLI sessions behind older UI sessions. Apply the same fix in both blocks.

Proposed fix
-        const mergedGemini = [...uiSessions, ...cliSessions.filter(s => !uiIds.has(s.id))];
-        project.geminiSessions = sessionLimit > 0 ? mergedGemini.slice(0, sessionLimit) : mergedGemini;
+        const mergedGemini = [...uiSessions, ...cliSessions.filter(s => !uiIds.has(s.id))]
+          .sort((a, b) => new Date(b.lastActivity || 0) - new Date(a.lastActivity || 0));
+        project.geminiSessions = sessionLimit > 0 ? mergedGemini.slice(0, sessionLimit) : mergedGemini;

Also applies to: 609-613

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/projects.js` around lines 489 - 493, The mergedGemini list currently
preserves UI-first ordering then slices by sessionLimit, hiding newer CLI
sessions; after creating mergedGemini (from uiSessions and cliSessions filtered
by uiIds) sort the merged array by the session timestamp (e.g., a createdAt or
timestamp property on session objects) in descending (newest-first) order and
only then apply sessionLimit and assign to project.geminiSessions; make this
same change for the second merging occurrence (the other block that builds
mergedGemini from uiSessions and cliSessions) so both places return the newest N
sessions consistently.
🧹 Nitpick comments (1)
src/components/quick-settings-panel/constants.ts (1)

49-53: Consider using a distinct icon for this toggle.

Both autoScrollToBottom (line 47) and showAllSessionsSortedByTime use the ArrowDown icon. Since they appear in the same "View Options" section, this may cause visual confusion for users. Consider using a more semantically appropriate icon like Clock, List, or SortDesc from lucide-react to better convey "all sessions sorted by time."

♻️ Suggested change
 import {
   ArrowDown,
   Brain,
+  Clock,
   Eye,
   Languages,
   Maximize2,
 } from 'lucide-react';
   {
     key: 'showAllSessionsSortedByTime',
     labelKey: 'quickSettings.showAllSessionsSortedByTime',
-    icon: ArrowDown,
+    icon: Clock,
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/quick-settings-panel/constants.ts` around lines 49 - 53, The
two view toggles use the same ArrowDown icon causing confusion; update the
showAllSessionsSortedByTime entry in constants.ts (the object with key
'showAllSessionsSortedByTime') to use a more semantically appropriate
lucide-react icon such as Clock, List, or SortDesc instead of ArrowDown so it
visually represents "sorted by time" and differs from autoScrollToBottom.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/index.js`:
- Around line 506-507: The watcher is calling getProjects(broadcastProgress)
without the includeAllSessions option, so after FS changes clients who requested
includeAllSessions see truncated lists; modify setupProjectsWatcher to accept an
includeAllSessions boolean (or options object) and have it call
getProjects(broadcastProgress, { includeAllSessions }) when building the
projects_updated payload; update the place where setupProjectsWatcher is invoked
so it forwards the request-scoped includeAllSessions value (the same value used
when initially calling getProjects) and ensure any broadcast or projects_updated
emission uses that option consistently.

---

Outside diff comments:
In `@server/projects.js`:
- Around line 489-493: The mergedGemini list currently preserves UI-first
ordering then slices by sessionLimit, hiding newer CLI sessions; after creating
mergedGemini (from uiSessions and cliSessions filtered by uiIds) sort the merged
array by the session timestamp (e.g., a createdAt or timestamp property on
session objects) in descending (newest-first) order and only then apply
sessionLimit and assign to project.geminiSessions; make this same change for the
second merging occurrence (the other block that builds mergedGemini from
uiSessions and cliSessions) so both places return the newest N sessions
consistently.

---

Nitpick comments:
In `@src/components/quick-settings-panel/constants.ts`:
- Around line 49-53: The two view toggles use the same ArrowDown icon causing
confusion; update the showAllSessionsSortedByTime entry in constants.ts (the
object with key 'showAllSessionsSortedByTime') to use a more semantically
appropriate lucide-react icon such as Clock, List, or SortDesc instead of
ArrowDown so it visually represents "sorted by time" and differs from
autoScrollToBottom.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 54248ae6-85d0-43d6-b0c3-10df185d2f93

📥 Commits

Reviewing files that changed from the base of the PR and between 31f28a2 and 111f46e.

📒 Files selected for processing (14)
  • server/index.js
  • server/projects.js
  • src/components/quick-settings-panel/constants.ts
  • src/components/quick-settings-panel/types.ts
  • src/components/quick-settings-panel/view/QuickSettingsPanelView.tsx
  • src/hooks/useProjectsState.ts
  • src/hooks/useUiPreferences.ts
  • src/i18n/locales/de/settings.json
  • src/i18n/locales/en/settings.json
  • src/i18n/locales/ja/settings.json
  • src/i18n/locales/ko/settings.json
  • src/i18n/locales/ru/settings.json
  • src/i18n/locales/zh-CN/settings.json
  • src/utils/api.js

Comment thread server/index.js Outdated
Comment on lines +506 to +507
const includeAllSessions = req.query.includeAllSessions === 'true';
const projects = await getProjects(broadcastProgress, { includeAllSessions });
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

The toggle only affects the initial fetch, not watcher-driven refreshes.

This route now honors includeAllSessions, but setupProjectsWatcher() still publishes projects_updated payloads from getProjects(broadcastProgress) with the default cap. After any filesystem change, opted-in clients get truncated session lists again until they do a full refetch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.js` around lines 506 - 507, The watcher is calling
getProjects(broadcastProgress) without the includeAllSessions option, so after
FS changes clients who requested includeAllSessions see truncated lists; modify
setupProjectsWatcher to accept an includeAllSessions boolean (or options object)
and have it call getProjects(broadcastProgress, { includeAllSessions }) when
building the projects_updated payload; update the place where
setupProjectsWatcher is invoked so it forwards the request-scoped
includeAllSessions value (the same value used when initially calling
getProjects) and ensure any broadcast or projects_updated emission uses that
option consistently.

原因:
- 全局按时间排序的会话列表在首屏会一次性加载大量历史,导致 loading projects 很慢
- Codex 全局索引首次构建成本高,需要缓存和预热来压低接口延迟
- review comment 指出的 Gemini 排序问题和设置项图标歧义需要补齐
- 用户希望 subagent 对话不要出现在主 session 列表里,避免噪音

改动:
- 将按时间排序模式改成分页加载,每次只返回 5 条,并保留按时间线继续加载的行为
- 为 Codex session 索引增加缓存、并发共享构建、后台预热和 stale-while-revalidate 脏标记刷新
- 统一 Gemini 会话聚合逻辑,确保截断前先按时间排序,并把时间排序设置图标改为 Clock3
- 解析 Codex session_meta 的 subagent 标记,默认从列表中过滤 subagent,会话删除等内部流程可显式包含它们

影响:
- 默认开启按时间排序后,首屏不会再全量拉历史记录
- Codex subagent 会话默认不再出现在 UI 列表中,但底层数据仍保留
@apple-ouyang
Copy link
Copy Markdown
Author

Updated in 416c5ee.

Addressed the remaining review feedback:

  • sorted Gemini merged sessions before truncation in the legacy per-provider path
  • changed the time-sorted sessions toggle to use Clock3 instead of ArrowDown
  • kept the timeline-mode performance fixes and added Codex subagent filtering so spawned subagent sessions no longer appear in the main session list by default

Local verification:

  • npm run typecheck
  • npm run build
  • restarted the local service and verified /api/projects?sortAllSessionsByTime=true still works
  • verified Codex session filtering on /Users/admin/code/ads_public: 685 total Codex sessions, 116 subagents hidden, visible list contains no subagent sessions

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 the current code and only fix it if needed.

Inline comments:
In `@server/projects.js`:
- Around line 1663-1675: The branch that checks codexSessionsIndexDirty must not
return the stale codexSessionsIndexCache; instead, when codexSessionsIndexDirty
is true ensure you await the rebuild promise (codexSessionsIndexPromise) or
start buildCodexSessionsIndex() and return its result; update the logic around
codexSessionsIndexPromise, codexSessionsIndexDirty, buildCodexSessionsIndex(),
and codexSessionsIndexCache so the function returns the newly built index (or
the in-flight promise's resolved index) rather than the old cache.

In `@src/components/sidebar/hooks/useSidebarController.ts`:
- Around line 508-511: The code passes getProjectSessions(project).length to
api.sessions which counts all providers, but the backend paginates Claude-only
in legacy mode; compute a Claude-only count instead and pass that when timeline
mode is off: replace currentSessionCount with something like const
currentClaudeSessionCount = getProjectSessions(project).filter(s => /* detect
Claude by s.provider === 'claude' or s.model startsWith('claude') */).length and
use it in the api.sessions call that currently passes currentSessionCount (and
apply the same change to the other api.sessions call around the second usage).

In `@src/hooks/useProjectsState.ts`:
- Around line 156-157: The code enables the new timeline path by default because
showAllSessionsSortedByTime defaults to true in useUiPreferences; change the
default to false so the feature is opt-in: update the default value for
showAllSessionsSortedByTime in the useUiPreferences implementation (and any
other place where that preference default is set) to false, or explicitly set
const { showAllSessionsSortedByTime = false } = preferences in useProjectsState
(and the other referenced usage) to ensure first-load behavior remains unchanged
until users opt in.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3001eca0-a7b9-4d8b-b928-579a9eee52fe

📥 Commits

Reviewing files that changed from the base of the PR and between 111f46e and 416c5ee.

📒 Files selected for processing (10)
  • server/index.js
  • server/projects.js
  • src/components/quick-settings-panel/constants.ts
  • src/components/sidebar/hooks/useSidebarController.ts
  • src/components/sidebar/types/types.ts
  • src/components/sidebar/utils/utils.ts
  • src/hooks/useProjectsState.ts
  • src/hooks/useUiPreferences.ts
  • src/types/app.ts
  • src/utils/api.js
✅ Files skipped from review due to trivial changes (2)
  • src/types/app.ts
  • src/components/quick-settings-panel/constants.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/hooks/useUiPreferences.ts

Comment thread server/projects.js
Comment on lines +1663 to +1675
if (codexSessionsIndexCache && codexSessionsIndexDirty) {
if (!codexSessionsIndexPromise) {
codexSessionsIndexPromise = buildCodexSessionsIndex()
.then((index) => {
codexSessionsIndexCache = index;
codexSessionsIndexDirty = false;
return index;
})
.finally(() => {
codexSessionsIndexPromise = null;
});
}
return codexSessionsIndexCache;
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

Don’t serve the stale Codex index after invalidation.

After markCodexSessionsIndexDirty(), this branch starts a rebuild but still returns codexSessionsIndexCache. In server/index.js, the watcher dirties the index and immediately calls getProjects(), so the first refresh after a Codex file change can be built from stale data and completely miss that change.

💡 Proposed fix
   if (codexSessionsIndexCache && codexSessionsIndexDirty) {
     if (!codexSessionsIndexPromise) {
       codexSessionsIndexPromise = buildCodexSessionsIndex()
         .then((index) => {
           codexSessionsIndexCache = index;
           codexSessionsIndexDirty = false;
           return index;
         })
         .finally(() => {
           codexSessionsIndexPromise = null;
         });
     }
-    return codexSessionsIndexCache;
+    return codexSessionsIndexPromise;
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/projects.js` around lines 1663 - 1675, The branch that checks
codexSessionsIndexDirty must not return the stale codexSessionsIndexCache;
instead, when codexSessionsIndexDirty is true ensure you await the rebuild
promise (codexSessionsIndexPromise) or start buildCodexSessionsIndex() and
return its result; update the logic around codexSessionsIndexPromise,
codexSessionsIndexDirty, buildCodexSessionsIndex(), and codexSessionsIndexCache
so the function returns the newly built index (or the in-flight promise's
resolved index) rather than the old cache.

Comment on lines +508 to +511
const currentSessionCount = getProjectSessions(project).length;
const response = await api.sessions(project.name, 5, currentSessionCount, {
sortAllSessionsByTime: showAllSessionsSortedByTime,
});
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

Use a Claude-only offset when timeline mode is off.

In legacy mode, this endpoint still paginates only Claude sessions (server/index.js Lines 533-535). getProjectSessions(project).length also counts Cursor/Codex/Gemini items, so projects with external sessions will skip Claude pages when the user clicks “load more”.

💡 Proposed fix
-        const currentSessionCount = getProjectSessions(project).length;
+        const currentSessionCount = showAllSessionsSortedByTime
+          ? getProjectSessions(project).length
+          : (project.sessions?.length ?? 0) + (additionalSessions[project.name]?.length ?? 0);
         const response = await api.sessions(project.name, 5, currentSessionCount, {
           sortAllSessionsByTime: showAllSessionsSortedByTime,
         });
@@
-    [getProjectSessions, loadingSessions, projectHasMoreOverrides, showAllSessionsSortedByTime],
+    [additionalSessions, getProjectSessions, loadingSessions, projectHasMoreOverrides, showAllSessionsSortedByTime],

Also applies to: 544-544

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/components/sidebar/hooks/useSidebarController.ts` around lines 508 - 511,
The code passes getProjectSessions(project).length to api.sessions which counts
all providers, but the backend paginates Claude-only in legacy mode; compute a
Claude-only count instead and pass that when timeline mode is off: replace
currentSessionCount with something like const currentClaudeSessionCount =
getProjectSessions(project).filter(s => /* detect Claude by s.provider ===
'claude' or s.model startsWith('claude') */).length and use it in the
api.sessions call that currently passes currentSessionCount (and apply the same
change to the other api.sessions call around the second usage).

Comment on lines +156 to +157
const { preferences } = useUiPreferences();
const { showAllSessionsSortedByTime } = preferences;
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

This ships the new mode as default-on, not opt-in.

useUiPreferences() currently defaults showAllSessionsSortedByTime to true, so routing all project fetches through this flag enables the new timeline query path for users with no saved preference. That contradicts the PR contract and changes first-load behavior/perf unexpectedly. Please flip the default to false before merging.

Also applies to: 186-186

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useProjectsState.ts` around lines 156 - 157, The code enables the
new timeline path by default because showAllSessionsSortedByTime defaults to
true in useUiPreferences; change the default to false so the feature is opt-in:
update the default value for showAllSessionsSortedByTime in the useUiPreferences
implementation (and any other place where that preference default is set) to
false, or explicitly set const { showAllSessionsSortedByTime = false } =
preferences in useProjectsState (and the other referenced usage) to ensure
first-load behavior remains unchanged until users opt in.

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