Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude/skills/release-shinyreact/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ does **not** oblige releasing the others.
| Package | Version lives in | Tag | Automation |
|---|---|---|---|
| `shinyreact` (PyPI) | `pyproject.toml` (repo root) `version` | `py/v1.2.3` | `.github/workflows/release-py.yaml` |
| `@posit/shinyreact` (npm) | `pkg-js/package.json` `version` | `js/v1.2.3` | `.github/workflows/release-js.yaml` |
| `@posit-dev/shinyreact` (npm) | `pkg-js/package.json` `version` | `js/v1.2.3` | `.github/workflows/release-js.yaml` |
| `shinyreact` (R) | `pkg-r/DESCRIPTION` `Version` | `r/v1.2.3` | none — CRAN is a manual submission |

The `<lang>/v` prefix is what keeps the three apart; nothing else distinguishes
Expand Down Expand Up @@ -93,5 +93,5 @@ produces the same tidyverse checklist.
## After any release

Nothing is automated post-release. Check the artifact actually landed
(`pip index versions shinyreact`, `npm view @posit/shinyreact version`) and
(`pip index versions shinyreact`, `npm view @posit-dev/shinyreact version`) and
say so — a green workflow with a skipped publish step is the usual failure.
4 changes: 2 additions & 2 deletions .claude/skills/shinyreact-build-app/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ root.render(<App />);
```

Hooks come off the same global: `const { useShinyInput } = window.shinyreact;`.
(An npm-tier app that installs `@posit/shinyreact` imports them instead and
(An npm-tier app that installs `@posit-dev/shinyreact` imports them instead and
externalizes React the same way; reach for that only when you are publishing a
component library, not for an app.)

Expand Down Expand Up @@ -242,7 +242,7 @@ identical; React-owned state is still the default for inputs.

## Step 4 — the client reads and writes named channels

Everything is on `window.shinyreact` (or imported from `@posit/shinyreact` in
Everything is on `window.shinyreact` (or imported from `@posit-dev/shinyreact` in
an npm-tier build).

| | Full | Read-only | Write-only |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release-js.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: release-js

# Publishes @posit/shinyreact to npm. Trigger manually (workflow_dispatch) or
# Publishes @posit-dev/shinyreact to npm. Trigger manually (workflow_dispatch) or
# by pushing a tag like js/v0.0.1 (the `js/` prefix distinguishes it from the
# Python and R release tags). Requires the NPM_TOKEN repository secret.
on:
Expand Down Expand Up @@ -56,7 +56,7 @@ jobs:
working-directory: pkg-js
run: npm run build

- name: 🚀 Publish @posit/shinyreact
- name: 🚀 Publish @posit-dev/shinyreact
working-directory: pkg-js
run: npm publish --access public --provenance
env:
Expand Down
4 changes: 2 additions & 2 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ my-app/
ui.css
```

The author owns `app.py`. Everything else is generated by AI (Claude or equivalent), typically via a Claude Skill or similar tooling that understands Shiny's client-server bridge. The `package.json` declares dependencies on the Shiny client runtime (`@posit/shiny`), the bridge hooks (`@posit/shinyreact`), React, and any component libraries. The build step (managed by `shiny run` or equivalent) resolves dependencies and bundles `src/` into `dist/`.
The author owns `app.py`. Everything else is generated by AI (Claude or equivalent), typically via a Claude Skill or similar tooling that understands Shiny's client-server bridge. The `package.json` declares dependencies on the Shiny client runtime (`@posit/shiny`), the bridge hooks (`@posit-dev/shinyreact`), React, and any component libraries. The build step (managed by `shiny run` or equivalent) resolves dependencies and bundles `src/` into `dist/`.

### What the server file contains

Expand Down Expand Up @@ -287,7 +287,7 @@ In ui.tsx-first apps, `shiny run` should explicitly disable HTMLDependency injec

### Shiny client runtime as an npm package

