-
Notifications
You must be signed in to change notification settings - Fork 493
feat: Backlog Manager - interface to manage and groom backlog features in bulk #467
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
📝 WalkthroughWalkthroughIntroduces 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
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
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧹 Recent nitpick comments
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
🧰 Additional context used📓 Path-based instructions (2)**/*.{ts,tsx,js,jsx}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
**/*.{ts,tsx}📄 CodeRabbit inference engine (CLAUDE.md)
Files:
🧠 Learnings (2)📚 Learning: 2025-12-30T01:02:07.114ZApplied to files:
📚 Learning: 2025-12-28T05:07:48.147ZApplied to files:
🔇 Additional comments (6)
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. Comment |
Summary of ChangesHello @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 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 AssistThe 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
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 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
|
There was a problem hiding this 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.
| 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] | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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]
);
| // 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` | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| const confirmed = window.confirm( | ||
| `Are you sure you want to delete ${selectedCount} item${selectedCount !== 1 ? 's' : ''}? This action cannot be undone.` | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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] | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
There was a problem hiding this 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 ofBacklogTableif 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.confirmis 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
useEffectresets the page whenfeatures.lengthchanges, 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
createFeaturereturnsnullwithout throwing (indicating failure), this condition correctly catches it. However, since the first attempt might succeed but returnnull, 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 withundefinedor 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 inbacklog-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
📒 Files selected for processing (11)
apps/ui/src/components/views/board-view.tsxapps/ui/src/components/views/board-view/backlog-manager/backlog-manager.tsxapps/ui/src/components/views/board-view/backlog-manager/backlog-row.tsxapps/ui/src/components/views/board-view/backlog-manager/backlog-table.tsxapps/ui/src/components/views/board-view/backlog-manager/backlog-toolbar.tsxapps/ui/src/components/views/board-view/backlog-manager/bulk-actions-bar.tsxapps/ui/src/components/views/board-view/backlog-manager/hooks/use-backlog-manager.tsapps/ui/src/components/views/board-view/backlog-manager/index.tsapps/ui/src/components/views/board-view/kanban-board.tsxapps/ui/src/lib/concurrent-utils.tsapps/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.tsxapps/ui/src/components/views/board-view/kanban-board.tsxapps/ui/src/lib/concurrent-utils.tsapps/ui/src/components/views/board-view/backlog-manager/hooks/use-backlog-manager.tsapps/ui/src/components/views/board-view/backlog-manager/backlog-table.tsxapps/ui/src/routes/board.tsxapps/ui/src/components/views/board-view/backlog-manager/backlog-toolbar.tsxapps/ui/src/components/views/board-view/backlog-manager/backlog-manager.tsxapps/ui/src/components/views/board-view/backlog-manager/backlog-row.tsxapps/ui/src/components/views/board-view/backlog-manager/index.tsapps/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.tsxapps/ui/src/components/views/board-view/kanban-board.tsxapps/ui/src/lib/concurrent-utils.tsapps/ui/src/components/views/board-view/backlog-manager/hooks/use-backlog-manager.tsapps/ui/src/components/views/board-view/backlog-manager/backlog-table.tsxapps/ui/src/routes/board.tsxapps/ui/src/components/views/board-view/backlog-manager/backlog-toolbar.tsxapps/ui/src/components/views/board-view/backlog-manager/backlog-manager.tsxapps/ui/src/components/views/board-view/backlog-manager/backlog-row.tsxapps/ui/src/components/views/board-view/backlog-manager/index.tsapps/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.tsxapps/ui/src/components/views/board-view/kanban-board.tsxapps/ui/src/components/views/board-view/backlog-manager/backlog-table.tsxapps/ui/src/routes/board.tsxapps/ui/src/components/views/board-view/backlog-manager/backlog-toolbar.tsxapps/ui/src/components/views/board-view/backlog-manager/backlog-manager.tsxapps/ui/src/components/views/board-view/backlog-manager/backlog-row.tsxapps/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
validateSearchfunction properly validates themodeparameter, returningundefinedfor 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 inboard-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
nextIndexby capturing the current index before anyawait, 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
mapWithConcurrencyFilteredandmapWithConcurrencyCollectErrorsproperly reusemapWithConcurrency, avoiding code duplication. The type guard inmapWithConcurrencyFiltered(line 115) correctly narrows the type, andmapWithConcurrencyCollectErrorspreserves 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
onManageBacklogprop 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:
onManageBacklogis 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
ImportErrorsBannercorrectly handles the empty state with early return, uses proper ARIA role for accessibility, and has smooth animations. The key usingfilename-indexis 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
handleCategoryChangewrapper appropriately delegates to the hook'supdateFeature.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"andaria-labelfor 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, callsclearSelection()when pressed, and the sameclearSelectionfunction is passed to this button asonClearSelection. 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.
allSelectedandsomeSelectedcompareselectedIds.sizeagainstfeatures.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
BoardModetype 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
onDeletecallback correctly receives afeatureIdstring and passes it tohandleDeleteFeature, which expectsasync (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 typefor 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-editorattribute properly supports the click delegation inhandleRowClick.
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/loggerfor 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
mapWithConcurrencywith 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.
| 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] | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 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 -50Repository: 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 -20Repository: 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.tsRepository: 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 5Repository: 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.
|
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. |
|
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). |
|
@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. |
Summary
This PR introduces a Backlog Manager to Automaker to enable efficient grooming and management of large backlogs.
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
Screenshots
[Include screenshots of the Backlog column Manage button and the Backlog Manager full-page view]


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