mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-16 21:53:07 +00:00
Changes from fix/board-crash-new-feat
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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.');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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 &&
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user