Overview
shinybreakpoint is designed to display in the Shiny app
(i.e. when the app runs) parts of the source code used in the app and to
allow to set breakpoint in some places of the displayed source
code. Thus it should be clear from the beginning that this package has
two-stage limitations:
- breakpoint can’t be set on the lines which are not displayed
 - breakpoint won’t be set on all lines which are displayed
 
Setting breakpoint is a debugging technique - when the point (line of code) is reached, code execution is halted, debug mode is enabled and one can check the values of objects or perform any other operations in the temporary environment (i.e. all changes exist only in the debug mode). It is one of the method to find out why unexpected behavior occurred.
shinybreakpoint do not provide new technique
for Shiny apps - it is already possible to set breakpoint using
e.g. RStudio IDE, but the current solution has its own limitation.
Breakpoint can’t be set when the code is split into multiple files
(which is often the case when the app is built of modules). Although
shinybreakpoint gives the solution for that, it makes it in
the radical way - the code has to be split into (at least one),
separated from server part, function.
Minimal App Structure
We will start from the snippet1 - minimal skeleton needed to successfully
run the Shiny app with the shinybreakpoint
functionality:
library(shiny)
ui <- fluidPage(
  
)
appServer <- function(input, output, session) {
  # here will be the code which will be run in the
  # 'server', not in the 'server' itself
}
server <- function(input, output, session) {
  appServer(input, output, session)
  shinybreakpoint::shinybreakpointServer()
}
shinyApp(ui, server)it is very similar to the regular Shiny snippet, however with the one significant difference - the server part is duplicated.
This duplication is necessary, because when the breakpoint is set
using shinybreakpoint, the session is refreshed to enable
changes in the code (these changes are the code added to the chosen
line, i.a. browser()). Refreshment works only for objects
nested in the functions which are then call in the server
part. In other words, breakpoint won’t work for objects used directly in
the server, but these objects will be visible in the source
code. As an example that works, we can consider the code below.
library(shiny)
library(magrittr)
ui <- fluidPage(
  numericInput("num1", "Num", 1),
  numericInput("num2", "Num", 2),
  actionButton("go", "Go")
)
appServer <- function(input, output, session) {
  observe({
    input$num1
  })
  
  observe({
    input$num2
  }) %>% 
    bindEvent(input$go)
}
server <- function(input, output, session) {
  appServer(input, output, session)
  shinybreakpoint::shinybreakpointServer()
}
shinyApp(ui, server)The key concept is that all code on which we possibly would like to
set breakpoint is separated from the server
(shinybreakpoint::shinybreakpointServer() does not
necessary need to be use in the server, it could be used in
the appServer as well). When the app is built of modules,
this separation is achieved by definition, but when not, then
shinybreakpoint needs this additional step.
Recommended App Structure
Duplicated main server part of the app is necessary if
one would like to set brekapoint in the main server, but
shinybreakpoint, being a shiny module, was developed having
Bootstrap 5 in mind, which means that the expected appearance of the
module will be noticeable with the bslib::bs_theme(5) set
as theme - this code sets Bootstrap version 5 for the app.
The second version of shinybreakpoint brought also a new
functionality - filtering displayed source code by Id -
this is Id of intput or output
element (more about this, reader can find in the article Filtering
by Id). Filtering is based on reactlog and to be
able to use it, it is needed to set the appropriate option. This is not
only enabling reactlog, but also setting up some additional
function responsible to manage temporary files, thus it is needed to use
shinybreakpoint::set_filtering_by_id(), not just
e.g. options(shiny.reactlog = TRUE). The snipped below
shows the recommended structure of the app - changes concerns only on
the main app file. In this code snippet, two
TODOs was included as a reminder to remove these calls
before app is send to production.
    library(shiny)
    shinybreakpoint::set_filtering_by_id() # TODO: remove
    ui <- fluidPage(
      theme = bslib::bs_theme(5),
    )
    appServer <- function(input, output, session) {
      # here will be the code which will be run in the
      # 'server', not in the 'server' itself
    }
    server <- function(input, output, session) {
      appServer(input, output, session)
      shinybreakpoint::shinybreakpointServer() # TODO: remove
    }
    shinyApp(ui, server)Setting breakpoint
To run any of the examples above, one should save the file with the
code, run the app and press F4 (as this is key used by
default, check out the other parameters by running
?shinybreakpoint::shinybreakpointServer in the console) -
the modal dialog will pop up. In our example there are two numeric
inputs and one button in the UI as well as two
observes (one is eager - will run immediately and
one will run only after the button is pushed) in the
server. observes will be visible in the modal
dialog and breakpoint can be set on two lines:
- 
input$num1and input$num2
other possibilities won’t work. Generally we can say that breakpoint won’t be set on the edges of the visible code blocks.
Immediately after the breakpoint is set (i.e. when the
red filled circle button
() in the
modal dialog is pushed), session is refreshed. That means that if the
breakpoint in our example was set on the input$num1 line,
debug mode will open up immediately, but if it was set on the
input$num2, then Go button needs to be pushed.
That fact strictly depends on the reactive programming - debug mode
opens up when the code block (where breakpoint is set) starts and the
chosen line is achieved.
Displaying source code
shinybreakpoint tries to find and display only objects
which belong to reactive context (observes,
reactives and render*s), however it is not
guaranteed that all of these objects will be find and that
only these objects will be find. If so, one should remember
that shinybreakpoint was designed to work with objects
which belong to reactive context and thus it can lead to errors if using
on other objects. It is also crucial to note that reactive
context will be displayed only if the body (of this context) is inside
curly ({}) brackets,
i.e. reactive({iris}) should be use instead of
reactive(iris). But - of course, if one would like to
set breakpoint inside the reactive context, it has to be a
space for this, i.e. in our example the code should look like below
(body of function is in the separate line):
r_iris <- reactive({
  iris
})Any element of reactive context must also be inside the
server part (in the main app or in the module).
Theoretically it is possible to include reactive context
(e.g. observe()) outside of the server part
(and of course outside of the UI part) if one would like to
share something between Shiny sessions, but shinybreakpoint
currently won’t display such a code.
Additional Comments
Not previously mentioned and possibly important remarks are:
- debugging is not performed on the original file, but on the temporary copy of the file
 - breakpoint is designed to be an one-time breakpoint and it means that breakpoint do not persist and only one breakpoint can be set in one time
 - when the 
browser()and other code is added, thesrcrefattribute of function is lost. However, it shouldn’t be a problem since this attribute is used to get information about the source of object and therefore is useful only for debugging - session is reload twice - first time after the breakpoint is set and
the second one when debug mode is closed using 
corf. This can be inconvenient if some heavy computations are performed when the session starts - if one uses RStudio IDE, 
shinybreakpointrecognizes the file opened in the source editor and if this file is used in Shiny app and contains reactive blocks of code, it will be opened as default file in modal dialog. This works only in a moment when the key specified inkeyEventparameter is pressed