@@ -70,7 +70,15 @@ export default function ConfigurePage() {
7070 alert ( "Pick a repo + branch on the Connect page first." ) ;
7171 return ;
7272 }
73- await regenerate ( { repo, branch } ) ;
73+
74+ await regenerate ( {
75+ repo,
76+ branch,
77+ template,
78+ provider,
79+ stages,
80+ options,
81+ } ) ;
7482 } ;
7583
7684 const handleOpenPr = async ( ) => {
@@ -95,6 +103,36 @@ export default function ConfigurePage() {
95103 const trimmed = chatInput . trim ( ) ;
96104 if ( ! trimmed ) return ;
97105
106+ // --- Sync AI intent with pipeline stages BEFORE sending to backend ---
107+ // The AI is a planner, not an authority. UI state must be updated first.
108+ const lower = trimmed . toLowerCase ( ) ;
109+
110+ // Reset to defaults first
111+ let nextStages : Array < "build" | "test" | "deploy" > = [ "build" , "test" , "deploy" ] ;
112+
113+ if ( lower . includes ( "just build" ) || lower . includes ( "only build" ) ) {
114+ nextStages = [ "build" ] ;
115+ } else if (
116+ lower . includes ( "build and test" ) ||
117+ ( lower . includes ( "build" ) && lower . includes ( "test" ) && ! lower . includes ( "deploy" ) )
118+ ) {
119+ nextStages = [ "build" , "test" ] ;
120+ } else if (
121+ lower . includes ( "no deploy" ) ||
122+ lower . includes ( "without deploy" )
123+ ) {
124+ nextStages = [ "build" , "test" ] ;
125+ }
126+
127+ // Apply stage changes to the pipeline store
128+ ( [ "build" , "test" , "deploy" ] as const ) . forEach ( ( stage ) => {
129+ const shouldEnable = nextStages . includes ( stage ) ;
130+ const isEnabled = stages . includes ( stage ) ;
131+ if ( shouldEnable !== isEnabled ) {
132+ toggleStage ( stage ) ;
133+ }
134+ } ) ;
135+
98136 if ( ! repo || ! branch ) {
99137 alert (
100138 "Pick a repo + branch on the Connect page first so I can give better suggestions."
@@ -114,7 +152,7 @@ export default function ConfigurePage() {
114152 template,
115153 provider,
116154 branch,
117- stages,
155+ stages : nextStages ,
118156 options,
119157 } ;
120158
@@ -159,9 +197,8 @@ export default function ConfigurePage() {
159197 pipelineName,
160198 branch,
161199 provider,
162- stages,
163- // Keep a copy of the current options in wizard context so follow-up prompts
164- // can reference the selected provider identity (AWS role / GCP service account).
200+ // 🔒 Never override stages from backend / metadata
201+ stages : pipelineSnapshot . stages ,
165202 options,
166203 } as any ) ;
167204 }
@@ -249,8 +286,8 @@ export default function ConfigurePage() {
249286 className = "rounded-md border border-white/25 bg-white px-3 py-2 text-sm text-slate-900 placeholder-slate-500"
250287 >
251288 < option value = "node_app" > Node.js app</ option >
252- < option value = "node_library" > Node.js library </ option >
253- < option value = "react_vite" > React/Vite app </ option >
289+ < option value = "python_app" > Python App </ option >
290+ < option value = "container_service" > Container </ option >
254291 </ select >
255292 < span className = "text-xs text-slate-200" >
256293 Pick the closest match to your repo; the MCP backend refines it.
@@ -296,54 +333,98 @@ export default function ConfigurePage() {
296333 </ div >
297334 </ fieldset >
298335
299- { /* Node version + commands */ }
336+ { /* Runtime version + commands */ }
300337 < div className = "grid gap-4" >
301- < label className = "grid gap-1" >
302- < span className = "text-sm font-medium text-slate-800" > Node version</ span >
303- < input
304- disabled = { busy }
305- value = { options . nodeVersion }
306- onChange = { ( e ) => setOption ( "nodeVersion" , e . target . value ) }
307- className = "rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
308- placeholder = "20"
309- />
310- </ label >
338+ { /* Node.js version: only show for node_app AND build stage enabled */ }
339+ { template === "node_app" && stages . includes ( "build" ) && (
340+ < label className = "grid gap-1" >
341+ < span className = "text-sm font-medium text-slate-800" > Node.js version</ span >
342+ < input
343+ disabled = { busy }
344+ value = { options . nodeVersion }
345+ onChange = { ( e ) => setOption ( "nodeVersion" , e . target . value ) }
346+ className = "rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
347+ placeholder = "20"
348+ />
349+ </ label >
350+ ) }
311351
312- < label className = "grid gap-1" >
313- < span className = "text-sm font-medium text-slate-800" > Install command</ span >
314- < input
315- disabled = { busy }
316- value = { options . installCmd }
317- onChange = { ( e ) => setOption ( "installCmd" , e . target . value ) }
318- className = "rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
319- placeholder = "npm ci"
320- />
321- </ label >
352+ { /* Install command: only show if build stage enabled */ }
353+ { stages . includes ( "build" ) && (
354+ < label className = "grid gap-1" >
355+ < span className = "text-sm font-medium text-slate-800" >
356+ { template === "node_app"
357+ ? "Install command (npm)"
358+ : template === "python_app"
359+ ? "Install command (pip)"
360+ : "Install command" }
361+ </ span >
362+ < input
363+ disabled = { busy }
364+ value = { options . installCmd }
365+ onChange = { ( e ) => setOption ( "installCmd" , e . target . value ) }
366+ className = "rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
367+ placeholder = {
368+ template === "node_app"
369+ ? "npm ci"
370+ : template === "python_app"
371+ ? "pip install -r requirements.txt"
372+ : ""
373+ }
374+ />
375+ </ label >
376+ ) }
322377
323- < label className = "grid gap-1" >
324- < span className = "text-sm font-medium text-slate-800" > Test command</ span >
325- < input
326- disabled = { busy }
327- value = { options . testCmd }
328- onChange = { ( e ) => setOption ( "testCmd" , e . target . value ) }
329- className = "rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
330- placeholder = "npm test"
331- />
332- </ label >
378+ { /* Test command: only show if test stage enabled */ }
379+ { stages . includes ( "test" ) && (
380+ < label className = "grid gap-1" >
381+ < span className = "text-sm font-medium text-slate-800" >
382+ { template === "node_app"
383+ ? "Test command (npm)"
384+ : template === "python_app"
385+ ? "Test command (pytest)"
386+ : "Test command" }
387+ </ span >
388+ < input
389+ disabled = { busy }
390+ value = { options . testCmd }
391+ onChange = { ( e ) => setOption ( "testCmd" , e . target . value ) }
392+ className = "rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
393+ placeholder = {
394+ template === "node_app"
395+ ? "npm test"
396+ : template === "python_app"
397+ ? "pytest"
398+ : ""
399+ }
400+ />
401+ </ label >
402+ ) }
333403
334- < label className = "grid gap-1" >
335- < span className = "text-sm font-medium text-slate-800" > Build command</ span >
336- < input
337- disabled = { busy }
338- value = { options . buildCmd }
339- onChange = { ( e ) => setOption ( "buildCmd" , e . target . value ) }
340- className = "rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
341- placeholder = "npm run build"
342- />
343- </ label >
404+ { /* Build command: only show if build stage enabled */ }
405+ { stages . includes ( "build" ) && (
406+ < label className = "grid gap-1" >
407+ < span className = "text-sm font-medium text-slate-800" >
408+ { template === "node_app"
409+ ? "Build command (npm)"
410+ : "Build command" }
411+ </ span >
412+ < input
413+ disabled = { busy }
414+ value = { options . buildCmd }
415+ onChange = { ( e ) => setOption ( "buildCmd" , e . target . value ) }
416+ className = "rounded-md border border-white/25 px-3 py-2 text-sm font-mono text-white bg-white/10 placeholder-white/60 disabled:bg-white/5 disabled:text-slate-400"
417+ placeholder = {
418+ template === "node_app"
419+ ? "npm run build"
420+ : ""
421+ }
422+ />
423+ </ label >
424+ ) }
344425 </ div >
345426
346- { provider === "aws" && (
427+ { provider === "aws" && stages . includes ( "deploy" ) && (
347428 < >
348429 < label className = "grid gap-1" >
349430 < span className = "text-sm font-medium" > AWS Role (OIDC)</ span >
0 commit comments