Skip to content

Conversation

@jkergosi
Copy link

@jkergosi jkergosi commented Jan 13, 2026

Summary

This PR introduces a Backlog Manager to Automaker to enable efficient grooming and management of large backlogs.

  • Adds a Manage action to the Backlog column on the Kanban board
  • Opens a full-page backlog manager view optimized for bulk operations
  • Provides a table-based interface better suited for reviewing, editing, and organizing many backlog items
  • Reduces reliance on Kanban card interactions for backlog-only workflows
  • Enables feature import from text or markdown files.

This addresses the issue where large backlogs become difficult to manage when constrained to a Kanban column layout.

Related Issue

N/A (feature improvement driven by usability and scale concerns)

Testing

  • Verified Manage button appears on Backlog column in Kanban board
  • Confirmed navigation to full-page Backlog Manager view
  • Tested backlog rendering and basic interactions with large item counts
  • Ensured no regression to existing Kanban board behavior

Screenshots

[Include screenshots of the Backlog column Manage button and the Backlog Manager full-page view]
button
manager

Summary by CodeRabbit

  • New Features
    • URL-driven board mode: switch views via URL (including a dedicated backlog management mode).
    • Manage Backlog action from the Kanban view to open the backlog manager.
    • Backlog manager with searchable, paginated, sortable table and inline category editing.
    • Bulk actions: multi-select category updates and batch delete with confirmations.
    • File import for creating backlog items and import error reporting.

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

@coderabbitai
Copy link

coderabbitai bot commented Jan 13, 2026

📝 Walkthrough

Walkthrough

Introduces a URL-driven board mode and a new BacklogManager feature with table, toolbar, bulk actions, import, inline editing, concurrency utilities, and route search-param validation for 'manage-backlog' mode.

Changes

Cohort / File(s) Summary
Board view & routing
apps/ui/src/components/views/board-view.tsx, apps/ui/src/components/views/board-view/kanban-board.tsx, apps/ui/src/routes/board.tsx
BoardView now derives primary view from URL mode; KanbanBoard gains onManageBacklog?: () => void and a Manage Backlog button; route adds typed BoardSearchParams and validateSearch for mode='manage-backlog'.
Backlog manager entry & exports
apps/ui/src/components/views/board-view/backlog-manager/index.ts, apps/ui/src/components/views/board-view/backlog-manager/backlog-manager.tsx
New exported BacklogManager component that composes toolbar, table, bulk actions, errors, and uses the backlog hook. Barrel file re-exports submodules and types.
Backlog table & row
apps/ui/src/components/views/board-view/backlog-manager/backlog-table.tsx, apps/ui/src/components/views/board-view/backlog-manager/backlog-row.tsx
New BacklogTable with sorting, pagination, bulk selection; BacklogRow with selection, inline category editing, save/delete flows, and delete confirmation.
Toolbar & bulk actions UI
apps/ui/src/components/views/board-view/backlog-manager/backlog-toolbar.tsx, apps/ui/src/components/views/board-view/backlog-manager/bulk-actions-bar.tsx
BacklogToolbar: search, multi-category filters, file import workflow with per-file error reporting. BulkActionsBar: floating bulk-category update, bulk-delete with confirmation, select-all/clear controls.
State hook & concurrency utils
apps/ui/src/components/views/board-view/backlog-manager/hooks/use-backlog-manager.ts, apps/ui/src/lib/concurrent-utils.ts
New useBacklogManager hook: filtering, selection, import errors, CRUD and bulk ops (uses Electron API), and refetch. concurrent-utils: mapWithConcurrency variants and helpers to run async work with concurrency limits and collect results/errors.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant BoardView
    participant BacklogManager
    participant BacklogTable
    participant BulkActionsBar
    participant Hook as useBacklogManager
    participant API as Electron API

    User->>BoardView: Click "Manage backlog"
    BoardView->>BoardView: Update URL mode='manage-backlog'
    BoardView->>BacklogManager: Render
    BacklogManager->>Hook: init(currentProject)
    Hook->>API: fetch features (status=backlog)
    API-->>Hook: return features
    Hook-->>BacklogManager: features + actions
    BacklogManager->>BacklogTable: render features
    User->>BacklogTable: select rows
    BacklogTable->>Hook: update selectedIds
    Hook-->>BulkActionsBar: selection state
    User->>BulkActionsBar: click "Delete Selected"
    BulkActionsBar->>Hook: bulkDelete(ids)
    Hook->>API: delete features (concurrency=5)
    API-->>Hook: deletion results
    Hook->>BacklogManager: refetch/update
    BacklogManager->>BacklogTable: re-render updated list
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

