@@ -32,6 +32,8 @@ import { TagIcon } from '../icons/TagIcon';
3232import { CubeIcon } from '../icons/CubeIcon' ;
3333import { ChevronsUpIcon } from '../icons/ChevronsUpIcon' ;
3434import { ChevronsDownIcon } from '../icons/ChevronsDownIcon' ;
35+ import { ChevronDownIcon } from '../icons/ChevronDownIcon' ;
36+ import { ChevronRightIcon } from '../icons/ChevronRightIcon' ;
3537import ReactMarkdown from 'react-markdown' ;
3638import remarkGfm from 'remark-gfm' ;
3739import { PencilIcon } from '../icons/PencilIcon' ;
@@ -131,6 +133,19 @@ const STEP_DEFINITIONS: Record<TaskStepType, { label: string; icon: React.Compon
131133 [ TaskStepType . DOCKER_COMPOSE_BUILD ] : { label : 'Docker: Compose Build' , icon : DockerIcon , description : 'Build or rebuild services with Docker Compose.' } ,
132134} ;
133135
136+ const STEPS_WITH_DETAILS = new Set < TaskStepType > ( [
137+ TaskStepType . GitCheckout ,
138+ TaskStepType . SvnSwitch ,
139+ TaskStepType . DelphiBuild ,
140+ TaskStepType . LAZARUS_BUILD ,
141+ TaskStepType . LAZARUS_BUILD_PACKAGE ,
142+ TaskStepType . FPC_TEST_FPCUNIT ,
143+ TaskStepType . DELPHI_PACKAGE_INNO ,
144+ TaskStepType . DELPHI_PACKAGE_NSIS ,
145+ TaskStepType . DELPHI_TEST_DUNITX ,
146+ TaskStepType . RunCommand ,
147+ ] ) ;
148+
134149const STEP_CATEGORIES = [
135150 { name : 'General' , types : [ TaskStepType . RunCommand ] } ,
136151 { name : 'Git' , types : [ TaskStepType . GitPull , TaskStepType . GitFetch , TaskStepType . GitCheckout , TaskStepType . GitStash ] } ,
@@ -217,12 +232,21 @@ const TaskStepItem: React.FC<{
217232 const logger = useLogger ( ) ;
218233 const stepDef = STEP_DEFINITIONS [ step . type ] ;
219234 const isEnabled = step . enabled ?? true ;
235+ const hasDetails = STEPS_WITH_DETAILS . has ( step . type ) ;
236+ const [ isCollapsed , setIsCollapsed ] = useState ( false ) ;
237+ const detailsId = useMemo ( ( ) => `task-step-${ step . id } -details` , [ step . id ] ) ;
220238
221239 // --- HOOKS MOVED TO TOP ---
222240 const toggleTooltip = useTooltip ( isEnabled ? 'Disable Step' : 'Enable Step' ) ;
223241 const duplicateTooltip = useTooltip ( 'Duplicate Step' ) ;
224242 const moveToTopTooltip = useTooltip ( 'Move Step to Top' ) ;
225243 const moveToBottomTooltip = useTooltip ( 'Move Step to Bottom' ) ;
244+
245+ useEffect ( ( ) => {
246+ if ( ! hasDetails ) {
247+ setIsCollapsed ( false ) ;
248+ }
249+ } , [ hasDetails ] ) ;
226250
227251 const selectedDelphiProject = useMemo ( ( ) => {
228252 return projectInfo ?. delphi ?. projects . find ( p => p . path === step . delphiProjectFile ) ;
@@ -301,47 +325,8 @@ const TaskStepItem: React.FC<{
301325 </ div >
302326 ) ;
303327
304- return (
305- < div className = { `bg-white dark:bg-gray-800/50 p-3 rounded-lg border border-gray-200 dark:border-gray-700 space-y-2 transition-opacity ${ ! isEnabled ? 'opacity-50' : '' } ` } >
306- < div className = "flex items-center justify-between" >
307- < div className = "flex items-center gap-3" >
308- < Icon className = "h-6 w-6 text-blue-500" />
309- < div >
310- < p className = "font-semibold text-gray-800 dark:text-gray-200" > { label } </ p >
311- < p className = "text-xs text-gray-500" > Step { index + 1 } </ p >
312- </ div >
313- </ div >
314- < div className = "flex items-center space-x-2" >
315- < label { ...toggleTooltip } className = "relative inline-flex items-center cursor-pointer" >
316- < input type = "checkbox" checked = { isEnabled } onChange = { ( e ) => onStepChange ( step . id , { enabled : e . target . checked } ) } className = "sr-only peer" />
317- < div className = "w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-500/50 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600" > </ div >
318- </ label >
319- < button
320- { ...moveToTopTooltip }
321- type = "button"
322- onClick = { ( ) => onMoveStep ( index , 'top' ) }
323- disabled = { index === 0 }
324- aria-label = "Move step to top"
325- className = "p-1.5 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full"
326- >
327- < ChevronsUpIcon className = "h-4 w-4" />
328- </ button >
329- < button type = "button" onClick = { ( ) => onMoveStep ( index , 'up' ) } disabled = { index === 0 } className = "p-1.5 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full" > < ArrowUpIcon className = "h-4 w-4" /> </ button >
330- < button type = "button" onClick = { ( ) => onMoveStep ( index , 'down' ) } disabled = { index === totalSteps - 1 } className = "p-1.5 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full" > < ArrowDownIcon className = "h-4 w-4" /> </ button >
331- < button
332- { ...moveToBottomTooltip }
333- type = "button"
334- onClick = { ( ) => onMoveStep ( index , 'bottom' ) }
335- disabled = { index === totalSteps - 1 }
336- aria-label = "Move step to bottom"
337- className = "p-1.5 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full"
338- >
339- < ChevronsDownIcon className = "h-4 w-4" />
340- </ button >
341- < button { ...duplicateTooltip } type = "button" onClick = { ( ) => onDuplicateStep ( index ) } className = "p-1.5 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full" > < DocumentDuplicateIcon className = "h-4 w-4" /> </ button >
342- < button type = "button" onClick = { ( ) => onRemoveStep ( step . id ) } className = "p-1.5 text-red-500 hover:bg-red-100 dark:hover:bg-red-900/50 rounded-full" > < TrashIcon className = "h-4 w-4" /> </ button >
343- </ div >
344- </ div >
328+ const detailFields = (
329+ < >
345330 { ( step . type === TaskStepType . GitCheckout || step . type === TaskStepType . SvnSwitch ) && (
346331 < div >
347332 < label className = "text-xs font-medium text-gray-500 dark:text-gray-400" >
@@ -517,7 +502,7 @@ const TaskStepItem: React.FC<{
517502 ( acc [ suggestion . group ] = acc [ suggestion . group ] || [ ] ) . push ( suggestion ) ;
518503 return acc ;
519504 } , { } as Record < string , ProjectSuggestion [ ] > ) ;
520-
505+
521506 const handleSelectChange = ( e : React . ChangeEvent < HTMLSelectElement > ) => {
522507 const newValue = e . target . value ;
523508 // When a predefined command is selected, update the step.
@@ -538,7 +523,7 @@ const TaskStepItem: React.FC<{
538523 ) ) }
539524 < option value = { CUSTOM_COMMAND_VALUE } > Custom Command...</ option >
540525 </ select >
541-
526+
542527 < textarea
543528 placeholder = { `e.g., npm run build -- --env=production\nUse \${VAR_NAME} for variables.` }
544529 value = { step . command || '' }
@@ -550,6 +535,71 @@ const TaskStepItem: React.FC<{
550535 </ div >
551536 ) ;
552537 } ) ( ) }
538+ </ >
539+ ) ;
540+
541+ return (
542+ < div className = { `bg-white dark:bg-gray-800/50 px-3 py-1.5 rounded-lg border border-gray-200 dark:border-gray-700 space-y-1.5 transition-opacity ${ ! isEnabled ? 'opacity-50' : '' } ` } >
543+ < div className = "flex items-center justify-between gap-2.5" >
544+ < div className = "flex items-center gap-1.5" >
545+ < div className = "flex h-6 w-6 items-center justify-center" >
546+ { hasDetails ? (
547+ < button
548+ type = "button"
549+ onClick = { ( ) => setIsCollapsed ( prev => ! prev ) }
550+ aria-label = { `${ isCollapsed ? 'Expand' : 'Collapse' } step details` }
551+ aria-expanded = { ! isCollapsed }
552+ aria-controls = { detailsId }
553+ className = "p-0.5 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full"
554+ >
555+ { isCollapsed ? < ChevronRightIcon className = "h-4 w-4" /> : < ChevronDownIcon className = "h-4 w-4" /> }
556+ </ button >
557+ ) : null }
558+ </ div >
559+ < Icon className = "h-6 w-6 text-blue-500" />
560+ < div >
561+ < p className = "font-semibold text-gray-800 dark:text-gray-200" > { label } </ p >
562+ < p className = "text-xs text-gray-500" > Step { index + 1 } </ p >
563+ </ div >
564+ </ div >
565+ < div className = "flex items-center space-x-1.5" >
566+ < label { ...toggleTooltip } className = "relative inline-flex items-center cursor-pointer" >
567+ < input type = "checkbox" checked = { isEnabled } onChange = { ( e ) => onStepChange ( step . id , { enabled : e . target . checked } ) } className = "sr-only peer" />
568+ < div className = "w-11 h-6 bg-gray-200 peer-focus:outline-none peer-focus:ring-2 peer-focus:ring-blue-500/50 dark:peer-focus:ring-blue-800 rounded-full peer dark:bg-gray-700 peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all dark:border-gray-600 peer-checked:bg-blue-600" > </ div >
569+ </ label >
570+ < button
571+ { ...moveToTopTooltip }
572+ type = "button"
573+ onClick = { ( ) => onMoveStep ( index , 'top' ) }
574+ disabled = { index === 0 }
575+ aria-label = "Move step to top"
576+ className = "p-1 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full"
577+ >
578+ < ChevronsUpIcon className = "h-4 w-4" />
579+ </ button >
580+ < button type = "button" onClick = { ( ) => onMoveStep ( index , 'up' ) } disabled = { index === 0 } className = "p-1 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full" > < ArrowUpIcon className = "h-4 w-4" /> </ button >
581+ < button type = "button" onClick = { ( ) => onMoveStep ( index , 'down' ) } disabled = { index === totalSteps - 1 } className = "p-1 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full" > < ArrowDownIcon className = "h-4 w-4" /> </ button >
582+ < button
583+ { ...moveToBottomTooltip }
584+ type = "button"
585+ onClick = { ( ) => onMoveStep ( index , 'bottom' ) }
586+ disabled = { index === totalSteps - 1 }
587+ aria-label = "Move step to bottom"
588+ className = "p-1 disabled:opacity-30 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full"
589+ >
590+ < ChevronsDownIcon className = "h-4 w-4" />
591+ </ button >
592+ < button { ...duplicateTooltip } type = "button" onClick = { ( ) => onDuplicateStep ( index ) } className = "p-1 text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-full" > < DocumentDuplicateIcon className = "h-4 w-4" /> </ button >
593+ < button type = "button" onClick = { ( ) => onRemoveStep ( step . id ) } className = "p-1 text-red-500 hover:bg-red-100 dark:hover:bg-red-900/50 rounded-full" > < TrashIcon className = "h-4 w-4" /> </ button >
594+ </ div >
595+ </ div >
596+ { hasDetails ? (
597+ < div id = { detailsId } className = { `mt-1.5 space-y-1.5 ${ isCollapsed ? 'hidden' : '' } ` } >
598+ { detailFields }
599+ </ div >
600+ ) : (
601+ detailFields
602+ ) }
553603 </ div >
554604 ) ;
555605} ;
@@ -724,7 +774,7 @@ const NodejsTaskGenerator: React.FC<{
724774
725775
726776 return (
727- < div className = "p-3 mb-4 bg-green-50 dark:bg-gray-900/50 rounded-lg border border-green-200 dark:border-gray-700" >
777+ < div className = "p-3 mb-3 bg-green-50 dark:bg-gray-900/50 rounded-lg border border-green-200 dark:border-gray-700" >
728778 < div className = "flex items-center gap-2 mb-2" >
729779 < NodeIcon className = "h-5 w-5 text-green-500" />
730780 < h3 className = "text-md font-semibold text-gray-800 dark:text-gray-200" > Node.js Project Detected</ h3 >
@@ -780,7 +830,7 @@ const GoTaskGenerator: React.FC<{
780830 } ;
781831
782832 return (
783- < div className = "p-3 mb-4 bg-cyan-50 dark:bg-gray-900/50 rounded-lg border border-cyan-200 dark:border-gray-700" >
833+ < div className = "p-3 mb-3 bg-cyan-50 dark:bg-gray-900/50 rounded-lg border border-cyan-200 dark:border-gray-700" >
784834 < div className = "flex items-center gap-2 mb-2" >
785835 < CodeBracketIcon className = "h-5 w-5 text-cyan-500" />
786836 < h3 className = "text-md font-semibold text-gray-800 dark:text-gray-200" > Go Project Detected</ h3 >
@@ -845,7 +895,7 @@ const RustTaskGenerator: React.FC<{
845895 } ;
846896
847897 return (
848- < div className = "p-3 mb-4 bg-amber-50 dark:bg-gray-900/50 rounded-lg border border-amber-200 dark:border-gray-700" >
898+ < div className = "p-3 mb-3 bg-amber-50 dark:bg-gray-900/50 rounded-lg border border-amber-200 dark:border-gray-700" >
849899 < div className = "flex items-center gap-2 mb-2" >
850900 < CodeBracketIcon className = "h-5 w-5 text-amber-500" />
851901 < h3 className = "text-md font-semibold text-gray-800 dark:text-gray-200" > Rust Project Detected</ h3 >
@@ -911,7 +961,7 @@ const MavenTaskGenerator: React.FC<{
911961 } ;
912962
913963 return (
914- < div className = "p-3 mb-4 bg-orange-50 dark:bg-gray-900/50 rounded-lg border border-orange-200 dark:border-gray-700" >
964+ < div className = "p-3 mb-3 bg-orange-50 dark:bg-gray-900/50 rounded-lg border border-orange-200 dark:border-gray-700" >
915965 < div className = "flex items-center gap-2 mb-2" >
916966 < DocumentTextIcon className = "h-5 w-5 text-orange-500" />
917967 < h3 className = "text-md font-semibold text-gray-800 dark:text-gray-200" > Maven Project Detected</ h3 >
@@ -979,7 +1029,7 @@ const DotnetTaskGenerator: React.FC<{
9791029 } ;
9801030
9811031 return (
982- < div className = "p-3 mb-4 bg-purple-50 dark:bg-gray-900/50 rounded-lg border border-purple-200 dark:border-gray-700" >
1032+ < div className = "p-3 mb-3 bg-purple-50 dark:bg-gray-900/50 rounded-lg border border-purple-200 dark:border-gray-700" >
9831033 < div className = "flex items-center gap-2 mb-2" >
9841034 < CubeIcon className = "h-5 w-5 text-purple-500" />
9851035 < h3 className = "text-md font-semibold text-gray-800 dark:text-gray-200" > .NET Project Detected</ h3 >
@@ -1047,7 +1097,7 @@ const PythonTaskGenerator: React.FC<{
10471097
10481098
10491099 return (
1050- < div className = "p-3 mb-4 bg-blue-50 dark:bg-gray-900/50 rounded-lg border border-blue-200 dark:border-gray-700" >
1100+ < div className = "p-3 mb-3 bg-blue-50 dark:bg-gray-900/50 rounded-lg border border-blue-200 dark:border-gray-700" >
10511101 < div className = "flex items-center gap-2 mb-2" >
10521102 < PythonIcon className = "h-5 w-5 text-blue-500" />
10531103 < h3 className = "text-md font-semibold text-gray-800 dark:text-gray-200" > Python Project Detected</ h3 >
@@ -1120,7 +1170,7 @@ const DockerTaskGenerator: React.FC<{
11201170 ] ;
11211171
11221172 return (
1123- < div className = "p-3 mb-4 bg-sky-50 dark:bg-gray-900/50 rounded-lg border border-sky-200 dark:border-gray-700" >
1173+ < div className = "p-3 mb-3 bg-sky-50 dark:bg-gray-900/50 rounded-lg border border-sky-200 dark:border-gray-700" >
11241174 < div className = "flex items-center gap-2 mb-2" >
11251175 < DockerIcon className = "h-5 w-5 text-sky-500" />
11261176 < h3 className = "text-md font-semibold text-gray-800 dark:text-gray-200" > Docker Artifacts Detected</ h3 >
@@ -1218,7 +1268,7 @@ const DelphiTaskGenerator: React.FC<{
12181268 } ;
12191269
12201270 return (
1221- < div className = "p-3 mb-4 bg-indigo-50 dark:bg-gray-900/50 rounded-lg border border-indigo-200 dark:border-gray-700" >
1271+ < div className = "p-3 mb-3 bg-indigo-50 dark:bg-gray-900/50 rounded-lg border border-indigo-200 dark:border-gray-700" >
12221272 < div className = "flex items-center gap-2 mb-2" >
12231273 < BeakerIcon className = "h-5 w-5 text-indigo-500" />
12241274 < h3 className = "text-md font-semibold text-gray-800 dark:text-gray-200" > Delphi Project Detected</ h3 >
@@ -1279,7 +1329,7 @@ const LazarusTaskGenerator: React.FC<{
12791329 } ;
12801330
12811331 return (
1282- < div className = "p-3 mb-4 bg-teal-50 dark:bg-gray-900/50 rounded-lg border border-teal-200 dark:border-gray-700" >
1332+ < div className = "p-3 mb-3 bg-teal-50 dark:bg-gray-900/50 rounded-lg border border-teal-200 dark:border-gray-700" >
12831333 < div className = "flex items-center gap-2 mb-2" >
12841334 < BeakerIcon className = "h-5 w-5 text-teal-500" />
12851335 < h3 className = "text-md font-semibold text-gray-800 dark:text-gray-200" > Lazarus/FPC Project Detected</ h3 >
@@ -1423,7 +1473,7 @@ const TaskStepsEditor: React.FC<{
14231473 } , [ repository ?. vcs , projectInfo ] ) ;
14241474
14251475 return (
1426- < div className = "space-y-4 " >
1476+ < div className = "space-y-3 " >
14271477 < div className = "flex items-center justify-between gap-4" >
14281478 < input
14291479 type = "text"
@@ -1441,13 +1491,13 @@ const TaskStepsEditor: React.FC<{
14411491 </ div >
14421492 </ div >
14431493
1444- < div className = "space-y-3 " >
1494+ < div className = "space-y-2.5 " >
14451495 < TaskVariablesEditor variables = { task . variables } onVariablesChange = { handleVariablesChange } />
14461496 < TaskEnvironmentVariablesEditor variables = { task . environmentVariables } onVariablesChange = { handleEnvironmentVariablesChange } />
14471497 </ div >
14481498
14491499 { task . steps . length === 0 && (
1450- < div className = "text-center py-6 px-4 bg-gray-50 dark:bg-gray-900/50 rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-700" >
1500+ < div className = "text-center py-5 px-4 bg-gray-50 dark:bg-gray-900/50 rounded-lg border-2 border-dashed border-gray-300 dark:border-gray-700" >
14511501 < CubeTransparentIcon className = "mx-auto h-10 w-10 text-gray-400" />
14521502 < h3 className = "mt-2 text-sm font-medium text-gray-800 dark:text-gray-200" > This task has no steps.</ h3 >
14531503 < p className = "mt-1 text-xs text-gray-500" > Add steps manually to begin.</ p >
@@ -1464,7 +1514,7 @@ const TaskStepsEditor: React.FC<{
14641514 < LazarusTaskGenerator lazarusCaps = { projectInfo ?. lazarus } onAddTask = { onAddTask } />
14651515 < DelphiTaskGenerator delphiCaps = { projectInfo ?. delphi } onAddTask = { onAddTask } />
14661516
1467- < div className = "space-y-3 " >
1517+ < div className = "space-y-2 " >
14681518 { task . steps . map ( ( step , index ) => (
14691519 < TaskStepItem
14701520 key = { step . id }
@@ -1483,15 +1533,15 @@ const TaskStepsEditor: React.FC<{
14831533 </ div >
14841534
14851535 { isAddingStep && (
1486- < div className = "space-y-4 " >
1536+ < div className = "space-y-3 " >
14871537 { STEP_CATEGORIES . map ( category => {
14881538 const relevantSteps = category . types . filter ( type => availableSteps . includes ( type ) ) ;
14891539 if ( relevantSteps . length === 0 ) return null ;
14901540
14911541 return (
14921542 < div key = { category . name } >
14931543 < h4 className = "text-sm font-semibold text-gray-600 dark:text-gray-400 mb-2" > { category . name } </ h4 >
1494- < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3 " >
1544+ < div className = "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-2.5 " >
14951545 { relevantSteps . map ( type => {
14961546 const { label, icon : Icon , description } = STEP_DEFINITIONS [ type ] ;
14971547 return (
0 commit comments