Skip to content

Conversation

@webdevcody
Copy link
Collaborator

@webdevcody webdevcody commented Dec 20, 2025

Summary by CodeRabbit

  • New Features

    • Redesigned Kanban cards with modular UI: agent progress panel, dynamic action buttons, priority/status badges, content sections, and an implementation summary dialog; improved drag/visual states.
  • Bug Fixes

    • Removed commit handler from in-progress task cards.
  • Refactor

    • Reorganized card internals into smaller components for maintainability.
  • Tests

    • Updated integration test to expect a "Mark as Verified" button instead of "Commit".

✏️ Tip: You can customize this high-level summary in your review settings.

Test User added 2 commits December 20, 2025 12:28
…and functionality

- Moved KanbanCard logic into separate files for better modularity, including card actions, badges, content sections, and agent info panel.
- Updated import paths to reflect new file structure.
- Enhanced readability and maintainability of the KanbanCard component by breaking it down into smaller, focused components.
- Removed the old KanbanCard implementation and replaced it with a new, organized structure that supports better code management.
…onents

- Removed the onCommit action from KanbanBoard and related components to streamline functionality.
- Updated CardActions to replace the Commit button with a Mark as Verified button, enhancing clarity in user interactions.
- Introduced a new CardBadge component for consistent styling of badges across KanbanCard, improving code reusability and maintainability.
- Refactored badge rendering logic to include a Just Finished badge, ensuring accurate representation of feature status.
@coderabbitai
Copy link

coderabbitai bot commented Dec 20, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

The monolithic KanbanCard component was removed and replaced by a new kanban-card/ directory containing a reorganized KanbanCard plus six specialized subcomponents and a re-export update in the public index. One per-card handler (onCommit) was un-wired in the board integration.

Changes

Cohort / File(s) Summary
Public re-export
apps/ui/src/components/views/board-view/components/index.ts
Updated KanbanCard re-export to use the new path ./kanban-card/kanban-card.
Monolithic component removed
apps/ui/src/components/views/board-view/components/kanban-card.tsx
Deleted the original monolithic KanbanCard and its props/types.
Main component
apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx
Added new memoized KanbanCard implementing drag-and-drop, styling, and composing the new subcomponents; accepts many per-card handlers and UI/config props.
Header
apps/ui/src/components/views/board-view/components/kanban-card/card-header.tsx
New CardHeaderSection: title/description rendering, delete confirmation, model info, and action menu.
Content sections
apps/ui/src/components/views/board-view/components/kanban-card/card-content-sections.tsx
New CardContentSections: branch/PR display and steps preview.
Badges
apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx
New CardBadges and PriorityBadges: error/blocked/priority/manual/just-finished badges with live updates.
Actions
apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx
New CardActions: state-driven action buttons (edit, view logs, resume, verify, approve plan, make, complete, etc.) with handler wiring.
Agent panel
apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx
New AgentInfoPanel: loads/parses agent output (via Electron API or file), shows model, phase badge, todo list, summary, and supports auto-refresh for current auto tasks.
Summary modal
apps/ui/src/components/views/board-view/components/kanban-card/summary-dialog.tsx
New SummaryDialog: modal rendering markdown summary with prioritized fallbacks and truncation.
Board integration change
apps/ui/src/components/views/board-view/kanban-board.tsx
Removed wiring that invoked onCommit from KanbanCard instances in the in-progress column (prop remains declared but not passed).
Tests
apps/ui/tests/worktree-integration.spec.ts
Updated test case names and expectations: replaced checks for a "Commit" button with "Mark as Verified", adjusted created feature description and assertions accordingly.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–60 minutes

  • Files needing extra attention:
    • apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx — Electron API/file read logic, parsing, auto-refresh effects, and error handling.
    • apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx — drag-and-drop integration (useSortable), dynamic styling and prop wiring to subcomponents.
    • apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx — multi-branch conditional rendering across feature states and many handlers.
  • Also verify: prop/signature compatibility for consumers (index re-export change) and test updates aligning with UI action changes.

Possibly related PRs

Suggested labels

Refactor, Tests

Poem

🐰 Hoppity hop, the card got split,
Badges shine and headers fit,
Agent notes and actions sing,
A tidier Kanban — what a spring! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Refactor kanban cards' accurately captures the main change: decomposing a monolithic KanbanCard component into modular subcomponents (CardBadges, CardActions, CardHeaderSection, CardContentSections, AgentInfoPanel, SummaryDialog).
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor-kanban-cards

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7232745 and 92e7945.

