Skip to content

Feature/webr playground - #107

Merged
correspondMerchant merged 12 commits into
mainfrom
feature/webr-playground
Sep 1, 2026
Merged

Feature/webr playground#107
correspondMerchant merged 12 commits into
mainfrom
feature/webr-playground

Conversation

@correspondMerchant

Copy link
Copy Markdown
Owner

What this changes

Adds an interactive, in-browser playground to the documentation site
(pkgdown/assets/playground.html) alongside the existing live demo. R runs
entirely 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:

  • Data: pick a bundled dataset (BB_data, mtcars) or upload a CSV
    (comma-separated, header row; numeric columns become the choices).
  • Model: a numeric-only outcome dropdown with auto binary/continuous
    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.
  • Run: the recommendation is drawn with the package's own D3 renderers
    (inst/js/lago-report.js, injected from the installed package at runtime, so
    there is a single source of truth with lago_report()) — a hover-enabled
    confidence-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 package
to 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.js guards that both pages stay in sync with
the build (12 assertions, run in CI).

UX and robustness, hardened over an internal review loop:

  • Separate, actionable messages for a webR-start failure vs a package-install
    failure (e.g. the wasm repo not being published yet on a fresh deploy).
  • A run that errors in R is framed as a settings problem with a suggested change,
    keeping the raw message as detail.
  • Run is enabled only when webR is ready and at least one component is ticked,
    with the reason shown while it is disabled; an empty component list shows an
    inline hint.
  • Reset button restores a clean runnable baseline without silently flipping the
    outcome/type.
  • The continuous goal slider is padded past the observed range so a goal beyond
    anything observed can be targeted.
  • A second concurrent optimization is prevented: while a run is in flight, Run,
    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.
  • Accessibility: the status line is a polite live region, the segmented toggles
    expose aria-pressed with group labels, the component group is labelled, and
    the drop zone is keyboard-operable.
  • Docs: NEWS.md, README.md, _pkgdown.yml nav, and reciprocal live-demo ↔
    playground links; webr-repo.yaml also runs on release: published to match
    the pkgdown workflow so a release rebuilds the wasm binary the docs install from.

Related issue

n/a (no tracking issue)

Checklist

  • Tests added or updated under tests/testthat/ — n/a: this change is site
    assets (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 (no R/,
    man/, or DESCRIPTION edits in this branch).
  • NEWS.md updated with a one-line entry for this user-facing change.

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.
@correspondMerchant
correspondMerchant merged commit f9da609 into main Sep 1, 2026
8 checks passed
@correspondMerchant
correspondMerchant deleted the feature/webr-playground branch September 1, 2026 18:48
@codecov

codecov Bot commented Sep 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant