Feature/webr playground - #107
Merged
Merged
Conversation
A new documentation page (pkgdown/assets/playground.html) turns the browser demo
into an interactive LAGO trial designer: it boots webR (pinned v0.6.0), installs
LAGOtrials from the site's /webr-repo, and reads the vendored D3 and
inst/js/lago-report.js out of the installed package so it renders with the
package's own charts. Users pick a bundled dataset (BB_data/mtcars) or upload a
CSV, choose the outcome and intervention components, set bounds, per-unit costs
and the goal, then Run: the recommendation shows as the console summary plus the
interactive confidence-set plot and per-component cost curves. Adds a Playground
navbar item and extends tests/js/test-webr-demo.js to guard both webR pages.
Robustness (from review): an empty confidence set serializes to {} (truthy), so
the confidence-set renderer was called with a non-array and threw, hiding the
cost curves; cost curves now render first and the confidence-set plot only draws
for a non-empty array. The confidence-set grid step is derived per component
from its range (~20 steps) instead of a hard-coded 1, so a wide continuous range
(mtcars) no longer builds a runaway grid. Switching datasets or uploading a CSV
clears the previous run's output and plots.
Replace the model-panel number inputs with tactile controls: each intervention component gets draggable lower/upper bound sliders spanning its observed data range (with live readouts and lower<=upper clamping) plus a unit-cost stepper (- / + buttons and a value), the outcome goal is a slider that re-ranges to 0-1 for a binary outcome or the outcome column's range for a continuous one, and the maximize/minimize direction is a segmented toggle button. Run reads the values from these controls; the optimization pipeline is unchanged.
The unit-cost input's min=0 only constrains the spinner, so a typed negative value could flow a negative cost into the optimization. Clamp it in the row's sync() handler so both the readout and the value passed to lago_optimization() stay non-negative.
Make the outcome and component pickers behave sensibly for arbitrary uploaded
data:
- the outcome dropdown lists only numeric columns (LAGO fits a GLM on the
outcome, so a character/id column can't be one), with a hint when there are
too few numeric columns;
- the component chips exclude whichever column is chosen as the outcome, and are
rebuilt (preserving ticks) whenever the outcome changes, so a column can't be
both the outcome and its own predictor;
- the outcome type is auto-detected from the selected column (values in {0,1} ->
binary, otherwise continuous) and shown as a segmented toggle the user can
override, and the goal slider re-ranges to match (0-1 for binary, the column's
range for continuous).
renderCompConfig() rebuilt every component row from column defaults, so switching the outcome (which triggers a rebuild via onOutcomeChanged) discarded any bound sliders or unit costs the user had already tuned. Snapshot the existing rows' values by data-comp before wiping the container, and restore them on the rebuilt rows (clamped to each row's current slider range), so an outcome-column change no longer resets the user's edits.
… a11y - Show the R code for the current configuration in a panel with a Copy button, live-updated from the controls (buildCall/updateRCode), so the playground doubles as a code generator to paste into RStudio. - Set loading expectations: a one-time-download note under the status line, and a "Computing the recommendation..." placeholder during a run, so neither the first load nor a run looks frozen. - Prefill a runnable example on first load: prefer a binary column as the outcome and tick up to two other numeric columns, so a newcomer can press Run immediately. - Accessibility: aria-labels on the bound/goal sliders and cost stepper, and the CSV drop zone is keyboard-operable (role=button, tabindex, Enter/Space).
- buildCall() now reads only rows that are actually rendered: ticking a component fires the delegated click/input listener (which rebuilds the R code) a tick before the checkbox 'change' builds the row, so the previous code threw a TypeError on the not-yet-rendered row. It self-healed on the following 'change', but now no exception is thrown; the transient component is simply omitted until its row exists. - The R-code panel no longer shows the Result panel's placeholder text during the webR load: the :empty::before placeholder is scoped to #output, and #rcode carries its own initial "choose an outcome..." hint. - The increase-cost button gets a per-component aria-label, matching its siblings.
- NEWS: extend the in-browser demo entry to cover the interactive playground. - README: the "try it in your browser" callout now also points to the playground. - live-demo.html: add a Playground link to its nav (the playground already links back to the quick demo). - playground.html: note that it exposes the common options and that advanced ones (power goal, center characteristics/fixed effects, icc, custom GLM family/link) are available via lago_optimization() in R; add a CSV format hint (comma-separated, header row, numeric columns) on the upload field.
From a completeness/UX gap audit of the in-browser feature: - Boot errors: split the webR start and the LAGOtrials install into separate try/catch blocks with distinct, actionable messages (browser/WASM/network vs package repo not published yet), so a first-visit-before-publish failure reads correctly instead of "Failed to start webR". - Run failures: frame a stop() from lago_optimization as a settings problem with a suggested change, keeping the raw R message as detail. - Run button is enabled only once webR is ready and at least one component is ticked, with the reason shown while disabled; empty component list now shows an inline hint instead of going silently blank. - Continuous outcome goal slider is padded half a span past the observed range so a goal beyond anything observed can be targeted; default stays in range. - Added a Reset button that restores the prefilled defaults and clears results. - Large CSV uploads warn that the tab may be slow rather than looking hung. - Accessibility: #status is a polite live region (both pages) and the segmented outcome-type/direction toggles expose aria-pressed and a group label. - README install callout also links the playground. - webr-repo workflow now also runs on release: published, matching pkgdown so a release rebuilds the wasm binary the docs install from.
From the two-reviewer round: - Guard against a second concurrent optimization: a mid-run model-panel edit fired updateRunEnabled(), which re-enabled Run before the "running…" hint guard. Add a `running` flag so Run stays disabled while a run is in flight (single-threaded webR shares global res/PG_DATA and shelter). - Fix the Run gate going stale on dataset switch / CSV upload: the dataset select is outside #model-panel so the delegated listener never fired; buildModelControls (the single rebuild funnel) now calls updateRunEnabled. - Reset no longer silently flips the outcome/type: it keeps the selected outcome and re-derives its type instead of re-running the first-load binary-outcome preference (which switched mtcars to a 0/1 column). - Don't classify an all-NA numeric column as binary (all() of an empty vector is TRUE); require a non-NA observation. Defensive; not reachable via read.csv. - Use errMsg() in the dataset-change catch, matching the other catches. - Associate the "Intervention components" label with its chip group (role=group + aria-labelledby). - test-webr-demo.js header now states it guards both webR pages.
Re-review follow-up: Run was gated by the running flag but Reset, the dataset select and the file input stayed live, so a mid-run reset/switch/upload rebuilt the controls while the in-flight result rendered against the old config, leaving the panels disagreeing. Disable those three alongside Run for the duration of a run and re-enable them in the finally. No state corruption before (webR serializes evals); this removes the transient visual mismatch.
The control lock disabled #file, but drag-and-drop calls readFile() directly and bypasses the disabled input, so a mid-run drop still rebuilt the controls under the in-flight result. Guard readFile with the running flag (covers both the drop and the file-change paths) and skip the drop-zone hover styling while running.
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
Adds an interactive, in-browser playground to the documentation site
(
pkgdown/assets/playground.html) alongside the existing live demo. R runsentirely client-side via webR (WebAssembly), so there is no install and no
server.
The playground lets a newcomer design a real
lago_optimization()run from a UI:BB_data,mtcars) or upload a CSV(comma-separated, header row; numeric columns become the choices).
detection, numeric component chips (the chosen outcome is excluded), per-component
lower/upper bound sliders and a unit-cost stepper, an outcome-goal
slider, and a maximize/minimize toggle.
(
inst/js/lago-report.js, injected from the installed package at runtime, sothere is a single source of truth with
lago_report()) — a hover-enabledconfidence-set plot plus per-component cost curves — next to the console
summary and a live, copy-pasteable R snippet for the current configuration.
A GitHub Actions workflow (
.github/workflows/webr-repo.yaml) builds the packageto WebAssembly with the rwasm toolchain and publishes it as a small CRAN-like
repository next to the site; the webR runtime is pinned to the build image's
version and
tests/js/test-webr-demo.jsguards that both pages stay in sync withthe build (12 assertions, run in CI).
UX and robustness, hardened over an internal review loop:
failure (e.g. the wasm repo not being published yet on a fresh deploy).
keeping the raw message as detail.
with the reason shown while it is disabled; an empty component list shows an
inline hint.
outcome/type.
anything observed can be targeted.
Reset, the dataset select, the file picker and drag-and-drop are all locked, so
nothing can rebuild the controls under an in-flight result.
expose
aria-pressedwith group labels, the component group is labelled, andthe drop zone is keyboard-operable.
NEWS.md,README.md,_pkgdown.ymlnav, and reciprocal live-demo ↔playground links;
webr-repo.yamlalso runs onrelease: publishedto matchthe pkgdown workflow so a release rebuilds the wasm binary the docs install from.
Related issue
n/a (no tracking issue)
Checklist
tests/testthat/— n/a: this change is siteassets (HTML/JS), a workflow, and docs; no R code changed. The browser
wiring is guarded by
tests/js/test-webr-demo.js(12 assertions, in CI),and the page's module script is
node --check-clean.devtools::document()— n/a: no roxygen comments changed.devtools::check()passes locally — not run: no R source changed (noR/,man/, orDESCRIPTIONedits in this branch).NEWS.mdupdated with a one-line entry for this user-facing change.