@@ -3,15 +3,7 @@ import { type TimestampFormat } from "../appSettings";
33import { Button } from "./ui/button" ;
44import { ScrollArea } from "./ui/scroll-area" ;
55import ChatMarkdown from "./ChatMarkdown" ;
6- import {
7- CheckIcon ,
8- ChevronDownIcon ,
9- ChevronRightIcon ,
10- EllipsisIcon ,
11- LoaderIcon ,
12- PanelRightCloseIcon ,
13- } from "lucide-react" ;
14- import { cn } from "~/lib/utils" ;
6+ import { ChevronDownIcon , ChevronRightIcon , EllipsisIcon , PanelRightCloseIcon } from "lucide-react" ;
157import type { ActivePlanState } from "../session-logic" ;
168import type { LatestProposedPlanState } from "../session-logic" ;
179import {
@@ -21,6 +13,8 @@ import {
2113 downloadPlanAsTextFile ,
2214 stripDisplayedPlanMarkdown ,
2315} from "../proposedPlan" ;
16+ import { extractPlanChecklistItems } from "../planChecklist" ;
17+ import PlanChecklist from "./PlanChecklist" ;
2418import { Menu , MenuItem , MenuPopup , MenuTrigger } from "./ui/menu" ;
2519import { readNativeApi } from "~/nativeApi" ;
2620import { toastManager } from "./ui/toast" ;
@@ -33,28 +27,6 @@ const PLAN_SIDEBAR_DEFAULT_WIDTH = 340;
3327const PLAN_SIDEBAR_MIN_WIDTH = 260 ;
3428const PLAN_SIDEBAR_MAX_WIDTH = 800 ;
3529
36- function stepStatusIcon ( status : string ) : React . ReactNode {
37- if ( status === "completed" ) {
38- return (
39- < span className = "flex size-4 shrink-0 items-center justify-center text-emerald-500" >
40- < CheckIcon className = "size-3" />
41- </ span >
42- ) ;
43- }
44- if ( status === "inProgress" ) {
45- return (
46- < span className = "flex size-4 shrink-0 items-center justify-center text-blue-400" >
47- < LoaderIcon className = "size-3 animate-spin" />
48- </ span >
49- ) ;
50- }
51- return (
52- < span className = "flex size-4 shrink-0 items-center justify-center" >
53- < span className = "size-1.5 rounded-full bg-muted-foreground/25" />
54- </ span >
55- ) ;
56- }
57-
5830interface PlanSidebarProps {
5931 activePlan : ActivePlanState | null ;
6032 activeProposedPlan : LatestProposedPlanState | null ;
@@ -175,7 +147,7 @@ const PlanSidebar = memo(function PlanSidebar({
175147 onClose,
176148} : PlanSidebarProps ) {
177149 const hasActiveSteps = ( activePlan ?. steps . length ?? 0 ) > 0 ;
178- const [ proposedPlanExpanded , setProposedPlanExpanded ] = useState ( ! hasActiveSteps ) ;
150+ const [ proposedPlanExpanded , setProposedPlanExpanded ] = useState ( false ) ;
179151 const [ isSavingToWorkspace , setIsSavingToWorkspace ] = useState ( false ) ;
180152 const { copyToClipboard, isCopied } = useCopyToClipboard ( ) ;
181153 const { width, railProps } = useResizablePlanSidebar ( ) ;
@@ -185,12 +157,34 @@ const PlanSidebar = memo(function PlanSidebar({
185157 const displayedPlanMarkdown = planMarkdown ? stripDisplayedPlanMarkdown ( planMarkdown ) : null ;
186158 const planTitle = planMarkdown ? proposedPlanTitle ( planMarkdown ) : null ;
187159
188- // Auto-expand the full plan when there are no active execution steps
160+ // Derive checklist items: prefer live execution steps; fall back to markdown extraction.
161+ // Always normalised to { text, status } for the PlanChecklist component.
162+ const checklistItems = useMemo <
163+ Array < { text : string ; status : "pending" | "inProgress" | "completed" } >
164+ > ( ( ) => {
165+ if ( hasActiveSteps && activePlan ) {
166+ return activePlan . steps . map ( ( s ) => ( { text : s . step , status : s . status } ) ) ;
167+ }
168+ if ( planMarkdown ) {
169+ const extracted = extractPlanChecklistItems ( planMarkdown ) ;
170+ if ( extracted . length > 0 ) {
171+ return extracted . map ( ( item ) => ( {
172+ text : item . text ,
173+ status : item . completed ? ( "completed" as const ) : ( "pending" as const ) ,
174+ } ) ) ;
175+ }
176+ }
177+ return [ ] ;
178+ } , [ hasActiveSteps , activePlan , planMarkdown ] ) ;
179+
180+ const hasChecklist = checklistItems . length > 0 ;
181+
182+ // Auto-expand markdown when there are no checklist items and no active steps.
189183 useEffect ( ( ) => {
190- if ( ! hasActiveSteps && planMarkdown ) {
184+ if ( ! hasChecklist && planMarkdown ) {
191185 setProposedPlanExpanded ( true ) ;
192186 }
193- } , [ hasActiveSteps , planMarkdown ] ) ;
187+ } , [ hasChecklist , planMarkdown ] ) ;
194188
195189 const handleCopyPlan = useCallback ( ( ) => {
196190 if ( ! planMarkdown ) return ;
@@ -320,33 +314,10 @@ const PlanSidebar = memo(function PlanSidebar({
320314 </ p >
321315 ) : null }
322316
323- { /* Plan Steps */ }
324- { activePlan && activePlan . steps . length > 0 ? (
325- < div className = "space-y-0.5" >
326- { activePlan . steps . map ( ( step ) => (
327- < div
328- key = { `${ step . status } :${ step . step } ` }
329- className = "flex items-start gap-2 px-1 py-1.5"
330- >
331- < div className = "mt-0.5" > { stepStatusIcon ( step . status ) } </ div >
332- < p
333- className = { cn (
334- "text-[13px] leading-snug" ,
335- step . status === "completed"
336- ? "text-muted-foreground/40 line-through decoration-muted-foreground/20"
337- : step . status === "inProgress"
338- ? "text-foreground/90"
339- : "text-muted-foreground/60" ,
340- ) }
341- >
342- { step . step }
343- </ p >
344- </ div >
345- ) ) }
346- </ div >
347- ) : null }
317+ { /* Checklist (primary view) */ }
318+ { hasChecklist ? < PlanChecklist items = { checklistItems } live = { hasActiveSteps } /> : null }
348319
349- { /* Proposed Plan Markdown */ }
320+ { /* Proposed Plan Markdown (collapsible detail) */ }
350321 { planMarkdown ? (
351322 < div className = "space-y-2" >
352323 < button
@@ -360,7 +331,7 @@ const PlanSidebar = memo(function PlanSidebar({
360331 < ChevronRightIcon className = "size-3 shrink-0 text-muted-foreground/40 transition-transform" />
361332 ) }
362333 < span className = "text-[10px] font-semibold tracking-widest text-muted-foreground/40 uppercase group-hover:text-muted-foreground/60" >
363- { planTitle ?? " Full Plan" }
334+ Full Plan
364335 </ span >
365336 </ button >
366337 { proposedPlanExpanded ? (
@@ -376,7 +347,7 @@ const PlanSidebar = memo(function PlanSidebar({
376347 ) : null }
377348
378349 { /* Empty state */ }
379- { ! activePlan && ! planMarkdown ? (
350+ { ! hasChecklist && ! planMarkdown ? (
380351 < div className = "flex flex-col items-center justify-center py-12 text-center" >
381352 < p className = "text-[13px] text-muted-foreground/40" > No active plan yet.</ p >
382353 < p className = "mt-1 text-[11px] text-muted-foreground/30" >
0 commit comments