Skip to content

⚡ Bolt: 내 작업 목록 useMemo 적용으로 O(N) 재렌더링 방지 - #1303

Closed
seonghobae wants to merge 1 commit into
developfrom
bolt-optimize-my-tasks-2835095134316709789
Closed

⚡ Bolt: 내 작업 목록 useMemo 적용으로 O(N) 재렌더링 방지#1303
seonghobae wants to merge 1 commit into
developfrom
bolt-optimize-my-tasks-2835095134316709789

Conversation

@seonghobae

@seonghobae seonghobae commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

💡 무엇을: TasksLayout 컴포넌트 내의 '내 작업' 목록 렌더링 부분을 useMemo로 감쌌습니다.
🎯 왜: 관련된 상태 변경이 일어날 때마다 filteredTicketTasks가 다시 매핑되면서 메인 스레드를 블로킹하는 O(N) 렌더링 비용을 방지하기 위함입니다.
📊 영향: 상태 변경 시 불필요한 재렌더링을 방지하여 체감 성능이 크게 향상됩니다.
🔬 측정 방법: 내 작업 탭에서 다른 상태(예: 검색) 변경 시 TasksLayout의 프로파일링을 통해 렌더링 시간을 확인합니다.


PR created automatically by Jules for task 2835095134316709789 started by @seonghobae

Summary by CodeRabbit

  • Performance
    • Improved the “내 작업” task list so it avoids unnecessary recalculation when unrelated screen state changes.
  • UI
    • Preserved task navigation, status and priority indicators, source details, and the empty-state display.

@google-labs-jules

Copy link
Copy Markdown
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

TasksLayout now memoizes the “내 작업” task board with useMemo. The view renders the memoized board. A .jules/bolt.md note records the memoization requirement and dependencies.

Changes

Tasks board memoization

Layer / File(s) Summary
Memoize and render the task board
.jules/bolt.md, frontend/src/components/TasksLayout.tsx
TasksLayout memoizes the filtered task list, task metadata, navigation behavior, and empty state. The “내 작업” view renders the memoized board instead of inline markup. The performance note records the relevant dependencies.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers: copilot

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes applying useMemo to the ‘내 작업’ list to reduce unnecessary O(N) rerendering.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bolt-optimize-my-tasks-2835095134316709789

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🤖 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 @.jules/bolt.md:
- Around line 22-24: Update the “Memoizing My Tasks view inside JSX”
documentation to clarify that memoization skips recalculation only when
filteredTicketTasks and all other dependencies remain unchanged; note that
taskSearch changes filteredTicketTasks and therefore invalidate myTasksBoard. If
the documented example includes a viewMode guard, include viewMode in its
dependency list.

In `@frontend/src/components/TasksLayout.tsx`:
- Around line 311-329: Update the myTasksBoard useMemo callback to return no
board or otherwise avoid mapping filteredTicketTasks when viewMode is not '내
작업'; preserve the existing board rendering for that mode. Since the callback
reads viewMode, include viewMode in the dependency list, or move the board into
a child rendered only for '내 작업'.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 260a65f2-4c80-4264-ae88-06c3b5db7415

📥 Commits

Reviewing files that changed from the base of the PR and between f781701 and 5c6b0ce.

📒 Files selected for processing (2)
  • .jules/bolt.md
  • frontend/src/components/TasksLayout.tsx

Comment thread .jules/bolt.md
Comment on lines +22 to +24
## 2025-02-12 - Memoizing My Tasks view inside JSX
**Learning:** Inline mapping of arrays inside JSX in large React components causes O(N) recalculation on every render. Although the `kanbanBoard` in `TasksLayout` was properly memoized, the `myTasksBoard` list was still mapped inline in the React return block, causing main thread blocking when other layout state (like search) was updated.
**Action:** Always verify that *all* conditional array map rendering blocks are properly wrapped in a `useMemo` hook with all necessary external variables and setters in the dependency array (e.g. `[filteredTicketTasks, setSelectedTaskId, setViewMode]`) to prevent state changes in unrelated components from causing a bottleneck.

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the memoization invalidation claim.

taskSearch is a direct input to filteredTicketTasks, so search changes invalidate myTasksBoard. Search is not an unrelated update for this board. State that memoization skips work only when filteredTicketTasks and the other dependencies remain unchanged. If the code adds the viewMode guard, include that dependency in the documented example.

