# Convert a data frame for JSON serialisation
result <- RDesk::rdesk_df_to_list(head(mtcars, 3))
length(result$rows) # 3
#> [1] 3
result$cols[1:3] # first three column names
#> [1] "mpg" "cyl" "disp"If you know Shiny, you can learn RDesk in an afternoon. Your R data logic moves across unchanged. What changes is the delivery layer – instead of a browser, you get a native Windows window.
The mental model shift
Shiny uses a reactive graph – inputs change, outputs update automatically. RDesk uses explicit message passing – the UI sends a message, R handles it and pushes a result back.
This feels more like writing an API handler than a Shiny server.
Side-by-side patterns
Responding to user input
Shiny
server <- function(input, output, session) {
output$plot <- renderPlot({
filtered <- mtcars[mtcars$cyl == input$cyl, ]
plot(filtered$wt, filtered$mpg)
})
}RDesk
app$on_message("filter", async(function(payload) {
filtered <- mtcars[mtcars$cyl == payload$cyl, ]
list(chart = rdesk_plot_to_base64(
plot(filtered$wt, filtered$mpg)
))
}, app = app))The key difference: RDesk does not re-run automatically. The UI
explicitly calls rdesk.send("filter", {cyl: 6}) and
receives filter_result back.
Sending data to the UI
Shiny
output$table <- renderTable({ mtcars })RDesk
File downloads
Shiny
output$download <- downloadHandler(
filename = "data.csv",
content = function(file) write.csv(mtcars, file)
)RDesk
Async processing
Shiny (with future/promises)
library(future)
plan(multisession)
observeEvent(input$run, {
future({
slow_model(input$data)
}) %...>% (function(result) {
output$result <- renderText(result)
})
})RDesk
app$on_message("run_model", async(function(payload) {
slow_model(payload$data)
}, app = app, loading_message = "Running model..."))RDesk’s async() handles the loading overlay,
cancellation, and result routing automatically.
What you can reuse unchanged
Everything that does not touch Shiny inputs/outputs moves across directly:
- All data loading and transformation code
- All ggplot2 chart code (render with
rdesk_plot_to_base64()) - All statistical modelling code
- All file reading and writing code
- All helper functions
What you must rewrite
| Shiny pattern | RDesk equivalent |
|---|---|
input$x |
payload$x inside on_message()
|
reactive({...}) |
Call helper functions explicitly |
renderPlot({...}) |
rdesk_plot_to_base64() + app$send()
|
renderTable({...}) |
app$send() with list of rows |
observe({...}) |
app$on_message() handler |
showNotification() |
app$toast() |
Practical migration path
- Keep all your data and modelling R files unchanged
- Replace
ui.Rwithwww/index.html(plain HTML – no Shiny DSL) - Replace
server.Rhandlers one by one usingapp$on_message() - Replace
renderPlotcalls withrdesk_plot_to_base64() + app$send() - Run
source("app.R")and test each handler as you migrate