From 341a6534e6cdfb2fcf2f4aab8c43dcea58a44d92 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Mon, 2 Mar 2026 20:45:23 -0800 Subject: [PATCH] refactor: extract shared isBacklogLikeStatus helper and improve comments Address PR #825 review feedback: - Extract duplicated backlog-like status check into shared helper in constants.ts - Improve spec-parser regex comment to clarify subsection preservation - Add file path reference in row-actions.tsx comment for traceability Co-Authored-By: Claude Opus 4.6 --- apps/server/src/services/spec-parser.ts | 3 +- apps/ui/src/components/views/board-view.tsx | 9 +-- .../components/list-view/row-actions.tsx | 68 +++++++++---------- .../components/views/board-view/constants.ts | 20 ++++++ 4 files changed, 57 insertions(+), 43 deletions(-) diff --git a/apps/server/src/services/spec-parser.ts b/apps/server/src/services/spec-parser.ts index 295246fb..811ed925 100644 --- a/apps/server/src/services/spec-parser.ts +++ b/apps/server/src/services/spec-parser.ts @@ -214,7 +214,8 @@ export function extractSummary(text: string): string | null { } // Check for ## Summary section (use last match) - // Use \n## [^#] to stop at same-level headers (## Foo) but NOT subsections (### Root Cause) + // Stop at \n## [^#] (same-level headers like "## Changes") but preserve ### subsections + // (like "### Root Cause", "### Fix Applied") that belong to the summary content. const sectionMatches = text.matchAll(/##\s*Summary\s*\n+([\s\S]*?)(?=\n## [^#]|\n\*\*|$)/gi); const sectionMatch = getLastMatch(sectionMatches); if (sectionMatch) { diff --git a/apps/ui/src/components/views/board-view.tsx b/apps/ui/src/components/views/board-view.tsx index f0c92784..ecbbe8a7 100644 --- a/apps/ui/src/components/views/board-view.tsx +++ b/apps/ui/src/components/views/board-view.tsx @@ -83,7 +83,7 @@ import type { StashApplyConflictInfo, } from './board-view/worktree-panel/types'; import { BoardErrorBoundary } from './board-view/board-error-boundary'; -import { COLUMNS, getColumnsWithPipeline } from './board-view/constants'; +import { COLUMNS, getColumnsWithPipeline, isBacklogLikeStatus } from './board-view/constants'; import { useBoardFeatures, useBoardDragDrop, @@ -1908,12 +1908,7 @@ export function BoardView({ initialFeatureId }: BoardViewProps) { // 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) { + if (isBacklogLikeStatus(feature.status) && !isRunning) { setEditingFeature(feature); } else { handleViewOutput(feature); diff --git a/apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx b/apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx index 173e014b..755ed017 100644 --- a/apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx +++ b/apps/ui/src/components/views/board-view/components/list-view/row-actions.tsx @@ -30,6 +30,7 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import type { Feature } from '@/store/app-store'; +import { isBacklogLikeStatus } from '../../constants'; /** * Action handler types for row actions @@ -431,44 +432,41 @@ export const RowActions = memo(function RowActions({ )} - {/* Running task with stale status (backlog/ready/interrupted but tracked as running). + {/* Running task with stale status - the feature is tracked as running but its + persisted status hasn't caught up yet during WebSocket/cache sync delays. 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 && ( + (hooks/use-board-column-features.ts) but no other menu block matches their + stale status, so we provide running-appropriate actions here. */} + {!isCurrentAutoTask && isRunningTask && isBacklogLikeStatus(feature.status) && ( + <> + {handlers.onViewOutput && ( + + )} + + {handlers.onSpawnTask && ( + + )} + {handlers.onForceStop && ( + <> + - )} - - {handlers.onSpawnTask && ( - - )} - {handlers.onForceStop && ( - <> - - - - )} - - )} + + )} + + )} {/* Backlog actions */} {!isCurrentAutoTask && diff --git a/apps/ui/src/components/views/board-view/constants.ts b/apps/ui/src/components/views/board-view/constants.ts index fda19ebf..8ec071d8 100644 --- a/apps/ui/src/components/views/board-view/constants.ts +++ b/apps/ui/src/components/views/board-view/constants.ts @@ -136,6 +136,26 @@ export function getPipelineInsertIndex(): number { return BASE_COLUMNS.length; } +/** + * Statuses that display in the backlog column because they don't have dedicated columns: + * - 'backlog': Default state for new features + * - 'ready': Feature has an approved plan, waiting for execution + * - 'interrupted': Feature execution was aborted (user stopped it, server restart) + * - 'merge_conflict': Automatic merge failed, user must resolve conflicts + * + * Used to determine row click behavior and menu actions when a feature is running + * but its status hasn't updated yet (race condition during WebSocket/cache sync). + * See use-board-column-features.ts for the column assignment logic. + */ +export function isBacklogLikeStatus(status: string): boolean { + return ( + status === 'backlog' || + status === 'ready' || + status === 'interrupted' || + status === 'merge_conflict' + ); +} + /** * Check if a status is a pipeline status */