@@ -21,7 +21,11 @@ import {
2121} from 'lucide-react' ;
2222import { Button , useToast , ToastContainer , useConfirm } from '@mcpmux/ui' ;
2323import type { FeatureSet , AddMemberInput } from '@/lib/api/featureSets' ;
24- import { isStarterFeatureSet , setFeatureSetMembers } from '@/lib/api/featureSets' ;
24+ import {
25+ isStarterFeatureSet ,
26+ setFeatureSetMembers ,
27+ updateFeatureSet ,
28+ } from '@/lib/api/featureSets' ;
2529import type { ServerFeature } from '@/lib/api/serverFeatures' ;
2630import { listServerFeatures } from '@/lib/api/serverFeatures' ;
2731
@@ -45,6 +49,11 @@ export function FeatureSetPanel({ featureSet, spaceId, onClose, onDelete, onUpda
4549 const [ searchQuery , setSearchQuery ] = useState ( '' ) ;
4650 const [ isLoading , setIsLoading ] = useState ( true ) ;
4751 const [ isSaving , setIsSaving ] = useState ( false ) ;
52+ const [ isSavingGeneral , setIsSavingGeneral ] = useState ( false ) ;
53+ const [ displayName , setDisplayName ] = useState ( featureSet . name ) ;
54+ const [ editName , setEditName ] = useState ( featureSet . name ) ;
55+ const [ editDescription , setEditDescription ] = useState ( featureSet . description ?? '' ) ;
56+ const [ editIcon , setEditIcon ] = useState ( featureSet . icon ?? '' ) ;
4857 const [ error , setError ] = useState < string | null > ( null ) ;
4958 const [ expandedServers , setExpandedServers ] = useState < Set < string > > ( new Set ( ) ) ;
5059 const { toasts, success, error : showError , dismiss } = useToast ( ) ;
@@ -68,6 +77,13 @@ export function FeatureSetPanel({ featureSet, spaceId, onClose, onDelete, onUpda
6877 const isFeatureSelected = ( featureId : string , _feature : ServerFeature ) =>
6978 selectedFeatureIds . has ( featureId ) ;
7079
80+ useEffect ( ( ) => {
81+ setDisplayName ( featureSet . name ) ;
82+ setEditName ( featureSet . name ) ;
83+ setEditDescription ( featureSet . description ?? '' ) ;
84+ setEditIcon ( featureSet . icon ?? '' ) ;
85+ } , [ featureSet ] ) ;
86+
7187 useEffect ( ( ) => {
7288 const loadFeatures = async ( ) => {
7389 setIsLoading ( true ) ;
@@ -167,6 +183,44 @@ export function FeatureSetPanel({ featureSet, spaceId, onClose, onDelete, onUpda
167183 } ) ;
168184 } ;
169185
186+ /**
187+ * Save name, description, and icon from the General Information section.
188+ */
189+ const handleSaveGeneral = async ( ) => {
190+ const trimmedName = editName . trim ( ) ;
191+ if ( ! trimmedName ) {
192+ setError ( 'Name is required.' ) ;
193+ return ;
194+ }
195+
196+ setIsSavingGeneral ( true ) ;
197+ setError ( null ) ;
198+ try {
199+ const updated = await updateFeatureSet ( featureSet . id , {
200+ name : trimmedName ,
201+ description : editDescription . trim ( ) || undefined ,
202+ icon : editIcon . trim ( ) || undefined ,
203+ } ) ;
204+ setDisplayName ( updated . name ) ;
205+ setEditName ( updated . name ) ;
206+ setEditDescription ( updated . description ?? '' ) ;
207+ setEditIcon ( updated . icon ?? '' ) ;
208+ success ( 'Feature set updated' , `"${ updated . name } " has been saved` ) ;
209+ onUpdate ?.( ) ;
210+ } catch ( e ) {
211+ const errorMsg = e instanceof Error ? e . message : String ( e ) ;
212+ setError ( errorMsg ) ;
213+ showError ( 'Failed to save feature set' , errorMsg ) ;
214+ } finally {
215+ setIsSavingGeneral ( false ) ;
216+ }
217+ } ;
218+
219+ const hasGeneralChanges =
220+ editName . trim ( ) !== featureSet . name ||
221+ editDescription . trim ( ) !== ( featureSet . description ?? '' ) ||
222+ editIcon . trim ( ) !== ( featureSet . icon ?? '' ) ;
223+
170224 const handleSave = async ( ) => {
171225 setIsSaving ( true ) ;
172226 setError ( null ) ;
@@ -251,7 +305,7 @@ export function FeatureSetPanel({ featureSet, spaceId, onClose, onDelete, onUpda
251305 </ div >
252306 < div className = "flex-1 min-w-0" >
253307 < h2 className = "text-lg font-bold truncate flex items-center gap-2" >
254- { featureSet . name }
308+ { displayName }
255309 </ h2 >
256310 < div className = "flex items-center gap-2 mt-0.5" >
257311 < span
@@ -327,13 +381,62 @@ export function FeatureSetPanel({ featureSet, spaceId, onClose, onDelete, onUpda
327381 < div className = "p-4 space-y-4 border-t-2 border-[rgb(var(--border))] bg-white dark:bg-[rgb(var(--background))]" >
328382 < div >
329383 < label className = "block text-xs font-medium mb-1.5 text-[rgb(var(--muted))]" >
330- Description
384+ Name *
331385 </ label >
332- < p className = "text-sm" >
333- { featureSet . description || 'No description provided.' }
334- </ p >
386+ < input
387+ type = "text"
388+ value = { editName }
389+ onChange = { ( e ) => setEditName ( e . target . value ) }
390+ className = "w-full px-3 py-2 text-sm rounded-lg border border-[rgb(var(--border))] bg-[rgb(var(--background))] focus:outline-none focus:ring-2 focus:ring-primary-500"
391+ data-testid = "featureset-panel-name"
392+ />
335393 </ div >
394+
336395
396+ < div >
397+ < label className = "block text-xs font-medium mb-1.5 text-[rgb(var(--muted))]" >
398+ Description
399+ </ label >
400+ < input
401+ type = "text"
402+ value = { editDescription }
403+ onChange = { ( e ) => setEditDescription ( e . target . value ) }
404+ placeholder = "What this feature set allows..."
405+ className = "w-full px-3 py-2 text-sm rounded-lg border border-[rgb(var(--border))] bg-[rgb(var(--background))] focus:outline-none focus:ring-2 focus:ring-primary-500"
406+ data-testid = "featureset-panel-description"
407+ />
408+ </ div >
409+
410+ < div >
411+ < label className = "block text-xs font-medium mb-1.5 text-[rgb(var(--muted))]" >
412+ Icon (emoji)
413+ </ label >
414+ < input
415+ type = "text"
416+ value = { editIcon }
417+ onChange = { ( e ) => setEditIcon ( e . target . value ) }
418+ placeholder = "🔧"
419+ maxLength = { 2 }
420+ className = "w-full px-3 py-2 text-sm rounded-lg border border-[rgb(var(--border))] bg-[rgb(var(--background))] focus:outline-none focus:ring-2 focus:ring-primary-500"
421+ data-testid = "featureset-panel-icon"
422+ />
423+ </ div >
424+
425+ < Button
426+ variant = "primary"
427+ size = "sm"
428+ onClick = { ( ) => void handleSaveGeneral ( ) }
429+ disabled = { isSavingGeneral || ! editName . trim ( ) || ! hasGeneralChanges }
430+ data-testid = "featureset-panel-save-general"
431+ >
432+ { isSavingGeneral ? (
433+ < Loader2 className = "h-4 w-4 animate-spin mr-2" />
434+ ) : (
435+ < Save className = "h-4 w-4 mr-2" />
436+ ) }
437+ Save
438+ </ Button >
439+
337440 { isStarter && (
338441 < div className = "p-3 bg-yellow-50 dark:bg-yellow-900/10 border border-yellow-200 dark:border-yellow-800 rounded-lg" >
339442 < div className = "flex gap-2" >
0 commit comments