`shiny.js` and the bridge hooks need to be published as `@posit/shiny` and `@posit/shinyreact` so client bundlers can resolve them via `package.json` instead of HTMLDependency injection — replacing IIFE bundles, `window.shinyreact` globals, and ad-hoc React deduplication. Significant upstream ask to the Shiny team. Tracked in [#28](https://github.com/posit-dev/shinyreact/issues/28); the hybrid distribution plan (npm runtime alongside the HTMLDependency zero-build tier) is decided in `decisions/2026-08-17-js-distribution.md`.
`shiny.js` and the bridge hooks need to be published as `@posit/shiny` and `@posit-dev/shinyreact` so client bundlers can resolve them via `package.json` instead of HTMLDependency injection — replacing IIFE bundles, `window.shinyreact` globals, and ad-hoc React deduplication. Significant upstream ask to the Shiny team. Tracked in [#28](https://github.com/posit-dev/shinyreact/issues/28); the hybrid distribution plan (npm runtime alongside the HTMLDependency zero-build tier) is decided in `decisions/2026-08-17-js-distribution.md`.

### `shiny run` build integration

Expand Down
12 changes: 6 additions & 6 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ R has no e2e suite, so no `(e2e)` leaf covers R (issue #194).
role="alert">` is appended to `<body>` carrying the same text as the
thrown error (e2e)
- literals in the message (both versions, `#shinyreact-config`,
`@posit/shinyreact`) are marked with backticks and render as `<code>`
`@posit-dev/shinyreact`) are marked with backticks and render as `<code>`
chips; the backticks are stripped from the thrown error and the
console-only warning (e2e)
- the message is written with `textContent`, never `innerHTML`, so a
Expand Down Expand Up @@ -109,7 +109,7 @@ R has no e2e suite, so no `(e2e)` leaf covers R (issue #194).
- `[js]` whether a *missing* tag is fatal depends on which build is running
- IIFE bundle (shipped inside the R/Python packages): tolerated, because a
hand-wired `page_bare()` page legitimately has no tag
- npm ESM build (`@posit/shinyreact`): fatal, opted into at import time —
- npm ESM build (`@posit-dev/shinyreact`): fatal, opted into at import time —
an independently installed client meeting a tagless page means the server
predates the protocol
- server → client custom message: `shinyReactMessage`, payload `{id, data}`
Expand Down Expand Up @@ -649,7 +649,7 @@ registries are exposed on `window.Shiny.reactRegistry`; the message registry on

### `ShinyReactComponentElement` (not exported)

- not reachable from `window.shinyreact`, `@posit/shinyreact`, or
- not reachable from `window.shinyreact`, `@posit-dev/shinyreact`, or
`shiny-react/index.ts`; the class and its unit test stay in the tree until
partial React app support brings it back
- a base class for custom elements that mount a React component with Shiny
Expand Down Expand Up @@ -690,7 +690,7 @@ the shinyreact bundle dependency and the `#shinyreact-config` tag — except
- it defaults to `"server"` — the package serves both as an `HTMLDependency`
- `"client"` omits **both files**; the `#shinyreact-config` tag is still
emitted, because the npm-tier client hard-errors without it
- it is for the npm tier: a client importing `@posit/shinyreact` bundles its own
- it is for the npm tier: a client importing `@posit-dev/shinyreact` bundles its own
copy, so serving them too puts two copies of React and the hooks on the page
- any other value raises, naming the bad value and both valid ones
- `[py]` `ValueError`; `[r]` `cli_abort`
Expand Down Expand Up @@ -1272,7 +1272,7 @@ initial page.
assignment — nothing writes that namespace before the bundle runs
- CSS is a side-effect import, which is how Vite bundles it
- a missing `#shinyreact-config` tag is tolerated
- the npm ESM build (`@posit/shinyreact`) is for bundler-tier apps
- the npm ESM build (`@posit-dev/shinyreact`) is for bundler-tier apps
- React and ReactDOM are **peer** dependencies resolved by the app's bundler,
so dev builds get a development React with Fast Refresh
- hooks are imported directly rather than read off `window.shinyreact`
Expand All @@ -1283,7 +1283,7 @@ initial page.
- it exports `PROTOCOL_VERSION`
- it does **not** re-export React / ReactDOM
- the stylesheet ships as `dist-npm/style.css`, reachable as
`@posit/shinyreact/styles` (#229; the build emitted no CSS at all before,
`@posit-dev/shinyreact/styles` (#229; the build emitted no CSS at all before,
leaving `ImageOutput`'s spinner without its `@keyframes spin`)
- Vite does not inject a CSS import into a lib-mode ESM bundle, so
consumers opt in rather than having it forced on them
Expand Down
10 changes: 5 additions & 5 deletions decisions/2026-08-17-js-distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,12 +116,12 @@ payoff); would ship React ESM builds inside pip/CRAN packages.

## Decision

Adopt **option E**, with the npm package named **`@posit/shinyreact`** (not
Adopt **option E**, with the npm package named **`@posit-dev/shinyreact`** (not
`@posit/shiny-react` — name consistency with the Python/R packages; the
upstream `@posit/shiny-react`@0.0.16 that `pkg-js/src/shiny-react/` was vendored
from is a separate, frozen artifact).

1. **Publish `@posit/shinyreact` to npm as the real runtime** — the hooks and
1. **Publish `@posit-dev/shinyreact` to npm as the real runtime** — the hooks and
components as ESM, with `react`/`react-dom` as peer dependencies. The
bundler tier imports it directly and never touches the global. Dev mode
gets a dev React and Fast Refresh with no bridge-alias workaround. Built
Expand All @@ -140,7 +140,7 @@ from is a separate, frozen artifact).
and gives the handshake its delivery vehicle. This change is independent of
npm publishing and should land first.

4. **Semver protocol handshake at hook boot.** `@posit/shinyreact` declares a
4. **Semver protocol handshake at hook boot.** `@posit-dev/shinyreact` declares a
supported protocol range (e.g. `^1`); on init it checks the
server-rendered `protocolVersion` and fails fast with a message naming both
versions and the fix. The protocol version bumps only when the wire format
Expand Down Expand Up @@ -204,9 +204,9 @@ The rule, then:
`global.ts` reading the tag instead of `_restore`.
2. Protocol schema document + cross-language fixture tests.
3. Package `pkg-js/src/` for dual output (ESM + IIFE); add publish workflow for
`@posit/shinyreact`.
`@posit-dev/shinyreact`.
4. Convert one Vite example (`09-hmr` is the natural candidate) to import
`@posit/shinyreact`; retire its dev/prod bridge alias. Done in #217, which
`@posit-dev/shinyreact`; retire its dev/prod bridge alias. Done in #217, which
also added the `shinyreact_js="client"` page-entry-point switch the
conversion needs — an npm-tier page must not *also* be served the IIFE
bundle.
Expand Down
2 changes: 1 addition & 1 deletion docs/posit-conf-2026-goals.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ Remaining known issues are tracked in the [GitHub issue tracker](https://github.

The vendored `shiny-react` code at `pkg-js/src/shiny-react/` and the broader Shiny JS infrastructure should live in its own repository, independent of `shinyreact`. This enables:

- Other packages (not just `shinyreact`) to depend on the published `@posit/shinyreact` npm package (naming decided in `decisions/2026-08-17-js-distribution.md`)
- Other packages (not just `shinyreact`) to depend on the published `@posit-dev/shinyreact` npm package (naming decided in `decisions/2026-08-17-js-distribution.md`)
- Independent versioning and release cycle
- Cleaner separation of concerns between Shiny's JS plumbing and `shinyreact`'s rendering

Expand Down
6 changes: 3 additions & 3 deletions examples/09-hmr/FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ a unit test; `(verify)` marks a claim not yet checked against the code.
- builds `pkg-js` first (`npm install` + `npm run build` there) when
`pkg-js/dist-npm` is missing — the `file:../../pkg-js` dep's `exports`
point into `dist-npm/`, which is not committed, so this app's own build
cannot resolve `@posit/shinyreact` until it exists
cannot resolve `@posit-dev/shinyreact` until it exists
- then `npm install` + `npm run build` in the app directory
- without it, Shiny raises `RuntimeError: Directory '.../www' does not exist`
at startup — `www/` itself does not exist until a build creates it
Expand All @@ -30,7 +30,7 @@ a unit test; `(verify)` marks a claim not yet checked against the code.

## npm tier

- the only example that imports `@posit/shinyreact` instead of destructuring
- the only example that imports `@posit-dev/shinyreact` instead of destructuring
`window.shinyreact`; the built `www/ui.js` contains no `window.shinyreact`
reference at all
- until the first npm publish, the dependency is `file:../../pkg-js`, so
Expand All @@ -51,7 +51,7 @@ a unit test; `(verify)` marks a claim not yet checked against the code.
- `www/ui.js` is gitignored, so a fresh checkout must run one of the two before
`shiny run` serves anything
- there is no dev/prod hook switch any more: both modes import
`@posit/shinyreact`, so no `shiny-bridge` alias and no rollup externals
`@posit-dev/shinyreact`, so no `shiny-bridge` alias and no rollup externals

## The dev stub (`vite-dev-stub.js`)

Expand Down
6 changes: 3 additions & 3 deletions examples/09-hmr/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ serves the page: edit a component and it hot-swaps in place, keeping component
state — no full page reload, no `vite build` wait.

This is the **npm-tier** example: the client imports the hooks from
`@posit/shinyreact` and bundles its own React, in both modes. That is what
`@posit-dev/shinyreact` and bundles its own React, in both modes. That is what
makes Fast Refresh possible — Fast Refresh needs a *development* React build,
and the React inside the server-shipped IIFE bundle is production-only.

Expand All @@ -14,10 +14,10 @@ Because the client ships the runtime itself, the server must not serve
`set_react_page(shinyreact_js="client")` in `app.py`. The `#shinyreact-config`
tag is still emitted, and the npm client requires it.

Until `@posit/shinyreact` is published, `package.json` depends on it as
Until `@posit-dev/shinyreact` is published, `package.json` depends on it as
`file:../../pkg-js` — repo-relative, so it only resolves inside a checkout of
this repo. Copy this example elsewhere and that line becomes
`"@posit/shinyreact": "^<version>"` from npm. In the meantime the package has
`"@posit-dev/shinyreact": "^<version>"` from npm. In the meantime the package has
to be built before this example can build against it:

```bash
Expand Down
4 changes: 2 additions & 2 deletions examples/09-hmr/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
_app_dir = Path(__file__).parent
if not (_app_dir / "www" / "ui.js").exists():
print("www/ui.js not found -- building the client bundle...", file=sys.stderr)
# `@posit/shinyreact` is a `file:../../pkg-js` dep whose exports point at
# `@posit-dev/shinyreact` is a `file:../../pkg-js` dep whose exports point at
# `dist-npm/`, which is not committed -- so the package has to be built
# before this app's own build can resolve it.
_pkg_js = _app_dir.parent.parent / "pkg-js"
Expand All @@ -22,7 +22,7 @@
subprocess.run(["npm", "install"], cwd=_app_dir, check=True)
subprocess.run(["npm", "run", "build"], cwd=_app_dir, check=True)

# npm tier: the client imports `@posit/shinyreact` and bundles shinyreact.js
# npm tier: the client imports `@posit-dev/shinyreact` and bundles shinyreact.js
# itself, so the server must not serve it too -- two copies on one page. The
# `#shinyreact-config` tag is still emitted, and the npm client requires it.
set_react_page(shinyreact_js="client")
Expand Down
2 changes: 1 addition & 1 deletion examples/09-hmr/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"test": "node --test"
},
"dependencies": {
"@posit/shinyreact": "file:../../pkg-js"
"@posit-dev/shinyreact": "file:../../pkg-js"
},
"devDependencies": {
"@vitejs/plugin-react": "^4.3.0",
Expand Down
2 changes: 1 addition & 1 deletion examples/09-hmr/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useEffect, useState } from "react";

import { useShinyInitialized, useShinyInput, useShinyOutputValue } from "@posit/shinyreact";
import { useShinyInitialized, useShinyInput, useShinyOutputValue } from "@posit-dev/shinyreact";

// Exported component = a Fast Refresh boundary. Editing this file (e.g. the
// heading text below) hot-swaps the component WITHOUT losing `count`.
Expand Down
4 changes: 2 additions & 2 deletions examples/09-hmr/vite.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ export default defineConfig(({ command }) => ({
shinyreactDevStub({ entry: ENTRY, outFile: "www/ui.js" }),
],
resolve: {
// `@posit/shinyreact` is a `file:` dep, so it is symlinked and brings its
// `@posit-dev/shinyreact` is a `file:` dep, so it is symlinked and brings its
// own node_modules. Without dedupe, App.tsx and the hooks would each get a
// React copy and every hook call would throw.
dedupe: ["react", "react-dom"],
},
server: {
port: 5173,
strictPort: true, // keep the stub's hard-coded :5173 honest
// `@posit/shinyreact` resolves through a symlink to the repo's pkg-js/.
// `@posit-dev/shinyreact` resolves through a symlink to the repo's pkg-js/.
fs: { allow: [repoRoot] },
},
build: {
Expand Down
2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ see [Example behavior trees](#example-behavior-trees) below.
| [06-data-frame](06-data-frame/) | Embeds `@render.data_frame` via `ShinyOutput` and `set_react_page()` |
| [07-plotly](07-plotly/) | Embeds `@render_plotly` via `ShinyOutput` and `set_react_page()`. Also ships an `app.R` using `plotly::renderPlotly()` over the same `www/` client — its binding JS is discovered from the render function and pushed automatically |
| [08-input-handler](08-input-handler/) | `useShinyInput` with `type="shiny.datetime"` — client sends unix seconds; server `input.when()` is a `datetime.datetime` via Shiny's built-in handler |
| [09-hmr](09-hmr/) | React Fast Refresh in dev (Vite dev server alongside Shiny). The npm tier: imports `@posit/shinyreact` and bundles its own React, with `set_react_page(shinyreact_js="client")` so the server doesn't also serve shinyreact.js |
| [09-hmr](09-hmr/) | React Fast Refresh in dev (Vite dev server alongside Shiny). The npm tier: imports `@posit-dev/shinyreact` and bundles its own React, with `set_react_page(shinyreact_js="client")` so the server doesn't also serve shinyreact.js |
| [10-bookmarking](10-bookmarking/) | Bookmark restoration: URL query string (or server-stored state) hydrates `useShinyInput` initial values via the `#shinyreact-config` tag emitted by `page_react()` |

## Running an example
Expand Down
4 changes: 2 additions & 2 deletions pkg-js/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg-js/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@posit/shinyreact",
"name": "@posit-dev/shinyreact",
"version": "0.0.1",
"description": "React hooks and components for Shiny (shinyreact) — the ui.tsx pattern's client runtime",
"license": "MIT",
Expand Down
6 changes: 3 additions & 3 deletions pkg-js/src/npm.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* `@posit/shinyreact` — the ESM entry for bundler-tier apps.
* `@posit-dev/shinyreact` — the ESM entry for bundler-tier apps.
*
* Import the hooks directly instead of reading `window.shinyreact`; React and
* ReactDOM are peer dependencies resolved by your bundler (so dev builds get
Expand Down Expand Up @@ -32,7 +32,7 @@ import "./shinyreact.css";
import { installDepDiscovery } from "./dep-discovery";
import { requireShinyReactConfigTag } from "./shiny-react/config";

// Two copies on one page: this app bundles `@posit/shinyreact` AND the server
// Two copies on one page: this app bundles `@posit-dev/shinyreact` AND the server
// served shinyreact.js, because the page entry point left `shinyreact_js` at its
// default of "server". It still works — the registries are page-scoped, so the
// two copies share one set of inputs, outputs, and message handlers — but the
Expand All @@ -50,7 +50,7 @@ if (
) {
console.warn(
"[shinyreact] shinyreact.js is loaded twice on this page: the server " +
"served it, and this app also imports @posit/shinyreact. The app works, " +
"served it, and this app also imports @posit-dev/shinyreact. The app works, " +
"but it is downloading a second copy of React and the hooks for " +
'nothing. Pass shinyreact_js="client" to your page entry point — ' +
"page_react(), page_react_html(), set_react_page(), or ReactApp().",
Expand Down
2 changes: 1 addition & 1 deletion pkg-js/src/shiny-react/bookmark.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export function applyRestoredValues(registry: InputRegistry): void {
if (config == null && isShinyReactConfigTagRequired()) {
throwVisibly(
"shinyreact: no `#shinyreact-config` tag found in this page. The " +
"`@posit/shinyreact` client requires a shinyreact server recent " +
"`@posit-dev/shinyreact` client requires a shinyreact server recent " +
"enough to emit it — upgrade the shinyreact Python/R package.",
);
}
Expand Down
Loading
Loading