@@ -64,9 +64,14 @@ fn trigger_preview_rebuild(state: &AppState, domain_code: &str) -> Task<Message>
6464 return Task :: none ( ) ;
6565 } ;
6666
67+ // Only source domains have mapping/normalization for preview
68+ let Some ( src) = domain. as_source ( ) else {
69+ return Task :: none ( ) ;
70+ } ;
71+
6772 let input = PreviewInput {
68- source_df : domain . source . data . clone ( ) ,
69- mapping : domain . mapping . clone ( ) ,
73+ source_df : src . source . data . clone ( ) ,
74+ mapping : src . mapping . clone ( ) ,
7075 ct_registry : state. terminology . clone ( ) ,
7176 } ;
7277
@@ -118,11 +123,12 @@ fn handle_mapping_message(state: &mut AppState, msg: MappingMessage) -> Task<Mes
118123 . study
119124 . as_mut ( )
120125 . and_then ( |s| s. domain_mut ( & domain_code) )
126+ . and_then ( |d| d. as_source_mut ( ) )
121127 {
122128 if let Err ( e) = domain. mapping . accept_suggestion ( & variable) {
123129 tracing:: error!( variable = %variable, error = %e, "Failed to accept suggestion" ) ;
124130 }
125- domain. invalidate_validation ( ) ;
131+ domain. validation_cache = None ;
126132 state. dirty_tracker . mark_dirty ( ) ;
127133 }
128134 if let ViewState :: DomainEditor ( editor) = & mut state. view {
@@ -140,9 +146,10 @@ fn handle_mapping_message(state: &mut AppState, msg: MappingMessage) -> Task<Mes
140146 . study
141147 . as_mut ( )
142148 . and_then ( |s| s. domain_mut ( & domain_code) )
149+ . and_then ( |d| d. as_source_mut ( ) )
143150 {
144151 domain. mapping . clear_assignment ( & variable) ;
145- domain. invalidate_validation ( ) ;
152+ domain. validation_cache = None ;
146153 state. dirty_tracker . mark_dirty ( ) ;
147154 }
148155 if let ViewState :: DomainEditor ( editor) = & mut state. view {
@@ -160,11 +167,12 @@ fn handle_mapping_message(state: &mut AppState, msg: MappingMessage) -> Task<Mes
160167 . study
161168 . as_mut ( )
162169 . and_then ( |s| s. domain_mut ( & domain_code) )
170+ . and_then ( |d| d. as_source_mut ( ) )
163171 {
164172 if let Err ( e) = domain. mapping . accept_manual ( & variable, & column) {
165173 tracing:: error!( variable = %variable, column = %column, error = %e, "Failed to accept manual mapping" ) ;
166174 }
167- domain. invalidate_validation ( ) ;
175+ domain. validation_cache = None ;
168176 state. dirty_tracker . mark_dirty ( ) ;
169177 }
170178 if let ViewState :: DomainEditor ( editor) = & mut state. view {
@@ -204,9 +212,10 @@ fn handle_mapping_message(state: &mut AppState, msg: MappingMessage) -> Task<Mes
204212 . study
205213 . as_mut ( )
206214 . and_then ( |s| s. domain_mut ( & domain_code) )
215+ . and_then ( |d| d. as_source_mut ( ) )
207216 {
208217 let _ = domain. mapping . mark_not_collected ( & variable, & reason) ;
209- domain. invalidate_validation ( ) ;
218+ domain. validation_cache = None ;
210219 state. dirty_tracker . mark_dirty ( ) ;
211220 }
212221 if let ViewState :: DomainEditor ( editor) = & mut state. view {
@@ -245,9 +254,10 @@ fn handle_mapping_message(state: &mut AppState, msg: MappingMessage) -> Task<Mes
245254 . study
246255 . as_mut ( )
247256 . and_then ( |s| s. domain_mut ( & domain_code) )
257+ . and_then ( |d| d. as_source_mut ( ) )
248258 {
249259 domain. mapping . clear_assignment ( & variable) ;
250- domain. invalidate_validation ( ) ;
260+ domain. validation_cache = None ;
251261 state. dirty_tracker . mark_dirty ( ) ;
252262 }
253263 if let ViewState :: DomainEditor ( editor) = & mut state. view {
@@ -265,9 +275,10 @@ fn handle_mapping_message(state: &mut AppState, msg: MappingMessage) -> Task<Mes
265275 . study
266276 . as_mut ( )
267277 . and_then ( |s| s. domain_mut ( & domain_code) )
278+ . and_then ( |d| d. as_source_mut ( ) )
268279 {
269280 let _ = domain. mapping . mark_omit ( & variable) ;
270- domain. invalidate_validation ( ) ;
281+ domain. validation_cache = None ;
271282 state. dirty_tracker . mark_dirty ( ) ;
272283 }
273284 if let ViewState :: DomainEditor ( editor) = & mut state. view {
@@ -285,9 +296,10 @@ fn handle_mapping_message(state: &mut AppState, msg: MappingMessage) -> Task<Mes
285296 . study
286297 . as_mut ( )
287298 . and_then ( |s| s. domain_mut ( & domain_code) )
299+ . and_then ( |d| d. as_source_mut ( ) )
288300 {
289301 domain. mapping . clear_assignment ( & variable) ;
290- domain. invalidate_validation ( ) ;
302+ domain. validation_cache = None ;
291303 state. dirty_tracker . mark_dirty ( ) ;
292304 }
293305 if let ViewState :: DomainEditor ( editor) = & mut state. view {
@@ -348,20 +360,25 @@ fn handle_validation_message(state: &mut AppState, msg: ValidationMessage) -> Ta
348360 return Task :: none ( ) ;
349361 } ;
350362
363+ // Only source domains support refresh validation
364+ let Some ( src) = domain. as_source ( ) else {
365+ return Task :: none ( ) ;
366+ } ;
367+
351368 let df = match & state. view {
352369 ViewState :: DomainEditor ( editor) => {
353370 // Use preview cache if available, otherwise clone from Arc<DataFrame>
354371 editor
355372 . preview_cache
356373 . clone ( )
357- . unwrap_or_else ( || ( * domain . source . data ) . clone ( ) )
374+ . unwrap_or_else ( || ( * src . source . data ) . clone ( ) )
358375 }
359- _ => ( * domain . source . data ) . clone ( ) ,
376+ _ => ( * src . source . data ) . clone ( ) ,
360377 } ;
361378
362- let sdtm_domain = domain . mapping . domain ( ) . clone ( ) ;
379+ let sdtm_domain = src . mapping . domain ( ) . clone ( ) ;
363380 let not_collected: std:: collections:: BTreeSet < String > =
364- domain . mapping . all_not_collected ( ) . keys ( ) . cloned ( ) . collect ( ) ;
381+ src . mapping . all_not_collected ( ) . keys ( ) . cloned ( ) . collect ( ) ;
365382
366383 let input = ValidationInput {
367384 domain : sdtm_domain,
@@ -410,8 +427,10 @@ fn handle_validation_message(state: &mut AppState, msg: ValidationMessage) -> Ta
410427 ValidationMessage :: GoToIssueSource { variable } => {
411428 if let ViewState :: DomainEditor ( editor) = & mut state. view {
412429 editor. tab = EditorTab :: Mapping ;
413- if let Some ( domain) = state. study . as_ref ( ) . and_then ( |s| s. domain ( & domain_code) ) {
414- let sdtm_domain = domain. mapping . domain ( ) ;
430+ if let Some ( domain) = state. study . as_ref ( ) . and_then ( |s| s. domain ( & domain_code) )
431+ && let Some ( src) = domain. as_source ( )
432+ {
433+ let sdtm_domain = src. mapping . domain ( ) ;
415434 if let Some ( idx) = sdtm_domain
416435 . variables
417436 . iter ( )
@@ -471,7 +490,13 @@ fn handle_preview_message(state: &mut AppState, msg: PreviewMessage) -> Task<Mes
471490 }
472491
473492 PreviewMessage :: RebuildPreview => {
474- let Some ( domain) = state. study . as_ref ( ) . and_then ( |s| s. domain ( & domain_code) ) else {
493+ // Preview rebuild only applies to source domains
494+ let Some ( source) = state
495+ . study
496+ . as_ref ( )
497+ . and_then ( |s| s. domain ( & domain_code) )
498+ . and_then ( |d| d. as_source ( ) )
499+ else {
475500 return Task :: none ( ) ;
476501 } ;
477502
@@ -481,8 +506,8 @@ fn handle_preview_message(state: &mut AppState, msg: PreviewMessage) -> Task<Mes
481506 }
482507
483508 let input = PreviewInput {
484- source_df : domain . source . data . clone ( ) ,
485- mapping : domain . mapping . clone ( ) ,
509+ source_df : source . source . data . clone ( ) ,
510+ mapping : source . mapping . clone ( ) ,
486511 ct_registry : state. terminology . clone ( ) ,
487512 } ;
488513
@@ -515,13 +540,14 @@ fn handle_supp_message(state: &mut AppState, msg: SuppMessage) -> Task<Message>
515540 editor. supp_ui . selected_column = Some ( col_name. clone ( ) ) ;
516541 editor. supp_ui . edit_draft = None ;
517542 }
518- // Initialize config if not exists
519- if let Some ( domain ) = state
543+ // Initialize config if not exists (only for source domains)
544+ if let Some ( source ) = state
520545 . study
521546 . as_mut ( )
522547 . and_then ( |s| s. domain_mut ( & domain_code) )
548+ . and_then ( |d| d. as_source_mut ( ) )
523549 {
524- domain
550+ source
525551 . supp_config
526552 . entry ( col_name. clone ( ) )
527553 . or_insert_with ( || SuppColumnConfig :: from_column ( & col_name) ) ;
@@ -596,11 +622,12 @@ fn handle_supp_message(state: &mut AppState, msg: SuppMessage) -> Task<Message>
596622 } ;
597623
598624 if let Some ( col_name) = col
599- && let Some ( domain ) = state
625+ && let Some ( source ) = state
600626 . study
601627 . as_mut ( )
602628 . and_then ( |s| s. domain_mut ( & domain_code) )
603- && let Some ( config) = domain. supp_config . get_mut ( & col_name)
629+ . and_then ( |d| d. as_source_mut ( ) )
630+ && let Some ( config) = source. supp_config . get_mut ( & col_name)
604631 {
605632 if config. qnam . trim ( ) . is_empty ( ) || config. qlabel . trim ( ) . is_empty ( ) {
606633 return Task :: none ( ) ;
@@ -621,11 +648,12 @@ fn handle_supp_message(state: &mut AppState, msg: SuppMessage) -> Task<Message>
621648 } ;
622649
623650 if let Some ( col_name) = col
624- && let Some ( domain ) = state
651+ && let Some ( source ) = state
625652 . study
626653 . as_mut ( )
627654 . and_then ( |s| s. domain_mut ( & domain_code) )
628- && let Some ( config) = domain. supp_config . get_mut ( & col_name)
655+ . and_then ( |d| d. as_source_mut ( ) )
656+ && let Some ( config) = source. supp_config . get_mut ( & col_name)
629657 {
630658 config. action = SuppAction :: Skip ;
631659 state. dirty_tracker . mark_dirty ( ) ;
@@ -643,11 +671,12 @@ fn handle_supp_message(state: &mut AppState, msg: SuppMessage) -> Task<Message>
643671 } ;
644672
645673 if let Some ( col_name) = col
646- && let Some ( domain ) = state
674+ && let Some ( source ) = state
647675 . study
648676 . as_mut ( )
649677 . and_then ( |s| s. domain_mut ( & domain_code) )
650- && let Some ( config) = domain. supp_config . get_mut ( & col_name)
678+ . and_then ( |d| d. as_source_mut ( ) )
679+ && let Some ( config) = source. supp_config . get_mut ( & col_name)
651680 {
652681 config. action = SuppAction :: Pending ;
653682 state. dirty_tracker . mark_dirty ( ) ;
@@ -665,8 +694,12 @@ fn handle_supp_message(state: &mut AppState, msg: SuppMessage) -> Task<Message>
665694 } ;
666695
667696 if let Some ( col_name) = & col
668- && let Some ( domain) = state. study . as_ref ( ) . and_then ( |s| s. domain ( & domain_code) )
669- && let Some ( config) = domain. supp_config . get ( col_name)
697+ && let Some ( source) = state
698+ . study
699+ . as_ref ( )
700+ . and_then ( |s| s. domain ( & domain_code) )
701+ . and_then ( |d| d. as_source ( ) )
702+ && let Some ( config) = source. supp_config . get ( col_name)
670703 {
671704 let draft = SuppEditDraft :: from_config ( config) ;
672705 if let ViewState :: DomainEditor ( editor) = & mut state. view {
@@ -690,11 +723,12 @@ fn handle_supp_message(state: &mut AppState, msg: SuppMessage) -> Task<Message>
690723 return Task :: none ( ) ;
691724 }
692725
693- if let Some ( domain ) = state
726+ if let Some ( source ) = state
694727 . study
695728 . as_mut ( )
696729 . and_then ( |s| s. domain_mut ( & domain_code) )
697- && let Some ( config) = domain. supp_config . get_mut ( & col_name)
730+ . and_then ( |d| d. as_source_mut ( ) )
731+ && let Some ( config) = source. supp_config . get_mut ( & col_name)
698732 {
699733 config. qnam = draft. qnam ;
700734 config. qlabel = draft. qlabel ;
@@ -746,8 +780,12 @@ where
746780 let mut dummy = SuppColumnConfig :: from_column ( "" ) ;
747781 update ( & mut dummy, Some ( draft) ) ;
748782 }
749- } else if let Some ( domain) = state. study . as_mut ( ) . and_then ( |s| s. domain_mut ( domain_code) )
750- && let Some ( config) = domain. supp_config . get_mut ( & col_name)
783+ } else if let Some ( source) = state
784+ . study
785+ . as_mut ( )
786+ . and_then ( |s| s. domain_mut ( domain_code) )
787+ . and_then ( |d| d. as_source_mut ( ) )
788+ && let Some ( config) = source. supp_config . get_mut ( & col_name)
751789 {
752790 update ( config, None ) ;
753791 }
0 commit comments