Enhancement

Poem

🐰 I hopped into the board today, so neat —
A backlog table, filters, and rows to meet.
Bulk hops in batches, five at a time,
Importing notes, sorting in rhyme.
URL modes now help me find my seat.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: introducing a Backlog Manager interface for bulk management and grooming of backlog features, which aligns with the extensive changes across multiple new components and the board-view integration.
Docstring Coverage ✅ Passed Docstring coverage is 88.24% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings

🧹 Recent nitpick comments
apps/ui/src/components/views/board-view/kanban-board.tsx (1)

151-162: Redundant disabled condition.

The button's disabled={!onManageBacklog} is redundant because when onManageBacklog is undefined, clicking the button simply calls undefined (which is a no-op). However, keeping disabled does provide visual feedback to users that the button is inactive, which improves UX.

Consider simplifying by relying solely on the disabled state for clarity, or document the intentional UX choice:

✨ Optional: Simplify with optional chaining
                      <Button
                        variant="ghost"
                        size="sm"
                        className="h-6 w-6 p-0 text-muted-foreground hover:text-foreground"
-                       onClick={onManageBacklog}
+                       onClick={() => onManageBacklog?.()}
                        title="Manage backlog"
                        data-testid="manage-backlog-button"
                        disabled={!onManageBacklog}
                      >
📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6148feb and a358646.

📒 Files selected for processing (2)
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/kanban-board.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/ui/src/components/views/board-view/kanban-board.tsx
  • apps/ui/src/components/views/board-view.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/ui/src/components/views/board-view/kanban-board.tsx
  • apps/ui/src/components/views/board-view.tsx
🧠 Learnings (2)
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/views/board-view/kanban-board.tsx
  • apps/ui/src/components/views/board-view.tsx
📚 Learning: 2025-12-28T05:07:48.147Z
Learnt from: CR
Repo: AutoMaker-Org/automaker PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-28T05:07:48.147Z
Learning: Each feature executes in an isolated git worktree created via automaker/git-utils to protect the main branch during AI agent execution

Applied to files:

  • apps/ui/src/components/views/board-view.tsx
🔇 Additional comments (6)
apps/ui/src/components/views/board-view/kanban-board.tsx (3)

5-9: LGTM!

The import additions are appropriate. LayoutList is correctly imported from lucide-react for the new backlog management button.


51-60: LGTM!

The new onManageBacklog prop is well-documented and follows the existing pattern for optional callback props in this component.


309-309: LGTM!

The dropAnimation formatting change to inline object syntax is cleaner and doesn't affect behavior.

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

1-9: LGTM!

The mode-based conditional rendering cleanly separates the BacklogManager view from regular board views. The boardMode === 'manage-backlog' check provides a clear URL-driven navigation pattern.


79-100: LGTM!

The GraphView branch is well-structured with all necessary props properly passed. The dual view mode (kanban/graph) conditional within the non-backlog branch maintains clean separation of concerns.


35-78: enterBacklogManager function definition not found in codebase.

The onManageBacklog={enterBacklogManager} prop wiring references a function that could not be located despite extensive searching. The route infrastructure in apps/ui/src/routes/board.tsx supports the mode=manage-backlog search parameter, but the function implementation that should trigger this navigation is not accessible. Verify that enterBacklogManager is properly defined and implemented to update URL search params with mode=manage-backlog.


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 and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @jkergosi, 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 enhances the Automaker UI by integrating a new Backlog Manager. This feature provides a specialized, full-page interface designed for streamlined management of extensive backlogs, moving beyond the limitations of the Kanban board for bulk actions. It introduces robust tools for importing, categorizing, and deleting multiple features, thereby improving the overall usability and scalability of backlog grooming workflows.

Highlights

  • New Backlog Manager Feature: Introduced a dedicated 'Backlog Manager' full-page view for efficient grooming and management of large backlogs, accessible via a new 'Manage' action on the Kanban board's Backlog column.
  • Bulk Operations: The Backlog Manager supports bulk operations including importing features from .txt and .md files, updating categories for multiple items, and deleting selected features with a concurrency limit to maintain UI responsiveness.
  • Table-Based Interface: Features are displayed in a sortable and paginated table, offering a more suitable interface for reviewing and organizing many backlog items compared to individual Kanban cards.
  • Inline Editing and Filtering: Users can perform inline category editing directly within the table rows. The manager also includes search functionality and multi-select category filters to easily narrow down the displayed backlog items.
  • URL-Driven Mode Switching: The board view now supports URL-driven mode switching, allowing navigation between the standard Kanban board and the new Backlog Manager using search parameters (e.g., ?mode=manage-backlog).

