<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Best Practices on Posit Open Source</title>
    <link>https://opensource.posit.co/topics/best-practices/</link>
    <description>Recent content in Best Practices on Posit Open Source</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en-us</language>
    <lastBuildDate>Thu, 15 Oct 2026 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://opensource.posit.co/topics/best-practices/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>rig 0.11.0 part three: R projects</title>
      <link>https://opensource.posit.co/blog/2026-10-15_rig-0-11-0-part-three/</link>
      <pubDate>Thu, 15 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-10-15_rig-0-11-0-part-three/</guid>
      <dc:creator>Gábor Csárdi</dc:creator><description><![CDATA[<!--
TODO:
- [x] Add image (1920×1080 PNG or JPG) and image-alt
- [x] Trim topics, software, and languages to only what applies
- [x] Open a PR against main for a Netlify preview
-->
<style>
.prose pre {
  font-size: 0.65em;
}
</style>
<p>We are thrilled to announce rig 0.11.0 (and 0.10.0), with lots of new
functionalities: user mode R installations, R package and project management
and much more. This is the third of three blog posts about the new rig
release. This post is about managing R projects.</p>
<p>The <a href="https://opensource.posit.co/blog/2026-10-09_rig-0-11-0-part-one/">first post</a> covered user mode R installations, the <a href="https://opensource.posit.co/blog/2026-10-12_rig-0-11-0-part-two/">second</a> focused on R
packages.</p>
<p><a href="https://rig.r-lib.org/news.html" target="_blank" rel="noopener">Full changelog on the rig web site</a>.</p>
<h2 id="introduction">Introduction
</h2>
<p>The new <code>rig proj</code> command lets you manage R projects and virtual
environments. An R project lets you share and run R code in a standardized
way. rig&rsquo;s R projects use an <code>rproj.toml</code> manifest, which is still
<a href="https://github.com/tidyverse/tidyups/pull/34" target="_blank" rel="noopener">under development</a>.</p>
<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><code>rig proj</code> is new and less battle-tested than other parts of rig.
Please report any issues you find on the <a href="https://github.com/r-lib/rig/issues" target="_blank" rel="noopener">rig GitHub repository</a>.</p>
</div>
</div>
<p>rig&rsquo;s R project implementation largely works the same way as other project
management tools for Python, Rust, and other languages.</p>
<h2 id="the-project-lifecycle">The project lifecycle
</h2>
<p>A project lifecycle typically involves</p>
<ol>
<li><code>rig proj init</code> (or <code>rig proj import</code>) to create a new project.</li>
<li><code>rig proj add</code> (and <code>rig proj remove</code>) to manage project dependencies.</li>
<li><code>rig proj lock</code> to resolve how the project is installed over a range
of R versions in a range of platforms.</li>
<li><code>rig proj sync</code> to install a project according to the lock file.</li>
<li><code>rig run</code> to execute R code within the context of the project.</li>
</ol>
<h2 id="creating-projects">Creating projects
</h2>
<p>There are three ways to create an R project. One is to create a brand new
project with <code>rig proj init</code>. This creates a minimal <code>rproj.toml</code> manifest
file that you can either edit manually or via <code>rig proj add</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig proj init
</span></span><span class="line"><span class="cl">✓ Created rproj.toml
</span></span><span class="line"><span class="cl">✓ Created .Renviron
</span></span><span class="line"><span class="cl">✓ Created .gitignore
</span></span><span class="line"><span class="cl">✓ Created .rvenvlib/rvenv
</span></span><span class="line"><span class="cl">ℹ Project set up for R 4.6.1. Next: add dependencies to rproj.toml, then
</span></span><span class="line"><span class="cl">  run `rig proj lock` and `rig proj sync`.</span></span></code></pre></div></div>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ cat rproj.toml
</span></span><span class="line"><span class="cl">[project]
</span></span><span class="line"><span class="cl">name = &#34;x&#34;
</span></span><span class="line"><span class="cl">version = &#34;0.1.0&#34;
</span></span><span class="line"><span class="cl">type = &#34;project&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[dependencies]
</span></span><span class="line"><span class="cl">R = &#34;&gt;= 4.6&#34;</span></span></code></pre></div></div>
<p>Another way is to convert an existing project to an <code>rproj.toml</code> manifest.
Start with an existing R project with a <code>DESCRIPTION</code>
file and run <code>rig proj import</code> to create an <code>rproj.toml</code> manifest from it.
Or start with an renv lock file and call <code>rig proj renv import</code>.
<code>rig proj import</code> keeps all fields of <code>DESCRIPTION</code>, including various
types of dependencies. For R packages you can use <code>rproj.toml</code> as the
source of truth for package metadata and dependencies and (re)generate
<code>DESCRIPTION</code> from it as needed, e.g. for CRAN submission.</p>
<p>A third way to create a project is to write an <code>rproj.toml</code> manifest manually.</p>
<h3 id="project-types">Project types
</h3>
<p>There are two types of R manifests: <em>projects</em> and <em>packages</em>. In general
we use <em>project</em> as a generic name for both types and consider a <em>package</em> as
a specialized project.</p>
<p>For a package, create the <code>R/</code>, <code>src/</code>, etc. directories as usual. See below
for a possible way to use the devtools based development tools for a rig
project.</p>
<p>For projects rig does not have a suggested way for organizing R and other
code, documentation and tests. If in doubt, follow the conventions of
R packages. Use <code>[[bin]]</code> to specify executable scripts within the project,
relative to the project root. You&rsquo;ll be able to run these scripts with
<code>rig run</code>.</p>
<h2 id="locking-a-project">Locking a project
</h2>
<p><code>rig proj lock</code> reads <code>rproj.toml</code> and works out which exact packages
should be downloaded and installed to satisfy <em>all</em> requirements from
<code>rproj.toml</code>. All requirements always include the optional and soft
dependencies. The result is a lock file called <code>rproj.lock</code>. This is also a
TOML file, typically much bigger than the original <code>rproj.toml</code> project
manifest.</p>
<h3 id="universal-lock-files">Universal lock files
</h3>
<p>The lock can and usually does include packages for multiple platforms.
The exact set of platforms and R versions to include in the lock file can
be specified in <code>rproj.toml</code> or at the command line. By default rig includes
the current R release and five platforms: the platform of the current
machine, macOS arm64, Windows x86_64, generic glibc Linux and <code>source</code>
(four platforms if the current platform coincides with one of the others).</p>
<p>A multi-platform lock file means that it is much easier to plan the
cross-platform deployment of a project: a team can include all relevant
platforms and R versions in the lock file and commit a lock file to version
control.</p>
<p>rig also records system requirements in the lock file, for platforms that
need them.</p>
<h3 id="stable-urls">Stable URLs
</h3>
<p>Universal lock files only really work well if they contain stable URLs for
binary packages for all platforms and R versions. This is why it is
important to use P3M (or another Posit Package Manager instance) with
extended metadata about all older package versions and all their
corresponding binary builds. With P3M rig is typically able to generate
lock files with binary packages for the last 3-4 R versions on all common
platforms.</p>
<h3 id="sticky-lock-files">Sticky lock files
</h3>
<p>Adding new dependencies to a project runs <code>rig proj lock</code>, but it keeps the
package versions already in <code>rproj.lock</code>, unless they conflict with the new
requirements. This is important to be able to maintain a stable set of
package versions while still adding new dependencies. To update an already
existing dependency to a newer version, you need to run
<code>rig proj lock --upgrade-package</code>. Or run <code>rig proj lock --upgrade</code> to
upgrade all packages.</p>
<h3 id="preferring-binary-packages">Preferring binary packages
</h3>
<p>By default rig does not choose an older binary package over a newer source
package. You can change that with the <code>prefer-binary</code> setting in <code>rproj.toml</code>.
E.g. <code>prefer-binary = true</code> means that rig will choose an older binary
package over a newer source package, if the older package also satisfies
the dependency requirements.</p>
<h3 id="excluding-newer-package-versions">Excluding newer package versions
</h3>
<p>You can prevent rig from automatically upgrading to newer package versions
by using the <code>exclude-newer</code> setting in <code>rproj.toml</code>. For example,
<code>exclude-newer = &quot;2026-09-30&quot;</code> will exclude any package versions that were
published after the specified date. This works for P3M&rsquo;s CRAN and
Bioconductor repositories, but not for generic CRAN-like repositories
which typically do not have release dates.</p>
<h3 id="repositories">Repositories
</h3>
<p>Projects ignore the global repository settings configured via <code>rig repos</code>.
By default projects use P3M&rsquo;s CRAN and Bioconductor repositories.
This can be configured with <code>[[repository]]</code> entries in <code>rproj.toml</code>.
You can turn off P3M&rsquo;s CRAN and Bioconductor repositories and you can
also add your own custom repositories.</p>
<p>rig&rsquo;s solver and universal lock files work best with P3M&rsquo;s CRAN and
Bioconductor metadata, so we strongly recommend using them whenever
possible.</p>
<h2 id="syncing-a-project">Syncing a project
</h2>
<p><code>rig proj sync</code> installs the packages listed in <code>rproj.lock</code> into the
project library in <code>.rvenv/lib</code>, inside the project&rsquo;s virtual environment.
It locks first if the project manifest has changed since creating the
lock file.</p>
<p>If the current project is a package (<code>type = &quot;package&quot;</code>), then
<code>rig proj sync</code> will also install the package itself into the project
library.</p>
<p><code>rig proj sync</code> also installs the R version requested in the lock file,
if it is not already installed. This makes it very easy to deploy a
project in an empty container or virtual machine: only rig and the lock
file are needed and rig installs everything required for the project: R,
system requirements and R packages.</p>
<p><code>rig proj sync</code> installs the hard requirements and the <code>dev</code> dependency
group by default. For a production deployment you can use the <code>--no-dev</code>
option to skip installing development dependencies. Use <code>--group</code> and
<code>--extra</code> to install optional dependency groups and extra dependencies.</p>
<h2 id="fast-and-lean-libraries">Fast and lean libraries
</h2>
<p>rig clones the package files when installing packages into the project
library, whenever possible. This means that installation is very fast and
space-efficient. Having the same package version installed for ten projects
on the same disk only takes up the space of a single copy.</p>
<h2 id="the-virtual-environment">The virtual environment
</h2>
<p>A rig project has two kinds of files. <code>rig proj init</code> creates the ones that
belong in version control: <code>rproj.toml</code>, <code>.Renviron</code>, <code>.rvenvlib/rvenv</code> and
<code>.gitignore</code>. Commit these, together with <code>rproj.lock</code>, and a fresh clone of
the project works after a <code>rig proj sync</code>, or right away with <code>rig run</code>.</p>
<p><code>rig proj sync</code> creates the rest in the <code>.rvenv</code> directory: the project
library in <code>.rvenv/lib</code>, <code>R</code> and <code>Rscript</code> wrapper scripts in <code>.rvenv/bin</code>,
and some configuration files. <code>.rvenv</code> is specific to the machine, it is in
<code>.gitignore</code>, and you can delete it any time, <code>rig proj sync</code> creates it
again.</p>
<p>The <code>.Renviron</code> file and the small <code>rvenv</code> package in <code>.rvenvlib</code> make sure
that R uses the project library whenever it starts in the project directory:
from a terminal, or from RStudio, Positron or VS Code. You do not need to
activate the project explicitly, although <code>.rvenv/bin</code> has activation
scripts for the common shells if you prefer that.</p>
<p>Inside the project R only sees the project library, the <code>tools</code> library
(see below) and the packages that come with R. Packages from your user or
site library do not leak into the project.</p>
<p>If the project library is out of date compared to <code>rproj.lock</code>, e.g. after
a <code>git pull</code>, R prints a warning at startup, and <code>rig proj status</code> tells
you what changed. If you start R with <code>rig run</code> in the project directory,
then it automatically runs <code>rig proj sync</code> as needed. If you use Positron,
VS Code, RStudio or another IDE, then R will warn at startup if the virtual
environment is out of date and suggest that you run <code>rig proj sync</code> and
restart R.</p>
<h2 id="managing-dependencies">Managing dependencies
</h2>
<p>R package dependencies are specified in the <code>rproj.toml</code> manifest under three
types of tables. The <code>[dependencies]</code> table includes packages that are
always installed. These are considered as R packages required for
the project. Packages in <code>[dependency-groups.&lt;name&gt;]</code> are groups of
development dependencies and they are only installed if requested, except
for the <code>dev</code> group which is installed by default. For example, the packages
that are needed for building the pkgdown website can go into a
<code>[dependency-groups.website]</code> table. <code>dependency-groups</code> is similar to
<code>Config/Needs/&lt;name&gt;</code> in <code>DESCRIPTION</code> files of R packages.
Dependencies of optional features go into <code>[optional-dependencies.&lt;name&gt;]</code>
tables and are only installed if requested. For example, if your package
has functions for visualization, but these are not required for the core
functionality, you may put them in an <code>[optional-dependencies.viz]</code> table
and other packages using yours may opt in to the <code>viz</code> feature.
(The infrastructure for depending on optional features in packages is not
built yet, and package repositories need to support it as well.)</p>
<p>You can edit the <code>rproj.toml</code> manifest manually to add dependencies or
use <code>rig proj</code> commands to do it. <code>rig proj add</code> adds packages to
<code>rproj.toml</code>, then updates <code>rproj.lock</code> and installs the new packages into
the project library:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig proj add dplyr cli</span></span></code></pre></div></div>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ cat rproj.toml
</span></span><span class="line"><span class="cl">[project]
</span></span><span class="line"><span class="cl">name = &#34;x&#34;
</span></span><span class="line"><span class="cl">version = &#34;0.1.0&#34;
</span></span><span class="line"><span class="cl">type = &#34;project&#34;
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">[dependencies]
</span></span><span class="line"><span class="cl">R = &#34;&gt;= 4.6&#34;
</span></span><span class="line"><span class="cl">cli = &#34;*&#34;
</span></span><span class="line"><span class="cl">dplyr = &#34;*&#34;</span></span></code></pre></div></div>
<p>Without a version requirement rig adds a package as <code>&quot;*&quot;</code>, any version. The
exact version is recorded in <code>rproj.lock</code>, so you only need a version
requirement if the project really needs a certain version. Version
requirements use the same syntax as for <code>rig pkg install</code>, e.g.
<code>'cli@&gt;= 3.6'</code> or <code>'dplyr@^1.1.0'</code>. <code>rig proj remove</code> removes packages.</p>
<p>Use <code>--dev</code> to add a development dependency, e.g. testthat. These go into
the <code>[dependency-groups.dev]</code> table.</p>
<p>Packages from GitHub, GitLab, git repositories, URLs and local paths use the
same syntax as <code>rig pkg install</code>, see the <a href="https://opensource.posit.co/blog/2026-10-12_rig-0-11-0-part-two/">second post</a>. Bioconductor or
other packages coming from the configured repositories work the same way as
CRAN packages. To pin a package to a certain repository, e.g. an R universe,
use <code>--with-repos</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig proj add --with-repos rlib=https://r-lib.r-universe.dev cli</span></span></code></pre></div></div>
<p>This adds the repository to <code>rproj.toml</code> and pins <code>cli</code> to it:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">dependencies</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">cli</span> <span class="p">=</span> <span class="p">{</span> <span class="nx">version</span> <span class="p">=</span> <span class="s2">&#34;*&#34;</span><span class="p">,</span> <span class="nx">repository</span> <span class="p">=</span> <span class="s2">&#34;rlib&#34;</span> <span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">repository</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;rlib&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">url</span> <span class="p">=</span> <span class="s2">&#34;https://r-lib.r-universe.dev&#34;</span></span></span></code></pre></div></div>
<p>If rig cannot resolve the new dependencies, e.g. because of a typo in a
package name, it restores <code>rproj.toml</code>.</p>
<h2 id="running-a-project">Running a project
</h2>
<p><code>rig run</code> starts R in the project&rsquo;s environment, from anywhere inside the
project. It locks and syncs the project first, if needed, so day to day you
mostly need <code>rig proj add</code>, <code>rig proj remove</code> and <code>rig run</code>.</p>
<p><code>rig run script.R</code> runs a script in the project&rsquo;s environment. Arguments
after the script name go to the script, and <code>commandArgs(TRUE)</code> picks them
up. Use <code>--no-project</code> to run the script without the project.</p>
<p>A project can give names to its scripts, in <code>[[bin]]</code> tables of
<code>rproj.toml</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[[</span><span class="nx">bin</span><span class="p">]]</span>
</span></span><span class="line"><span class="cl"><span class="nx">name</span> <span class="p">=</span> <span class="s2">&#34;report&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">path</span> <span class="p">=</span> <span class="s2">&#34;scripts/report.R&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">description</span> <span class="p">=</span> <span class="s2">&#34;Build the report&#34;</span></span></span></code></pre></div></div>
<p>Then <code>rig run report --format pdf</code> runs <code>scripts/report.R</code> with the
<code>--format pdf</code> arguments, and <code>rig run --list</code> lists all named scripts of
the project.</p>
<p>You can also run the <code>.rvenv/bin/R</code> and <code>.rvenv/bin/Rscript</code> wrappers
directly, or start R from your IDE in the project directory, see the
virtual environment above.</p>
<h2 id="package-development-with-devtools">Package development with devtools
</h2>
<p>To develop an R package as a rig project, use <code>type = &quot;package&quot;</code> in
<code>rproj.toml</code>, or run <code>rig proj import</code> on an existing package.
<code>rig proj sync</code> then installs the package itself into the project library,
and creates <code>DESCRIPTION</code> from <code>rproj.toml</code> (if missing), so <code>R CMD build</code>,
<code>R CMD check</code> and other tools keep working.</p>
<p>Development tools like devtools, usethis and roxygen2 are not dependencies
of the package, so they should not be in <code>rproj.toml</code>. rig has a special
global package library for them, called <code>tools</code>, and adds it to the library
path of every project. Install your tools there once:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg install -l tools devtools usethis roxygen2</span></span></code></pre></div></div>
<p>and then they are available in all projects:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig run
</span></span><span class="line"><span class="cl">&gt; devtools::load_all()
</span></span><span class="line"><span class="cl">&gt; devtools::document()
</span></span><span class="line"><span class="cl">&gt; devtools::test()</span></span></code></pre></div></div>
<p>Packages that the package&rsquo;s tests and documentation need, e.g. testthat or
knitr, are different. Add these with <code>rig proj add --dev</code>, so they are
locked together with the other dependencies.</p>
<h2 id="inline-scripts">Inline scripts
</h2>
<p>Sometimes a project is just a single R script. For these rig supports inline
dependencies: the script declares the R version and the packages it needs in
a special comment block with a <code># /// script</code> header and a <code># ///</code> footer:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># /// script</span>
</span></span><span class="line"><span class="cl"><span class="c1"># [dependencies]</span>
</span></span><span class="line"><span class="cl"><span class="c1"># R = &#34;&gt;= 4.4&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># cli = &#34;*&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># dplyr = &#34;&gt;= 1.1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1"># ///</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">dplyr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">cli</span><span class="o">::</span><span class="nf">cli_text</span><span class="p">(</span><span class="s">&#34;Hello from {.pkg cli}!&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Without the leading <code>#</code> characters, the block is the same TOML as in
<code>rproj.toml</code>.</p>
<p><code>rig run script.R</code> installs the right R version if needed, installs the
packages into a separate environment in rig&rsquo;s cache, and then runs the
script. Your own package libraries do not change. The next run reuses the
environment, so it starts right away. You can share a single <code>.R</code> file, and
it runs the same way on every machine that has rig.</p>
<p>You can write the block by hand, or let rig manage it:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig proj init --script script.R
</span></span><span class="line"><span class="cl">❯ rig proj add --script script.R cli dplyr
</span></span><span class="line"><span class="cl">❯ rig proj remove --script script.R dplyr</span></span></code></pre></div></div>
<p>To pin the exact R and package versions, lock the script:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig proj lock --script script.R</span></span></code></pre></div></div>
<p>This writes <code>script.R.lock</code> next to the script. Keep the two files together,
and <code>rig run</code> will use the versions from the lock file.</p>
<p>On macOS and Linux, start the script with <code>#!/usr/bin/env -S rig run</code> and
make it executable, to run it like any other command. On Windows,
<code>rig system script-assoc</code> lets you run <code>.R</code> files from <code>cmd</code> and PowerShell.
See the <a href="https://rig.r-lib.org/scripts.html" target="_blank" rel="noopener">scripts guide</a> for more.</p>
<h2 id="caches">Caches
</h2>
<p>rig caches the files it downloads: repository metadata, package files,
packages it built from source, git repositories of git and GitHub
dependencies, and the environments of inline scripts. <code>rig pkg</code>,
<code>rig proj</code> and <code>rig run</code> share the same cache, and so do all projects, so
rig downloads or builds a package only once. rig refreshes the repository
metadata once a day by default.</p>
<p><code>rig cache info</code> shows how much space the cache takes, by category, and
<code>rig cache clean</code> deletes it, or only one category of it, with
<code>--category</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig cache info
</span></span><span class="line"><span class="cl">❯ rig cache clean --category scripts</span></span></code></pre></div></div>
<p>Use <code>--no-cache</code> to skip the cache for a single command, and
<code>rig system dirs --cache</code> to see where the cache is.</p>
<h2 id="your-feedback-is-welcome">Your feedback is welcome!
</h2>
<p>We would love to hear your thoughts, suggestions, and any issues you
encounter while using rig. Please use the <a href="https://github.com/r-lib/rig/issues" target="_blank" rel="noopener">issue tracker</a> for bug reports and feature
requests, and the <a href="https://github.com/r-lib/rig/discussions" target="_blank" rel="noopener">discussion forum</a> or <a href="https://forum.posit.co/" target="_blank" rel="noopener">Posit community</a> for general questions and discussions.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-10-15_rig-0-11-0-part-three/featured.jpg" length="424877" type="image/jpeg" />
    </item>
    <item>
      <title>Positron October Release Highlights</title>
      <link>https://opensource.posit.co/blog/2026-10-13_positron-2026-10-release/</link>
      <pubDate>Tue, 13 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-10-13_positron-2026-10-release/</guid>
      <dc:creator>Julia Silge</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co/?utm_source=opensource-posit-co&amp;utm_medium=referral&amp;utm_campaign=positron-2026-10-release-blog&amp;utm_content=intro-note" target="_blank" rel="noopener">Positron</a> is Posit&rsquo;s new, next-generation IDE for data science. Positron is designed to be an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>Welcome back to another edition of our monthly Positron updates! Each month we share highlights from our <a href="https://positron.posit.co/release-notes?utm_source=opensource-posit-co&amp;utm_medium=referral&amp;utm_campaign=positron-2026-10-release-blog&amp;utm_content=release-notes-link" target="_blank" rel="noopener">latest release</a> and useful resources. <a href="https://opensource.posit.co/blog/2026-09-09_positron-2026-09-release">Last release</a> we told you about a redesigned welcome page, a first version of Import Data, new Posit Assistant features, expanded Data Connections, package vulnerability scanning, and a more responsive Console. This milestone brings a new ability for Posit Assistant to drive Positron itself, a built-in MCP server so you can bring your own coding agent, Data Connections on by default, faster language features for Quarto, and smoother interpreter and project setup.</p>
<h2 id="posit-assistant-can-drive-positron">Posit Assistant can drive Positron
</h2>
<p><a href="https://assistant.posit.co/?utm_source=opensource-posit-co&amp;utm_medium=referral&amp;utm_campaign=positron-2026-10-release-blog&amp;utm_content=assistant-section" target="_blank" rel="noopener">Posit Assistant</a> can now operate Positron itself, not just the R or Python session inside it. You can ask it to run a Shiny app, open a Parquet file in the Data Explorer, set up a Python environment, browse your data connections, or deploy to Posit Connect. Posit Assistant uses the same source of truth as the Positron UI, so it works with your real interpreters, packages, and connections rather than guessing at them.</p>
<img src="https://opensource.posit.co/blog/2026-10-13_positron-2026-10-release/assistant-data-explorer.png" data-fig-align="center" data-fig-alt="Positron with the Posit Assistant chat beside the Data Explorer, which shows pokemon.xlsx that Posit Assistant opened." />
<p>In practice, this means Posit Assistant can run and preview Dash, FastAPI, Flask, Gradio, marimo, Streamlit, and Shiny apps with the same app commands you use, see which apps are running along with their status and URL, and stop them. It can show HTML files and URLs in the Viewer pane, and it can read, take screenshots of, and interact with data apps and other content there, which makes it much more useful for checking its own work on an app. It can read your configured and detected Data Connections, browse a connection&rsquo;s schema, and give you the code to open it. It can also tell which interpreter a project uses, explain why an installed interpreter isn&rsquo;t showing up, and make that interpreter available, and it knows the commands for deploying to Connect through Posit Publisher.</p>
<p>To try this preview feature, enable the <a href="positron://settings/assistant.previewFeatures"><code>assistant.previewFeatures</code></a> setting, and tell us what you think in the <a href="https://github.com/posit-dev/positron/discussions/16280" target="_blank" rel="noopener">GitHub discussion</a>.</p>
<h2 id="bring-your-own-coding-agent">Bring your own coding agent
</h2>
<p>If you already have a favorite coding agent other than Posit Assistant, this release lets it work alongside you in Positron. A new built-in Model Context Protocol (MCP) server lets coding agents such as Claude Code, Codex, and Gemini CLI work in your live Python and R sessions. Our goal is to give these agents a set of tools comparable to what Posit Assistant has: agents can list your sessions, run code in the Console, inspect objects without cluttering your history, read recent session history, interrupt a long-running computation, look at the current plot, and search for and run Positron commands. These other agents may not have the data science expertise that Posit Assistant has, but they can still help you write code and explore data.</p>
<p>You stay in the loop on what an agent does. Code that an agent runs shows up in the Console with the agent&rsquo;s name next to it, so you can always tell agent code from your own. Console tabs also now show a dot when code runs in a console you aren&rsquo;t looking at, whether that code came from Posit Assistant or an external agent.</p>
<img src="https://opensource.posit.co/blog/2026-10-13_positron-2026-10-release/claude-code-in-positron.png" data-fig-align="center" data-fig-alt="Claude Code in Positron&#39;s editor area, asked whether to visualize country-to-country twinning relationships as a heatmap or a network graph. It calls the Positron execute_code and get_plot tools, then recommends the heatmap and explains it. In the Console, the R code it ran is labeled Claude Code, and the Plots pane shows the resulting heatmap of twinning links between the top countries." />
<p>To try this experimental feature, enable the <a href="positron://settings/ai.mcp.enabled"><code>ai.mcp.enabled</code></a> setting. Positron configures Claude Code for you automatically, and the <em>Positron MCP: Add to Coding Agent</em> command sets up the Codex and Gemini CLIs. For any other MCP client, <em>Copy Connection Details</em> gives you a URL and token to connect with.</p>
<h2 id="data-connections-on-by-default">Data Connections on by default
</h2>
<p><a href="https://positron.posit.co/data-connections?utm_source=opensource-posit-co&amp;utm_medium=referral&amp;utm_campaign=positron-2026-10-release-blog&amp;utm_content=data-connections-section" target="_blank" rel="noopener">Data Connections</a> is now the default way to work with databases and data warehouses in Positron. Data Connections connects to databases directly, without needing a Python or R session, and it replaces both the older Connections pane and the Catalog Explorer. If you need the older Connections pane, set <a href="positron://settings/dataConnections.enabled"><code>dataConnections.enabled</code></a> to <code>false</code> and reload the window.</p>
<p>This release also extends what you can connect to and how you sign in. Amazon Redshift connections can sign in with AWS Identity and Access Management (IAM), for both Redshift Serverless and provisioned clusters, using credentials from the standard AWS credential provider chain. On Posit Workbench, Snowflake and Databricks connections can use the session&rsquo;s managed credentials with no credential input at all. Snowflake users get the most new features this month; the Data Connections pane shows Snowflake semantic views with their dimensions, facts, metrics, and relationships, you can browse Snowflake stages as folders and files, and connections in <code>connections.toml</code> show up automatically as detected connections.</p>
<p>Smaller improvements round out the experience. When you open a DuckDB or SQLite file from the Explorer, Positron offers to create a data connection for it. <strong>Connect With</strong> can now generate ggsql code for SQLite, DuckDB, ODBC, PostgreSQL, and Redshift connections.</p>
<img src="https://opensource.posit.co/blog/2026-10-13_positron-2026-10-release/connections-ggsql-bikeshare.gif" data-fig-align="center" data-fig-alt="The Data Connections pane in Positron showing a Bikeshare demo SQLite connection. Choosing ggsql under Connect With opens a dialog with the generated connection code. After clicking Connect, the code runs in a ggsql console, followed by a SQL query that counts bikes by bike type and returns a results table." />
<p>The <strong>Add Data Connection</strong> dialog lists each driver with a short description, and Positron now tells you when a connection fails to open or expand. For slow tables and views, the Data Explorer now shows a progress indicator and placeholders while data arrives, and it loads column summaries in two passes so that missing-value counts appear quickly and sparklines follow later.</p>
<h2 id="faster-language-features-for-quarto">Faster language features for Quarto
</h2>
<p>Positron now provides completions, hover, diagnostics, outline, formatting, and other language features natively for R and Python cells in Quarto and R Markdown documents. Before, the Quarto extension wrote temporary virtual documents to disk to power these features. Positron now uses a virtual notebook in memory instead, which is faster (especially in remote sessions) and solves a whole category of bugs, such as Python imports that did not resolve correctly. It also means Go to Definition, Find References, and Rename work across R cells, finding names that earlier cells define.</p>
<p>The document outline for Quarto and R Markdown documents with many code chunks now updates quickly, and it no longer shows duplicate groups or symbols. Inline output, including HTML tables and the inline Data Explorer, now scales with your editor font size. If you run into problems with the new language features and need to go back to the old behavior, set <a href="positron://settings/quarto.embeddedLanguageFeatures.native"><code>quarto.embeddedLanguageFeatures.native</code></a> to <code>false</code>.</p>
<h2 id="interpreter-project-setup">Interpreter project setup
</h2>
<p>Positron is better at setting up the right Python environment for your project. When a workspace has a <code>uv.lock</code> or <code>pixi.lock</code> file but no environment yet, Positron offers to create it with <code>uv sync</code> or <code>pixi install</code>, which is especially handy right after you clone a project. If uv is missing, the New Folder flow can install it for you, and package installs and the Packages pane work with uv right away. When Positron finds a new Python environment in a workspace, it now offers to start a console session in it, and system Python installations now show last in the interpreter picker, below uv, venv, conda, and pyenv environments.</p>
<img src="https://opensource.posit.co/blog/2026-10-13_positron-2026-10-release/uv-lock-create-env.gif" data-fig-align="center" data-fig-alt="A Shiny dashboard Quarto document open in Positron. A notification says the project has a uv.lock file but no virtual environment and offers to run uv sync. After clicking Yes, Positron creates the environment and then offers to start a console session with it." />
<p>For more control over interpreters in <em>both</em> Python and R, interpreter path settings now support <code>${workspaceFolder}</code>, so a project can point to an interpreter that lives inside it. The new <a href="positron://settings/interpreters.definitions"><code>interpreters.definitions</code></a> setting lets you define additional R and Python interpreters, each with its own label, environment variables, and startup script. This is useful for workflows like loading environment modules or configuring rJava before a session starts. Set <a href="positron://settings/interpreters.discovery"><code>interpreters.discovery</code></a> to <code>definitionsOnly</code> to show only the interpreters you have defined for a language.</p>
<h2 id="whats-coming-next">What&rsquo;s coming next
</h2>
<ul>
<li>Meet Posit at <a href="https://posit.co/events/snowflake-world-tour-2026-nyc?utm_source=opensource-posit-co&amp;utm_medium=referral&amp;utm_campaign=positron-2026-10-release-blog&amp;utm_term=snowflake&amp;utm_content=events-section" target="_blank" rel="noopener">Snowflake World Tour NYC</a> on October 15. Stop by our booth, schedule time with a Posit expert, or catch our theater session on AI-powered data science where your data lives, inside Snowflake.</li>
<li>Join us at <a href="https://opensource.posit.co/events/data-outpost-2026/" target="_blank" rel="noopener">Data Outpost</a> on November 4-5 in San Francisco. I&rsquo;ll be speaking on &ldquo;The SQL Agent Harness We Didn&rsquo;t Mean to Build,&rdquo; about how the SQL tools we built in Positron for people turned out to already be a harness for AI assistants.</li>
</ul>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Tip</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co/download?utm_source=opensource-posit-co&amp;utm_medium=referral&amp;utm_campaign=positron-2026-10-release-blog&amp;utm_content=download-cta" target="_blank" rel="noopener">Download Positron</a> to try out the new features and improvements in this release!</p>
</div>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-10-13_positron-2026-10-release/featured.svg" length="91754" type="image/svg&#43;xml" />
    </item>
    <item>
      <title>rig 0.11.0 part two: R packages</title>
      <link>https://opensource.posit.co/blog/2026-10-12_rig-0-11-0-part-two/</link>
      <pubDate>Mon, 12 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-10-12_rig-0-11-0-part-two/</guid>
      <dc:creator>Gábor Csárdi</dc:creator><description><![CDATA[<!--
TODO:
- [x] Add image (1920×1080 PNG or JPG) and image-alt
- [x] Trim topics, software, and languages to only what applies
- [x] Open a PR against main for a Netlify preview
-->
<style>
.prose pre {
  font-size: 0.65em;
}
</style>
<p>We are thrilled to announce rig 0.11.0 (and 0.10.0), with lots of new
functionalities: user mode R installations, R package and project management
and much more. This is the second of three blog posts about the new rig
release. This post is about managing R packages.</p>
<p>The <a href="https://opensource.posit.co/blog/2026-10-09_rig-0-11-0-part-one/">first post</a> covered user mode R installations. The <a href="https://opensource.posit.co/blog/2026-10-15_rig-0-11-0-part-three/">third post</a> focuses on
<a href="https://rig.r-lib.org/proj-guide.html" target="_blank" rel="noopener">project management</a>.</p>
<p><a href="https://rig.r-lib.org/news.html" target="_blank" rel="noopener">Full changelog on the rig web site</a>.</p>
<h2 id="introduction">Introduction
</h2>
<p>The new <code>rig pkg</code> command lets you manage R packages: installing,
removing, and querying package dependencies. It supports CRAN-like
repositories, packages in git repositories (including on GitHub), URLs
pointing to package files and packages in local files and directories.</p>
<p><code>rig pkg</code> is aimed at R users and developers who use a single (or a few)
package libraries. If you prefer to use project-specific libraries, you
probably want to use <code>rig proj</code> instead.</p>
<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><code>rig pkg</code> is new and less battle-tested than other parts of rig.
Please report any issues you find on the <a href="https://github.com/r-lib/rig/issues" target="_blank" rel="noopener">rig GitHub repository</a>.</p>
</div>
</div>
<h2 id="managing-installed-packages">Managing installed packages
</h2>
<p><code>rig pkg list</code> lists installed packages in a library. <code>rig pkg remove</code>
removes a package from a library.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg list
</span></span><span class="line"><span class="cl">559 packages (R 4.6.1, main: /Users/gaborcsardi/Library/R/arm64/4.6/library)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Package              Version      Built   Platform                   Source
</span></span><span class="line"><span class="cl">----------------------------------------------------------------------------------------------------------------
</span></span><span class="line"><span class="cl">abind                1.4-8        4.6.0   -                          RSPM
</span></span><span class="line"><span class="cl">ade4                 1.7-24       4.6.0   aarch64-apple-darwin23     CRAN
</span></span><span class="line"><span class="cl">adegenet             2.1.11       4.6.0   aarch64-apple-darwin23     CRAN
</span></span><span class="line"><span class="cl">AdmixPoly            1.0.1        4.6.0   aarch64-apple-darwin20     RSPM
</span></span><span class="line"><span class="cl">affy                 1.90.0       4.6.1   aarch64-apple-darwin23     RSPM
</span></span><span class="line"><span class="cl">affyio               1.82.0       4.6.0   aarch64-apple-darwin20     RSPM
</span></span><span class="line"><span class="cl">[...]</span></span></code></pre></div></div>
<p><code>rig pkg</code> commands use the current default R version (as in <code>rig default</code>),
unless the <code>-r</code> or <code>--r-version</code> option specifies otherwise. They use the
default library (as in <code>rig library default</code>), unless the <code>-l</code> or
<code>--library</code> option specifies otherwise.</p>
<p><code>rig pkg doctor</code> checks the health of your package library, identifying any
issues with installed packages or their dependencies: missing dependencies,
dependency conflicts, ABI conflicts, etc. It also tells you how to fix these
issues.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg doctor
</span></span><span class="line"><span class="cl">3 problems (1 error, 2 warnings) in 559 packages (R 4.6.1, main: /Users/gaborcsardi/Library/R/arm64/4.6/library)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Package              Version   Severity   Problem   Details
</span></span><span class="line"><span class="cl">-------------------------------------------------------------------------------------------------------------
</span></span><span class="line"><span class="cl">00LOCK-dress.graph   -         warning    lock      lock directory left behind by an interrupted installation
</span></span><span class="line"><span class="cl">dress.graph          -         warning    broken    no DESCRIPTION file
</span></span><span class="line"><span class="cl">missoNet             1.5.1     error      missing   Imports ComplexHeatmap, not installed
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">To fix the errors, run:
</span></span><span class="line"><span class="cl">  rig pkg install ComplexHeatmap
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">To remove the leftover lock directories, if no installation is running, run:
</span></span><span class="line"><span class="cl">  rm -rf /Users/gaborcsardi/Library/R/arm64/4.6/library/00LOCK-dress.graph
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">To remove the broken packages, run:
</span></span><span class="line"><span class="cl">  rm -rf /Users/gaborcsardi/Library/R/arm64/4.6/library/dress.graph
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">✗ Found 1 error in /Users/gaborcsardi/Library/R/arm64/4.6/library</span></span></code></pre></div></div>
<h2 id="installing-packages">Installing packages
</h2>
<p><code>rig pkg install</code> can install R packages from CRAN-like repositories,
git repositories, GitHub, URLs, and local paths. It uses the default R
version and the default package library (see below about libraries),
unless otherwise specified. It caches metadata and package files heavily,
avoids redundant downloads and installations, and performs downloads and
package builds in parallel. It uses the host OS&rsquo;s file cloning capabilities
to efficiently copy files when installing packages.</p>
<p>If you used the pak R package to install R packages previously, <code>rig pkg</code>
will probably feel familiar, except that you can run it from the command
line instead of from within R.</p>
<p>This is what installing a package into an empty library looks like, if the
package files are already cached:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg install dplyr
</span></span><span class="line"><span class="cl">▶ Downloading binary package metadata
</span></span><span class="line"><span class="cl">▶ Solving dependencies
</span></span><span class="line"><span class="cl">✓ Solved dependencies
</span></span><span class="line"><span class="cl">15 of 15 packages to install (R 4.6.1, test: /Users/gaborcsardi/Library/R/arm64/4.6/library/__test)
</span></span><span class="line"><span class="cl">Package     Version  Type    Action   Reason
</span></span><span class="line"><span class="cl">cli         3.6.6    binary  install  not installed
</span></span><span class="line"><span class="cl">dplyr       1.2.1    binary  install  not installed
</span></span><span class="line"><span class="cl">generics    0.1.4    binary  install  not installed
</span></span><span class="line"><span class="cl">glue        1.8.1    binary  install  not installed
</span></span><span class="line"><span class="cl">lifecycle   1.0.5    binary  install  not installed
</span></span><span class="line"><span class="cl">magrittr    2.0.5    binary  install  not installed
</span></span><span class="line"><span class="cl">pillar      1.11.1   binary  install  not installed
</span></span><span class="line"><span class="cl">pkgconfig   2.0.3    binary  install  not installed
</span></span><span class="line"><span class="cl">R6          2.6.1    binary  install  not installed
</span></span><span class="line"><span class="cl">rlang       1.3.0    binary  install  not installed
</span></span><span class="line"><span class="cl">tibble      3.3.1    binary  install  not installed
</span></span><span class="line"><span class="cl">tidyselect  1.2.1    binary  install  not installed
</span></span><span class="line"><span class="cl">utf8        1.2.6    binary  install  not installed
</span></span><span class="line"><span class="cl">vctrs       0.7.3    binary  install  not installed
</span></span><span class="line"><span class="cl">withr       3.0.3    binary  install  not installed
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">▶ Downloading 15 packages
</span></span><span class="line"><span class="cl">✓ Cached: cli
</span></span><span class="line"><span class="cl">✓ Cached: dplyr
</span></span><span class="line"><span class="cl">✓ Cached: generics
</span></span><span class="line"><span class="cl">✓ Cached: glue
</span></span><span class="line"><span class="cl">✓ Cached: lifecycle
</span></span><span class="line"><span class="cl">✓ Cached: magrittr
</span></span><span class="line"><span class="cl">✓ Cached: pillar
</span></span><span class="line"><span class="cl">✓ Cached: pkgconfig
</span></span><span class="line"><span class="cl">✓ Cached: R6
</span></span><span class="line"><span class="cl">✓ Cached: rlang
</span></span><span class="line"><span class="cl">✓ Cached: tibble
</span></span><span class="line"><span class="cl">✓ Cached: tidyselect
</span></span><span class="line"><span class="cl">✓ Cached: utf8
</span></span><span class="line"><span class="cl">✓ Cached: vctrs
</span></span><span class="line"><span class="cl">✓ Cached: withr
</span></span><span class="line"><span class="cl">Complete: 0 downloaded, 15 cached [========================================] 15/15 packages
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">▶ Installing 15 packages.
</span></span><span class="line"><span class="cl">Installed R6 2.6.1
</span></span><span class="line"><span class="cl">Installed cli 3.6.6
</span></span><span class="line"><span class="cl">Installed rlang 1.3.0
</span></span><span class="line"><span class="cl">Installed magrittr 2.0.5
</span></span><span class="line"><span class="cl">Installed pkgconfig 2.0.3
</span></span><span class="line"><span class="cl">Installed glue 1.8.1
</span></span><span class="line"><span class="cl">Installed withr 3.0.3
</span></span><span class="line"><span class="cl">Installed utf8 1.2.6
</span></span><span class="line"><span class="cl">Installed generics 0.1.4
</span></span><span class="line"><span class="cl">Installed lifecycle 1.0.5
</span></span><span class="line"><span class="cl">Installed vctrs 0.7.3
</span></span><span class="line"><span class="cl">Installed tidyselect 1.2.1
</span></span><span class="line"><span class="cl">Installed pillar 1.11.1
</span></span><span class="line"><span class="cl">Installed tibble 3.3.1
</span></span><span class="line"><span class="cl">Installed dplyr 1.2.1
</span></span><span class="line"><span class="cl">✓ Installed all 15 packages successfully
</span></span><span class="line"><span class="cl">✓ Installed 15 packages (R 4.6.1, test: /Users/gaborcsardi/Library/R/arm64/4.6/library/__test)</span></span></code></pre></div></div>
<h3 id="version-constraints">Version constraints
</h3>
<p><code>rig pkg install</code> supports version constraints for packages. Request a
specific version of a package with <code>@==&lt;version&gt;</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg install ggplot2@==4.0.2</span></span></code></pre></div></div>
<p>If you are familiar with <code>~</code> and <code>^</code> version constraints from other languages,
you can use them with <code>rig pkg install</code> as well. E.g. to install the
latest version of the ggplot2 3.x.y series, you would run:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg install ggplot2@^3.0.0</span></span></code></pre></div></div>
<h3 id="dependency-solver">Dependency solver
</h3>
<p>The rig dependency solver makes sure that all package dependencies are
resolved correctly before installation. If the combination of packages
cannot be installed on the specified R version or platform, rig will report
an error and refuse to proceed with the installation. For example:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg install -r 4.0.5-x86_64 &#39;testthat@&gt;=3.3.0&#39;
</span></span><span class="line"><span class="cl">▶ Downloading binary package metadata
</span></span><span class="line"><span class="cl">▶ Solving dependencies
</span></span><span class="line"><span class="cl">✗ Cannot resolve dependencies for R 4.0.5 / x86_64-apple-darwin:
</span></span><span class="line"><span class="cl">  Because testthat &gt;=3.3.0 depends on R &gt;=4.1.0 and you requested
</span></span><span class="line"><span class="cl">  testthat &gt;=3.3.0, the requested packages cannot be installed.</span></span></code></pre></div></div>
<h3 id="packages-from-git-github-urls-paths">Packages from git, GitHub, URLs, paths
</h3>
<p>If you are familiar with pak&rsquo;s syntax for installing packages from
various non-CRAN sources, you can use the same syntax with rig. E.g. a
GitHub package is simply <code>username/repo</code>. You can use the usual syntax
for tags, branches, pull requests:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg install r-lib/cli
</span></span><span class="line"><span class="cl">❯ rig pkg install r-lib/cli@badcafe
</span></span><span class="line"><span class="cl">❯ rig pkg install r-lib/cli@some-branch
</span></span><span class="line"><span class="cl">❯ rig pkg install r-lib/cli#41</span></span></code></pre></div></div>
<p>Prefix packages in git repositories with <code>git::</code>. To install a package
from a URL simply use the full URL. For a local path use a relative path
starting with <code>./</code>, <code>../</code> or <code>~/</code> or an absolute path:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg install .
</span></span><span class="line"><span class="cl">❯ rig pkg install ./mypkg
</span></span><span class="line"><span class="cl">❯ rig pkg install ../mypkg
</span></span><span class="line"><span class="cl">❯ rig pkg install ~/works/mypkg
</span></span><span class="line"><span class="cl">❯ rig pkg install /opt/src/mypkg
</span></span><span class="line"><span class="cl">❯ rig pkg install ./mypkg_1.0.0.tar.gz</span></span></code></pre></div></div>
<h3 id="preferring-binary-packages">Preferring binary packages
</h3>
<p>rig prefers to install the binary package for a certain package version if
it is available, instead of building and potentially compiling it from
source. By default it does not choose an older version over a newer one
just because a binary is available for the older version. The
<code>--prefer-binary</code> option changes this. Use it if you prefer older binary
packages over newer source packages:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg install --prefer-binary tidyverse</span></span></code></pre></div></div>
<h3 id="installing-dependencies">Installing dependencies
</h3>
<p>To install the dependencies of a package, without installing the package
itself, use <code>--no-install-project</code>. This is especially useful to install
all dependencies of a package tree in the current working directory. The
second variant also installs soft (development) dependencies:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg install --no-install-project .
</span></span><span class="line"><span class="cl">❯ rig pkg install --no-install-project --dev .</span></span></code></pre></div></div>
<h2 id="repositories">Repositories
</h2>
<p><code>rig pkg</code> subcommands always use the configured repositories, see the
<code>rig repos</code> command. They also have <code>--with-repos</code> and <code>--without-repos</code>
options to include or exclude specific repositories when performing a single
package operation, without modifying the configured repositories. E.g. to
query the version of a package on CRAN and P3M you can run:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg info otel --without-repos --with-repos=p3m --versions
</span></span><span class="line"><span class="cl">otel — 2 versions (latest 0.2.0)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Version   Published    R          Deps
</span></span><span class="line"><span class="cl">-------------------------------------------------------
</span></span><span class="line"><span class="cl">0.1.0     2025-07-31   &gt;= 3.6.0      0
</span></span><span class="line"><span class="cl">0.2.0     2025-08-29   &gt;= 3.6.0      0   ← latest</span></span></code></pre></div></div>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg info otel --without-repos --with-repos=cran --versions
</span></span><span class="line"><span class="cl">otel — 1 version (latest 0.2.0)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Version   Published   R          Deps
</span></span><span class="line"><span class="cl">-------------------------------------------------------
</span></span><span class="line"><span class="cl">0.2.0     ?           &gt;= 3.6.0      0   ← latest</span></span></code></pre></div></div>
<h2 id="package-queries">Package queries
</h2>
<p><code>rig pkg info</code> prints information about a package, by default about the
latest version:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg info tidymodels
</span></span><span class="line"><span class="cl">tidymodels 1.5.0 (RSPM)
</span></span><span class="line"><span class="cl">Easily Install and Load the &#39;Tidymodels&#39; Packages
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">The tidy modeling &#34;verse&#34; is a collection of packages for modeling and
</span></span><span class="line"><span class="cl">statistical analysis that share the underlying design philosophy, grammar, and
</span></span><span class="line"><span class="cl">data structures of the tidyverse.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Maintainer    Max Kuhn &lt;max@posit.co&gt;
</span></span><span class="line"><span class="cl">License       MIT + file LICENSE
</span></span><span class="line"><span class="cl">Published     2026-04-23 06:20:02 UTC
</span></span><span class="line"><span class="cl">URL           https://tidymodels.tidymodels.org,
</span></span><span class="line"><span class="cl">              https://github.com/tidymodels/tidymodels
</span></span><span class="line"><span class="cl">BugReports    https://github.com/tidymodels/tidymodels/issues
</span></span><span class="line"><span class="cl">Compilation   no
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Depends       R (&gt;= 4.1)
</span></span><span class="line"><span class="cl">Imports       broom (&gt;= 1.0.12), cli (&gt;= 3.6.6), conflicted (&gt;= 1.2.0), dials
</span></span><span class="line"><span class="cl">              (&gt;= 1.4.3), dplyr (&gt;= 1.2.1), ggplot2 (&gt;= 4.0.3), hardhat (&gt;=
</span></span><span class="line"><span class="cl">              1.4.3), infer (&gt;= 1.1.0), modeldata (&gt;= 1.5.1), parsnip (&gt;=
</span></span><span class="line"><span class="cl">              1.5.0), purrr (&gt;= 1.2.2), recipes (&gt;= 1.3.2), rlang (&gt;= 1.2.0),
</span></span><span class="line"><span class="cl">              rsample (&gt;= 1.3.2), rstudioapi (&gt;= 0.18.0), tailor (&gt;= 0.1.0),
</span></span><span class="line"><span class="cl">              tidyr (&gt;= 1.3.2), tune (&gt;= 2.1.0), workflows (&gt;= 1.3.0),
</span></span><span class="line"><span class="cl">              workflowsets (&gt;= 1.1.1), yardstick (&gt;= 1.4.0)
</span></span><span class="line"><span class="cl">Suggests      covr, glue, knitr, rmarkdown, testthat (&gt;= 3.0.0), xml2</span></span></code></pre></div></div>
<p><code>rig pkg info --versions</code> lists all versions of a package. This only works
with P3M repositories because it needs extended metadata that plain
CRAN-like repositories typically do not have:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg info tidymodels --versions
</span></span><span class="line"><span class="cl">tidymodels — 17 versions (latest 1.5.0)
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Version   Published    R        Deps
</span></span><span class="line"><span class="cl">-------------------------------------------------------
</span></span><span class="line"><span class="cl">0.0.1     2018-07-27   &gt;= 3.1     18
</span></span><span class="line"><span class="cl">0.0.2     2018-11-27   &gt;= 3.1     20
</span></span><span class="line"><span class="cl">0.0.3     2019-10-04   &gt;= 3.1     20
</span></span><span class="line"><span class="cl">0.1.0     2020-02-16   &gt;= 3.1     22
</span></span><span class="line"><span class="cl">0.1.1     2020-07-14   &gt;= 3.1     20
</span></span><span class="line"><span class="cl">0.1.2     2020-11-22   &gt;= 3.1     19
</span></span><span class="line"><span class="cl">0.1.3     2021-04-19   &gt;= 3.1     20
</span></span><span class="line"><span class="cl">0.1.4     2021-10-01   &gt;= 3.1     21
</span></span><span class="line"><span class="cl">0.2.0     2022-03-19   &gt;= 3.4     21
</span></span><span class="line"><span class="cl">1.0.0     2022-07-13   &gt;= 3.4     21
</span></span><span class="line"><span class="cl">1.1.0     2023-05-01   &gt;= 3.4     21
</span></span><span class="line"><span class="cl">1.1.1     2023-08-24   &gt;= 3.4     21
</span></span><span class="line"><span class="cl">1.2.0     2024-03-25   &gt;= 3.6     21
</span></span><span class="line"><span class="cl">1.3.0     2025-02-21   &gt;= 3.6     21
</span></span><span class="line"><span class="cl">1.4.0     2025-09-05   &gt;= 4.1     21
</span></span><span class="line"><span class="cl">1.4.1     2025-09-08   &gt;= 4.1     21
</span></span><span class="line"><span class="cl">1.5.0     2026-04-23   &gt;= 4.1     21   ← latest</span></span></code></pre></div></div>
<p><code>rig pkg deps</code> prints the dependencies of a package. <code>rig pkg tree</code> does
the same, but as a dependency tree. You can use these commands to query
non-CRAN-like packages. E.g. the dependency tree of the package in the
current working directory is</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg tree --dev .
</span></span><span class="line"><span class="cl">otelsdk 0.2.4.9000 (local: /Users/gaborcsardi/works/otelsdk) — 14 direct, 51 total
</span></span><span class="line"><span class="cl">├── otel 0.2.0
</span></span><span class="line"><span class="cl">│   └── R (&gt;= 3.6.0) [D]
</span></span><span class="line"><span class="cl">└── utils
</span></span><span class="line"><span class="cl">[Suggests]
</span></span><span class="line"><span class="cl">├── callr 3.8.0
</span></span><span class="line"><span class="cl">│   ├── R (&gt;= 3.4) [D]
</span></span><span class="line"><span class="cl">│   ├── otel 0.2.0 (&gt;= 0.2.0) (*)
</span></span><span class="line"><span class="cl">│   ├── processx 3.9.0 (&gt;= 3.6.1)
</span></span><span class="line"><span class="cl">│   │   ├── R (&gt;= 3.4.0) [D]
</span></span><span class="line"><span class="cl">│   │   ├── ps 1.9.3 (&gt;= 1.9.3)
</span></span><span class="line"><span class="cl">│   │   │   ├── R (&gt;= 3.4) [D]
</span></span><span class="line"><span class="cl">│   │   │   └── utils
</span></span><span class="line"><span class="cl">│   │   ├── R6 2.6.1
</span></span><span class="line"><span class="cl">│   │   │   └── R (&gt;= 3.6) [D]
</span></span><span class="line"><span class="cl">│   │   └── utils
</span></span><span class="line"><span class="cl">│   ├── R6 2.6.1 (*)
</span></span><span class="line"><span class="cl">│   └── utils
</span></span><span class="line"><span class="cl">├── cli 3.6.6
</span></span><span class="line"><span class="cl">│   ├── R (&gt;= 3.4) [D]
</span></span><span class="line"><span class="cl">│   └── utils
</span></span><span class="line"><span class="cl">├── curl 8.0.0
</span></span><span class="line"><span class="cl">│   └── R (&gt;= 3.0.0) [D]
</span></span><span class="line"><span class="cl">[...]</span></span></code></pre></div></div>
<p>The <code>--dev</code> flag includes development dependencies as well.</p>
<p><code>rig pkg search</code> searches all CRAN packages using R-hub package search
service:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig pkg search palette --short
</span></span><span class="line"><span class="cl">- &#34;palette&#34; ----------------------------------- 177 packages in 0.009 seconds -
</span></span><span class="line"><span class="cl"> #     package      version  by                     @ title
</span></span><span class="line"><span class="cl"> 1 100 RColorBrewer 1.1-3    Erich Neuwirth        5y ColorBrewer Palettes
</span></span><span class="line"><span class="cl"> 2  46 colorspace   2.1-3    Achim Zeileis         3M A Toolbox for Manipula...
</span></span><span class="line"><span class="cl"> 3  23 paletteer    1.7.0    Emil Hvitfeldt        9M Comprehensive Collecti...
</span></span><span class="line"><span class="cl"> 4  16 ggsci        5.2.0    Nan Xiao              2M Scientific Journal and...
</span></span><span class="line"><span class="cl"> 5  13 wesanderson  0.3.7    Karthik Ram           3y A Wes Anderson Palette...
</span></span><span class="line"><span class="cl"> 6   8 rcartocolor  2.1.2    Jakub Nowosad         1y &#39;CARTOColors&#39; Palettes
</span></span><span class="line"><span class="cl"> 7   8 pals         1.10     Kevin Wright          2y Color Palettes, Colorm...
</span></span><span class="line"><span class="cl"> 8   8 scico        1.5.0    Thomas Lin Pedersen   3y Colour Palettes Based...
</span></span><span class="line"><span class="cl"> 9   6 palette      0.0.3    Christopher T. Kenny  1y Color Scheme Helpers
</span></span><span class="line"><span class="cl">10   6 MetBrewer    0.2.0    Blake Robert Mills    5y Color Palettes Inspire...
</span></span><span class="line"><span class="cl">11   6 palr         0.5.0    Michael D. Sumner     2M Colour Palettes for Data
</span></span><span class="line"><span class="cl">12   6 blends       0.1.2    David Hodge           3M Blend Colours and Pale...
</span></span><span class="line"><span class="cl">13   6 biscale      1.1.0    Christopher Prener    1y Tools and Palettes for...
</span></span><span class="line"><span class="cl">14   5 palettes     0.2.2    Michael McCarthy      1y Methods for Colour Vec...
</span></span><span class="line"><span class="cl">15   5 PrettyCols   1.1.0    Nicola Rennie         2y Pretty Colour Palettes
</span></span><span class="line"><span class="cl">16   5 gifski       1.32.0-2 Jeroen Ooms           2y Highest Quality GIF En...
</span></span><span class="line"><span class="cl">17   5 wacolors     0.3.1    Cory McCartan         5y Colorblind-Friendly Pa...
</span></span><span class="line"><span class="cl">18   4 colourpicker 1.3.0    Dean Attali           3y A Colour Picker Tool f...
</span></span><span class="line"><span class="cl">19   4 amerika      0.1.1    Philip Waggoner       1y American Politics-Insp...
</span></span><span class="line"><span class="cl">20   4 makePalette  0.1.2    Jorge L. C. Musaja    3y Make Palette
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Next 20 results: rig pkg search palette --from 21 --short</span></span></code></pre></div></div>
<h2 id="package-libraries">Package libraries
</h2>
<p>rig creates a user-level package library for each R installation by default.
(See the <a href="https://rig.r-lib.org/faq.html" target="_blank" rel="noopener">rig FAQ</a> why.) This is the package
library that <code>rig pkg</code> commands use by default.</p>
<p>The <code>rig library</code> command supports creating other package libraries, still
stored in the user&rsquo;s home directory. You can refer to them by name and make
them the default library for <code>rig pkg</code> commands with <code>rig library default</code>.</p>
<p>For example to create a temporary library and install the Shiny package from
GitHub into it, including development dependencies, you can run:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">rig library add <span class="nb">test</span>
</span></span><span class="line"><span class="cl">rig library default <span class="nb">test</span>
</span></span><span class="line"><span class="cl">rig pkg install --dev rstudio/shiny</span></span></code></pre></div></div>
<p>You can also use the <code>-l</code> or <code>--library</code> option of the <code>rig pkg</code> commands to
change the package library temporarily for one command, without changing the
default.</p>
<p>If you are using the <a href="https://rig.r-lib.org/macos-app.html" target="_blank" rel="noopener">macOS menu bar app</a>, then you can also manage package
libraries through the app&rsquo;s interface.</p>
<h2 id="system-requirements">System requirements
</h2>
<p>On certain Linux systems rig supports automatic system requirements
installation. These are currently:</p>
<ul>
<li>Ubuntu Linux</li>
<li>RedHat Enterprise Linux and clones</li>
<li>openSUSE Linux and SUSE Linux Enterprise</li>
<li>Debian Linux</li>
<li>Fedora Linux</li>
<li>Alpine Linux</li>
</ul>
<p>For the automatic system requirements installation to work, rig must run
as the <code>root</code> user or with password-less <code>sudo</code> privileges to call the
system package manager.</p>
<p>Note that in user mode, rig installs the self-contained manylinux R build,
which does not require any system-level dependencies and also sets up
Posit&rsquo;s manylinux package repository, which has self-contained binaries for
most CRAN and Bioconductor packages. So in user mode you typically do not
need to worry about system-level dependencies.</p>
<h2 id="your-feedback-is-welcome">Your feedback is welcome!
</h2>
<p>We would love to hear your thoughts, suggestions, and any issues you
encounter while using rig. Please use the <a href="https://github.com/r-lib/rig/issues" target="_blank" rel="noopener">issue tracker</a> for bug reports and feature
requests, and the <a href="https://github.com/r-lib/rig/discussions" target="_blank" rel="noopener">discussion forum</a> or <a href="https://forum.posit.co/" target="_blank" rel="noopener">Posit community</a> for general questions and discussions.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-10-12_rig-0-11-0-part-two/featured.jpg" length="427378" type="image/jpeg" />
    </item>
    <item>
      <title>rig 0.11.0 part one: R installations</title>
      <link>https://opensource.posit.co/blog/2026-10-09_rig-0-11-0-part-one/</link>
      <pubDate>Fri, 09 Oct 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-10-09_rig-0-11-0-part-one/</guid>
      <dc:creator>Gábor Csárdi</dc:creator><description><![CDATA[<!--
TODO:
- [x] Add image (1920×1080 PNG or JPG) and image-alt
- [x] Trim topics, software, and languages to only what applies
- [x] Open a PR against main for a Netlify preview
-->
<style>
.prose pre {
  font-size: 0.65em;
}
</style>
<p>We are thrilled to announce rig 0.11.0 (and 0.10.0), with lots of new
functionalities: user mode R installations, R package and project management
and much more. This is the first of three blog posts about the new rig
release. This post is about managing R installations and the new user mode.
The <a href="https://opensource.posit.co/blog/2026-10-12_rig-0-11-0-part-two/">second post</a> shows how rig can help with <a href="https://rig.r-lib.org/pkg-guide.html" target="_blank" rel="noopener">managing R packages</a>, and the <a href="https://opensource.posit.co/blog/2026-10-15_rig-0-11-0-part-three/">third</a> focuses on
<a href="https://rig.r-lib.org/proj-guide.html" target="_blank" rel="noopener">project management</a>.</p>
<p><a href="https://rig.r-lib.org/news.html" target="_blank" rel="noopener">Full changelog on the rig web site</a>.</p>
<h2 id="new-web-site">New Web Site
</h2>
<p>rig has a new web site at <a href="https://rig.r-lib.org" target="_blank" rel="noopener">https://rig.r-lib.org</a>! See this web site for
installation instructions, including the new user mode install; reference
documentation (also in <code>rig help</code>!), guides about larger topics and
tutorials to get started.</p>
<figure>
<a href="https://rig.r-lib.org"><img src="https://opensource.posit.co/blog/2026-10-09_rig-0-11-0-part-one/rig-website.png" /></a>
<figcaption>The home page of the new rig web site, with a sidebar listing tutorials, guides and reference pages, and a quick start section.</figcaption>
</figure>
<h2 id="user-mode-r-installations">User Mode R Installations
</h2>
<p>rig can now install and manage R installations without
requiring administrative privileges. rig calls this <em>user mode</em>. In user
mode, R installations are installed in the user&rsquo;s home directory rather
than system-wide. System-wide installations are still the default currently,
and they are called <em>admin mode</em>.</p>
<p>You may have both user mode and admin mode installations on one computer,
but each rig command runs in either user mode or admin mode. The default is
admin mode. See below for how to change it. Use <code>--admin</code> or <code>--user</code> to
pick the mode for a single command, e.g. <code>rig ls --admin</code> always lists
admin mode installations, and <code>rig ls --user</code> always lists user mode ones.</p>
<p>Advantages of using user mode:</p>
<ul>
<li><strong>No admin</strong>: no need for administrative privileges.</li>
<li><strong>All Linux</strong>: works on all glibc (from glibc 2.34) and musl (from musl
1.2) based Linux systems.</li>
<li><strong>Multiple patch versions</strong>: on macOS you can install multiple patch
versions side by side.</li>
<li><strong>Convenience</strong>: no need to use <code>sudo</code> or an admin account to install and
manage R.</li>
</ul>
<p>User mode is quite new, though. It is much less tested than admin mode,
and you may encounter bugs or unexpected behavior more frequently. On Linux,
the generic distro-agnostic builds may be slower for numerical work than
builds specifically compiled for the distribution.</p>
<p>User mode (admin mode as well, actually) is slightly different on each
operating system. Here is a table about where rig stores its files on each
OS in both modes.</p>
<table>
  <thead>
      <tr>
          <th>Platform</th>
          <th>Mode</th>
          <th>R install root</th>
          <th>Binary directory (<code>R</code>, <code>Rscript</code>, <code>R-*</code>)</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>macOS</td>
          <td>admin</td>
          <td><code>/Library/Frameworks/R.framework</code></td>
          <td><code>/usr/local/bin</code></td>
      </tr>
      <tr>
          <td>macOS</td>
          <td>user</td>
          <td><code>~/.local/share/rig/r</code></td>
          <td><code>~/.local/bin</code></td>
      </tr>
      <tr>
          <td>Linux</td>
          <td>admin</td>
          <td><code>/opt/R</code></td>
          <td><code>/usr/local/bin</code></td>
      </tr>
      <tr>
          <td>Linux</td>
          <td>user</td>
          <td><code>~/.local/share/rig/r</code></td>
          <td><code>~/.local/bin</code></td>
      </tr>
      <tr>
          <td>Windows</td>
          <td>admin</td>
          <td><code>C:\Program Files\R</code></td>
          <td><code>C:\Program Files\R\bin</code></td>
      </tr>
      <tr>
          <td>Windows</td>
          <td>user</td>
          <td><code>%APPDATA%\rig\data\r</code></td>
          <td><code>%USERPROFILE%\.local\bin</code></td>
      </tr>
  </tbody>
</table>
<p>You can call the new <code>rig system dirs</code> command to see where rig stores its
various files:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig system dirs
</span></span><span class="line"><span class="cl">Mode                  user
</span></span><span class="line"><span class="cl">Architecture          arm64
</span></span><span class="line"><span class="cl">R root                /Users/gaborcsardi/.local/share/rig/r
</span></span><span class="line"><span class="cl">Binary dir            /Users/gaborcsardi/.local/bin
</span></span><span class="line"><span class="cl">Config file           /Users/gaborcsardi/Library/Application Support/com.gaborcsardi.rig/config.json
</span></span><span class="line"><span class="cl">Data dir              /Users/gaborcsardi/Library/Application Support/com.gaborcsardi.rig
</span></span><span class="line"><span class="cl">Cache dir             /Users/gaborcsardi/Library/Caches/com.gaborcsardi.rig
</span></span><span class="line"><span class="cl">Download dir          /var/folders/h9/6ct_py4s319fxt88dxn506l00000gp/T/rig-502
</span></span><span class="line"><span class="cl">Logs dir              /Users/gaborcsardi/Library/Logs/com.gaborcsardi.rig
</span></span><span class="line"><span class="cl">Project library root  (in-project, .rvenv/lib)</span></span></code></pre></div></div>
<p>The same in admin mode:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig system dirs --admin
</span></span><span class="line"><span class="cl">Mode                  admin
</span></span><span class="line"><span class="cl">Architecture          arm64
</span></span><span class="line"><span class="cl">R root                /Library/Frameworks/R.framework/Versions
</span></span><span class="line"><span class="cl">Binary dir            /usr/local/bin
</span></span><span class="line"><span class="cl">Config file           /Users/gaborcsardi/Library/Application Support/com.gaborcsardi.rig/config.json
</span></span><span class="line"><span class="cl">Data dir              /Users/gaborcsardi/Library/Application Support/com.gaborcsardi.rig
</span></span><span class="line"><span class="cl">Cache dir             /Users/gaborcsardi/Library/Caches/com.gaborcsardi.rig
</span></span><span class="line"><span class="cl">Download dir          /var/folders/h9/6ct_py4s319fxt88dxn506l00000gp/T/rig-502
</span></span><span class="line"><span class="cl">Logs dir              /Users/gaborcsardi/Library/Logs/com.gaborcsardi.rig
</span></span><span class="line"><span class="cl">Project library root  (in-project, .rvenv/lib)</span></span></code></pre></div></div>
<h3 id="macos">macOS
</h3>
<p>On arm64 macOS rig can install both arm64 and x86_64 builds of R. The latter
ones need Rosetta to run. User mode has a different, simpler naming scheme
than admin mode on macOS, matching Linux and Windows. This is an example
list of user mode R installations:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig ls
</span></span><span class="line"><span class="cl">* name          version    aliases
</span></span><span class="line"><span class="cl">------------------------------------------
</span></span><span class="line"><span class="cl">  3.6.3-x86_64  (R 3.6.3)
</span></span><span class="line"><span class="cl">  4.0.5-x86_64  (R 4.0.5)
</span></span><span class="line"><span class="cl">  4.1.3
</span></span><span class="line"><span class="cl">  4.1.3-x86_64  (R 4.1.3)
</span></span><span class="line"><span class="cl">  4.2.3
</span></span><span class="line"><span class="cl">  4.3.3
</span></span><span class="line"><span class="cl">  4.4.3
</span></span><span class="line"><span class="cl">  4.5.3                    oldrel
</span></span><span class="line"><span class="cl">  4.5.3-x86_64  (R 4.5.3)  oldrel-x86_64
</span></span><span class="line"><span class="cl">  4.6.0
</span></span><span class="line"><span class="cl">* 4.6.1                    release
</span></span><span class="line"><span class="cl">  4.6.1-x86_64  (R 4.6.1)  release-x86_64
</span></span><span class="line"><span class="cl">  devel         (R 4.7.0)
</span></span><span class="line"><span class="cl">  devel-x86_64  (R 4.7.0)
</span></span><span class="line"><span class="cl">  next          (R 4.6.1)
</span></span><span class="line"><span class="cl">  next-x86_64   (R 4.6.1)</span></span></code></pre></div></div>
<ul>
<li>The native (arm64) builds are named simply with the version number.</li>
<li>R-devel is simply named <code>devel</code> and the next version of R (R-patched,
R-alpha, etc.) is named <code>next</code>.</li>
<li>The x86_64 builds are suffixed with <code>-x86_64</code>.</li>
<li>You can use the <code>release</code> alias for the most recent version of R and
the <code>oldrel</code> alias for the previous minor release branch. E.g. the
<code>R-release</code> quick link starts the latest version of R.</li>
</ul>
<h3 id="windows">Windows
</h3>
<p>On Windows rig installs CRAN&rsquo;s R builds as a regular user in user mode.
This installation writes the user&rsquo;s registry settings instead of the
system-wide registry settings.</p>
<p>The names of the R installations are the same as for admin mode. rig now
supports installing x86_64 and aarch64 R builds on aarch64 Windows, and
similarly to macOS, the non-native builds get a <code>-x86_64</code> suffix:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">PS C:\Users\Gabor Csardi&gt; rig ls
</span></span><span class="line"><span class="cl">* name          version    aliases
</span></span><span class="line"><span class="cl">------------------------------------------
</span></span><span class="line"><span class="cl">  4.4.3
</span></span><span class="line"><span class="cl">  4.4.3-x86_64  (R 4.4.3)
</span></span><span class="line"><span class="cl">  4.5.3
</span></span><span class="line"><span class="cl">* 4.6.1                    release
</span></span><span class="line"><span class="cl">  devel         (R 4.7.0)
</span></span><span class="line"><span class="cl">  next          (R 4.6.1)</span></span></code></pre></div></div>
<p>The new <code>rig rtools</code> command lets you manage Rtools installations. In user
mode they are installed into the user&rsquo;s roaming profile directory, and rig
configures R and Rtools to work together correctly. On aarch64 Windows,
you can install both x86_64 and aarch64 versions of Rtools.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">PS C:\Users\Gabor Csardi&gt; rig rtools ls
</span></span><span class="line"><span class="cl">name  version  full-version   arch     path
</span></span><span class="line"><span class="cl">------------------------------------------------------
</span></span><span class="line"><span class="cl">40    4.0      4.0.3.0        x86_64   C:\Users\Gabor Csardi\AppData\Roaming\rig\data\rtools\40-x86_64
</span></span><span class="line"><span class="cl">44    4.4      4.4.6459.6401  aarch64  C:\Users\Gabor Csardi\AppData\Roaming\rig\data\rtools\44
</span></span><span class="line"><span class="cl">45    4.5      4.5.6768.6492  aarch64  C:\Users\Gabor Csardi\AppData\Roaming\rig\data\rtools\45</span></span></code></pre></div></div>
<h3 id="linux">Linux
</h3>
<p>On Linux rig installs a <a href="https://github.com/rstudio/r-builds#portable-builds-experimental" target="_blank" rel="noopener">portable (manylinux) R build</a> in user
mode. These builds are self-contained, generic glibc (or musl) builds of R.
These builds are intended to work on a wide range of Linux distributions.
However, they are not as battle-tested as the builds rig uses in admin mode.</p>
<p>On glibc systems rig also configures the <a href="https://posit.co/blog/introducing-portable-linux-r-binary-packages" target="_blank" rel="noopener">manylinux repository</a>
for CRAN, that pairs very well with the portable R builds. These packages
are self-contained and compatible with most glibc-based Linux distributions:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="o">&gt;</span> <span class="nf">getOption</span><span class="p">(</span><span class="s">&#34;repos&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">                                                         <span class="n">P3M</span><span class="o">-</span><span class="n">manylinux</span>
</span></span><span class="line"><span class="cl"><span class="s">&#34;https://packagemanager.posit.co/cran/__linux__/manylinux_2_28/latest&#34;</span>
</span></span><span class="line"><span class="cl">                                                                  <span class="n">CRAN</span>
</span></span><span class="line"><span class="cl">                                         <span class="s">&#34;https://cloud.r-project.org&#34;</span></span></span></code></pre></div></div>
<h3 id="positron-and-rstudio-configuration">Positron and RStudio configuration
</h3>
<p>rig automatically configures RStudio (when running <code>rig add</code> and
<code>rig default</code>), so that the rig-default R version is also the default in
RStudio. On macOS and Windows this is automatic. On Linux rig puts the rig
binary directory (see the table above) on the PATH in user mode, and it
is generally on the PATH in admin mode. This is enough to ensure that the
rig-default R version is also the default in RStudio on Linux.</p>
<p>Positron does not have a generic default R version like RStudio does, but
you can choose a default for each workspace. rig (in <code>rig add</code>
and <code>rig default</code>) updates your Positron configuration (the current user&rsquo;s
config file) to make sure that Positron finds all user-mode R installations.</p>
<h3 id="switching-to-user-mode">Switching to user mode
</h3>
<p>Call <code>rig system user-mode</code> to switch a previous setup from admin mode to
user mode. This will reinstall your current R installations in user mode,
and remove the admin mode installation. (But see its options to adjust this!)</p>
<p>See more about the migration at the <a href="https://rig.r-lib.org/tutorial-migrate.html" target="_blank" rel="noopener">rig homepage</a>.</p>
<h2 id="portable-linux">Portable Linux
</h2>
<p>Above you have seen how rig installs portable manylinux builds in user mode.
It is also possible to install them in admin mode. This can be useful for
Linux systems that rig does not have native builds for. To install a
portable Linux build use <code>--platform portable</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">rig add --platform portable 4.6</span></span></code></pre></div></div>
<p>Moreover, if rig does not support a Linux distribution natively, it will
install the manylinux (or musllinux) builds instead, assuming that they
are supported, even in admin mode. With this, most Linux distributions
work automatically, in both admin and user modes.</p>
<h2 id="repository-management">Repository management
</h2>
<p>The rig release improved the <code>rig repos</code> subcommand substantially. These
are the biggest changes.</p>
<h3 id="p3m-by-default">P3M by default
</h3>
<p>rig now configures Posit Public Package Manager (P3M) as a default
repository, on all platforms except aarch64 Windows. (Because P3M would
serve incompatible x86_64 binaries here.) This is because we found
P3M more reliable and because P3M includes older package versions and has
more extensive metadata.</p>
<p>Nevertheless you can opt out with <code>rig add --without-repos=p3m ...</code> at
installation time, or with <code>rig repos setup --without-repos=p3m</code> at any
time.</p>
<h3 id="custom-repositories">Custom repositories
</h3>
<p>You can now add custom repositories to your rig configuration using
<code>rig repos add &lt;name&gt; &lt;url&gt;</code>. For example:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-sh" data-lang="sh"><span class="line"><span class="cl">rig repos add myrepo https://myrepo.example.com</span></span></code></pre></div></div>
<p>You can then turn these (and also the built-in) repositories on or off
using <code>rig repos enable</code> and <code>rig repos disable</code>.</p>
<h3 id="improved-repository-list">Improved repository list
</h3>
<p>The output of <code>rig repos available</code> is now more informative and easier to
read. Type <code>rig repos available &lt;reponame&gt;</code> to get more information about a
repository.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig repos available
</span></span><span class="line"><span class="cl">8 repositories
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Name              Default   Type       Title
</span></span><span class="line"><span class="cl">-----------------------------------------------------------------------------
</span></span><span class="line"><span class="cl">P3M               depends   built-in   Posit Public Package Manager
</span></span><span class="line"><span class="cl">P3M-manylinux     depends   built-in   Posit Package Manager manylinux portable packages
</span></span><span class="line"><span class="cl">RHUB              no        built-in   R-Hub repositories
</span></span><span class="line"><span class="cl">r-universe/cran   depends   built-in   R-universe CRAN mirror
</span></span><span class="line"><span class="cl">r-universe/bioc   no        built-in   R-universe Bioconductor mirror
</span></span><span class="line"><span class="cl">CRAN              yes       built-in   The Comprehensive R Archive Network
</span></span><span class="line"><span class="cl">CRAN-archive      depends   built-in   CRAN archive
</span></span><span class="line"><span class="cl">Bioconductor      no        built-in   Open source software for Bioinformatics
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Use `rig repos available &lt;name&gt;` to see a repository&#39;s URLs.
</span></span><span class="line"><span class="cl">`depends`: a default only on some platforms, architectures or R versions.</span></span></code></pre></div></div>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig repos available r-universe/cran
</span></span><span class="line"><span class="cl">r-universe/cran (1 URL)
</span></span><span class="line"><span class="cl">R-universe CRAN mirror
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">R-universe&#39;s mirror of CRAN. The only repository serving arm64 Windows binary
</span></span><span class="line"><span class="cl">packages.
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">Default      only on platforms matching aarch64-*-mingw32
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">URL          https://cran.r-universe.dev
</span></span><span class="line"><span class="cl">Platforms    windows, *-mingw32
</span></span><span class="line"><span class="cl">Archs        aarch64</span></span></code></pre></div></div>
<h3 id="repository-status">Repository status
</h3>
<p>Use <code>rig repos status</code> to check the status of all enabled repositories.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-text" data-lang="text"><span class="line"><span class="cl">❯ rig repos status
</span></span><span class="line"><span class="cl">6 repositories of R 4.6.1, index bin/macosx/sonoma-arm64/contrib/4.6
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">name              ping   status        types              updated            url
</span></span><span class="line"><span class="cl">-----------------------------------------------------------------------------
</span></span><span class="line"><span class="cl">P3M             601 ms   ok            source, win, mac   -                  https://packagemanager.posit.co/cran/latest
</span></span><span class="line"><span class="cl">BioCsoft        233 ms   ok            source, win, mac   2026-10-01 17:08   https://bioconductor.org/packages/3.23/bioc
</span></span><span class="line"><span class="cl">BioCann         120 ms   source only   source, win, mac   2026-08-01 16:49   https://bioconductor.org/packages/3.23/data/annotation
</span></span><span class="line"><span class="cl">BioCexp         111 ms   source only   source, win, mac   2026-09-15 20:32   https://bioconductor.org/packages/3.23/data/experiment
</span></span><span class="line"><span class="cl">BioCworkflows   111 ms   source only   source, win, mac   2026-08-01 16:39   https://bioconductor.org/packages/3.23/workflows
</span></span><span class="line"><span class="cl">BioCbooks       120 ms   source only   source, win, mac   2026-08-01 16:25   https://bioconductor.org/packages/3.23/books
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl">`ping` is the time the repository took to answer for the index above.
</span></span><span class="line"><span class="cl">`types` is what the R installation&#39;s `repositories` file declares.
</span></span><span class="line"><span class="cl">`source only`: this repository has no index for this platform and R version.</span></span></code></pre></div></div>
<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p>rig does not manage repositories in Positron and RStudio sessions. Use
the built-in Positron and RStudio tools instead.</p>
</div>
</div>
<h2 id="other-improvements">Other improvements
</h2>
<h3 id="avoid-unneeded-reinstalls">Avoid unneeded reinstalls
</h3>
<p><code>rig add</code> now does not reinstall an R version if it is already installed.
Use <code>--reinstall</code> to force a reinstall.</p>
<h3 id="exe-quick-links-on-windows"><code>.exe</code> quick links on Windows
</h3>
<p>On Windows rig now uses <code>.exe</code> quick links instead of <code>.bat</code> files.
<code>.bat</code> files have a lot of issues with passing arguments, <code>.exe</code> files
work much better.</p>
<h3 id="run-r-with-r-on-windows">Run R with <code>R</code> on Windows
</h3>
<p>The new <code>rig system fix-r-alias</code> subcommand deletes the <code>R</code> alias from
PowerShell, so you can now use <code>R</code> to start R.</p>
<h3 id="rig-is-now-signed-on-windows">rig is now signed on Windows
</h3>
<p>Thanks to the <a href="https://signpath.org" target="_blank" rel="noopener">SignPath Foundation</a>, the rig
executables and the rig installer are all signed now.</p>
<h3 id="rig-system-blas"><code>rig system blas</code>
</h3>
<p>The new <code>rig system blas</code> command lets you manage the BLAS/LAPACK library
used by R on macOS.</p>
<h3 id="update-and-uninstall">Update and uninstall
</h3>
<p>Use <code>rig self update</code> to update rig itself, and <code>rig self uninstall</code> to
uninstall it. These commands only work if you installed rig using the
install script. If you installed it with a package manager or ran the
installer manually, you have to use the same package manager or
uninstall it manually.</p>
<h2 id="your-feedback-is-welcome">Your feedback is welcome!
</h2>
<p>We would love to hear your thoughts, suggestions, and any issues you
encounter while using rig. Please use the <a href="https://github.com/r-lib/rig/issues" target="_blank" rel="noopener">issue tracker</a> for bug reports and feature
requests, and the <a href="https://github.com/r-lib/rig/discussions" target="_blank" rel="noopener">discussion forum</a> or <a href="https://forum.posit.co/" target="_blank" rel="noopener">Posit community</a> for general questions and discussions.</p>
<h2 id="acknowledgments">Acknowledgments
</h2>
<p>Huge thanks to everyone who contributed to rig 0.10.0 and 0.11.0:
<a href="https://github.com/achubaty" target="_blank" rel="noopener">@achubaty</a>,
<a href="https://github.com/AdaemmerP" target="_blank" rel="noopener">@AdaemmerP</a>,
<a href="https://github.com/AdrienLeGuillou" target="_blank" rel="noopener">@AdrienLeGuillou</a>,
<a href="https://github.com/Adrilihan" target="_blank" rel="noopener">@Adrilihan</a>,
<a href="https://github.com/Andryas" target="_blank" rel="noopener">@Andryas</a>,
<a href="https://github.com/bashirhamidi" target="_blank" rel="noopener">@bashirhamidi</a>,
<a href="https://github.com/benyamins" target="_blank" rel="noopener">@benyamins</a>,
<a href="https://github.com/biocyberman" target="_blank" rel="noopener">@biocyberman</a>,
<a href="https://github.com/Bisaloo" target="_blank" rel="noopener">@Bisaloo</a>,
<a href="https://github.com/cderv" target="_blank" rel="noopener">@cderv</a>,
<a href="https://github.com/CGMossa" target="_blank" rel="noopener">@CGMossa</a>,
<a href="https://github.com/danielloader" target="_blank" rel="noopener">@danielloader</a>,
<a href="https://github.com/dkczk" target="_blank" rel="noopener">@dkczk</a>,
<a href="https://github.com/eitsupi" target="_blank" rel="noopener">@eitsupi</a>,
<a href="https://github.com/elendil95" target="_blank" rel="noopener">@elendil95</a>,
<a href="https://github.com/eliocamp" target="_blank" rel="noopener">@eliocamp</a>,
<a href="https://github.com/EllaKaye" target="_blank" rel="noopener">@EllaKaye</a>,
<a href="https://github.com/etiennebacher" target="_blank" rel="noopener">@etiennebacher</a>,
<a href="https://github.com/frosforever" target="_blank" rel="noopener">@frosforever</a>,
<a href="https://github.com/gdevenyi" target="_blank" rel="noopener">@gdevenyi</a>,
<a href="https://github.com/ggrothendieck" target="_blank" rel="noopener">@ggrothendieck</a>,
<a href="https://github.com/grantmcdermott" target="_blank" rel="noopener">@grantmcdermott</a>,
<a href="https://github.com/gvelasq" target="_blank" rel="noopener">@gvelasq</a>,
<a href="https://github.com/hadley" target="_blank" rel="noopener">@hadley</a>,
<a href="https://github.com/jabenninghoff" target="_blank" rel="noopener">@jabenninghoff</a>,
<a href="https://github.com/jameslairdsmith" target="_blank" rel="noopener">@jameslairdsmith</a>,
<a href="https://github.com/jennybc" target="_blank" rel="noopener">@jennybc</a>,
<a href="https://github.com/jeroen" target="_blank" rel="noopener">@jeroen</a>,
<a href="https://github.com/jfin4" target="_blank" rel="noopener">@jfin4</a>,
<a href="https://github.com/John15321" target="_blank" rel="noopener">@John15321</a>,
<a href="https://github.com/jonbry" target="_blank" rel="noopener">@jonbry</a>,
<a href="https://github.com/JosiahParry" target="_blank" rel="noopener">@JosiahParry</a>,
<a href="https://github.com/kalenkovich" target="_blank" rel="noopener">@kalenkovich</a>,
<a href="https://github.com/kenahoo" target="_blank" rel="noopener">@kenahoo</a>,
<a href="https://github.com/kieran-mace" target="_blank" rel="noopener">@kieran-mace</a>,
<a href="https://github.com/klmr" target="_blank" rel="noopener">@klmr</a>,
<a href="https://github.com/krlmlr" target="_blank" rel="noopener">@krlmlr</a>,
<a href="https://github.com/lotum-david-j" target="_blank" rel="noopener">@lotum-david-j</a>,
<a href="https://github.com/malcolmbarrett" target="_blank" rel="noopener">@malcolmbarrett</a>,
<a href="https://github.com/mcanouil" target="_blank" rel="noopener">@mcanouil</a>,
<a href="https://github.com/mhurtado13" target="_blank" rel="noopener">@mhurtado13</a>,
<a href="https://github.com/MilesMcBain" target="_blank" rel="noopener">@MilesMcBain</a>,
<a href="https://github.com/mitsuki5284" target="_blank" rel="noopener">@mitsuki5284</a>,
<a href="https://github.com/mns-nordicals" target="_blank" rel="noopener">@mns-nordicals</a>,
<a href="https://github.com/morphatic" target="_blank" rel="noopener">@morphatic</a>,
<a href="https://github.com/Nerwosolek" target="_blank" rel="noopener">@Nerwosolek</a>,
<a href="https://github.com/noamross" target="_blank" rel="noopener">@noamross</a>,
<a href="https://github.com/paluigi" target="_blank" rel="noopener">@paluigi</a>,
<a href="https://github.com/robjhyndman" target="_blank" rel="noopener">@robjhyndman</a>,
<a href="https://github.com/rumichaska" target="_blank" rel="noopener">@rumichaska</a>,
<a href="https://github.com/SaintRod" target="_blank" rel="noopener">@SaintRod</a>,
<a href="https://github.com/sammieephung" target="_blank" rel="noopener">@sammieephung</a>,
<a href="https://github.com/sda030" target="_blank" rel="noopener">@sda030</a>,
<a href="https://github.com/ShixiangWang" target="_blank" rel="noopener">@ShixiangWang</a>,
<a href="https://github.com/t-kalinowski" target="_blank" rel="noopener">@t-kalinowski</a>,
<a href="https://github.com/tylermorganwall" target="_blank" rel="noopener">@tylermorganwall</a>,
<a href="https://github.com/venpopov" target="_blank" rel="noopener">@venpopov</a>,
<a href="https://github.com/vikasrawal" target="_blank" rel="noopener">@vikasrawal</a>,
<a href="https://github.com/wzbillings" target="_blank" rel="noopener">@wzbillings</a>, and
<a href="https://github.com/zivankaraman" target="_blank" rel="noopener">@zivankaraman</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-10-09_rig-0-11-0-part-one/featured.jpg" length="299922" type="image/jpeg" />
    </item>
    <item>
      <title>Positron September Release Highlights</title>
      <link>https://opensource.posit.co/blog/2026-09-09_positron-2026-09-release/</link>
      <pubDate>Wed, 09 Sep 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-09-09_positron-2026-09-release/</guid>
      <dc:creator>Julia Silge</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is Posit&rsquo;s new, next-generation IDE for data science. Positron is designed to be an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>Welcome back to another edition of our monthly Positron updates! Each month we share highlights from our <a href="https://positron.posit.co/release-notes" target="_blank" rel="noopener">latest release</a> and useful resources. <a href="https://opensource.posit.co/blog/2026-08-13_positron-2026-08-release">Last release</a> we told you about new Data Connections sources, a round of polish for inline output in Quarto documents, and help installing missing packages. This milestone brings a redesigned welcome page, a first version of Import Data, new Posit Assistant features, expanded Data Connections, package vulnerability scanning, and a more responsive Console.</p>
<h2 id="welcome-page-refresh-and-interpreter-setup">Welcome page refresh and interpreter setup
</h2>
<p>We redesigned the Positron welcome page. It now leads with an environment setup card that checks whether Python and R are ready to use, with actions to help resolve any problem it finds. The Positron badge and name now come with a <strong>Help</strong> button that opens the Help pane, and a banner links to the walkthroughs, including a new &ldquo;Get Started with Positron&rdquo; walkthrough that covers the Positron panes, keyboard shortcuts, built-in extensions, and Git.</p>
<img src="https://opensource.posit.co/blog/2026-09-09_positron-2026-09-release/welcome-page.gif" data-fig-align="center" data-fig-alt="The redesigned Positron Welcome page, showing an Environment setup card with Python checks (3 of 4 passed) including a Create Python Environment button, and R already set up successfully." />
<p>The environment setup theme continues into interpreter selection itself. When you select a Python managed by your operating system or a package manager, Positron now offers to create a virtual environment for your workspace instead of installing packages into that shared interpreter. With no folder open, the environment Positron creates now lives at <code>~/.virtualenvs/positron</code> rather than <code>~/.venv</code>, so it stays in the interpreter picker after a restart, and Positron asks before creating an environment in your home directory. We also removed the confusing startup notification that reported no interpreters were found while linking to documentation saying no setup was needed, and interpreters installed under <code>/opt/python</code> are now labeled <code>Global</code> instead of <code>Unknown</code> in the interpreter picker.</p>
<h2 id="import-data">Import Data
</h2>
<p>Before we started work this month, importing data via the Positron UI was our most upvoted feature request and this release now delivers a first version. When you view a CSV or TSV file in the Data Explorer, a new <strong>Import Data</strong> button in the action bar opens a dialog. The dialog shows the code to load that file into a data frame. You can copy the code, or click <strong>Import</strong> to run it in the console, starting a session if one is not already running.</p>
<img src="https://opensource.posit.co/blog/2026-09-09_positron-2026-09-release/import-data-excel.gif" data-fig-align="center" data-fig-alt="An Excel spreadsheet open in the Data Explorer with an Import Data button in the action bar, next to a Python console session ready to run the generated import code." />
<p>Import Data supports CSV and TSV files in Python with pandas and in R with the readr package, as well as Excel workbooks and Parquet files (with readxl and nanoparquet in R). The generated code can reproduce the filters and sorts you have applied in the Data Explorer, and it names the file by a workspace-relative path when the file is inside your workspace, so the code is easier to share and rerun. You can open the dialog from the Data Explorer, the File menu, the Variables pane, or the File Explorer context menu.</p>
<h2 id="posit-assistant">Posit Assistant
</h2>
<p>This release brings a new <strong>Agent Layout</strong> that opens <a href="https://pos.it/assistant" target="_blank" rel="noopener">Posit Assistant</a> in the editor area with a compact Session pane, giving you more visibility into the agent&rsquo;s actions as it works alongside your code. Configuring language model providers also gets a redesign. Our new Configure LLM Providers modal groups providers by connection state, so you can see at a glance which providers are ready to use. If you have trouble with the new dialog and need to switch back, set <a href="positron://settings/assistant.newProviderModal"><code>assistant.newProviderModal</code></a> to <code>false</code>.</p>
<img src="https://opensource.posit.co/blog/2026-09-09_positron-2026-09-release/provider-modal-dialog.png" data-fig-align="center" data-fig-alt="The Configure LLM Providers modal in Positron, listing connected providers (Posit AI Pass, Anthropic, GitHub Copilot) and additional model providers available to connect (Amazon Bedrock, Microsoft Foundry, OpenAI)." />
<p>You can now configure multiple custom providers, each with its own name, type, endpoint, credential, and model list; the previous single &ldquo;Custom Provider&rdquo; option is now called &ldquo;OpenAI Compatible&rdquo; to better match what it actually does. Amazon Bedrock users get a smoother experience as well. An expired AWS SSO session can be renewed right from the provider modal instead of requiring <code>aws sso login</code> in a terminal, and the AWS profile and region can now be set in the configuration dialog rather than only through environment variables or a hand-edited <code>providers.json</code>. Speaking of which, <code>providers.json</code> now accepts comments, and your comments survive edits that Positron makes to the file.</p>
<h2 id="data-connections">Data Connections
</h2>
<p>The Data Connections preview keeps growing. A new ODBC data connection driver lets you browse any database with an installed ODBC driver in the Connections pane and open it in the Data Explorer. Data sources already configured on your machine appear automatically. Databricks gains OAuth sign-in on desktop and reads <code>DATABRICKS_TOKEN</code>, <code>DATABRICKS_HOST</code>, and <code>DATABRICKS_CONFIG_FILE</code> credentials managed by Posit Workbench automatically. A <strong>Disconnect</strong> option in the context menu closes a connection and any Data Explorers opened from it, and each data connection driver now gets its own log output channel.</p>
<p>Smaller improvements round out the preview. You can now set <a href="positron://settings/dataConnections.enabled"><code>dataConnections.enabled</code></a> per workspace, so a repository can turn on the Connections pane for anyone who opens it. The tree is shallower so table and column names get more of the panel&rsquo;s width, and a new <a href="positron://settings/dataConnections.tree.indent"><code>dataConnections.tree.indent</code></a> setting controls the indentation. DuckDB connections now default to read-only, so the Connections pane and a Python or R session can have the same database open at once; when a lock conflict does happen, the error now explains that another session has locked the database.</p>
<h2 id="package-security-vulnerabilities">Package security vulnerabilities
</h2>
<p>The Packages pane now shows known security vulnerabilities (Common Vulnerabilities and Exposures scoring) for installed Python and R packages, so you can see at a glance whether something in your environment has a known CVE. The data comes from your environment&rsquo;s own Posit Package Manager repository when it has one, and from the public Posit instance otherwise, so what you see reflects the same package source your organization already governs.</p>
<img src="https://opensource.posit.co/blog/2026-09-09_positron-2026-09-release/packages-pane-cve.png" data-fig-align="center" data-fig-alt="The Packages pane showing the tornado package&#39;s Security tab with three known vulnerabilities listed by severity, each with a CVSS score, description, and the version where it was fixed." />
<h2 id="a-more-responsive-console">A more responsive Console
</h2>
<p>Console code submission is now faster, always shows visual feedback, and can be canceled while a completeness check is in flight; the Console no longer waits indefinitely with no feedback when a kernel is slow or unreachable. The Console breaks multi-statement input into complete expressions and executes them statement by statement for languages that support it. A new <a href="positron://settings/console.promptWhenIncomplete"><code>console.promptWhenIncomplete</code></a> setting runs submitted code immediately without a completeness check.</p>
<p>A few more Console fixes are worth knowing about. The Console no longer takes focus at runtime startup when you are working in another view or editor, <strong>Interrupt</strong> stays visible after switching between busy consoles, and session names now ellipsize to fit as the console tab list narrows.</p>
<h2 id="whats-coming-next">What&rsquo;s coming next
</h2>
<ul>
<li>posit::conf(2026) is next week! Our team will have several sessions on Positron, and there is still time to <a href="https://conf.posit.co/2026/" target="_blank" rel="noopener">register</a> to join us virtually from anywhere in the world.</li>
<li>Meet Posit at <a href="https://posit.co/events/cdao-government-2026" target="_blank" rel="noopener">CDAO Government</a> on September 22-23 in Washington, D.C. Stop by our booth to talk data modernization and where agentic AI fits in government.</li>
</ul>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Tip</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co/download" target="_blank" rel="noopener">Download Positron</a> to try out the new features and improvements in this release!</p>
</div>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-09-09_positron-2026-09-release/featured.svg" length="93678" type="image/svg&#43;xml" />
    </item>
    <item>
      <title>Positron August Release Highlights</title>
      <link>https://opensource.posit.co/blog/2026-08-13_positron-2026-08-release/</link>
      <pubDate>Thu, 13 Aug 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-08-13_positron-2026-08-release/</guid>
      <dc:creator>Julia Silge</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is Posit&rsquo;s new, next-generation IDE for data science. Positron is designed to be an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>Welcome back to another edition of our monthly Positron updates! Each month we share highlights from our <a href="https://positron.posit.co/release-notes" target="_blank" rel="noopener">latest release</a> and useful resources. <a href="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release">Last release</a> we told you about several major features that came out of preview to general availability, including the <a href="https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga">new notebook editor</a>. This milestone we are excited to share new functionality for SQL support, reproducible authoring, helping you know when packages are missing, and more.</p>
<h2 id="data-connections">Data Connections
</h2>
<p>Data Connections is our new way to work with SQL and database-like resources in Positron, from local files and database servers to cloud data warehouses. It is currently available as a preview feature, and you can enable it with the <a href="positron://settings/dataConnections.enabled"><code>dataConnections.enabled</code></a> setting. This release more than doubles the number of data sources you can reach. Amazon Redshift, Snowflake, Databricks, and Posit Connect pins join the existing DuckDB, PostgreSQL, and SQLite support.</p>
<img src="https://opensource.posit.co/blog/2026-08-13_positron-2026-08-release/data-connections.gif" data-fig-align="center" data-fig-alt="Browsing the schemas and tables of a DuckDB connection in the Data Connections panel, then opening a table in the Data Explorer to see its column profiles and data." />
<p>The panel itself is more capable as well. <strong>Refresh</strong> and <strong>Refresh All</strong> reload the tree while preserving what you have expanded, briefly highlighting the rows that were reloaded. Open connections now show an indicator. Collapsing a connection in the UI keeps you connected to your data source, while anything you&rsquo;ve previewed with the Data Explorer stays still open. When you remove a connection, Positron asks for confirmation and reports how many Data Explorers will close with it.</p>
<p>Data Connections is still an experimental preview, and your feedback continues to shape it. Tell us which databases and warehouses you need, and anything confusing, missing, or broken, in the <a href="https://github.com/posit-dev/positron/discussions/14571" target="_blank" rel="noopener">Data Connections discussion</a>.</p>
<h2 id="inline-output-for-quarto">Inline output for Quarto
</h2>
<p><a href="https://positron.posit.co/quarto-inline-output" target="_blank" rel="noopener">Inline output for <code>.qmd</code> documents</a> came out of preview last release, and this release brings you a substantial round of polish for this way of working. Be aware that the Quarto settings have moved into a dedicated <code>quarto.*</code> namespace with its own group in the Settings editor. The previous <code>positron.quarto.*</code> keys still work but are deprecated, and Positron will prompt you to update your settings.</p>
<p>Before a kernel starts, the kernel status names the interpreter it will start and offers an explicit <strong>Start Kernel</strong> action. When a cell fails, <strong>Fix</strong> and <strong>Explain</strong> buttons send the error to <a href="https://assistant.posit.co/" target="_blank" rel="noopener">Posit Assistant</a>, matching the Positron notebook experience. The editor also scrolls to reveal output as it is produced, which you can turn off with the new <a href="positron://settings/quarto.inlineOutput.autoScroll"><code>quarto.inlineOutput.autoScroll</code></a> setting.</p>
<p>Output renders more faithfully as well. The editor gutter now shows which statement is currently executing and per-statement progress. Positron draws images at your display&rsquo;s pixel ratio, so plots are sharp on retina screens, and Python figures now respect the <code>fig-width</code> and <code>fig-height</code> cell options.</p>
<img src="https://opensource.posit.co/blog/2026-08-13_positron-2026-08-release/inline-output-plot-metadata.png" data-fig-align="center" data-fig-alt="A Quarto document open in Positron with a Python cell that sets the fig-width and fig-height options, and the resulting matplotlib scatter plot rendered inline below the cell at that size." />
<p>HTML widgets no longer stick in the editor corner when you scroll past them, or trap scrolling instead of letting the document scroll. HTML widgets no longer render as raw HTML after a reload, and collapsed output no longer springs back open when its cell re-runs. Running code in a Quarto document also pins the editor tab now, so Positron does not silently close the document and its session when you open another file.</p>
<h2 id="ai-model-providers">AI model providers
</h2>
<p>Positron now reads AI model provider configuration from a single <code>providers.json</code> file rather than a scattered set of settings. The new release will migrate your existing configuration automatically when you start it, and deprecates the <code>authentication.*</code> and <code>positron.assistant.provider.*.enable</code> settings in favor of it. Two new commands give you direct access: <em>Open AI Provider Settings (JSON)</em> opens <code>providers.json</code> from the Command Palette, and <em>Migrate Provider Settings to providers.json</em> runs the migration on demand.</p>
<h2 id="install-missing-packages">Install missing packages
</h2>
<p>Positron now notices when your code refers to a package you do not have installed and offers to install it for you. The prompt appears for packages referenced by your scripts and notebooks in both R and Python.</p>
<img src="https://opensource.posit.co/blog/2026-08-13_positron-2026-08-release/missing-package.gif" data-fig-align="center" data-fig-alt="A Shiny app in Positron showing a missing package button in the editor toolbar. Clicking it installs bslib in the console, and the app then runs with its bubble chart in the Viewer pane." />
<p>A <code>library()</code> or <code>import</code> call for something missing becomes a single click instead of an error you have to go resolve by hand.</p>
<h2 id="performance-and-memory">Performance and memory
</h2>
<p>We continue to invest in the memory footprint, performance, and reliability of Positron. Several components now load only when they are actually needed, and turning off <a href="positron://settings/ai.enabled"><code>ai.enabled</code></a> now means Positron never loads some heavy AI-related components at all. We fixed a cluster of long-standing reliability problems around session restarts and lifecycles.</p>
<p>Startup and editing are faster as well. R and Python kernels start much faster on Windows systems with aggressive antivirus software. The interpreter session picker appears immediately instead of waiting for interpreter discovery to finish. We also fixed slow typing, formatting, and saving in long R and Python files.</p>
<h2 id="whats-coming-next">What&rsquo;s coming next
</h2>
<ul>
<li>Join <a href="https://posit.co/workflow-demo/ai-governance-workbench" target="_blank" rel="noopener">our upcoming webinar</a> on August 26 to learn about AI governance in Posit Workbench.</li>
<li>We are looking forward to posit::conf(2026) next month, where our team will have several sessions on Positron. <a href="https://conf.posit.co/2026/" target="_blank" rel="noopener">Register now</a> to join us in person in Houston or virtually from anywhere in the world.</li>
</ul>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Tip</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co/download" target="_blank" rel="noopener">Download Positron</a> to try out the new features and improvements in this release!</p>
</div>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-08-13_positron-2026-08-release/featured.svg" length="91423" type="image/svg&#43;xml" />
    </item>
    <item>
      <title>roxygen2 8.1.0</title>
      <link>https://opensource.posit.co/blog/2026-08-04_roxygen2-8-1-0/</link>
      <pubDate>Tue, 04 Aug 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-08-04_roxygen2-8-1-0/</guid>
      <dc:creator>Hadley Wickham</dc:creator><description><![CDATA[<p>I&rsquo;m well chuffed to announce that <a href="https://roxygen2.r-lib.org" target="_blank" rel="noopener">roxygen2 8.1.0</a> is now on CRAN.
roxygen2 turns specially formatted comments in your R code into the <code>.Rd</code> files that power R&rsquo;s help system.
You can install it with:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;roxygen2&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>This is a small release, and the two main changes are about performance: one makes your package load faster for your users, and one makes documenting faster for you.
You can see the full list of changes in the <a href="https://github.com/r-lib/roxygen2/releases/tag/v8.1.0" target="_blank" rel="noopener">release notes</a>.</p>
<h2 id="merged-importfrom-directives">Merged <code>importFrom()</code> directives
</h2>
<p><code>@importFrom</code> used to generate one <code>importFrom()</code> directive per symbol.
Now it generates a single multi-line directive per package:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># before</span>
</span></span><span class="line"><span class="cl"><span class="nf">importFrom</span><span class="p">(</span><span class="n">utils</span><span class="p">,</span><span class="n">URLdecode</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">importFrom</span><span class="p">(</span><span class="n">utils</span><span class="p">,</span><span class="n">URLencode</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">importFrom</span><span class="p">(</span><span class="n">utils</span><span class="p">,</span><span class="n">head</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">importFrom</span><span class="p">(</span><span class="n">utils</span><span class="p">,</span><span class="n">tail</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># after</span>
</span></span><span class="line"><span class="cl"><span class="nf">importFrom</span><span class="p">(</span><span class="n">utils</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">URLdecode</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">URLencode</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">head</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">  <span class="n">tail</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span></span></span></code></pre></div></div>
<p>We made this change because we discovered that the number of <code>importFrom()</code> directives in your <code>NAMESPACE</code> has a surprisingly large effect on loading time.
<code>loadNamespace()</code> does a chunk of per-directive work, so the cost grows with the number of directives, not the number of symbols.
Importing 1,000 functions with 1,000 separate directives costs around 120ms; importing the same 1,000 functions with one directive per package costs around 9ms, the same as importing 10.</p>
<p>Of course, most packages import far fewer than 1,000 objects.
But <code>library()</code> costs are paid by every user, on every session, and they&rsquo;re cumulative: loading a package also loads its dependencies.
Merging the directives removes the cost for the user entirely, at little cost to you as the developer: just one noisy <code>NAMESPACE</code> diff.</p>
<p>My colleague Kevin Ushey also fixed a related annoyance: <code>@importFrom</code>, <code>@importClassesFrom</code>, and <code>@importMethodsFrom</code> once again accept multi-line input, so you can spread a long list of imports over several lines for readability.
You&rsquo;ll just need a hanging indent on the continuation lines.</p>
<h2 id="faster-documenting-with-rdtools">Faster documenting with rdtools
</h2>
<p>The other change helps developers, rather than users: documenting a package with lots of cross-references is now considerably faster.</p>
<p>The bottleneck was topic resolution: every time roxygen2 sees a link like <code>[dplyr::filter()]</code>, or needs to work out which package an unqualified <code>[filter()]</code> refers to, it has to ask &ldquo;does this package have a topic with this alias?&rdquo;.
Previously roxygen2 answered that question by calling <code>utils::help()</code>, which takes about 1.5ms.
That doesn&rsquo;t sound like much, but to resolve an unqualified link roxygen2 has to check every dependency plus the base packages, which adds up.
On a link-heavy package like testthat, that meant roughly half of the total time spent documenting was just looking up cross-references.</p>
<p>The fix is to stop asking one question at a time, and to cache each lookup.
Installed packages already ship an alias index (<code>help/aliases.rds</code>), and packages loaded from source have one maintained by pkgload, so all the information needed is available in bulk &mdash; it just needs to be read once and kept in a hash table, rather than rediscovered on every query.</p>
<p>That logic now lives in a new package, <a href="https://rdtools.r-lib.org" target="_blank" rel="noopener">rdtools</a>, which provides cached lookup of help topics and aliases across installed, source, and in-development packages, along with efficient retrieval of parsed <code>Rd</code> objects.
Pulling it out of roxygen2 deleted a decent amount of fiddly code that I&rsquo;d also implemented (to varying levels of quality) in pkgdown and downlit.</p>
<h2 id="acknowledgements">Acknowledgements
</h2>
<p>A big thanks to everyone who helped make this release possible! <a href="https://github.com/aphalo" target="_blank" rel="noopener">@aphalo</a>, <a href="https://github.com/DavisVaughan" target="_blank" rel="noopener">@DavisVaughan</a>, <a href="https://github.com/dragosmg" target="_blank" rel="noopener">@dragosmg</a>, <a href="https://github.com/etiennebacher" target="_blank" rel="noopener">@etiennebacher</a>, <a href="https://github.com/gadenbuie" target="_blank" rel="noopener">@gadenbuie</a>, <a href="https://github.com/hadley" target="_blank" rel="noopener">@hadley</a>, <a href="https://github.com/jan-swissre" target="_blank" rel="noopener">@jan-swissre</a>, <a href="https://github.com/jemus42" target="_blank" rel="noopener">@jemus42</a>, <a href="https://github.com/jranke" target="_blank" rel="noopener">@jranke</a>, <a href="https://github.com/kevinushey" target="_blank" rel="noopener">@kevinushey</a>, <a href="https://github.com/lionel-" target="_blank" rel="noopener">@lionel-</a>, <a href="https://github.com/mcol" target="_blank" rel="noopener">@mcol</a>, <a href="https://github.com/MichaelChirico" target="_blank" rel="noopener">@MichaelChirico</a>, <a href="https://github.com/peterdesmet" target="_blank" rel="noopener">@peterdesmet</a>, and <a href="https://github.com/thisisnic" target="_blank" rel="noopener">@thisisnic</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-08-04_roxygen2-8-1-0/thumbnail.jpg" length="158270" type="image/jpeg" />
    </item>
    <item>
      <title>Positron&#39;s Jupyter Notebook Editor Is Now Generally Available</title>
      <link>https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga/</link>
      <pubDate>Wed, 29 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga/</guid>
      <dc:creator>Wasim Lorgat</dc:creator>
      <dc:creator>Cindy Tong</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is Posit&rsquo;s new, next-generation IDE for data science. Positron is designed to be an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>Data scientists are often forced to choose between the simplicity of the classic Jupyter notebook experience and the advanced capabilities of a general-purpose IDE. With the <a href="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/" target="_blank" rel="noopener">2026.07 release</a>, you no longer have to choose: Positron now delivers a first-class Notebook Editor inside an IDE built for data science.</p>
<p>If you&rsquo;re new to Positron, here&rsquo;s what the Notebook Editor is like in your daily work.</p>
<h2 id="environment-management">Environment management
</h2>
<p>You clone your colleague&rsquo;s repo and try to rerun their notebook. Which Python version do you use? What dependencies do you install and which versions? Did you previously set up an environment that you could reuse? You&rsquo;re faced with all of these questions before you&rsquo;ve run a single line of code.</p>
<p>Positron has several <a href="https://opensource.posit.co/blog/2026-07-08_positron-uv/" target="_blank" rel="noopener">built-in workflows</a> to help you manage your environments. When you open a repo, Positron discovers your installed environments, suggests the right one for the repo, and prompts you to set up a new environment if needed. The active environment is displayed front and center; click to restart or switch environments. Positron uses the same environment across your Jupyter and Quarto notebooks, scripts, and consoles. You can see inside your environments, which packages are installed, their versions, whether newer versions are available, and you can upgrade them. Environments are first-class citizens of Positron. <a href="https://positron.posit.co/positron-notebook-editor.html#setting-up-your-environment" target="_blank" rel="noopener">Explore Positron&rsquo;s environment management</a>.</p>
<img src="https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga/notebook-from-git.gif" data-fig-align="center" data-fig-alt="Positron helps you resolve environment and package dependencies as you pull down a colleague&#39;s notebook" />
<h2 id="interactive-data-exploration">Interactive data exploration
</h2>
<p>When you run a cell in your notebook, your variables appear in the <a href="https://positron.posit.co/variables-pane.html" target="_blank" rel="noopener">Variables Pane</a>. Filter, sort, and search your data in the <a href="https://positron.posit.co/data-explorer.html" target="_blank" rel="noopener">Data Explorer</a>, and try out plot variations in the Visualize wizard. You can easily convert your point-and-click interactions into code by using the copy-code feature to bring them back into your notebook. If you get stuck, look up library documentation in the <a href="https://positron.posit.co/help-pane.html" target="_blank" rel="noopener">Help Pane</a>. All of this works out of the box.</p>
<img src="https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga/notebook-data-exploration.gif" data-fig-align="center" data-fig-alt="Run a notebook and see your variables update live, inspect them in the data explorer and convert filters back to code." />
<h2 id="streamlined-version-control">Streamlined version control
</h2>
<p>Notebooks store outputs and execution metadata alongside code, which is great for sharing but does not work well with version control. Positron includes settings to exclude outputs and execution metadata from the saved file, so the diff is just your code change. <a href="https://positron.posit.co/positron-notebook-editor.html#version-control" target="_blank" rel="noopener">Explore the custom settings</a> to clean up your git diffs.</p>
<p>Your notebooks stay ordinary <code>.ipynb</code> files and the editing experience stays classic Jupyter. When a plain-text workflow suits you better, conversion to and from <code>.ipynb</code> and other formats is built in.</p>
<img src="https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga/notebook-save-settings.png" data-fig-align="center" data-fig-alt="Customize what is saved in your notebooks for better version control." />
<h2 id="ai-assistance">AI assistance
</h2>
<p>Posit Assistant sees your notebook as more than just text. It works with the live session behind the notebook: your variables, your data, your plots. Ask it to fix a chart and it can inspect the dataframe, look at the plot itself, edit the cell, and run it again. It does the same things you would do, using the same panes you use. <a href="https://positron.posit.co/positron-notebook-editor.html#ai-integration" target="_blank" rel="noopener">Explore notebook-aware AI assistance in Positron</a>.</p>
<img src="https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga/notebook-ai.gif" data-fig-align="center" data-fig-alt="Assistant can fix, explain, run your notebook and suggest next steps." />
<h2 id="get-started">Get started
</h2>
<p><a href="https://positron.posit.co/download" target="_blank" rel="noopener">Download Positron</a> and open a project with a Jupyter notebook. The Notebook Editor is now on by default. If you&rsquo;re coming from another IDE, your existing <code>.ipynb</code> files will open as-is without any conversion needed.</p>
<p>For the full feature reference, check out our <a href="https://positron.posit.co" target="_blank" rel="noopener">documentation</a>.</p>
<p>If you run into any issues or have ideas on how we can improve Positron, reach out on <a href="https://github.com/posit-dev/positron" target="_blank" rel="noopener">GitHub</a>.</p>
<p>Already using Positron? Everything in this release is in the <a href="https://positron.posit.co/release-notes" target="_blank" rel="noopener">release notes</a>.</p>
<p>Thank you so much for helping us build Positron&rsquo;s Jupyter notebook support from the ground up by chatting with us live, sharing your pain points, and giving us feedback on GitHub!</p>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Tip</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co/download" target="_blank" rel="noopener">Download Positron</a> to try out the Notebook Editor and other new features!</p>
</div>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-29_positron-jupyter-notebook-editor-ga/featured.png" length="686743" type="image/png" />
    </item>
    <item>
      <title>ir 0.1.0: self-describing R scripts and Quarto documents</title>
      <link>https://opensource.posit.co/blog/2026-07-23_ir-0-1-0/</link>
      <pubDate>Thu, 23 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-23_ir-0-1-0/</guid>
      <dc:creator>Tomasz Kalinowski</dc:creator>
      <dc:creator>Christophe Dervieux</dc:creator>
      <dc:creator>Charlie Gao</dc:creator><description><![CDATA[<style>
.chroma code[data-lang="r"] .c1 {
  font-style: normal;
}
</style>
<p>Today we are announcing the first public release of <code>ir</code>, a small
command-line tool that runs R scripts and renders Quarto documents using
runtime requirements declared in the file itself.</p>
<p><code>ir</code> is for one-file workflows that do not quite need a project but
still need to be easy to share and rerun later. Simply put the package
requirements, and optionally the R version, next to the code. When you
render or execute the file, <code>ir</code> resolves the requirements, prepares
cached package libraries, and launches R or Quarto with a runtime ready
to use.</p>
<p>The interface is inspired by
<a href="https://peps.python.org/pep-0723/" target="_blank" rel="noopener">PEP 723</a> and
<a href="https://docs.astral.sh/uv/" target="_blank" rel="noopener"><code>uv run --script</code></a>. A script can carry
enough metadata to describe its runtime, and the runner can resolve that
runtime into a cached environment on demand. <code>ir</code> brings that pattern to
R scripts and Quarto documents, using <code>pak</code>, <code>renv</code>, and <code>rig</code> on the R
side, and <code>reticulate</code>&rsquo;s uv-backed helper when Python is part of the
runtime.</p>
<p><code>ir</code> focuses on two workflows:</p>
<ul>
<li>running or rendering self-describing scripts and documents <br>
(<code>ir run</code>, <code>ir render</code>)</li>
<li>running or installing command-line tools distributed through R
packages <br>
(<code>rx</code>, <code>ir tool install</code>)</li>
</ul>
<h2 id="why-ir">Why <code>ir</code>?
</h2>
<p>R scripts often begin as small, local utilities: a report, a data pull,
a model-training or evaluation run, a quick diagnostic, or an example
shared with a colleague. Over time, the script can become important, but
the setup still lives somewhere else: in a README, in a shell history,
in a project library, or in the author&rsquo;s current R installation.</p>
<p><code>ir</code> makes the runtime specification part of the source file. That means
a script can say, directly:</p>
<ul>
<li>which packages it needs</li>
<li>which R should run it, when that needs to be explicit</li>
<li>whether user libraries should be visible</li>
<li>whether CRAN packages should be resolved as of a specific date</li>
</ul>
<p>This should help you rerun the script reliably at a later date. Keeping
the metadata in the file makes it less likely to be lost or fall out of
sync with the code.</p>
<h2 id="how-ir-fits-with-existing-tools">How <code>ir</code> fits with existing tools
</h2>
<p><code>ir</code> sits alongside the R tools people already use and builds on several
of them directly:</p>
<ul>
<li><a href="https://github.com/r-lib/rig" target="_blank" rel="noopener"><code>rig</code></a> installs, removes, and switches
between R versions on macOS, Windows, and Linux. <code>ir</code> calls <code>rig</code> when
a file requests a specific R version, or when date-only
<code>exclude-newer</code> needs to select the latest R minor version available
on that date. <code>rig</code> is optional for files that only declare packages.</li>
<li><a href="https://pak.r-lib.org/" target="_blank" rel="noopener"><code>pak</code></a> is a fast package installer with a
built-in solver. <code>ir</code> uses <code>pak</code> to resolve the dependency graph from
a file&rsquo;s declared packages and fetch them from the appropriate
repositories. <code>pak</code> is bootstrapped automatically on first use, so you
do not need to install it separately.</li>
<li><a href="https://rstudio.github.io/renv/" target="_blank" rel="noopener"><code>renv</code></a> gives R projects isolated
package libraries and lockfiles. <code>ir</code> uses <code>renv</code>&rsquo;s global package
cache to assemble reusable libraries without creating a <code>renv</code> project
or lockfile.</li>
</ul>
<p><code>ir</code> builds on that stack for non-project workflows: resolving the
runtime for a self-describing script or Quarto document, and running or
installing command-line entry points distributed by R packages.</p>
<h2 id="a-self-describing-r-script">A self-describing R script
</h2>
<p>Here is a complete script:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#!/usr/bin/env -S ir run</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| packages:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#|   - dplyr&gt;=1.0</span>
</span></span><span class="line"><span class="cl"><span class="c1">#|   - tidyr</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| isolated: true</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| exclude-newer: &#34;2024-01-15&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">dplyr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">tidyr</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">mtcars</span> <span class="o">|&gt;</span> <span class="nf">count</span><span class="p">(</span><span class="n">cyl</span><span class="p">,</span> <span class="n">gear</span><span class="p">)</span> <span class="o">|&gt;</span> <span class="nf">pivot_wider</span><span class="p">(</span><span class="n">names_from</span> <span class="o">=</span> <span class="n">gear</span><span class="p">,</span> <span class="n">values_from</span> <span class="o">=</span> <span class="n">n</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Run it with:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> ir run script.R
</span></span></code></pre></div></div>
<p>Or, on macOS and Linux, make it executable and run it directly:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> chmod +x script.R
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ./script.R
</span></span></code></pre></div></div>
<p>The metadata block is YAML written in <code>#|</code> comments after an optional
shebang. <code>ir</code> reads that metadata, resolves the declared packages with
<code>pak</code>, materializes a package library with <code>renv</code>, and starts R with the
resolved library at the front of <code>.libPaths()</code>.</p>
<p>By default, user libraries remain visible as a fallback. Add
<code>isolated: true</code> in the file, or use <code>--isolated</code> at the command line,
to run without the user library.</p>
<h2 id="running-r-package-tools-with-rx">Running R package tools with <code>rx</code>
</h2>
<p>The <code>ir</code> release also includes <code>rx</code>, a short alias for <code>ir tool run</code>
that runs executables provided by R packages.</p>
<p>Package authors can expose command-line entry points through standard
package subdirectories such as <a href="https://cran.r-project.org/doc/manuals/r-release/R-exts.html#Package-subdirectories-1" target="_blank" rel="noopener"><code>exec/</code> and <code>bin/</code></a>. Files in <code>exec/</code> can be regular <code>Rscript</code> files,
<a href="https://github.com/r-lib/Rapp" target="_blank" rel="noopener"><code>Rapp</code></a> apps, or direct executable
scripts. <code>rx</code> resolves the package, finds the requested executable, and
runs it in an isolated library.</p>
<p>For example, this resolves the <code>btw</code> CLI from the <code>btw</code> R package on
demand and runs <code>btw --help</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> rx btw --help
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> same as:
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ir tool run btw --help
</span></span></code></pre></div></div>
<p>For tools you use regularly, install them with <code>ir tool install</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">#</span> run once
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ir tool install btw
</span></span><span class="line"><span class="cl"><span class="err">
</span></span></span><span class="line"><span class="cl"><span class="gp">#</span> now you only need
</span></span><span class="line"><span class="cl"><span class="gp">$</span> btw --help
</span></span></code></pre></div></div>
<h2 id="cached-by-design">Cached by design
</h2>
<p>The first run of a new dependency set does the normal work of resolving
and installing packages. Later runs reuse cached resolutions and
content-addressed package libraries when the same requirements are seen
again.</p>
<p>That makes <code>ir</code> useful for both one-off and repeated command-line work.
You can run a script, run an inline expression, render a report, or
launch a package-provided executable without creating a project
directory just to hold the dependency state.</p>
<p><code>ir</code> also bootstraps its own resolver tooling on first use, so you do
not need to pre-install <code>pak</code> or <code>renv</code>.</p>
<h2 id="reproducibility-without-a-project">Reproducibility without a project
</h2>
<p>For R scripts that need more explicit reproducibility, <code>exclude-newer</code>
is usually the first thing to reach for:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">exclude-newer</span><span class="p">:</span><span class="w"> </span><span class="ld">2024-01-15</span></span></span></code></pre></div></div>
<p>You can also provide the same date at the command line:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> ir run --exclude-newer 2024-01-15 script.R
</span></span></code></pre></div></div>
<p>This resolves packages from the Posit Package Manager snapshot for that
date. When no other R selector is set, the same date also tells <code>ir</code> to
select the latest R minor version available on that date. If you were
writing with the current release of R and current CRAN packages, the
date alone is usually enough.</p>
<p>Use <code>r-version</code> when the script really needs a specific installed R
version or version range. This selection uses
<a href="https://github.com/r-lib/rig" target="_blank" rel="noopener"><code>rig</code></a>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">r-version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;4.3&#34;</span></span></span></code></pre></div></div>
<p>Together, these options let a file carry the important parts of its
runtime requirements without needing a surrounding project. For
reproducibility, most files should need only a list of packages and a
date.</p>
<h2 id="quarto-documents-too">Quarto documents too
</h2>
<p><code>ir</code> uses the same metadata model for Quarto documents. Put package
metadata under an <code>ir:</code> key in the document YAML:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">My report</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">ir</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">packages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">dplyr&gt;=1.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">gt@1.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">isolated</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">exclude-newer</span><span class="p">:</span><span class="w"> </span><span class="ld">2025-05-15</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span></span></span></code></pre></div></div>
<p>Then render with:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> ir render report.qmd
</span></span><span class="line"><span class="cl"><span class="gp">$</span> ir render report.qmd --to pdf
</span></span></code></pre></div></div>
<p>When <code>ir</code> selects an R executable using <code>--r-version</code>, frontmatter
<code>ir.r-version</code>, or date-only <code>exclude-newer</code>, it sets <a href="https://quarto.org/docs/advanced/environment-vars.html#variables-quarto-inspects" target="_blank" rel="noopener"><code>QUARTO_R</code></a> so Quarto renders with that R. <code>ir</code> also seeds <code>rmarkdown</code>
automatically for knitr-based renders unless you declare it yourself.</p>
<h2 id="python-environments-too">Python environments too
</h2>
<p>Some R scripts and Quarto documents also need Python. In an R script,
declare the R packages and Python requirements in the same metadata
block:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1">#!/usr/bin/env -S ir run</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| packages:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#|   - reticulate</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| python-packages:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#|   - pandas</span>
</span></span><span class="line"><span class="cl"><span class="c1">#|   - matplotlib</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| python-version: &#34;3.11&#34;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#| exclude-newer: &#34;2026-06-01&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">reticulate</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">pd</span> <span class="o">&lt;-</span> <span class="nf">import</span><span class="p">(</span><span class="s">&#34;pandas&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p><code>ir</code> creates the environment with <code>reticulate</code>&rsquo;s uv-backed environment
helper, sets <code>RETICULATE_PYTHON</code>, and activates the environment for
subprocesses. Declare <code>reticulate</code> under <code>packages</code> when the R script
loads <code>reticulate</code>.</p>
<p>For Quarto, put Python metadata under the document&rsquo;s <code>ir:</code> key. A knitr
document that uses reticulate can mix R and Python requirements:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">My report</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">ir</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">packages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">reticulate</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">python-packages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">pandas</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">exclude-newer</span><span class="p">:</span><span class="w"> </span><span class="ld">2025-01-01</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span></span></span></code></pre></div></div>
<p>For a Quarto document that uses the Jupyter engine, the <code>ir:</code> metadata
can contain only Python requirements:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nn">---</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">My notebook</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">jupyter</span><span class="p">:</span><span class="w"> </span><span class="l">python3</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">ir</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">python-packages</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">matplotlib</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="l">pandas</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">python-version</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;3.11&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nn">---</span></span></span></code></pre></div></div>
<p>For Quarto renders, <code>ir</code> injects <code>jupyter</code> into the Python environment,
passes the resolved interpreter to Quarto with <code>QUARTO_PYTHON</code>, and also
sets <code>RETICULATE_PYTHON</code> so documents that use reticulate see the same
interpreter. If the document uses the knitr engine and contains Python
chunks, <code>ir</code> automatically adds <code>reticulate</code> to the R package manifest.</p>
<p>When Python metadata is present, <code>exclude-newer</code> is also used for Python
environment resolution unless <code>python-exclude-newer</code> is set. Use
<code>python-exclude-newer</code> when Python packages should use a different
snapshot date from R packages.</p>
<h2 id="install-ir">Install <code>ir</code>
</h2>
<p>Install a pre-built binary on Linux or macOS:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">$</span> curl -fsSL https://raw.githubusercontent.com/r-lib/ir/main/scripts/install.sh <span class="p">|</span> sh
</span></span></code></pre></div></div>
<p>Install on Windows PowerShell:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-console" data-lang="console"><span class="line"><span class="cl"><span class="gp">&gt;</span> irm https://raw.githubusercontent.com/r-lib/ir/main/scripts/install.ps1 <span class="p">|</span> iex
</span></span></code></pre></div></div>
<p>The installers download the latest GitHub release and install both <code>ir</code>
and <code>rx</code>. You can also download release artifacts from the
<a href="https://github.com/r-lib/ir/releases" target="_blank" rel="noopener">GitHub releases</a> page or with
<code>gh release download</code>.</p>
<p>You will also need <code>R</code> / <code>Rscript</code>; <code>rig</code> is required when selecting R
by version or by date-only <code>exclude-newer</code>, and Quarto is required when
rendering Quarto sources.</p>
<h2 id="learn-more">Learn more
</h2>
<p>The project is open source under the MIT license. To get started:</p>
<ul>
<li>Read the documentation: <a href="https://r-lib.github.io/ir/" target="_blank" rel="noopener">https://r-lib.github.io/ir/</a></li>
<li>Browse the source: <a href="https://github.com/r-lib/ir" target="_blank" rel="noopener">https://github.com/r-lib/ir</a></li>
<li>Open an issue: <a href="https://github.com/r-lib/ir/issues" target="_blank" rel="noopener">https://github.com/r-lib/ir/issues</a></li>
</ul>
<p>This is a first public release, and feedback is especially useful now.
If you try <code>ir</code> on your own scripts or Quarto documents, we would like
to hear which workflows feel natural, where the metadata model needs
more room, and which command-line edges still need smoothing.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-23_ir-0-1-0/terrarium.png" length="2120322" type="image/png" />
    </item>
    <item>
      <title>Tips for managing your Python &amp; R environments in Positron</title>
      <link>https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/</link>
      <pubDate>Wed, 15 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/</guid>
      <dc:creator>Cindy Tong</dc:creator>
      <dc:creator>Brice Stacey</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is the Posit next-generation IDE for data science. Positron is an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>If you work across Python and R or juggle multiple projects with different dependency needs, environment management is probably one of the most challenging parts of your workflow. <a href="https://positron.posit.co/" target="_blank" rel="noopener">Positron</a> comes with several out-of-the-box ways to help simplify environment management. Here are a few tips to streamline your workflow.</p>
<h2 id="1-understand-how-positron-discovers-your-environment">1: Understand how Positron discovers your environment
</h2>
<p>Positron does not just look at your system PATH. For Python, it actively discovers venv, uv, pyenv, and conda environments. If Positron is not seeing your project&rsquo;s virtual environment, you can add custom search locations via the <a href="positron://settings/python.interpreters.include"><code>python.interpreters.include</code></a> setting, or trigger a manual rescan with <em>Interpreter: Discover All Interpreters</em>. Learn more about <a href="https://positron.posit.co/python-installations.html#python-installation-discovery" target="_blank" rel="noopener">Python discovery in the Positron documentation</a>.</p>
<p>For R, discovery works differently. Positron consults various sources to build the list of R interpreters. These include your PATH, R root folders based on specific operating systems, well-known executable locations, and on Windows the registry. You can customize your R discovery through a few settings including <a href="positron://settings/positron.r.customRootFolders"><code>positron.r.customRootFolders</code></a> and <a href="positron://settings/positron.r.customBinaries"><code>positron.r.customBinaries</code></a>. Learn more about <a href="https://positron.posit.co/r-installations.html#customizing-r-discovery" target="_blank" rel="noopener">R discovery in the Positron documentation</a>.</p>
<h2 id="2-use-the-interpreter-selector">2. Use the Interpreter Selector
</h2>
<p>To begin your first session, click &ldquo;Start Session&rdquo; in the top right corner and select your preferred R or Python interpreter. Positron can run multiple R and Python interpreter sessions at once, but only one is ever the active session at a given moment. The Interpreter Selector always shows you the active session and its status (idle, busy, or shut down) and you can use it to switch between or start additional sessions.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/active-interpreter-session.png" width="100%" data-fig-align="center" data-fig-alt="Use the Interpreter Selector to change your active session" />
<h2 id="3-install-python-with-uv">3. Install Python with uv
</h2>
<p>If Positron does not find a usable Python on your machine, it will offer to install Python via uv to help streamline your setup. If you prefer to manage the installation yourself, you can disable uv with the <a href="positron://settings/python.allowUvPythonInstall"><code>python.allowUvPythonInstall</code></a> setting. Check out our <a href="https://opensource.posit.co/blog/2026-07-08_positron-uv/" target="_blank" rel="noopener">blog post exploring on-demand Python installation in Positron</a> or explore configurations in our <a href="https://positron.posit.co/python-installations.html#troubleshooting" target="_blank" rel="noopener">Python installation documentation</a>.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/install-python-with-uv.png" width="100%" data-fig-align="center" data-fig-alt="Install Python via uv" />
<h2 id="4-manage-packages-with-the-packages-pane">4. Manage packages with the Packages Pane
</h2>
<p>The Packages Pane in Positron lets you manage the packages installed in your active session. Whether you use pip, uv, conda, pak, base R, or renv, you can browse installed packages and search package repositories. You can also track outdated packages and install, update, or uninstall packages without leaving Positron or writing any code.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/packages-pane.gif" width="100%"  data-fig-align="center" data-fig-alt="Manage Python and R packages in the Packages Pane" />
<h2 id="5-view-package-documentation">5. View package documentation
</h2>
<p>If you need to learn more about a specific package, the Packages Pane has buttons to navigate to the source documentation or link to the package&rsquo;s website. You can also pull up the <a href="https://positron.posit.co/help-pane.html" target="_blank" rel="noopener">Help Pane</a> for any reference in the Console using the <code>?</code> operator, including both packages and functions.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/packages-pane-help.png" width="100%" data-fig-align="center" data-fig-alt="View source documentation for specific packages in the Help Pane" />
<h2 id="6-start-projects-with-new-folder-from-template">6. Start projects with New Folder from Template
</h2>
<p>The New Folder from Template flow helps you start new projects faster. Instead of running multiple setup commands you can make a few selections and Positron helps you set up an environment directory, version control, directory structure, and an interpreter instance. Learn more about the <a href="https://positron.posit.co/folder-templates.html" target="_blank" rel="noopener">Python and R templates</a> available in the documentation.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/new-folder-from-template.png" width="100%" data-fig-align="center" data-fig-alt="New Folder from Template helps you start new projects faster with ready to use environments" />
<h2 id="7-make-use-of-the-command-palette">7. Make use of the Command Palette
</h2>
<p>We recommend taking advantage of the following commands when managing your environments:</p>
<ul>
<li><em>Interpreter: Discover All Interpreters</em>: Find new interpreters</li>
<li><em>Interpreter: Select Session</em>: Select a running interpreter session</li>
<li><em>Python: Create Environment</em>: Create a new virtual environment</li>
</ul>
<h2 id="8-let-posit-assistant-help-manage-your-environment">8. Let Posit Assistant help manage your environment
</h2>
<p><a href="https://assistant.posit.co/docs/features/context-management/" target="_blank" rel="noopener">Posit Assistant</a> has knowledge of your R and Python session including the language, version, names, and types of variables in your environment. You can prompt Posit Assistant to set up environments and troubleshoot issues you run into.</p>
<img src="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/assistant-installed-packages.png" width="100%" data-fig-align="center" data-fig-alt="Posit Assistant in Positron showing a summary of packages installed in the user's Python environment, organized by category." />
<p>Have an idea for how we can improve environment management in Positron? We would love to hear from you in a <a href="https://github.com/posit-dev/positron/discussions" target="_blank" rel="noopener">discussion on GitHub</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-15_positron-environment-management-tips/featured.svg" length="75593" type="image/svg&#43;xml" />
    </item>
    <item>
      <title>Positron July Release Highlights</title>
      <link>https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/</link>
      <pubDate>Mon, 13 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/</guid>
      <dc:creator>Julia Silge</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is Posit&rsquo;s new, next-generation IDE for data science. Positron is designed to be an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>Welcome back to another edition of our monthly Positron updates! Each month we share highlights from our <a href="https://positron.posit.co/release-notes" target="_blank" rel="noopener">latest release</a> and useful resources. <a href="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/" target="_blank" rel="noopener">Last release</a> we told you that several major features were on track to leave preview in July, and the time is now here! The new Notebook editor, the Packages pane, and Posit Assistant are all now generally available.</p>
<h2 id="positron-notebook-editor">Positron Notebook Editor
</h2>
<p>Positron&rsquo;s <a href="https://positron.posit.co/positron-notebook-editor" target="_blank" rel="noopener">new Notebook editor</a> is now the default experience for Jupyter (<code>.ipynb</code>) files. This release brings a long list of additions, including split-pane editing, cell tag management, and executing a line or selection within a cell with <kbd>Cmd/Ctrl+Shift+Enter</kbd>. You can export notebooks to Quarto, Python, or R, and inline PDF rendering makes it easier to work with generated output. If you are coming from JupyterLab, you will find familiar keyboard shortcuts, and we have improved output fidelity for Mermaid diagrams, htmlwidgets, and ipywidgets.</p>
<img src="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/positron-notebook-editor.png" data-fig-align="center" data-fig-alt="The Notebook editor in Positron, showing a Jupyter notebook with a Mermaid diagram." />
<p>Posit has built on and for the Jupyter ecosystem for years now, and we are excited to have recently <a href="https://opensource.posit.co/blog/2026-06-25_posit-joins-jupyter-foundation/" target="_blank" rel="noopener">joined the Jupyter Foundation</a>.</p>
<h2 id="packages-pane">Packages pane
</h2>
<p>The <a href="https://positron.posit.co/packages-pane" target="_blank" rel="noopener">Packages pane</a> has also come out of preview. It gives you a live view of the R and Python packages installed in your active session, so you can search, install, update, and remove packages, and jump to their documentation, without leaving Positron.</p>
<img src="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/packages-pane.gif" data-fig-align="center" data-fig-alt="The Packages pane in Positron, showing installed Python packages with a detail editor." />
<p>This release also makes the pane more capable. Clicking a package opens a detail editor with its overview, metadata, and actions, similar to the Extensions pane. Installing a package searches results as you type, and each row has a button that opens the package&rsquo;s website. Update indicators appear immediately on a new session from a cached snapshot, and <strong>Update All Packages</strong> reports exactly what changed. Package operations keep your environment consistent too, resolving against a workspace <code>requirements.txt</code> for Python and updating the <code>renv.lock</code> snapshot for R.</p>
<h2 id="posit-assistant">Posit Assistant
</h2>
<p><a href="https://pos.it/assistant" target="_blank" rel="noopener">Posit Assistant</a>, our unified, data-science-focused approach to AI assistance, is now out of preview and generally available; Posit Assistant fully replaces the legacy Positron Assistant. We know the names are similar, and Joe Cheng <a href="https://opensource.posit.co/blog/2026-06-11_history-of-posit-data-science-agents/" target="_blank" rel="noopener">recently walked through a bit of the story behind how our AI agent tools have evolved</a>.</p>
<p>This release also gives you finer control over AI in Positron. The new <a href="positron://settings/ai.enabled"><code>ai.enabled</code></a> setting turns off every Positron AI feature at once, and administrators can enforce it; <a href="positron://settings/notebook.ai.enabled"><code>notebook.ai.enabled</code></a> does the same for notebooks specifically. The set of language model providers keeps growing, with DeepSeek joining as an experimental provider and Microsoft Foundry reaching general availability. The configuration modal now shows every provider by default with preview and experimental badges.</p>
<h2 id="data-explorer">Data Explorer
</h2>
<p>The <a href="https://positron.posit.co/data-explorer" target="_blank" rel="noopener">Data Explorer</a> can now open Excel workbooks directly, with no code required, without first needing to load it via Python or R. Sort, filter, and profile columns, switch between worksheets, and toggle whether the first row holds column names. An <strong>Open in Excel</strong> button opens the workbook in your native spreadsheet application.</p>
<p>This release broadens what you can open in the Data Explorer overall. Backed by a native DuckDB engine, it now also previews compressed CSV, TSV, and Parquet files. Also, a new <strong>Open in Data Explorer</strong> code action, from the editor lightbulb or <kbd>Cmd+.</kbd>, opens the data frame under your cursor in R, Python, and Quarto files, so you can jump straight from your code to exploring your data.</p>
<img src="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/open-in-data-explorer-code-action.gif" data-fig-align="center" data-fig-alt="The Open in Data Explorer code action in Positron, showing a lightbulb menu in a Python file with the option to open a data frame in the Data Explorer." />
<h2 id="r-language-intelligence">R language intelligence
</h2>
<p>Last release we introduced within-file symbol resolution for R; this release expands it across files. Go to Definition, Find References, and Rename Symbol now work across the packages and scripts in your workspace, and diagnostics and workspace symbols react to external file changes so your language intelligence stays in sync as your project evolves.</p>
<h2 id="whats-coming-next">What&rsquo;s coming next
</h2>
<ul>
<li>New in preview this release, Data Connections lets you browse the schemas, tables, views, and indexes of a database, open tables in the Data Explorer, and generate connection code. It currently supports DuckDB, PostgreSQL, and SQLite. Learn how to try it out and tell us what you think in the <a href="https://github.com/posit-dev/positron/discussions/14571" target="_blank" rel="noopener">Data Connections discussion post</a>: which databases and warehouses you need, whether the connection setup is clear, and anything confusing, missing, or broken.</li>
<li>We are looking forward to posit::conf(2026) in September, where our team will have several sessions on Positron. <a href="https://conf.posit.co/2026/" target="_blank" rel="noopener">Register now</a> to join us in person in Houston or virtually from anywhere in the world.</li>
</ul>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Tip</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co/download" target="_blank" rel="noopener">Download Positron</a> to try out the new features and improvements in this release!</p>
</div>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-13_positron-2026-07-release/featured.svg" length="88493" type="image/svg&#43;xml" />
    </item>
    <item>
      <title>Positron &#43; uv: Python setup in one click</title>
      <link>https://opensource.posit.co/blog/2026-07-08_positron-uv/</link>
      <pubDate>Wed, 08 Jul 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-07-08_positron-uv/</guid>
      <dc:creator>Isabel Zimmerman</dc:creator><description><![CDATA[<p>The Python community has a long-standing joke about how hard it can be to get Python running on your machine.
The options can feel overwhelming, and the post-install result often looks like this:</p>
<img src="https://imgs.xkcd.com/comics/python_environment.png" data-fig-align="center" data-fig-alt="The xkcd 'Python Environment' comic showing a tangled web of arrows between dozens of conflicting Python installations on one machine" />
<p>If you&rsquo;re new to Python, Positron has a few built-in workflows to help you skip that installation pain entirely.
Most of it is powered by <a href="https://docs.astral.sh/uv/" target="_blank" rel="noopener">uv, created by Astral</a>.
We chose uv because it&rsquo;s fast and has quickly become widely adopted across the Python community.
Behind the scenes, uv handles the heavy lifting, downloading Python versions and building environments.
Positron wraps that power in a friendly interface, so you never have to remember a command.</p>
<h2 id="from-zero-to-python-in-one-click">From zero to Python in one click
</h2>
<p>If you don&rsquo;t have a suitable Python available, Positron will offer to install it for you.
When you go to start a Python runtime through the <strong>Start Session</strong> button, you&rsquo;ll see an option for <strong>+ Install Python via uv</strong>.</p>
<img src="https://opensource.posit.co/blog/2026-07-08_positron-uv/select-interpreter.png" data-fig-align="center" data-fig-alt="Positron's 'Start New Console Session' picker listing installed Python and R interpreters, with a highlighted '+ Install Python via uv' option at the bottom" />
<p>When you choose it, Positron will ask to install uv for you if you don&rsquo;t already have it.
Then it shows you the supported Python versions (currently 3.9 through 3.14) and installs whichever one you pick.
If you have a folder open, it will also offer to create a virtual environment for the project and start a Python console session using that environment.
Once you have a Python available, this option disappears from the session picker.</p>
<p>If you&rsquo;d rather not see the prompt to install Python, you can turn it off with the <a href="positron://settings/python.allowUvPythonInstall"><code>python.allowUvPythonInstall</code></a> setting (enabled by default).</p>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Why do I see this when I already have Python on my machine?</span>
</div>
<div class="callout-body">
<p>If you only have system Pythons available, you&rsquo;ll still see the <strong>+ Install Python via uv</strong> option.
<a href="https://pydevtools.com/handbook/explanation/why-should-i-avoid-system-python/" target="_blank" rel="noopener">Using system Python tends to cause problems down the road</a>, so Positron nudges you toward a managed Python and virtual environment instead.</p>
</div>
</div>
<h2 id="on-demand-python-installation">On-demand Python installation
</h2>
<p>You don&rsquo;t have to wait for Positron to ask, either.
Open the command palette with <kbd>Cmd/Ctrl+Shift+P</kbd> and run <em>Python: Install Python via uv</em> any time you&rsquo;d like another version.
This command will download uv if needed, show you the available Python versions, and optionally offer to create a virtual environment.</p>
<img src="https://opensource.posit.co/blog/2026-07-08_positron-uv/cmd-uv.png" data-fig-align="center" data-fig-alt="Positron's command palette with 'python install via' typed, showing the 'Python: Install Python via uv' command" />
<p>Since it runs on demand, it&rsquo;s a good way to add another interpreter to a project you&rsquo;ve already set up.
And because it follows the same flow every time, it works well for teaching, since everyone ends up in the same place, the same way.</p>
<h2 id="automatic-project-setup-support">Automatic project setup support
</h2>
<p>Setting up a virtual environment isn&rsquo;t limited to that first run.
If Positron finds a <code>pyproject.toml</code> or <code>requirements.txt</code> in a project that doesn&rsquo;t have a virtual environment yet, it&rsquo;ll offer to create one with uv and install your dependencies.
This comes in handy when you&rsquo;ve cloned a colleague&rsquo;s project and want to get up and running without extra setup.</p>
<img src="https://opensource.posit.co/blog/2026-07-08_positron-uv/requirements.png" data-fig-align="center" data-fig-alt="A Positron notification reading 'This project has requirements.txt but no virtual environment. Use uv to create a .venv and install these requirements?' with Yes, Not now, and Don't show again buttons" />
<p>If there&rsquo;s a single requirements source, like a lone <code>requirements.txt</code> or <code>pyproject.toml</code>, Positron will prompt you to create a <code>.venv</code> and install everything right away.
And if there are several sources, you can choose which files to include in your installation.</p>
<p>Positron also knows when to stay out of your way.
If you already have a local virtual environment, or if you use non-uv files like <code>environment.yml</code>, <code>Pipfile</code>, or <code>poetry.lock</code>, you won&rsquo;t get a prompt at all.</p>
<h2 id="try-it-yourself">Try it yourself
</h2>
<p>Setting up Python doesn&rsquo;t have to feel like a chore, whether you&rsquo;re starting on a brand new machine, picking up a colleague&rsquo;s project, or getting a room full of students ready to code.</p>
<p>Give it a try in <a href="https://positron.posit.co/download" target="_blank" rel="noopener">Positron&rsquo;s July 2026 release</a> and let us know what you think.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-07-08_positron-uv/image.png" length="386340" type="image/png" />
    </item>
    <item>
      <title>Air 0.10.0</title>
      <link>https://opensource.posit.co/blog/2026-06-26_air-0-10-0/</link>
      <pubDate>Fri, 26 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-26_air-0-10-0/</guid>
      <dc:creator>Davis Vaughan</dc:creator><description><![CDATA[<p>We&rsquo;re very excited to announce that Air 0.10.0 is out now!
<a href="https://github.com/posit-dev/air" target="_blank" rel="noopener">Air</a> is an extremely fast R code formatter, capable of styling entire projects in the blink of an eye.
We haven&rsquo;t done a release post in awhile, so this one will serve as a round up of everything from 0.8.2 to 0.10.0.</p>
<p>If you use <a href="https://positron.posit.co/" target="_blank" rel="noopener">Positron</a>, the Air Extension has probably already updated you to the new release!
Otherwise, follow one of Air&rsquo;s <a href="https://posit-dev.github.io/air/editors.html" target="_blank" rel="noopener">editor guides</a> to install the latest version for your editor.</p>
<p>To see every bug fix, check out our <a href="https://github.com/posit-dev/air/blob/main/CHANGELOG.md" target="_blank" rel="noopener">CLI changelog</a> and <a href="https://github.com/posit-dev/air/blob/main/editors/code/CHANGELOG.md" target="_blank" rel="noopener">extension changelog</a>.</p>
<h2 id="assignment-style-">Assignment style 😎
</h2>
<p>The headlining feature of 0.10.0 is a new <code>assignment-style</code> option, one of our <a href="https://github.com/posit-dev/air/issues/359" target="_blank" rel="noopener">top requests</a>!
This allows you to enforce <code>&lt;-</code> or <code>=</code> for assignment throughout your codebase.
Choose one of <code>&quot;arrow&quot;</code> for <code>&lt;-</code>, <code>&quot;equal&quot;</code> for <code>=</code>, or <code>&quot;preserve&quot;</code> to leave existing code as is.
For example, with <code>assignment-style = &quot;arrow&quot;</code>:</p>
<!-- panache-ignore-format-start -->
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># This</span>
</span></span><span class="line"><span class="cl"><span class="n">a</span> <span class="o">=</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="n">fn</span> <span class="o">=</span> <span class="kr">function</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">z</span> <span class="o">&lt;-</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span>
</span></span><span class="line"><span class="cl">  <span class="n">z</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Is standardized to</span>
</span></span><span class="line"><span class="cl"><span class="n">a</span> <span class="o">&lt;-</span> <span class="m">1</span>
</span></span><span class="line"><span class="cl"><span class="n">fn</span> <span class="o">&lt;-</span> <span class="kr">function</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="n">z</span> <span class="o">&lt;-</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span>
</span></span><span class="line"><span class="cl">  <span class="n">z</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<!-- panache-ignore-format-end -->
<p>This option defaults to enforcing <code>&lt;-</code> everywhere, which is what the <a href="https://style.tidyverse.org/syntax.html#assignment-1" target="_blank" rel="noopener">style guide</a> already recommended.
Note that this is a breaking change for Air, as the previous behavior didn&rsquo;t enforce any assignment style.
To revert to the old behavior, use <code>&quot;preserve&quot;</code>.</p>
<p>This feature is surprisingly sophisticated!
In R, you can always convert from <code>=</code> to <code>&lt;-</code> without changing the meaning of the code, but the reverse isn&rsquo;t true.
Consider the following:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">quote</span><span class="p">(</span><span class="n">x</span> <span class="o">&lt;-</span> <span class="m">1</span><span class="p">)</span></span></span></code></pre></div></div>
<p>This quotes the expression <code>x &lt;- 1</code> and returns it.
Blindly changing to <code>=</code> results in:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">quote</span><span class="p">(</span><span class="n">x</span> <span class="o">=</span> <span class="m">1</span><span class="p">)</span></span></span></code></pre></div></div>
<p>That&rsquo;s very different!
This sets an argument named <code>x</code> to <code>1</code>.
Trying to run this will result in an error since <code>quote()</code> doesn&rsquo;t have an <code>x</code> argument.
Air knows every case where <code>&lt;-</code> is allowed but <code>=</code> isn&rsquo;t, so you can rest assured that this option won&rsquo;t impact the meaning of your code in any way.</p>
<h2 id="positron-terminal-support">Positron terminal support
</h2>
<p>Positron&rsquo;s integration with Air has gotten even better!
When the Air extension activates, it now prepends the bundled Air executable to your integrated terminal&rsquo;s <code>PATH</code>.
This means:</p>
<ul>
<li>You can now run Air commands from Positron&rsquo;s terminal, even without installing the CLI separately.</li>
<li>Your terminal version of Air should now always match the version used by the editor itself.</li>
</ul>
<div class="w-full aspect-video">
      <video
        src="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/assets/which-air.mp4"
        class="w-full h-full object-contain"
        title="Air in your Positron terminal"
        controls></video>
    </div>
<p>This is particularly useful with agents like Claude Code, where you&rsquo;ll often want to tell your agent to run Air on any R files it touches (see <code>usethis::use_tidy_agents()</code> in the development version of usethis for a way to automatically add this to an <code>AGENTS.md</code>).</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/assets/air-claude.png"
      alt="Claude Code finding Air" 
      loading="lazy"
    >
  </figure></div>
</p>
<p>If you&rsquo;ve already installed the Air CLI separately, note that the bundled version will take precedence in Positron&rsquo;s terminals by default.
To force both the editor and the integrated terminal to use your externally installed version of Air, set <code>air.executableStrategy: &quot;environment&quot;</code>.
To opt-out of terminal support, set <code>air.addExecutableToTerminalPath: false</code>.</p>
<h2 id="rstudio-improvements">RStudio improvements
</h2>
<p>Thanks to <a href="https://github.com/kevinushey" target="_blank" rel="noopener">Kevin Ushey</a>, RStudio&rsquo;s support for Air is improving rapidly!</p>
<blockquote>
<p>Note that you&rsquo;ll need RStudio 2026.06.0 for this, which isn&rsquo;t out yet, but should be by the end of next week.</p>
</blockquote>
<p>RStudio now has &ldquo;native&rdquo; support for Air, rather than just being configurable as an External Formatter.
If you opt-in to Air, RStudio will now automatically download the latest version of the <code>air</code> binary the first time you save a file, making the whole experience much smoother.</p>
<div class="w-full aspect-video">
      <video
        src="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/assets/air-download.mp4"
        class="w-full h-full object-contain"
        title="Air auto downloading in RStudio"
        controls></video>
    </div>
<p>You can turn this on via <code>Tools -&gt; Global Options... -&gt; Code -&gt; Formatting</code>.
I recommend checking both <code>Use Air for code formatting</code> and <code>Reformat documents on save</code>.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/assets/air-rstudio-settings.png"
      alt="RStudio settings for Air" 
      loading="lazy"
    >
  </figure></div>
</p>
<h2 id="lionstigersandbears-uv-pixi-and-mise-oh-my"><del>Lions, tigers, and bears</del> uv, pixi, and mise, oh my!
</h2>
<p>Air now lives on PyPi as <a href="https://pypi.org/project/air-formatter/" target="_blank" rel="noopener"><code>air-formatter</code></a> (sadly <code>air</code> was already taken), which means that you can now install Air&rsquo;s CLI via <a href="https://github.com/astral-sh/uv" target="_blank" rel="noopener">uv</a>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Install globally</span>
</span></span><span class="line"><span class="cl">uv tool install air-formatter
</span></span><span class="line"><span class="cl">air format path/to/file.R
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run one-off command</span>
</span></span><span class="line"><span class="cl">uvx --from air-formatter air format path/to/file.R</span></span></code></pre></div></div>
<p>It might sound a little absurd to put an R code formatting tool on Python&rsquo;s PyPI, but in a world where an increasingly large number of R users are also uv users, it&rsquo;s pretty darn convenient to be able to invoke it this way!</p>
<p>Additionally, Air is on <a href="https://github.com/conda-forge/air-feedstock" target="_blank" rel="noopener">conda-forge</a> thanks to <a href="https://github.com/salim-b" target="_blank" rel="noopener"><code>@salim-b</code></a>, which means you can also install it with <a href="https://pixi.prefix.dev/latest/" target="_blank" rel="noopener">pixi</a> and <a href="https://mise.jdx.dev/" target="_blank" rel="noopener">mise</a>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl"><span class="c1"># Add to a project</span>
</span></span><span class="line"><span class="cl">pixi add air
</span></span><span class="line"><span class="cl">mise use conda:air
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Install globally</span>
</span></span><span class="line"><span class="cl">pixi global install air
</span></span><span class="line"><span class="cl">mise use --global conda:air
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Run one-off command</span>
</span></span><span class="line"><span class="cl">pixi <span class="nb">exec</span> air format path/to/my/script.R
</span></span><span class="line"><span class="cl">mise <span class="nb">exec</span> conda:air -- air format path/to/my/script.R</span></span></code></pre></div></div>
<h2 id="pre-commit-support">pre-commit support
</h2>
<p>Air now has <a href="https://pre-commit.com/" target="_blank" rel="noopener">pre-commit</a> and <a href="https://prek.j178.dev/" target="_blank" rel="noopener">prek</a> support via <a href="https://github.com/posit-dev/air-pre-commit" target="_blank" rel="noopener">posit-dev/air-pre-commit</a>.
To run Air on changed R files before every commit, add the following to your <code>.pre-commit-config.yaml</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">repos</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl">- <span class="nt">repo</span><span class="p">:</span><span class="w"> </span><span class="l">https://github.com/posit-dev/air-pre-commit</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="c"># Air version</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">rev</span><span class="p">:</span><span class="w"> </span><span class="m">0.10.0</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">hooks</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="c"># Run the formatter</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">id</span><span class="p">:</span><span class="w"> </span><span class="l">air-format</span></span></span></code></pre></div></div>
<h2 id="stdin-support">stdin support
</h2>
<p>Air&rsquo;s CLI now has <a href="https://posit-dev.github.io/air/cli.html#stdin" target="_blank" rel="noopener">stdin support</a>!
You can activate this with the new <code>--stdin-file-path</code> option like so:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">cat R/across.R <span class="p">|</span> air format --stdin-file-path R/across.R</span></span></code></pre></div></div>
<p><code>air</code> will receive the contents of <code>cat R/across.R</code> on stdin, format it, and then emit the formatted result back out on stdout.</p>
<p>You typically won&rsquo;t call this directly, but it&rsquo;s very useful for editors and IDEs without full language server support.
For example, with <a href="https://zed.dev/" target="_blank" rel="noopener">Zed</a> you can configure Air as an external formatter with the following setup:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-json" data-lang="json"><span class="line"><span class="cl"><span class="s2">&#34;formatter&#34;</span><span class="err">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&#34;external&#34;</span><span class="p">:</span> <span class="p">{</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;command&#34;</span><span class="p">:</span> <span class="s2">&#34;air&#34;</span><span class="p">,</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&#34;arguments&#34;</span><span class="p">:</span> <span class="p">[</span><span class="s2">&#34;format&#34;</span><span class="p">,</span> <span class="s2">&#34;--stdin-file-path&#34;</span><span class="p">,</span> <span class="s2">&#34;{buffer_path}&#34;</span><span class="p">]</span>
</span></span><span class="line"><span class="cl">  <span class="p">}</span>
</span></span><span class="line"><span class="cl"><span class="p">}</span></span></span></code></pre></div></div>
<p>This allows Zed to invoke Air on every file save.</p>
<blockquote>
<p>Note that Zed does have full language server support, and it&rsquo;s recommended to use our <a href="https://posit-dev.github.io/air/editor-zed.html" target="_blank" rel="noopener">Air extension for Zed</a> instead, but it makes for a nice example anyways!</p>
</blockquote>
<p>Invoking Air via stdin can also be useful in some embedded contexts, such as inside <a href="https://github.com/eitsupi/arf" target="_blank" rel="noopener">arf</a>, an R console.</p>
<h2 id="shell-completions">Shell completions
</h2>
<p>Thanks to <a href="https://github.com/salim-b" target="_blank" rel="noopener"><code>@salim-b</code></a>, Air can now generate shell completions for itself via a new <code>air generate-shell-completion &lt;shell&gt;</code> command, where <code>&lt;shell&gt;</code> can be one of <code>zsh</code>, <code>bash</code>, <code>powershell</code>, <code>fish</code>, or <code>elvish</code>.
Read the <a href="https://posit-dev.github.io/air/cli.html#shell-completions" target="_blank" rel="noopener">documentation</a> for how to set this up for your specific shell.
With shell completions set up, you can <code>Tab</code> your way through <code>air</code> commands at the command line:</p>
<div class="w-full aspect-video">
      <video
        src="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/assets/air-completions.mp4"
        class="w-full h-full object-contain"
        title="Air shell completions"
        controls></video>
    </div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-26_air-0-10-0/featured.jpg" length="638589" type="image/jpeg" />
    </item>
    <item>
      <title>Introducing debrief: profiling summaries for AI agents</title>
      <link>https://opensource.posit.co/blog/2026-06-22_debrief-0-1-0/</link>
      <pubDate>Mon, 22 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-22_debrief-0-1-0/</guid>
      <dc:creator>Emil Hvitfeldt</dc:creator><description><![CDATA[<p>While AI agents are getting better at writing code,
they sometimes struggle with making the code faster.
We have a number of tools at our disposal to do this,
but some of them are not made with AI agents in mind.
The <a href="https://profvis.r-lib.org/" target="_blank" rel="noopener">profvis</a> package is one such example.
The interactive flame graph it produces is fantastic for human eyes,
it allows us to easily see the broad picture while also drilling down into the details.
This is what started the idea for this new package; debrief.
Allowing the AI agent to read the profvis results as text,
thus letting it make more informed decisions about how to optimize the code.</p>
<p>debrief is on CRAN:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="s">&#34;debrief&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1"># or the development version:</span>
</span></span><span class="line"><span class="cl"><span class="n">pak</span><span class="o">::</span><span class="nf">pak</span><span class="p">(</span><span class="s">&#34;r-lib/debrief&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<h2 id="from-guessing-to-measuring">From guessing to measuring
</h2>
<p>Prompt an AI agent to make your R code faster,
and it will either declare that a bottleneck exists without evidence,
or try to use <code>Rprof()</code> directly.
We find that <code>profvis()</code> is better and easier to use than <code>Rprof()</code> directly,
as it provides a more user-friendly interface and visualizations.
But that interface is designed for human eyes,
not for an LLM reading a transcript.</p>
<p>This is where debrief comes in.
It turns the output of <code>profvis()</code> into a text-based summary that can be easily read and understood by AI agents.
Every function is prefixed <code>pv_</code> (for profvis) and returns text designed to be read top to bottom:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">debrief</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">p</span> <span class="o">&lt;-</span> <span class="n">profvis</span><span class="o">::</span><span class="nf">profvis</span><span class="p">(</span><span class="nf">slow_function</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="nf">pv_print_debrief</span><span class="p">(</span><span class="n">p</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ## PROFILING SUMMARY</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Total time: 190 ms (19 samples @ 10 ms interval)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; Source references: available</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### TOP FUNCTIONS BY SELF-TIME</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    110 ms ( 57.9%)  paste</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     20 ms ( 10.5%)  &lt;GC&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  .bincode</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  any</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  anyDuplicated.default</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  apply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  rnorm</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  unlist</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### TOP FUNCTIONS BY TOTAL TIME</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    180 ms ( 94.7%)  FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    180 ms ( 94.7%)  lapply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    180 ms ( 94.7%)  process_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    140 ms ( 73.7%)  summarize_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    110 ms ( 57.9%)  paste</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     30 ms ( 15.8%)  clean_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     20 ms ( 10.5%)  &lt;GC&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     20 ms ( 10.5%)  apply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  .bincode</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  [.data.frame</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### HOT LINES (by self-time)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    120 ms ( 63.2%)  analysis.R:22</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                    list(</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     10 ms (  5.3%)  analysis.R:9</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;                    x &lt;- rnorm(n)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### HOT CALL PATHS</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 110 ms (57.9%) - 11 samples:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     lapply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; process_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; summarize_data (analysis.R:5)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; paste (analysis.R:22)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 10 ms (5.3%) - 1 samples:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     base::tryCatch</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; any</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 10 ms (5.3%) - 1 samples:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     lapply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; process_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; clean_data (analysis.R:4)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; [.data.frame (analysis.R:15)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; anyDuplicated.default</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 10 ms (5.3%) - 1 samples:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     lapply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; process_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; clean_data (analysis.R:4)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; cut.default</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; .bincode</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; 10 ms (5.3%) - 1 samples:</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     lapply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; process_data</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; clean_data (analysis.R:4)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; scale.default</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; apply</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; FUN</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;   -&gt; &lt;GC&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### MEMORY ALLOCATION (by function)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    51.28 MB paste</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    10.01 MB anyDuplicated.default</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     2.90 MB any</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     1.62 MB rnorm</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### MEMORY ALLOCATION (by line)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;    51.28 MB analysis.R:22</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;             list(</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;     1.62 MB analysis.R:9</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;             x &lt;- rnorm(n)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt;</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; ### Next steps</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; pv_focus(p, &#34;paste&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; pv_source_context(p, &#34;analysis.R&#34;)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; pv_suggestions(p)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; pv_help()</span></span></span></code></pre></div></div>
<p><code>pv_print_debrief()</code> is meant as the first entry point after profiling.
It gives a comprehensive overview of the profile with a number of sections.</p>
<ul>
<li>Top functions by self-time and total time</li>
<li>Hot lines and hot call paths</li>
<li>Memory allocation by function and by line</li>
<li>Next steps</li>
</ul>
<p>Both the hot lines and hot paths sections link back to source references when available,
Especially relevant if the bottleneck is happening in a common function name like <code>paste()</code> that is scattered across your package.
With this, we know exactly which <code>paste()</code> call is the culprit.</p>
<p>Each function in {debrief} also prints the name of the possible next functions to run,
so the assistant can drill down into the details.</p>
<p>debrief is a new package.
Its output almost certainly isn&rsquo;t part of any model&rsquo;s training data.
And output is also very unlikely to be added,
as profiling code is rarely committed.
This means that the package tries to be self-documenting by design,
With the hope that an AI agent doesn&rsquo;t need to read the documentation to understand how to use it.</p>
<h2 id="getting-an-agent-into-the-session">Getting an agent into the session
</h2>
<p>While you can use debrief with any AI agent,
it works better with agents that keep a persistent R session.
Otherwise, it will need to rerun the profile every time it wants to check the results of an edit.
You also won&rsquo;t be able to take full advantage of the &ldquo;Next steps&rdquo; hints,
which are designed to guide the agent through drilling down into the profile.</p>
<p><a href="https://posit.com/assistant" target="_blank" rel="noopener">Posit Assistant</a> is the easiest way to do this.
It runs in the same session as you, so you can be a bigger part of the process.
The same assistant runs in Positron, RStudio, or the terminal,
so you can profile interactively or drive the whole loop headlessly from the <code>pa</code> CLI.</p>
<p>If you aren&rsquo;t using RStudio or Positron, or prefer an agent like Claude Code or Codex,
<a href="https://github.com/posit-dev/mcp-repl" target="_blank" rel="noopener">mcp-repl</a> gives you the same thing.
It&rsquo;s an MCP server that hands any MCP-capable agent a persistent R (or Python) session,
so you can load debrief and run the profiler once, then drill down as needed.</p>
<h2 id="trying-it-out">Trying it out
</h2>
<p>The point of debrief is to put profiling results in a form an agent can easily interact with,
so it can measure instead of guess and iterate toward faster code.
We have already used it this way:
<a href="https://github.com/tidymodels/textrecipes/pull/309" target="_blank" rel="noopener">tidymodels/textrecipes#309</a>
is a profiling-driven optimization of <code>step_word_embeddings()</code>,
where each round of profile, read, change, and re-measure was guided by debrief&rsquo;s summaries.</p>
<p>Install debrief, point an agent at a slow function, and let it profile.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-22_debrief-0-1-0/featured.png" length="332953" type="image/png" />
    </item>
    <item>
      <title>RStudio&#39;s Top Feature Requests ... In Positron</title>
      <link>https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/</link>
      <pubDate>Wed, 10 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/</guid>
      <dc:creator>Jonathan McPherson</dc:creator><description><![CDATA[<p>One of the most fun aspects of developing software in the open is that you don&rsquo;t have to guess what your users want; you can just let them tell you. For over a decade, <a href="https://github.com/rstudio/rstudio/issues" target="_blank" rel="noopener">RStudio&rsquo;s issue tracker</a> has been public, available for anyone to report problems or request enhancements. And you, the community, have delivered, collectively filing thousands of bug reports and feature ideas.</p>
<p>One of the <em>least</em> fun aspects, though, is that you never have time to do most (or even <em>half</em>) of the great ideas that bubble up through the community. RStudio&rsquo;s issue tracker is full of worthy requests that have been unimplemented for years, often because they would require outsized changes to the core system&rsquo;s architecture or behavior.</p>
<p>When we set out to make Positron, RStudio&rsquo;s issue tracker became a gold mine of possibilities. Because Positron has a new architecture and is built on a different platform, many of the things that we&rsquo;d never been able to do in RStudio suddenly became possible.</p>
<p>Today, we take a look at 10 of the most upvoted RStudio feature requests of all time and how those features have been incorporated into Positron. Every one of these 10 requests is on the <a href="https://github.com/rstudio/rstudio/issues?q=is%3Aissue%20state%3Aopen%20sort%3Areactions-%2B1-desc" target="_blank" rel="noopener">front page of the most-upvoted RStudio issues</a> as of this writing.</p>
<h2 id="1-fully-configurable-pane-layouts">#1: Fully configurable pane layouts
</h2>
<blockquote>
<p>Most IDEs have a somewhat more flexible system which allows for arbitrary tab arrangement, often using mouse gestures to edit configuration on the fly.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/2879" target="_blank" rel="noopener">2879</a></p>
<p>This has been one of RStudio&rsquo;s most-upvoted requests for years. In Positron, this feature is largely inherited from upstream Code OSS (the open source core of VS Code). It has a very flexible layout system which allows tabs to be rearranged, split, hidden, and more with natural dragging and mouse gestures, or via context menus. Try right-clicking on any tab or divider to show places you can move it:</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/01-pane-layout.png" height="1180" width="898" alt="Customizable pane layout in Positron" />
<p>Read more about it here:</p>
<p><a href="https://code.visualstudio.com/docs/configure/custom-layout" target="_blank" rel="noopener">VS Code Layout Configuration</a></p>
<p>Positron also ships with a variety of layout presets customized for specific tasks. Try the <em>View: Stacked Layout</em> command to make it look more like RStudio, or <em>View: Notebook Layout</em> to focus on a Quarto document or Jupyter Notebook.</p>
<h2 id="2-fixed-size-graphics-device">#2: Fixed size graphics device
</h2>
<blockquote>
<p>It would be excellent if one could set the RStudio graphics device to generate plots as they would be generated by a non-interactive device with fixed dimensions, so that one could do iterative plot-making within the IDE rather than call <code>png()</code>, look at the outputs in another program, and then fiddle with font scaling, etc. and repeat.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/4422" target="_blank" rel="noopener">4422</a></p>
<p>RStudio always draws plots to fit the exact dimensions of its Plots pane, which can make it difficult to iterate on a plot you&rsquo;re preparing for publication.</p>
<p>When we added a Plots pane to Positron, we included a new tool that lets you indicate the dimensions at which you&rsquo;d like the plot to draw. You can have it fit to some common aspect ratios or &ndash; as in the RStudio feature request &ndash; specify the exact size at which you&rsquo;d like the plot to be drawn.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/02-plot-size.png" height="1016" width="1026" alt="The Plots pane with a custom size dialog" />
<p>Positron&rsquo;s Plots pane also features a visual history browser, and it remembers the code used to create the plot so you can jump to it or re-run it.</p>
<h2 id="3-multi-line-statement-support-for-python-code">#3: Multi-line statement support for Python code
</h2>
<blockquote>
<p>It would be really great to have RStudio support of multi-line statements extended to Python code as well.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/9014" target="_blank" rel="noopener">9014</a></p>
<p>One of RStudio&rsquo;s key R features is multi-line statement detection. When you tell RStudio to execute R code at your cursor location, it doesn&rsquo;t just send the current line of code; it gathers all the lines that are part of the statement and sends them together. RStudio doesn&rsquo;t know how to do this for Python, however.</p>
<p>In Positron, Python and R are peers, and we&rsquo;ve implemented multi-line statement detection for Python, too. Placing your cursor anywhere in a Python statement and invoking Run will execute the whole statement.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/03-python-multi-line.png" height="1130" width="1280" alt="A multi-line Python statement" />
<h2 id="4-tree-view-for-browsing-files-and-directories">#4: Tree view for browsing files and directories
</h2>
<blockquote>
<p>Hi, is there any plan for browsing files and directories in tree view mode?</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/2552" target="_blank" rel="noopener">2552</a></p>
<p>RStudio&rsquo;s file browser lists only one directory at a time. Positron inherits Code OSS&rsquo;s tree-based explorer, which not only shows files and directories in context but even has <a href="https://code.visualstudio.com/docs/configure/themes#_file-icon-themes" target="_blank" rel="noopener">customizeable icons</a>!</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/04-file-explorer.png" alt="A tree-view file explorer" />
<h2 id="5-view-different-parts-of-a-source-file-at-the-same-time">#5: View different parts of a source file at the same time
</h2>
<blockquote>
<p>In TexShop there’s an option to split source which allows you to see (and edit) multiple areas in a document in one window so that you don’t need to keep scrolling up and down. It would be wonderful to have this as a functionality in the RStudio IDE editor</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/2129" target="_blank" rel="noopener">2129</a></p>
<p>In Positron, you can have several editors open at once against the same file. With a file open, try using the <em>Split Right</em> or <em>Split Down</em> commands, which will create a second tab for the file that can be scrolled independently. You can access this via the Command Palette, or via the editor action toolbar:</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/05-split-source-file.png" alt="A single R script open in two different editor tabs" />
<h2 id="6-add-builds-for-windows-on-arm">#6: Add builds for Windows on ARM
</h2>
<blockquote>
<p>RStudio is built for Windows only for Intel (x86) processors. However, there is now a version of Windows 11 available for ARM based processors.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/11977" target="_blank" rel="noopener">11977</a></p>
<p>Microsoft has taken a page from Apple&rsquo;s playbook and started producing its own ARM-based hardware, such as the <a href="https://learn.microsoft.com/en-us/surface/surface-arm-faq" target="_blank" rel="noopener">ARM-based Surface devices</a>, and ARM-based Windows PCs are also now available from third-party manufacturers.</p>
<p>When we built Positron, we designed its native components to be easy to recompile against different CPU architectures. On Windows, Positron includes both x64 and arm64 R computation engines (kernels), so it can run natively with both x64 and arm64 versions of R. Its Python support, likewise, works great on ARM-based PCs.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/06-windows-arm.png" alt="R aarch64 running natively in Positron" />
<h2 id="7-persistent-julia-engine">#7: Persistent Julia engine
</h2>
<blockquote>
<p>It would be useful if we can use Julia in RStudio seamlessly as well as Python.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/1798" target="_blank" rel="noopener">1798</a></p>
<p>One of the main architectural goals of Positron was to make it adaptable to any language. It is a data science platform that supports &ldquo;language extensions&rdquo;. Positron&rsquo;s Python and R support subsystems are included in your Positron download, but are implemented as extensions.</p>
<p>While Posit doesn&rsquo;t currently have the expertise to develop well-rounded support for Julia, that hasn&rsquo;t stopped the community from creating a Positron extension that you can install to add Julia support. When installed, Julia becomes a peer of R and Python in Positron, and connects to Positron&rsquo;s Console, Variables, History, Packages, and Plots panes.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/07-julia-support.png" alt="Positron running a Julia session" />
<p>Find it here: <a href="https://open-vsx.org/extension/ntluong95/positron-julia" target="_blank" rel="noopener">Julia for Positron</a></p>
<p>One of the questions we get a lot is why Positron itself isn&rsquo;t a VS Code extension. This is one of the reasons why: Positron isn&rsquo;t an extension; it <em>has</em> extensions.</p>
<h2 id="8-teachingpresentation-mode">#8: Teaching/presentation mode
</h2>
<blockquote>
<p>Quite a lot of people use RStudio hooked up to a projector to teach others. It would be great if those people had a key combo that could toggle between their current theme settings and some good defaults suitable for teaching/demos. &hellip; Perhaps the ultimate goal would be to have the teaching mode itself configurable.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/4276" target="_blank" rel="noopener">4276</a></p>
<p>In Positron, it is possible to define different &ldquo;profiles&rdquo; that have different themes, font sizes, and other settings, and switch between them from the Command Palette, so it is possible set up a &ldquo;Presentation&rdquo; profile with a high-contrast theme and large text.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/08-presentation-mode.png" alt="Positron's Profile editor showing a Presentation mode" />
<p>Profiles go beyond just layouts and themes. More documentation here:</p>
<p><a href="https://code.visualstudio.com/docs/configure/profiles" target="_blank" rel="noopener">https://code.visualstudio.com/docs/configure/profiles</a></p>
<h2 id="9-fix-columns-or-rows-when-scrolling-data">#9: Fix columns or rows when scrolling data
</h2>
<blockquote>
<p>The data viewer is one of the most used tool of data analysis in RStudio. However, a very useful feature is missing. In horizontal scrolling a big table with lots of column (it&rsquo;s very common to have more columns than one screen can fit), it will be very helpful to fix the row name column so that it&rsquo;s always visible(i.e. always keep it within screen with scrolling). &hellip; More generally, sometimes we also want to fix certain columns so that it remains visible when scrolled horizontally. The row name column is just a special case of this request.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/3463" target="_blank" rel="noopener">3463</a></p>
<p>It&rsquo;s pretty common to have a column of data that represents a key value or name for the observation, or a value to which you wish to compare other values. In Positron, you can pin <em>any</em> column to fix it to the left, so that it is always visible as you scroll the other columns horizontally. Click on the column&rsquo;s action menu (vertical ellipsis) and choose <em>Pin Column</em>.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/09-pin-column.png" alt="Positron's Data Explorer with a column pinned" />
<p>You can also pin rows! Right-click on the row and choose <em>Pin Row</em>.</p>
<h2 id="10-remove-all-objects-in-the-python-environment">#10: Remove all objects in the Python environment
</h2>
<blockquote>
<p>Normally, we can do it easily in R environment by just 1 click but Python doesn&rsquo;t support it.</p>
</blockquote>
<p>&ndash; #<a href="https://github.com/rstudio/rstudio/issues/8750" target="_blank" rel="noopener">8750</a></p>
<p>It&rsquo;s supported in Positron! With a Python session open in the Console, you can use the trash icon to clear all the objects from the session &ndash; in one click.</p>
<img src="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/10-python-delete-all.png" alt="The Delete All Objects feature in a Python session" />
<h2 id="-and-a-lot-more">&hellip; And a lot more.
</h2>
<p>Some of our other favorite Positron-only features that don&rsquo;t have a top-upvoted RStudio issue attached:</p>
<ul>
<li><strong>Multiple R sessions</strong>: RStudio runs only one primary R session at a time; any concurrent work needs to be done in a non-interactive background job. Positron supports multiple concurrent interactive R sessions.</li>
<li><strong>Multiple R versions</strong>: RStudio can only work with one R version at a time, and you need to switch externally using a tool like <a href="https://github.com/r-lib/rig" target="_blank" rel="noopener">rig</a>. Positron will let you choose from any R installation on your system and can even associate specific R versions with specific projects.</li>
<li><strong>Crash recovery</strong>: RStudio crashes when R does, but in Positron all you&rsquo;ll lose is the R session itself, which just gets safely restarted.</li>
<li><strong>Remote sessions</strong>: Connect remotely to another computer over SSH and run R sessions inside it, or work with reproducible projects inside <a href="https://containers.dev/" target="_blank" rel="noopener">devcontainers</a>.</li>
</ul>
<p>See <a href="https://positron.posit.co/migrate-rstudio-compare.html" target="_blank" rel="noopener">Comparing RStudio and Positron Features</a> for more.</p>
<h2 id="what-about-rstudio">What about RStudio?
</h2>
<p>While Positron&rsquo;s design has made it more practical to make many of these advancements, the point of this post isn&rsquo;t that you should switch to Positron if you&rsquo;re happy in RStudio. In fact, features are also flowing in the other direction; many of Positron&rsquo;s features are making their way back into RStudio. For example, the latest release of RStudio has:</p>
<ul>
<li>a redesigned data viewer inspired by Positron&rsquo;s data viewer;</li>
<li>code formatting optionally <a href="https://posit-dev.github.io/air/editor-rstudio.html" target="_blank" rel="noopener">powered by air</a>, Positron&rsquo;s R formatter;</li>
<li>warning/error styling in the R console inspired by Positron&rsquo;s Console; and, of course</li>
<li><a href="https://posit-dev.github.io/assistant/" target="_blank" rel="noopener">Posit Assistant</a>, a platform-agnostic data analysis assistant that works in both IDEs.</li>
</ul>
<p>If you use RStudio and are interested in trying Positron, a good place to start is our <a href="https://positron.posit.co/migrate-rstudio.html" target="_blank" rel="noopener">Migrating from RStudio guide</a>.</p>
<p>Thank you for the creativity, ideas, and support you&rsquo;ve shown our IDEs over the years. Keep it coming!</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-10_rstudios-top-features-in-positron/header.png" length="111055" type="image/png" />
    </item>
    <item>
      <title>Positron June Release Highlights</title>
      <link>https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/</link>
      <pubDate>Mon, 08 Jun 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/</guid>
      <dc:creator>Julia Silge</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is Posit&rsquo;s new, next-generation IDE for data science. Positron is designed to be an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>Welcome back to another edition of our monthly Positron updates! Each month we share highlights from our <a href="https://positron.posit.co/release-notes" target="_blank" rel="noopener">latest release</a> and useful resources.</p>
<h2 id="posit-assistant">Posit Assistant
</h2>
<p><a href="https://opensource.posit.co/blog/2026-05-11_positron-2026-05-release/" target="_blank" rel="noopener">Last release</a> we introduced <a href="https://pos.it/assistant" target="_blank" rel="noopener">Posit Assistant</a>, our unified, data-science-focused approach to AI assistance. This release continues that work, and we want to give you advance notice that the older Positron Assistant will be deprecated in the next release. If you are still using Positron Assistant, we encourage you to migrate to Posit Assistant now.</p>
<img src="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/posit-assistant.gif" data-fig-align="center" data-fig-alt="Posit Assistant in Positron, Posit&#39;s unified data-science AI assistant, which is replacing the older Positron Assistant." />
<div class="callout callout-important" role="note" aria-label="Important">
<div class="callout-header">
<span class="callout-title">Important</span>
</div>
<div class="callout-body">
<p>During this migration period, you will likely want to keep both the old <a href="positron://settings/positron.assistant.enable"><code>positron.assistant.enable</code></a> setting and the new <a href="positron://settings/assistant.enabled"><code>assistant.enabled</code></a> set to true for best functionality, but do be aware that the older setting is in the process of being deprecated.</p>
</div>
</div>
<p>Posit Assistant supports the same broad set of providers as Positron Assistant, along with the Posit AI model provider and new experimental support for Google Vertex. Learn more about the differences between the new Posit Assistant and the older Positron Assistant:</p>
<div class="w-full aspect-video">
      <iframe
        src="https://www.youtube.com/embed/Y9P2nlFXKnQ"
        class="w-full h-full"
        
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowfullscreen></iframe>
    </div>
<p>You may also be interested in <a href="https://youtu.be/7GI6-4J0AXA" target="_blank" rel="noopener">comparing Posit Assistant and Claude Code</a>.</p>
<p>New in Posit Assistant this release, you can use the Posit AI model provider for Next Edit Suggestions, which propose your likely next change as you edit. You can also access the <strong>Configure Language Model Providers</strong> item in the accounts menu. You can <a href="https://github.com/posit-dev/assistant-feedback" target="_blank" rel="noopener">ask a question, report a bug, or request a new feature</a> for Posit Assistant separately from Positron now, but don&rsquo;t worry too much about where to send your feedback; we will help route it to the right place!</p>
<h2 id="packages-pane-improvements">Packages pane improvements
</h2>
<p>We introduced the <a href="https://positron.posit.co/packages-pane" target="_blank" rel="noopener">Packages</a> pane last release, and this release makes it more informative and flexible. A <strong>Show Help</strong> button and context menu entry on every package take you straight to its documentation in the <a href="https://positron.posit.co/help-pane" target="_blank" rel="noopener">Help pane</a>. You can now combine category filters as an intersection, so you can narrow the list to, for example, packages that are both attached and outdated. A new <strong>Item Size</strong> toggle lets you switch between a compact row view and a richer card view that surfaces package descriptions and other metadata.</p>
<img src="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/packages-pane.gif" data-fig-align="center" data-fig-alt="The Positron Packages pane showing the attached category filter, the Item Size toggle switching between compact rows and a card view with package descriptions, and how to update a package." />
<p>For R users, the new <a href="positron://settings/packages.r.installer"><code>packages.r.installer</code></a> setting controls whether installs, updates, and removals use pak, base R, or an automatic choice. The setting that controls the pane has been renamed to <a href="positron://settings/packages.enabled"><code>packages.enabled</code></a>; the previous <code>positron.packages.enable</code> setting is deprecated but still honored.</p>
<h2 id="inline-output-for-quarto">Inline output for Quarto
</h2>
<p><a href="https://positron.posit.co/quarto-inline-output" target="_blank" rel="noopener">Inline output for <code>.qmd</code> documents</a> was one of Positron&rsquo;s most-requested features ever, and it is now out of preview. A new toolbar button in Quarto documents gives you one-click access to running cells, managing inline output, and showing the console.</p>
<img src="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/inline-output.gif" data-fig-align="center" data-fig-alt="A Quarto document in Positron showing inline output beneath an executed code cell, with the new toolbar button for managing output." />
<p>Inserting code into a Quarto document from the History pane now automatically wraps it in cell markup when you drop it into a prose region. You can opt into inline output with the <a href="positron://settings/positron.quarto.inlineOutput.enabled"><code>positron.quarto.inlineOutput.enabled</code></a> setting.</p>
<h2 id="faster-startup">Faster startup
</h2>
<p>We continue to invest in performance improvements, and Positron starts up even faster in this release. Positron now caches previously discovered system interpreters, dramatically speeding up startup in new folders and projects; you can control this with the new <a href="positron://settings/interpreters.discoveryCache.enabled"><code>interpreters.discoveryCache.enabled</code></a> setting. On Windows, we fixed multi-minute startup delays by skipping slow <code>PATH</code> discovery by default, governed by the new <a href="positron://settings/positron.r.interpreters.pathDiscoveryMode"><code>positron.r.interpreters.pathDiscoveryMode</code></a> setting. We also fixed an occasional hang at &ldquo;Preparing&rdquo; when starting Positron after updating to a new version.</p>
<h2 id="a-more-customizable-interface">A more customizable interface
</h2>
<p>This release gives you finer control over Positron&rsquo;s chrome so you can tailor the interface to your workflow. Several layout settings that previously weren&rsquo;t always honored now work as expected:</p>
<ul>
<li><a href="positron://settings/workbench.topActionBar.visible"><code>workbench.topActionBar.visible</code></a> reliably hides and shows the top action bar at runtime</li>
<li><a href="positron://settings/workbench.secondarySideBar.defaultVisibility"><code>workbench.secondarySideBar.defaultVisibility</code></a> is respected when you set it explicitly</li>
<li><a href="positron://settings/workbench.secondarySideBar.showLabel"><code>workbench.secondarySideBar.showLabel</code></a> lets Secondary Side Bar items render as compact icons</li>
</ul>
<p>You can also enable <a href="positron://settings/window.commandCenter"><code>window.commandCenter</code></a> to bring the command center into the title bar. Learn more about some of our own team members&rsquo; favorite customizations:</p>
<div class="w-full aspect-video">
      <iframe
        src="https://www.youtube.com/embed/QIYyeuZ_ISY"
        class="w-full h-full"
        
        frameborder="0"
        allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
        allowfullscreen></iframe>
    </div>
<h2 id="r-language-intelligence">R language intelligence
</h2>
<p>Positron&rsquo;s R language support now understands symbols within a file. You can rename a local symbol and have every use of it updated at once, and Go to Definition and Find References now work for local symbols, so you can jump to where a variable is defined (such as via <code>&lt;-</code> or a function parameter) or see everywhere it is used within a function. Renaming symbols across files is not yet supported, but it is coming soon.</p>
<h2 id="whats-coming-next">What&rsquo;s coming next
</h2>
<ul>
<li>We have some exciting milestones planned for our July release! The <a href="https://positron.posit.co/positron-notebook-editor.html" target="_blank" rel="noopener">new notebook editor for <code>.ipynb</code> files</a>, the <a href="https://positron.posit.co/packages-pane.html" target="_blank" rel="noopener">Packages pane</a>, and <a href="https://pos.it/assistant" target="_blank" rel="noopener">Posit Assistant</a> will all come out of preview to general availability. We are thrilled for these features to be ready for your production workflows, and we hope you will give them a try and <a href="https://github.com/posit-dev/positron/discussions" target="_blank" rel="noopener">share your feedback</a>.</li>
<li>We&rsquo;re still prototyping first-class SQL editing and execution in Positron, including support for visualizations with <a href="https://opensource.posit.co/blog/2026-04-20_ggsql_alpha_release/" target="_blank" rel="noopener">ggsql</a>. We&rsquo;ll share more as this takes shape, and in the meantime, <a href="https://github.com/posit-dev/positron/issues/7233" target="_blank" rel="noopener">let us know</a> your thoughts and current pain points!</li>
<li>If you will be at the Databricks Data + AI Summit next week, <a href="https://posit.co/events/databricks-data-ai-summit-2026" target="_blank" rel="noopener">join us</a> to learn about using Positron and Workbench within the Databricks platform.</li>
<li>We are looking forward to posit::conf(2026) in September, where our team will have several sessions on Positron. The <a href="https://posit.co/blog/posit-conf-2026-agenda-breakdown" target="_blank" rel="noopener">conference program</a> was released last week and we are pretty excited about everything happening! <a href="https://conf.posit.co/2026/" target="_blank" rel="noopener">Register now</a> to join us in person in Houston or virtually from anywhere in the world.</li>
</ul>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Tip</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co/download" target="_blank" rel="noopener">Download Positron</a> to try out the new features and improvements in this release!</p>
</div>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-06-08_positron-2026-06-release/featured.png" length="73388" type="image/png" />
    </item>
    <item>
      <title>In Defense of YAML</title>
      <link>https://opensource.posit.co/blog/2026-05-21_in-defense-of-yaml/</link>
      <pubDate>Thu, 21 May 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-05-21_in-defense-of-yaml/</guid>
      <dc:creator>Rich Iannone</dc:creator>
      <dc:creator>Tomasz Kalinowski</dc:creator><description><![CDATA[<p>Every programmer has opinions about configuration files. These opinions
tend to be strongly held and inversely proportional to the stakes
involved. In the last few years, the consensus view has shifted: YAML is
bad, TOML is good, and enthusiastic users of YAML just might be plainly
uninformed. This post takes a different view. We intend to present an
argument for YAML which is grounded in history, its specification, and
the state of tooling in 2026.</p>
<p>The case against YAML was, for a long time, a reasonable one. The format
attracted its critics for real reasons, through years of surprising
behavior that burned even careful users. But the specification evolved,
and the tooling is finally catching up. To understand why the current
consensus is outdated, we need to trace the lineage of configuration
formats themselves, because this sort of argument has played out before.</p>
<h2 id="a-brief-history-of-configuration-formats">A brief history of configuration formats
</h2>
<p>The INI file emerged in the early 1980s alongside MS-DOS and the first
versions of Windows.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> It was the simplest thing that could possibly
work: key-value pairs, grouped into sections denoted by square brackets,
with semicolons for comments. They are flat, readable, and
human-editable. For the needs of that era (like configuring device
drivers, specifying font paths, or setting application preferences) it
was entirely adequate. Its only real limitation was structural: you
could not nest deeper than one level, and there was no formal
specification, which meant every parser implemented its own dialect. But
for two decades, this was fine.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-ini" data-lang="ini"><span class="line"><span class="cl"><span class="k">[boot]</span>
</span></span><span class="line"><span class="cl"><span class="na">shell</span><span class="o">=</span><span class="s">COMMAND.COM</span>
</span></span><span class="line"><span class="cl"><span class="na">device</span><span class="o">=</span><span class="s">HIMEM.SYS</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">[display]</span>
</span></span><span class="line"><span class="cl"><span class="na">resolution</span><span class="o">=</span><span class="s">640x480</span>
</span></span><span class="line"><span class="cl"><span class="na">colors</span><span class="o">=</span><span class="s">256</span></span></span></code></pre></div></div>
<p>Then came XML. In the late 1990s, the enterprise software world adopted
angle brackets broadly. XML could represent arbitrary hierarchy. It had
schemas, namespaces, transformations. It was self-describing. For a
while it seemed as though the debate was settled. But XML configuration
files grew large in practice. Anyone who maintained a Java <code>web.xml</code> or
an Ant build file in 2003 knows what it was like to edit dozens of
nested elements just to change a database connection string. The
verbosity made the files difficult to maintain by hand, which is
precisely what configuration files demand.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-xml" data-lang="xml"><span class="line"><span class="cl"><span class="nt">&lt;web-app&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;servlet&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;servlet-name&gt;</span>myServlet<span class="nt">&lt;/servlet-name&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;servlet-class&gt;</span>com.example.MyServlet<span class="nt">&lt;/servlet-class&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;init-param&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&lt;param-name&gt;</span>database.url<span class="nt">&lt;/param-name&gt;</span>
</span></span><span class="line"><span class="cl">      <span class="nt">&lt;param-value&gt;</span>jdbc:postgresql://localhost/mydb<span class="nt">&lt;/param-value&gt;</span>
</span></span><span class="line"><span class="cl">    <span class="nt">&lt;/init-param&gt;</span>
</span></span><span class="line"><span class="cl">  <span class="nt">&lt;/servlet&gt;</span>
</span></span><span class="line"><span class="cl"><span class="nt">&lt;/web-app&gt;</span></span></span></code></pre></div></div>
<p>JSON appeared as the lightweight reaction. Douglas Crockford, who claims
to have discovered rather than invented the format,<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> offered the
simplicity of the JavaScript object literal: curly braces, square
brackets, quoted strings, and a tiny set of types. JSON displaced XML in
web APIs through the late 2000s and early 2010s. But as people began
using it for configuration (rather than machine-to-machine data
exchange), its limitations became apparent. JSON has no comments. It has
no multiline strings. Trailing commas are illegal. These are reasonable
constraints for a serialization format, but they make JSON miserable for
files that humans must author and maintain. The removal of comments from
JSON&rsquo;s spec was, according to Crockford himself, motivated by people
abusing them for parsing directives.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup> It was the right call for data
interchange, but it left a gap.</p>
<p>YAML (2001) and TOML (2013) each arose to fill that gap, and both
positioned themselves explicitly against what came before. YAML offered
the full expressive power of a serialization language (including
arbitrary nesting, multiple documents, references, and custom types)
with a syntax built on indentation rather than brackets. TOML, created
by Tom Preston-Werner a dozen years later, was a reaction to YAML&rsquo;s
complexity: it aimed to be a &ldquo;standardized INI&rdquo; with explicit typing,
obvious semantics, and a formal specification.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup> The pattern repeats
in each generation: the previous format&rsquo;s excess becomes the new
format&rsquo;s founding grievance. What is interesting about the current
moment is that YAML&rsquo;s problems were not inherent to the format&rsquo;s design.
They were artifacts of a particular specification version and the
parsers frozen on it.</p>
<h2 id="the-case-against-yaml-as-it-was">The case against YAML (as it was)
</h2>
<p>The criticisms of YAML are not fabricated. They reflect real experiences
that real programmers had over many years.</p>
<p>The most infamous problem is the Norway incident, which has become
shorthand for YAML&rsquo;s implicit typing behavior. In YAML 1.1, the bare
scalar <code>NO</code> was interpreted as the boolean value <code>false</code>. This meant
that a list of country codes would silently transform Norway into a
falsehood:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="c"># What you wrote:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">countries</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">dk</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">fi</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">is</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="kc">no</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="l">se</span></span></span></code></pre></div></div>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="c1"># What YAML 1.1 parsed:</span>
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="s2">&#34;dk&#34;</span><span class="p">,</span> <span class="s2">&#34;fi&#34;</span><span class="p">,</span> <span class="s2">&#34;is&#34;</span><span class="p">,</span> <span class="n">false</span><span class="p">,</span> <span class="s2">&#34;se&#34;</span><span class="p">]</span></span></span></code></pre></div></div>
<p>The same applied to <code>yes</code>, <code>on</code>, <code>off</code>, <code>y</code>, <code>n</code>, and various
capitalizations thereof. Ruud van Asseldonk&rsquo;s widely-circulated &ldquo;YAML
Document from Hell&rdquo;<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup> catalogued these and other problems: port
mappings like <code>22:22</code> parsed as sexagesimal (base-60) integers, version
numbers like <code>10.23</code> parsed as floats rather than strings, date-like
values parsed as timestamps,<sup id="fnref:6"><a href="#fn:6" class="footnote-ref" role="doc-noteref">6</a></sup> and tags beginning with <code>!</code> could
trigger arbitrary code execution in some parsers.</p>
<p>This is not only a country-code problem. In data science and machine
learning code, <code>n</code> and <code>y</code> are natural variable names:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">variables</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">x</span><span class="p">:</span><span class="w"> </span><span class="l">features</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">n</span><span class="p">:</span><span class="w"> </span><span class="l">sample_size</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">y</span><span class="p">:</span><span class="w"> </span><span class="l">target</span></span></span></code></pre></div></div>
<p>Under YAML 1.1&rsquo;s implicit boolean rules, a parser can resolve those keys
as booleans instead of strings:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="p">{</span><span class="s2">&#34;variables&#34;</span><span class="p">:</span> <span class="p">{</span><span class="s2">&#34;x&#34;</span><span class="p">:</span> <span class="s2">&#34;features&#34;</span><span class="p">,</span> <span class="kc">False</span><span class="p">:</span> <span class="s2">&#34;sample_size&#34;</span><span class="p">,</span> <span class="kc">True</span><span class="p">:</span> <span class="s2">&#34;target&#34;</span><span class="p">}}</span></span></span></code></pre></div></div>
<p>These were not edge cases encountered only by the reckless. They emerged
from the YAML 1.1 specification&rsquo;s design philosophy of aggressive
implicit typing, where the parser attempted to be &ldquo;helpful&rdquo; by guessing
the intended type of unquoted values. The intention was readability (you
could write <code>true</code> without quotes and get a boolean), but the result was
unpredictable behavior in practice. Configuration files are precisely
the domain where surprising behavior is least tolerable. They are edited
infrequently, often by people who did not write the original file, and a
silent misparse can propagate through a system undetected for months.</p>
<p>The complexity of the full specification compounded the problem. The
YAML 1.2.2 spec runs to ten chapters with sections numbered four levels
deep.<sup id="fnref:7"><a href="#fn:7" class="footnote-ref" role="doc-noteref">7</a></sup> There are dozens of ways to express multiline strings. Anchors
and aliases create a reference system that, while powerful, adds
conceptual weight far beyond what most configuration tasks require. And
the security implications of tag-based object deserialization (the
<code>yaml.load()</code> vulnerability in Python) became a well-known attack
vector.<sup id="fnref:8"><a href="#fn:8" class="footnote-ref" role="doc-noteref">8</a></sup> All of these criticisms were valid, and they were valid
specifically of YAML 1.1 and the tooling ecosystem built around it.</p>
<h2 id="what-toml-gets-right">What TOML gets right
</h2>
<p>TOML deserves some credit. For flat or shallow configuration structures,
it is clean, readable, and unambiguous. The syntax is familiar to anyone
who has seen an INI file, but with the addition of explicit types, a
formal specification, and support for nested tables via dot-separated
keys.</p>
<p>Consider a <code>pyproject.toml</code> or a <code>Cargo.toml</code>. These files are typically
one or two levels deep, with well-defined sections and predictable
content. TOML handles them well. Strings are always quoted, so there is
no ambiguity about whether <code>no</code> is a boolean or the word &ldquo;no&rdquo;. Integers
are integers, floats are floats, and dates are first-class types.
Comments work exactly as you would expect. For this class of problem,
TOML works well, and its adoption by the Python packaging ecosystem (PEP
518)<sup id="fnref:9"><a href="#fn:9" class="footnote-ref" role="doc-noteref">9</a></sup> and the Rust community (Cargo) makes sense.</p>
<p>TOML also benefits from simplicity of implementation. The specification
is short enough that a competent programmer can write a compliant parser
in a weekend. This means that the ecosystem of parsers is large,
well-tested, and consistent. There is no equivalent of the YAML 1.1/1.2
version split. TOML 1.0 is TOML 1.0, everywhere. These are real
advantages.</p>
<h2 id="where-toml-strains">Where TOML strains
</h2>
<p>The trouble begins when configuration needs to express depth. TOML&rsquo;s
handling of nested structures relies on either dot-separated section
headers (<code>[servers.alpha]</code>) or arrays of tables (<code>[[products]]</code>), both
of which become difficult to read as the nesting increases. This is not
a theoretical concern: it is the reason that Martin Vejnár, the author
of the PyTOML parser, eventually abandoned his own project. When asked
whether his library should become a dependency for pip, he declined and
explained: &ldquo;TOML is a bad file format. It looks good at first glance,
and for really really trivial things it is probably good. But once I
started using it and the configuration schema became more complex, I
found the syntax ugly and hard to read&rdquo;.<sup id="fnref:10"><a href="#fn:10" class="footnote-ref" role="doc-noteref">10</a></sup></p>
<p>Consider a moderately complex configuration. In YAML, the indentation
communicates the hierarchy at a glance:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">services</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">web</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">image</span><span class="p">:</span><span class="w"> </span><span class="l">nginx:latest</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">environment</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">DB_HOST</span><span class="p">:</span><span class="w"> </span><span class="l">postgres</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">DB_PORT</span><span class="p">:</span><span class="w"> </span><span class="m">5432</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span><span class="nt">resources</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">limits</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">memory</span><span class="p">:</span><span class="w"> </span><span class="l">512M</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">        </span><span class="nt">cpu</span><span class="p">:</span><span class="w"> </span><span class="s2">&#34;0.5&#34;</span></span></span></code></pre></div></div>
<p>The equivalent in TOML requires repeating the full path in each section
header:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-toml" data-lang="toml"><span class="line"><span class="cl"><span class="p">[</span><span class="nx">services</span><span class="p">.</span><span class="nx">web</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">image</span> <span class="p">=</span> <span class="s2">&#34;nginx:latest&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">services</span><span class="p">.</span><span class="nx">web</span><span class="p">.</span><span class="nx">environment</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">DB_HOST</span> <span class="p">=</span> <span class="s2">&#34;postgres&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">DB_PORT</span> <span class="p">=</span> <span class="mi">5432</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="p">[</span><span class="nx">services</span><span class="p">.</span><span class="nx">web</span><span class="p">.</span><span class="nx">resources</span><span class="p">.</span><span class="nx">limits</span><span class="p">]</span>
</span></span><span class="line"><span class="cl"><span class="nx">memory</span> <span class="p">=</span> <span class="s2">&#34;512M&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nx">cpu</span> <span class="p">=</span> <span class="s2">&#34;0.5&#34;</span></span></span></code></pre></div></div>
<p>The reader must mentally reconstruct the tree from a flat sequence of
qualified names. The StrictYAML documentation measured this concretely:
equivalent TOML files use approximately 50% more characters to represent
the same data, largely because of the repeated path prefixes.<sup id="fnref:11"><a href="#fn:11" class="footnote-ref" role="doc-noteref">11</a></sup></p>
<p>There is also the matter of meaningful indentation itself. Python
demonstrated decades ago that indentation as structure is not a weakness
but a strength: it eliminates the class of bugs where visual structure
disagrees with syntactic structure. YAML inherits this property. TOML
does not require indentation (though many authors add it voluntarily, as
a non-parsed visual aid), which means that the relationship between a
key and its containing table exists only in the section header, not in
the physical layout of the file. For deeply nested configurations, this
makes TOML files harder to scan and harder to edit confidently.</p>
<h2 id="what-yaml-12-changed">What YAML 1.2 changed
</h2>
<p>The YAML 1.2 specification was published in 2009, with a clarifying
revision (1.2.2) completed in October 2021.<sup id="fnref:12"><a href="#fn:12" class="footnote-ref" role="doc-noteref">12</a></sup> Its changes address the
complaints described above directly.</p>
<p>The implicit typing that created the Norway problem is gone. In the YAML
1.2 Core Schema, only <code>true</code> and <code>false</code> (and their capitalizations
<code>True</code>, <code>False</code>, <code>TRUE</code>, <code>FALSE</code>) are recognized as booleans. The words
<code>yes</code>, <code>no</code>, <code>on</code>, <code>off</code>, <code>y</code>, and <code>n</code> are plain strings. Sexagesimal
number literals (the <code>22:22</code> problem) are removed entirely. Timestamp is
no longer a core type, so an unquoted <code>2026-05-05</code> is a string under the
core schema rather than an automatically detected date. JSON is now a
strict, proper subset of YAML 1.2, which means any valid JSON document
parses identically as YAML. The tag resolution rules are tightened and
clarified. The specification itself, while still substantial, is written
more clearly and maintained openly on GitHub.</p>
<p>In short, the YAML that people complain about is YAML 1.1. The
specification that actually governs the language today is a different,
safer, more predictable document. The problem is that most people&rsquo;s
experience of YAML is mediated not by the specification but by their
parser, and for most Python users, that parser has been PyYAML, which
implements YAML 1.1 and has not changed its core semantics since 2006.</p>
<h2 id="the-python-yaml-parser-landscape">The Python YAML parser landscape
</h2>
<p><a href="https://github.com/yaml/pyyaml" target="_blank" rel="noopener">PyYAML</a>, written by Kirill Simonov in
2006, is the de facto standard YAML library in Python. It wraps LibYAML
(a C library) for performance and provides a pure-Python fallback. It is
downloaded millions of times per week, it is a dependency of countless
packages, and it implements YAML 1.1. This last fact is the root of most
YAML complaints in the Python ecosystem. When someone says &ldquo;YAML parsed
my country code as a boolean&rdquo;, they are describing PyYAML&rsquo;s behavior,
not YAML&rsquo;s specification. PyYAML&rsquo;s GitHub repository shows over 200 open
issues and 100 open pull requests.<sup id="fnref:13"><a href="#fn:13" class="footnote-ref" role="doc-noteref">13</a></sup> The project is maintained but
moves slowly, and a major version bump to YAML 1.2 semantics has not
materialized.</p>
<p>The <a href="https://github.com/ruamel/yaml" target="_blank" rel="noopener"><code>ruamel.yaml</code></a> library, maintained
by Anthon van der Neut, offers YAML 1.2 support with round-trip
preservation of comments, flow style, and key order.<sup id="fnref:14"><a href="#fn:14" class="footnote-ref" role="doc-noteref">14</a></sup> It is widely
used and is significantly more capable than PyYAML for tasks requiring
comment preservation or format-aware editing. However, it is primarily a
pure-Python implementation in its default round-trip mode, which makes
it considerably slower than PyYAML&rsquo;s C-backed fast path. Its packaging
history has also been complicated: namespace package issues and a
dependency chain that has occasionally confused deployment pipelines.</p>
<p><a href="https://github.com/crdoconnor/strictyaml" target="_blank" rel="noopener">StrictYAML</a> takes a different
approach entirely, implementing a deliberate subset of YAML with all
implicit typing removed, no tags, no anchors, and no flow style.<sup id="fnref:15"><a href="#fn:15" class="footnote-ref" role="doc-noteref">15</a></sup>
Philosophically it is closer to TOML than to full YAML: a safe, simple
format that happens to use YAML&rsquo;s indentation syntax. It is Python-only,
has no implementations in other languages, and does not aim for spec
compliance.</p>
<p>What has been missing from this landscape is a library that is fast,
fully 1.2-compliant, and simple enough to use as a drop-in replacement
for PyYAML&rsquo;s basic interface.</p>
<h2 id="introducing-py-yaml12">Introducing py-yaml12
</h2>
<p>The <a href="https://github.com/posit-dev/py-yaml12" target="_blank" rel="noopener"><code>py-yaml12</code></a> library is a
YAML 1.2 parser and formatter for Python, implemented in Rust for speed
and correctness. It is built on the <code>saphyr</code> crate<sup id="fnref:16"><a href="#fn:16" class="footnote-ref" role="doc-noteref">16</a></sup> (a Rust YAML
library) and exposes a minimal, focused API: <code>parse_yaml()</code> and
<code>read_yaml()</code> for loading, <code>format_yaml()</code> and <code>write_yaml()</code> for
serialization.</p>
<h3 id="simple">Simple
</h3>
<p>The design philosophy is straightforward. For the vast majority of use
cases, you work with plain Python builtins end to end: <code>dict</code>, <code>list</code>,
<code>int</code>, <code>float</code>, <code>str</code>, and <code>None</code>. There is no special document class,
no custom node types in the common path. Because YAML 1.2 is a superset
of JSON, all valid JSON parses identically. The library achieves 100%
compliance with the yaml-test-suite,<sup id="fnref:17"><a href="#fn:17" class="footnote-ref" role="doc-noteref">17</a></sup> the community-maintained
corpus of edge cases and conformance tests.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">yaml12</span> <span class="kn">import</span> <span class="n">parse_yaml</span><span class="p">,</span> <span class="n">format_yaml</span>
</span></span><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">rich.pretty</span> <span class="kn">import</span> <span class="n">pprint</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">config</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">server:
</span></span></span><span class="line"><span class="cl"><span class="s2">  host: 0.0.0.0
</span></span></span><span class="line"><span class="cl"><span class="s2">  port: 8080
</span></span></span><span class="line"><span class="cl"><span class="s2">  debug: false
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">database:
</span></span></span><span class="line"><span class="cl"><span class="s2">  url: postgres://localhost/mydb
</span></span></span><span class="line"><span class="cl"><span class="s2">  pool_size: 5
</span></span></span><span class="line"><span class="cl"><span class="s2">
</span></span></span><span class="line"><span class="cl"><span class="s2">regions:
</span></span></span><span class="line"><span class="cl"><span class="s2">  - us-east-1
</span></span></span><span class="line"><span class="cl"><span class="s2">  - eu-west-1
</span></span></span><span class="line"><span class="cl"><span class="s2">  - no         # Norway, not false
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">data</span> <span class="o">=</span> <span class="n">parse_yaml</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="n">pprint</span><span class="p">(</span><span class="n">data</span><span class="p">)</span></span></span></code></pre></div></div>
<pre style="white-space:pre;overflow-x:auto;line-height:normal;font-family:Menlo,'DejaVu Sans Mono',consolas,'Courier New',monospace"><span style="font-weight: bold">{</span>
<span style="color: #7fbf7f; text-decoration-color: #7fbf7f">│   </span><span style="color: #008000; text-decoration-color: #008000">'server'</span>: <span style="font-weight: bold">{</span><span style="color: #008000; text-decoration-color: #008000">'host'</span>: <span style="color: #008000; text-decoration-color: #008000">'0.0.0.0'</span>, <span style="color: #008000; text-decoration-color: #008000">'port'</span>: <span style="color: #008080; text-decoration-color: #008080; font-weight: bold">8080</span>, <span style="color: #008000; text-decoration-color: #008000">'debug'</span>: <span style="color: #ff0000; text-decoration-color: #ff0000; font-style: italic">False</span><span style="font-weight: bold">}</span>,
<span style="color: #7fbf7f; text-decoration-color: #7fbf7f">│   </span><span style="color: #008000; text-decoration-color: #008000">'database'</span>: <span style="font-weight: bold">{</span><span style="color: #008000; text-decoration-color: #008000">'url'</span>: <span style="color: #008000; text-decoration-color: #008000">'postgres://localhost/mydb'</span>, <span style="color: #008000; text-decoration-color: #008000">'pool_size'</span>: <span style="color: #008080; text-decoration-color: #008080; font-weight: bold">5</span><span style="font-weight: bold">}</span>,
<span style="color: #7fbf7f; text-decoration-color: #7fbf7f">│   </span><span style="color: #008000; text-decoration-color: #008000">'regions'</span>: <span style="font-weight: bold">[</span><span style="color: #008000; text-decoration-color: #008000">'us-east-1'</span>, <span style="color: #008000; text-decoration-color: #008000">'eu-west-1'</span>, <span style="color: #008000; text-decoration-color: #008000">'no'</span><span style="font-weight: bold">]</span>
<span style="font-weight: bold">}</span>
</pre>
<p>Notice the <code>no</code> in the regions list. Under PyYAML (YAML 1.1), this would
silently become <code>False</code>. Under <code>py-yaml12</code> (YAML 1.2), it is the string
<code>&quot;no&quot;</code>, as the specification requires. This single behavioral difference
encapsulates the entire argument: the format is not broken, the old
tooling was.</p>
<p>The file API is similarly direct:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">yaml12</span> <span class="kn">import</span> <span class="n">write_yaml</span><span class="p">,</span> <span class="n">read_yaml</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">path</span> <span class="o">=</span> <span class="s2">&#34;config.yaml&#34;</span>
</span></span><span class="line"><span class="cl"><span class="n">write_yaml</span><span class="p">(</span><span class="n">data</span><span class="p">,</span> <span class="n">path</span><span class="p">)</span></span></span></code></pre></div></div>
<p>The round-trip is lossless. Writing a Python dictionary to disk and
reading it back produces an identical object:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">round_tripped</span> <span class="o">=</span> <span class="n">read_yaml</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="k">assert</span> <span class="n">round_tripped</span> <span class="o">==</span> <span class="n">data</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">&#34;Round-trip matches: </span><span class="si">{</span><span class="n">round_tripped</span> <span class="o">==</span> <span class="n">data</span><span class="si">}</span><span class="s2">&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<div class="code-block"><pre tabindex="0"><code>Round-trip matches: True</code></pre></div>
<p>For advanced YAML features like tagged values, <code>py-yaml12</code> provides the
<code>Yaml</code> wrapper type. It is opt-in and unnecessary for typical
configuration work.</p>
<h3 id="safe">Safe
</h3>
<p>The defaults in <code>py-yaml12</code> are not just about ergonomics and
simplicity; they also improve safety. PyYAML shows the risk of the
opposite approach: treating tags as instructions can execute arbitrary
Python code simply by reading a YAML file.<sup id="fnref:18"><a href="#fn:18" class="footnote-ref" role="doc-noteref">18</a></sup></p>
<p>For example, someone can produce a YAML file that aliases PyYAML&rsquo;s
Python object-apply tag namespace:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="n">dangerous_yaml</span> <span class="o">=</span> <span class="s2">&#34;&#34;&#34;</span><span class="se">\
</span></span></span><span class="line"><span class="cl"><span class="s2">%TAG !py! tag:yaml.org,2002:python/object/apply:
</span></span></span><span class="line"><span class="cl"><span class="s2">--- !py!builtins.eval
</span></span></span><span class="line"><span class="cl"><span class="s2">- &#34;(__import__(&#39;os&#39;).environ.__setitem__(&#39;YAML_PAYLOAD_RAN&#39;, &#39;1&#39;), {&#39;debug&#39;: False, &#39;retries&#39;: 3})[1]&#34;
</span></span></span><span class="line"><span class="cl"><span class="s2">&#34;&#34;&#34;</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;dangerous.yaml&#34;</span><span class="p">,</span> <span class="s2">&#34;w&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">dangerous_yaml</span><span class="p">)</span></span></span></code></pre></div></div>
<p>Then a user expecting only to load a config file runs that code during
parsing:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">yaml</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">&#34;dangerous.yaml&#34;</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
</span></span><span class="line"><span class="cl">    <span class="n">data</span> <span class="o">=</span> <span class="n">yaml</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">Loader</span><span class="o">=</span><span class="n">yaml</span><span class="o">.</span><span class="n">Loader</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nb">print</span><span class="p">(</span><span class="n">data</span><span class="p">)</span></span></span></code></pre></div></div>
<div class="code-block"><pre tabindex="0"><code>{&#39;debug&#39;: False, &#39;retries&#39;: 3}</code></pre></div>
<p>The <code>yaml.load()</code> call looks like ordinary data loading: it returns an
ordinary dictionary. But producing that dictionary executed Python code
first. Unless you inspect the YAML itself, nothing in the result tells
you that happened.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">import</span> <span class="nn">os</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s2">&#34;YAML_PAYLOAD_RAN&#34;</span><span class="p">]</span></span></span></code></pre></div></div>
<div class="code-block"><pre tabindex="0"><code>&#39;1&#39;</code></pre></div>
<p>In contrast, <code>py-yaml12</code> keeps an unhandled tag as data unless you
explicitly opt in:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">yaml12</span> <span class="kn">import</span> <span class="n">read_yaml</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">read_yaml</span><span class="p">(</span><span class="s2">&#34;dangerous.yaml&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<div class="code-block"><pre tabindex="0"><code>Yaml(value=[&#34;(__import__(&#39;os&#39;).environ.__setitem__(&#39;YAML_PAYLOAD_RAN&#39;, &#39;1&#39;), {&#39;debug&#39;: False, &#39;retries&#39;: 3})[1]&#34;], tag=&#39;tag:yaml.org,2002:python/object/apply:builtins.eval&#39;)</code></pre></div>
<h3 id="fast">Fast
</h3>
<p>Performance is a practical concern for any library that might be called
in startup paths or CI pipelines. The <code>py-yaml12</code> benchmarks<sup id="fnref:19"><a href="#fn:19" class="footnote-ref" role="doc-noteref">19</a></sup>
compare read and write performance against PyYAML (both its default
pure-Python path and the fast CSafeLoader/CSafeDumper backed by LibYAML)
and <code>ruamel.yaml</code>, across file sizes ranging from kilobytes to
megabytes. Because the core parsing and formatting logic is implemented
in compiled Rust rather than interpreted Python, <code>py-yaml12</code> is
competitive with PyYAML&rsquo;s C extension while maintaining full 1.2
compliance. As of this writing, few other Python libraries offer both.</p>
<h2 id="conclusion">Conclusion
</h2>
<p>The YAML-versus-TOML debate, as typically conducted, is an argument
against a format that no longer exists in its problematic form. The
complaints are real, but they are historical. They describe YAML 1.1 as
mediated by PyYAML, not YAML 1.2 as specified and now properly
implemented. TOML remains a good choice for shallow, flat
configurations, and <code>pyproject.toml</code> is well-suited to its role. But the
claim that YAML is inherently unsafe or unpredictable does not hold
against a compliant 1.2 parser.</p>
<p>This is, in the end, a familiar pattern in computing. Every generation
of configuration format is a correction of the previous generation&rsquo;s
excesses: INI was too flat, so XML added hierarchy; XML was too verbose,
so JSON stripped it bare; JSON was too austere for humans, so YAML and
TOML each offered different compromises. The interesting question is
never &ldquo;which format is best in the abstract&rdquo; but &ldquo;which format, with
which tooling, serves this particular task well&rdquo;. For complex, nested,
human-authored configuration, YAML 1.2 with a modern parser is a strong
answer. Perhaps in another decade, something new will arise to correct
YAML&rsquo;s remaining rough edges, and the cycle will continue. That is how
formats improve.</p>
<p>In the meantime, you can <code>pip install py-yaml12</code> and see what a modern,
spec-compliant YAML experience looks like in Python.</p>
<p>And if you work in R, the <a href="https://github.com/posit-dev/r-yaml12" target="_blank" rel="noopener"><code>r-yaml12</code></a>
package brings the same benefits: full YAML 1.2 compliance, Rust-backed
performance, and safe defaults. Everything good about the Python package
is in the R version as well.</p>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Wikipedia, &ldquo;<a href="https://en.wikipedia.org/wiki/INI_file" target="_blank" rel="noopener">INI file</a>&rdquo;.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Douglas Crockford, &ldquo;<a href="https://www.youtube.com/watch?v=-C-JoyNuQJs" target="_blank" rel="noopener">The JSON
Saga</a>&rdquo;, presentation at
Yahoo, 2011.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Douglas Crockford, in the same presentation (see [^2]),
explains that comments were removed because &ldquo;people were using
comments to hold parsing directives&rdquo;.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Tom Preston-Werner, &ldquo;<a href="https://toml.io/en/" target="_blank" rel="noopener">TOML: Tom&rsquo;s Obvious Minimal
Language</a>&rdquo;.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>Ruud van Asseldonk, &ldquo;<a href="https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell" target="_blank" rel="noopener">The yaml document from
hell</a>&rdquo;,
January 2023.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:6">
<p>Examples include Stack Overflow, &ldquo;<a href="https://stackoverflow.com/questions/50900727/skip-converting-entities-while-loading-a-yaml-string-using-pyyaml" target="_blank" rel="noopener">Skip converting entities while
loading a yaml string (using
PyYAML)</a>&rdquo;;
PyYAML issue <a href="https://github.com/yaml/pyyaml/issues/382" target="_blank" rel="noopener">#382</a>; and
ruamel.yaml ticket
<a href="https://sourceforge.net/p/ruamel-yaml/tickets/509/" target="_blank" rel="noopener">#509</a>.&#160;<a href="#fnref:6" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:7">
<p>&ldquo;<a href="https://yaml.org/spec/1.2.2/" target="_blank" rel="noopener">YAML Ain&rsquo;t Markup Language (YAML) Version 1.2, Revision
1.2.2</a>&rdquo;, October 2021.&#160;<a href="#fnref:7" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:8">
<p>See
<a href="https://nvd.nist.gov/vuln/detail/CVE-2017-18342" target="_blank" rel="noopener">CVE-2017-18342</a>,
<a href="https://nvd.nist.gov/vuln/detail/CVE-2020-1747" target="_blank" rel="noopener">CVE-2020-1747</a>,
<a href="https://nvd.nist.gov/vuln/detail/CVE-2020-14343" target="_blank" rel="noopener">CVE-2020-14343</a>,
PyYAML issue <a href="https://github.com/yaml/pyyaml/issues/420" target="_blank" rel="noopener">#420</a>, and
the PyYAML wiki page on <a href="https://github.com/yaml/pyyaml/wiki/PyYAML-yaml.load%28input%29-Deprecation" target="_blank" rel="noopener"><code>yaml.load(input)</code>
deprecation</a>.&#160;<a href="#fnref:8" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:9">
<p>Brett Cannon et al., &ldquo;<a href="https://peps.python.org/pep-0518/" target="_blank" rel="noopener">PEP 518 &ndash; Specifying Minimum Build System
Requirements for Python
Projects</a>&rdquo;, 2016.&#160;<a href="#fnref:9" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:10">
<p>Martin Vejnár (avakar), comment on
<a href="https://github.com/avakar/pytoml/issues/15#issuecomment-217804599" target="_blank" rel="noopener">avakar/pytoml#15</a>,
May 2016.&#160;<a href="#fnref:10" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:11">
<p>StrictYAML documentation, &ldquo;<a href="https://hitchdev.com/strictyaml/why-not/toml/" target="_blank" rel="noopener">What is wrong with
TOML?</a>&rdquo;.&#160;<a href="#fnref:11" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:12">
<p>&ldquo;<a href="https://yaml.org/spec/1.2.2/" target="_blank" rel="noopener">YAML Ain&rsquo;t Markup Language (YAML) Version 1.2, Revision
1.2.2</a>&rdquo;, October 2021.&#160;<a href="#fnref:12" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:13">
<p><a href="https://github.com/yaml/pyyaml" target="_blank" rel="noopener">PyYAML GitHub repository</a>,
accessed April 2026.&#160;<a href="#fnref:13" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:14">
<p><a href="https://pypi.org/project/ruamel.yaml/" target="_blank" rel="noopener">ruamel.yaml on PyPI</a>.&#160;<a href="#fnref:14" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:15">
<p><a href="https://hitchdev.com/strictyaml/" target="_blank" rel="noopener">StrictYAML documentation</a>.&#160;<a href="#fnref:15" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:16">
<p><a href="https://crates.io/crates/saphyr" target="_blank" rel="noopener">saphyr crate</a> on crates.io.&#160;<a href="#fnref:16" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:17">
<p><a href="https://github.com/yaml/yaml-test-suite" target="_blank" rel="noopener">yaml-test-suite</a> on
GitHub.&#160;<a href="#fnref:17" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:18">
<p>See the PyYAML object-construction examples in issue #420, cited
above.&#160;<a href="#fnref:18" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:19">
<p>py-yaml12,
&ldquo;<a href="https://posit-dev.github.io/py-yaml12/benchmarks/benchmarks.html" target="_blank" rel="noopener">Benchmarks</a>&rdquo;.&#160;<a href="#fnref:19" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-05-21_in-defense-of-yaml/assets/in-defense-of-yaml.png" length="1094864" type="image/png" />
    </item>
    <item>
      <title>Ten Great Things We Added to Great Docs</title>
      <link>https://opensource.posit.co/blog/2026-05-13_great-docs-ten-things/</link>
      <pubDate>Wed, 13 May 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-05-13_great-docs-ten-things/</guid>
      <dc:creator>Rich Iannone</dc:creator><description><![CDATA[<p>Great Docs started with a premise: you should be able to point a tool at
your Python package and get a documentation site that looks good without
any design work. Run <code>great-docs init</code>, run <code>great-docs build</code>, open the
result in a browser, and you are done. That three-command workflow has
not changed since <code>v0.1</code>, and the simplicity of the entry point is
important. But behind that simple surface, ten releases have added a
world of capability for when you are ready to go further.</p>
<p>With the release of <code>v0.10.0</code>, we have reached a nice, round number and
it&rsquo;s worth looking back and surveying all the things that were done! So
what follows is a survey of ten features (one per release) that
represent the range of what Great Docs has become. Taken together, they
tell the story of a documentation generator that starts simple and
scales with your ambitions.</p>
<h2 id="1-auto-discovery">1. Auto-Discovery
</h2>
<p>The original release (<code>v0.1</code>) came out with the feature that defines the
project&rsquo;s philosophy. When you run <code>great-docs init</code>, Great Docs
inspects your package and discovers its public API automatically. It
finds classes, functions, dataclasses, protocols, enumerations,
exceptions, type aliases, and more, using a combination of runtime
introspection and static analysis via
<a href="https://mkdocstrings.github.io/griffe/" target="_blank" rel="noopener">griffe</a>. It detects your
docstring style (NumPy, Google, or Sphinx) and writes a <code>great-docs.yml</code>
configuration file that captures the full structure of your API.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">reference</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">sections</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">Core</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">contents</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">MyApp, Config, build, preview]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">Utilities</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">contents</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">parse, validate, format_output]</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">    </span>- <span class="nt">title</span><span class="p">:</span><span class="w"> </span><span class="l">Exceptions</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">      </span><span class="nt">contents</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">ConfigError, BuildError]</span></span></span></code></pre></div></div>
<p>The practical consequence is that you don&rsquo;t have to enumerate your
exports by hand. If you add a new class or function to your package, the
next build picks it up. If you remove something, it disappears from the
site. The configuration file exists so you can reorder sections, add
display names, or exclude internal symbols, but the default is complete
and correct without any having to do any manual intervention.</p>
<p>This was the design decision that everything else built on: if
Great Docs can figure something out automatically, it should, and
configuration should be for expressing preferences rather than providing
information that we could have inferred.</p>
<h2 id="2-seo-and-proofreading">2. SEO and Proofreading
</h2>
<p>The <code>v0.2</code> release added two capabilities that address different aspects
of the same problem: making sure people can find your documentation and
making sure what they find reads well.</p>
<p>On the discoverability side, Great Docs now generates <code>sitemap.xml</code>,
<code>robots.txt</code>, canonical URLs, and JSON-LD structured data automatically.
Per-page Open Graph meta tags are injected so that when someone shares a
link to your documentation, the preview card shows a title, description,
and image rather than a generic URL. The <code>great-docs seo</code> audit command
checks your site for common problems (e.g., missing descriptions, broken
canonical links, orphaned pages, etc.).</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">great-docs proofread</span></span></code></pre></div></div>
<p>On the quality side, the <code>great-docs proofread</code> command runs local
grammar and spelling checks powered by
<a href="https://writewithharper.com/" target="_blank" rel="noopener">Harper</a>, a fast grammar checker that runs
entirely on your machine. It skips code blocks and YAML frontmatter,
ships with a technical dictionary so it does not flag standard
programming terms, and supports project-specific custom dictionaries for
your package&rsquo;s terminology. The output is available as plain text or
JSON (which is well-suited for CI pipelines).</p>
<p>The combination means that documentation sites built with Great Docs
tend to surface well in search results and tend to read cleanly once
readers arrive. Neither of these things sound all that exciting, but
both matter more than a lot of visual features.</p>
<h2 id="3-documentation-linting">3. Documentation Linting
</h2>
<p>Release <code>v0.3</code> introduced <code>great-docs lint</code>, a static analysis tool for
your documentation rather than your code. It inspects your package&rsquo;s
public API and checks for problems that would otherwise surface only
when a user encounters a confusing or broken page.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">great-docs lint</span></span></code></pre></div></div>
<p>The linter catches missing docstrings (a function exists in your API but
has no documentation), broken cross-references (you reference
<code>MyClass.process</code> but the method is actually called <code>MyClass.run</code>),
style mismatches (you declared NumPy-style docstrings but a function
uses Google style), and unknown directives in your docstrings. It
produces machine-readable JSON output, making it straightforward to add
a lint step to your CI pipeline that fails the build when documentation
quality degrades.</p>
<p>This sort of feature is more useful than you&rsquo;d expect. You might run it,
discover three functions that never got docstrings and two
cross-references that broke during a refactor, fix them, and then feel
quite happy. Using it again and again will probably prevent the same
sort of problems from recurring. The goal is that no user should ever
land on an API reference page and find it empty or confusing because of
some oversight.</p>
<h2 id="4-internationalization">4. Internationalization
</h2>
<p>Great Docs <code>v0.4</code> made it possible to present your entire documentation
site in any of 23 languages with a single configuration option. The
actual content of your pages remains in whatever language you write it
in (usually English), but every piece of UI text (navbar labels, button
tooltips, relative timestamps, search placeholders, accessibility
attributes, pagination controls) is automatically translated.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">site</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span><span class="nt">language</span><span class="p">:</span><span class="w"> </span><span class="l">fr</span></span></span></code></pre></div></div>
<p>That single line transforms every &ldquo;Next page&rdquo; into &ldquo;Page suivante&rdquo;,
every &ldquo;Search&rdquo; into &ldquo;Rechercher&rdquo;, and every &ldquo;2 days ago&rdquo; into &ldquo;il y a 2
jours&rdquo;. Translations include proper plural forms for languages that need
them (like Polish, which has three plural forms depending on the number)
and localized date expressions.</p>
<p>The reasoning behind this feature is straightforward: if your package
has international users (and most packages do, whether they realize it
or not), the friction of navigating a site where every label is in a
language you do not speak fluently is real. While translating content is
a large undertaking, translating UI chrome is something a tool can do
for you.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-05-13_great-docs-ten-things/assets/reference-page-french.png"
      alt="A reference page for a class called TraiteurDeDonnees with all UI
elements rendered in French." 
      loading="lazy"
    >
  </figure></div>
</p>
<h2 id="5-keyboard-navigation">5. Keyboard Navigation
</h2>
<p>The <code>v0.5</code> release added a full keyboard shortcut layer that ships with
every documentation site. Press <code>/</code> or <code>s</code> to focus search. Press <code>[</code>
and <code>]</code> to navigate to the previous or next page. Press <code>d</code> to toggle
dark mode. Press <code>c</code> to copy the current page as Markdown. Press <code>h</code> or
<code>?</code> to see a help overlay listing all available shortcuts.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-05-13_great-docs-ten-things/assets/keyboard-shortcuts-overlay.png"
      alt="The keyboard shortcuts help overlay, showing all available navigation
shortcuts." 
      loading="lazy"
    >
  </figure></div>
</p>
<p>All shortcuts are disabled when a text input has focus (so typing in
search does not trigger navigation) and respect <code>prefers-reduced-motion</code>
for users who have requested reduced animation. The system is enabled by
default and can be disabled entirely via <code>keyboard_nav: false</code> in the
configuration for projects that don&rsquo;t want it.</p>
<p>Keyboard navigation is one of those features that, once you have used it
for a day, makes every site without it feel slightly slower. The ability
to browse through a long user guide using <code>[</code> and <code>]</code> without moving
your hands to the trackpad is a small daily improvement that compounds
over time. It also makes the sites more accessible to users who navigate
primarily or exclusively by keyboard.</p>
<h2 id="6-page-tags-and-status-badges">6. Page Tags and Status Badges
</h2>
<p>Release <code>v0.6</code> introduced page-level metadata that surfaces in both the
page body and the sidebar, giving documentation sites a lightweight
content management layer.</p>
<p>Page tags let you categorize pages by topic using YAML frontmatter:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">tags</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="l">Configuration, Theming, Advanced]</span></span></span></code></pre></div></div>
<p>Great Docs renders these as pill-shaped links above the page title and
auto-generates a tags index page listing all tags across the site with
links to their associated pages. If you maintain a large user guide with
dozens of pages, tags give readers an alternative navigation path:
instead of scrolling through the sidebar, they can click a tag to find
all related content. Every User Guide page on the <a href="https://posit-dev.github.io/great-docs/" target="_blank" rel="noopener">Great Docs
site</a> uses page tags, and the
<a href="https://posit-dev.github.io/great-docs/user-guide/page-tags.html" target="_blank" rel="noopener">Page
Tags</a>
guide explains how to set them up.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-05-13_great-docs-ten-things/assets/page-tag-index.png"
      alt="The auto-generated Tags index page on the Great Docs site, showing the
AI/LLM and API tag categories with their associated
pages." 
      loading="lazy"
    >
  </figure></div>
</p>
<p>Page status badges mark pages with lifecycle states:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">status</span><span class="p">:</span><span class="w"> </span><span class="l">beta</span></span></span></code></pre></div></div>
<p>The supported statuses are <code>new</code>, <code>beta</code>, <code>deprecated</code>, and
<code>experimental</code>, each rendered as a color-coded badge below the page
title. In the sidebar, these appear as compact icons so that readers can
see at a glance which parts of the API are stable and which are still
evolving. Status badges are automatically translated for non-English
sites (the same i18n system from v0.4 applies) and include built-in
Lucide icons and color schemes.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-05-13_great-docs-ten-things/assets/page-status-badges.png"
      alt="The five preconfigured page status badges: New, Update, Beta,
Deprecated, and Experimental." 
      loading="lazy"
    >
  </figure></div>
</p>
<p>Together, tags and status badges give documentation authors a way to
communicate structure and maturity without writing prose about it. A
reader landing on a page immediately knows whether it describes a stable
feature or an experiment, and can navigate to related topics through
shared tags. The <a href="https://posit-dev.github.io/great-docs/user-guide/page-status-badges.html" target="_blank" rel="noopener">Page Status
Badges</a>
guide covers the full set of options.</p>
<h2 id="7-scale-to-fit">7. Scale-to-Fit
</h2>
<p>Great Docs <code>v0.7</code> introduced a solution to a problem that plagues
documentation for data-oriented packages: wide tables and HTML widgets
that overflow the content column. When a table produced by <a href="https://posit-dev.github.io/great-tables/" target="_blank" rel="noopener">Great
Tables</a> or a Pandas DataFrame
is slightly wider than the page, the reader has to scroll horizontally
to see a sliver of remaining content, which is a frustrating experience.
But when content is dramatically wider than the container, shrinking it
would make text unreadably small. So, in essence, different situations
demand different responses.</p>
<p>Scale-to-Fit handles both cases. It automatically shrinks targeted
elements so they fit within the container width, and falls back to
horizontal scrolling when the element would need to shrink beyond a
configurable minimum threshold. The default threshold is 60% of natural
size, meaning content will shrink up to 40% to avoid scrollbars, but
anything more extreme gets a scrollbar instead.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">scale_to_fit</span><span class="p">:</span><span class="w"> </span><span class="kc">true</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="nt">scale_to_fit_min_scale</span><span class="p">:</span><span class="w"> </span><span class="m">0.6</span></span></span></code></pre></div></div>
<p>The feature can be enabled globally (all rendered HTML output gets the
treatment), per-page via frontmatter, or manually on individual elements
using a <code>:::{.scale-to-fit}</code> div wrapper. The global setting is what
most data-oriented packages want: turn it on once and every GT table,
every DataFrame display, and every custom widget in your documentation
adapts gracefully to the reader&rsquo;s viewport without any per-page
configuration. The
<a href="https://posit-dev.github.io/great-docs/user-guide/scale-to-fit.html" target="_blank" rel="noopener">Scale-to-Fit</a>
guide demonstrates the behavior at different widths.</p>
<h2 id="8-versioned-documentation">8. Versioned Documentation
</h2>
<p>Release <code>v0.8</code> addressed a fundamental challenge of maintaining
documentation for evolving libraries: users on older releases need docs
that match the version they have installed. Great Docs makes
multi-version documentation a build-time concern rather than a
deployment concern. You declare your versions in configuration, and the
build system produces a coherent, version-aware site.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-yaml" data-lang="yaml"><span class="line"><span class="cl"><span class="nt">versions</span><span class="p">:</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="s2">&#34;0.3&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="s2">&#34;0.2&#34;</span><span class="w">
</span></span></span><span class="line"><span class="cl"><span class="w">  </span>- <span class="s2">&#34;0.1&#34;</span></span></span></code></pre></div></div>
<p>That configuration produces three independent copies of your site: the
latest version at the root URL, and previous versions under <code>/v/0.2/</code>
and <code>/v/0.1/</code>. A version selector dropdown appears in the navbar,
letting readers switch between versions. Each version can reference an
<code>api_snapshot</code> to regenerate API pages from that release (rather than
the current code), and pages can use <code>versions</code> frontmatter to declare
which releases they apply to. Pages that did not exist in older releases
are automatically excluded from older builds.</p>
<p>The system also supports prerelease versions (marked in the dropdown but
not served as &ldquo;latest&rdquo;), end-of-life versions (visually distinguished),
and floating URL aliases like <code>/v/stable/</code> that always resolve to the
current release. Maintainers declare versions once and the build handles
the rest. The <a href="https://posit-dev.github.io/great-docs/" target="_blank" rel="noopener">Great Docs documentation
site</a> itself is a good example
of this feature in practice: its version selector currently contains ten
versions, one for each release from <code>v0.1</code> through <code>v0.10.0</code>. The
<a href="https://posit-dev.github.io/great-docs/user-guide/multi-version-docs.html" target="_blank" rel="noopener">Versioned
Docs</a>
guide covers the full configuration in detail.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-05-13_great-docs-ten-things/assets/version-selector-widget.png"
      alt="The version selector dropdown on the Great Docs site, showing all ten
versions (plus the prerelease) available for
navigation." 
      loading="lazy"
    >
  </figure></div>
</p>
<h2 id="9-color-swatches">9. Color Swatches
</h2>
<p>Great Docs <code>v0.9</code> added a shortcode for documenting color palettes
interactively. If your package has a theming system, a set of brand
colors, or a palette of status indicators, you have probably faced the
problem of showing colors in documentation. Screenshots go stale,
hand-coded HTML swatches are tedious, and plain text hex codes are
meaningless without a visual reference. The <code>color-swatch</code> shortcode
turns a YAML file into an interactive palette.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-05-13_great-docs-ten-things/assets/color-swatches.png"
      alt="The Color Swatches shortcode in action: a YAML color definition file
rendered as interactive circle swatches with names and hex
labels." 
      loading="lazy"
    >
  </figure></div>
</p>
<p>Each swatch displays its hex code, shows RGB and HSL breakdowns on
hover, evaluates WCAG and APCA contrast ratios against white and black
backgrounds, and lets readers copy the color value with a single click.
Two display modes are available: circles (compact, scannable) and
rectangles (detailed, with contrast information front and center). Great
Docs also ships with built-in presets for its own gradient themes, so
you can reference <code>preset=&quot;sky&quot;</code> or <code>preset=&quot;lilac&quot;</code> without creating a
YAML file at all.</p>
<p>For packages that deal with color (charting libraries, design systems,
theming frameworks), this feature eliminates a category of maintenance
burden. The YAML file is the source of truth; the rendered output is
always current, always interactive, and always accessible. The <a href="https://posit-dev.github.io/great-docs/user-guide/color-swatches.html" target="_blank" rel="noopener">Color
Swatches</a>
page in the User Guide covers the full range of options.</p>
<h2 id="10-table-previews-and-table-explorer">10. Table Previews and Table Explorer
</h2>
<p>The most recent release, <code>v0.10.0</code>, added two complementary tools for
embedding data directly in documentation pages. If your package works
with tabular data (and many Python packages do), showing what a dataset
looks like is one of the most common documentation tasks, and also one
of the hardest to do well with static publishing.</p>
<p><code>tbl_preview()</code> generates a self-contained, JavaScript-free HTML table
from almost any data source:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-python" data-lang="python"><span class="line"><span class="cl"><span class="kn">from</span> <span class="nn">great_docs</span> <span class="kn">import</span> <span class="n">tbl_preview</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">tbl_preview</span><span class="p">(</span><span class="s2">&#34;data/example.parquet&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>It accepts Polars DataFrames, Pandas DataFrames, PyArrow Tables, CSV
files, Parquet files, and plain Python dictionaries. Each preview
includes a colored type badge identifying the source format, compact
dtype labels beneath every column header, row-number gutters, automatic
head/tail splitting for large tables (showing the first and last rows
with an ellipsis in between), and missing-value highlighting. The output
works identically in light and dark modes and requires no JavaScript at
all.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-05-13_great-docs-ten-things/assets/tbl-preview-light-dark.png"
      alt="A table preview rendered in light mode (top) and dark mode (bottom),
showing identical structure and readability in both
themes." 
      loading="lazy"
    >
  </figure></div>
</p>
<p><code>tbl_explorer()</code> is the interactive counterpart. It embeds all data as
inline JSON and progressively enhances a static fallback table with
sorting, token-based filtering, pagination, column toggling,
copy-to-clipboard, and CSV download. A Quarto shortcode variant lets you
embed an explorer directly in a <code>.qmd</code> page by pointing at a data file,
without writing any Python.</p>
<p><div class="not-prose"><figure>
    <img class="h-auto max-w-full rounded-lg"
      src="https://opensource.posit.co/blog/2026-05-13_great-docs-ten-things/assets/tbl-explorer-view.png"
      alt="A Table Explorer widget with three active filters and two column sorts
applied, showing how readers can interactively narrow and reorder
data." 
      loading="lazy"
    >
  </figure></div>
</p>
<p>The distinction between the two is deliberate. <code>tbl_preview()</code> is for
showing what data looks like: schema, types, a representative sample.
<code>tbl_explorer()</code> is for letting readers interact with data: sort columns
to find extremes, filter rows by values, hide irrelevant columns, and
export subsets. Together they cover the full spectrum of data
documentation needs, from a quick &ldquo;here is the structure of this
DataFrame&rdquo; to a fully explorable reference dataset. The User Guide has
dedicated pages for <a href="https://posit-dev.github.io/great-docs/user-guide/table-previews.html" target="_blank" rel="noopener">Table
Previews</a>
and <a href="https://posit-dev.github.io/great-docs/user-guide/table-explorer.html" target="_blank" rel="noopener">Table
Explorer</a>;
the latter is worth visiting just to try the interactive tables yourself
and see how sorting, filtering, and column toggling feel in practice.</p>
<h2 id="what-ten-releases-add-up-to">What Ten Releases Add Up To
</h2>
<p>Looking back across ten releases, what is striking is not any single
feature but the range of concerns the tool now addresses. Documentation
generation is the starting point, but the problems that come after
generation (discoverability, quality, accessibility,
internationalization, versioning, interactivity) are where most of the
ongoing work has gone. A documentation site is not just a rendering of
your docstrings; it is a product that needs search engine optimization,
proofreading, linting, keyboard accessibility, responsive design, and
support for readers across languages and connection speeds.</p>
<p>The guiding principle remains the same as it was in <code>v0.1</code>: you should
be able to get a good site with minimal effort! But &ldquo;minimal effort&rdquo;
scales so, for a new package, that means three commands and zero
configuration. For a mature package with international users, multiple
supported versions, and extensive user guides, it means a
<code>great-docs.yml</code> file that declares your preferences while the tool
handles the mechanics of producing a site that meets the standard you
have set.</p>
<p>Great Docs is <a href="https://pypi.org/project/great-docs/" target="_blank" rel="noopener">available on PyPI</a>
so if you maintain a Python package and want to try it:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">pip install great-docs
</span></span><span class="line"><span class="cl"><span class="nb">cd</span> your-python-project
</span></span><span class="line"><span class="cl">great-docs init
</span></span><span class="line"><span class="cl">great-docs build
</span></span><span class="line"><span class="cl">great-docs preview</span></span></code></pre></div></div>
<p>The <a href="https://posit-dev.github.io/great-docs/" target="_blank" rel="noopener">documentation site</a> covers
everything discussed here and much more. If there is a feature you wish
your documentation site had (something that would save you time, help
your readers, or make maintenance less painful), we would genuinely like
to hear about it. The best ideas for the next ten releases will come
from people who use the tool daily and notice what is missing. Open an
<a href="https://github.com/posit-dev/great-docs/issues" target="_blank" rel="noopener">issue on GitHub</a> with
your idea, however rough, and we will take it seriously!</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-05-13_great-docs-ten-things/assets/great-docs-ten-things.png" length="1750648" type="image/png" />
    </item>
    <item>
      <title>A memory model for production async R: mirai 2.7.0 and mori 0.2.0</title>
      <link>https://opensource.posit.co/blog/2026-05-12_production-async-r/</link>
      <pubDate>Tue, 12 May 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-05-12_production-async-r/</guid>
      <dc:creator>Charlie Gao</dc:creator><description><![CDATA[<p>Async R has always come with tradeoffs. Parallel batch compute meant duplicated data and an unbounded queue that could push your session out of memory, while Shiny gave you an event loop but background-worker submissions had no way to tell you when to slow down.</p>
<p>Shared memory across processes is standard in other programming languages. In R it&rsquo;s been either platform-specific (<code>fork</code> on Unix-alikes, unsafe from GUIs like Positron or with multi-threaded code) or file-format-specific (memory-mapped Parquet).</p>
<p>Two CRAN releases address those constraints directly:</p>
<ul>
<li><strong><a href="https://mirai.r-lib.org/" target="_blank" rel="noopener">mirai 2.7.0</a></strong> — <code>daemons(memory = ...)</code> puts a hard byte-level cap on the dispatcher queue. <code>try_mirai()</code> returns <code>NULL</code> immediately when that cap is hit, instead of blocking your session. The dispatcher itself runs as an in-process thread now; and through <a href="https://nanonext.r-lib.org/" target="_blank" rel="noopener">nanonext 1.9.0</a> underneath, peak memory during serialized sends has halved.</li>
<li><strong><a href="https://shikokuchuo.net/mori/" target="_blank" rel="noopener">mori 0.2.0</a></strong> — graduates from experimental status, with a stable wire format underneath. Sub-lists and extracted elements now expose stable path-form names (<code>/mori_abc[2,3]</code>) that any process can attach to via <code>map_shared()</code> directly, without going through the parent.</li>
</ul>
<h2 id="building-out-the-foundations">Building out the foundations
</h2>
<p>mirai already shipped a lot of what production async needs: microsecond round-trip latency, FIFO scheduling, task cancellation, OpenTelemetry tracing, reproducible parallel RNG, custom serialization for torch / Arrow / polars, an event-driven promises method for Shiny ExtendedTask, and deployment across SSH / Slurm / Posit Workbench.</p>
<p>What these releases add is more foundational. R hasn&rsquo;t really had a built-in memory model for asynchronous work — no way to size a task queue in bytes, and no portable way to share data across processes without paying for a full copy. mirai 2.7.0 introduces the first: a bounded queue built into the dispatcher, with <code>try_mirai()</code> exposing non-blocking submission under this constraint. mori 0.2.0 brings the second to a stable footing: a self-describing shared-memory format, addressable down to individual list elements by name, that any process on the machine can attach to.</p>
<p>Together they extend what R can credibly be used for — streaming pipelines, multi-stage data flows, and event-loop applications that previously had to reach outside the language.</p>
<h2 id="mirai-270--bounded-queues-and-try_mirai">mirai 2.7.0 — bounded queues and <code>try_mirai()</code>
</h2>
<h3 id="daemonsmemory--"><code>daemons(memory = ...)</code>
</h3>
<p>The dispatcher is the single point in your session where every <code>mirai()</code> call lands before being routed to a daemon. Each call carries its serialized arguments — the queued payload — and that payload sits in memory until a daemon is ready for it. By default the queue is unbounded, which is fine until it isn&rsquo;t.</p>
<p><code>daemons()</code> now takes a <code>memory</code> argument that caps the approximate total payload of queued tasks at the dispatcher, in MB:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">mirai</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">daemons</span><span class="p">(</span><span class="m">4</span><span class="p">,</span> <span class="n">memory</span> <span class="o">=</span> <span class="m">100</span><span class="p">)</span>  <span class="c1"># 100 MB queue capacity</span></span></span></code></pre></div></div>
<p>When the queued bytes are below the cap, <code>mirai()</code> calls submit normally. When the cap is reached, <code>mirai()</code> blocks the calling R thread until daemons drain the queue enough for the new payload to fit. Existing code keeps working — the only visible difference is that your session can no longer be pushed out of memory by a runaway producer.</p>
<p><code>status()</code> surfaces current and peak usage:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">status</span><span class="p">()</span><span class="o">$</span><span class="n">memory</span>
</span></span><span class="line"><span class="cl"><span class="c1">#     used     peak capacity </span>
</span></span><span class="line"><span class="cl"><span class="c1">#     12.4     87.3    100.0 </span></span></span></code></pre></div></div>
<p><code>peak</code> is the high-watermark queued payload over the lifetime of the dispatcher, which gives you an empirical way to size the cap. The recommended workflow is to run a representative workload with <code>memory = NULL</code> first (where <code>capacity</code> reports <code>NA</code>), observe <code>peak</code>, then set <code>memory</code> at or above that watermark.</p>
<p>If profiling isn&rsquo;t practical, the local machine has to fit your session, the dispatcher queue, and <code>n</code> daemon processes (each a full R session holding an in-flight payload while executing). Half of currently available RAM (<code>ps::ps_system_memory()[[&quot;avail&quot;]] / 2e6</code>, in MB) is a reasonable starting <code>memory</code> cap, revised down for large <code>n</code> or payloads. Remote daemons don&rsquo;t draw on the local budget, so when most workers are remote, the cap can be more generous.</p>
<p>For <code>n</code> itself, one less than your number of CPU cores is the right starting point for CPU-bound work (one core left for your session), while I/O-bound work (API calls, database queries, file reads) can comfortably exceed core count since idle daemons cost little.</p>
<p>The capacity is measured in <em>bytes</em>, not <em>task count</em>. A queue capped at 100 tasks can hold either 100 small tasks or 100 huge ones, and only the second runs out of memory. A queue capped at 100 MB never runs your session out of memory regardless of the mix.</p>
<h3 id="try_mirai"><code>try_mirai()</code>
</h3>
<p>A blocking submit is fine in a batch script, but not an event loop: a Shiny session that calls <code>mirai()</code> from inside an ExtendedTask can&rsquo;t afford to block while the queue drains, because that same session is also driving the UI for all users.</p>
<p><code>try_mirai()</code> returns immediately when the queue is full, instead of blocking. The semantics are simple:</p>
<ul>
<li>If the queue is below capacity, behaves identically to <code>mirai()</code> and returns a mirai.</li>
<li>If the queue is at capacity, returns <code>NULL</code> immediately without blocking, and leaves the caller to decide what to do next.</li>
</ul>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="n">m</span> <span class="o">&lt;-</span> <span class="nf">try_mirai</span><span class="p">({</span> <span class="nf">slow_thing</span><span class="p">()</span> <span class="p">})</span> <span class="o">%||%</span> <span class="nf">stop</span><span class="p">(</span><span class="s">&#34;queue full&#34;</span><span class="p">)</span></span></span></code></pre></div></div>
<p>The three response strategies are: drop the task (best when the work is idempotent and frequent), retry later (queue the request behind a <code>later::later()</code> call), or propagate backpressure upstream — as in the example above, by raising a condition. Which is right is application-specific — <code>try_mirai()</code> hands you the flexibility.</p>
<p>If <code>memory</code> isn&rsquo;t set, or the daemon configuration has no dispatcher, <code>try_mirai()</code> always returns a mirai and is identical to <code>mirai()</code> — so the function is safe to use unconditionally; it only does anything different in the bounded-queue case.</p>
<p>The two halves — <code>daemons(memory = ...)</code> and <code>try_mirai()</code> — are the right combination for any event-loop integration. Set the cap to reflect what your session can actually afford to hold, submit through <code>try_mirai()</code>, and the application gracefully adapts to load instead of locking up or crashing.</p>
<h3 id="under-the-hood">Under the hood
</h3>
<p>A few transport-layer changes ship alongside 2.7.0 — invisible most of the time, important when payloads are large or throughput is high:</p>
<ul>
<li><strong>Dispatcher as a thread.</strong> The dispatcher is no longer a separate R process; it runs as an in-process thread, courtesy of a C-level reimplementation in nanonext. mirai round-trip latency is now in the tens of microseconds.</li>
<li><strong>Halved peak memory on sends.</strong> <a href="https://nanonext.r-lib.org/" target="_blank" rel="noopener">nanonext 1.9.0</a>, the transport underneath mirai, now writes serialized sends directly instead of going through an intermediate buffer. A 500 MB argument going to a daemon used to need ~1 GB of momentary headroom in your session; now it needs ~500 MB. The dispatcher&rsquo;s actual resident memory tracks the <code>daemons(memory = ...)</code> cap more accurately as a result.</li>
<li><strong>&gt;2 GB payloads on macOS and Windows.</strong> Transfers above 2 GB previously failed silently near the <code>INT_MAX</code>-byte boundary on those platforms. They now go through cleanly. Linux was already fine.</li>
</ul>
<h2 id="mori-020--out-of-experimental-status">mori 0.2.0 — out of experimental status
</h2>
<p>mori is a younger package — its <a href="https://opensource.posit.co/blog/2026-04-23_mori-0-1-0/">first CRAN release</a> was just last month — and its job is the other half of memory pressure: the data itself, rather than the queue. R processes don&rsquo;t share memory, and data crosses between them through message-passing. When eight workers each need the same 200 MB data frame, that&rsquo;s 1.6 GB of serialize / transfer / deserialize, plus eight separate copies of the data resident in RAM.</p>
<p><code>mori::share()</code> writes the object once into OS-level shared memory and returns an ALTREP (alternative representation) wrapper. Multiple processes on the same machine map the same physical pages — zero-copy, lazy, and managed by R&rsquo;s garbage collector. Pass the wrapper to a daemon and only the shared memory identifier (hundreds of bytes) crosses the wire; the daemon attaches and reads the pages directly.</p>
<p>We implemented this in mori 0.1.0, but 0.2.0 takes mori out of experimental status, landing a stable wire format. A key consequence of this is that every sub-list and extracted element of a shared region now has a stable identifier of its own. <code>shared_name()</code> emits path-form names (<code>/mori_abc_1[2]</code> for the second element of region <code>/mori_abc_1</code>, <code>/mori_abc_1[2,3]</code> for nested addressing), and <code>map_shared()</code> accepts the path form directly.</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">mori</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">library</span><span class="p">(</span><span class="n">mirai</span><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">lst</span> <span class="o">&lt;-</span> <span class="nf">share</span><span class="p">(</span>
</span></span><span class="line"><span class="cl">  <span class="nf">list</span><span class="p">(</span><span class="n">weights</span> <span class="o">=</span> <span class="nf">rnorm</span><span class="p">(</span><span class="m">1e6</span><span class="p">),</span> <span class="n">targets</span> <span class="o">=</span> <span class="nf">rnorm</span><span class="p">(</span><span class="m">1e6</span><span class="p">),</span> <span class="n">inputs</span> <span class="o">=</span> <span class="nf">rnorm</span><span class="p">(</span><span class="m">1e6</span><span class="p">))</span>
</span></span><span class="line"><span class="cl"><span class="p">)</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="c1"># Sub-elements have their own path-form names</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="nf">shared_name</span><span class="p">(</span><span class="n">lst</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] &#34;/mori_abc_1&#34;</span>
</span></span><span class="line"><span class="cl"><span class="nf">shared_name</span><span class="p">(</span><span class="n">lst[[1]]</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] &#34;/mori_abc_1[1]&#34;</span></span></span></code></pre></div></div>
<p>Pass the shared object directly to a daemon — the ALTREP wrapper serializes as just the name, and the daemon attaches on deserialize:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">mirai</span><span class="p">(</span><span class="nf">length</span><span class="p">(</span><span class="n">weights</span><span class="p">),</span> <span class="n">weights</span> <span class="o">=</span> <span class="n">lst[[1]]</span><span class="p">)</span><span class="n">[]</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] 1000000</span></span></span></code></pre></div></div>
<p>A consumer in a separate session that only has the name as a string — from a config file, message queue, or HTTP response — attaches with <code>map_shared()</code>:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="c1"># In a separate R session</span>
</span></span><span class="line"><span class="cl">
</span></span><span class="line"><span class="cl"><span class="n">weights</span> <span class="o">&lt;-</span> <span class="n">mori</span><span class="o">::</span><span class="nf">map_shared</span><span class="p">(</span><span class="s">&#34;/mori_abc_1[1]&#34;</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="nf">length</span><span class="p">(</span><span class="n">weights</span><span class="p">)</span>
</span></span><span class="line"><span class="cl"><span class="c1">#&gt; [1] 1000000</span></span></span></code></pre></div></div>
<p>A path-form name is a small thing on the wire – just a string – but it enables a lot:</p>
<ul>
<li><strong>Attach by name.</strong> Path-form identifiers are plain strings, so they fit anywhere a string can go: a queue payload, a config entry, an HTTP response, a database row. A consumer reads the name, calls <code>map_shared()</code>, and gets exactly that slice — the parent region never has to enter their session.</li>
<li><strong>Decoupled pipelines.</strong> Stage A shares a 100-column frame and tells stage B the names of the three columns to process. Stage B passes the name of its output to stage C, and so on. No stage holds more in memory than it actually uses, and no stage needs to know how its inputs were produced — only the names.</li>
<li><strong>Code symmetry.</strong> <code>map_shared(shared_name(x))</code> returns an equivalent of <code>x</code> whether <code>x</code> is a root region or a single leaf element, so consumer code doesn&rsquo;t need a special branch for sub-objects.</li>
<li><strong>Lifetime through one chain.</strong> Holding a reference to a leaf keeps the whole parent region alive automatically — the garbage collector walks the chain of dependencies — so a consumer never has to manually pin the root to stay safe.</li>
</ul>
<h3 id="composing-with-daemonsmemory--">Composing with <code>daemons(memory = ...)</code>
</h3>
<img src="https://opensource.posit.co/blog/2026-05-12_production-async-r/composition-diagram.svg" data-fig-alt="Diagram: the single call try_mirai({ f(y) }, y = share(x)) forks into two effects — submission to a bounded dispatcher queue (memory = 100 MB) and writing the data once into a shared-memory region (/mori_abc_1). Both feed a single daemon: the queue carries only the name, and the daemon attaches to shared memory by name via mmap." />
<p>The two memory tools compose. <code>mori::share()</code> shrinks each task&rsquo;s <em>queued payload</em> from megabytes to hundreds of bytes, which means the dispatcher&rsquo;s <code>memory</code> cap rarely fills up at all when workers are reading shared data. The pieces are complementary:</p>
<ul>
<li><code>mori::share()</code> removes the per-worker copy of large data.</li>
<li><code>daemons(memory = ...)</code> caps the dispatcher backlog when something <em>is</em> being sent.</li>
<li><code>try_mirai()</code> makes hitting that cap a non-event in an event loop.</li>
</ul>
<h2 id="putting-r-on-the-concurrency-map">Putting R on the concurrency map
</h2>
<p>The design of <code>daemons(memory = ...)</code>, <code>try_mirai()</code>, and <code>mori::share()</code> draws on how async runtimes in other languages handle the same problems. Here&rsquo;s the comparison:</p>
<p><strong>Byte-budgeted backpressure.</strong> Async frameworks elsewhere — Python&rsquo;s <code>asyncio.Queue</code>, Ray&rsquo;s <code>max_pending_tasks</code>, Tokio&rsquo;s bounded <code>mpsc</code> channels — measure queue capacity by task count. <code>daemons(memory = ...)</code> caps by <em>bytes</em> instead, which tracks what actually drives memory pressure.</p>
<p><strong>Non-blocking submission on a full queue.</strong> Tokio&rsquo;s <code>try_send</code> is the closest direct analogue: it returns immediately when the channel is full instead of blocking. <code>try_mirai()</code> provides the same semantics for R submission, with the additional advantage that the cap itself is byte-aware.</p>
<p><strong>Integrated cross-process zero-copy.</strong> Ray&rsquo;s Plasma object store offers cross-process zero-copy, but it&rsquo;s a heavyweight component — you run a Ray cluster to use it, not a function. <code>mori::share()</code> is just a function call, producing an ALTREP wrapper that consumer processes attach to via lazy reads, down to individual columns by name.</p>
<p>Combined with mirai&rsquo;s microsecond round-trip latency and event-driven C-level transport, these three pieces give R a concurrency story that stands on its own: byte-aware backpressure, non-blocking submission, and integrated zero-copy sharing.</p>
<h2 id="try-it">Try it
</h2>
<p>Install from CRAN:</p>
<div class="code-block"><div class="highlight"><pre tabindex="0" class="chroma"><code class="language-r" data-lang="r"><span class="line"><span class="cl"><span class="nf">install.packages</span><span class="p">(</span><span class="nf">c</span><span class="p">(</span><span class="s">&#34;mirai&#34;</span><span class="p">,</span> <span class="s">&#34;mori&#34;</span><span class="p">))</span></span></span></code></pre></div></div>
<p>Pointers from here:</p>
<ul>
<li><a href="https://mirai.r-lib.org/articles/v01-reference.html" target="_blank" rel="noopener">mirai reference vignette</a> — the memory-management section walks through <code>daemons(memory = ...)</code>, <code>status()$memory</code>, <code>try_mirai()</code>, and shared-memory composition.</li>
<li><a href="https://mirai.r-lib.org/articles/v02-promises.html" target="_blank" rel="noopener">mirai promises vignette</a> — Shiny ExtendedTask and event-driven async.</li>
<li><a href="https://shikokuchuo.net/mori/" target="_blank" rel="noopener">mori package site</a> — <code>share()</code>, <code>shared_name()</code> and <code>map_shared()</code>.</li>
<li><a href="https://nanonext.r-lib.org/" target="_blank" rel="noopener">nanonext package site</a> — the transport layer and concurrency primitives.</li>
<li>The <code>r-lib</code> agent skill in the <a href="https://github.com/posit-dev/skills" target="_blank" rel="noopener"><code>posit-dev-skills</code></a> plugin includes an LLM-optimised mirai guide for AI coding assistants.</li>
</ul>
<p>Issues, feedback, and questions are very welcome on GitHub: <a href="https://github.com/r-lib/mirai/issues" target="_blank" rel="noopener">r-lib/mirai</a>, <a href="https://github.com/r-lib/nanonext/issues" target="_blank" rel="noopener">r-lib/nanonext</a>, <a href="https://github.com/shikokuchuo/mori/issues" target="_blank" rel="noopener">shikokuchuo/mori</a>.</p>
<h2 id="acknowledgments">Acknowledgments
</h2>
<p>A big thanks to everyone who contributed to mirai 2.7.0, mori 0.2.0, and nanonext 1.9.0 through their issues, pull requests, and discussions: <a href="https://github.com/HenrikBengtsson" target="_blank" rel="noopener">@HenrikBengtsson</a>, <a href="https://github.com/kentqin-cve" target="_blank" rel="noopener">@kentqin-cve</a>, <a href="https://github.com/king-of-poppk" target="_blank" rel="noopener">@king-of-poppk</a>, <a href="https://github.com/manforkr" target="_blank" rel="noopener">@manforkr</a>, <a href="https://github.com/mcol" target="_blank" rel="noopener">@mcol</a>, <a href="https://github.com/michaelmayer2" target="_blank" rel="noopener">@michaelmayer2</a>, <a href="https://github.com/pmac0451" target="_blank" rel="noopener">@pmac0451</a>, <a href="https://github.com/t-kalinowski" target="_blank" rel="noopener">@t-kalinowski</a>, and <a href="https://github.com/wlandau" target="_blank" rel="noopener">@wlandau</a>.</p>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-05-12_production-async-r/featured.jpg" length="261721" type="image/jpeg" />
    </item>
    <item>
      <title>Positron May Release Highlights</title>
      <link>https://opensource.posit.co/blog/2026-05-11_positron-2026-05-release/</link>
      <pubDate>Mon, 11 May 2026 00:00:00 +0000</pubDate>
      <guid>https://opensource.posit.co/blog/2026-05-11_positron-2026-05-release/</guid>
      <dc:creator>Julia Silge</dc:creator><description><![CDATA[<div class="callout callout-note" role="note" aria-label="Note">
<div class="callout-header">
<span class="callout-title">Note</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co" target="_blank" rel="noopener">Positron</a> is Posit&rsquo;s new, next-generation IDE for data science. Positron is designed to be an extensible, polyglot tool for exploring data and reproducible authoring in Python, R, and more.</p>
</div>
</div>
<p>Welcome back to another edition of our monthly Positron updates! Each month we share highlights from our <a href="https://positron.posit.co/release-notes" target="_blank" rel="noopener">latest release</a> and useful resources.</p>
<h2 id="product-updates">Product updates
</h2>
<h3 id="announcing-the-packages-pane">Announcing the Packages pane
</h3>
<p>Our <a href="https://positron.posit.co/packages-pane" target="_blank" rel="noopener">Packages pane</a> is available in this release as a preview feature. The new Packages pane brings streamlined package management directly into the IDE so you can see at a glance what&rsquo;s installed, what&rsquo;s attached, and what&rsquo;s out of date.</p>
<img src="https://opensource.posit.co/blog/2026-05-11_positron-2026-05-release/packages-pane-attached.gif" data-fig-alt="The Positron Packages pane in the Primary Sidebar, showing installed packages with attach status and a search field; the user attaches a package and the row updates to show it as attached." />
<p>Find it in the Primary Sidebar by clicking the package icon. Positron automatically detects and integrates with your current interpreter; today we have support for pip, venv, uv, and conda for Python, plus base R, pak, and renv for R. You can search, sort, install, update, remove, filter by category (including &ldquo;Outdated Packages&rdquo;), and even see which packages are attached in your current session. To hide the pane, disable <a href="positron://settings/positron.packages.enable"><code>positron.packages.enable</code></a>. <a href="https://github.com/posit-dev/positron/discussions" target="_blank" rel="noopener">Let us know</a> how it fits into your workflow.</p>
<h3 id="inline-output-for-quarto">Inline output for Quarto
</h3>
<p>Inline output for <code>.qmd</code> documents was one of Positron&rsquo;s most-requested features ever, with many users telling us it was the one thing keeping them tied to RStudio or Jupyter notebooks. We are happy to share that Quarto inline output is available this release as a preview feature.</p>
<img src="https://opensource.posit.co/blog/2026-05-11_positron-2026-05-release/quarto-inline-output.png" data-fig-alt="A Quarto document open in Positron showing inline output beneath an executed code cell, with an execution status line indicating runtime." />
<p>Quarto kernels start automatically when you open a document with inline output, so you don&rsquo;t wait for a kernel on first execution. Each inline output cell shows a status line with execution status and elapsed time, and outputs are now collapsible when they get long. We&rsquo;ve also added a tool to switch the interpreter used for inline output, support for <em>Show Notebook Console</em> for Quarto documents, and fixed a handful of papercuts around execution indicators, autocompletion, and the kernel selector. Enable inline output via the <a href="positron://settings/positron.quarto.inlineOutput.enabled"><code>positron.quarto.inlineOutput.enabled</code></a> setting.</p>
<h3 id="extensions-available-from-posit-public-package-manager">Extensions available from Posit Public Package Manager
</h3>
<p>Positron&rsquo;s <a href="https://positron.posit.co/extensions" target="_blank" rel="noopener">extension gallery</a> now uses <a href="https://p3m.dev" target="_blank" rel="noopener">Posit Public Package Manager</a> (P3M) by default, replacing Open VSX as the source for browsing and installing extensions. The catalog is the same set of extensions you already use, served from Posit infrastructure for reliable distribution. If you prefer to stay on Open VSX, the new <a href="positron://settings/positron.extensions.gallerySource"><code>positron.extensions.gallerySource</code></a> setting lets you switch back at any time. For organizations on Posit Workbench, <a href="https://posit.co/blog/manage-vs-code-extensions-like-packages-with-posit-package-manager-2026-04-0/" target="_blank" rel="noopener">Posit Package Manager also added support for mirroring and serving VS Code extensions</a> from your own instance, bringing the same governance, security, and air-gapped distribution to extensions that you already have for R and Python packages.</p>
<h3 id="positron-notebook-editor-is-now-in-beta">Positron Notebook Editor is now in beta
</h3>
<p>The <a href="https://positron.posit.co/positron-notebook-editor" target="_blank" rel="noopener">Positron Notebook Editor</a> for <code>.ipynb</code> files is officially moving from alpha to beta.</p>
<img src="https://opensource.posit.co/blog/2026-05-11_positron-2026-05-release/notebook-rstats.gif" data-fig-alt="Working with an R Jupyter notebook in the Positron Notebook Editor." />
<p>This release brings a wave of polish and reliability we hope makes it ready for your daily workflow:</p>
<ul>
<li>
<p><strong>Cleaner git diffs by default:</strong> New <a href="positron://settings/notebook.save.outputs"><code>notebook.save.outputs</code></a> and <a href="positron://settings/notebook.save.executionCounts"><code>notebook.save.executionCounts</code></a> settings let you exclude outputs and execution counts from saved notebook files so reviewers see only the relevant code changes.</p>
</li>
<li>
<p><strong>Stronger R support:</strong> R notebooks can now be debugged with breakpoints and the &ldquo;Debug Cell&rdquo; action, and R dataframe outputs in notebook cells now render in the inline Data Explorer for the same interactive exploration you get in the Console.</p>
</li>
<li>
<p><strong>Help on F1:</strong> Pressing <kbd>F1</kbd> on a function in a notebook cell now opens the <a href="https://positron.posit.co/help-pane" target="_blank" rel="noopener">Help pane</a>, just like in <code>.py</code> and <code>.R</code> files. Works for both Python and R Jupyter notebooks.</p>
</li>
<li>
<p><strong>UX polish:</strong> Long cell outputs default to scrolling instead of overwhelming the page, the cell action bar stays visible as you scroll through tall cells, markdown cells render footnotes, and your scroll position is preserved when switching between notebook tabs.</p>
</li>
</ul>
<h3 id="ai-chat-and-code-assistance">AI chat and code assistance
</h3>
<p>This release introduces two new AI options: Posit Assistant and Posit AI.</p>
<ul>
<li>
<p>You may have tried some of our previous experiments for AI code assistance available in Positron, Positron Assistant and/or Databot. We are now migrating to a more data-science-specific, holistic, unified approach to AI chat, built on the same Posit Assistant interface that <a href="https://posit.co/blog/introducing-ai-in-rstudio/" target="_blank" rel="noopener">we recently brought to RStudio</a>. We acknowledge that the number of different tools we&rsquo;ve made available and how their names have evolved have been pretty confusing, but this space is changing fast and our initial experiments have helped us understand the best ways to integrate AI into your data science workflow. With Posit Assistant, instead of switching between tools, you get code generation, next edit suggestions, chat, and agentic tools all in one place. <a href="https://posit-dev.github.io/assistant/docs/downloads/positron/" target="_blank" rel="noopener">Learn how to get started</a>.</p>
</li>
<li>
<p><a href="https://posit.co/products/ai" target="_blank" rel="noopener">Posit AI</a> is a new, optional model provider service available to use with Posit Assistant. Posit AI is a subscription priced at $20/month for individual users; both Positron and Posit Assistant remain free to use. <a href="https://posit.co/products/ai" target="_blank" rel="noopener">Learn how to sign up for an account</a>.</p>
</li>
</ul>
<p>This release also provides additional improvements to the broader Assistant experience. Each Assistant response now shows which model generated it, so you always know whether you&rsquo;re getting output from Claude, GPT-5, or another provider. We also improved the documentation and error handling for Microsoft Foundry and prompt caching for AWS Bedrock.</p>
<h2 id="whats-coming-next">What&rsquo;s coming next
</h2>
<ul>
<li><a href="https://opensource.posit.co/blog/2026-04-07_april-newsletter/" target="_blank" rel="noopener">Last release</a>, we announced <a href="https://opensource.posit.co/blog/2026-04-06_positron-server-jupyterhub/" target="_blank" rel="noopener">Positron Server</a> for academic use in JupyterHub, making it free to host Positron for teaching. We are now expanding support for Positron Server on Open OnDemand. If you are interested in using Positron for teaching, <a href="https://opensource.posit.co/blog/2026-04-06_positron-server-jupyterhub/" target="_blank" rel="noopener">follow the instructions</a> to get a teaching license and <a href="https://scheduler.zoom.us/cindy-tong/positron-education" target="_blank" rel="noopener">book time with our team</a> to discuss your use case.</li>
<li>We&rsquo;re prototyping first-class SQL editing and execution in Positron, including support for visualizations with <a href="https://opensource.posit.co/blog/2026-04-20_ggsql_alpha_release/" target="_blank" rel="noopener">ggsql</a>. We&rsquo;ll share more as this takes shape, and in the meantime, <a href="https://github.com/posit-dev/positron/issues/7233" target="_blank" rel="noopener">let us know</a> your thoughts and current pain points.</li>
<li>Join <a href="https://posit.co/webinar/compliance-without-friction-mastering-the-persistent-analysis-lifecycle" target="_blank" rel="noopener">our next webinar</a> on May 12 to learn how Positron, Workbench, Connect, and Package Manager work together in the reproducible analysis lifecycle.</li>
</ul>
<div class="callout callout-tip" role="note" aria-label="Tip">
<div class="callout-header">
<span class="callout-title">Tip</span>
</div>
<div class="callout-body">
<p><a href="https://positron.posit.co/download" target="_blank" rel="noopener">Download Positron</a> to try out the new features and improvements in this release!</p>
</div>
</div>
]]></description>
      <enclosure url="https://opensource.posit.co/blog/2026-05-11_positron-2026-05-release/featured.svg" length="97354" type="image/svg&#43;xml" />
    </item>
  </channel>
</rss>
