Skip to content

Real-time collaborative editing for R and Shiny applications, built on Automerge CRDT.

Ask DeepWiki

Installation

pak::pak("shikokuchuo/shinysync")

Features

Component Description Sync Server
editor() CodeMirror 6 code editor with cursor-preserving sync Required
sync_inputs() Synchronize any Shiny inputs across sessions with replay Not required
kanban_ui() Collaborative kanban board with movable items Not required
textarea_ui() Basic synchronized textarea Not required

The editor provides the best experience for collaborative text editing, with proper cursor preservation when remote changes arrive.

sync_inputs makes an entire Shiny app collaborative with a single function call. All scalar inputs (sliders, dropdowns, checkboxes, text inputs) are synchronized automatically. Pair with replay_ui() / replay_server() to step through the full history of input changes.

The kanban module works well without a sync server because its actions (add, toggle, delete, move) are discrete - there’s no cursor position to preserve.

The textarea syncs correctly but has UX limitations (cursor jumps on remote edits), making it suitable only for simple use cases.

Quick Start

Kanban Board (serverless)

library(shiny)
library(shinysync)

ui <- fluidPage(kanban_ui("board"))

server <- function(input, output, session) {
  kanban_server("board", initial_items = list(
    todo = c("Design feature", "Write tests"),
    in_progress = c("Code review"),
    done = c("Deploy v1.0")
  ))
}

shinyApp(ui, server)

Collaborative Shiny App (serverless)

library(shiny)
library(shinysync)

ui <- fluidPage(
  selectInput("dist", "Distribution", c("Normal", "Uniform", "Exponential")),
  sliderInput("n", "Observations", 10, 500, 100),
  plotOutput("plot"),
  replay_ui("timeline")
)

server <- function(input, output, session) {
  replaying <- sync_inputs()
  replay_server("timeline", replaying = replaying)

  output$plot <- renderPlot({
    data <- switch(input$dist,
      Normal = rnorm(input$n),
      Uniform = runif(input$n),
      Exponential = rexp(input$n)
    )
    hist(data, main = input$dist, col = "steelblue", border = "white")
  })
}

shinyApp(ui, server)

Editor (with sync server)

library(shiny)
library(shinysync)
library(autosync)
library(automerge)

sync_server <- amsync_server()
sync_server$start()

doc_id <- create_document(sync_server)
doc <- get_document(sync_server, doc_id)
am_put(doc, AM_ROOT, "text", am_text("Start typing..."))
am_commit(doc, "init")

ui <- fluidPage(editor_output("editor"))

server <- function(input, output, session) {
  output$editor <- editor_render(editor(sync_server$url, doc_id))
}

onStop(function() sync_server$close())
shinyApp(ui, server)

Open either app in multiple browser windows for real-time collaboration.

Vignettes

  • Collaborative Shiny Apps - Using sync_inputs() with replay
  • Collaborative Meeting Notes App - Using the CodeMirror editor with a sync server
  • Collaborative Kanban Board - Serverless task management

Development

To rebuild the bundled JavaScript widget:

cd inst/build
npm install
npm run build
  • automerge - R bindings for Automerge CRDT
  • autosync - R sync server for Automerge documents