🧠 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 introduces a new Backlog Manager feature, which is a significant and well-structured addition. The use of a dedicated hook (useBacklogManager) to encapsulate the logic is excellent, and the component-based architecture is clean. The introduction of a URL-based routing for the manager view is a modern and effective approach. My feedback focuses on improving maintainability by reducing code duplication, enhancing user experience by providing better feedback on bulk operation failures, and replacing a jarring native confirmation dialog with a custom component.

Comment on lines +102 to +145
const handleSaveCategory = useCallback(async () => {
if (isSaving) return;

// Only save if value changed
if (editingCategoryValue !== (feature.category || '')) {
setIsSaving(true);
try {
await onCategoryChange(feature.id, editingCategoryValue);
} catch (error) {
// Reset to original value on error
setEditingCategoryValue(feature.category || '');
} finally {
setIsSaving(false);
}
}
setIsEditingCategory(false);
}, [editingCategoryValue, feature.category, feature.id, onCategoryChange, isSaving]);

// Cancel category edit
const handleCancelCategoryEdit = useCallback(() => {
setEditingCategoryValue(feature.category || '');
setIsEditingCategory(false);
}, [feature.category]);

// Auto-save when a category is selected (blur or selection)
const handleCategorySelect = useCallback(
async (value: string) => {
setEditingCategoryValue(value);
// Only save if value changed
if (value !== (feature.category || '')) {
setIsSaving(true);
try {
await onCategoryChange(feature.id, value);
} catch (error) {
// Reset to original value on error
setEditingCategoryValue(feature.category || '');
} finally {
setIsSaving(false);
}
}
setIsEditingCategory(false);
},
[feature.category, feature.id, onCategoryChange]
);
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 handleSaveCategory and handleCategorySelect functions contain duplicated logic for saving the category update. This can be refactored into a single helper function to improve maintainability and reduce redundancy.

  const saveCategory = useCallback(
    async (value: string) => {
      if (isSaving) return;

      // Only save if value changed
      if (value !== (feature.category || '')) {
        setIsSaving(true);
        try {
          await onCategoryChange(feature.id, value);
        } catch (error) {
          // Reset to original value on error
          setEditingCategoryValue(feature.category || '');
        } finally {
          setIsSaving(false);
        }
      }
      setIsEditingCategory(false);
    },
    [feature.category, feature.id, onCategoryChange, isSaving]
  );

  const handleSaveCategory = useCallback(() => {
    saveCategory(editingCategoryValue);
  }, [editingCategoryValue, saveCategory]);

  // Cancel category edit
  const handleCancelCategoryEdit = useCallback(() => {
    setEditingCategoryValue(feature.category || '');
    setIsEditingCategory(false);
  }, [feature.category]);

  // Auto-save when a category is selected (blur or selection)
  const handleCategorySelect = useCallback(
    (value: string) => {
      setEditingCategoryValue(value);
      saveCategory(value);
    },
    [saveCategory]
  );

