@@ -627,10 +627,8 @@ const SectionEditor = ({ sectionName, highlightParam = null, onHighlightComplete
627627 const { isMobile, isTablet, isCompactDesktop, compactTable } = useResponsive ( ) ;
628628 const globalState = useConfigGlobalState ( ) ;
629629
630- // Use card layout on mobile/tablet/compact-desktop, OR always for Safety/Follower sections
631- const isSafetySection = sectionName === 'Safety' || sectionName === 'SafetyLimits' ;
632- const isFollowerSection = sectionName === 'Follower' ;
633- const useCardLayout = isMobile || isTablet || isCompactDesktop || isSafetySection || isFollowerSection ;
630+ // Use card layout on mobile/tablet/compact-desktop (responsive breakpoint only)
631+ const useCardLayout = isMobile || isTablet || isCompactDesktop ;
634632
635633 const [ localValues , setLocalValues ] = useState ( { } ) ;
636634 const [ saveStatuses , setSaveStatuses ] = useState ( { } ) ; // 'saving' | 'saved' | 'error' | null
@@ -825,6 +823,16 @@ const SectionEditor = ({ sectionName, highlightParam = null, onHighlightComplete
825823 return localValues [ param ] !== undefined ? localValues [ param ] : config [ param ] ;
826824 } ;
827825
826+ // Identify specialized object params that need full-width rendering (Safety/Follower editors)
827+ const isSpecializedParam = ( param ) => {
828+ if ( parameters [ param ] ?. type !== 'object' ) return false ;
829+ if ( param === 'GlobalLimits' || ( param === 'FollowerOverrides' && 'GlobalLimits' in config ) ) return true ;
830+ if ( ( param === 'General' || param === 'FollowerOverrides' ) && 'General' in config ) return true ;
831+ return false ;
832+ } ;
833+ const simpleParams = paramNames . filter ( p => ! isSpecializedParam ( p ) ) ;
834+ const specializedParams = paramNames . filter ( p => isSpecializedParam ( p ) ) ;
835+
828836 return (
829837 < Paper
830838 ref = { containerRef }
@@ -929,136 +937,159 @@ const SectionEditor = ({ sectionName, highlightParam = null, onHighlightComplete
929937 ) ) }
930938 </ Box >
931939 ) : (
932- // Desktop (>= 900px): Table Layout with responsive overflow
933- < TableContainer sx = { { overflowX : 'auto' } } >
934- < Table size = "small" sx = { { tableLayout : 'fixed' , width : '100%' } } >
935- < TableHead >
936- < TableRow >
937- < TableCell sx = { { fontWeight : 'bold' , width : compactTable ? '22%' : '20%' } } > Parameter</ TableCell >
938- < TableCell sx = { { fontWeight : 'bold' , width : compactTable ? '38%' : '35%' } } > Value</ TableCell >
939- < TableCell sx = { { fontWeight : 'bold' , width : compactTable ? '14%' : '18%' } } > Default</ TableCell >
940- < TableCell sx = { { fontWeight : 'bold' , width : compactTable ? '12%' : '14%' } } > Info</ TableCell >
941- < TableCell sx = { { fontWeight : 'bold' , width : compactTable ? '14%' : '13%' , whiteSpace : 'nowrap' } } > Actions</ TableCell >
942- </ TableRow >
943- </ TableHead >
944- < TableBody >
945- { paramNames . map ( ( param ) => {
946- const paramSchema = parameters [ param ] ;
947- const currentValue = getValue ( param ) ;
948- const defaultValue = defaultConfig [ param ] ?? paramSchema ?. default ;
949- const modified = ! isDeepEqual ( currentValue , defaultValue ) ;
950- const hasPending = param in pendingChanges ;
951- const saveStatus = saveStatuses [ param ] ;
952-
953- return (
954- < TableRow
955- key = { param }
956- data-param = { param }
957- sx = { {
958- bgcolor : modified ? 'action.selected' : undefined ,
959- '&:hover' : { bgcolor : 'action.hover' }
960- } }
961- >
962- < TableCell sx = { { overflow : 'hidden' } } >
963- < Box >
964- < Typography variant = "body2" sx = { { fontFamily : 'monospace' , overflow : 'hidden' , textOverflow : 'ellipsis' , whiteSpace : 'nowrap' } } >
965- { param }
966- </ Typography >
967- { paramSchema ?. description && (
968- < Typography variant = "caption" color = "text.secondary" sx = { { display : 'block' , overflow : 'hidden' , textOverflow : 'ellipsis' , whiteSpace : 'nowrap' } } >
969- { paramSchema . description . slice ( 0 , 60 ) }
970- { paramSchema . description . length > 60 ? '...' : '' }
971- </ Typography >
972- ) }
973- </ Box >
974- </ TableCell >
975-
976- < TableCell sx = { { overflow : 'hidden' } } >
977- < ParameterInput
978- param = { param }
979- schema = { schema }
980- value = { currentValue }
981- defaultValue = { defaultValue }
982- onChange = { handleLocalChange }
983- onSave = { handleSave }
984- saveStatus = { saveStatus }
985- configValues = { config }
986- autoSaveEnabled = { autoSaveEnabled }
987- />
988- </ TableCell >
989-
990- < TableCell sx = { { overflow : 'hidden' } } >
991- < Typography
992- variant = "body2"
993- color = "text.secondary"
994- noWrap
995- sx = { { fontFamily : 'monospace' , fontSize : '0.75rem' } }
940+ // Desktop: Table for simple params, full-width cards for specialized editors
941+ < Box >
942+ { /* Table for simple (non-specialized) params */ }
943+ { simpleParams . length > 0 && (
944+ < TableContainer sx = { { overflowX : 'auto' } } >
945+ < Table size = "small" sx = { { tableLayout : 'fixed' , width : '100%' } } >
946+ < TableHead >
947+ < TableRow >
948+ < TableCell sx = { { fontWeight : 'bold' , width : compactTable ? '22%' : '20%' } } > Parameter</ TableCell >
949+ < TableCell sx = { { fontWeight : 'bold' , width : compactTable ? '38%' : '35%' } } > Value</ TableCell >
950+ < TableCell sx = { { fontWeight : 'bold' , width : compactTable ? '14%' : '18%' } } > Default</ TableCell >
951+ < TableCell sx = { { fontWeight : 'bold' , width : compactTable ? '12%' : '14%' } } > Info</ TableCell >
952+ < TableCell sx = { { fontWeight : 'bold' , width : compactTable ? '14%' : '13%' , whiteSpace : 'nowrap' } } > Actions</ TableCell >
953+ </ TableRow >
954+ </ TableHead >
955+ < TableBody >
956+ { simpleParams . map ( ( param ) => {
957+ const paramSchema = parameters [ param ] ;
958+ const currentValue = getValue ( param ) ;
959+ const defaultValue = defaultConfig [ param ] ?? paramSchema ?. default ;
960+ const modified = ! isDeepEqual ( currentValue , defaultValue ) ;
961+ const hasPending = param in pendingChanges ;
962+ const saveStatus = saveStatuses [ param ] ;
963+
964+ return (
965+ < TableRow
966+ key = { param }
967+ data-param = { param }
968+ sx = { {
969+ bgcolor : modified ? 'action.selected' : undefined ,
970+ '&:hover' : { bgcolor : 'action.hover' }
971+ } }
996972 >
997- { typeof defaultValue === 'object'
998- ? JSON . stringify ( defaultValue ) . slice ( 0 , 20 )
999- : String ( defaultValue ) }
1000- </ Typography >
1001- </ TableCell >
1002-
1003- < TableCell >
1004- < Box sx = { { display : 'flex' , gap : 0.5 , flexWrap : 'wrap' } } >
1005- { paramSchema ?. reload_tier && (
1006- < ReloadTierChip tier = { paramSchema . reload_tier } size = "small" />
1007- ) }
1008- { paramSchema ?. unit && (
1009- < Chip label = { paramSchema . unit } size = "small" variant = "outlined" />
1010- ) }
1011- { hasPending && (
1012- < Chip label = "Unsaved" size = "small" color = "info" />
1013- ) }
1014- { modified && ! hasPending && (
1015- < Chip label = "Modified" size = "small" color = "warning" />
1016- ) }
1017- </ Box >
1018- </ TableCell >
1019-
1020- < TableCell >
1021- < Box sx = { { display : 'flex' , gap : 0.5 } } >
1022- < Tooltip title = "Open detail editor" >
1023- < IconButton
1024- size = "small"
1025- onClick = { ( ) => setSelectedParam ( param ) }
973+ < TableCell sx = { { overflow : 'hidden' } } >
974+ < Box >
975+ < Typography variant = "body2" sx = { { fontFamily : 'monospace' , overflow : 'hidden' , textOverflow : 'ellipsis' , whiteSpace : 'nowrap' } } >
976+ { param }
977+ </ Typography >
978+ { paramSchema ?. description && (
979+ < Typography variant = "caption" color = "text.secondary" sx = { { display : 'block' , overflow : 'hidden' , textOverflow : 'ellipsis' , whiteSpace : 'nowrap' } } >
980+ { paramSchema . description . slice ( 0 , 60 ) }
981+ { paramSchema . description . length > 60 ? '...' : '' }
982+ </ Typography >
983+ ) }
984+ </ Box >
985+ </ TableCell >
986+
987+ < TableCell sx = { { overflow : 'hidden' } } >
988+ < ParameterInput
989+ param = { param }
990+ schema = { schema }
991+ value = { currentValue }
992+ defaultValue = { defaultValue }
993+ onChange = { handleLocalChange }
994+ onSave = { handleSave }
995+ saveStatus = { saveStatus }
996+ configValues = { config }
997+ autoSaveEnabled = { autoSaveEnabled }
998+ />
999+ </ TableCell >
1000+
1001+ < TableCell sx = { { overflow : 'hidden' } } >
1002+ < Typography
1003+ variant = "body2"
1004+ color = "text.secondary"
1005+ noWrap
1006+ sx = { { fontFamily : 'monospace' , fontSize : '0.75rem' } }
10261007 >
1027- < OpenInNew fontSize = "small" />
1028- </ IconButton >
1029- </ Tooltip >
1030- { hasPending && (
1031- < Tooltip title = "Save this parameter" >
1032- < IconButton
1033- size = "small"
1034- color = "primary"
1035- onClick = { ( ) => handleSave ( param , currentValue ) }
1036- disabled = { saveStatus === 'saving' }
1037- >
1038- < Save fontSize = "small" />
1039- </ IconButton >
1040- </ Tooltip >
1041- ) }
1042- { modified && (
1043- < Tooltip title = "Revert to default" >
1044- < IconButton
1045- size = "small"
1046- onClick = { ( ) => handleRevert ( param ) }
1047- disabled = { saveStatus === 'saving' }
1048- >
1049- < Undo fontSize = "small" />
1050- </ IconButton >
1051- </ Tooltip >
1052- ) }
1053- </ Box >
1054- </ TableCell >
1055- </ TableRow >
1056- ) ;
1057- } ) }
1058- </ TableBody >
1059- </ Table >
1060- </ TableContainer >
1061- ) }
1008+ { typeof defaultValue === 'object'
1009+ ? JSON . stringify ( defaultValue ) . slice ( 0 , 20 )
1010+ : String ( defaultValue ) }
1011+ </ Typography >
1012+ </ TableCell >
1013+
1014+ < TableCell >
1015+ < Box sx = { { display : 'flex' , gap : 0.5 , flexWrap : 'wrap' } } >
1016+ { paramSchema ?. reload_tier && (
1017+ < ReloadTierChip tier = { paramSchema . reload_tier } size = "small" />
1018+ ) }
1019+ { paramSchema ?. unit && (
1020+ < Chip label = { paramSchema . unit } size = "small" variant = "outlined" />
1021+ ) }
1022+ { hasPending && (
1023+ < Chip label = "Unsaved" size = "small" color = "info" />
1024+ ) }
1025+ { modified && ! hasPending && (
1026+ < Chip label = "Modified" size = "small" color = "warning" />
1027+ ) }
1028+ </ Box >
1029+ </ TableCell >
1030+
1031+ < TableCell >
1032+ < Box sx = { { display : 'flex' , gap : 0.5 } } >
1033+ < Tooltip title = "Open detail editor" >
1034+ < IconButton
1035+ size = "small"
1036+ onClick = { ( ) => setSelectedParam ( param ) }
1037+ >
1038+ < OpenInNew fontSize = "small" />
1039+ </ IconButton >
1040+ </ Tooltip >
1041+ { hasPending && (
1042+ < Tooltip title = "Save this parameter" >
1043+ < IconButton
1044+ size = "small"
1045+ color = "primary"
1046+ onClick = { ( ) => handleSave ( param , currentValue ) }
1047+ disabled = { saveStatus === 'saving' }
1048+ >
1049+ < Save fontSize = "small" />
1050+ </ IconButton >
1051+ </ Tooltip >
1052+ ) }
1053+ { modified && (
1054+ < Tooltip title = "Revert to default" >
1055+ < IconButton
1056+ size = "small"
1057+ onClick = { ( ) => handleRevert ( param ) }
1058+ disabled = { saveStatus === 'saving' }
1059+ >
1060+ < Undo fontSize = "small" />
1061+ </ IconButton >
1062+ </ Tooltip >
1063+ ) }
1064+ </ Box >
1065+ </ TableCell >
1066+ </ TableRow >
1067+ ) ;
1068+ } ) }
1069+ </ TableBody >
1070+ </ Table >
1071+ </ TableContainer >
1072+ ) }
1073+
1074+ { /* Full-width specialized editors (Safety/Follower) */ }
1075+ { specializedParams . map ( ( param ) => (
1076+ < ParameterCard
1077+ key = { param }
1078+ param = { param }
1079+ schema = { schema }
1080+ value = { getValue ( param ) }
1081+ defaultValue = { defaultConfig [ param ] ?? parameters [ param ] ?. default }
1082+ saveStatus = { saveStatuses [ param ] }
1083+ onLocalChange = { handleLocalChange }
1084+ onSave = { handleSave }
1085+ onRevert = { handleRevert }
1086+ onOpenDetails = { ( ) => setSelectedParam ( param ) }
1087+ configValues = { config }
1088+ autoSaveEnabled = { autoSaveEnabled }
1089+ />
1090+ ) ) }
1091+ </ Box >
1092+ ) }
10621093
10631094 { paramNames . length === 0 && (
10641095 < Box sx = { { py : 4 , textAlign : 'center' } } >
0 commit comments