📒 Files selected for processing (1)
  • apps/ui/tests/worktree-integration.spec.ts (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e
  • GitHub Check: claude-review
🔇 Additional comments (2)
apps/ui/tests/worktree-integration.spec.ts (2)

2860-2870: LGTM! Test naming and description updated to reflect UI changes.

The test name and feature description have been properly updated to reflect the new "Mark as Verified" button terminology, aligning with the kanban card refactoring. The changes maintain consistency between the test name and the test data.


2883-2883: LGTM! Button selector and assertions properly updated.

The test correctly verifies the presence of the "Mark as Verified" button (via data-testid="mark-as-verified-${featureData.id}") for features in waiting_approval status without a prUrl. The negative assertion confirming the "Verify" button is not visible maintains the proper distinction between the two scenarios (with/without PR URL).

Also applies to: 2911-2918


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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @webdevcody, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the KanbanCard component by decomposing it into a set of smaller, more focused sub-components. This architectural change aims to enhance the maintainability, readability, and testability of the Kanban card's UI logic, making it easier to develop and manage future features or modifications.

Highlights

  • Component Refactoring: The monolithic KanbanCard component has been broken down into several smaller, specialized components, improving modularity and maintainability.
  • New Component Structure: A new directory kanban-card now houses dedicated components such as AgentInfoPanel, CardActions, CardBadges, CardContentSections, CardHeaderSection, KanbanCard (the new wrapper), and SummaryDialog.
  • Improved Code Organization: Each new component is responsible for a specific part of the Kanban card's UI or logic, making the codebase easier to understand, test, and manage.
  • Removed onCommit Prop: The onCommit prop was removed from the KanbanCard component in kanban-board.tsx, suggesting a change in how commit actions are handled or triggered.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request is a great refactoring of the massive KanbanCard component into smaller, more manageable sub-components. This significantly improves the readability and maintainability of the code.

I've noticed one functional change alongside the refactoring: the 'Commit' button on cards in the 'Waiting Approval' column has been replaced with a 'Mark as Verified' button. This seems intentional as the onCommit prop has been removed. It would be good to mention this in the pull request description if it's not already covered elsewhere.

I have a few minor suggestions to further improve the code quality.

Comment on lines +73 to +74
// eslint-disable-next-line @typescript-eslint/no-explicit-any, no-undef
const currentProject = (window as any).__currentProject;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

I've noticed several eslint-disable-next-line no-undef comments for standard browser globals like window, console, setInterval, and clearInterval in this file and card-badges.tsx. This suggests a potential misconfiguration in your ESLint setup. Instead of disabling the rule, it would be better to configure the ESLint environment to recognize browser globals. You can typically do this by adding env: { browser: true } to your ESLint configuration file (e.g., .eslintrc.js). This will make the code cleaner and ensure the linter works as expected for a web project.

Comment on lines +169 to +172
// eslint-disable-next-line no-undef
const interval = setInterval(() => {
setCurrentTime(Date.now());
}, 1000);
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

Similar to agent-info-panel.tsx, there are eslint-disable-next-line no-undef comments here for setInterval and clearInterval. Please see my other comment about configuring the ESLint environment to avoid disabling this rule for standard browser globals.

onResume,
onForceStop,
onManualVerify,
onMoveBackToInProgress: _onMoveBackToInProgress,
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The onMoveBackToInProgress prop is destructured with an underscore, indicating it's unused within this component. It seems this prop is passed down through KanbanBoard but isn't actually used by any of the card's actions. This might be an opportunity to remove this prop entirely to clean up the component's API, unless it's planned for future use.

@claude
Copy link

claude bot commented Dec 20, 2025

Code Review: Refactor Kanban Cards

Summary

This is an excellent refactoring that breaks down a monolithic 1332-line component into smaller, focused modules. The changes significantly improve maintainability and code organization. Overall, this is a well-executed refactor that follows clean code principles.


✅ Strengths

1. Excellent Component Decomposition

  • Successfully split a massive 1332-line file into 7 focused components
  • Each new component has a single, clear responsibility:
    • kanban-card.tsx (214 lines) - Main orchestrator
    • card-header.tsx (330 lines) - Header logic and actions
    • card-actions.tsx (337 lines) - Button actions by status
    • card-badges.tsx (266 lines) - Badge display logic
    • card-content-sections.tsx (82 lines) - Content sections
    • agent-info-panel.tsx (283 lines) - Agent information display
    • summary-dialog.tsx (75 lines) - Summary modal

2. Improved Maintainability

  • Each component is now easier to understand, test, and modify
  • Clear separation of concerns makes the codebase more navigable
  • Follows Single Responsibility Principle effectively

3. Preserved Functionality

  • All props are properly passed through
  • State management is appropriately distributed
  • Event handlers maintain proper event propagation control
  • Test IDs are preserved for testing

4. Good Abstraction Choices

  • Shared utilities like formatThinkingLevel() are properly extracted
  • Reusable CardBadge component for consistent badge styling
  • Clean interface definitions for all component props

🔍 Issues & Recommendations

1. Unused Prop - Missing onCommit Callback ⚠️

Location: kanban-card.tsx:48

The onMoveBackToInProgress prop is prefixed with _ indicating it's unused:

onMoveBackToInProgress: _onMoveBackToInProgress,

However, onCommit is accepted as a prop but never passed to child components and appears unused.

Impact: Dead code, potential confusion

Recommendation:

  • Remove onCommit prop if it's truly unused
  • Or if it should be used in CardActions, wire it up properly

2. Inconsistent ESLint Disable Comments ⚠️

Location: card-badges.tsx:169, 175-176 and agent-info-panel.tsx:73, 97-98, 105-106, 108-109

Multiple instances of:

// eslint-disable-next-line no-undef
const interval = setInterval(() => {
  setCurrentTime(Date.now());
}, 1000);

return () => {
  // eslint-disable-next-line no-undef
  clearInterval(interval);
};

Issues:

  1. setInterval and clearInterval are not undefined in browser/Node.js environments
  2. If TypeScript is complaining, the proper fix is to update tsconfig.json or add type declarations
  3. Disabling no-undef for standard browser APIs is a code smell
  4. The original file didn't have these eslint-disable comments

Recommendation:

// Remove the eslint-disable comments entirely
const interval = setInterval(() => {
  setCurrentTime(Date.now());
}, 1000);

return () => clearInterval(interval);

If there's a real linting issue, fix the root cause in configuration rather than suppressing warnings.


3. Commented-out Type Assertion

Location: card-header.tsx:28

import { formatModelName, DEFAULT_MODEL } from "@/lib/agent-context-parser";

The original file had inline comments about // suggesting there might have been typing issues. The code looks clean now, but verify TypeScript compiles without errors.


4. Missing Test Coverage 🧪

Observation: No test files found for the refactored components

Recommendation:

  • Since this is a refactor (not new functionality), existing integration tests should still pass
  • Consider adding unit tests for the new smaller components since they're now easier to test
  • At minimum, add smoke tests for each new component

5. Potential Performance Optimization

Location: agent-info-panel.tsx:104-111

The useEffect creates a polling interval (3s) when isCurrentAutoTask is true. This is fine, but consider:

  • Using a larger interval (5-10s) to reduce unnecessary API calls
  • Implementing WebSocket-based updates for real-time data
  • Adding cleanup to prevent memory leaks if component unmounts during async operations

Current:

if (isCurrentAutoTask) {
  const interval = setInterval(loadContext, 3000); // Every 3 seconds
  return () => clearInterval(interval);
}

Suggested:

if (isCurrentAutoTask) {
  const interval = setInterval(loadContext, 5000); // Every 5 seconds
  return () => clearInterval(interval);
}

6. Magic Numbers Still Present

Location: Multiple files

While the refactor is excellent, there are still some magic numbers that could be extracted per the clean-code.md guidelines:

// card-badges.tsx:163
const twoMinutes = 2 * 60 * 1000; // Good!

// agent-info-panel.tsx:104
const interval = setInterval(loadContext, 3000); // Could be POLLING_INTERVAL_MS

// card-content-sections.tsx:57
{feature.steps.slice(0, 3).map(...)} // Could be MAX_VISIBLE_STEPS

Recommendation:

const POLLING_INTERVAL_MS = 3000;
const MAX_VISIBLE_STEPS = 3;
const JUST_FINISHED_WINDOW_MS = 2 * 60 * 1000;

7. Minor: Inconsistent Border Styling Logic

Location: kanban-card.tsx:94-101

The border styling logic uses type assertions that could be cleaner:

if (!cardBorderEnabled) {
  (borderStyle as Record<string, string>).borderWidth = "0px";
  (borderStyle as Record<string, string>).borderColor = "transparent";
}

Recommendation:

if (!cardBorderEnabled) {
  borderStyle.borderWidth = "0px";
  borderStyle.borderColor = "transparent";
}

Or use a cleaner object spread:

const borderStyle: React.CSSProperties = {
  ...style,
  ...((!cardBorderEnabled) && { borderWidth: "0px", borderColor: "transparent" }),
  ...((cardBorderEnabled && cardBorderOpacity !== 100) && { 
    borderWidth: "1px", 
    borderColor: `color-mix(in oklch, var(--border) ${cardBorderOpacity}%, transparent)` 
  })
};

🔒 Security

✅ No security concerns identified

  • Proper event propagation control (stopPropagation, onPointerDown)
  • Safe URL validation with regex for PR URLs
  • No XSS vulnerabilities in dynamic content

🎨 Code Quality

Overall Score: 9/10

Pros:

  • Excellent separation of concerns
  • Clean interfaces
  • Proper TypeScript usage
  • Good naming conventions
  • Preserved test IDs

Cons:

  • Unnecessary ESLint disables
  • Minor unused props
  • Some magic numbers could be extracted

📝 Summary

This refactor is approved with minor suggestions. The component decomposition is well-thought-out and significantly improves the codebase. Address the ESLint disable comments and unused props for a perfect refactor.

Suggested Actions Before Merge:

  1. ✅ Remove unnecessary eslint-disable-next-line no-undef comments
  2. ✅ Remove unused onCommit prop (or implement if needed)
  3. ⚠️ Verify all TypeScript compilation passes
  4. 🧪 Run existing tests to ensure no regressions

Great work! This is exactly the kind of refactoring that makes codebases more maintainable. 🎉

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (12)
apps/ui/src/components/views/board-view/components/kanban-card/card-header.tsx (4)

51-58: Consider simplifying the passthrough function.

handleConfirmDelete is a passthrough that adds no logic. While it's fine for future extensibility, you could pass onDelete directly to DeleteConfirmDialog.

🔎 Suggested simplification
-  const handleConfirmDelete = () => {
-    onDelete();
-  };
...
       <DeleteConfirmDialog
         open={isDeleteDialogOpen}
         onOpenChange={setIsDeleteDialogOpen}
-        onConfirm={handleConfirmDelete}
+        onConfirm={onDelete}

129-190: Redundant Fragment wrapper.

The Fragment (<>...</>) wrapping a single child element (<div>) is unnecessary.

🔎 Suggested fix
       {!isCurrentAutoTask &&
         (feature.status === "waiting_approval" ||
           feature.status === "verified") && (
-          <>
             <div className="absolute top-2 right-2 flex items-center gap-1">
               {/* ... buttons ... */}
             </div>
-          </>
         )}

192-258: Redundant Fragment wrapper in "In progress" header block.

Same issue as the waiting_approval/verified section - the Fragment is not needed around a single <div>.


291-313: Consider using optional chaining for safer length check.

If both feature.description and feature.summary are undefined, the current expression (feature.description || feature.summary || "").length works, but it's slightly inconsistent with how Line 289 displays the content (which also falls back to feature.id).

🔎 Suggested improvement for consistency
-          {(feature.description || feature.summary || "").length > 100 && (
+          {((feature.description || feature.summary)?.length ?? 0) > 100 && (
apps/ui/src/components/views/board-view/components/kanban-card/card-content-sections.tsx (2)

27-52: PR number extraction may produce misleading results for non-standard URLs.

The logic feature.prUrl.split("/").pop() extracts the last path segment. For standard GitHub PR URLs like https://github.com/org/repo/pull/123, this works. However, for URLs with trailing slashes or query parameters, it may produce unexpected results (empty string or query string).

🔎 More robust extraction
       {typeof feature.prUrl === "string" &&
         /^https?:\/\//i.test(feature.prUrl) &&
         (() => {
-          const prNumber = feature.prUrl.split("/").pop();
+          const match = feature.prUrl.match(/\/pull\/(\d+)/);
+          const prNumber = match?.[1];
           return (
             // ...
                 <span className="truncate max-w-[150px]">
-                  {prNumber ? `Pull Request #${prNumber}` : "Pull Request"}
+                  {prNumber ? `Pull Request #${prNumber}` : "View Pull Request"}
                 </span>

57-71: Consider using stable keys for steps list.

Using array index as a key (key={index}) can cause issues if steps are reordered. If steps have unique identifiers or are immutable during render, this is acceptable.

apps/ui/src/components/views/board-view/components/kanban-card/summary-dialog.tsx (2)

23-29: Consider whether feature prop is needed or if specific fields suffice.

The component uses only feature.id, feature.description, and feature.summary. Passing the entire Feature object works, but passing specific fields would make the component's dependencies more explicit and potentially more reusable.


45-51: IIFE pattern for truncation is fine but could be extracted.

This truncation logic is duplicated conceptually. Consider a utility function like truncateText(text, maxLength) if used elsewhere.

apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx (1)

169-176: ESLint disable comments suggest environment configuration issue.

The no-undef warnings for setInterval/clearInterval indicate the ESLint environment doesn't recognize browser globals. Consider adding browser: true to your ESLint env config rather than disabling per-line.

apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx (1)

15-31: Large number of optional props may indicate need for composition.

The interface has 11 optional handler props. While this works, it could be simplified by grouping related actions or using a discriminated union based on feature status.

apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx (2)

48-48: Unused prop onMoveBackToInProgress.

The prop is received but renamed with underscore prefix and never used in the component. Either remove it from the interface and destructuring, or implement the intended functionality.

🔎 Proposed fix: Remove unused prop
   onFollowUp,
   onImplement,
   onComplete,
   onViewPlan,
   onApprovePlan,
-  onMoveBackToInProgress: _onMoveBackToInProgress,
   hasContext,

Also remove from the interface (line 22):

-  onMoveBackToInProgress?: () => void;

93-101: Consider typed style object instead of type assertions.

The as Record<string, string> casts work but are verbose. A cleaner approach would be to build the style object with proper typing from the start.

🔎 Proposed refactor
-  const borderStyle: React.CSSProperties = { ...style };
-  if (!cardBorderEnabled) {
-    (borderStyle as Record<string, string>).borderWidth = "0px";
-    (borderStyle as Record<string, string>).borderColor = "transparent";
-  } else if (cardBorderOpacity !== 100) {
-    (borderStyle as Record<string, string>).borderWidth = "1px";
-    (borderStyle as Record<string, string>).borderColor =
-      `color-mix(in oklch, var(--border) ${cardBorderOpacity}%, transparent)`;
-  }
+  const borderStyle: React.CSSProperties = {
+    ...style,
+    ...(!cardBorderEnabled && {
+      borderWidth: "0px",
+      borderColor: "transparent",
+    }),
+    ...(cardBorderEnabled && cardBorderOpacity !== 100 && {
+      borderWidth: "1px",
+      borderColor: `color-mix(in oklch, var(--border) ${cardBorderOpacity}%, transparent)`,
+    }),
+  };
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bcd87cc and 7232745.

📒 Files selected for processing (10)
  • apps/ui/src/components/views/board-view/components/index.ts (1 hunks)
  • apps/ui/src/components/views/board-view/components/kanban-card.tsx (0 hunks)
  • apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx (1 hunks)
  • apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx (1 hunks)
  • apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx (1 hunks)
  • apps/ui/src/components/views/board-view/components/kanban-card/card-content-sections.tsx (1 hunks)
  • apps/ui/src/components/views/board-view/components/kanban-card/card-header.tsx (1 hunks)
  • apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx (1 hunks)
  • apps/ui/src/components/views/board-view/components/kanban-card/summary-dialog.tsx (1 hunks)
  • apps/ui/src/components/views/board-view/kanban-board.tsx (0 hunks)
💤 Files with no reviewable changes (2)
  • apps/ui/src/components/views/board-view/kanban-board.tsx
  • apps/ui/src/components/views/board-view/components/kanban-card.tsx
🧰 Additional context used
🧬 Code graph analysis (3)
apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx (7)
apps/ui/src/components/views/board-view/components/index.ts (1)
  • KanbanCard (1-1)
apps/ui/src/store/app-store.ts (1)
  • useAppStore (853-2358)
apps/ui/src/components/ui/card.tsx (2)
  • Card (94-94)
  • CardContent (100-100)
apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx (1)
  • CardBadges (48-134)
apps/ui/src/components/views/board-view/components/kanban-card/card-header.tsx (1)
  • CardHeaderSection (40-329)
apps/ui/src/components/views/board-view/components/kanban-card/card-content-sections.tsx (1)
  • CardContentSections (10-81)
apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx (1)
  • AgentInfoPanel (46-283)
apps/ui/src/components/views/board-view/components/kanban-card/card-header.tsx (1)
apps/ui/src/components/ui/card.tsx (3)
  • CardHeader (95-95)
  • CardTitle (97-97)
  • CardDescription (99-99)
apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx (1)
apps/server/src/lib/dependency-resolver.ts (1)
  • getBlockingDependencies (209-221)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e
  • GitHub Check: claude-review
🔇 Additional comments (15)
apps/ui/src/components/views/board-view/components/kanban-card/card-header.tsx (2)

1-38: LGTM - Clean imports and well-defined interface.

The imports are organized and the CardHeaderProps interface clearly defines the component's contract with appropriate optional callback for onViewOutput.


283-290: Good defensive fallback for description display.

Using feature.description || feature.summary || feature.id ensures a value is always rendered, which is good UX. The ID fallback is a reasonable last resort for debugging.

apps/ui/src/components/views/board-view/components/index.ts (1)

1-2: LGTM - Export path correctly updated.

The barrel export maintains the public API while pointing to the new nested directory structure. Consumers importing KanbanCard from this index won't need changes.

apps/ui/src/components/views/board-view/components/kanban-card/card-content-sections.tsx (1)

10-81: LGTM - Clean component structure with good conditional rendering.

The component properly handles optional sections and uses appropriate accessibility attributes on links.

apps/ui/src/components/views/board-view/components/kanban-card/summary-dialog.tsx (1)

30-74: LGTM - Well-structured dialog component.

The dialog properly manages open state, has good accessibility with semantic Dialog components, and provides clear user feedback with the Close button.

apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx (2)

48-57: LGTM - Proper memoization of blocking dependencies.

The useMemo correctly depends on enableDependencyBlocking, feature, and features, ensuring recalculation only when necessary.


196-221: Priority badge rendering logic is clear and well-structured.

The conditional styling based on priority levels (1=High, 2=Medium, 3=Low) is easy to follow with appropriate visual differentiation.

apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx (2)

50-111: LGTM - Well-structured action buttons for running tasks.

The priority ordering (Approve Plan first when available) and proper event handling make this section clear and functional.


285-334: LGTM - Backlog actions are comprehensive.

The Edit, View Plan, and Make actions cover the expected backlog workflow with appropriate conditional rendering.

apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx (3)

58-112: LGTM on useEffect structure.

The polling interval setup and cleanup logic is correctly implemented. The interval is only created when isCurrentAutoTask is true, and the cleanup properly clears it when the effect re-runs or the component unmounts.


27-37: LGTM!

Clean helper function with correct type handling.


114-135: LGTM!

The backlog rendering branch correctly displays model configuration without the summary dialog, which is appropriate since backlog items haven't been processed yet.

apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx (3)

70-91: LGTM!

The draggable state logic and useSortable configuration are correctly implemented. Cards are appropriately non-draggable when they're the current auto task (in_progress).


103-206: LGTM!

Well-structured composition of subcomponents with clean separation of concerns. The conditional spreading of drag listeners on line 134 correctly prevents drag interactions when the card should not be draggable.


208-214: LGTM!

Clean conditional wrapper pattern for the animated border effect on active tasks.

Comment on lines +138 to +266
if (showAgentInfo && feature.status !== "backlog" && agentInfo) {
return (
<div className="mb-3 space-y-2 overflow-hidden">
{/* Model & Phase */}
<div className="flex items-center gap-2 text-[11px] flex-wrap">
<div className="flex items-center gap-1 text-[var(--status-info)]">
<Cpu className="w-3 h-3" />
<span className="font-medium">
{formatModelName(feature.model ?? DEFAULT_MODEL)}
</span>
</div>
{agentInfo.currentPhase && (
<div
className={cn(
"px-1.5 py-0.5 rounded-md text-[10px] font-medium",
agentInfo.currentPhase === "planning" &&
"bg-[var(--status-info-bg)] text-[var(--status-info)]",
agentInfo.currentPhase === "action" &&
"bg-[var(--status-warning-bg)] text-[var(--status-warning)]",
agentInfo.currentPhase === "verification" &&
"bg-[var(--status-success-bg)] text-[var(--status-success)]"
)}
>
{agentInfo.currentPhase}
</div>
)}
</div>

{/* Task List Progress */}
{agentInfo.todos.length > 0 && (
<div className="space-y-1">
<div className="flex items-center gap-1 text-[10px] text-muted-foreground/70">
<ListTodo className="w-3 h-3" />
<span>
{agentInfo.todos.filter((t) => t.status === "completed").length}
/{agentInfo.todos.length} tasks
</span>
</div>
<div className="space-y-0.5 max-h-16 overflow-y-auto">
{agentInfo.todos.slice(0, 3).map((todo, idx) => (
<div
key={idx}
className="flex items-center gap-1.5 text-[10px]"
>
{todo.status === "completed" ? (
<CheckCircle2 className="w-2.5 h-2.5 text-[var(--status-success)] shrink-0" />
) : todo.status === "in_progress" ? (
<Loader2 className="w-2.5 h-2.5 text-[var(--status-warning)] animate-spin shrink-0" />
) : (
<Circle className="w-2.5 h-2.5 text-muted-foreground/50 shrink-0" />
)}
<span
className={cn(
"break-words hyphens-auto line-clamp-2 leading-relaxed",
todo.status === "completed" &&
"text-muted-foreground/60 line-through",
todo.status === "in_progress" &&
"text-[var(--status-warning)]",
todo.status === "pending" && "text-muted-foreground/80"
)}
>
{todo.content}
</span>
</div>
))}
{agentInfo.todos.length > 3 && (
<p className="text-[10px] text-muted-foreground/60 pl-4">
+{agentInfo.todos.length - 3} more
</p>
)}
</div>
</div>
)}

{/* Summary for waiting_approval and verified */}
{(feature.status === "waiting_approval" ||
feature.status === "verified") && (
<>
{(feature.summary || summary || agentInfo.summary) && (
<div className="space-y-1.5 pt-2 border-t border-border/30 overflow-hidden">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-1 text-[10px] text-[var(--status-success)] min-w-0">
<Sparkles className="w-3 h-3 shrink-0" />
<span className="truncate font-medium">Summary</span>
</div>
<button
onClick={(e) => {
e.stopPropagation();
setIsSummaryDialogOpen(true);
}}
onPointerDown={(e) => e.stopPropagation()}
className="p-0.5 rounded-md hover:bg-muted/80 transition-colors text-muted-foreground/60 hover:text-muted-foreground shrink-0"
title="View full summary"
data-testid={`expand-summary-${feature.id}`}
>
<Expand className="w-3 h-3" />
</button>
</div>
<p className="text-[10px] text-muted-foreground/70 line-clamp-3 break-words hyphens-auto leading-relaxed overflow-hidden">
{feature.summary || summary || agentInfo.summary}
</p>
</div>
)}
{!feature.summary &&
!summary &&
!agentInfo.summary &&
agentInfo.toolCallCount > 0 && (
<div className="flex items-center gap-2 text-[10px] text-muted-foreground/60 pt-2 border-t border-border/30">
<span className="flex items-center gap-1">
<Wrench className="w-2.5 h-2.5" />
{agentInfo.toolCallCount} tool calls
</span>
{agentInfo.todos.length > 0 && (
<span className="flex items-center gap-1">
<CheckCircle2 className="w-2.5 h-2.5 text-[var(--status-success)]" />
{
agentInfo.todos.filter((t) => t.status === "completed")
.length
}{" "}
tasks done
</span>
)}
</div>
)}
</>
)}
</div>
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

SummaryDialog is not rendered when the expand button is present.

When this branch returns (non-backlog cards with agentInfo), the expand button on lines 223-234 sets isSummaryDialogOpen to true, but the SummaryDialog component is only rendered in the final return block (lines 270-282), which is never reached due to this early return. Clicking the expand button will have no visible effect.

🔎 Proposed fix: Include SummaryDialog in this return block
     // Agent Info Panel for non-backlog cards
     if (showAgentInfo && feature.status !== "backlog" && agentInfo) {
       return (
-        <div className="mb-3 space-y-2 overflow-hidden">
+        <>
+          <div className="mb-3 space-y-2 overflow-hidden">
             {/* Model & Phase */}
             ...
             {/* Summary for waiting_approval and verified */}
             ...
-        </div>
+          </div>
+          <SummaryDialog
+            feature={feature}
+            agentInfo={agentInfo}
+            summary={summary}
+            isOpen={isSummaryDialogOpen}
+            onOpenChange={setIsSummaryDialogOpen}
+          />
+        </>
       );
     }
📝 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
if (showAgentInfo && feature.status !== "backlog" && agentInfo) {
return (
<div className="mb-3 space-y-2 overflow-hidden">
{/* Model & Phase */}
<div className="flex items-center gap-2 text-[11px] flex-wrap">
<div className="flex items-center gap-1 text-[var(--status-info)]">
<Cpu className="w-3 h-3" />
<span className="font-medium">
{formatModelName(feature.model ?? DEFAULT_MODEL)}
</span>
</div>
{agentInfo.currentPhase && (
<div
className={cn(
"px-1.5 py-0.5 rounded-md text-[10px] font-medium",
agentInfo.currentPhase === "planning" &&
"bg-[var(--status-info-bg)] text-[var(--status-info)]",
agentInfo.currentPhase === "action" &&
"bg-[var(--status-warning-bg)] text-[var(--status-warning)]",
agentInfo.currentPhase === "verification" &&
"bg-[var(--status-success-bg)] text-[var(--status-success)]"
)}
>
{agentInfo.currentPhase}
</div>
)}
</div>
{/* Task List Progress */}
{agentInfo.todos.length > 0 && (
<div className="space-y-1">
<div className="flex items-center gap-1 text-[10px] text-muted-foreground/70">
<ListTodo className="w-3 h-3" />
<span>
{agentInfo.todos.filter((t) => t.status === "completed").length}
/{agentInfo.todos.length} tasks
</span>
</div>
<div className="space-y-0.5 max-h-16 overflow-y-auto">
{agentInfo.todos.slice(0, 3).map((todo, idx) => (
<div
key={idx}
className="flex items-center gap-1.5 text-[10px]"
>
{todo.status === "completed" ? (
<CheckCircle2 className="w-2.5 h-2.5 text-[var(--status-success)] shrink-0" />
) : todo.status === "in_progress" ? (
<Loader2 className="w-2.5 h-2.5 text-[var(--status-warning)] animate-spin shrink-0" />
) : (
<Circle className="w-2.5 h-2.5 text-muted-foreground/50 shrink-0" />
)}
<span
className={cn(
"break-words hyphens-auto line-clamp-2 leading-relaxed",
todo.status === "completed" &&
"text-muted-foreground/60 line-through",
todo.status === "in_progress" &&
"text-[var(--status-warning)]",
todo.status === "pending" && "text-muted-foreground/80"
)}
>
{todo.content}
</span>
</div>
))}
{agentInfo.todos.length > 3 && (
<p className="text-[10px] text-muted-foreground/60 pl-4">
+{agentInfo.todos.length - 3} more
</p>
)}
</div>
</div>
)}
{/* Summary for waiting_approval and verified */}
{(feature.status === "waiting_approval" ||
feature.status === "verified") && (
<>
{(feature.summary || summary || agentInfo.summary) && (
<div className="space-y-1.5 pt-2 border-t border-border/30 overflow-hidden">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-1 text-[10px] text-[var(--status-success)] min-w-0">
<Sparkles className="w-3 h-3 shrink-0" />
<span className="truncate font-medium">Summary</span>
</div>
<button
onClick={(e) => {
e.stopPropagation();
setIsSummaryDialogOpen(true);
}}
onPointerDown={(e) => e.stopPropagation()}
className="p-0.5 rounded-md hover:bg-muted/80 transition-colors text-muted-foreground/60 hover:text-muted-foreground shrink-0"
title="View full summary"
data-testid={`expand-summary-${feature.id}`}
>
<Expand className="w-3 h-3" />
</button>
</div>
<p className="text-[10px] text-muted-foreground/70 line-clamp-3 break-words hyphens-auto leading-relaxed overflow-hidden">
{feature.summary || summary || agentInfo.summary}
</p>
</div>
)}
{!feature.summary &&
!summary &&
!agentInfo.summary &&
agentInfo.toolCallCount > 0 && (
<div className="flex items-center gap-2 text-[10px] text-muted-foreground/60 pt-2 border-t border-border/30">
<span className="flex items-center gap-1">
<Wrench className="w-2.5 h-2.5" />
{agentInfo.toolCallCount} tool calls
</span>
{agentInfo.todos.length > 0 && (
<span className="flex items-center gap-1">
<CheckCircle2 className="w-2.5 h-2.5 text-[var(--status-success)]" />
{
agentInfo.todos.filter((t) => t.status === "completed")
.length
}{" "}
tasks done
</span>
)}
</div>
)}
</>
)}
</div>
);
}
if (showAgentInfo && feature.status !== "backlog" && agentInfo) {
return (
<>
<div className="mb-3 space-y-2 overflow-hidden">
{/* Model & Phase */}
<div className="flex items-center gap-2 text-[11px] flex-wrap">
<div className="flex items-center gap-1 text-[var(--status-info)]">
<Cpu className="w-3 h-3" />
<span className="font-medium">
{formatModelName(feature.model ?? DEFAULT_MODEL)}
</span>
</div>
{agentInfo.currentPhase && (
<div
className={cn(
"px-1.5 py-0.5 rounded-md text-[10px] font-medium",
agentInfo.currentPhase === "planning" &&
"bg-[var(--status-info-bg)] text-[var(--status-info)]",
agentInfo.currentPhase === "action" &&
"bg-[var(--status-warning-bg)] text-[var(--status-warning)]",
agentInfo.currentPhase === "verification" &&
"bg-[var(--status-success-bg)] text-[var(--status-success)]"
)}
>
{agentInfo.currentPhase}
</div>
)}
</div>
{/* Task List Progress */}
{agentInfo.todos.length > 0 && (
<div className="space-y-1">
<div className="flex items-center gap-1 text-[10px] text-muted-foreground/70">
<ListTodo className="w-3 h-3" />
<span>
{agentInfo.todos.filter((t) => t.status === "completed").length}
/{agentInfo.todos.length} tasks
</span>
</div>
<div className="space-y-0.5 max-h-16 overflow-y-auto">
{agentInfo.todos.slice(0, 3).map((todo, idx) => (
<div
key={idx}
className="flex items-center gap-1.5 text-[10px]"
>
{todo.status === "completed" ? (
<CheckCircle2 className="w-2.5 h-2.5 text-[var(--status-success)] shrink-0" />
) : todo.status === "in_progress" ? (
<Loader2 className="w-2.5 h-2.5 text-[var(--status-warning)] animate-spin shrink-0" />
) : (
<Circle className="w-2.5 h-2.5 text-muted-foreground/50 shrink-0" />
)}
<span
className={cn(
"break-words hyphens-auto line-clamp-2 leading-relaxed",
todo.status === "completed" &&
"text-muted-foreground/60 line-through",
todo.status === "in_progress" &&
"text-[var(--status-warning)]",
todo.status === "pending" && "text-muted-foreground/80"
)}
>
{todo.content}
</span>
</div>
))}
{agentInfo.todos.length > 3 && (
<p className="text-[10px] text-muted-foreground/60 pl-4">
{agentInfo.todos.length - 3} more
</p>
)}
</div>
</div>
)}
{/* Summary for waiting_approval and verified */}
{(feature.status === "waiting_approval" ||
feature.status === "verified") && (
<>
{(feature.summary || summary || agentInfo.summary) && (
<div className="space-y-1.5 pt-2 border-t border-border/30 overflow-hidden">
<div className="flex items-center justify-between gap-2">
<div className="flex items-center gap-1 text-[10px] text-[var(--status-success)] min-w-0">
<Sparkles className="w-3 h-3 shrink-0" />
<span className="truncate font-medium">Summary</span>
</div>
<button
onClick={(e) => {
e.stopPropagation();
setIsSummaryDialogOpen(true);
}}
onPointerDown={(e) => e.stopPropagation()}
className="p-0.5 rounded-md hover:bg-muted/80 transition-colors text-muted-foreground/60 hover:text-muted-foreground shrink-0"
title="View full summary"
data-testid={`expand-summary-${feature.id}`}
>
<Expand className="w-3 h-3" />
</button>
</div>
<p className="text-[10px] text-muted-foreground/70 line-clamp-3 break-words hyphens-auto leading-relaxed overflow-hidden">
{feature.summary || summary || agentInfo.summary}
</p>
</div>
)}
{!feature.summary &&
!summary &&
!agentInfo.summary &&
agentInfo.toolCallCount > 0 && (
<div className="flex items-center gap-2 text-[10px] text-muted-foreground/60 pt-2 border-t border-border/30">
<span className="flex items-center gap-1">
<Wrench className="w-2.5 h-2.5" />
{agentInfo.toolCallCount} tool calls
</span>
{agentInfo.todos.length > 0 && (
<span className="flex items-center gap-1">
<CheckCircle2 className="w-2.5 h-2.5 text-[var(--status-success)]" />
{
agentInfo.todos.filter((t) => t.status === "completed")
.length
}{" "}
tasks done
</span>
)}
</div>
)}
</>
)}
</div>
<SummaryDialog
feature={feature}
agentInfo={agentInfo}
summary={summary}
isOpen={isSummaryDialogOpen}
onOpenChange={setIsSummaryDialogOpen}
/>
</>
);
}
🤖 Prompt for AI Agents
In
apps/ui/src/components/views/board-view/components/kanban-card/agent-info-panel.tsx
around lines 138-266, this return block renders the agent info and the "expand"
button but never renders the SummaryDialog, so opening it does nothing; update
this return to also render the SummaryDialog (same props/state used in the main
return) — i.e., add the SummaryDialog component with
isOpen={isSummaryDialogOpen}, setIsOpen={setIsSummaryDialogOpen}, summary source
(feature.summary || summary || agentInfo.summary), and preserve event
stopPropagation and data-testid so the dialog can actually open when the expand
button is clicked.

Comment on lines +161 to +176
) : onVerify ? (
<Button
variant="default"
size="sm"
className="flex-1 h-7 text-[11px] bg-[var(--status-success)] hover:bg-[var(--status-success)]/90"
onClick={(e) => {
e.stopPropagation();
onVerify();
}}
onPointerDown={(e) => e.stopPropagation()}
data-testid={`verify-feature-${feature.id}`}
>
<PlayCircle className="w-3 h-3 mr-1" />
Resume
</Button>
) : null}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Button label "Resume" is misleading when using onVerify handler.

This button displays "Resume" but calls onVerify. The previous branch (lines 146-160) correctly shows "Resume" with onResume. This branch should likely have a different label like "Verify" to match its action.

🔎 Suggested fix
           ) : onVerify ? (
             <Button
               variant="default"
               size="sm"
               className="flex-1 h-7 text-[11px] bg-[var(--status-success)] hover:bg-[var(--status-success)]/90"
               onClick={(e) => {
                 e.stopPropagation();
                 onVerify();
               }}
               onPointerDown={(e) => e.stopPropagation()}
-              data-testid={`verify-feature-${feature.id}`}
+              data-testid={`run-tests-${feature.id}`}
             >
               <PlayCircle className="w-3 h-3 mr-1" />
-              Resume
+              Run Tests
             </Button>

Alternatively, if "Resume" is the intended UX term regardless of handler, consider renaming the handler for clarity.

🤖 Prompt for AI Agents
In
apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx
around lines 161 to 176, the button text reads "Resume" but invokes the onVerify
handler; change the visible label to "Verify" to match the handler (or
alternatively rename the handler to onResume if the UX should remain "Resume")
so the action and label align; keep existing props (event stopPropagation
handlers, data-testid, icon, classes) and only update the displayed text (and
update data-testid if you rename the handler to maintain test clarity).

Comment on lines +156 to +178
useEffect(() => {
if (!feature.justFinishedAt || feature.status !== "waiting_approval") {
return;
}

const finishedTime = new Date(feature.justFinishedAt).getTime();
const twoMinutes = 2 * 60 * 1000;
const timeRemaining = twoMinutes - (currentTime - finishedTime);

if (timeRemaining <= 0) {
return;
}

// eslint-disable-next-line no-undef
const interval = setInterval(() => {
setCurrentTime(Date.now());
}, 1000);

return () => {
// eslint-disable-next-line no-undef
clearInterval(interval);
};
}, [feature.justFinishedAt, feature.status, currentTime]);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential issue: currentTime in dependency array causes interval recreation.

Including currentTime in the effect's dependency array means the effect re-runs every time currentTime changes (every second). This causes the interval to be cleared and recreated every second, which is inefficient.

🔎 Suggested fix
   useEffect(() => {
     if (!feature.justFinishedAt || feature.status !== "waiting_approval") {
       return;
     }

     const finishedTime = new Date(feature.justFinishedAt).getTime();
     const twoMinutes = 2 * 60 * 1000;
-    const timeRemaining = twoMinutes - (currentTime - finishedTime);
+    const timeRemaining = twoMinutes - (Date.now() - finishedTime);

     if (timeRemaining <= 0) {
       return;
     }

     // eslint-disable-next-line no-undef
     const interval = setInterval(() => {
       setCurrentTime(Date.now());
     }, 1000);

     return () => {
       // eslint-disable-next-line no-undef
       clearInterval(interval);
     };
-  }, [feature.justFinishedAt, feature.status, currentTime]);
+  }, [feature.justFinishedAt, feature.status]);

This way the interval is set up once when the feature enters waiting_approval state and cleaned up when the time expires or status changes.

🤖 Prompt for AI Agents
In
apps/ui/src/components/views/board-view/components/kanban-card/card-badges.tsx
around lines 156 to 178, the useEffect recreates the interval every second
because currentTime is included in the dependency array; remove currentTime from
the deps so the effect only runs when feature.justFinishedAt or feature.status
changes, set up a single interval inside the effect that calls
setCurrentTime(Date.now()) each second, and keep the existing early returns and
cleanup (clearInterval) so the interval is created once when the feature enters
waiting_approval and is cleared when status changes or the component unmounts;
if you need to stop the interval when the remaining time elapses, compute
timeRemaining inside the interval and clearInterval when it <= 0.

- Renamed the Commit button to Mark as Verified in the test cases to align with recent UI changes.
- Updated feature descriptions in the tests to match the new functionality.
- Adjusted visibility checks for the Mark as Verified button to ensure accurate testing of the updated UI behavior.
@claude
Copy link

claude bot commented Dec 20, 2025

Code Review: Refactor Kanban Cards

Thank you for this refactoring PR! This is a well-executed component decomposition that significantly improves maintainability.

Strengths

1. Excellent Component Decomposition

The refactoring from a monolithic 1,332-line file into smaller, focused components follows clean code principles perfectly. Each new component has a clear, focused purpose with improved maintainability and better code organization.

2. Minimal Breaking Changes

Export path updated cleanly in index.ts, public API remains unchanged, and test changes are minimal.

3. Good State Management

State appropriately co-located with components that use it.

4. Clean Separation of Concerns

  • kanban-card.tsx: Orchestration and drag-and-drop logic
  • card-header.tsx: Title, description, action buttons per status
  • card-actions.tsx: Bottom action buttons based on feature state
  • card-badges.tsx: Status badges
  • agent-info-panel.tsx: Agent context and task progress
  • card-content-sections.tsx: Branch info, PR links, steps preview
  • summary-dialog.tsx: Summary modal

Issues and Concerns

1. Bug: Removed Commit Handler from In-Progress Cards

Location: apps/ui/src/components/views/board-view/components/kanban-card/card-actions.tsx:110-111

The PR description states removed commit handler from in-progress task cards - however, the onCommit prop is still defined in the interface but never used. This is dead code that could confuse developers.

Recommendation: Remove the onCommit prop from KanbanCardProps interface if commit functionality was intentionally removed.

2. ESLint Disable Comments

Location: Multiple files (agent-info-panel.tsx, card-badges.tsx)

Multiple eslint-disable-next-line no-undef comments suggest missing type definitions. setInterval, clearInterval, console, and window should be available in DOM environment.

Recommendation: Review TypeScript configuration and add proper type definitions instead of disabling lints.

3. Type Safety Concerns

Location: agent-info-panel.tsx:74

Using any for window.__currentProject defeats TypeScript purpose. Consider creating proper type definitions.

4. Unused Parameter

Location: kanban-card.tsx:48

Parameter onMoveBackToInProgress is prefixed with underscore but never used. Remove from props if not needed.

5. Magic Numbers

Location: card-badges.tsx:152, 163

const twoMinutes = 2 * 60 * 1000 is duplicated across files. Consider extracting to a shared constant.

6. Border Style Type Assertions

Location: kanban-card.tsx:95-100

Mutating object with type assertions is error-prone. Consider using object spread pattern instead.

Code Quality Assessment

Adherence to Clean Code Guidelines

  • DRY: Good extraction of repeated logic
  • Code Reusability: Components are now highly reusable
  • Abstractions: Well-abstracted concerns
  • Single Responsibility: Each component has one clear purpose
  • Avoid Magic Numbers: Some duplication of time constants
  • Type Safety: Several any types and lint disables

Overall Quality

  • Readability: Excellent - much easier to navigate
  • Maintainability: Significantly improved
  • Type Safety: Good, but has some gaps
  • Testing: Minimal test changes are appropriate

Security Considerations

No security concerns identified. The refactoring maintains the same security posture as the original implementation.

Performance Considerations

Positive:

  • Component memoization preserved
  • Conditional rendering prevents unnecessary work
  • useMemo for expensive calculations maintained

Potential Concerns:

  • Multiple useEffect hooks now distributed across components could lead to more re-renders
  • Recommendation: Monitor re-render frequency, especially for AgentInfoPanel with its 3-second interval

Test Coverage

Test changes are minimal and appropriate. Updated test description from Commit to Mark as Verified which reflects the actual functionality being tested.

Recommendations Summary

High Priority:

  1. Remove or document onCommit prop - Align interface with actual usage
  2. Fix ESLint disable comments - Address root cause rather than suppressing warnings
  3. Improve type safety - Replace any types with proper interfaces

Medium Priority:
4. Extract shared constants - DRY principle for JUST_FINISHED_DURATION_MS
5. Refactor border style logic - Use object spread instead of mutations
6. Remove unused parameter - Clean up _onMoveBackToInProgress

Low Priority:
7. Add JSDoc comments - Document public component interfaces
8. Consider performance monitoring - Track re-render frequency post-refactor

Conclusion

This is a high-quality refactoring that significantly improves the codebase. The decomposition is logical and follows clean code principles. The issues identified are relatively minor and do not detract from the overall value of this change.

Recommendation: Approve with minor fixes

The identified issues should be addressed (particularly the onCommit handler clarification and type safety improvements), but they do not block merging if the team is comfortable addressing them in follow-up PRs.

Great work on improving the maintainability of this critical component!

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.

2 participants