# Clean Room (R)

> **Beta Access** — Clean Room is currently in beta and not yet generally available. To request early access, email <info@gofigr.io>.

> Working in Python instead? See [Clean Room (Python)](https://docs.gofigr.io/features/clean-room-python).

## Overview

Clean Room turns an R function into a self-contained, reproducible, interactive application. You wrap a function with `reproducible()`, and GoFigr draws a clean boundary around it: the function can only access the parameters you declare, the packages you list, and `publish()`. When the function produces a figure, everything—source, data, parameters, environment—is captured and published automatically.

The workflow:

1. **Explore** — work however you normally work in RStudio or R Markdown
2. **Distill** — pull the core logic into a function with declared parameters
3. **Run** — wrap it in `reproducible()`; GoFigr captures everything
4. **Share** — send a link; stakeholders interact with the figure in the browser

## Quick Start

Load the package and configure it:

```r
library(gofigR)
library(ggplot2)

gofigR::enable(workspace_name = "Analytics",
               analysis_name  = "Penguins")
```

### Simplest Example

```r
reproducible(
  function(data = static(iris)) {
    p <- ggplot(data, aes(x = Sepal.Length, y = Petal.Length, color = Species)) +
      geom_point()
    publish(p, figure_name = "Iris Scatter")
  },
  packages = c("ggplot2")
)
```

That's it. When the function runs, GoFigr captures:

* **Source code** — the function body, extracted via R's AST
* **Parameters** — types, defaults, and widget metadata for every argument
* **Data** — data frames passed via `static()`, serialized as Parquet
* **Environment** — package names and versions, R version
* **Output** — the figures published by the run

## Interactive Mode

Add `interactive = TRUE` to launch a Shiny gadget with parameter widgets. Adjusting any control re-runs the function and updates the plot. Click **Publish** to push a revision with the current parameter values.

```r
reproducible(
  function(
      point_size = slider(2, min = 0.5, max = 5, step = 0.5),
      alpha      = slider(0.7, min = 0.1, max = 1.0, step = 0.1),
      color_by   = dropdown("Species",
                            choices = c("Species", "Sepal.Width", "Petal.Width")),
      show_smooth = checkbox(FALSE),
      data        = static(iris)
    ) {
      p <- ggplot(data, aes(x = Sepal.Length, y = Petal.Length,
                            color = .data[[color_by]])) +
        geom_point(size = point_size, alpha = alpha) +
        theme_minimal()

      if (show_smooth) {
        p <- p + geom_smooth(method = "lm", se = FALSE)
      }

      publish(p, figure_name = "Iris Scatter")
    },
    packages = c("ggplot2"),
    interactive = TRUE
)
```

Interactive mode requires a live, interactive R session. Inside `knitr`/R Markdown rendering or non-interactive scripts the gadget is skipped automatically and the function runs once with its default parameter values.

### Choosing the viewer

By default the gadget opens in the RStudio Viewer pane (when RStudio is available) or in a dialog window. Override with the `viewer` argument:

```r
# RStudio Viewer pane
reproducible(fn, packages = "ggplot2", interactive = TRUE,
             viewer = shiny::paneViewer())

# External browser
reproducible(fn, packages = "ggplot2", interactive = TRUE,
             viewer = shiny::browserViewer())

# Modal dialog
reproducible(fn, packages = "ggplot2", interactive = TRUE,
             viewer = shiny::dialogViewer("Clean Room", width = 1100, height = 700))
```

## Parameter Widgets

Parameters are declared as **function defaults** using one of the parameter constructors below. Plain defaults (e.g. `bins = 20`) are accepted and treated as static values, but for interactive mode you'll want explicit widget constructors.

### `slider()`

Numeric slider for `int` and `numeric` values.

```r
bins  = slider(20L, min = 5L,  max = 50L,  step = 5L)    # integer
alpha = slider(0.7, min = 0.1, max = 1.0,  step = 0.05)  # numeric
```

Pass an integer literal (`20L`) to get an integer slider; the gadget preserves integer type when feeding the value back into your function.

### `dropdown()`

Categorical dropdown with explicit choices.

```r
species = dropdown("Adelie", choices = c("Adelie", "Chinstrap", "Gentoo"))
```

### `checkbox()`

Boolean toggle.

```r
show_grid = checkbox(TRUE)
```

### `text_input()`

Free-form text input.

```r
title = text_input("Flipper Length Distribution")
```

### `static()`

Read-only value — no widget is rendered. Use this for data frames and any other value you want captured but not edited interactively. Plain defaults are wrapped in `static()` automatically, so `data = iris` and `data = static(iris)` are equivalent.

```r
data = static(penguins)
```

In the Clean Room studio the value is available for inspection, and data frames are serialized alongside the revision as Parquet.

## Packages

Declare every package the function needs in the `packages` argument. The clean execution environment will only have access to functions exported by those packages (plus base R and `publish()`).

```r
reproducible(fn, packages = c("ggplot2", "dplyr"))
```

### Pinning versions

`packages` accepts a named list to pin explicit versions in the manifest:

```r
reproducible(fn, packages = list(ggplot2 = "3.5.0", dplyr = "1.1.4"))
```

If you pass an unnamed character vector, the currently installed versions are resolved automatically.

### Imports / aliases

Use the `imports` argument to record alias-to-package mappings in the manifest (useful for parity with Python clients and for re-creating the environment elsewhere):

```r
reproducible(fn,
             packages = c("ggplot2"),
             imports  = list(plt = "ggplot2"))
```

## Publishing

Call `publish()` inside the function body. It is always injected into the clean environment — there is no separate "publisher" argument like in Python. The active session set up by `gofigR::enable()` determines where the figure goes.

```r
reproducible(
  function(data = static(mtcars)) {
    p <- ggplot(data, aes(mpg)) + geom_histogram(bins = 20)
    publish(p, figure_name = "MPG Distribution")
  },
  packages = c("ggplot2")
)
```

In interactive mode `publish()` is replaced with a no-op preview while you tweak parameters; clicking **Publish** in the gadget runs the function one more time with the real `publish()` and shows a link plus QR code to the new revision.

### What gets stored

Each published revision includes:

* **Source code** — the function body, extracted from the R AST
* **Manifest** — JSON with parameter types, widget metadata, package versions, and R version
* **DataFrame parameters** — serialized as Parquet
* **Revision flag** — marks the revision as a Clean Room revision

### Naming the function

Pass `name = "..."` to give the clean room function a display name in the webapp. Without it the function is anonymous.

```r
reproducible(fn, packages = c("ggplot2"), name = "Flipper Histogram")
```

## Edge Cases and Caveats

**Clean room isolation** — The function cannot access variables from your global R environment. Only declared package exports, base R, parameters, and `publish()` are available. This is by design.

**DataFrames are copied** — Data frame arguments are round-tripped through Parquet serialization before the function sees them. The function receives a deserialized copy, ensuring the clean room version matches what gets stored.

**Other params round-trip through JSON** — Atomic parameter values go through `jsonlite` so the function sees the same values that will be persisted in the manifest.

**100 MB limit** — Total data frame size (via `object.size()`) must be under 100 MB. If exceeded, a warning is issued and the function runs normally without clean room metadata.

**Unsupported parameter types** — Only atomic values (numeric, integer, logical, character), data frames, and nested lists of those types are serializable. Anything else triggers a warning and falls back to direct execution.

**`nanoparquet` required** — Clean room support requires the [`nanoparquet`](https://cran.r-project.org/package=nanoparquet) package. Without it a warning is issued and the function runs without clean room metadata.

**`interactive = TRUE` outside an interactive session** — Inside `knitr` or non-interactive R, the gadget is skipped and the function runs once with default parameters.

**Source code extraction** — The function body is captured from the R AST via `body(fn)`. Functions defined inline as the first argument to `reproducible()` work out of the box. Programmatically constructed functions also work as long as `body(fn)` returns a valid expression.

## Usage in R Markdown / knitr

`reproducible()` works inside R Markdown chunks. Interactive mode is automatically downgraded to a single-shot run during knitting:

````markdown
```{r setup, include=FALSE}
library(gofigR)
library(ggplot2)
gofigR::enable(workspace_name = "Scratchpad",
               analysis_name  = "Clean room in R")
```

```{r iris_scatter, fig.width=8, fig.height=6}
reproducible(
  function(
      point_size = slider(2, min = 0.5, max = 5, step = 0.5),
      data       = static(iris)
    ) {
      p <- ggplot(data, aes(Sepal.Length, Petal.Length, color = Species)) +
        geom_point(size = point_size) +
        theme_minimal()
      publish(p, figure_name = "Iris Scatter")
    },
    packages = c("ggplot2")
)
```
````

The chunk renders the figure inline, captures the clean room metadata, and publishes a revision to GoFigr — all from a single function call.
