Changes from fix/board-crash-new-feat

This commit is contained in:
gsxdsm
2026-03-02 20:32:56 -08:00
committed by gsxdsm
parent 54d69e907b
commit 4a128efbf4
6 changed files with 117 additions and 17 deletions

View File

@@ -214,10 +214,13 @@ export function extractSummary(text: string): string | null {
} }
// Check for ## Summary section (use last match) // Check for ## Summary section (use last match)
const sectionMatches = text.matchAll(/##\s*Summary\s*\n+([\s\S]*?)(?=\n##|\n\*\*|$)/gi); // Use \n## [^#] to stop at same-level headers (## Foo) but NOT subsections (### Root Cause)
const sectionMatches = text.matchAll(/##\s*Summary\s*\n+([\s\S]*?)(?=\n## [^#]|\n\*\*|$)/gi);
const sectionMatch = getLastMatch(sectionMatches); const sectionMatch = getLastMatch(sectionMatches);
if (sectionMatch) { if (sectionMatch) {
return truncate(sectionMatch[1].trim(), 500); const content = sectionMatch[1].trim();
// Keep full content (including ### subsections) up to max length
return content.length > 500 ? `${content.substring(0, 500)}...` : content;
} }
// Check for **Goal**: section (lite mode, use last match) // Check for **Goal**: section (lite mode, use last match)

View File

@@ -573,6 +573,55 @@ Implementation details.
`; `;
expect(extractSummary(text)).toBe('Summary content here.'); expect(extractSummary(text)).toBe('Summary content here.');
}); });
it('should include ### subsections within the summary (not cut off at ### Root Cause)', () => {
const text = `
## Summary
Overview of changes.
### Root Cause
The bug was caused by X.
### Fix Applied
Changed Y to Z.
## Other Section
More content.
`;
const result = extractSummary(text);
expect(result).not.toBeNull();
expect(result).toContain('Overview of changes.');
expect(result).toContain('### Root Cause');
expect(result).toContain('The bug was caused by X.');
expect(result).toContain('### Fix Applied');
expect(result).toContain('Changed Y to Z.');
expect(result).not.toContain('## Other Section');
});
it('should include ### subsections and stop at next ## header', () => {
const text = `
## Summary
Brief intro.
### Changes
- File A modified
- File B added
### Notes
Important context.
## Implementation
Details here.
`;
const result = extractSummary(text);
expect(result).not.toBeNull();
expect(result).toContain('Brief intro.');
expect(result).toContain('### Changes');
expect(result).toContain('### Notes');
expect(result).not.toContain('## Implementation');
});
}); });
describe('**Goal**: section (lite planning mode)', () => { describe('**Goal**: section (lite planning mode)', () => {
@@ -692,7 +741,7 @@ Summary section content.
expect(extractSummary('Random text without any summary patterns')).toBeNull(); expect(extractSummary('Random text without any summary patterns')).toBeNull();
}); });
it('should handle multiple paragraph summaries (return first paragraph)', () => { it('should include all paragraphs in ## Summary section', () => {
const text = ` const text = `
## Summary ## Summary
@@ -702,7 +751,9 @@ Second paragraph of summary.
## Other ## Other
`; `;
expect(extractSummary(text)).toBe('First paragraph of summary.'); const result = extractSummary(text);
expect(result).toContain('First paragraph of summary.');
expect(result).toContain('Second paragraph of summary.');
}); });
}); });

View File

