@@ -123,44 +123,31 @@ function formatInlineValue(value: unknown): string {
123123 return String ( value )
124124}
125125
126- interface ExecutionDataSectionProps {
126+ interface CollapsibleSectionProps {
127127 title : string
128- data : unknown
128+ defaultExpanded ?: boolean
129+ children : React . ReactNode
130+ isEmpty ?: boolean
131+ emptyMessage ?: string
132+ /** Whether this section represents an error state (styles title red) */
129133 isError ?: boolean
130- wrapText ?: boolean
131- searchQuery ?: string
132- currentMatchIndex ?: number
133- onMatchCountChange ?: ( count : number ) => void
134- contentRef ?: React . RefObject < HTMLDivElement | null >
135- onContextMenu ?: ( e : React . MouseEvent ) => void
136134}
137135
138136/**
139- * Collapsible section for execution data (input/output)
140- * Uses Code.Viewer for proper syntax highlighting matching the logs UI
137+ * Collapsible section wrapper for organizing preview editor content
141138 */
142- function ExecutionDataSection ( {
139+ function CollapsibleSection ( {
143140 title,
144- data,
141+ defaultExpanded = false ,
142+ children,
143+ isEmpty = false ,
144+ emptyMessage = 'No data' ,
145145 isError = false ,
146- wrapText = true ,
147- searchQuery,
148- currentMatchIndex = 0 ,
149- onMatchCountChange,
150- contentRef,
151- onContextMenu,
152- } : ExecutionDataSectionProps ) {
153- const [ isExpanded , setIsExpanded ] = useState ( false )
154-
155- const jsonString = useMemo ( ( ) => {
156- if ( ! data ) return ''
157- return formatValueAsJson ( data )
158- } , [ data ] )
159-
160- const isEmpty = jsonString === '—' || jsonString === ''
146+ } : CollapsibleSectionProps ) {
147+ const [ isExpanded , setIsExpanded ] = useState ( defaultExpanded )
161148
162149 return (
163- < div className = 'flex min-w-0 flex-col gap-[8px] overflow-hidden' >
150+ < div className = 'flex min-w-0 flex-col gap-[8px] overflow-hidden border-[var(--border)] border-b px-[12px] py-[10px] ' >
164151 < div
165152 className = 'group flex cursor-pointer items-center justify-between'
166153 onClick = { ( ) => setIsExpanded ( ! isExpanded ) }
@@ -199,20 +186,10 @@ function ExecutionDataSection({
199186 < >
200187 { isEmpty ? (
201188 < div className = 'rounded-[6px] bg-[var(--surface-3)] px-[10px] py-[8px]' >
202- < span className = 'text-[12px] text-[var(--text-tertiary)]' > No data </ span >
189+ < span className = 'text-[12px] text-[var(--text-tertiary)]' > { emptyMessage } </ span >
203190 </ div >
204191 ) : (
205- < div onContextMenu = { onContextMenu } ref = { contentRef } >
206- < Code . Viewer
207- code = { jsonString }
208- language = 'json'
209- className = '!bg-[var(--surface-3)] max-h-[300px] min-h-0 max-w-full rounded-[6px] border-0 [word-break:break-all]'
210- wrapText = { wrapText }
211- searchQuery = { searchQuery }
212- currentMatchIndex = { currentMatchIndex }
213- onMatchCountChange = { onMatchCountChange }
214- />
215- </ div >
192+ children
216193 ) }
217194 </ >
218195 ) }
@@ -601,7 +578,7 @@ function SubflowConfigDisplay({ block, loop, parallel }: SubflowConfigDisplayPro
601578 }
602579
603580 return (
604- < div className = 'flex-1 overflow-y-auto overflow-x-hidden pt-[5px ] pb-[8px]' >
581+ < div className = 'flex-1 overflow-y-auto overflow-x-hidden pt-[8px ] pb-[8px]' >
605582 { /* Type Selection - matches SubflowEditor */ }
606583 < div >
607584 < Label className = 'mb-[6.5px] block pl-[2px] font-medium text-[13px] text-[var(--text-primary)]' >
@@ -1145,7 +1122,7 @@ function PreviewEditorContent({
11451122
11461123 { /* Content area */ }
11471124 < div className = 'flex flex-1 flex-col overflow-hidden pt-[0px]' >
1148- { /* Subblocks Section */ }
1125+ { /* Main content sections */ }
11491126 < div ref = { subBlocksRef } className = 'subblocks-section flex flex-1 flex-col overflow-hidden' >
11501127 < div className = 'flex-1 overflow-y-auto overflow-x-hidden' >
11511128 { /* Not Executed Banner - shown when in execution mode but block wasn't executed */ }
@@ -1159,91 +1136,99 @@ function PreviewEditorContent({
11591136 </ div >
11601137 ) }
11611138
1162- { /* Execution Input/Output (if provided) */ }
1163- { executionData &&
1164- ( executionData . input !== undefined || executionData . output !== undefined ) ? (
1165- < div className = 'flex min-w-0 flex-col gap-[8px] overflow-hidden border-[var(--border)] border-b px-[12px] py-[10px]' >
1166- { /* Execution Status & Duration Header */ }
1167- { ( executionData . status || executionData . durationMs !== undefined ) && (
1168- < div className = 'flex items-center justify-between' >
1169- { executionData . status && (
1170- < Badge variant = { statusVariant } size = 'sm' dot >
1171- < span className = 'capitalize' > { executionData . status } </ span >
1172- </ Badge >
1173- ) }
1174- { executionData . durationMs !== undefined && (
1175- < span className = 'font-medium text-[12px] text-[var(--text-tertiary)]' >
1176- { formatDuration ( executionData . durationMs , { precision : 2 } ) }
1177- </ span >
1178- ) }
1179- </ div >
1139+ { /* Execution Status & Duration Header */ }
1140+ { executionData && ( executionData . status || executionData . durationMs !== undefined ) && (
1141+ < div className = 'flex min-w-0 items-center justify-between overflow-hidden border-[var(--border)] border-b px-[12px] py-[10px]' >
1142+ { executionData . status && (
1143+ < Badge variant = { statusVariant } size = 'sm' dot >
1144+ < span className = 'capitalize' > { executionData . status } </ span >
1145+ </ Badge >
11801146 ) }
1147+ { executionData . durationMs !== undefined && (
1148+ < span className = 'font-medium text-[12px] text-[var(--text-tertiary)]' >
1149+ { formatDuration ( executionData . durationMs , { precision : 2 } ) }
1150+ </ span >
1151+ ) }
1152+ </ div >
1153+ ) }
11811154
1182- { /* Divider between Status/Duration and Input/Output */ }
1183- { ( executionData . status || executionData . durationMs !== undefined ) &&
1184- ( executionData . input !== undefined || executionData . output !== undefined ) && (
1185- < div className = 'border-[var(--border)] border-t border-dashed' />
1186- ) }
1187-
1188- { /* Input Section */ }
1189- { executionData . input !== undefined && (
1190- < ExecutionDataSection
1191- title = 'Input'
1192- data = { executionData . input }
1155+ { /* Input Section - Collapsible */ }
1156+ { executionData ?. input !== undefined && (
1157+ < CollapsibleSection
1158+ title = 'Input'
1159+ defaultExpanded = { false }
1160+ isEmpty = {
1161+ formatValueAsJson ( executionData . input ) === '—' ||
1162+ formatValueAsJson ( executionData . input ) === ''
1163+ }
1164+ emptyMessage = 'No input data'
1165+ >
1166+ < div onContextMenu = { handleExecutionContextMenu } ref = { contentRef } >
1167+ < Code . Viewer
1168+ code = { formatValueAsJson ( executionData . input ) }
1169+ language = 'json'
1170+ className = '!bg-[var(--surface-3)] max-h-[300px] min-h-0 max-w-full rounded-[6px] border-0 [word-break:break-all]'
11931171 wrapText = { wrapText }
11941172 searchQuery = { isSearchActive ? searchQuery : undefined }
11951173 currentMatchIndex = { currentMatchIndex }
11961174 onMatchCountChange = { handleMatchCountChange }
1197- contentRef = { contentRef }
1198- onContextMenu = { handleExecutionContextMenu }
11991175 />
1200- ) }
1201-
1202- { /* Divider between Input and Output */ }
1203- { executionData . input !== undefined && executionData . output !== undefined && (
1204- < div className = 'border-[var(--border)] border-t border-dashed' />
1205- ) }
1176+ </ div >
1177+ </ CollapsibleSection >
1178+ ) }
12061179
1207- { /* Output Section */ }
1208- { executionData . output !== undefined && (
1209- < ExecutionDataSection
1210- title = { executionData . status === 'error' ? 'Error' : 'Output' }
1211- data = { executionData . output }
1212- isError = { executionData . status === 'error' }
1180+ { /* Output Section - Collapsible, expanded by default */ }
1181+ { executionData ?. output !== undefined && (
1182+ < CollapsibleSection
1183+ title = { executionData . status === 'error' ? 'Error' : 'Output' }
1184+ defaultExpanded = { true }
1185+ isEmpty = {
1186+ formatValueAsJson ( executionData . output ) === '—' ||
1187+ formatValueAsJson ( executionData . output ) === ''
1188+ }
1189+ emptyMessage = 'No output data'
1190+ isError = { executionData . status === 'error' }
1191+ >
1192+ < div onContextMenu = { handleExecutionContextMenu } >
1193+ < Code . Viewer
1194+ code = { formatValueAsJson ( executionData . output ) }
1195+ language = 'json'
1196+ className = { cn (
1197+ '!bg-[var(--surface-3)] max-h-[300px] min-h-0 max-w-full rounded-[6px] border-0 [word-break:break-all]' ,
1198+ executionData . status === 'error' && 'text-[var(--text-error)]'
1199+ ) }
12131200 wrapText = { wrapText }
12141201 searchQuery = { isSearchActive ? searchQuery : undefined }
12151202 currentMatchIndex = { currentMatchIndex }
12161203 onMatchCountChange = { handleMatchCountChange }
1217- contentRef = { contentRef }
1218- onContextMenu = { handleExecutionContextMenu }
12191204 />
1220- ) }
1221- </ div >
1222- ) : null }
1205+ </ div >
1206+ </ CollapsibleSection >
1207+ ) }
12231208
12241209 { /* Subblock Values - Using SubBlock components in preview mode */ }
1225- < div className = 'readonly-preview px-[8px] py -[8px]' >
1210+ < div className = 'readonly-preview px-[8px] pt-[12px] pb -[8px]' >
12261211 { /* CSS override to show full opacity and prevent interaction instead of dimmed disabled state */ }
12271212 < style > { `
1228- .readonly-preview,
1229- .readonly-preview * {
1230- cursor: default !important;
1231- }
1232- .readonly-preview [disabled],
1233- .readonly-preview [data-disabled],
1234- .readonly-preview input,
1235- .readonly-preview textarea,
1236- .readonly-preview [role="combobox"],
1237- .readonly-preview [role="slider"],
1238- .readonly-preview [role="switch"],
1239- .readonly-preview [role="checkbox"] {
1240- opacity: 1 !important;
1241- pointer-events: none;
1242- }
1243- .readonly-preview .opacity-50 {
1244- opacity: 1 !important;
1245- }
1246- ` } </ style >
1213+ .readonly-preview,
1214+ .readonly-preview * {
1215+ cursor: default !important;
1216+ }
1217+ .readonly-preview [disabled],
1218+ .readonly-preview [data-disabled],
1219+ .readonly-preview input,
1220+ .readonly-preview textarea,
1221+ .readonly-preview [role="combobox"],
1222+ .readonly-preview [role="slider"],
1223+ .readonly-preview [role="switch"],
1224+ .readonly-preview [role="checkbox"] {
1225+ opacity: 1 !important;
1226+ pointer-events: none;
1227+ }
1228+ .readonly-preview .opacity-50 {
1229+ opacity: 1 !important;
1230+ }
1231+ ` } </ style >
12471232 { visibleSubBlocks . length > 0 ? (
12481233 < div className = 'flex flex-col' >
12491234 { visibleSubBlocks . map ( ( subBlockConfig , index ) => (
0 commit comments