Chat sidebar: lazy Archived + System groups, unbounded conversation feed - #269
Chat sidebar: lazy Archived + System groups, unbounded conversation feed#269arsenmuk wants to merge 1 commit into
Conversation
The sidebar feed used one LIMIT 50 window over every session row, cron included, so ~96 cron runs a day pushed conversations out of the pane within hours. Rework it into three server-scoped lists with one shared page-size knob: - sessions.sidebar_page_size (default 50, 0 = unlimited) caps the feed and sizes one Archived/System page. - The window applies only to non-archived, non-system rows, so cron traffic can never displace conversations. - Starred sessions are off-budget: always returned in full, whatever the page size, and pinned even when their source is cron/hook. - Archived and System load lazily (nothing fetched until expanded) and page via ?offset=, with has_more/next_offset driving a '...' row. Collapsing a group drops its rows, so reopening repeats the same cold request. The feed gets the same '...' rather than truncating silently. - Sources split by exclusion (system = cron/hook): the client no longer whitelists, so workflow/external sessions stop rendering nowhere. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
8e8d187 to
72c7263
Compare
|
Let's ensure the docs are updated in sync with the changes:
|
| return {"archived": True} | ||
|
|
||
|
|
||
| @router.post("/api/sessions/{session_id}/unarchive") |
There was a problem hiding this comment.
This will return 500 when the session is not found, let's catch the ValueError and return 404.
| if not session: | ||
| raise ValueError(f"Session {session_id} not found") | ||
| await self.db.update_session_fields(session_id, { | ||
| "status": SessionStatus.IDLE.value, |
There was a problem hiding this comment.
Maybe we should also update updated_at here, so that unarchiving a session pushes it to the top?
|
ChatPage.tsx and ChatInput.tsx only consult So opening an archived session results in various things being broken. Need to also consult archivedSessions/systemSessions. |
| # is the persisted SessionStatus.ARCHIVED value. | ||
| if fields["starred"] == 1 and session.get("status") == "archived": | ||
| fields["status"] = "idle" | ||
| fields["archived_at"] = None |
There was a problem hiding this comment.
Let's go through the unarchive_session() path here to avoid any inconsistencies. There's already one: this path skips log_session_event(session_id, "unarchived", {}).
| """Archived sessions page, most recently archived first — lazily | ||
| fetched when the sidebar Archived group is expanded.""" | ||
| return await self._page( | ||
| "SELECT * FROM sessions WHERE status = 'archived'" |
There was a problem hiding this comment.
Should we maybe split off by SYSTEM_SOURCES here? Otherwise we're mixing user sessions and cron/hook sessions together here.
| @@ -505,8 +550,24 @@ export const useChatStore = create<ChatState>((set, get) => ({ | |||
|
|
|||
| loadSessions: async () => { | |||
There was a problem hiding this comment.
This is called in a whole bunch of places, and will replace the session list with page 1 only. Let's make it preserve the depth
Problem
The sidebar silently dropped active chats:
GET /api/sessionscapped non-starred sessions at 50, and cron sessions (bumped every heartbeat tick) crowded conversations out of those slots. Archived sessions had no UI at all — and no unarchive path existed anywhere in the backend.Changes
Backend
GET /api/sessionsnow serveslist_active_sessions(): all non-archived conversation sessions, unbounded;cron/hooksources excluded from the feed.archived_count+system_countride on the payload (twoCOUNT(*)s), so collapsed groups show a badge without fetching rows.GET /api/sessions/archivedandGET /api/sessions/system— fetched lazily, only when the corresponding sidebar group is expanded.POST /api/sessions/{id}/unarchive(restores toidle, clearsarchived_at, logs anunarchivedevent) — inverse of archive.PATCH /api/sessions/{id}withstarred: trueon an archived session unarchives + stars in one write, so the star→project hook fires on a live session.list_sessions— untouched.Frontend
Testing
pytest tests/test_sessions.py— 70/70 (10 new: unarchive lifecycle/events/missing, archived list/count, star-unarchive write, active-feed exclusions, unbounded feed regression, system list/count).npm run build(tsc + vite) green./archived//systemrequest until expand; unarchive returns a session to its date group; starring an archived session lands it in Starred.🤖 Generated with Claude Code