Skip to content

Guard React Doctor and clean focused diagnostics#57

Merged
Jay1 merged 8 commits into
mainfrom
jcode/bd0581f7
Jun 4, 2026
Merged

Guard React Doctor and clean focused diagnostics#57
Jay1 merged 8 commits into
mainfrom
jcode/bd0581f7

Conversation

@Jay1

@Jay1 Jay1 commented Jun 4, 2026

Copy link
Copy Markdown
Owner

Summary

  • route React Doctor through a guarded runner with changed-file defaults and full-scan opt-in
  • split component/helper exports for task notifications, mention chips, command menu, and drop overlay
  • clean focused React Doctor component diagnostics while preserving public import paths

Verification

  • bun run --cwd apps/web test src/components/chat/ComposerCommandMenu.test.ts src/components/chat-drop-overlay/ChatPaneDropOverlay.test.tsx
  • bunx oxfmt@0.52.0 --check
  • bun run --cwd apps/web typecheck
  • bun run --cwd scripts test react-doctor-runner.test.ts
  • bun run --cwd scripts typecheck
  • bun run scripts/react-doctor-runner.ts changed -- --verbose

Notes

  • Explicit branch-diff React Doctor against main was attempted with the guarded runner and cancelled at the 1536 MB RSS guard, confirming the runaway full/diff scan protection is active.

Summary by CodeRabbit

  • New Features

    • Thread drag-and-drop support in the chat composer
    • Composer command menu groups commands into clearer sections
    • Case-sensitive terminal search with a toggle
    • Browser notification permission detection and request helpers
  • Bug Fixes

    • Rename dialogs reliably remount/reset when switching threads
    • Improved terminal search and Escape/close behavior
    • Better favicon/language icons in project sidebar and more stable UI overlays
  • Chores

    • Updated React tooling runner and related scripts

@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR removes many React memoizations and shifts effect-based state resets to render-time checks or key-driven remounts, extracts chat drag/drop and notification permission helpers into dedicated modules, simplifies handlers across terminal/image/git components, and adds a guarded React Doctor CLI runner with tests and package script updates.

Changes

Lifecycle Refactoring: useEffect → useRef Sync & Memoization Removal

Layer / File(s) Summary
useEffect → useRef render-time sync
apps/web/src/components/ProjectScriptsControl.tsx, apps/web/src/components/ProjectSidebarIcon.tsx, apps/web/src/components/ThemePackEditor.tsx, apps/web/src/components/WhatsNewDialog.tsx, apps/web/src/components/WorkspaceView.tsx, apps/web/src/components/terminal/useTerminalDrawerHeight.ts, apps/web/src/routes/_chat.$threadId.tsx, apps/web/src/routes/_chat.index.tsx, apps/web/src/routes/_chat.settings.tsx, apps/web/src/routes/pair.tsx, apps/web/src/hooks/useLocalStorage.ts, apps/web/src/whatsNew/useWhatsNew.ts
Replaces effect-driven synchronization with render-time conditional updates using refs/state to detect changes and update local state only when inputs differ.
useCallback → inline functions
apps/web/src/components/GitActionsControl.tsx, apps/web/src/components/TerminalScrollToBottom.tsx, apps/web/src/components/TerminalSearch.tsx, apps/web/src/components/chat/GeneratedMarkdownImage.tsx
Converts memoized handlers to plain functions, preserving logic while removing dependency arrays and memoization.
useMemo → inline IIFE computations
apps/web/src/components/PullRequestThreadDialog.tsx, apps/web/src/components/RenameThreadDialog.tsx, apps/web/src/components/ShortcutsDialog.tsx, apps/web/src/components/chat/ComposerCommandMenuComponent.tsx, apps/web/src/components/chat/GeneratedMarkdownImage.tsx
Replaces useMemo with direct render-time computations or IIFEs to simplify lifecycle and dependency concerns.
React.memo unwrapping
apps/web/src/components/chat/ComposerCommandMenuComponent.tsx, apps/web/src/components/chat/RateLimitBanner.tsx
Removes React.memo wrappers and exports plain function components.
Key-driven component remounting
apps/web/src/components/ChatView.tsx, apps/web/src/components/Sidebar.tsx, apps/web/src/components/chat/GeneratedMarkdownImage.tsx
Adds key props to force remounts when identifiers change to reset internal state without explicit effects.
useRef → useState for latched state
apps/web/src/hooks/useIsDisposableThread.ts
Replaces ref-backed latched sets with useState-backed ReadonlySet to latch disposable thread IDs across renders.

Code Organization: Module Extraction & Restructuring

