# 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 a `plotly` htmlwidget — minting the per-row selection keys and wiring the crosstalk `source` internally, 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_ggplotly` and `?ptr_plotly_selection`.
- **`ptr_options(gate_draw = )`** toggles the "Update plot" button. When `TRUE` (the default) the plot re-renders only on the button click, batching placeholder changes; when `FALSE` the 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 before `ptr_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"). Default `FALSE`. Only the plot-drawing step is wrapped; errors still propagate to the inline error pane.

## Bug fixes

- **Runtime `ppExpr` safety messages are now truthful** — when a runtime `ppExpr` input 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 call `ggpaintr_docs(topic)`, but the registered `ellmer` tool schema (and `ptr_llm_topic()`'s parameter) named the argument `name`, so a model following the primer emitted a key the schema rejected. The schema argument and `ptr_llm_topic()` now agree on `topic`.

## Documentation

- The **safety** and **LLM** vignettes were migrated to the ggpaintr book (https://willju-wangqian.github.io/ggpaintr-book/); the package ships the `ggpaintr-tutorial` vignette 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`, and `runtime` → `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_eval` defaults to the new `embellish_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 identity `function(x, ...) x`) and `embellish_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.** Use `ptr_arg_numeric(vector = TRUE)`. The argument-validator factories (`ptr_arg_numeric` / `ptr_arg_string` / `ptr_arg_symbol_or_string`) gained a `vector = FALSE` flag (with `length =` honored in vector mode for `ptr_arg_numeric`), and a new `ptr_arg_symbol()` factory parses a bareword column name (vector-capable) for multi-column positional defaults.
- **`ptr_app_bslib()` and `ptr_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 is `ptr_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 via `rstudioapi::getThemeInfo()`).
- **Documentation now leads with the expression form.** Roxygen `@examples` for 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 with `if (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 inside `ppExpr` (e.g. `lm(ppExpr(y ~ x), data = ppUpload(d))`); placeholders inside the formula are still detected and wired because `translate_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 as `formulas` elements, alongside strings.** Each element of `formulas` may be a formula string (as before) or a quoted expression built with `rlang::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 by `ptr_app()` / `ptr_server()`. A built ggplot object (whose source text is unrecoverable) is rejected with a message pointing at `expr()`. 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 a `companion_id_fn = function(id) -> string` callback; pass `shortcut = TRUE` (single logical, default `FALSE`) to opt into the env-shortcut sibling input. The framework stamps `node$shortcut_id <- paste0(node$id, "_shortcut")` on every translated source node (the old `node$companion_id` slot and the `"_name"` suffix are gone). Built-in `ppUpload` migrates verbatim. The reserved shared key `"shortcut"` aborts translation with a named-conflict error. Hard rename, no `lifecycle::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_pass` splices `as.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>")` (or `read.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]]`, and `state$active_uploads[[key]]`. `ptr_spec()` dumps `node$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.

