Description
When uploading settings that include a specific pcspec (specimen) selection, the pcspec picker briefly shows the correct imported value but then reverts to the default (e.g., "PLASMA").
Root Cause
The bidirectional cascade between the analyte and pcspec observers in inst/shiny/modules/tab_nca/setup/settings.R causes a double-trigger that loses the imported pcspec value.
The sequence:
settings_override() fires → pending_settings is set with imported settings
- Analyte observer fires → calls
.consume_settings() (reads and clears pending_settings) → sets pcspec to the imported value via updatePickerInput → sets profile ✅
- Pcspec observer fires (triggered by step 2's
updatePickerInput) → calls .consume_settings() → gets NULL (already consumed) → updates analyte picker with current value (no-op selection)
- Analyte observer fires again (triggered by step 3's
updatePickerInput for analyte) → calls .consume_settings() → gets NULL → computes pcspec with no override → falls back to default_logic (grep for "plasma"/"serum") → overwrites the imported pcspec ❌
The updating_filters guard doesn't prevent this because each observer resets the flag via on.exit() before the next observer fires (they run on separate flush cycles since updatePickerInput is async).
Expected Behavior
The pcspec picker should retain the imported value from settings after the cascade settles.
Possible Fix
The .consume_settings() pattern (read-once-then-clear) doesn't work with bidirectional cascades that span multiple flush cycles. Options:
- Don't clear
pending_settings until the full cascade settles. Use a debounce or a counter to track how many cascade rounds have occurred, and only clear after the cascade is idle.
- Use a flag to skip the second analyte observer fire. After the analyte observer runs with settings, set a flag that prevents the next analyte observer fire from recomputing pcspec.
- Prevent the pcspec observer from re-triggering the analyte observer when the selection hasn't actually changed. Compare
target_analyte with isolate(input$select_analyte) and skip the updatePickerInput call if they match.
Option 3 is the simplest and most targeted fix.
Steps to Reproduce
- Open the app and upload a dataset
- Configure NCA settings with a non-default pcspec (e.g., "URINE" instead of "PLASMA")
- Save settings to YAML
- Reload the app, upload the same dataset with the saved settings
- Observe that the pcspec picker shows the default value instead of the saved one
Description
When uploading settings that include a specific pcspec (specimen) selection, the pcspec picker briefly shows the correct imported value but then reverts to the default (e.g., "PLASMA").
Root Cause
The bidirectional cascade between the analyte and pcspec observers in
inst/shiny/modules/tab_nca/setup/settings.Rcauses a double-trigger that loses the imported pcspec value.The sequence:
settings_override()fires →pending_settingsis set with imported settings.consume_settings()(reads and clearspending_settings) → sets pcspec to the imported value viaupdatePickerInput→ sets profile ✅updatePickerInput) → calls.consume_settings()→ getsNULL(already consumed) → updates analyte picker with current value (no-op selection)updatePickerInputfor analyte) → calls.consume_settings()→ getsNULL→ computes pcspec with no override → falls back todefault_logic(grep for "plasma"/"serum") → overwrites the imported pcspec ❌The
updating_filtersguard doesn't prevent this because each observer resets the flag viaon.exit()before the next observer fires (they run on separate flush cycles sinceupdatePickerInputis async).Expected Behavior
The pcspec picker should retain the imported value from settings after the cascade settles.
Possible Fix
The
.consume_settings()pattern (read-once-then-clear) doesn't work with bidirectional cascades that span multiple flush cycles. Options:pending_settingsuntil the full cascade settles. Use a debounce or a counter to track how many cascade rounds have occurred, and only clear after the cascade is idle.target_analytewithisolate(input$select_analyte)and skip theupdatePickerInputcall if they match.Option 3 is the simplest and most targeted fix.
Steps to Reproduce