@@ -66,7 +66,12 @@ impl Runtime {
6666 let store = self . hook_stores . entry ( self . root_view_id ) . or_default ( ) ;
6767 let mut ctx = BuildContext :: new ( store, Some ( self . rebuild_tx . clone ( ) ) ) ;
6868 ctx. reset ( ) ;
69- let element = self . root . build ( & mut ctx) ;
69+ let mut element = self . root . build ( & mut ctx) ;
70+
71+ // Automatic widget ID assignment: walk the tree and assign IDs
72+ // to any widgets that don't already have one, and register their events.
73+ // This mirrors Ivy Framework's WidgetTree.BuildWidget() pattern.
74+ element. assign_ids ( & mut ctx) ;
7075
7176 // Extract the event registry populated during build
7277 let registry = ctx. take_event_registry ( ) ;
@@ -167,6 +172,79 @@ mod tests {
167172 assert ! ( json. to_string( ) . contains( "Hello from runtime" ) ) ;
168173 }
169174
175+ #[ tokio:: test]
176+ async fn test_runtime_build_assigns_ids_automatically ( ) {
177+ use crate :: widgets:: layout:: Layout ;
178+
179+ struct AutoIdView ;
180+
181+ impl View for AutoIdView {
182+ fn build ( & self , _ctx : & mut BuildContext ) -> Element {
183+ Layout :: vertical ( )
184+ . child ( TextBlock :: new ( "First" ) )
185+ . child ( TextBlock :: new ( "Second" ) )
186+ . child ( Button :: new ( "Click" ) )
187+ . into ( )
188+ }
189+ }
190+
191+ let mut runtime = Runtime :: new ( AutoIdView ) ;
192+ let tree = runtime. build ( ) . await ;
193+ let json = serde_json:: to_value ( & tree) . unwrap ( ) ;
194+ let json_str = json. to_string ( ) ;
195+
196+ // All widgets should have auto-assigned IDs
197+ assert ! ( json_str. contains( "\" id\" :\" w-0\" " ) ) ; // Layout
198+ assert ! ( json_str. contains( "\" id\" :\" w-1\" " ) ) ; // TextBlock "First"
199+ assert ! ( json_str. contains( "\" id\" :\" w-2\" " ) ) ; // TextBlock "Second"
200+ assert ! ( json_str. contains( "\" id\" :\" w-3\" " ) ) ; // Button
201+ }
202+
203+ #[ tokio:: test]
204+ async fn test_runtime_auto_id_registers_events ( ) {
205+ let clicked = Arc :: new ( AtomicBool :: new ( false ) ) ;
206+ let clicked_clone = clicked. clone ( ) ;
207+
208+ struct AutoClickView {
209+ clicked : Arc < AtomicBool > ,
210+ }
211+
212+ impl View for AutoClickView {
213+ fn build ( & self , _ctx : & mut BuildContext ) -> Element {
214+ let clicked = self . clicked . clone ( ) ;
215+ // No .build(ctx) call — relies on automatic ID assignment
216+ Button :: new ( "Auto Click" )
217+ . on_click ( move || {
218+ clicked. store ( true , Ordering :: SeqCst ) ;
219+ } )
220+ . into ( )
221+ }
222+ }
223+
224+ let mut runtime = Runtime :: new ( AutoClickView {
225+ clicked : clicked_clone. clone ( ) ,
226+ } ) ;
227+
228+ let _ = runtime. build ( ) . await ;
229+
230+ // Send click event to the auto-assigned ID
231+ let tx = runtime. event_sender ( ) ;
232+ tx. send ( RuntimeMessage :: Event {
233+ widget_id : "w-0" . to_string ( ) ,
234+ event_name : "click" . to_string ( ) ,
235+ args : serde_json:: Value :: Null ,
236+ } )
237+ . await
238+ . unwrap ( ) ;
239+
240+ tokio:: spawn ( async move {
241+ runtime. run ( ) . await ;
242+ } ) ;
243+
244+ tokio:: time:: sleep ( std:: time:: Duration :: from_millis ( 50 ) ) . await ;
245+ assert ! ( clicked_clone. load( Ordering :: SeqCst ) ) ;
246+ }
247+
170248 #[ tokio:: test]
171249 async fn test_button_click_dispatched ( ) {
172250 let clicked = Arc :: new ( AtomicBool :: new ( false ) ) ;
0 commit comments