ggpaintr 0.11.1
New features
-
Linked plotly selection helpers (ADR 0028). Two new exports turn the state returned by
ptr_server()into an interactive, brushable plot.ptr_ggplotly()converts the state’s current ggplot into aplotlyhtmlwidget — minting the per-row selection keys and wiring the crosstalksourceinternally, so the call is state-first (ptr_ggplotly(state)).ptr_plotly_selection()returns a reactive of the rows the user brushed or clicked on that plot (mode = "rows"and other projections), reset on each draw. Together they support a full brush-to-table loop. See?ptr_ggplotlyand?ptr_plotly_selection. -
ptr_options(gate_draw = )toggles the “Update plot” button. WhenTRUE(the default) the plot re-renders only on the button click, batching placeholder changes; whenFALSEthe button is omitted and the plot re-renders reactively on every placeholder change (live mode). It also governs the grid host’s “Draw all” button. Read once when the app is built, so set it beforeptr_app()/ptr_ui(). -
ptr_options(suppress_warnings = )silences R warnings emitted while the plot is drawn (e.g.loess“all data on boundary of neighborhood” / “Failed to fit group N”). DefaultFALSE. Only the plot-drawing step is wrapped; errors still propagate to the inline error pane.
Bug fixes
-
Runtime
ppExprsafety messages are now truthful — when a runtimeppExprinput is rejected, the abort reports the actual reason the expression was blocked rather than a generic message. -
The LLM docs tool argument was renamed
name→topic. The shipped primer instructs models to callggpaintr_docs(topic), but the registeredellmertool schema (andptr_llm_topic()’s parameter) named the argumentname, so a model following the primer emitted a key the schema rejected. The schema argument andptr_llm_topic()now agree ontopic.
Documentation
- The safety and LLM vignettes were migrated to the ggpaintr book (https://willju-wangqian.github.io/ggpaintr-book/); the package ships the
ggpaintr-tutorialvignette as its single built vignette.
ggpaintr 0.11.0
Breaking changes (ADR 0027)
-
The four public placeholder-constructor arguments were renamed across
ptr_define_placeholder_value()/ptr_define_placeholder_consumer()/ptr_define_placeholder_source():positional_arg→parse_positional_arg,named_args→parse_named_args,validate_input→validate_session_input, andruntime→embellish_eval. The rename makes the slot family legible — two slots parse the formula’s default args into the widget seed (parse_positional_arg,parse_named_args), one validates the live session value (validate_session_input), and one defines the plain-R evaluation of the embellished expression (embellish_eval). This is a hard break with no deprecation shim: passing an old name now raises R’s “unused argument” error. The internal registry-record keys are unchanged (entry$default_arg/named_args/validate_input/runtime); only the public argument names changed. -
embellish_evaldefaults to the newembellish_identity()for value/consumer placeholders (the source role keeps its abort-guard default). Two built-in helpers ship for the plain-R evaluation slot:embellish_identity()(the identityfunction(x, ...) x) andembellish_symbol_to_string()(NSE-captures its argument and returns column names as a character vector, so a column-selecting consumer works inside a tidyselect verb when the placeholder-embellished formula is run as ordinary R with no app). -
ptr_arg_numeric_vector()was removed. Useptr_arg_numeric(vector = TRUE). The argument-validator factories (ptr_arg_numeric/ptr_arg_string/ptr_arg_symbol_or_string) gained avector = FALSEflag (withlength =honored in vector mode forptr_arg_numeric), and a newptr_arg_symbol()factory parses a bareword column name (vector-capable) for multi-column positional defaults. -
ptr_app_bslib()andptr_app_grid()are no longer exported (changed to@keywords internal). They remain available internally and their help pages are still generated so existing cross-reference links resolve, but they are no longer presented as public API; the public turn-key entry point isptr_app(). Write a thin wrapper on the public primitives for a custom page shell or theme.
New features
-
RStudio command-palette addin.
ptr_wrap_placeholder_addin()wraps a highlighted selection in a placeholder from the RStudio command palette (classifies the selection, offers a placeholder picker, rewrites in place; dark/light-aware viarstudioapi::getThemeInfo()). -
Documentation now leads with the expression form. Roxygen
@examplesfor the formula-capturing entry points (ptr_app,ptr_ui_controls,ptr_ui_page,ptr_server) lead with the unquoted ggplot expression (the canonical input), keeping the string form as the documented fallback; Shiny examples are gated withif (interactive()).
Bug fixes (formula / ppExpr codegen)
-
A
~formula is treated as a first-class atomic in the canonical-pipeline lift, so the resugar pass no longer shreds a formula insideppExpr(e.g.lm(ppExpr(y ~ x), data = ppUpload(d))); placeholders inside the formula are still detected and wired becausetranslate_call()recurses into every argument independently. -
A two-sided formula renders with its infix
~(gear ~ cyl) instead of falling through to a prefix~(gear, cyl), and an explicit parenthesis node(x)now renders as a balanced(x)instead of the unparseable((x).
ggpaintr 0.10.0
New features
-
ptr_shared()now accepts quoted ggplot expressions asformulaselements, alongside strings. Each element offormulasmay be a formula string (as before) or a quoted expression built withrlang::expr()/quote(); quoted expressions are deparsed to their source and the two forms are interchangeable, including in a single mixed list. This brings the multi-formula coordinator in line with the string-or-expression input already accepted byptr_app()/ptr_server(). A built ggplot object (whose source text is unrecoverable) is rejected with a message pointing atexpr(). Additive and fully backward-compatible — existing all-string call sites are unchanged. (As with the single-formula entries, a native pipe|>inside a quoted expression is desugared by R before capture and so does not survive into the generated code panel.)
Breaking changes
-
Source surface rename + auto-name + UI mutex + code prologue (ADR 0025).
ptr_define_placeholder_source()no longer accepts acompanion_id_fn = function(id) -> stringcallback; passshortcut = TRUE(single logical, defaultFALSE) to opt into the env-shortcut sibling input. The framework stampsnode$shortcut_id <- paste0(node$id, "_shortcut")on every translated source node (the oldnode$companion_idslot and the"_name"suffix are gone). Built-inppUploadmigrates verbatim. The reserved shared key"shortcut"aborts translation with a named-conflict error. Hard rename, nolifecycle::deprecate_warn()shim. The same surface ships four further behaviours:-
Auto-name (§2). Every source node now carries
node$auto_name, stamped at translate time for non-shared sources (node$default %||% node$id) and at runtime for shared sources (paste0(obj$id, "_", key)). When the shortcut textbox is empty,substitute_passsplicesas.name(node$auto_name)so the rendered call always references a real binding. -
UI mutex (§3). For
ppUpload, picking a file synchronously clears the shortcut textbox (the auto-name takes over as the binding name); typing into the textbox synchronously clears any pending file. Typing always means “look up this object in the caller env”, never “rename the uploaded payload”. -
Code-panel prologue (§4). Every active upload emits a one-line prologue at the top of the code panel —
<auto-name> <- read.csv("<filename>")(orread.delim,readxl::read_excel,jsonlite::fromJSON, … per extension) — so the rendered snippet reads top-to-bottom as a self-contained script. -
Vacate-on-empty + spec round-trip (§5, §7 A1). Clearing the shortcut textbox with no file present synchronously vacates
state$bound_names[[key]],state$eval_env[[key]], andstate$active_uploads[[key]].ptr_spec()dumpsnode$auto_name(not"") as the bound name whenever the textbox is empty, so reloading a spec re-establishes the same binding the running app saw.
-
Auto-name (§2). Every source node now carries
Bug fixes — ADR 0025 follow-ups
-
Two
ptr_shared(..., id = ...)coordinators on one page sharing a panel-owned source key no longer collide on bare DOM ids. When a panel-sharedppUpload(shared = '<key>')was rendered under a coordinator with a non-NULLid, the innerfileInput+ shortcuttextInputstill bound at the bareshared_<key>/shared_<key>_shortcutids — so a second coordinator (id = "right") on the same page emitted duplicate ids and silently failed to bind one of the two uploads. The rendered widget now binds at the coordinator-namespaced ids (left-shared_<key>/right-shared_<key>), matching the sibling render paths ininvoke_build_ui()andptr_setup_source_uis(). Single-coordinator apps (noid =supplied) are byte-for-byte unchanged.
Known limitation — ADR 0025 §7 A2 deferred (400 ms shortcut debounce)
ptr_define_placeholder_source(shortcut = TRUE) reads the shortcut textbox at every keystroke. Two implementation attempts at the ADR’s proposed 400 ms debounce (shiny::debounce() on the shortcut input read) both broke unrelated test contracts — the bind-path delay broke test-rewrite-pipeline-data-source.R (6 FAIL) and the mutex-file-reset race in test-shared-source-panel-multi-instance.R (2 FAIL). The debounce is therefore not shipped in this release. User-visible impact: typing a partial, unresolvable name into the shortcut textbox may briefly surface a transient Object 'm' not found-style error in the inline error pane during the typing burst; the bind settles once the user finishes typing. A debounce-with-race-preservation variant is tracked as a follow-up; A1 (vacate-on-empty) is unaffected and ships in this release. - validate_input hook signature unified across value and consumer placeholders. Both ptr_define_placeholder_value() and ptr_define_placeholder_consumer() now accept validate_input = function(value, ctx). The consumer’s previous function(value, upstream_cols) signature is gone — registrations that still use it fail loudly via validate_hook() (“must accept argument(s): ctx”). ctx is a plain list with named fields: node (the placeholder AST node), keyword (alias for node$keyword), upstream_cols (character vector or NULL), and data (the upstream data frame or NULL). For value placeholders, ctx$upstream_cols and ctx$data are always NULL (value placeholders have no upstream column scope by definition); for consumer placeholders, both fields are populated when the upstream resolves, letting data-aware validators inspect column types / ranges / levels and not just column names. Migration: rename the second formal from upstream_cols to ctx and read ctx$upstream_cols in the body. Value placeholders that previously had no validator gain the option to declare one.
Breaking changes (ADR 0009)
-
Placeholder keywords renamed from
var/num/text/expr/uploadtoppVar/ppNum/ppText/ppExpr/ppUpload. Every formula passed toptr_app()/ptr_server()/ptr_translate()must use the new vocabulary. Thepp-prefix avoids shadowing base R / ggplot2 names (stats::var(),graphics::text(),base::expression, etc.) and lets the same symbols double as identity / guard plain-R functions exported from the package — soggplot(mtcars, aes(x = ppVar(mpg)))works both insideptr_app()(as a placeholder) and as a standalone ggplot call (ppVaris identity, soaes(x = ppVar(mpg))evaluates toaes(x = mpg)). See ADR 0009 for the full rationale. Migration: rename every placeholder call inside your formula strings or unquoted-expression formulas. -
Old keyword names are no longer reserved. The names
var,num,text,expr,uploadare no longer registered by ggpaintr and no longer trigger the shadow-check guard, so users who previously had custom placeholders blocked by built-ins of these names can now register them without conflict. -
Shiny input ids changed. ggpaintr’s internal Shiny input ids are not part of the public API; the keyword rename has shifted them (e.g.
ggplot_1_1_var_NAis nowggplot_1_1_ppVar_NA). Bookmarked URLs /setBookmarkExcludeallow-lists /setInputs()test fixtures from previous versions will need to be updated.
New features
Default-value arguments and code-mode toggle (ADR 0009). Built-in placeholders now accept a positional argument that seeds the widget’s initial value:
ppVar(mpg)opens the column picker pre-selected onmpg,ppNum(5)seeds the numeric input at 5,ppText("hello")pre-fills the text input, andppExpr(factor(cyl))pre-fills the expression box. Defaults survive the rename round-trip:ptr_render(root, preserve_placeholders = TRUE)re-emits the originalppX(default)call rather than the substituted value. Theptr_app()code panel gained a “Final code” / “Show placeholders” toggle that switches between the two render modes.Custom placeholders can opt in to the default/named-args schema.
ptr_define_placeholder_value()/ptr_define_placeholder_consumer()/ptr_define_placeholder_source()now acceptdefault_arg = <validator>andnamed_args = list(<name> = <validator>, ...). Use the bundled validator factories —ptr_default_symbol_or_string(),ptr_default_numeric(),ptr_default_numeric_vector(),ptr_default_string(),ptr_default_expression()— or pass a custom closure. See?ptr_define_placeholder_value.L3 — own every piece of the UI. Every piece of ggpaintr’s public UI now has its own exported builder:
ptr_ui_header(),ptr_ui_controls(),ptr_ui_plot(),ptr_ui_error(),ptr_ui_code(),ptr_ui_code_toggle(), andptr_ui_assets(). Compose exactly the pieces you want, place each anywhere in your own layout, and wire them with the existing server API (ptr_server()/ptr_module_server()). The bundledptr_app()/ptr_module_ui()/ptr_outputs_ui()/ptr_controls_ui()paths are reimplemented as thin composites of these pieces — same<body>DOM, no behavior or performance change. The pieces are deliberately bare (no assets, no.ptr-appwrapper); the newptr_ui_page()shell wraps them in a Bootstrap page + the single.ptr-apptheme scope + the (deduped) asset bundle in one call, so that is all an L3 user has to remember. Swap the page builder withpage =(fluidPagedefault, alsofixedPage/fillPage/bootstrapPage/basicPage); for anavbarPageor bslib root use the documented decomposition recipe. Seevignette("ggpaintr-use-cases")§ “L3 — Own every piece of the UI”.ptr_server()now accepts aptr_shared_server()bundle directly viashared_state =, mirroringptr_module_server(). Wiring a page-levelptr_shared_ui()panel to a single embedded or custom-rendered plot no longer requires spreading the four bundle slots through....The custom-renderer pattern (reading
state$runtime()for your ownrenderPlotly()/renderGirafe()) is now documented as an L2 capability — bothptr_server()andptr_module_server()return theptr_state, so it needs nothing beyond embedding.
Behavior changes
-
ptr_register_plot()/ptr_register_error()/ptr_register_code()are no longer exported. Post-rewrite they only ever ran insideptr_server()(their sole caller, via the internal runtime setup) and rendered nothing without it, so the “compose outputs manually” use case they advertised was impossible. They remain as internal helpers; no replacement is needed (use the L3 pieces +ptr_server(), or readstate$runtime()for a custom renderer). No deprecation cycle — this dev cycle’s own surface, single pre-1.0 user. -
ggpaintr’s CSS/JS now ship as deduped
htmltools::htmlDependency()bundles.ggpaintr.css, the code-window JS, and theptr_set_classhandler + stage CSS are emitted as two dependencies (ggpaintr,ggpaintr-layer) instead of inline<style>/<script>/<link>tags. htmltools collapses each to a single<head>injection no matter how many shells/pieces a page nests, so the oldwindow.__ptr_*_registeredJS guards are gone. Rendered<body>is unchanged; only<head>asset emission differs.htmltoolsmoves from Suggests to Imports. -
Custom placeholder registration is now process-global. The legacy
placeholders = ptr_merge_placeholders(...)argument has been removed from every public entry point (ptr_app(),ptr_app_bslib(),ptr_app_grid(),ptr_module_ui(),ptr_module_server(),ptr_server(),ptr_translate(),ptr_render()). Custom placeholders are now defined once per R session viaptr_define_placeholder_value() / _consumer() / _source(), which write directly into an internal global registry. All instances in that session see the same registry.- The hook signatures changed too:
build_ui(node, label = NULL, ...)for value/source,build_ui(node, cols, label = NULL, selected = character(0), ...)for consumer. The id is onnode$id(and, for sources registered withshortcut = TRUE, the sibling-input id onnode$shortcut_id— see ADR 0025); there is no separateidargument.resolve_expr(value, node, ...)returningNULLis the new “drop this argument” signal (replacingptr_missing_expr()). - Distribution: package authors should ship a setup function that calls
ptr_define_placeholder_*()(caller-driven), or auto-register from.onLoad(drop-in). Either pattern works; pick by whether you want the registration to be visible at the call site. - Multi-instance limitation: two
ptr_module_server()instances in the same Shiny app cannot have different implementations of the same keyword. The second registration overwrites the first (with acli::cli_warnnotice). If you need divergent widgets per instance, prefix custom keywords with a package-specific tag (mypkg_pctrather thanpct). A scoped per-instance registry override is on the table for a future release. - Test isolation: tests that register custom placeholders should restore state with
ptr_registry_clear()+ptr_register_builtins()(currently internal) or run in a fresh R session.
- The hook signatures changed too:
- Extended the curated empty-call cleanup list with 14 more ggplot2 names:
annotation_custom(),annotation_map(),annotation_raster()(their required args make zero-arg calls error in stock ggplot2 — dropping prevents the crash);aes(),aes_(),aes_q(),aes_string(),vars()(empty mapping helpers are render-identical to “absent” viainherit.aes); andelement_text(),element_line(),element_rect(),element_point(),element_polygon(),element_geom()(empty form inherits from the active theme element). This means e.g.geom_point(aes(colour = var))withvarmissing now reduces to+ geom_point()(clean) instead of+ geom_point(aes()).element_blank()is intentionally excluded — empty form is a meaningful “suppress” directive, not a no-op. - Replaced the old name-prefix heuristic for pruning empty calls. The new rule is: a zero-argument call is dropped iff its bare function name is in a curated ggplot2 cleanup list (
theme(),labs(),xlab/ylab/ggtitle,facet_wrap/facet_grid/facet_null,xlim/ylim/lims,expand_limits,guides,annotate) or in the newsafe_to_removeargument onptr_app(),ptr_app_bslib(),ptr_app_components(),ptr_server(),ptr_module_server(),ptr_server_state(),ptr_complete_expr(), andptr_exec(). Empty calls whose name is not in the set (e.g. third-party helpers likepcp_theme(),pcp_arrange(), or user-authoredaes_pcp()) are preserved by default — being absent from the set is the “removal safety unknown” signal. Passsafe_to_remove = c("pcp_theme")to opt a specific name into the cleanup pass. - An
exprplaceholder, when the user supplies an expression, always wins over the cleanup pass: whatever the user typed into anexprinput is honoured verbatim, even if its top-level name is insafe_to_remove. The intent (“I want this here”) overrides the curated list. -
geom_*()andstat_*()layers are kept empty regardless ofsafe_to_remove, since they inherit aesthetics fromggplot(). Sogeom_point(colour = var)withvarmissing still renders asgeom_point(). - Behavior change vs. the prior implementation: user-authored literal
+ labs(),+ theme(),+ guides()calls are now dropped (no semantic change — they’re no-ops in stock ggplot2). The previous build kept them via a “diff guard” that has been removed in favour of the cleaner curated-list rule.
ggpaintr 0.9.1
Documentation and exports
-
ptr_resolve_layer_data()andptr_ns_id()are now exported with full docs. Custom data-aware placeholders should call these helpers from abind_ui()callback instead of replicating the source-symbol walk or the namespace-function dance. -
?ptr_define_placeholdernow documents thebind_uicontract, the contents of the placeholdercontext, and themeta$idnamespacing rule. A second runnable example demonstrates a data-aware “numeric-columns-only” placeholder using the public helpers.
ggpaintr 0.9.0
New features
-
ptr_gg_extra()— advanced helper for embedded apps that own their ownrenderPlot({...})block. Captures ggplot components (themes, scales, coords, …) added on top ofptr_extract_plot()and stores them on a newptr_state$extrasreactiveVal, so the default code binder (ptr_register_code()) appends them to the generated-code pane. Extras are suppressed automatically when the underlying runtime reports a failure, so stale extras from a prior successful draw never surface during an error state. Seevignette("ggpaintr-extensibility"), Recipe 3. -
ptr_llm_primer(),ptr_llm_topic(),ptr_llm_topics(),ptr_llm_register()— ellmer-facing helpers that expose the bundledinst/llm/primer and topic files as plain strings or as registered ellmer tools, so coding assistants can fetch just-in-time ggpaintr guidance. Seevignette("ggpaintr-llm"). -
uploadplaceholder now accepts.tsv,.xlsx,.xls, and.jsonin addition to.csvand.rds. JSON uploads must be an array of records; nested objects are flattened, nested arrays error out. Excel and JSON readers require the new suggested packagesreadxlandjsonlite. The “show me the code” pane reflects the format-appropriate reader (read.delim,readxl::read_excel,jsonlite::fromJSON).
Breaking changes
- Removed the Shiny app export feature:
ptr_generate_shiny(),ptr_register_export(), the download button, andids$export_buttonare gone. Use the public Shiny integration surface (ptr_server_state(),ptr_register_*(),ptr_input_ui(),ptr_output_ui(),ptr_app_bslib()) to compose and distribute custom apps. - The placeholder distribution parameters
source_file,source_package,source_function, andon_missingare removed fromptr_define_placeholder(). They only existed to support the exporter. Hook functions should be defined inline. - The last commit before these removals is tagged
v0-pre-export-removal.
ggpaintr 0.1.0
- repositioned the package around the maintained
ggpaintrworkflow - shortened API prefix: all exported functions now use the
ptr_*prefix for conciseness (previouslyggpaintr_*) - improved semantic clarity of function names across the public API (e.g.,
bind_*→register_*,*_value→extract_*) - renamed
copy_rulesparameter/system toui_textthroughout - migrated error signaling from
base::stop()torlang::abort() - added
@examplesto all exported functions - clarified the maintained public API boundary in the README and pkgdown-facing docs so the beginner path stays centered on the wrapper, integration, export, placeholder, and intentionally exported runtime helpers
- removed generated documentation topics for package-internal helper functions so internal implementation details are no longer presented as part of the public community-facing surface
- updated
ptr_generate_shiny()so the maintained public call path is nowptr_generate_shiny(ptr_obj, output_file, ...)Note:ptr_generate_shiny()was removed in 0.9.0 — see breaking changes above. - archived the legacy package implementation under
archive/legacy-package/ - replaced legacy package docs, vignette, and pkgdown content with
ggpaintr-first documentation - added roxygen2-based package documentation for the active implementation
- prepared the package structure for
R CMD checkand CRAN-oriented cleanup
