# Coming from Shiny

    # 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

    # In R -- push data explicitly
    app$send("table_data", list(
      rows = lapply(seq_len(nrow(mtcars)), function(i) as.list(mtcars[i,])),
      cols = names(mtcars)
    ))

    // In JavaScript -- receive it
    rdesk.on("table_data", function(data) {
      renderTable(data.rows, data.cols);
    });

### File downloads

Shiny

    output$download <- downloadHandler(
      filename = "data.csv",
      content  = function(file) write.csv(mtcars, file)
    )

RDesk

    app$on_message("save_csv", function(payload) {
      path <- app$dialog_save(
        title   = "Save CSV",
        filters = "CSV files (*.csv)|*.csv"
      )
      if (!is.null(path)) write.csv(mtcars, path, row.names = FALSE)
    })

### Native menus (no Shiny equivalent)

    app$set_menu(list(
      File = list(
        "Open..."  = function() app$dialog_open(),
        "Save..."  = function() app$dialog_save(),
        "---",
        "Exit"     = app$quit
      ),
      Help = list(
        "About"    = function() app$toast("MyApp v1.0", type = "info")
      )
    ))

### 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

1. Keep all your data and modelling R files unchanged
2. Replace `ui.R` with `www/index.html` (plain HTML – no Shiny DSL)
3. Replace `server.R` handlers one by one using `app$on_message()`
4. Replace `renderPlot` calls with `rdesk_plot_to_base64() + app$send()`
5. Run `source("app.R")` and test each handler as you migrate