Skip to content

feat(review): add business context (task/requirement) to diff viewer file tree (#480)#522

Merged
frankbria merged 7 commits into
mainfrom
feature/issue-480-business-context-diff-viewer
Apr 2, 2026
Merged

feat(review): add business context (task/requirement) to diff viewer file tree (#480)#522
frankbria merged 7 commits into
mainfrom
feature/issue-480-business-context-diff-viewer

Conversation

@frankbria

@frankbria frankbria commented Apr 2, 2026

Copy link
Copy Markdown
Owner

Summary

Closes #480

  • Extended FileChange type with optional task_id? and task_title? fields (backward-compatible)
  • Review page now fetches the workspace task list alongside the diff; auto-selects a single IN_PROGRESS task as contextTask
  • FileTreePanel: Dir/Task grouping toggle, per-file task badges in dir mode, collapsible task groups in task mode — contextTask used as fallback when files lack explicit task_id
  • DiffViewer: Per-file task context chip + "View Task →" link in each file header (resolves via changedFiles mapping, falls back to contextTask, gracefully absent when neither is set)
  • 18 new tests covering all acceptance criteria including edge cases (empty tasks, missing requirement IDs, contextTask fallback, per-file mapping, collapse behavior, link stopPropagation)

Acceptance Criteria

  • Task name visible in file tree next to filename (badge in dir mode; group header in task mode)
  • Requirement ID shown in diff header when available (REQ: {id} chip)
  • 'View Task' link navigates to task detail (/tasks)
  • File tree supports grouping by task (Dir/Task toggle)
  • Works for multi-task diff sessions (per-file resolution via changedFiles mapping)

Test Plan

  • cd web-ui && npm test -- --watchAll=false --testPathPattern=review → 18 tests pass
  • npm run lint → 0 errors
  • Open the Review page with an IN_PROGRESS task → task badge appears next to files, "View Task →" link in diff headers
  • Toggle Dir/Task in file tree → files group under task headers
  • Click "View Task →" → navigates to /tasks without collapsing the diff

Summary by CodeRabbit

  • New Features

    • Toggle between directory and task-based file grouping in the review sidebar
    • File-level task attribution: task badges in the sidebar and task context (titles and requirement IDs) in diffs
    • Added links to navigate to related tasks from the review interface
  • Tests

    • New automated tests covering FileTreePanel and DiffViewer rendering, grouping, badges, links, and interactions

@coderabbitai

coderabbitai Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This change adds task/requirement context to the Review page by extending types, updating FileTreePanel and DiffViewer to accept and render task metadata and grouping, fetching tasks in the Review page, and adding tests and icon mocks to support the new UI behavior.

Changes

Cohort / File(s) Summary
Type Extensions
web-ui/src/types/index.ts
Added optional task_id?: string and task_title?: string to FileChange.
Icon Mocks
web-ui/__mocks__/@hugeicons/react.js
Exported FileAddIcon and FileRemoveIcon mocks for tests.
Core Components
web-ui/src/components/review/FileTreePanel.tsx, web-ui/src/components/review/DiffViewer.tsx
Added tasks? and contextTask? props. FileTreePanel: supports Dir/Task grouping, task-group collapse, resolves group titles and badges via task_id/task_title (fallback to contextTask). DiffViewer: accepts tasks, contextTask, changedFiles; builds per-file task mapping, renders requirement IDs, truncated task title and a View Task link in the file header.
Page Integration
web-ui/src/app/review/page.tsx
Fetches tasks via useSWR (/api/v2/tasks?workspace_path=...), derives contextTask when exactly one IN_PROGRESS task exists, passes tasks, contextTask, and changedFiles to child components.
Component Tests
web-ui/__tests__/components/review/FileTreePanel.test.tsx, web-ui/__tests__/components/review/DiffViewer.test.tsx
Added tests covering directory vs task grouping, task toggle, task badges, contextTask behavior, requirement ID display, View Task link behavior, and collapse behavior; includes jsdom shims (ResizeObserver, scrollIntoView).

Sequence Diagram(s)

sequenceDiagram
    participant ReviewPage as ReviewPage
    participant TasksAPI as TasksAPI
    participant FileTreePanel as FileTreePanel
    participant DiffViewer as DiffViewer

    ReviewPage->>TasksAPI: GET /api/v2/tasks?workspace_path=...
    TasksAPI-->>ReviewPage: task list
    ReviewPage->>ReviewPage: derive contextTask (filter IN_PROGRESS)
    ReviewPage->>FileTreePanel: props (files, tasks, contextTask)
    ReviewPage->>DiffViewer: props (diffFiles, changedFiles, tasks, contextTask)
    FileTreePanel->>FileTreePanel: group files by dir or task, resolve titles/badges
    DiffViewer->>DiffViewer: map changedFiles -> task, render requirement ID, task title, View Task link
    FileTreePanel-->>ReviewPage: (UI interactions: group toggle, collapse)
    DiffViewer-->>ReviewPage: (UI interactions: file select)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through diffs with a twitch of my nose,
Tasks snug beside files where context now shows.
Requirements now peep and the sidebar will sing,
Hooray — reviews find purpose in every changed thing! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 40.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately describes the main feature: adding business context (task/requirement) to the diff viewer file tree, matching the substantial changes across FileTreePanel and DiffViewer components.
Linked Issues check ✅ Passed All acceptance criteria from issue #480 are met: task names visible via badges and grouping, requirement IDs shown in diff header, View Task link added, Dir/Task toggle implemented, and multi-task sessions supported via per-file mapping with contextTask fallback.
Out of Scope Changes check ✅ Passed All changes are directly related to #480 objectives. Mock icon exports enable component tests, type extensions support task attribution, and UI changes implement grouping, badges, and task navigation as specified.

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

✨ 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 feature/issue-480-business-context-diff-viewer

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

@claude

claude Bot commented Apr 2, 2026

Copy link
Copy Markdown

PR #522 Code Review: Business Context (Task/Requirement) in Diff Viewer

Overall this is a well-scoped, cleanly implemented feature. The graceful-degradation design is a strong choice, and the test coverage is solid. I have a few issues worth addressing before merge, ranging from a functional bug to some architecture considerations.


Critical / Functional

1. taskMap lookup uses getFilePath key but changedFiles uses raw path — potential mismatch

In DiffViewer.tsx, the taskMap is keyed by f.path from changedFiles (line 88), but fileTask is then looked up with key = getFilePath(file) (line 143), which for renamed files returns newPath. If changedFiles[i].path contains the old path (or a different form than getFilePath returns), all per-file task lookups silently fall back to contextTask. The test fixture avoids this by using src/foo.ts for both, so it would not catch the mismatch.

Recommend verifying what changed_files[].path returns from the backend for renamed files and confirming it always matches getFilePath(diffFile).

2. groupedByTask runs unconditionally even when groupBy === 'dir'

In FileTreePanel.tsx the groupedByTask useMemo runs on every render regardless of the current grouping mode. This iterates all files and builds a Map on every files, tasks, or contextTask change, even when the user is in directory mode and the result is never consumed. For typical review sessions this is harmless, but it is wasted work — computing the task grouping only when groupBy === 'task' (or lazily) would be cleaner.


Important

3. task_id / task_title on FileChange are frontend-only fields with no backend source

The type comment in web-ui/src/types/index.ts reads:

task_id?: string;  // Which task modified this file (optional, for future backend enrichment)

The backend DiffStatsResponse model (codeframe/ui/routers/review_v2.py) does not populate these fields today. As written, the only data path that enriches per-file task context is the changedFiles prop being passed down from the review page — but since the backend never sets task_id, the taskMap built inside DiffViewer will always be empty in production, making the per-file mapping dead code path today.

This is acceptable as forward-looking scaffolding, but it should be documented more explicitly (e.g. a TODO comment linking to the issue that will add backend enrichment), or the acceptance criteria updated to reflect that the per-file task badge currently only works via contextTask fallback, not explicit per-file mapping.

4. onError: () => {} suppresses all task-fetch errors silently

In review/page.tsx (line 71):

{ onError: () => {} }

This intentionally swallows errors, which is good for the "graceful degradation" goal. However, it also suppresses network failures, 5xx responses, and auth errors without any logging. If an engineer later needs to debug why task context is missing, there will be no signal. Consider at minimum onError: (err) => console.debug('[review] task fetch failed silently:', err) to preserve debuggability in development without surfacing UI noise.

5. collapsedDirs state is shared between dir-mode and task-mode keys

FileTreePanel uses a single Set<string> (collapsedDirs) for both directory collapse state (dir) and task collapse state (task:${taskId}). The task: prefix prevents key collisions, which is correct. However, switching modes and back preserves the collapse state of the opposite mode. This could surprise a user who collapses all task groups, switches to dir mode, then back — tasks reappear collapsed. Whether this is intentional UX is worth a comment or the state should be split.


Accessibility

6. "View Task →" arrow is a raw Unicode character with no accessible label

In DiffViewer.tsx (line 195):

<Link href="/tasks" ...>View Task →</Link>

The character is read by screen readers as "right-pointing arrow" or "right arrow". Consider using aria-label="View task details" on the link to give screen reader users cleaner output, or replace the arrow with an icon that has aria-hidden.

7. Dir/Task toggle buttons lack an explicit active/pressed state for assistive technology

The grouping toggle buttons in FileTreePanel rely on visual background color for active state but have no aria-pressed attribute. Adding aria-pressed={groupBy === 'dir'} / aria-pressed={groupBy === 'task'} would make the current mode discoverable for keyboard/screen reader users.


Test Coverage

8. No test covering the taskMap/per-file mapping miss case

There is a test for "renders task chip via per-file changedFiles mapping" but no test for the case where changedFiles contains a task_id that does NOT exist in the tasks array — the path that should gracefully fall back to nothing (not contextTask). Given the lookup logic byId.get(f.task_id) returning undefined, this is worth testing explicitly.

9. FileTreePanel test for contextTask fallback in task mode uses getByRole('button', { name: /group by task/i }) — fragile label match

The aria-label in the production code is "Group by task" (line 133 of FileTreePanel.tsx). The test on line 290 matches /group by task/i which is fine, but an earlier test on line 259 uses /task/i which could match other elements (the task group headers once rendered). This may cause spurious failures if the test environment changes. Consider using getByRole('button', { name: /^task$/i }) for the toggle specifically.


Minor / Nice-to-have

  • The task context area in DiffViewer.tsx truncates the task title to max-w-[120px]. On very narrow viewports or with long task names this could clip the title entirely. A title tooltip attribute on the <span> would help.
  • The groupedByTask useMemo dep array includes tasks and contextTask as objects. Because the review page passes tasksData?.tasks ?? [] as a new array literal on every render, this memo will recompute on every render until memoization is added at the page level. Low priority but worth noting.
  • DiffViewer.tsx imports tasks in the prop signature but the parameter is only used in taskMap. If tasks is empty but contextTask is set, the taskMap memo early-returns an empty map (line 82: if (!tasks?.length) return new Map<string, Task>()). This means a contextTask passed without a tasks list still works correctly via the fallback, which is the right behavior — just worth confirming this is intentional in the case where contextTask came from outside the tasks list.

Summary

Category Count
Critical / Functional 2
Important 3
Accessibility 2
Test coverage 2
Minor 3

The most important issues to address before merge are #1 (potential key mismatch for renamed files) and #3 (clarifying that per-file mapping is forward-scaffolding with no backend source today). Everything else is quality hardening that can be tracked or addressed as follow-up.

Reviewed files:

  • web-ui/src/components/review/DiffViewer.tsx
  • web-ui/src/components/review/FileTreePanel.tsx
  • web-ui/src/app/review/page.tsx
  • web-ui/src/types/index.ts
  • web-ui/__tests__/components/review/DiffViewer.test.tsx
  • web-ui/__tests__/components/review/FileTreePanel.test.tsx

@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.

🧹 Nitpick comments (3)
web-ui/src/components/review/FileTreePanel.tsx (1)

180-184: Consider clarifying badge display behavior for mixed-task scenarios.

When contextTask is set, files without explicit task_title will display the context task's title as a badge. This could be misleading if a file was actually modified by a different task (with task_id set but no task_title).

Consider only showing the badge when the file's attribution is explicitly known:

♻️ Optional refinement
-                             {(file.task_title ?? contextTask?.title) && (
+                             {file.task_title && (
                                <span className="mx-1 shrink-0 rounded bg-muted px-1 py-0.5 text-[10px] text-muted-foreground">
-                                  {file.task_title ?? contextTask?.title}
+                                  {file.task_title}
                                </span>
                              )}

Alternatively, if the intent is to show contextTask for untagged files, consider showing it only when !file.task_id to avoid conflicts.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/components/review/FileTreePanel.tsx` around lines 180 - 184, The
badge currently renders whenever file.task_title or contextTask?.title exists
which can misattribute files that have a task_id but no task_title; update the
render condition so the badge shows file.task_title when present, otherwise show
contextTask?.title only if the file has no task_id (i.e., use file.task_title ??
(!file.task_id ? contextTask?.title : undefined)); apply this change where the
JSX uses (file.task_title ?? contextTask?.title) and keep the same span markup
but guard against showing the context task for files that already have a
task_id.
web-ui/__tests__/components/review/FileTreePanel.test.tsx (1)

67-67: Minor inconsistency in button query selectors.

Lines 67 and 98 query the same "Task" toggle button but use different patterns (/task/i vs /group by task/i). Consider using a consistent selector for clarity:

♻️ Optional refinement for consistency
-    await user.click(screen.getByRole('button', { name: /task/i }));
+    await user.click(screen.getByRole('button', { name: /group by task/i }));

Both work due to how getByRole matches against aria-label and text content, but consistency improves test maintainability.

Also applies to: 98-98

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/__tests__/components/review/FileTreePanel.test.tsx` at line 67, The
test uses two different button name regexes for the same "Task" toggle; make
them consistent by replacing both occurrences of await
user.click(screen.getByRole('button', { name: /task/i })); and await
user.click(screen.getByRole('button', { name: /group by task/i })); with the
same selector (choose one, e.g., await user.click(screen.getByRole('button', {
name: /group by task/i }));) in FileTreePanel.test.tsx so both user.click calls
target the identical getByRole('button', { name: /group by task/i }) matcher for
clarity and maintainability.
web-ui/src/components/review/DiffViewer.tsx (1)

191-197: Consider linking to the specific task detail page.

The "View Task" link currently navigates to the generic /tasks page. Since fileTask has an id property, you could provide a more direct navigation experience:

♻️ Optional enhancement
                    <Link
-                      href="/tasks"
+                      href={`/tasks?highlight=${fileTask.id}`}
                      className="text-primary hover:underline"
                      onClick={(e) => e.stopPropagation()}
                    >
                      View Task →
                    </Link>

Or if the execution page supports task-specific views:

-                      href="/tasks"
+                      href={`/execution/${fileTask.id}`}

This would align better with the PR objective to "navigate to task detail" without requiring the user to find the task manually.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/components/review/DiffViewer.tsx` around lines 191 - 197, The
current Link component in DiffViewer renders a generic href "/tasks"; update it
to navigate to the specific task detail using the fileTask id (e.g., set Link's
href to the route that includes fileTask.id such as `/tasks/${fileTask.id}` or
the app's task-detail route), keeping the existing onClick={(e) =>
e.stopPropagation()} behavior; locate the Link in DiffViewer.tsx and replace the
static "/tasks" href with the task-specific path that uses fileTask.id.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@web-ui/__tests__/components/review/FileTreePanel.test.tsx`:
- Line 67: The test uses two different button name regexes for the same "Task"
toggle; make them consistent by replacing both occurrences of await
user.click(screen.getByRole('button', { name: /task/i })); and await
user.click(screen.getByRole('button', { name: /group by task/i })); with the
same selector (choose one, e.g., await user.click(screen.getByRole('button', {
name: /group by task/i }));) in FileTreePanel.test.tsx so both user.click calls
target the identical getByRole('button', { name: /group by task/i }) matcher for
clarity and maintainability.

In `@web-ui/src/components/review/DiffViewer.tsx`:
- Around line 191-197: The current Link component in DiffViewer renders a
generic href "/tasks"; update it to navigate to the specific task detail using
the fileTask id (e.g., set Link's href to the route that includes fileTask.id
such as `/tasks/${fileTask.id}` or the app's task-detail route), keeping the
existing onClick={(e) => e.stopPropagation()} behavior; locate the Link in
DiffViewer.tsx and replace the static "/tasks" href with the task-specific path
that uses fileTask.id.

In `@web-ui/src/components/review/FileTreePanel.tsx`:
- Around line 180-184: The badge currently renders whenever file.task_title or
contextTask?.title exists which can misattribute files that have a task_id but
no task_title; update the render condition so the badge shows file.task_title
when present, otherwise show contextTask?.title only if the file has no task_id
(i.e., use file.task_title ?? (!file.task_id ? contextTask?.title : undefined));
apply this change where the JSX uses (file.task_title ?? contextTask?.title) and
keep the same span markup but guard against showing the context task for files
that already have a task_id.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7d9224c1-c852-4781-a79e-0c5de6807655

📥 Commits

Reviewing files that changed from the base of the PR and between 10ec571 and e9d2c4e.

📒 Files selected for processing (7)
  • web-ui/__mocks__/@hugeicons/react.js
  • web-ui/__tests__/components/review/DiffViewer.test.tsx
  • web-ui/__tests__/components/review/FileTreePanel.test.tsx
  • web-ui/src/app/review/page.tsx
  • web-ui/src/components/review/DiffViewer.tsx
  • web-ui/src/components/review/FileTreePanel.tsx
  • web-ui/src/types/index.ts

@claude

claude Bot commented Apr 2, 2026

Copy link
Copy Markdown

test comment to verify access

@claude

claude Bot commented Apr 2, 2026

Copy link
Copy Markdown

Review of PR 522 - Dir/Task grouping feature

@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 the current code and only fix it if needed.

Inline comments:
In `@web-ui/src/components/review/FileTreePanel.tsx`:
- Around line 57-75: grouping currently computes a group title on first insert
using different fallbacks per mode which causes mislabeling; change
groupedByTask to compute a single deterministic resolved title per file before
inserting: for each file derive taskId = file.task_id ?? contextTask?.id ??
'unassigned' and resolve taskTitle = file.task_title ??
taskTitleById.get(file.task_id) ?? (file.task_id ? undefined :
contextTask?.title) ?? 'Unassigned' (i.e. prefer embedded title, then tasks
lookup by file.task_id, then contextTask only when file has no task_id), then
use that resolved title when creating or merging into groups and if merging into
an existing group update its title when the existing title is a weaker
placeholder (like 'Unassigned') so later files with real titles correct the
header; update references to taskTitleById, groups, files, tasks, contextTask,
file.task_id, and file.task_title accordingly.
- Line 218: The span rendering the task marker in FileTreePanel.tsx currently
hard-codes the color class "bg-amber-500"; replace that hard-coded palette with
the Nova/Shadcn semantic token or variant used across the app (e.g., the
project's Nova token such as "bg-primary" or the appropriate shadcn variant
class) so the marker follows the configured gray theme; update the span with the
semantic class and ensure any helper/utility (e.g., cn usage) is applied
consistently instead of the literal "bg-amber-500".
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cdb45244-1981-4019-ac44-42b1db03ab64

📥 Commits

Reviewing files that changed from the base of the PR and between e9d2c4e and 2e7d695.

📒 Files selected for processing (1)
  • web-ui/src/components/review/FileTreePanel.tsx

Comment on lines +57 to +75
const groupedByTask = useMemo(() => {
const taskTitleById = new Map(tasks?.map((t) => [t.id, t.title]) ?? []);
const groups = new Map<string, { title: string; files: FileChange[] }>();
for (const file of files) {
const taskId = file.task_id ?? contextTask?.id ?? 'unassigned';
const taskTitle =
file.task_title ??
taskTitleById.get(taskId) ??
contextTask?.title ??
'Unassigned';
const existing = groups.get(taskId);
if (existing) {
existing.files.push(file);
} else {
groups.set(taskId, { title: taskTitle, files: [file] });
}
}
return groups;
}, [files, tasks, contextTask]);

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.

⚠️ Potential issue | 🟠 Major

Unify task-title resolution; current fallbacks can mislabel or hide the task context.

In task mode, a file with an explicit task_id can still inherit contextTask?.title when its title is missing, so task B can render under task A’s name. In dir mode, the opposite happens: files with task_id but no embedded task_title never consult tasks, so the badge disappears even when the title is available. Because the group title is fixed on first insert, a later file with the real title also will not correct the header.

Suggested fix
+  const taskTitleById = useMemo(
+    () => new Map(tasks?.map((task) => [task.id, task.title]) ?? []),
+    [tasks]
+  );
+
   const groupedByTask = useMemo(() => {
-    const taskTitleById = new Map(tasks?.map((t) => [t.id, t.title]) ?? []);
     const groups = new Map<string, { title: string; files: FileChange[] }>();
     for (const file of files) {
       const taskId = file.task_id ?? contextTask?.id ?? 'unassigned';
       const taskTitle =
         file.task_title ??
-        taskTitleById.get(taskId) ??
-        contextTask?.title ??
+        (file.task_id ? taskTitleById.get(file.task_id) : contextTask?.title) ??
         'Unassigned';
       const existing = groups.get(taskId);
       if (existing) {
+        if (existing.title === 'Unassigned' && taskTitle !== 'Unassigned') {
+          existing.title = taskTitle;
+        }
         existing.files.push(file);
       } else {
         groups.set(taskId, { title: taskTitle, files: [file] });
       }
     }
     return groups;
-  }, [files, tasks, contextTask]);
+  }, [files, taskTitleById, contextTask]);
@@
                         {dirFiles.map((file) => {
                           const Icon = changeTypeIcon[file.change_type];
                           const iconColor = changeTypeColor[file.change_type];
                           const isSelected = selectedFile === file.path;
+                          const taskTitle =
+                            file.task_title ??
+                            (file.task_id ? taskTitleById.get(file.task_id) : contextTask?.title);
 
                           return (
                             <button
@@
-                              {(file.task_title ?? (!file.task_id ? contextTask?.title : undefined)) && (
+                              {taskTitle && (
                                 <span className="mx-1 shrink-0 rounded bg-muted px-1 py-0.5 text-[10px] text-muted-foreground">
-                                  {file.task_title ?? contextTask?.title}
+                                  {taskTitle}
                                 </span>
                               )}

Also applies to: 180-183

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/components/review/FileTreePanel.tsx` around lines 57 - 75,
grouping currently computes a group title on first insert using different
fallbacks per mode which causes mislabeling; change groupedByTask to compute a
single deterministic resolved title per file before inserting: for each file
derive taskId = file.task_id ?? contextTask?.id ?? 'unassigned' and resolve
taskTitle = file.task_title ?? taskTitleById.get(file.task_id) ?? (file.task_id
? undefined : contextTask?.title) ?? 'Unassigned' (i.e. prefer embedded title,
then tasks lookup by file.task_id, then contextTask only when file has no
task_id), then use that resolved title when creating or merging into groups and
if merging into an existing group update its title when the existing title is a
weaker placeholder (like 'Unassigned') so later files with real titles correct
the header; update references to taskTitleById, groups, files, tasks,
contextTask, file.task_id, and file.task_title accordingly.

) : (
<ArrowDown01Icon className="h-3 w-3 shrink-0" />
)}
<span className="h-2 w-2 rounded-full bg-amber-500" />

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.

⚠️ Potential issue | 🟡 Minor

Use a theme token instead of bg-amber-500 for the task marker.

This hard-codes a non-gray palette color into the tree and bypasses the Nova theme tokens. Prefer a semantic token or Shadcn variant so it stays aligned with the configured color scheme.

As per coding guidelines, "Web UI must use Shadcn/UI (Nova preset) with gray color scheme and Hugeicons (@hugeicons/react); never use lucide-react".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web-ui/src/components/review/FileTreePanel.tsx` at line 218, The span
rendering the task marker in FileTreePanel.tsx currently hard-codes the color
class "bg-amber-500"; replace that hard-coded palette with the Nova/Shadcn
semantic token or variant used across the app (e.g., the project's Nova token
such as "bg-primary" or the appropriate shadcn variant class) so the marker
follows the configured gray theme; update the span with the semantic class and
ensure any helper/utility (e.g., cn usage) is applied consistently instead of
the literal "bg-amber-500".

@claude

claude Bot commented Apr 2, 2026

Copy link
Copy Markdown

(See the full formatted review in the comment above.)

@claude

claude Bot commented Apr 2, 2026

Copy link
Copy Markdown

(test cleanup)

@claude

claude Bot commented Apr 2, 2026

Copy link
Copy Markdown

Review

Good feature work. The Dir/Task grouping toggle, per-file task badges, and graceful degradation pattern are solid design choices. CodeRabbit covered the key nitpicks. A few additional observations:

Bug: Shared collapsedDirs state across view modes
FileTreePanel uses one collapsedDirs map for both dir and task mode. Toggling between modes preserves stale collapsed keys from the previous mode. Fix: reset collapsedDirs to empty when groupByTask changes, or maintain two separate maps.

Bug: contextTask badge misattribution (echoing CodeRabbit)
A file with an explicit task_id but no task_title will show the context task title as the badge label — wrong attribution. Correct condition: file.task_title ?? (!file.task_id ? contextTask?.title : undefined)

UX: View Task link target (echoing CodeRabbit)
The link goes to /tasks (list) rather than the specific task. If a task detail route exists, use it. If not, a TODO comment noting the known limitation would help.

Silent error swallowing
onError: () => {} on the tasks SWR fetch is invisible in development. Consider logging in non-production environments so task fetch failures surface during debugging.

Backend readiness gap
FileChange.task_id and task_title are in types/index.ts but the backend diff endpoint does not populate them yet. The taskMap in DiffViewer will be effectively empty at runtime until that changes. A TODO comment near the taskMap memo would help future contributors.

Tests — Coverage is thorough across both components. No gaps beyond the selector inconsistency CodeRabbit flagged.

Summary: The collapsed state bleed and badge misattribution bugs plus the /tasks link precision are worth addressing before merge. Silent error handler and TODO comment are lower priority. Overall a clean implementation.

@frankbria frankbria merged commit 2b81a71 into main Apr 2, 2026
15 checks passed
@frankbria frankbria deleted the feature/issue-480-business-context-diff-viewer branch April 2, 2026 19:46
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.

UX: Add business context (task/requirement) to diff viewer file tree

1 participant