From 8c88c217175ed58bcda8a1365b7260516564dcc4 Mon Sep 17 00:00:00 2001
From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com>
Date: Tue, 3 Mar 2026 12:44:41 +0000
Subject: [PATCH 1/3] docs: add plan.md
---
plan.md | 144 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 144 insertions(+)
create mode 100644 plan.md
diff --git a/plan.md b/plan.md
new file mode 100644
index 000000000..11cd6003c
--- /dev/null
+++ b/plan.md
@@ -0,0 +1,144 @@
+# Fix: Eliminate redundant `unifiedSessions.list` query invocations
+
+## Problem
+
+`useSidebarSessions` — which calls `useQuery` for `unifiedSessions.list` — is called inside
+`CloudChatContainer`. That component re-renders ~6 times during its session-loading lifecycle due
+to cascading `useState` / `useAtomValue` updates, and React Strict Mode doubles that to ~12.
+
+Each re-render within a single React tick adds another entry to tRPC's batching layer, which
+collects all `useQuery` calls in a tick and sends them as one HTTP request. React Query
+deduplicates *concurrent* requests with the same key, but here it sees a single in-flight batched
+request containing 12 sub-operations — so deduplication does not fire.
+
+The fix is to **lift `useSidebarSessions` one level up into `CloudChatPage`**, which is a
+stateless pass-through component that never re-renders on its own. The `sessions` list and
+`refetchSessions` callback are then passed down as props.
+
+The fix applies identically to both `cloud-agent` (v1) and `cloud-agent-next` (v2).
+
+---
+
+## Files to change
+
+### 1. `src/components/cloud-agent/CloudChatPage.tsx`
+
+**Before** — thin wrapper, no logic:
+```tsx
+export default function CloudChatPage(props: CloudChatPageProps) {
+ return ;
+}
+```
+
+**After** — call `useSidebarSessions` here, pass results as props:
+```tsx
+import { useSidebarSessions } from './hooks/useSidebarSessions';
+
+export default function CloudChatPage({ organizationId }: CloudChatPageProps) {
+ const { sessions, refetchSessions } = useSidebarSessions({
+ organizationId: organizationId ?? null,
+ });
+ return (
+
+ );
+}
+```
+
+### 2. `src/components/cloud-agent-next/CloudChatPage.tsx`
+
+Identical change to the above (different import paths, otherwise the same).
+
+---
+
+### 3. `src/components/cloud-agent/CloudChatContainer.tsx`
+
+**A. Extend `CloudChatContainerProps`:**
+```ts
+// Before
+type CloudChatContainerProps = {
+ organizationId?: string;
+};
+
+// After
+type CloudChatContainerProps = {
+ organizationId?: string;
+ sessions: StoredSession[];
+ refetchSessions: () => void;
+};
+```
+
+**B. Update function signature** to destructure the new props:
+```ts
+// Before
+export function CloudChatContainer({ organizationId }: CloudChatContainerProps) {
+
+// After
+export function CloudChatContainer({ organizationId, sessions, refetchSessions }: CloudChatContainerProps) {
+```
+
+**C. Remove `useSidebarSessions` call** (lines 237–241) and its import (line 43):
+
+Lines to delete:
+```ts
+// import
+import { useSidebarSessions } from './hooks/useSidebarSessions';
+
+// usage
+// Sidebar sessions (scoped to organization when in org context, personal-only when undefined)
+// Pass null for personal chat to filter out org sessions, or the org ID for org chat
+const { sessions, refetchSessions } = useSidebarSessions({
+ organizationId: organizationId ?? null,
+});
+```
+
+Everything else (`handleStreamComplete` using `refetchSessions`, `useSessionDeletion` receiving
+`refetchSessions`, `sessions` prop on `CloudChatPresentation`) stays exactly as-is — they simply
+consume the prop value instead of the locally-derived value.
+
+**D. Add `StoredSession` to imports** (it must be imported at the type level for the prop type):
+```ts
+import type { AgentMode, SessionStartConfig, StoredSession } from './types';
+```
+(Currently `StoredSession` is only imported inside `useSidebarSessions`, not in the container.)
+
+---
+
+### 4. `src/components/cloud-agent-next/CloudChatContainer.tsx`
+
+Identical changes A–D above (different import paths, otherwise the same).
+
+---
+
+## What does NOT change
+
+- `useSidebarSessions` itself — no changes.
+- `CloudChatPageWrapper` / `CloudChatPageWrapperNext` — no changes; they already pass
+ `organizationId` down to `CloudChatPage`, which now passes it to both `useSidebarSessions` and
+ `CloudChatContainer`.
+- `useSessionDeletion`, `CloudChatPresentation`, all other hooks — no changes; they continue
+ receiving `refetchSessions` / `sessions` exactly as before.
+
+---
+
+## Why `CloudChatPage` is the right lift-point
+
+- Zero state, zero effects, zero atoms — it re-renders only when `organizationId` changes
+ (i.e. on navigation), not during the session-loading lifecycle.
+- Already owns `organizationId`, so `useSidebarSessions({ organizationId: organizationId ?? null })`
+ requires no new prop threading.
+- Sits inside the `` boundary in `CloudChatPageWrapper`, which is correct — the sessions
+ query should suspend/load after the Suspense boundary hydrates.
+- No new files, no new abstractions, minimal diff.
+
+---
+
+## Expected outcome
+
+| Scenario | Before | After |
+|---|---|---|
+| Dev (StrictMode on) | ~12 sub-operations in one batch | ~2 (one mount + StrictMode double) |
+| Production | ~6 sub-operations in one batch | ~1 |
From c25a799ec1cd65ff1e5053d895d24fdc3bc72aa8 Mon Sep 17 00:00:00 2001
From: "kiloconnect[bot]" <240665456+kiloconnect[bot]@users.noreply.github.com>
Date: Tue, 3 Mar 2026 12:48:13 +0000
Subject: [PATCH 2/3] refactor(cloud-chat): lift sidebar sessions loading to
page and pass as props
Move logic:
- move useSidebarSessions call from CloudChatContainer to CloudChatPage for both packages
- CloudChatContainer now accepts sessions: StoredSession[] and refetchSessions: () => void; remove internal fetch
- CloudChatPage now uses useSidebarSessions and passes sessions and refetchSessions to container
- update type imports to include StoredSession
BREAKING CHANGE: CloudChatContainer API changed to require sessions and refetchSessions props; CloudChatPage wires loading to avoid re-renders
---
.../cloud-agent-next/CloudChatContainer.tsx | 13 ++++-------
.../cloud-agent-next/CloudChatPage.tsx | 23 ++++++++++++-------
.../cloud-agent/CloudChatContainer.tsx | 13 ++++-------
src/components/cloud-agent/CloudChatPage.tsx | 23 ++++++++++++-------
4 files changed, 38 insertions(+), 34 deletions(-)
diff --git a/src/components/cloud-agent-next/CloudChatContainer.tsx b/src/components/cloud-agent-next/CloudChatContainer.tsx
index 743da2028..da3fc77dc 100644
--- a/src/components/cloud-agent-next/CloudChatContainer.tsx
+++ b/src/components/cloud-agent-next/CloudChatContainer.tsx
@@ -47,7 +47,6 @@ import { useCloudAgentStream } from './useCloudAgentStream';
import { useAutoScroll } from './hooks/useAutoScroll';
import { useCelebrationSound } from '@/hooks/useCelebrationSound';
import { useNotificationSound } from '@/hooks/useNotificationSound';
-import { useSidebarSessions } from './hooks/useSidebarSessions';
import { useOrganizationModels } from './hooks/useOrganizationModels';
import { useSessionDeletion } from './hooks/useSessionDeletion';
import { useResumeConfigModal } from './hooks/useResumeConfigModal';
@@ -59,7 +58,7 @@ import { useSlashCommandSets } from '@/hooks/useSlashCommandSets';
import { CloudChatPresentation } from './CloudChatPresentation';
import { QuestionContextProvider } from './QuestionContext';
import type { ResumeConfig } from './ResumeConfigModal';
-import type { AgentMode, SessionStartConfig } from './types';
+import type { AgentMode, SessionStartConfig, StoredSession } from './types';
/** Normalize legacy mode strings ('build' → 'code', 'architect' → 'plan') from DB/DO */
function normalizeMode(mode: string): AgentMode {
@@ -70,9 +69,11 @@ function normalizeMode(mode: string): AgentMode {
type CloudChatContainerProps = {
organizationId?: string;
+ sessions: StoredSession[];
+ refetchSessions: () => void;
};
-export function CloudChatContainer({ organizationId }: CloudChatContainerProps) {
+export function CloudChatContainer({ organizationId, sessions, refetchSessions }: CloudChatContainerProps) {
const router = useRouter();
const searchParams = useSearchParams();
const trpc = useTRPC();
@@ -267,12 +268,6 @@ export function CloudChatContainer({ organizationId }: CloudChatContainerProps)
setIsSessionInitiated(true);
}, []);
- // Sidebar sessions (scoped to organization when in org context, personal-only when undefined)
- // Pass null for personal chat to filter out org sessions, or the org ID for org chat
- const { sessions, refetchSessions } = useSidebarSessions({
- organizationId: organizationId ?? null,
- });
-
// Callback for stream completion
const handleStreamComplete = useCallback(() => {
playCelebrationSound();
diff --git a/src/components/cloud-agent-next/CloudChatPage.tsx b/src/components/cloud-agent-next/CloudChatPage.tsx
index c73281291..300e1646f 100644
--- a/src/components/cloud-agent-next/CloudChatPage.tsx
+++ b/src/components/cloud-agent-next/CloudChatPage.tsx
@@ -1,24 +1,31 @@
/**
* Cloud Chat Page
*
- * Simple wrapper that exports the CloudChatContainer component.
- * All business logic, hooks, and state management are in CloudChatContainer.
- * All rendering logic is in CloudChatPresentation.
+ * Owns the sidebar session query so it runs in a stable component that does not
+ * re-render during the session-loading lifecycle, eliminating redundant
+ * unifiedSessions.list invocations that would otherwise be batched by tRPC.
*/
'use client';
import { CloudChatContainer } from './CloudChatContainer';
+import { useSidebarSessions } from './hooks/useSidebarSessions';
type CloudChatPageProps = {
organizationId?: string;
};
-/**
- * Main export - renders the cloud chat container
- */
-export default function CloudChatPage(props: CloudChatPageProps) {
- return ;
+export default function CloudChatPage({ organizationId }: CloudChatPageProps) {
+ const { sessions, refetchSessions } = useSidebarSessions({
+ organizationId: organizationId ?? null,
+ });
+ return (
+
+ );
}
// Named export for compatibility
diff --git a/src/components/cloud-agent/CloudChatContainer.tsx b/src/components/cloud-agent/CloudChatContainer.tsx
index 47c2a862f..068ee1ab7 100644
--- a/src/components/cloud-agent/CloudChatContainer.tsx
+++ b/src/components/cloud-agent/CloudChatContainer.tsx
@@ -40,7 +40,6 @@ import {
import { useCloudAgentStreamV2 } from './useCloudAgentStreamV2';
import { useAutoScroll } from './hooks/useAutoScroll';
import { useCelebrationSound } from '@/hooks/useCelebrationSound';
-import { useSidebarSessions } from './hooks/useSidebarSessions';
import { useOrganizationModels } from './hooks/useOrganizationModels';
import { useSessionDeletion } from './hooks/useSessionDeletion';
import { useResumeConfigModal } from './hooks/useResumeConfigModal';
@@ -51,10 +50,12 @@ import { buildPrepareSessionRepoParams } from './utils/git-utils';
import { useSlashCommandSets } from '@/hooks/useSlashCommandSets';
import { CloudChatPresentation } from './CloudChatPresentation';
import type { ResumeConfig } from './ResumeConfigModal';
-import type { AgentMode, SessionStartConfig } from './types';
+import type { AgentMode, SessionStartConfig, StoredSession } from './types';
type CloudChatContainerProps = {
organizationId?: string;
+ sessions: StoredSession[];
+ refetchSessions: () => void;
};
/**
@@ -85,7 +86,7 @@ type ResumeConfigState =
| { status: 'persisted'; config: ResumeConfig }
| { status: 'failed'; config: ResumeConfig; error: Error };
-export function CloudChatContainer({ organizationId }: CloudChatContainerProps) {
+export function CloudChatContainer({ organizationId, sessions, refetchSessions }: CloudChatContainerProps) {
const router = useRouter();
const searchParams = useSearchParams();
const trpc = useTRPC();
@@ -234,12 +235,6 @@ export function CloudChatContainer({ organizationId }: CloudChatContainerProps)
setIsSessionInitiated(true);
}, []);
- // Sidebar sessions (scoped to organization when in org context, personal-only when undefined)
- // Pass null for personal chat to filter out org sessions, or the org ID for org chat
- const { sessions, refetchSessions } = useSidebarSessions({
- organizationId: organizationId ?? null,
- });
-
// Callback for stream completion
const handleStreamComplete = useCallback(() => {
playCelebrationSound();
diff --git a/src/components/cloud-agent/CloudChatPage.tsx b/src/components/cloud-agent/CloudChatPage.tsx
index c73281291..300e1646f 100644
--- a/src/components/cloud-agent/CloudChatPage.tsx
+++ b/src/components/cloud-agent/CloudChatPage.tsx
@@ -1,24 +1,31 @@
/**
* Cloud Chat Page
*
- * Simple wrapper that exports the CloudChatContainer component.
- * All business logic, hooks, and state management are in CloudChatContainer.
- * All rendering logic is in CloudChatPresentation.
+ * Owns the sidebar session query so it runs in a stable component that does not
+ * re-render during the session-loading lifecycle, eliminating redundant
+ * unifiedSessions.list invocations that would otherwise be batched by tRPC.
*/
'use client';
import { CloudChatContainer } from './CloudChatContainer';
+import { useSidebarSessions } from './hooks/useSidebarSessions';
type CloudChatPageProps = {
organizationId?: string;
};
-/**
- * Main export - renders the cloud chat container
- */
-export default function CloudChatPage(props: CloudChatPageProps) {
- return ;
+export default function CloudChatPage({ organizationId }: CloudChatPageProps) {
+ const { sessions, refetchSessions } = useSidebarSessions({
+ organizationId: organizationId ?? null,
+ });
+ return (
+
+ );
}
// Named export for compatibility
From 037d6a6f51a643a0e0d114d6c5796db0cdd9cdac Mon Sep 17 00:00:00 2001
From: Evgeny Shurakov
Date: Tue, 3 Mar 2026 16:13:10 +0100
Subject: [PATCH 3/3] style: apply prettier formatting and remove plan.md
---
plan.md | 144 ------------------
.../cloud-agent-next/CloudChatContainer.tsx | 6 +-
.../cloud-agent-next/CloudChatPage.tsx | 6 +-
.../cloud-agent/CloudChatContainer.tsx | 6 +-
src/components/cloud-agent/CloudChatPage.tsx | 6 +-
5 files changed, 16 insertions(+), 152 deletions(-)
delete mode 100644 plan.md
diff --git a/plan.md b/plan.md
deleted file mode 100644
index 11cd6003c..000000000
--- a/plan.md
+++ /dev/null
@@ -1,144 +0,0 @@
-# Fix: Eliminate redundant `unifiedSessions.list` query invocations
-
-## Problem
-
-`useSidebarSessions` — which calls `useQuery` for `unifiedSessions.list` — is called inside
-`CloudChatContainer`. That component re-renders ~6 times during its session-loading lifecycle due
-to cascading `useState` / `useAtomValue` updates, and React Strict Mode doubles that to ~12.
-
-Each re-render within a single React tick adds another entry to tRPC's batching layer, which
-collects all `useQuery` calls in a tick and sends them as one HTTP request. React Query
-deduplicates *concurrent* requests with the same key, but here it sees a single in-flight batched
-request containing 12 sub-operations — so deduplication does not fire.
-
-The fix is to **lift `useSidebarSessions` one level up into `CloudChatPage`**, which is a
-stateless pass-through component that never re-renders on its own. The `sessions` list and
-`refetchSessions` callback are then passed down as props.
-
-The fix applies identically to both `cloud-agent` (v1) and `cloud-agent-next` (v2).
-
----
-
-## Files to change
-
-### 1. `src/components/cloud-agent/CloudChatPage.tsx`
-
-**Before** — thin wrapper, no logic:
-```tsx
-export default function CloudChatPage(props: CloudChatPageProps) {
- return ;
-}
-```
-
-**After** — call `useSidebarSessions` here, pass results as props:
-```tsx
-import { useSidebarSessions } from './hooks/useSidebarSessions';
-
-export default function CloudChatPage({ organizationId }: CloudChatPageProps) {
- const { sessions, refetchSessions } = useSidebarSessions({
- organizationId: organizationId ?? null,
- });
- return (
-
- );
-}
-```
-
-### 2. `src/components/cloud-agent-next/CloudChatPage.tsx`
-
-Identical change to the above (different import paths, otherwise the same).
-
----
-
-### 3. `src/components/cloud-agent/CloudChatContainer.tsx`
-
-**A. Extend `CloudChatContainerProps`:**
-```ts
-// Before
-type CloudChatContainerProps = {
- organizationId?: string;
-};
-
-// After
-type CloudChatContainerProps = {
- organizationId?: string;
- sessions: StoredSession[];
- refetchSessions: () => void;
-};
-```
-
-**B. Update function signature** to destructure the new props:
-```ts
-// Before
-export function CloudChatContainer({ organizationId }: CloudChatContainerProps) {
-
-// After
-export function CloudChatContainer({ organizationId, sessions, refetchSessions }: CloudChatContainerProps) {
-```
-
-**C. Remove `useSidebarSessions` call** (lines 237–241) and its import (line 43):
-
-Lines to delete:
-```ts
-// import
-import { useSidebarSessions } from './hooks/useSidebarSessions';
-
-// usage
-// Sidebar sessions (scoped to organization when in org context, personal-only when undefined)
-// Pass null for personal chat to filter out org sessions, or the org ID for org chat
-const { sessions, refetchSessions } = useSidebarSessions({
- organizationId: organizationId ?? null,
-});
-```
-
-Everything else (`handleStreamComplete` using `refetchSessions`, `useSessionDeletion` receiving
-`refetchSessions`, `sessions` prop on `CloudChatPresentation`) stays exactly as-is — they simply
-consume the prop value instead of the locally-derived value.
-
-**D. Add `StoredSession` to imports** (it must be imported at the type level for the prop type):
-```ts
-import type { AgentMode, SessionStartConfig, StoredSession } from './types';
-```
-(Currently `StoredSession` is only imported inside `useSidebarSessions`, not in the container.)
-
----
-
-### 4. `src/components/cloud-agent-next/CloudChatContainer.tsx`
-
-Identical changes A–D above (different import paths, otherwise the same).
-
----
-
-## What does NOT change
-
-- `useSidebarSessions` itself — no changes.
-- `CloudChatPageWrapper` / `CloudChatPageWrapperNext` — no changes; they already pass
- `organizationId` down to `CloudChatPage`, which now passes it to both `useSidebarSessions` and
- `CloudChatContainer`.
-- `useSessionDeletion`, `CloudChatPresentation`, all other hooks — no changes; they continue
- receiving `refetchSessions` / `sessions` exactly as before.
-
----
-
-## Why `CloudChatPage` is the right lift-point
-
-- Zero state, zero effects, zero atoms — it re-renders only when `organizationId` changes
- (i.e. on navigation), not during the session-loading lifecycle.
-- Already owns `organizationId`, so `useSidebarSessions({ organizationId: organizationId ?? null })`
- requires no new prop threading.
-- Sits inside the `` boundary in `CloudChatPageWrapper`, which is correct — the sessions
- query should suspend/load after the Suspense boundary hydrates.
-- No new files, no new abstractions, minimal diff.
-
----
-
-## Expected outcome
-
-| Scenario | Before | After |
-|---|---|---|
-| Dev (StrictMode on) | ~12 sub-operations in one batch | ~2 (one mount + StrictMode double) |
-| Production | ~6 sub-operations in one batch | ~1 |
diff --git a/src/components/cloud-agent-next/CloudChatContainer.tsx b/src/components/cloud-agent-next/CloudChatContainer.tsx
index da3fc77dc..914e0030d 100644
--- a/src/components/cloud-agent-next/CloudChatContainer.tsx
+++ b/src/components/cloud-agent-next/CloudChatContainer.tsx
@@ -73,7 +73,11 @@ type CloudChatContainerProps = {
refetchSessions: () => void;
};
-export function CloudChatContainer({ organizationId, sessions, refetchSessions }: CloudChatContainerProps) {
+export function CloudChatContainer({
+ organizationId,
+ sessions,
+ refetchSessions,
+}: CloudChatContainerProps) {
const router = useRouter();
const searchParams = useSearchParams();
const trpc = useTRPC();
diff --git a/src/components/cloud-agent-next/CloudChatPage.tsx b/src/components/cloud-agent-next/CloudChatPage.tsx
index 300e1646f..46f465e58 100644
--- a/src/components/cloud-agent-next/CloudChatPage.tsx
+++ b/src/components/cloud-agent-next/CloudChatPage.tsx
@@ -1,9 +1,9 @@
/**
* Cloud Chat Page
*
- * Owns the sidebar session query so it runs in a stable component that does not
- * re-render during the session-loading lifecycle, eliminating redundant
- * unifiedSessions.list invocations that would otherwise be batched by tRPC.
+ * Owns the sidebar session query so it runs outside CloudChatContainer,
+ * whose frequent internal state changes would otherwise cause redundant
+ * unifiedSessions.list invocations batched by tRPC.
*/
'use client';
diff --git a/src/components/cloud-agent/CloudChatContainer.tsx b/src/components/cloud-agent/CloudChatContainer.tsx
index 068ee1ab7..6ffd5987a 100644
--- a/src/components/cloud-agent/CloudChatContainer.tsx
+++ b/src/components/cloud-agent/CloudChatContainer.tsx
@@ -86,7 +86,11 @@ type ResumeConfigState =
| { status: 'persisted'; config: ResumeConfig }
| { status: 'failed'; config: ResumeConfig; error: Error };
-export function CloudChatContainer({ organizationId, sessions, refetchSessions }: CloudChatContainerProps) {
+export function CloudChatContainer({
+ organizationId,
+ sessions,
+ refetchSessions,
+}: CloudChatContainerProps) {
const router = useRouter();
const searchParams = useSearchParams();
const trpc = useTRPC();
diff --git a/src/components/cloud-agent/CloudChatPage.tsx b/src/components/cloud-agent/CloudChatPage.tsx
index 300e1646f..46f465e58 100644
--- a/src/components/cloud-agent/CloudChatPage.tsx
+++ b/src/components/cloud-agent/CloudChatPage.tsx
@@ -1,9 +1,9 @@
/**
* Cloud Chat Page
*
- * Owns the sidebar session query so it runs in a stable component that does not
- * re-render during the session-loading lifecycle, eliminating redundant
- * unifiedSessions.list invocations that would otherwise be batched by tRPC.
+ * Owns the sidebar session query so it runs outside CloudChatContainer,
+ * whose frequent internal state changes would otherwise cause redundant
+ * unifiedSessions.list invocations batched by tRPC.
*/
'use client';