Comment on lines +97 to +111
// Attempt 1: empty string title
try {
const result = await createFeature({
title: '',
description,
status: 'backlog',
});
if (result) {
created = true;
logger.info(`Imported file "${file.name}" with empty title`);
}
} catch (error) {
logger.warn(
`Failed to create feature with empty title for "${file.name}", trying single space`
);
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 nested try-catch logic to attempt creating a feature first with an empty title and then with a single-space title is complex. While it's a robust way to handle a likely backend constraint, the reason for this two-step process is not immediately obvious. Adding a code comment explaining why an empty title might fail and why a single space is used as a fallback would significantly improve the maintainability of this function for future developers.

Comment on lines +74 to +76
const confirmed = window.confirm(
`Are you sure you want to delete ${selectedCount} item${selectedCount !== 1 ? 's' : ''}? This action cannot be undone.`
);
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 component uses window.confirm() for the bulk delete action. This is a blocking browser-native dialog that can feel disconnected from the app's UI. For a more integrated and non-blocking user experience, consider replacing this with a custom confirmation dialog, such as the DeleteConfirmDialog component already used elsewhere in the application.

Comment on lines +268 to +354
const bulkDelete = useCallback(
async (featureIds: string[]) => {
if (!currentProject || featureIds.length === 0) return;

const api = getElectronAPI();
if (!api.features) {
logger.error('Features API not available');
return;
}

logger.info(`Bulk deleting ${featureIds.length} features with concurrency cap of 5`);

const results = await mapWithConcurrency(
featureIds,
async (id) => {
await api.features!.delete(currentProject.path, id);
return id;
},
5 // Concurrency limit
);

// Remove successfully deleted features from store
const deletedIds: string[] = [];
results.forEach((result, index) => {
if (result.status === 'fulfilled') {
deletedIds.push(featureIds[index]);
} else {
logger.error(`Failed to delete feature ${featureIds[index]}:`, result.reason);
}
});

// Update store by removing deleted features
deletedIds.forEach((id) => removeFeature(id));

// Clear selection for deleted items
setSelectedIds((prev) => {
const next = new Set(prev);
deletedIds.forEach((id) => next.delete(id));
return next;
});

logger.info(`Successfully deleted ${deletedIds.length}/${featureIds.length} features`);
},
[currentProject, removeFeature]
);

// Bulk update category with concurrency cap of 5
const bulkUpdateCategory = useCallback(
async (featureIds: string[], category: string) => {
if (!currentProject || featureIds.length === 0) return;

const api = getElectronAPI();
if (!api.features) {
logger.error('Features API not available');
return;
}

logger.info(`Bulk updating category to "${category}" for ${featureIds.length} features`);

const results = await mapWithConcurrency(
featureIds,
async (id) => {
const result = await api.features!.update(currentProject.path, id, { category });
return { id, feature: result.feature };
},
5 // Concurrency limit
);

// Update store for successful updates
results.forEach((result, index) => {
if (result.status === 'fulfilled' && result.value.feature) {
storeUpdateFeature(result.value.id, result.value.feature);
} else if (result.status === 'rejected') {
logger.error(
`Failed to update category for feature ${featureIds[index]}:`,
result.reason
);
}
});

const successCount = results.filter((r) => r.status === 'fulfilled').length;
logger.info(
`Successfully updated category for ${successCount}/${featureIds.length} features`
);
},
[currentProject, storeUpdateFeature]
);
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 bulkDelete and bulkUpdateCategory functions handle API errors by logging them to the console, but this feedback isn't surfaced to the user. In bulk operations where partial success is possible, it's crucial to inform the user about any failures. Consider using a toast notification to report the number of failed operations and why they failed.

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: 1

🤖 Fix all issues with AI agents
In
@apps/ui/src/components/views/board-view/backlog-manager/hooks/use-backlog-manager.ts:
- Around line 197-219: The code calls storeUpdateFeature(result.feature.id,
result.feature) after creating a new feature, but update only modifies existing
entries; replace this call with the store's addFeature (or equivalent add
function) to insert the new feature into the store (e.g., call
addFeature(result.feature) instead of storeUpdateFeature(...)); update the
invocation in useBacklogManager's feature creation path so newly created
features are added rather than skipped.
🧹 Nitpick comments (10)
apps/ui/src/components/views/board-view/backlog-manager/backlog-manager.tsx (1)

225-237: Consider memoizing the onDelete wrapper to avoid recreation on each render.

The inline function (feature) => onDelete(feature.id) creates a new function on every render. For a table with many rows, this could cause unnecessary re-renders of BacklogTable if it's memoized.

♻️ Optional optimization
+ import { useMemo, useCallback } from 'react';

  // Near other handlers
+ const handleDelete = useCallback(
+   (feature: Feature) => onDelete?.(feature.id),
+   [onDelete]
+ );

  // In render
  <BacklogTable
    ...
-   onDelete={onDelete ? (feature) => onDelete(feature.id) : undefined}
+   onDelete={onDelete ? handleDelete : undefined}
  />
apps/ui/src/components/views/board-view/backlog-manager/bulk-actions-bar.tsx (1)

73-76: Consider a custom confirmation dialog for better UX.

window.confirm is functional but blocks the main thread and has limited styling options. A custom modal dialog would provide a more consistent experience with the rest of the UI.

This is a nice-to-have improvement that could be addressed in a follow-up.

apps/ui/src/components/views/board-view/backlog-manager/backlog-table.tsx (1)

324-326: Consider using a stable dependency for page reset.

The useEffect resets the page when features.length changes, but this could miss cases where the features array is replaced with a different set of features that happens to have the same length (e.g., switching filters). Consider using a hash or the features themselves as the dependency if more precise reset behavior is needed.

apps/ui/src/components/views/board-view/backlog-manager/backlog-row.tsx (2)

147-159: Global keydown listener should be scoped more carefully.