Suggested wording change
- ... when other layout state (like search) was updated.
+ ... when unrelated state changes without changing filteredTicketTasks.
📝 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
## 2025-02-12 - Memoizing My Tasks view inside JSX
**Learning:** Inline mapping of arrays inside JSX in large React components causes O(N) recalculation on every render. Although the `kanbanBoard` in `TasksLayout` was properly memoized, the `myTasksBoard` list was still mapped inline in the React return block, causing main thread blocking when other layout state (like search) was updated.
**Action:** Always verify that *all* conditional array map rendering blocks are properly wrapped in a `useMemo` hook with all necessary external variables and setters in the dependency array (e.g. `[filteredTicketTasks, setSelectedTaskId, setViewMode]`) to prevent state changes in unrelated components from causing a bottleneck.
## 2025-02-12 - Memoizing My Tasks view inside JSX
**Learning:** Inline mapping of arrays inside JSX in large React components causes O(N) recalculation on every render. Although the `kanbanBoard` in `TasksLayout` was properly memoized, the `myTasksBoard` list was still mapped inline in the React return block, causing main thread blocking when unrelated state changes without changing `filteredTicketTasks`.
**Action:** Always verify that *all* conditional array map rendering blocks are properly wrapped in a `useMemo` hook with all necessary external variables and setters in the dependency array (e.g. `[filteredTicketTasks, setSelectedTaskId, setViewMode]`) to prevent state changes in unrelated components from causing a bottleneck.
🤖 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 @.jules/bolt.md around lines 22 - 24, Update the “Memoizing My Tasks view
inside JSX” documentation to clarify that memoization skips recalculation only
when filteredTicketTasks and all other dependencies remain unchanged; note that
taskSearch changes filteredTicketTasks and therefore invalidate myTasksBoard. If
the documented example includes a viewMode guard, include viewMode in its
dependency list.

Comment on lines +311 to +329
const myTasksBoard = useMemo(() => (
<div className="space-y-4 max-w-4xl mx-auto">
<h2 className="font-bold text-lg mb-4">내 작업</h2>
{filteredTicketTasks.length > 0 ? filteredTicketTasks.map(task => (
<button key={task.id} type="button" className="flex w-full items-center justify-between p-4 rounded-xl border border-border bg-card text-left shadow-sm transition-colors hover:border-primary/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40" onClick={() => { setSelectedTaskId(task.id); setViewMode('작업 상세'); }}>
<div className="flex items-center gap-4">
<div className={`size-3 rounded-full ${task.priority === 'urgent' ? 'bg-red-500' : task.priority === 'high' ? 'bg-orange-500' : 'bg-blue-500'}`}></div>
<div>
<h3 className="font-bold text-sm">{safeTaskTitle(task.title)}</h3>
<p className="text-xs text-muted-foreground mt-1">근거: {getTaskEvidenceLabel(task)} | 원본: {getTaskSourceLabel(task.source_type)}</p>
</div>
</div>
<span className={`px-2 py-1 rounded-full text-xs font-bold ${task.status === 'done' ? 'bg-green-100 text-green-700' : 'bg-secondary text-secondary-foreground'}`}>{taskStatusLabels[task.status]}</span>
</button>
)) : (
<p className="rounded-xl border border-dashed border-border bg-card p-4 text-sm font-semibold text-muted-foreground">서명 세션에 연결된 내 작업이 없습니다.</p>
)}
</div>
), [filteredTicketTasks, setSelectedTaskId, setViewMode]);

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.

🚀 Performance & Scalability | 🟠 Major | ⚡ Quick win

Guard the memoized board before mapping hidden tasks.

useMemo evaluates this callback when filteredTicketTasks changes, even when viewMode is not '내 작업'. Line 706 only controls whether the already-built element is returned. Search, priority changes, and ticket refreshes therefore allocate an O(N) invisible board.

Guard the callback with viewMode, or isolate the board in a child component rendered only for '내 작업'. Include viewMode in the dependency list when the callback reads it.