@@ -1905,7 +1905,15 @@ export function BoardView({ initialFeatureId }: BoardViewProps) {
selectedFeatureIds={selectedFeatureIds} selectedFeatureIds={selectedFeatureIds}
onToggleFeatureSelection={toggleFeatureSelection} onToggleFeatureSelection={toggleFeatureSelection}
onRowClick={(feature) => { onRowClick={(feature) => {
if (feature.status === 'backlog') { // Running features should always show logs, even if status is
// stale (still 'backlog'/'ready'/'interrupted' during race window)
const isRunning = runningAutoTasksAllWorktrees.includes(feature.id);
const isBacklogLike =
feature.status === 'backlog' ||
feature.status === 'merge_conflict' ||
feature.status === 'ready' ||
feature.status === 'interrupted';
if (isBacklogLike && !isRunning) {
setEditingFeature(feature); setEditingFeature(feature);
} else { } else {
handleViewOutput(feature); handleViewOutput(feature);

View File

@@ -431,6 +431,45 @@ export const RowActions = memo(function RowActions({
</> </>
)} )}
{/* Running task with stale status (backlog/ready/interrupted but tracked as running).
These features are placed in the in_progress column by useBoardColumnFeatures
but their actual status hasn't updated yet, so no other menu block matches. */}
{!isCurrentAutoTask &&
isRunningTask &&
(feature.status === 'backlog' ||
feature.status === 'ready' ||
feature.status === 'interrupted' ||
feature.status === 'merge_conflict') && (
<>
{handlers.onViewOutput && (
<MenuItem
icon={FileText}
label="View Logs"
onClick={withClose(handlers.onViewOutput)}
/>
)}
<MenuItem icon={Edit} label="Edit" onClick={withClose(handlers.onEdit)} />
{handlers.onSpawnTask && (
<MenuItem
icon={GitFork}
label="Spawn Sub-Task"
onClick={withClose(handlers.onSpawnTask)}
/>
)}
{handlers.onForceStop && (
<>
<DropdownMenuSeparator />
<MenuItem
icon={StopCircle}
label="Force Stop"
onClick={withClose(handlers.onForceStop)}
variant="destructive"
/>
</>
)}
</>
)}
{/* Backlog actions */} {/* Backlog actions */}
{!isCurrentAutoTask && {!isCurrentAutoTask &&
!isRunningTask && !isRunningTask &&

View File

@@ -115,16 +115,14 @@ export function useBoardFeatures({ currentProject }: UseBoardFeaturesProps) {
// Board view only reacts to events for the currently selected project // Board view only reacts to events for the currently selected project
const eventProjectId = ('projectId' in event && event.projectId) || projectId; const eventProjectId = ('projectId' in event && event.projectId) || projectId;
if (event.type === 'auto_mode_feature_start') { // NOTE: auto_mode_feature_start and auto_mode_feature_complete are NOT handled here
// Reload features when a feature starts to ensure status update (backlog -> in_progress) is reflected // for feature list reloading. That is handled by useAutoModeQueryInvalidation which
logger.info( // invalidates the features.all query on those events. Duplicate invalidation here
`[BoardFeatures] Feature ${event.featureId} started for project ${projectPath}, reloading features to update status...` // caused a re-render cascade through DndContext that triggered React error #185
); // (maximum update depth exceeded), crashing the board view with an infinite spinner
loadFeatures(); // when a new feature was added and moved to in_progress.
} else if (event.type === 'auto_mode_feature_complete') {
// Reload features when a feature is completed if (event.type === 'auto_mode_feature_complete') {
logger.info('Feature completed, reloading features...');
loadFeatures();
// Play ding sound when feature is done (unless muted) // Play ding sound when feature is done (unless muted)
const { muteDoneSound } = useAppStore.getState(); const { muteDoneSound } = useAppStore.getState();
if (!muteDoneSound) { if (!muteDoneSound) {

View File

@@ -1,4 +1,5 @@
import { import {
memo,
useMemo, useMemo,
useRef, useRef,
useState, useState,
@@ -280,7 +281,7 @@ function VirtualizedList<Item extends VirtualListItem>({
); );
} }
export function KanbanBoard({ export const KanbanBoard = memo(function KanbanBoard({
activeFeature, activeFeature,
getColumnFeatures, getColumnFeatures,
backgroundImageStyle, backgroundImageStyle,
@@ -719,4 +720,4 @@ export function KanbanBoard({
</DragOverlay> </DragOverlay>
</div> </div>
); );
} });