The global document.addEventListener('keydown', ...) will capture Escape presses anywhere in the document when editing. Consider attaching the listener to a ref-based element or checking if the event target is within the component's subtree to avoid interfering with other Escape handlers.

💡 Suggestion for scoped keyboard handling
 useEffect(() => {
   if (!isEditingCategory) return;

   const handleKeyDown = (e: KeyboardEvent) => {
-    if (e.key === 'Escape') {
+    // Only handle if the event originated from within this row or its popovers
+    const target = e.target as HTMLElement;
+    if (e.key === 'Escape' && (categoryEditRef.current?.contains(target) || target.closest('[data-radix-popper-content-wrapper]'))) {
       handleCancelCategoryEdit();
     }
   };

   document.addEventListener('keydown', handleKeyDown);
   return () => document.removeEventListener('keydown', handleKeyDown);
 }, [isEditingCategory, handleCancelCategoryEdit]);

176-184: setTimeout pattern for deferred listener attachment is fragile.

Using setTimeout(..., 0) to defer adding the click-outside listener works but relies on event loop timing. A more robust approach would be to track whether the initial click event that opened editing has completed.

apps/ui/src/components/views/board-view/backlog-manager/backlog-toolbar.tsx (2)

94-142: Import retry logic with empty/space title is unusual and should be documented.

The pattern of trying an empty string title first, then falling back to a single space, suggests working around a backend validation issue. This workaround should be documented with a comment explaining why it's necessary, or ideally the root cause should be fixed.

📝 Suggested documentation
+          // Workaround: Some backends reject empty titles but accept single space.
+          // Try empty first (preferred), fall back to space if rejected.
           // Attempt 1: empty string title
           try {

136-142: Edge case: silent failure when createFeature returns null.

If createFeature returns null without throwing (indicating failure), this condition correctly catches it. However, since the first attempt might succeed but return null, the error message "Failed to create feature" is vague. Consider logging more context.

apps/ui/src/components/views/board-view/backlog-manager/hooks/use-backlog-manager.ts (3)

118-121: Category filter may unexpectedly exclude features with undefined category.

When selectedCategories.length > 0, features with undefined or empty category are filtered out. If a user selects categories to filter, items without a category won't appear even if that might be unexpected. Consider whether "(Uncategorized)" should be a filterable option.


169-175: Model alias 'opus' should use resolveModelString().

Per coding guidelines, model aliases should be resolved using resolveModelString() from @automaker/model-resolver. The hardcoded 'opus' string should be resolved to ensure consistency.

+import { resolveModelString } from '@automaker/model-resolver';
+
 // In refetchFeatures:
           model: f.model || 'opus',
+          // Should be:
+          model: f.model || resolveModelString('opus'),

374-384: Global Escape handler may conflict with other handlers.

This effect adds a global window.addEventListener('keydown', ...) for Escape. Combined with the similar handler in backlog-row.tsx, pressing Escape while editing a category could trigger both handlers. Consider coordinating these handlers or using event propagation control.

💡 Suggestion to prevent handler conflicts
 useEffect(() => {
   const handleKeyDown = (e: KeyboardEvent) => {
-    if (e.key === 'Escape' && selectedIds.size > 0) {
+    // Only clear selection if no input/editing elements are focused
+    const activeElement = document.activeElement;
+    const isEditingElement = activeElement?.tagName === 'INPUT' || 
+                             activeElement?.tagName === 'TEXTAREA' ||
+                             activeElement?.closest('[data-category-editor]');
+    if (e.key === 'Escape' && selectedIds.size > 0 && !isEditingElement) {
       clearSelection();
     }
   };

   window.addEventListener('keydown', handleKeyDown);
   return () => window.removeEventListener('keydown', handleKeyDown);
 }, [selectedIds.size, clearSelection]);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0dc70ad and 6148feb.

📒 Files selected for processing (11)
  • apps/ui/src/components/views/board-view.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-manager.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-row.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-table.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-toolbar.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/bulk-actions-bar.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/hooks/use-backlog-manager.ts
  • apps/ui/src/components/views/board-view/backlog-manager/index.ts
  • apps/ui/src/components/views/board-view/kanban-board.tsx
  • apps/ui/src/lib/concurrent-utils.ts
  • apps/ui/src/routes/board.tsx
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/ui/src/components/views/board-view/backlog-manager/bulk-actions-bar.tsx
  • apps/ui/src/components/views/board-view/kanban-board.tsx
  • apps/ui/src/lib/concurrent-utils.ts
  • apps/ui/src/components/views/board-view/backlog-manager/hooks/use-backlog-manager.ts
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-table.tsx
  • apps/ui/src/routes/board.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-toolbar.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-manager.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-row.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/index.ts
  • apps/ui/src/components/views/board-view.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/ui/src/components/views/board-view/backlog-manager/bulk-actions-bar.tsx
  • apps/ui/src/components/views/board-view/kanban-board.tsx
  • apps/ui/src/lib/concurrent-utils.ts
  • apps/ui/src/components/views/board-view/backlog-manager/hooks/use-backlog-manager.ts
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-table.tsx
  • apps/ui/src/routes/board.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-toolbar.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-manager.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-row.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/index.ts
  • apps/ui/src/components/views/board-view.tsx
🧠 Learnings (1)
📚 Learning: 2025-12-30T01:02:07.114Z
Learnt from: illia1f
Repo: AutoMaker-Org/automaker PR: 324
File: apps/ui/src/components/views/board-view/components/kanban-card/kanban-card.tsx:122-131
Timestamp: 2025-12-30T01:02:07.114Z
Learning: Tailwind CSS v4 uses postfix syntax for the important modifier: append ! at the end of the utility class (e.g., backdrop-blur-[0px]! or hover:bg-red-500!). The older v3 style used a prefix (!) at the start (e.g., !backdrop-blur-[0px]); prefer the postfix form for consistency across TSX files.

Applied to files:

  • apps/ui/src/components/views/board-view/backlog-manager/bulk-actions-bar.tsx
  • apps/ui/src/components/views/board-view/kanban-board.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-table.tsx
  • apps/ui/src/routes/board.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-toolbar.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-manager.tsx
  • apps/ui/src/components/views/board-view/backlog-manager/backlog-row.tsx
  • apps/ui/src/components/views/board-view.tsx
🧬 Code graph analysis (5)
apps/ui/src/components/views/board-view/backlog-manager/bulk-actions-bar.tsx (3)
apps/ui/src/components/views/board-view/backlog-manager/index.ts (1)
  • BulkActionsBar (10-10)
apps/ui/src/lib/utils.ts (1)
  • cn (6-8)
apps/ui/src/components/ui/button.tsx (1)
  • Button (108-108)
apps/ui/src/components/views/board-view/backlog-manager/hooks/use-backlog-manager.ts (2)
apps/ui/src/lib/electron.ts (1)
  • getElectronAPI (888-897)
apps/ui/src/lib/concurrent-utils.ts (1)
  • mapWithConcurrency (44-86)
apps/ui/src/components/views/board-view/backlog-manager/backlog-table.tsx (2)
apps/ui/src/lib/utils.ts (1)
  • cn (6-8)
apps/ui/src/components/views/board-view/backlog-manager/backlog-row.tsx (1)
  • BacklogRow (49-352)
apps/ui/src/routes/board.tsx (1)
apps/ui/src/components/views/board-view.tsx (1)
  • BoardView (75-1601)
apps/ui/src/components/views/board-view/backlog-manager/backlog-row.tsx (3)
apps/ui/src/components/views/board-view/backlog-manager/index.ts (2)
  • BacklogRowProps (9-9)
  • BacklogRow (8-8)
apps/ui/src/lib/utils.ts (1)
  • cn (6-8)
apps/ui/src/components/ui/button.tsx (1)
  • Button (108-108)
🔇 Additional comments (27)
apps/ui/src/routes/board.tsx (1)

4-16: LGTM! Clean route parameter validation.

The validateSearch function properly validates the mode parameter, returning undefined for invalid values which provides a safe fallback to the default Kanban view. The type definition is appropriately minimal and well-aligned with the URL-driven mode switching pattern used in board-view.tsx.

apps/ui/src/lib/concurrent-utils.ts (2)

44-86: Well-designed concurrency utility with correct worker pool implementation.

The worker pool pattern correctly handles the shared nextIndex by capturing the current index before any await, which is safe in JavaScript's single-threaded event loop. The pre-allocated results array ensures order preservation regardless of completion order.


108-171: Good composition pattern for the filtered and error-collecting variants.

Both mapWithConcurrencyFiltered and mapWithConcurrencyCollectErrors properly reuse mapWithConcurrency, avoiding code duplication. The type guard in mapWithConcurrencyFiltered (line 115) correctly narrows the type, and mapWithConcurrencyCollectErrors preserves the original item reference for useful error reporting.

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

53-54: LGTM! Clean prop addition for backlog navigation.

The optional onManageBacklog prop is appropriately typed and integrates well with the existing props structure.


133-164: Good UI integration for backlog management access.

The new "Manage backlog" button is well-positioned alongside the existing selection mode toggle. The fragment wrapper cleanly groups related backlog column actions.

One consideration: onManageBacklog is optional and clicking the button when undefined will be a no-op. If this button should always navigate somewhere, consider conditionally rendering it based on whether the callback is provided:

{onManageBacklog && (
  <Button ... onClick={onManageBacklog}>
    ...
  </Button>
)}

However, if the parent always provides this callback in practice, the current implementation is fine.

apps/ui/src/components/views/board-view/backlog-manager/backlog-manager.tsx (2)

27-75: Well-implemented error banner with good accessibility.

The ImportErrorsBanner correctly handles the empty state with early return, uses proper ARIA role for accessibility, and has smooth animations. The key using filename-index is acceptable since errors are displayed temporarily and unlikely to reorder.


106-157: Clean component structure with proper hook usage.

The hook is called unconditionally at the top level, and the destructuring pattern makes the component's dependencies clear. The handleCategoryChange wrapper appropriately delegates to the hook's updateFeature.

apps/ui/src/components/views/board-view/backlog-manager/bulk-actions-bar.tsx (3)

49-117: Well-structured component with proper hook placement and loading guards.

The early return on line 117 is correctly placed after all hooks, respecting React's rules of hooks. The loading guards in both handlers prevent double-submissions, and the try-catch-finally pattern ensures state is always cleaned up.


119-131: Good accessibility implementation with ARIA attributes.

The floating toolbar has appropriate role="toolbar" and aria-label for screen reader users. The animation classes provide smooth entry feedback.


231-246: Tooltip text is accurate—Escape key handler is implemented in the useBacklogManager hook.

The Escape key handler for clearing selection is already implemented in use-backlog-manager.ts (lines 374–383). The hook listens for the Escape key, calls clearSelection() when pressed, and the same clearSelection function is passed to this button as onClearSelection. The tooltip text is correct.

apps/ui/src/components/views/board-view/backlog-manager/backlog-table.tsx (4)

1-21: LGTM! Well-structured imports and type definitions.

The component is cleanly organized with proper separation of concerns between pagination controls, sortable headers, and the main table component.


329-330: Selection state may be inconsistent with filtered/paginated view.

allSelected and someSelected compare selectedIds.size against features.length, but the selection operations (selectAll) work on all features, not just the paginated view. This is consistent behavior, but consider whether users expect "select all" to select all features or just the visible page.


361-389: Sorting implementation looks correct.

The sorting logic handles null/undefined values gracefully with fallback to empty strings and correctly implements case-insensitive string comparison.


414-457: Table structure and pagination integration are well-implemented.

The component properly combines sorting, pagination, and selection with appropriate memoization to avoid unnecessary re-renders.

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

72-90: Clean implementation of URL-driven mode switching.

The BoardMode type and navigation helpers (enterBacklogManager, exitBacklogManager) are well-designed. Using URL search params for mode state enables proper browser history navigation and shareable URLs.


1251-1290: KanbanBoard now properly wired with backlog manager navigation.

The onManageBacklog={enterBacklogManager} prop integration enables seamless switching from the Kanban view to the backlog manager.


1218-1226: BacklogManager prop handling is correct.

The onDelete callback correctly receives a featureId string and passes it to handleDeleteFeature, which expects async (featureId: string). No changes needed.

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

1-14: Well-organized barrel export.

The index file properly separates component exports, type exports, and hook exports with clear comments. Using export type for interface exports is TypeScript best practice.

apps/ui/src/components/views/board-view/backlog-manager/backlog-row.tsx (4)

1-9: Imports are well-organized and appropriate.

All imports follow the project conventions using path aliases (@/components/ui/*, @/lib/utils, @/store/app-store).


198-218: Row structure and selection handling look correct.

The click delegation pattern properly excludes interactive elements from triggering row-level clicks, and the checkbox integration is clean.


246-300: Inline category editing implementation is well-designed.

The edit mode toggle, autocomplete integration, and cancel button provide a smooth UX. The data-category-editor attribute properly supports the click delegation in handleRowClick.


339-348: Delete confirmation dialog is properly integrated.

The dialog receives appropriate props including unique test IDs for automated testing.

apps/ui/src/components/views/board-view/backlog-manager/backlog-toolbar.tsx (2)

1-16: Imports and logger setup are appropriate.

Using @automaker/utils/logger for logging follows the coding guidelines for shared packages.


174-296: Toolbar UI implementation is clean and accessible.

Good use of tooltips, proper disabled state handling, and clear test IDs for automation. The category filter dropdown with "Clear all" functionality provides good UX.

apps/ui/src/components/views/board-view/backlog-manager/hooks/use-backlog-manager.ts (3)

1-5: Imports follow project conventions.

Using shared packages (@automaker/utils/logger) and path aliases appropriately.


267-312: Bulk delete implementation is robust.

Proper use of mapWithConcurrency with error handling for individual failures. The selection cleanup after deletion is correctly implemented.


386-426: Return interface is comprehensive and well-organized.

The hook provides a clean API surface with logical groupings for features, selection, search, filtering, errors, and operations.

Comment on lines +197 to +219
const newFeature: Feature = {
id: `feature-${Date.now()}`,
title: feature.title ?? '',
description: feature.description ?? '',
category: feature.category ?? '',
status: 'backlog',
steps: feature.steps ?? [],
...feature,
} as Feature;

const result = await api.features.create(currentProject.path, newFeature);
if (result.success && result.feature) {
storeUpdateFeature(result.feature.id, result.feature);
return result.feature;
}
return null;
} catch (error) {
logger.error('Failed to create feature:', error);
throw error;
}
},
[currentProject, storeUpdateFeature]
);
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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how updateFeature and addFeature are implemented in app-store
rg -n -A 15 'updateFeature.*=.*\(' apps/ui/src/store/app-store.ts | head -50
echo "---"
rg -n -A 15 'addFeature.*=.*\(' apps/ui/src/store/app-store.ts | head -50

Repository: AutoMaker-Org/automaker

Length of output: 70


🏁 Script executed:

#!/bin/bash
# Find app-store files
find apps/ui/src -name "*app-store*" -o -name "*store*" -type f | head -20
echo "---"
# Check what storeUpdateFeature is in use-backlog-manager.ts
rg -n "storeUpdateFeature\|import.*store" apps/ui/src/components/views/board-view/backlog-manager/hooks/use-backlog-manager.ts | head -20

Repository: AutoMaker-Org/automaker

Length of output: 202


🏁 Script executed:

#!/bin/bash
# Look at imports and where storeUpdateFeature comes from
head -50 apps/ui/src/components/views/board-view/backlog-manager/hooks/use-backlog-manager.ts
echo "---DIVIDER---"
# Check the app-store.ts file for updateFeature and addFeature
cat apps/ui/src/store/app-store.ts

Repository: AutoMaker-Org/automaker

Length of output: 50380


🏁 Script executed:

#!/bin/bash
# Search for updateFeature implementation
rg -n "updateFeature:" apps/ui/src/store/app-store.ts -A 5
echo "---"
# Search for addFeature implementation
rg -n "addFeature:" apps/ui/src/store/app-store.ts -A 5

Repository: AutoMaker-Org/automaker

Length of output: 1621


Use addFeature instead of updateFeature for newly created features.

Line 209 calls storeUpdateFeature(result.feature.id, result.feature) for a newly created feature. The updateFeature implementation only updates existing features in the store—it uses a map function that won't add missing entries. For newly created features, use addFeature instead to ensure the feature is added to the store.

🤖 Prompt for AI Agents
In
@apps/ui/src/components/views/board-view/backlog-manager/hooks/use-backlog-manager.ts
around lines 197 - 219, The code calls storeUpdateFeature(result.feature.id,
result.feature) after creating a new feature, but update only modifies existing
entries; replace this call with the store's addFeature (or equivalent add
function) to insert the new feature into the store (e.g., call
addFeature(result.feature) instead of storeUpdateFeature(...)); update the
invocation in useBacklogManager's feature creation path so newly created
features are added rather than skipped.

@webdevcody
Copy link
Collaborator

I see in your screenshots you are on v0.9.0 of automaker and we already have a list view of the features. We are currently working on v0.11.0rc branch of automaker soon to be v0.12. Please pull the latest changes and verify if this is still useful.

@jkergosi
Copy link
Author

jkergosi commented Jan 14, 2026

The momentum here is great. I definitely need to get more connected so I don't redouble efforts.

I like the switching from cards to list. That is helpful. It is definitely better in the same UI versus a sub page like I did.

Here are the things that are missing: bulk delete, sort by column, search, filter, and import (txt and md).
You want me to apply these to a PR for this branch?

@webdevcody
Copy link
Collaborator

@jkergosi yeah we are going to merge 0.11.0rc tonight into main, so I'd make your changes off of 0.11.0rc if you can, but tomorrow check if we have a 0.12.0rc branch if you don't get it done by then.

@jkergosi jkergosi closed this by deleting the head repository Jan 14, 2026
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