Layer / File(s) Summary
Chat drop overlay logic extraction
apps/web/src/components/chat-drop-overlay/ChatPaneDropOverlay.logic.ts, apps/web/src/components/chat-drop-overlay/ChatPaneDropOverlay.ts, apps/web/src/components/chat-drop-overlay/ChatPaneDropOverlayComponent.tsx
Extracts drop-zone calculation (getDropZoneFromPointer), drag payload parsing/validation, and mapping helpers into a logic module and re-exports them via a barrel; component imports and uses the new helpers.
Composer command menu restructuring
apps/web/src/components/chat/ComposerCommandMenu.ts, apps/web/src/components/chat/ComposerCommandMenuComponent.tsx
Adds ComposerCommandItem union and public grouping API in the barrel; component inlines grouping logic and removes memoization.
MentionChipIcon DOM helper extraction
apps/web/src/components/chat/MentionChipIcon.dom.tsx, apps/web/src/components/chat/MentionChipIcon.tsx, apps/web/src/components/composer-nodes/index.tsx
Moves static SVG DOM creation into .dom.tsx; main MentionChipIcon is React-only and composer-nodes imports the DOM helper where needed.
Browser notification permissions extraction
apps/web/src/notifications/taskCompletion.permissions.ts, apps/web/src/notifications/taskCompletion.ts, apps/web/src/notifications/taskCompletionNotifications.tsx
Introduces a permission-state type and helpers in a new module, re-exports them, and simplifies TaskCompletionNotifications to compute visible threads inside its effect.
React Doctor CLI runner & safety guardrails
scripts/react-doctor-runner.ts, scripts/react-doctor-runner.test.ts, package.json
Adds a runner that builds safe react-doctor invocations, validates full vs changed modes, adds Linux RSS monitoring to guard memory, updates package scripts to use the runner, and includes tests verifying invocation and validation behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Jay1/jcode#46: Both PRs modify apps/web/src/components/ProjectSidebarIcon.tsx to change language icon artwork rendering and fallback behavior.
  • Jay1/jcode#21: Both PRs touch PullRequestThreadDialog.tsx changes around status derivation and dialog confirm flow.
  • Jay1/jcode#47: Overlaps on ProjectSidebarIcon.tsx icon styling and artwork/container sizing.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main changes: routing React Doctor through a guarded runner and cleaning component diagnostics.
Description check ✅ Passed The description covers the summary, verification steps, and notes, aligning with the template structure and providing substantial context.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. labels Jun 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (3)
apps/web/src/components/GitActionsControl.tsx (1)

963-1043: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Run the formatter on this hunk.

CI is already failing oxfmt --check, and this block has the indentation drift that will keep that job red.

🤖 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 `@apps/web/src/components/GitActionsControl.tsx` around lines 963 - 1043, This
hunk has inconsistent indentation causing CI to fail oxfmt --check; run the
project's formatter (or manually fix indentation) for the block containing
setIsCreateBranchDialogOpen, setCreateBranchName, the branch-equality early
return, the toast creation (toastManager.add), the try/catch that calls
api.git.createBranch and api.git.checkout, the orchestration dispatchCommand
blocks, setThreadWorkspaceAction calls, invalidateGitQueries, and the
toastManager.update calls so the spacing/indentation matches the repo's
formatting rules and oxfmt passes. Ensure nested promises/blocks are indented
consistently and re-run the formatter before committing.
apps/web/src/components/PullRequestThreadDialog.tsx (1)

75-86: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restore per-open initialization for initialReference and validation state.

This dialog now keeps reference, referenceDirty, and the last resolved PR across close/reopen, but initialReference is still a prop. After one failed or completed checkout, reopening can show the previous PR instead of the new seed value and preserve stale validation/error UI. Reintroduce a fresh-open reset here or force a remount from the caller.

Also applies to: 117-142

🤖 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 `@apps/web/src/components/PullRequestThreadDialog.tsx` around lines 75 - 86,
The dialog is preserving reference, referenceDirty and the cached last-resolved
PR across close/reopen; when the dialog opens we must reinitialize per-open
state so initialReference is applied and validation/errors are cleared. In
PullRequestThreadDialog, on the prop that controls visibility (e.g. open) add a
useEffect that when opening sets reference = initialReference, sets
referenceDirty = false, and clears any lastResolvedPullRequest/cachedPullRequest
state so validation and error UI reset; ensure the same reset logic is applied
for the other similar block (lines referenced around 117-142) so every open
starts fresh.
apps/web/src/components/chat/ComposerCommandMenuComponent.tsx (1)

241-295: 🛠️ Refactor suggestion | 🟠 Major | 🏗️ Heavy lift

Deduplicate the grouping contract before the two entrypoints drift.

groupCommandItems now exists here and in ComposerCommandMenu.ts. That makes it easy for the rendered menu and external callers importing the helper to diverge on the next label/order change. Please move the shared model/grouping logic into one module and re-export from both paths if you still need backward-compatible imports.

🤖 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 `@apps/web/src/components/chat/ComposerCommandMenuComponent.tsx` around lines
241 - 295, The grouping logic (function groupCommandItems and the
ComposerCommandGroupModel contract) is duplicated between
ComposerCommandMenuComponent and ComposerCommandMenu; extract this shared logic
into a single module (e.g., a new shared grouping module), export
groupCommandItems and the ComposerCommandGroupModel/type from there, update both
ComposerCommandMenuComponent and ComposerCommandMenu to import and use that
single exported function, and optionally re-export from the original modules to
keep backward-compatible imports so labels/order cannot drift between the two
entrypoints.
🤖 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.

