Skip to content

feat: #shinyreact-config tag + protocol handshake, decide JS distribution - #198

Open
schloerke wants to merge 4 commits into
schloerke/rename-pkg-jsfrom
schloerke/npm-distribution
Open

feat: #shinyreact-config tag + protocol handshake, decide JS distribution#198
schloerke wants to merge 4 commits into
schloerke/rename-pkg-jsfrom
schloerke/npm-distribution

Conversation

@schloerke

@schloerke schloerke commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

Refs #172. Stacked on #197 (base is schloerke/rename-pkg-js; retarget to main once #197 merges).

Summary

Implements step 1 of the JS-distribution decision and checks in the decision record itself (decisions/2026-08-17-js-distribution.md: hybrid npm distribution — publish @posit/shinyreact as a real ESM runtime while keeping the IIFE HTMLDependency as the zero-build tier, with a wire-protocol handshake instead of lockstep releases).

The #shinyreact-config script tag. Page entry points (Python _dep_page(), R shinyreact_dep_page()) now always emit <script type="application/json" id="shinyreact-config">{"protocolVersion":"1.0","restore":{...}}</script>. This replaces the inline JS that pre-seeded window.shinyreact._restore — the one structural dependency on the global existing before the bundle runs — with inert JSON that any future ESM client can read too. Because the payload is never parsed as JavaScript, the two-layer JS-string-literal escaping (and the U+2028/U+2029 handling from #183) collapses to a single rule: every < is emitted as the JSON escape \u003c.

Protocol handshake. PROTOCOL_VERSION = "1.0" is declared in all three languages (pkg-js/src/shiny-react/config.ts, pkg-py/src/shinyreact/_protocol.py, pkg-r/R/protocol.R); each language has a parity test pinning the three strings equal. The JS client asserts the major version at boot and throws naming both versions on mismatch — this is what lets a future npm-installed client and a pip/CRAN server release independently.

Back-compat. The client still falls back to the legacy window.shinyreact._restore global when the config tag is absent (a server predating this change), and the -applied/-values DevTools sentinel is unchanged.

Verification

  • Python: 74 unit tests, pyright, ruff, and all 9 Playwright e2e tests pass (the e2e bookmark tests exercise the new tag end-to-end through a real browser).
  • R: 122 testthat tests pass; test helpers moved to helper-config.R and mirrored against the Python test names per the parity policy.
  • JS: 177 vitest tests pass (new config.test.ts plus config-tag cases in use-shiny-restore.test.tsx), tsc --noEmit clean.
  • make update-dist rebuilt and synced the bundle to both language packages.

Reproduce: run a bookmarkable app (examples/10-bookmarking), view source — the head now contains the #shinyreact-config JSON tag instead of a window.shinyreact._restore = JSON.parse(...) script, and restored inputs behave identically.

- Add decisions/2026-08-17-js-distribution.md: hybrid npm runtime
  (@posit/shinyreact) + HTMLDependency zero-build tier, protocol
  handshake via a #shinyreact-config script tag
- Update DESIGN.md and conf-2026 goals to the @posit/shinyreact name
- Page entry points now always emit <script type="application/json"
  id="shinyreact-config"> carrying protocolVersion and, when a bookmark
  is restoring, the restored input values — replacing the inline JS
  that set window.shinyreact._restore
- JS client reads the tag (legacy _restore global kept as fallback) and
  asserts the protocol major version, failing fast with both versions
  named on mismatch
- PROTOCOL_VERSION declared in all three languages with parity tests
  pinning them to the same string
- Step 1 of decisions/2026-08-17-js-distribution.md; unblocks
  publishing @posit/shinyreact without the global as bootstrap
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant