From 2ffd29792dcfc21aae3bbc2114f95d5f6d7f343f Mon Sep 17 00:00:00 2001 From: Jay1 Date: Thu, 28 May 2026 13:44:48 -0400 Subject: [PATCH 1/3] fix(web): guard sidebar prune useEffect against reference-only changes The useEffect that calls setExpandedThreadListsByProject fires on every standardProjects reference change. During active LLM chat, SSE events cause sidebarThreadSummaryById to update frequently, which cascades through sortProjectsForSidebar (always creates a new array) to standardProjects (filter, also always new), triggering the effect on every streaming update. The prune function itself returns the same Set reference when no pruning is needed, but the setState call still schedules a render. Under rapid SSE events this exceeds React 19's nested update limit, producing minified error #185 (Maximum update depth exceeded). Fix: track the previous project cwd->expanded mapping in a ref. Only call setExpandedThreadListsByProject when a project's expanded status has actually changed, not just when the standardProjects array got a new reference. --- apps/web/src/components/Sidebar.tsx | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 5db7082b..1803e01a 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -270,6 +270,7 @@ const SIDEBAR_LIST_ANIMATION_OPTIONS = { easing: "ease-out", } as const; const EMPTY_THREAD_JUMP_LABELS = new Map(); +const EMPTY_PROJECT_EXPANDED_MAP = new Map(); const EMPTY_SHORTCUT_PARTS: readonly string[] = []; const ADD_PROJECT_SNAPSHOT_CATCH_UP_MAX_ATTEMPTS = 6; const ADD_PROJECT_SNAPSHOT_CATCH_UP_DELAY_MS = 50; @@ -3572,7 +3573,24 @@ export default function Sidebar() { ); // Reset per-project preview expansion when a folder closes so reopening starts at five rows again. + const previousProjectExpandedRef = useRef>(EMPTY_PROJECT_EXPANDED_MAP); useEffect(() => { + const nextExpanded = new Map( + standardProjects.map((project) => [project.cwd, project.expanded] as const), + ); + const prev = previousProjectExpandedRef.current; + let expandedChanged = nextExpanded.size !== prev.size; + if (!expandedChanged) { + for (const [cwd, expanded] of nextExpanded) { + if (prev.get(cwd) !== expanded) { + expandedChanged = true; + break; + } + } + } + previousProjectExpandedRef.current = nextExpanded; + if (!expandedChanged) return; + setExpandedThreadListsByProject((current) => pruneExpandedProjectThreadListsForCollapsedProjects({ expandedProjectThreadListCwds: current, From 05e067cf93becebb153393d40884a0a6e94d03ec Mon Sep 17 00:00:00 2001 From: Jay1 Date: Thu, 28 May 2026 13:51:25 -0400 Subject: [PATCH 2/3] style: fix formatting for CI --- README.md | 4 ++-- apps/web/src/components/Sidebar.tsx | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 763cf1ab..a92987b9 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ A local cockpit for coding agents, tuned for low-level and cybersecurity workflo Copy the prompt below and paste it into your AI tool (Claude, Codex, OpenClaw, etc.) to install JCode on this machine: -````markdown +```markdown Install JCode desktop app on this machine. JCode is at https://github.com/Jay1/jcode 1. Go to https://github.com/Jay1/jcode/releases/latest @@ -34,7 +34,7 @@ Install JCode desktop app on this machine. JCode is at https://github.com/Jay1/j 5. Launch JCode and confirm it opens. If the latest release page or assets cannot be found, report the error. -```` +``` ### Linux and macOS diff --git a/apps/web/src/components/Sidebar.tsx b/apps/web/src/components/Sidebar.tsx index 1803e01a..a83bb428 100644 --- a/apps/web/src/components/Sidebar.tsx +++ b/apps/web/src/components/Sidebar.tsx @@ -3573,7 +3573,9 @@ export default function Sidebar() { ); // Reset per-project preview expansion when a folder closes so reopening starts at five rows again. - const previousProjectExpandedRef = useRef>(EMPTY_PROJECT_EXPANDED_MAP); + const previousProjectExpandedRef = useRef>( + EMPTY_PROJECT_EXPANDED_MAP, + ); useEffect(() => { const nextExpanded = new Map( standardProjects.map((project) => [project.cwd, project.expanded] as const), From 30533677a0fa7548b3a93ec152142969f98741e9 Mon Sep 17 00:00:00 2001 From: Jay1 Date: Thu, 28 May 2026 13:57:19 -0400 Subject: [PATCH 3/3] style: fix README formatting after merge --- README.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 066bd526..6c683fb8 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,6 @@ A local cockpit for coding agents, tuned for low-level and cybersecurity workflo ## Installation - ### Linux and macOS #### Homebrew @@ -26,7 +25,7 @@ brew tap Jay1/jcode https://github.com/Jay1/jcode brew install --cask jcode ``` -#### Download the latest macOS +#### Download the latest macOS `JCode--arm64.dmg` or `JCode--x64.dmg`, or the latest Linux `JCode--x64.AppImage`, from @@ -34,7 +33,7 @@ brew install --cask jcode ### Windows -#### Scoop +#### Scoop If you do not have Scoop yet, follow the [official Scoop installation guide](https://scoop.sh/): @@ -43,7 +42,7 @@ scoop bucket add jcode https://github.com/Jay1/scoop-jcode scoop install jcode ``` -#### Winget +#### Winget ```powershell winget install Jay1.JCode