Inline comments:
In `@apps/web/src/components/ProjectScriptsControl.tsx`:
- Around line 273-280: The current guard prevents handling the first external
open request because lastOpenAddActionNonceRef.current starts undefined; change
the conditional to trigger when openAddActionNonce is defined and differs from
lastOpenAddActionNonceRef.current (i.e., use openAddActionNonce !== undefined &&
openAddActionNonce !== lastOpenAddActionNonceRef.current), then set
lastOpenAddActionNonceRef.current = openAddActionNonce and call openAddDialog();
this ensures the initial undefined→value transition is handled while still
ignoring repeated identical nonces.

In `@apps/web/src/components/ProjectSidebarIcon.tsx`:
- Around line 122-129: The component's hasFavicon state isn't synced when
faviconSrc changes because the useEffect returns early if
projectFaviconPresence.has(faviconSrc) without updating state; update the effect
inside ProjectSidebarIcon so that when projectFaviconPresence.has(faviconSrc)
you call setHasFavicon(projectFaviconPresence.get(faviconSrc) === true) before
returning, ensuring state mirrors the cache for the new faviconSrc (apply the
same change to the other similar effect that references projectFaviconPresence
and hasFavicon).

In `@apps/web/src/components/PullRequestThreadDialog.tsx`:
- Around line 138-140: The catch block in handleConfirm (where
setPreparingMode(null) is called) currently rethrows the caught error, which
converts a handled mutation error (tracked via
preparePullRequestThreadMutation.error) into an unhandled rejection; remove the
throw error so the function swallows the error after cleaning up state (keep
setPreparingMode(null)), and if needed replace the rethrow with a concise log or
no-op; locate the catch in PullRequestThreadDialog.tsx surrounding
setPreparingMode and update it to not rethrow.

In `@apps/web/src/components/RenameThreadDialog.tsx`:
- Around line 64-67: The dialog is still being closed via the Dialog's
onOpenChange handler while a rename is in progress, losing error state; update
the onOpenChange callback used on the Dialog component and any other
dialog-level dismissal handlers (e.g., the ones around the cancel/escape paths)
to check isSaving and only call closeDialog() when isSaving is false (i.e.,
onOpenChange={(nextOpen) => { if (!isSaving) nextOpen ? onOpenChange(true) :
closeDialog() }}), and ensure any Escape/overlay-dismiss config is similarly
gated or disabled while isSaving so the dialog cannot be dismissed until the
rename finishes.

In `@apps/web/src/components/TerminalSearch.tsx`:
- Around line 107-111: The component currently only clears terminal decorations
in handleClose(), so when the parent toggles isOpen to false and the component
unmounts without calling handleClose(), stale highlights remain; add a useEffect
watching isOpen (and on unmount) that calls searchAddon?.clearDecorations() and
resets state (e.g., setQuery(""), setHasResults(null)) as cleanup, and ensure
the cleanup also runs on component unmount to mirror handleClose() behavior;
reference handleClose, isOpen prop, and searchAddon.clearDecorations when
locating where to add the effect.

In `@apps/web/src/routes/_chat.index.tsx`:
- Around line 24-25: splitViewIds is recreated each render which changes its
reference and retriggers the restore/new-chat useEffect; memoize it using
React.useMemo so its identity only changes when splitViewsById changes (e.g.,
const splitViewIds = useMemo(() => Object.keys(splitViewsById).filter(id =>
splitViewsById[id]), [splitViewsById])), then keep using splitViewIds in the
effect dependency array as before so the effect only runs when splitViewsById
actually changes.

In `@apps/web/src/routes/pair.tsx`:
- Around line 89-93: The render currently calls submitPairing(initialCredential)
guarded by hasSubmittedRef inside PairRoute, causing side effects in render;
move this one-shot auto-submit into a post-commit hook by replacing the inline
call with a useEffect that depends on initialCredential and uses hasSubmittedRef
to ensure a single invocation, then calls submitPairing(initialCredential)
inside the effect callback (so network requests, history.replaceState, toasts,
and navigation run after commit) and keep hasSubmittedRef.current set to true to
avoid repeats.

