fix: slope rules table clearing and data corruption on row removal (#1302)#1341
fix: slope rules table clearing and data corruption on row removal (#1302)#1341wangzhengdna-lang wants to merge 2 commits into
Conversation
…harmaverse#1302) When all slope rules were removed, the reactable table failed to clear visually and successive row removals corrupted the data (TYPE changing to "Selection", RANGE cleared, ghost columns appearing from row.names). Root cause was multi-layered — four fixes applied: 1. Replace validate(need(FALSE)) with return(NULL) in renderReactable to explicitly clear output (validate retained previous render). 2. Normalize both sides of rbind/bind_rows to common columns, preventing stray columns (e.g. ATPTREF from plot clicks) from leaking into the rules data frame. Applied in add handler, check_slope_rule_overlap, and render function. 3. Revert broken updateReactable in slope_selector.R to a no-op. The outputId was missing one module namespace suffix since PR pharmaverse#1262, making it a silent no-op. Fixing it caused double-trigger races with renderReactable that cleared widget selection state. 4. Add edit event suppression guard (generation counter + 500ms window) to prevent reactable.extras widgets from overwriting valid data with defaults during post-render initialization. This was the core issue causing TYPE/REASON/RANGE corruption. Also added an observer in tab_nca.R with ignoreNULL=FALSE to clear the Results > Manual Adjustments table when slope_rules becomes empty. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
The new code in reactable::updateReactable(ns("manual_slopes-table"), data = ...)This constructs the ID So Beyond the wrong ID, this pattern breaks module encapsulation. The parent is reaching into the child module's namespace by guessing its internal output name. If the child renames its output, this breaks silently. Suggested fix: Remove the data <- manual_slopes()
if (is.null(data) || nrow(data) == 0) {
return(NULL)
}When |
wangzhengdna-lang
left a comment
There was a problem hiding this comment.
@Shaakon35 Thanks for the careful review. However, the namespace analysis here is mixing up two different Shiny modules:
The two modules are different
| Results tab reactable | Setup tab reactable | |
|---|---|---|
| Module | reactable_server("manual_slopes", ...) |
manual_slopes_table_server("manual_slopes", ...) |
| Output ID pattern | output\$table → <ns>-manual_slopes-table |
output\$manual_slopes → <ns>-manual_slopes-manual_slopes |
| Location | tab_nca.R:118 |
slope_selector.R:245 |
The updateReactable in tab_nca.R targets ns("manual_slopes-table") which resolves to tab_nca-manual_slopes-table — this matches the reactable_server module's output ID (<ns>-manual_slopes-table). The namespace IS correct.
Why the observer is still needed
The reactable_server module uses req(data()) in its renderReactable (line 73-78 of reactable.R). When slope_rules() becomes NULL, req() silently aborts — retaining the previous table content instead of clearing it. This is the exact same pattern we fixed in manual_slopes_table.R (changed validate(need(FALSE)) → return(NULL) there).
Since we can't change reactable_server (it's shared by all tables in the app), the observer is the targeted fix: when slope rules are cleared, it explicitly updates this one table to show "No rules defined".
…armaverse#1302) Two new tests in each test file verify: - Extra columns in `existing` not present in `new` are dropped - Columns in `existing` not in `new` (e.g. REASON) are excluded from result Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Issue
Closes #1302
Description
When all slope rules were removed via the "- Remove selected rows" button, the reactable table failed to clear visually. Successive row removals further corrupted the displayed data: TYPE changed from "Exclusion" to "Selection", RANGE was cleared, and a ghost column (row.names leaked as data) appeared on the left.
Reported by @Gero1999 — identified that
req()guards silently abort on NULL, preventing table clearing after PR #1262 refactoredmanual_slopesto NULL initialization.Root Cause (4 layers)
validate(need(FALSE))retains previous output;req()silently abortsreturn(NULL)in renderReactable to explicitly clearreactable.extraswidgets (dropdown_extra,text_extra) send Shiny input events during post-render initialization, overwritingmanual_slopes()with widget defaultsrbind/bind_rowsmerge data frames with different column setscheck_slope_rule_overlap, render)updateReactable(silent no-op since #1262)outputIdin slope_selector.R missing one-manual_slopesnamespace suffix; fixing it caused double-trigger races withrenderReactablerenderReactable+bindEvent(refresh_reactable())already handles all updatesDefinition of Done
How to test
Requires manual testing (Shiny UI, cannot be covered by unit tests):
Files changed
inst/shiny/modules/tab_nca/setup/manual_slopes_table.R— Core fix:return(NULL), column normalization, edit suppression guardinst/shiny/modules/tab_nca/setup/slope_selector.R— Reverted brokenupdateReactableto no-opinst/shiny/functions/utils-slope_selector.R— Column normalization incheck_slope_rule_overlapinst/shiny/modules/tab_nca.R— Results tab clearing observertests/testthat/test-utils-slope_selector.R— Updated NULL→0-row DF assertions + column normalization testsinst/shiny/tests/testthat/test-utils-slope-selector.R— Updated NULL→0-row DF assertions + column normalization testsContributor checklist
check_slope_rule_overlapcolumn normalization has 4 new tests.scsschange was done, rundata-raw/compile_css.R— N/Adata-raw/test_suggests_hidden.R— N/ANotes to reviewer
reactable.extrasbehavior where widgets send Shiny input events during post-render initialization, which would otherwise overwrite valid data with widget defaults.updateReactableinslope_selector.Rwas intentionally reverted to a no-op rather than fixed. Fixing the namespace would cause double-trigger races withrenderReactable. TherenderReactable+bindEvent(refresh_reactable())pattern already handles all table updates.tab_nca.Robserver targets the Results tab'sreactable_server("manual_slopes")(output ID<ns>-manual_slopes-table), NOT the Setup tab'smanual_slopes_table_server(output ID<ns>-manual_slopes-manual_slopes). These are different Shiny modules.