Suggested fix
-  const myTasksBoard = useMemo(() => (
+  const myTasksBoard = useMemo(() => {
+    if (viewMode !== '내 작업') return null;
+    return (
     <div className="space-y-4 max-w-4xl mx-auto">
       ...
     </div>
-  ), [filteredTicketTasks, setSelectedTaskId, setViewMode]);
+    );
+  }, [filteredTicketTasks, viewMode, setSelectedTaskId, setViewMode]);
📝 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 myTasksBoard = useMemo(() => (
<div className="space-y-4 max-w-4xl mx-auto">
<h2 className="font-bold text-lg mb-4">내 작업</h2>
{filteredTicketTasks.length > 0 ? filteredTicketTasks.map(task => (
<button key={task.id} type="button" className="flex w-full items-center justify-between p-4 rounded-xl border border-border bg-card text-left shadow-sm transition-colors hover:border-primary/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40" onClick={() => { setSelectedTaskId(task.id); setViewMode('작업 상세'); }}>
<div className="flex items-center gap-4">
<div className={`size-3 rounded-full ${task.priority === 'urgent' ? 'bg-red-500' : task.priority === 'high' ? 'bg-orange-500' : 'bg-blue-500'}`}></div>
<div>
<h3 className="font-bold text-sm">{safeTaskTitle(task.title)}</h3>
<p className="text-xs text-muted-foreground mt-1">근거: {getTaskEvidenceLabel(task)} | 원본: {getTaskSourceLabel(task.source_type)}</p>
</div>
</div>
<span className={`px-2 py-1 rounded-full text-xs font-bold ${task.status === 'done' ? 'bg-green-100 text-green-700' : 'bg-secondary text-secondary-foreground'}`}>{taskStatusLabels[task.status]}</span>
</button>
)) : (
<p className="rounded-xl border border-dashed border-border bg-card p-4 text-sm font-semibold text-muted-foreground">서명 세션에 연결된 내 작업이 없습니다.</p>
)}
</div>
), [filteredTicketTasks, setSelectedTaskId, setViewMode]);
const myTasksBoard = useMemo(() => {
if (viewMode !== '내 작업') return null;
return (
<div className="space-y-4 max-w-4xl mx-auto">
<h2 className="font-bold text-lg mb-4">내 작업</h2>
{filteredTicketTasks.length > 0 ? filteredTicketTasks.map(task => (
<button key={task.id} type="button" className="flex w-full items-center justify-between p-4 rounded-xl border border-border bg-card text-left shadow-sm transition-colors hover:border-primary/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40" onClick={() => { setSelectedTaskId(task.id); setViewMode('작업 상세'); }}>
<div className="flex items-center gap-4">
<div className={`size-3 rounded-full ${task.priority === 'urgent' ? 'bg-red-500' : task.priority === 'high' ? 'bg-orange-500' : 'bg-blue-500'}`}></div>
<div>
<h3 className="font-bold text-sm">{safeTaskTitle(task.title)}</h3>
<p className="text-xs text-muted-foreground mt-1">근거: {getTaskEvidenceLabel(task)} | 원본: {getTaskSourceLabel(task.source_type)}</p>
</div>
</div>
<span className={`px-2 py-1 rounded-full text-xs font-bold ${task.status === 'done' ? 'bg-green-100 text-green-700' : 'bg-secondary text-secondary-foreground'}`}>{taskStatusLabels[task.status]}</span>
</button>
)) : (
<p className="rounded-xl border border-dashed border-border bg-card p-4 text-sm font-semibold text-muted-foreground">서명 세션에 연결된 내 작업이 없습니다.</p>
)}
</div>
);
}, [filteredTicketTasks, viewMode, setSelectedTaskId, setViewMode]);
🤖 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 `@frontend/src/components/TasksLayout.tsx` around lines 311 - 329, Update the
myTasksBoard useMemo callback to return no board or otherwise avoid mapping
filteredTicketTasks when viewMode is not '내 작업'; preserve the existing board
rendering for that mode. Since the callback reads viewMode, include viewMode in
the dependency list, or move the board into a child rendered only for '내 작업'.

@github-actions

Copy link
Copy Markdown
Contributor

PR governance metadata gate is not ready for 5c6b0ce1efbc9a2b2217a72361822bf5d4e4173d:

  • Review decision is CHANGES_REQUESTED; address requested changes before merge.
  • 2 unresolved current review thread(s) remain.
  • Current-head CodeRabbit review comment has blocking warning/failure evidence on 5c6b0ce.

Copy link
Copy Markdown
Contributor Author

Closing this older lane as a proven semantic duplicate of #1315.

Fresh comparison at the current refs found both PRs start from f781701f108cacae08bf1b0749b62ddb8b8385fd, change the same TasksLayout “내 작업” list from inline mapping to useMemo, and carry the same still-valid review defect: the memoized callback maps filteredTicketTasks while the view is hidden because it has no viewMode !== '내 작업' guard. #1315 is the narrower canonical lane (one production file); this PR additionally changes .jules/bolt.md and has an extra documentation finding.

No performance or review evidence is transferred. #1315 must still be rebuilt onto the live protected base, fix its current-head finding, and pass fresh exact-head gates and governance.

@seonghobae seonghobae closed this Aug 11, 2026
@google-labs-jules

Copy link
Copy Markdown
Contributor

Closing this older lane as a proven semantic duplicate of #1315.

Fresh comparison at the current refs found both PRs start from f781701f108cacae08bf1b0749b62ddb8b8385fd, change the same TasksLayout “내 작업” list from inline mapping to useMemo, and carry the same still-valid review defect: the memoized callback maps filteredTicketTasks while the view is hidden because it has no viewMode !== '내 작업' guard. #1315 is the narrower canonical lane (one production file); this PR additionally changes .jules/bolt.md and has an extra documentation finding.

No performance or review evidence is transferred. #1315 must still be rebuilt onto the live protected base, fix its current-head finding, and pass fresh exact-head gates and governance.

Understood. Acknowledging that this work is now obsolete as a duplicate of #1315 and stopping work on this task.

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