In `@scripts/react-doctor-runner.ts`:
- Around line 110-118: The memory monitor never stops when the RSS cap is hit;
inside the setInterval handler (the block using memoryCheckInterval,
readProcessTreeRssMb, child.pid and invocation.maxRssMb) call
clearInterval(memoryCheckInterval) immediately before invoking
killProcessGroup(child.pid) so the interval is cleared and the monitor stops
issuing further logs/kills after the first termination request; ensure
memoryCheckInterval is in scope for the handler so it can be cleared.
- Around line 144-152: The current readProcessTreeRssMb(rootPid) silently
returns 0 on non-Linux which disables the RSS guard; modify the logic so it
emits a clear one-time warning when monitoring is unavailable and does not
falsely report 0 as real usage: update readProcessTreeRssMb to detect non-Linux
and call the process logger (or console.warn if processLogger isn't available)
with a descriptive message that memory monitoring is unsupported on this
platform, and in main (before starting the interval that enforces the RSS cap)
add an explicit check that if readProcessTreeRssMb would be a no-op (platform
!== "linux") emit/warn once that the RSS guard is inactive so users know
protections are disabled. Ensure you reference the functions
readProcessTreeRssMb and the main routine that starts the interval so the
warning is surfaced before any scanning begins.
- Line 158: Replace the dynamic require call with top-level ESM imports: add an
import like `import { readdirSync, readFileSync } from "node:fs";` at the top of
the module and then update the `procEntries =
Array.from(require("node:fs").readdirSync("/proc"));` line to use
`readdirSync("/proc")`; also replace any other
`require("node:fs").readFileSync(...)` usages to `readFileSync(...)` so the
script no longer mixes CommonJS `require` with ESM imports and will run under
standard Node ESM.

---

Outside diff comments:
In `@apps/web/src/components/chat/ComposerCommandMenuComponent.tsx`:
- Around line 241-295: The grouping logic (function groupCommandItems and the
ComposerCommandGroupModel contract) is duplicated between
ComposerCommandMenuComponent and ComposerCommandMenu; extract this shared logic
into a single module (e.g., a new shared grouping module), export
groupCommandItems and the ComposerCommandGroupModel/type from there, update both
ComposerCommandMenuComponent and ComposerCommandMenu to import and use that
single exported function, and optionally re-export from the original modules to
keep backward-compatible imports so labels/order cannot drift between the two
entrypoints.

In `@apps/web/src/components/GitActionsControl.tsx`:
- Around line 963-1043: This hunk has inconsistent indentation causing CI to
fail oxfmt --check; run the project's formatter (or manually fix indentation)
for the block containing setIsCreateBranchDialogOpen, setCreateBranchName, the
branch-equality early return, the toast creation (toastManager.add), the
try/catch that calls api.git.createBranch and api.git.checkout, the
orchestration dispatchCommand blocks, setThreadWorkspaceAction calls,
invalidateGitQueries, and the toastManager.update calls so the
spacing/indentation matches the repo's formatting rules and oxfmt passes. Ensure
nested promises/blocks are indented consistently and re-run the formatter before
committing.

In `@apps/web/src/components/PullRequestThreadDialog.tsx`:
- Around line 75-86: The dialog is preserving reference, referenceDirty and the
cached last-resolved PR across close/reopen; when the dialog opens we must
reinitialize per-open state so initialReference is applied and validation/errors
are cleared. In PullRequestThreadDialog, on the prop that controls visibility
(e.g. open) add a useEffect that when opening sets reference = initialReference,
sets referenceDirty = false, and clears any
lastResolvedPullRequest/cachedPullRequest state so validation and error UI
reset; ensure the same reset logic is applied for the other similar block (lines
referenced around 117-142) so every open starts fresh.
🪄 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: ASSERTIVE

Plan: Pro Plus

Run ID: 530baf04-4636-44dd-847f-124c929e6ff2

📥 Commits

Reviewing files that changed from the base of the PR and between 674b291 and 56bb5c6.

📒 Files selected for processing (37)
  • apps/web/src/components/ChatView.tsx
  • apps/web/src/components/GitActionsControl.tsx
  • apps/web/src/components/ProjectScriptsControl.tsx
  • apps/web/src/components/ProjectSidebarIcon.tsx
  • apps/web/src/components/PullRequestThreadDialog.tsx
  • apps/web/src/components/RenameThreadDialog.tsx
  • apps/web/src/components/ShortcutsDialog.tsx
  • apps/web/src/components/Sidebar.tsx
  • apps/web/src/components/TerminalScrollToBottom.tsx
  • apps/web/src/components/TerminalSearch.tsx
  • apps/web/src/components/ThemePackEditor.tsx
  • apps/web/src/components/WhatsNewDialog.tsx
  • apps/web/src/components/WorkspaceView.tsx
  • apps/web/src/components/chat-drop-overlay/ChatPaneDropOverlay.logic.ts
  • apps/web/src/components/chat-drop-overlay/ChatPaneDropOverlay.ts
  • apps/web/src/components/chat-drop-overlay/ChatPaneDropOverlayComponent.tsx
  • apps/web/src/components/chat/ComposerCommandMenu.ts
  • apps/web/src/components/chat/ComposerCommandMenuComponent.tsx
  • apps/web/src/components/chat/GeneratedMarkdownImage.tsx
  • apps/web/src/components/chat/MentionChipIcon.dom.tsx
  • apps/web/src/components/chat/MentionChipIcon.tsx
  • apps/web/src/components/chat/RateLimitBanner.tsx
  • apps/web/src/components/composer-nodes/index.tsx
  • apps/web/src/components/terminal/useTerminalDrawerHeight.ts
  • apps/web/src/hooks/useIsDisposableThread.ts
  • apps/web/src/hooks/useLocalStorage.ts
  • apps/web/src/notifications/taskCompletion.permissions.ts
  • apps/web/src/notifications/taskCompletion.ts
  • apps/web/src/notifications/taskCompletionNotifications.tsx
  • apps/web/src/routes/_chat.$threadId.tsx
  • apps/web/src/routes/_chat.index.tsx
  • apps/web/src/routes/_chat.settings.tsx
  • apps/web/src/routes/pair.tsx
  • apps/web/src/whatsNew/useWhatsNew.ts
  • package.json
  • scripts/react-doctor-runner.test.ts
  • scripts/react-doctor-runner.ts

Comment thread apps/web/src/components/ProjectScriptsControl.tsx Outdated
Comment on lines 122 to 129
const [hasFavicon, setHasFavicon] = useState<boolean>(
() => shouldUseFavicon && projectFaviconPresence.get(faviconSrc) === true,
() => projectFaviconPresence.get(faviconSrc) === true,
);
const FolderGlyph = expanded ? HiOutlineFolderOpen : FolderClosed;

// Probe with Image() so Electron/file-origin behaves like the actual visible <img>.
useEffect(() => {
if (!shouldUseFavicon) {
setHasFavicon(false);
return;
}

const cached = projectFaviconPresence.get(faviconSrc);
if (cached !== undefined) {
setHasFavicon(cached);
if (projectFaviconPresence.has(faviconSrc)) {
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Sync hasFavicon when faviconSrc changes.

useState keeps the previous project's value. If this component is reused for a new cwd whose favicon presence is already cached, Lines 128-129 return before copying that cached value into state, so the badge can stay stale or disappear for the new project.

Suggested fix
   const [hasFavicon, setHasFavicon] = useState<boolean>(
     () => projectFaviconPresence.get(faviconSrc) === true,
   );

   // Probe with Image() so Electron/file-origin behaves like the actual visible <img>.
   useEffect(() => {
-    if (projectFaviconPresence.has(faviconSrc)) {
+    const cachedPresence = projectFaviconPresence.get(faviconSrc);
+    if (cachedPresence !== undefined) {
+      setHasFavicon(cachedPresence);
       return;
     }
+
+    setHasFavicon(false);

     let cancelled = false;
     const image = new Image();

Also applies to: 157-157

🤖 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 `@apps/web/src/components/ProjectSidebarIcon.tsx` around lines 122 - 129, The
component's hasFavicon state isn't synced when faviconSrc changes because the
useEffect returns early if projectFaviconPresence.has(faviconSrc) without
updating state; update the effect inside ProjectSidebarIcon so that when
projectFaviconPresence.has(faviconSrc) you call
setHasFavicon(projectFaviconPresence.get(faviconSrc) === true) before returning,
ensuring state mirrors the cache for the new faviconSrc (apply the same change
to the other similar effect that references projectFaviconPresence and
hasFavicon).

Comment thread apps/web/src/components/PullRequestThreadDialog.tsx Outdated
Comment thread apps/web/src/components/RenameThreadDialog.tsx
Comment thread apps/web/src/components/TerminalSearch.tsx
Comment thread apps/web/src/routes/_chat.index.tsx Outdated
Comment on lines 24 to 25
const splitViewIds = Object.keys(splitViewsById).filter((splitViewId) => splitViewsById[splitViewId]);
const [attempt, setAttempt] = useState(0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/web/src/routes/_chat.index.tsx"

# Show the relevant sections with line numbers
nl -ba "$FILE" | sed -n '1,140p'

# Narrow specifically to the claimed dependency/effect region if present
nl -ba "$FILE" | sed -n '15,40p'
nl -ba "$FILE" | sed -n '55,95p'

Repository: Jay1/jcode

Length of output: 97


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/web/src/routes/_chat.index.tsx"

awk '{printf "%d\t%s\n", NR, $0}' "$FILE" | sed -n '1,140p'
echo "-----"
awk '{printf "%d\t%s\n", NR, $0}' "$FILE" | sed -n '15,45p'
echo "-----"
awk '{printf "%d\t%s\n", NR, $0}' "$FILE" | sed -n '55,95p'

Repository: Jay1/jcode

Length of output: 5066


Memoize splitViewIds to prevent the restore/new-chat effect from retriggering on every render.

splitViewIds is derived inline (Object.keys(...).filter(...)) and then included in the useEffect dependency array (splitViewIds), so its reference changes every render. After threadsHydrated and splitViewsHydrated are true, the effect runs again and again, re-invoking either navigate(...) (route restore) or handleNewChat({ fresh: true }) (fresh chat creation).

💡 Proposed fix
-import { useEffect, useState } from "react";
+import { useEffect, useMemo, useState } from "react";
@@
-  const splitViewIds = Object.keys(splitViewsById).filter(
-    (splitViewId) => splitViewsById[splitViewId],
-  );
+  const splitViewIds = useMemo(
+    () => Object.keys(splitViewsById).filter((splitViewId) => splitViewsById[splitViewId]),
+    [splitViewsById],
+  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const splitViewIds = Object.keys(splitViewsById).filter((splitViewId) => splitViewsById[splitViewId]);
const [attempt, setAttempt] = useState(0);
const splitViewIds = useMemo(
() => Object.keys(splitViewsById).filter((splitViewId) => splitViewsById[splitViewId]),
[splitViewsById],
);
const [attempt, setAttempt] = useState(0);
🤖 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 `@apps/web/src/routes/_chat.index.tsx` around lines 24 - 25, splitViewIds is
recreated each render which changes its reference and retriggers the
restore/new-chat useEffect; memoize it using React.useMemo so its identity only
changes when splitViewsById changes (e.g., const splitViewIds = useMemo(() =>
Object.keys(splitViewsById).filter(id => splitViewsById[id]),
[splitViewsById])), then keep using splitViewIds in the effect dependency array
as before so the effect only runs when splitViewsById actually changes.

Comment on lines +89 to +93
const hasSubmittedRef = useRef(false);
if (initialCredential && !hasSubmittedRef.current) {
hasSubmittedRef.current = true;
void submitPairing(initialCredential);
}, [initialCredential, submitPairing]);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/web/src/routes/pair.tsx"

# Basic sanity: file exists
test -f "$FILE"

# Show relevant section around the cited lines (89-93) with line numbers
sed -n '60,130p' "$FILE" | nl -ba | sed -n '1,100p'

# Extract definition of submitPairing (function/const) and any usage
rg -n "submitPairing" "$FILE" || true
rg -n "function submitPairing|const submitPairing|let submitPairing" "$FILE" || true

# Print around the first occurrence of submitPairing definition (best-effort)
FIRST_DEF_LINE="$(rg -n "const submitPairing|function submitPairing" "$FILE" | head -n1 | cut -d: -f1 || true)"
if [[ -n "${FIRST_DEF_LINE:-}" ]]; then
  start=$((FIRST_DEF_LINE-40))
  end=$((FIRST_DEF_LINE+80))
  sed -n "${start},${end}p" "$FILE" | nl -ba | head -n 180
fi

# Check whether there is any current effect boundary already using initialCredential
rg -n "useEffect\\(|useLayoutEffect\\(" "$FILE" || true
sed -n '1,80p' "$FILE" | nl -ba | head -n 120

Repository: Jay1/jcode

Length of output: 98


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/web/src/routes/pair.tsx"
test -f "$FILE"

python3 - <<'PY'
from pathlib import Path
path = Path("apps/web/src/routes/pair.tsx")
lines = path.read_text().splitlines()
for i in range(55, 135):  # approx region around lines 89-93
    if 1 <= i <= len(lines):
        print(f"{i:4d} {lines[i-1]}")
PY

echo "---- submitPairing occurrences ----"
rg -n "submitPairing" "$FILE" || true

echo "---- first submitPairing definition context ----"
FIRST_DEF_LINE="$(rg -n "function submitPairing|const submitPairing|let submitPairing" "$FILE" | head -n1 | cut -d: -f1 || true)"
if [[ -n "${FIRST_DEF_LINE:-}" ]]; then
  python3 - <<PY
from pathlib import Path
path = Path("$FILE")
lines = path.read_text().splitlines()
start = max(1, int("$FIRST_DEF_LINE")-40)
end = min(len(lines), int("$FIRST_DEF_LINE")+90)
for i in range(start, end+1):
    print(f"{i:4d} {lines[i-1]}")
PY
fi

echo "---- effect boundaries using initialCredential ----"
rg -n "useEffect\\(|useLayoutEffect\\(" "$FILE" || true
rg -n "initialCredential" "$FILE" || true

Repository: Jay1/jcode

Length of output: 8590


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="apps/web/src/routes/pair.tsx"
python3 - <<'PY'
from pathlib import Path
path = Path("apps/web/src/routes/pair.tsx")
lines = path.read_text().splitlines()
for i in range(1, 45):
    print(f"{i:4d} {lines[i-1]}")
PY

Repository: Jay1/jcode

Length of output: 1737


Move auto-pairing out of the render path in apps/web/src/routes/pair.tsx (lines 89-93)

PairRoute calls void submitPairing(initialCredential) during render; submitPairing performs state updates and impure side effects (network request, window.history.replaceState, toasts, and window.location.assign/navigation). Even with the ref “one-shot” guard, render-phase side effects can run for abandoned/restarted renders before commit. Move this auto-submit to a useEffect (post-commit).

💡 Proposed fix
-import { useCallback, useMemo, useRef, useState } from "react";
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
@@
   const hasSubmittedRef = useRef(false);
-  if (initialCredential && !hasSubmittedRef.current) {
-    hasSubmittedRef.current = true;
-    void submitPairing(initialCredential);
-  }
+  useEffect(() => {
+    if (!initialCredential || hasSubmittedRef.current) {
+      return;
+    }
+    hasSubmittedRef.current = true;
+    void submitPairing(initialCredential);
+  }, [initialCredential, submitPairing]);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const hasSubmittedRef = useRef(false);
if (initialCredential && !hasSubmittedRef.current) {
hasSubmittedRef.current = true;
void submitPairing(initialCredential);
}, [initialCredential, submitPairing]);
}
const hasSubmittedRef = useRef(false);
useEffect(() => {
if (!initialCredential || hasSubmittedRef.current) {
return;
}
hasSubmittedRef.current = true;
void submitPairing(initialCredential);
}, [initialCredential, submitPairing]);
🤖 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 `@apps/web/src/routes/pair.tsx` around lines 89 - 93, The render currently
calls submitPairing(initialCredential) guarded by hasSubmittedRef inside
PairRoute, causing side effects in render; move this one-shot auto-submit into a
post-commit hook by replacing the inline call with a useEffect that depends on
initialCredential and uses hasSubmittedRef to ensure a single invocation, then
calls submitPairing(initialCredential) inside the effect callback (so network
requests, history.replaceState, toasts, and navigation run after commit) and
keep hasSubmittedRef.current set to true to avoid repeats.

Comment thread scripts/react-doctor-runner.ts
Comment thread scripts/react-doctor-runner.ts
const childrenByParent = new Map<number, number[]>();
let procEntries: string[] = [];
try {
procEntries = Array.from(require("node:fs").readdirSync("/proc"));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Is require defined inside an ES module scope when running scripts with Bun 1.3?

💡 Result:

Yes, the require function is available and defined within the scope of ES modules when running scripts in Bun 1.3 [1][2]. Bun treats both CommonJS and ES modules as first-class citizens [1][3]. Unlike standard Node.js, where require is not available in ES module files (which instead use import), Bun provides native, synchronous require() support that can be used within both module types [1][2]. Key technical details regarding this behavior in Bun: * Availability: You can use require() directly inside an ES module file to load other modules [1][2]. * Transpilation: When you use require inside an ES module, Bun’s internal transpiler effectively rewrites these calls to use import.meta.require [4]. * Interoperability: When using require() to load an ES module, Bun returns the module namespace object (the equivalent of import * as) [1][2]. * Limitations: While require() is synchronous and widely available, it cannot be used to load modules that utilize top-level await, as top-level await requires asynchronous resolution [2]. Please note that while this functionality is a core feature of Bun's runtime, the import.meta.require API has been documented as unstable in some references [4]. Additionally, because require()-ing ESM is a complex operation, users should be aware that it has historically been subject to edge-case bugs (such as dependency deadlocks or crashes with specific static import combinations) that are addressed in ongoing Bun updates [5][6][7].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the relevant region around the referenced line numbers
FILE="scripts/react-doctor-runner.ts"

if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE" >&2
  exit 1
fi

# Print with line numbers for context around 130-220
nl -ba "$FILE" | sed -n '130,220p'

Repository: Jay1/jcode

Length of output: 98


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="scripts/react-doctor-runner.ts"

if [ ! -f "$FILE" ]; then
  echo "Missing file: $FILE" >&2
  exit 1
fi

# Print with line numbers for context around the reported lines
cat -n "$FILE" | sed -n '130,220p'

Repository: Jay1/jcode

Length of output: 2733


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# List likely config files
ls -la

# Show package.json relevant fields if present
if [ -f package.json ]; then
  cat package.json | sed -n '1,200p'
fi

# Show tsconfig.base.json if present
if [ -f tsconfig.base.json ]; then
  echo "---- tsconfig.base.json ----"
  cat tsconfig.base.json | sed -n '1,200p'
fi

# Locate references to react-doctor-runner.ts
rg -n "react-doctor-runner\.ts|react-doctor-runner" -S . || true

Repository: Jay1/jcode

Length of output: 7541


🏁 Script executed:

#!/bin/bash
set -euo pipefail

ls -la

if [ -f package.json ]; then
  echo "---- package.json (first 200 lines) ----"
  sed -n '1,200p' package.json
fi

if [ -f tsconfig.base.json ]; then
  echo "---- tsconfig.base.json (first 200 lines) ----"
  sed -n '1,200p' tsconfig.base.json
fi

echo "---- References to react-doctor-runner ----"
rg -n "react-doctor-runner\.ts|react-doctor-runner" -S . || true

Repository: Jay1/jcode

Length of output: 7644


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="scripts/react-doctor-runner.ts"
cat -n "$FILE" | sed -n '1,120p'

Repository: Jay1/jcode

Length of output: 4392


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "node\s+scripts/react-doctor-runner\.ts|react-doctor-runner\.ts\s" package.json scripts -S || true

Repository: Jay1/jcode

Length of output: 322


🏁 Script executed:

#!/bin/bash
set -euo pipefail

sed -n '1,200p' scripts/react-doctor-runner.test.ts
echo "---- vitest.config.ts ----"
sed -n '1,260p' vitest.config.ts

Repository: Jay1/jcode

Length of output: 3749


Use top-level ESM node:fs imports instead of require() in scripts/react-doctor-runner.ts.

This script is run via Bun (bun run scripts/react-doctor-runner.ts ...), so require() works, but require isn’t available in standard Node ESM, and the file mixes module styles.

♻️ Proposed refactor
-import { spawn } from "node:child_process";
+import { spawn } from "node:child_process";
+import { readdirSync, readFileSync } from "node:fs";

Then replace require("node:fs").readdirSync(...) / readFileSync(...) with the imported readdirSync / readFileSync.

🤖 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 `@scripts/react-doctor-runner.ts` at line 158, Replace the dynamic require call
with top-level ESM imports: add an import like `import { readdirSync,
readFileSync } from "node:fs";` at the top of the module and then update the
`procEntries = Array.from(require("node:fs").readdirSync("/proc"));` line to use
`readdirSync("/proc")`; also replace any other
`require("node:fs").readFileSync(...)` usages to `readFileSync(...)` so the
script no longer mixes CommonJS `require` with ESM imports and will run under
standard Node ESM.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

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

⚠️ Outside diff range comments (1)
scripts/react-doctor-runner.test.ts (1)

9-28: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make the command assertion platform-aware

buildReactDoctorInvocation() returns react-doctor.cmd on Windows (process.platform === "win32"), but the test currently hard-codes "react-doctor".

🧪 Proposed fix
-    assert.equal(invocation.command, "react-doctor");
+    assert.equal(
+      invocation.command,
+      process.platform === "win32" ? "react-doctor.cmd" : "react-doctor",
+    );
🤖 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 `@scripts/react-doctor-runner.test.ts` around lines 9 - 28, The test hard-codes
the command name but buildReactDoctorInvocation returns "react-doctor.cmd" on
Windows; modify the assertion to compute the expected command based on
process.platform (e.g., const expectedCmd = process.platform === "win32" ?
"react-doctor.cmd" : "react-doctor") and assert invocation.command equals
expectedCmd; update the test in scripts/react-doctor-runner.test.ts referencing
buildReactDoctorInvocation to use that platform-aware expectedCmd.
♻️ Duplicate comments (1)
scripts/react-doctor-runner.ts (1)

169-207: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Use top-level node:fs imports instead of require() in this ESM module.

This file already uses ESM syntax, but Lines 169, 195, and 207 still load node:fs via require(). That keeps the runner dependent on Bun-specific interop instead of standard ESM behavior.

♻️ Proposed fix
 import { spawn } from "node:child_process";
+import { readdirSync, readFileSync } from "node:fs";
-    procEntries = Array.from(require("node:fs").readdirSync("/proc"));
+    procEntries = Array.from(readdirSync("/proc"));
-    const stat = require("node:fs").readFileSync(`/proc/${pid}/stat`, "utf8");
+    const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
-    const status = require("node:fs").readFileSync(`/proc/${pid}/status`, "utf8");
+    const status = readFileSync(`/proc/${pid}/status`, "utf8");

As per coding guidelines, **/*.{ts,tsx,js,mjs}: Use ESM modules in TypeScript code.

🤖 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 `@scripts/react-doctor-runner.ts` around lines 169 - 207, Replace dynamic
require("node:fs") calls with a single top-level ESM import and use that import
across the module: add an import like "import * as fs from 'node:fs'" at the top
of the file, then change procEntries =
Array.from(require('node:fs').readdirSync(...)) to use fs.readdirSync(...),
change the readFileSync calls in readParentPid and readProcessRssKb to
fs.readFileSync(...), and remove any remaining require("node:fs") usages so the
module is pure ESM; the key symbols to update are the procEntries assignment and
the functions readParentPid and readProcessRssKb.
🤖 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.

Outside diff comments:
In `@scripts/react-doctor-runner.test.ts`:
- Around line 9-28: The test hard-codes the command name but
buildReactDoctorInvocation returns "react-doctor.cmd" on Windows; modify the
assertion to compute the expected command based on process.platform (e.g., const
expectedCmd = process.platform === "win32" ? "react-doctor.cmd" :
"react-doctor") and assert invocation.command equals expectedCmd; update the
test in scripts/react-doctor-runner.test.ts referencing
buildReactDoctorInvocation to use that platform-aware expectedCmd.

---

Duplicate comments:
In `@scripts/react-doctor-runner.ts`:
- Around line 169-207: Replace dynamic require("node:fs") calls with a single
top-level ESM import and use that import across the module: add an import like
"import * as fs from 'node:fs'" at the top of the file, then change procEntries
= Array.from(require('node:fs').readdirSync(...)) to use fs.readdirSync(...),
change the readFileSync calls in readParentPid and readProcessRssKb to
fs.readFileSync(...), and remove any remaining require("node:fs") usages so the
module is pure ESM; the key symbols to update are the procEntries assignment and
the functions readParentPid and readProcessRssKb.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 2a76d019-feba-48ad-a0f9-c41a8141c056

📥 Commits

Reviewing files that changed from the base of the PR and between 081e904 and 11e4c1b.

📒 Files selected for processing (7)
  • apps/web/src/components/ProjectScriptsControl.tsx
  • apps/web/src/components/ProjectSidebarIcon.tsx
  • apps/web/src/components/PullRequestThreadDialog.tsx
  • apps/web/src/components/RenameThreadDialog.tsx
  • apps/web/src/components/TerminalSearch.tsx
  • scripts/react-doctor-runner.test.ts
  • scripts/react-doctor-runner.ts

@Jay1 Jay1 merged commit 35d4e92 into main Jun 4, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XXL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant