@@ -180,6 +180,13 @@ export function ConfigEditor() {
180180 throw new Error ( 'Invalid config response' ) ;
181181 }
182182
183+ // Ensure role menu options have stable IDs
184+ if ( data . welcome ?. roleMenu ?. options ) {
185+ data . welcome . roleMenu . options = data . welcome . roleMenu . options . map ( ( opt ) => ( {
186+ ...opt ,
187+ id : opt . id || crypto . randomUUID ( ) ,
188+ } ) ) ;
189+ }
183190 setSavedConfig ( data ) ;
184191 setDraftConfig ( structuredClone ( data ) ) ;
185192 setDmStepsRaw ( ( data . welcome ?. dmSequence ?. steps ?? [ ] ) . join ( '\n' ) ) ;
@@ -208,6 +215,12 @@ export function ConfigEditor() {
208215 // Currently only validates system prompt length; extend with additional checks as needed.
209216 const hasValidationErrors = useMemo ( ( ) => {
210217 if ( ! draftConfig ) return false ;
218+ // Role menu validation: all options must have non-empty label and roleId
219+ const roleMenuOptions = draftConfig . welcome ?. roleMenu ?. options ?? [ ] ;
220+ const hasRoleMenuErrors = roleMenuOptions . some (
221+ ( opt ) => ! opt . label ?. trim ( ) || ! opt . roleId ?. trim ( ) ,
222+ ) ;
223+ if ( hasRoleMenuErrors ) return true ;
211224 const promptLength = draftConfig . ai ?. systemPrompt ?. length ?? 0 ;
212225 return promptLength > SYSTEM_PROMPT_MAX_LENGTH ;
213226 } , [ draftConfig ] ) ;
@@ -773,7 +786,7 @@ export function ConfigEditor() {
773786 </ div >
774787 < div className = "space-y-3" >
775788 { ( draftConfig . welcome ?. roleMenu ?. options ?? [ ] ) . map ( ( opt , i ) => (
776- < div key = { i } className = "flex flex-col gap-2 rounded-md border p-2" >
789+ < div key = { opt . id } className = "flex flex-col gap-2 rounded-md border p-2" >
777790 < div className = "flex items-center gap-2" >
778791 < input
779792 type = "text"
@@ -791,10 +804,15 @@ export function ConfigEditor() {
791804 variant = "ghost"
792805 size = "sm"
793806 onClick = { ( ) => {
794- const opts = [ ...( draftConfig . welcome ?. roleMenu ?. options ?? [ ] ) ] . filter ( ( _ , idx ) => idx !== i ) ;
807+ const opts = [ ...( draftConfig . welcome ?. roleMenu ?. options ?? [ ] ) ] . filter (
808+ ( o ) => o . id !== opt . id ,
809+ ) ;
795810 updateWelcomeRoleMenu ( 'options' , opts ) ;
796811 } }
797- disabled = { saving || ( draftConfig . welcome ?. roleMenu ?. options ?? [ ] ) . length <= 1 }
812+ disabled = {
813+ saving || ( draftConfig . welcome ?. roleMenu ?. options ?? [ ] ) . length <= 1
814+ }
815+ aria-label = { `Remove role option ${ opt . label || i + 1 } ` }
798816 >
799817 ✕
800818 </ Button >
@@ -829,7 +847,10 @@ export function ConfigEditor() {
829847 variant = "outline"
830848 size = "sm"
831849 onClick = { ( ) => {
832- const opts = [ ...( draftConfig . welcome ?. roleMenu ?. options ?? [ ] ) , { label : '' , roleId : '' } ] ;
850+ const opts = [
851+ ...( draftConfig . welcome ?. roleMenu ?. options ?? [ ] ) ,
852+ { id : crypto . randomUUID ( ) , label : '' , roleId : '' } ,
853+ ] ;
833854 updateWelcomeRoleMenu ( 'options' , opts ) ;
834855 } }
835856 disabled = { saving || ( draftConfig . welcome ?. roleMenu ?. options ?? [ ] ) . length >= 25 }
@@ -1370,7 +1391,9 @@ export function ConfigEditor() {
13701391 < span className = "text-sm font-medium" > Admin Role ID</ span >
13711392 < RoleSelector
13721393 guildId = { guildId }
1373- selected = { draftConfig . permissions ?. adminRoleId ? [ draftConfig . permissions . adminRoleId ] : [ ] }
1394+ selected = {
1395+ draftConfig . permissions ?. adminRoleId ? [ draftConfig . permissions . adminRoleId ] : [ ]
1396+ }
13741397 onChange = { ( selected ) => updatePermissionsField ( 'adminRoleId' , selected [ 0 ] ?? null ) }
13751398 placeholder = "Select admin role"
13761399 disabled = { saving }
@@ -1381,8 +1404,14 @@ export function ConfigEditor() {
13811404 < span className = "text-sm font-medium" > Moderator Role ID</ span >
13821405 < RoleSelector
13831406 guildId = { guildId }
1384- selected = { draftConfig . permissions ?. moderatorRoleId ? [ draftConfig . permissions . moderatorRoleId ] : [ ] }
1385- onChange = { ( selected ) => updatePermissionsField ( 'moderatorRoleId' , selected [ 0 ] ?? null ) }
1407+ selected = {
1408+ draftConfig . permissions ?. moderatorRoleId
1409+ ? [ draftConfig . permissions . moderatorRoleId ]
1410+ : [ ]
1411+ }
1412+ onChange = { ( selected ) =>
1413+ updatePermissionsField ( 'moderatorRoleId' , selected [ 0 ] ?? null )
1414+ }
13861415 placeholder = "Select moderator role"
13871416 disabled = { saving }
13881417 maxSelections = { 1 }
0 commit comments