@@ -176,10 +176,16 @@ fn reference_to_url(reference: &Reference) -> String {
176176 // Convert canonical book name back to display book name used in the Bible
177177 let display_book_name = get_display_book_name ( & reference. to_book_name ) ;
178178 let encoded_book = encode ( & display_book_name) ;
179+
180+ // Ensure chapter and verse are valid positive numbers
181+ let chapter = reference. to_chapter . max ( 1 ) ;
182+ let verse_start = reference. to_verse_start . max ( 1 ) ;
183+
179184 if let Some ( end_verse) = reference. to_verse_end {
180- format ! ( "/{}/{}?verses={}-{}" , encoded_book, reference. to_chapter, reference. to_verse_start, end_verse)
185+ let verse_end = end_verse. max ( verse_start) ;
186+ format ! ( "/{}/{}?verses={}-{}" , encoded_book, chapter, verse_start, verse_end)
181187 } else {
182- format ! ( "/{}/{}?verses={}" , encoded_book, reference . to_chapter , reference . to_verse_start )
188+ format ! ( "/{}/{}?verses={}" , encoded_book, chapter , verse_start )
183189 }
184190}
185191
@@ -265,6 +271,11 @@ pub fn CrossReferencesSidebar(
265271 let ( _sidebar_has_focus, set_sidebar_has_focus) = signal ( false ) ;
266272 let navigate = use_navigate ( ) ;
267273
274+ // Use a simple Arc<AtomicBool> for disposal tracking that doesn't rely on reactive system
275+ use std:: sync:: atomic:: { AtomicBool , Ordering } ;
276+ use std:: sync:: Arc ;
277+ let is_disposed = Arc :: new ( AtomicBool :: new ( false ) ) ;
278+
268279 // Convert display book name (e.g. "I Samuël") to canonical English name (e.g. "1 Samuel")
269280 // for cross-reference lookup
270281 let canonical_book_name = get_canonical_book_name ( & book_name) ;
@@ -351,24 +362,32 @@ pub fn CrossReferencesSidebar(
351362 } ) ;
352363
353364 // Keyboard navigation for references - with comprehensive safety checks and debouncing
365+ let is_disposed_clone = is_disposed. clone ( ) ;
366+ let _component_book_name = book_name. clone ( ) ;
367+ let _component_chapter = chapter;
368+ let _component_verse = verse;
354369 let handle_keydown = move |e : KeyboardEvent | {
355- // Don't handle navigation when command palette is open (let palette handle it)
356- if palette_open . get ( ) {
370+ // Early exit if component is disposed
371+ if is_disposed_clone . load ( Ordering :: Relaxed ) {
357372 return ;
358373 }
359374
360- // Only handle specific Ctrl combinations to avoid conflicts with vim navigation
361- if !e. ctrl_key ( ) {
362- return ; // Let vim navigation handle non-Ctrl keys
363- }
364375
365- // Only handle navigation when references are available and we're specifically targeting cross-refs
366- if !sorted_references . get ( ) . is_some_and ( |refs| !refs . is_empty ( ) ) {
376+ // Don't handle navigation when command palette is open (let palette handle it)
377+ if palette_open . get ( ) {
367378 return ;
368379 }
369380
370- // Only handle Ctrl+J and Ctrl+K specifically for cross-references
371- if !matches ! ( ( e. key( ) . as_str( ) , e. ctrl_key( ) ) , ( "j" , true ) | ( "k" , true ) ) {
381+ // Safe access to sorted_references with disposal check
382+ let refs = match std:: panic:: catch_unwind ( std:: panic:: AssertUnwindSafe ( || {
383+ sorted_references. get ( )
384+ } ) ) {
385+ Ok ( Some ( refs) ) if !refs. is_empty ( ) => refs,
386+ _ => return , // Component is disposed or no references available
387+ } ;
388+
389+ // Only handle specific keys: Ctrl+J, Ctrl+K for navigation, or Enter for selection
390+ if !matches ! ( ( e. key( ) . as_str( ) , e. ctrl_key( ) ) , ( "j" , true ) | ( "k" , true ) | ( "Enter" , false ) ) {
372391 return ;
373392 }
374393
@@ -377,11 +396,6 @@ pub fn CrossReferencesSidebar(
377396 return ; // Skip if already processing navigation
378397 }
379398
380- // Get current references safely with additional checks
381- let refs = match sorted_references. get ( ) {
382- Some ( refs) if !refs. is_empty ( ) => refs,
383- _ => return , // No references available
384- } ;
385399
386400 // Bounds check current selection before processing with recovery
387401 let current = selected_reference_index. get ( ) ;
@@ -463,16 +477,18 @@ pub fn CrossReferencesSidebar(
463477 ( "Enter" , false , false ) => {
464478 // Enter: Navigate to selected reference with bounds checking
465479 e. prevent_default ( ) ;
480+ web_sys:: console:: log_1 ( & format ! ( "Enter pressed, current index: {}, refs length: {}" , current, refs. len( ) ) . into ( ) ) ;
466481 if let Some ( reference) = refs. get ( current) {
467482 let reference_url = reference_to_url ( reference) ;
483+ web_sys:: console:: log_1 ( & format ! ( "Navigating to: {}" , reference_url) . into ( ) ) ;
468484 navigate ( & reference_url, NavigateOptions { scroll : false , ..Default :: default ( ) } ) ;
469485 // Close sidebar on mobile when reference is selected
470486 if is_mobile_screen ( ) {
471487 set_sidebar_open. set ( false ) ;
472488 save_references_sidebar_open ( false ) ;
473489 }
474490 } else {
475- web_sys:: console:: warn_1 ( & "Attempted to navigate to reference at invalid index" . into ( ) ) ;
491+ web_sys:: console:: warn_1 ( & format ! ( "Attempted to navigate to reference at invalid index: {} (refs.len: {})" , current , refs . len ( ) ) . into ( ) ) ;
476492 }
477493 set_is_navigating. set ( false ) ; // Clear navigation flag
478494 }
@@ -482,8 +498,14 @@ pub fn CrossReferencesSidebar(
482498 }
483499 } ;
484500
485- // Add keyboard event listener - with proper bounds checking to prevent WASM errors
486- window_event_listener ( ev:: keydown, handle_keydown) ;
501+ // Add keyboard event listener - with proper cleanup to prevent disposed reactive value access
502+ let _cleanup = window_event_listener ( ev:: keydown, handle_keydown) ;
503+ on_cleanup ( move || {
504+ // Mark component as disposed to prevent reactive value access
505+ is_disposed. store ( true , Ordering :: Relaxed ) ;
506+ // Cleanup is handled automatically when _cleanup is dropped
507+ drop ( _cleanup) ;
508+ } ) ;
487509
488510 view ! {
489511 <div
0 commit comments