Publishing runnable R chunks with CarmaR

A ‘Quarto’ site rendered with the carmar filter keeps every page exactly as rendered, and gives each visible R chunk a Run button that evaluates the chunk on the reader’s own computer, in the reader’s own R. No server runs the code; the page talks to a small local process - a CarmaR kernel - over a loopback socket. This vignette is the author’s story, then the reader’s.

The author

Enable publishing in the project once:

carmar::use_publishing("path/to/project")   # or "." from inside the project

This copies the extension into _extensions/carmar, puts - carmar under filters: in _quarto.yml (appended after any filters already there), and writes the two options you may want to change when the file has none:

Prefer a folder to a command? Download carmar-quarto.zip from https://lacarm.com/carmar/ and unzip it inside the project: it creates the same _extensions/carmar/ folder. Then add - carmar under filters: in _quarto.yml yourself (quarto add https://lacarm.com/carmar/carmar-quarto.zip does the copy too).

carmar:
  port: 4747
  label: Run on my computer

The port is the loopback port a reader’s kernel will listen on; leave it at 4747 unless something else on readers’ machines owns that port. The label is the text of the Run button. A _quarto.yml that use_publishing() cannot edit safely (an inline filters: [...] list) is left as it was and the lines to add are printed.

Render and publish as you always do:

quarto render
quarto publish

Before publishing, or after, check the rendered output:

carmar::check_publishing("_site")     # a book renders to "_book"

One row per page: port is NA for a page rendered without the filter, and assets is FALSE when the page references the runtime script and stylesheet but they are not beside it - which is what happens when a site is uploaded without its site_libs directory.

A chunk that should be shown but never offered to run goes in a div with the class carmar-no-run:

::: {.carmar-no-run}

``` r
unlink("~", recursive = TRUE)   # example only
```
:::

If the site sets its own Content Security Policy, allow the loopback socket the page dials, on the port you chose:

connect-src ws://127.0.0.1:4747

Pages that are already rendered

A report rendered years ago by R Markdown, a bookdown _book/, a pkgdown article: none of them needs rendering again. One tag before </head> loads the runtime, which gives every R block a Run button and the strip at the top:

carmar::make_runnable("report.html")     # one file
carmar::make_runnable("_book")           # every .html under a folder, once

By default the page loads the runtime from CarmaR’s published copy; pass assets = "local" to copy the two files beside the pages instead. The tag is inserted once; calling it again changes nothing.

Any page at all

A reader can make a page runnable that its author never touched: a bookmark whose address is the one-line script at https://lacarm.com/carmar/ injects the runtime on click. The kernel treats the site like any other — approved once per session in the consent tab, code run only on Run — so no author cooperation is needed, only a site whose Content-Security-Policy allows the script.

The reader

The reader needs R and this package:

install.packages("carmar")
carmar::listen("https://book.example")

listen() starts a headless kernel on the page’s port and approves that one site - scheme and host, nothing more - for the kernel’s lifetime. It prints one line:

CarmaR is listening on port 4747 for https://book.example - open the page and press Run.

Then the reader opens the page and presses Run. The chunk’s output appears under the chunk, from their own R, with their own packages and files.

If listen() is called without a site, the page’s first Run opens a local consent page naming the site, and the reader approves it there. Either way a site is never trusted because it asked: approval is per exact origin, per kernel, and ends when the kernel stops. The browser may also ask, once, for permission for the site to reach the local network; that is the browser’s own question, and answering yes lets the page reach 127.0.0.1 and nothing else.

This is execution, not a sandbox. What a reader runs has the same access as their own R console, so authors keep the code visible and readers read a chunk before running it.

To see and stop what is running:

carmar::sessions()
carmar::stop_kernel(4747)