### 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-shared `ppUpload(shared = '<key>')` was rendered under a coordinator with a non-`NULL` `id`, the inner `fileInput` + shortcut `textInput` still bound at the bare `shared_<key>` / `shared_<key>_shortcut` ids — 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 in `invoke_build_ui()` and `ptr_setup_source_uis()`. Single-coordinator apps (no `id =` 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` / `upload` to `ppVar` / `ppNum` / `ppText` / `ppExpr` / `ppUpload`.** Every formula passed to `ptr_app()` / `ptr_server()` / `ptr_translate()` must use the new vocabulary. The `pp`-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 — so `ggplot(mtcars, aes(x = ppVar(mpg)))` works both inside `ptr_app()` (as a placeholder) and as a standalone ggplot call (`ppVar` is identity, so `aes(x = ppVar(mpg))` evaluates to `aes(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`, `upload` are 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_NA` is now `ggplot_1_1_ppVar_NA`). Bookmarked URLs / `setBookmarkExclude` allow-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 on `mpg`, `ppNum(5)` seeds the numeric input at 5, `ppText("hello")` pre-fills the text input, and `ppExpr(factor(cyl))` pre-fills the expression box. Defaults survive the rename round-trip: `ptr_render(root, preserve_placeholders = TRUE)` re-emits the original `ppX(default)` call rather than the substituted value. The `ptr_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 accept `default_arg = <validator>` and `named_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()`, and `ptr_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 bundled `ptr_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-app` wrapper); the new **`ptr_ui_page()`** shell wraps them in a Bootstrap page + the single `.ptr-app` theme scope + the (deduped) asset bundle in one call, so that is all an L3 user has to remember. Swap the page builder with `page =` (`fluidPage` default, also `fixedPage`/`fillPage`/`bootstrapPage`/`basicPage`); for a `navbarPage` or bslib root use the documented decomposition recipe. See `vignette("ggpaintr-use-cases")` § "L3 — Own every piece of the UI".
- `ptr_server()` now accepts a `ptr_shared_server()` bundle directly via `shared_state =`, mirroring `ptr_module_server()`. Wiring a page-level `ptr_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 own `renderPlotly()` / `renderGirafe()`) is now documented as an L2 capability — both `ptr_server()` and `ptr_module_server()` return the `ptr_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 inside `ptr_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 read `state$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 the `ptr_set_class` handler + 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 old `window.__ptr_*_registered` JS guards are gone. Rendered `<body>` is unchanged; only `<head>` asset emission differs. `htmltools` moves 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 via `ptr_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 on `node$id` (and, for sources registered with `shortcut = TRUE`, the sibling-input id on `node$shortcut_id` — see ADR 0025); there is no separate `id` argument. `resolve_expr(value, node, ...)` returning `NULL` is the new "drop this argument" signal (replacing `ptr_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 a `cli::cli_warn` notice). If you need divergent widgets per instance, prefix custom keywords with a package-specific tag (`mypkg_pct` rather than `pct`). 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.
- 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" via `inherit.aes`); and `element_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))` with `var` missing 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 new `safe_to_remove` argument on `ptr_app()`, `ptr_app_bslib()`, `ptr_app_components()`, `ptr_server()`, `ptr_module_server()`, `ptr_server_state()`, `ptr_complete_expr()`, and `ptr_exec()`. Empty calls whose name is not in the set (e.g. third-party helpers like `pcp_theme()`, `pcp_arrange()`, or user-authored `aes_pcp()`) are preserved by default — being absent from the set is the "removal safety unknown" signal. Pass `safe_to_remove = c("pcp_theme")` to opt a specific name into the cleanup pass.
- An `expr` placeholder, when the user supplies an expression, always wins over the cleanup pass: whatever the user typed into an `expr` input is honoured verbatim, even if its top-level name is in `safe_to_remove`. The intent ("I want this here") overrides the curated list.
- `geom_*()` and `stat_*()` layers are kept empty regardless of `safe_to_remove`, since they inherit aesthetics from `ggplot()`. So `geom_point(colour = var)` with `var` missing still renders as `geom_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()` and `ptr_ns_id()` are now exported with full docs. Custom data-aware placeholders should call these helpers from a `bind_ui()` callback instead of replicating the source-symbol walk or the namespace-function dance.
- `?ptr_define_placeholder` now documents the `bind_ui` contract, the contents of the placeholder `context`, and the `meta$id` namespacing 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 own `renderPlot({...})` block. Captures ggplot components (themes, scales, coords, ...) added on top of `ptr_extract_plot()` and stores them on a new `ptr_state$extras` reactiveVal, 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. See `vignette("ggpaintr-extensibility")`, Recipe 3.
- `ptr_llm_primer()`, `ptr_llm_topic()`, `ptr_llm_topics()`, `ptr_llm_register()` — ellmer-facing helpers that expose the bundled `inst/llm/` primer and topic files as plain strings or as registered ellmer tools, so coding assistants can fetch just-in-time ggpaintr guidance. See `vignette("ggpaintr-llm")`.
- `upload` placeholder now accepts `.tsv`, `.xlsx`, `.xls`, and `.json` in addition to `.csv` and `.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 packages `readxl` and `jsonlite`. 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, and `ids$export_button` are 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`, and `on_missing` are removed from `ptr_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 `ggpaintr` workflow
- shortened API prefix: all exported functions now use the `ptr_*` prefix for conciseness (previously `ggpaintr_*`)
- improved semantic clarity of function names across the public API (e.g., `bind_*` → `register_*`, `*_value` → `extract_*`)
- renamed `copy_rules` parameter/system to `ui_text` throughout
- migrated error signaling from `base::stop()` to `rlang::abort()`
- added `@examples` to 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 now `ptr_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 check` and CRAN-oriented cleanup
