@@ -7,11 +7,66 @@ import {
77 isSubBlockVisibleForMode ,
88} from '@/lib/workflows/subblocks/visibility'
99import type { BlockConfig , SubBlockConfig , SubBlockType } from '@/blocks/types'
10+ import { useWorkspaceCredential } from '@/hooks/queries/credentials'
1011import { usePermissionConfig } from '@/hooks/use-permission-config'
1112import { useWorkflowDiffStore } from '@/stores/workflow-diff'
13+ import { useSubBlockStore } from '@/stores/workflows/subblock/store'
1214import { mergeSubblockState } from '@/stores/workflows/utils'
1315import { useWorkflowStore } from '@/stores/workflows/workflow/store'
1416
17+ /**
18+ * Evaluates reactive conditions for subblocks. Always calls the same hooks
19+ * regardless of whether a reactive condition exists (Rules of Hooks).
20+ *
21+ * Returns a Set of subblock IDs that should be hidden.
22+ */
23+ function useReactiveConditions (
24+ subBlocks : SubBlockConfig [ ] ,
25+ blockId : string ,
26+ activeWorkflowId : string | null ,
27+ blockSubBlockValues : Record < string , unknown >
28+ ) : Set < string > {
29+ const reactiveSubBlock = useMemo (
30+ ( ) => subBlocks . find ( ( sb ) => sb . reactiveCondition ) ,
31+ [ subBlocks ]
32+ )
33+ const reactiveCond = reactiveSubBlock ?. reactiveCondition
34+
35+ // Subscribe to watched field values — always called (stable hook count)
36+ const watchedCredentialId = useSubBlockStore (
37+ useCallback (
38+ ( state ) => {
39+ if ( ! reactiveCond || ! activeWorkflowId ) return ''
40+ const blockValues = state . workflowValues [ activeWorkflowId ] ?. [ blockId ] ?? { }
41+ const merged = { ...blockSubBlockValues , ...blockValues }
42+ for ( const field of reactiveCond . watchFields ) {
43+ const val = merged [ field ]
44+ if ( val && typeof val === 'string' ) return val
45+ }
46+ return ''
47+ } ,
48+ [ reactiveCond , activeWorkflowId , blockId , blockSubBlockValues ]
49+ )
50+ )
51+
52+ // Always call useWorkspaceCredential (stable hook count), disable when not needed
53+ const { data : credential } = useWorkspaceCredential (
54+ watchedCredentialId || undefined ,
55+ Boolean ( reactiveCond && watchedCredentialId )
56+ )
57+
58+ return useMemo ( ( ) => {
59+ const hidden = new Set < string > ( )
60+ if ( ! reactiveSubBlock || ! reactiveCond ) return hidden
61+
62+ const conditionMet = credential ?. type === reactiveCond . requiredType
63+ if ( ! conditionMet ) {
64+ hidden . add ( reactiveSubBlock . id )
65+ }
66+ return hidden
67+ } , [ reactiveSubBlock , reactiveCond , credential ?. type ] )
68+ }
69+
1570/**
1671 * Custom hook for computing subblock layout in the editor panel.
1772 * Determines which subblocks should be visible based on mode, conditions, and feature flags.
@@ -39,6 +94,14 @@ export function useEditorSubblockLayout(
3994 )
4095 const { config : permissionConfig } = usePermissionConfig ( )
4196
97+ // Evaluate reactive conditions (hooks-based, must be called before useMemo)
98+ const hiddenByReactiveCondition = useReactiveConditions (
99+ config ?. subBlocks || [ ] ,
100+ blockId ,
101+ activeWorkflowId ,
102+ blockSubBlockValues
103+ )
104+
42105 return useMemo ( ( ) => {
43106 // Guard against missing config or block selection
44107 if ( ! config || ! Array . isArray ( ( config as any ) . subBlocks ) || ! blockId ) {
@@ -109,6 +172,9 @@ export function useEditorSubblockLayout(
109172 const visibleSubBlocks = ( config . subBlocks || [ ] ) . filter ( ( block ) => {
110173 if ( block . hidden ) return false
111174
175+ // Filter by reactive condition (evaluated via hooks before useMemo)
176+ if ( hiddenByReactiveCondition . has ( block . id ) ) return false
177+
112178 // Hide skill-input subblock when skills are disabled via permissions
113179 if ( block . type === 'skill-input' && permissionConfig . disableSkills ) return false
114180
@@ -164,6 +230,7 @@ export function useEditorSubblockLayout(
164230 activeWorkflowId ,
165231 isSnapshotView ,
166232 blockDataFromStore ,
233+ hiddenByReactiveCondition ,
167234 permissionConfig . disableSkills ,
168235 ] )
169236}
0 commit comments