You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
code:'# R/zzz.R\n.onLoad <- function(libname, pkgname) {\n blockr.core::register_block(\n ctor = "new_my_block",\n name = "Threshold filter",\n description = "Filter rows where a numeric column exceeds a threshold",\n category = "transform",\n package = pkgname\n )\n}'
18
18
}
19
19
]
20
20
</script>
@@ -23,113 +23,147 @@ const blockSteps = [
23
23
24
24
<VideoEmbedid="-PdixmAscQI"title="Creating blocks in blockr" />
25
25
26
-
Write custom blocks in pure R to extend blockr with your own logic. A block is a specialized [Shiny module](https://mastering-shiny.org/scaling-modules.html) that returns an **expression** (the R code it generates) and a **state** (its current input values). A workflow is a Shiny app composed of connected blocks.
26
+
Write custom blocks in pure R to extend blockr with your own logic. A block is a [Shiny module](https://mastering-shiny.org/scaling-modules.html) that returns an `expr` (the R code it generates) and a `state` (its current input values). A workflow is a Shiny app composed of connected blocks.
27
27
28
-
Blocks should live in an R package so they can be registered, shared, and tested. The examples below show the package-based approach.
28
+
Blocks should live in an R package so they can be registered, shared, and tested.
29
29
30
-
## Block anatomy
30
+
::: tip Just getting started?
31
+
The fastest way to your first block is to let a coding agent scaffold it. See the [cat facts walkthrough](/learn/03-create-a-block). It ships a working block (package, tests, registration) in one prompt. Come back here when you want the technical reference.
32
+
:::
31
33
32
-
Every block has three parts: a **UI function**, a **server function**, and a **constructor**. Step through the animation below to see how they come together:
34
+
::: info Source of truth
35
+
Block patterns are documented canonically in [blockr.docs](https://github.com/cynkra/blockr.docs/tree/main/patterns). `r-driven-blocks.md` covers everything below in more depth, plus the JS-driven path for polished UIs.
36
+
:::
33
37
34
-
<CodeStepper:steps="blockSteps" />
38
+
## Block anatomy
35
39
36
-
The sections below break down each part in detail.
40
+
Every block is built from a constructor that wires together a server function and a UI function, then forwards them to a typed parent constructor (`new_data_block`, `new_transform_block`, `new_join_block`, `new_plot_block`, or `new_variadic_block`).
37
41
38
-
```
39
-
Block
40
-
├── UI function → defines the user interface (Shiny UI)
41
-
├── Server function → handles reactive logic, returns expr + state
Step through the animation to see how the pieces come together:
44
43
45
-
### UI function
44
+
<CodeStepper:steps="blockSteps" />
45
+
46
+
### Constructor
46
47
47
-
A standard Shiny module UI. Takes a single `id`argument and returns `shiny.tag` objects:
48
+
The constructor exposes every UI-controllable parameter as an argument and forwards `...` to the parent so framework options (`class`, `allow_empty_state`, `expr_type`, `external_ctrl`, …) can pass through:
-**`expr` is a quoted call**, not a string. Use `blockr.core::bbquote()` with `.(x)` splices and pair it with `expr_type = "bquoted"` on the parent constructor. Splice the upstream data via `.(data)`.
111
+
-**`state` is a list of reactives**, one per constructor parameter. Names must match constructor argument names exactly. Serialization breaks silently otherwise.
112
+
-**The expression must evaluate outside a reactive context.** If `expr` only works because a reactive happens to be in scope, the export pipeline will fail.
113
+
-**Don't expose data inputs as constructor arguments.**`data` / `x` / `y` / `...args` are wired by the framework via the server signature.
114
+
115
+
### UI function
88
116
89
-
Wraps UI and server together with default state values:
117
+
A standard Shiny module UI taking `id`and returning `shiny.tag` objects. Initialise inputs with the constructor's defaults (not empty), so unsaved blocks render their starting state:
Blocks must be registered so they appear in the block menu. Use `.onLoad()` in your package:
130
+
Register on package load so the block has metadata (without it, every constructor call emits a "No block metadata available" warning, and the block doesn't show up in board / AI / MCP discovery):
105
131
106
132
```r
133
+
# R/zzz.R
107
134
.onLoad<-function(libname, pkgname) {
108
135
blockr.core::register_block(
109
-
constructor=new_my_block,
110
-
name="My custom filter",
111
-
description="Filter rows by a numeric threshold",
136
+
ctor="new_my_block",
137
+
name="Threshold filter",
138
+
description="Filter rows where a numeric column exceeds a threshold",
112
139
category="transform",
113
140
package=pkgname
114
141
)
115
142
}
116
143
```
117
144
118
-
You can also register blocks dynamically at runtime:
145
+
`category` must be one of `blockr.core::suggested_categories()`: `input`, `transform`, `structured`, `plot`, `table`, `model`, `output`, `utility`, `uncategorized`. Data-fetching blocks are `input`, not `data`. To register multiple blocks at once, use `register_blocks()` (vectorised).
119
146
120
-
```r
121
-
register_block(
122
-
constructor=new_my_block,
123
-
name="My custom filter",
124
-
description="Filter rows by a numeric threshold",
125
-
category="transform"
126
-
)
127
-
```
147
+
## AI-controllable blocks
148
+
149
+
Blockr's AI assistant configures blocks for end users by writing to their state from outside the block's server. To opt in, set `external_ctrl` on the parent constructor:
150
+
151
+
|`external_ctrl` value | Meaning |
152
+
|---|---|
153
+
|`FALSE` (default) | Block state is read-only from outside. AI can't change it. |
154
+
|`TRUE`| All constructor arguments are externally writable. |
155
+
|`"column"` (a string) | Only the named state slot is externally writable. |
156
+
|`c("column", "threshold")`| Multiple named slots are writable. |
157
+
158
+
State names handed to `external_ctrl` must match the names in the server's `state` list (and therefore the constructor argument names). The framework validates writes by re-evaluating the block expression: if evaluation fails, the previous state is restored and downstream evaluation is gated until the next successful submit. See `?blockr.core::ctrl_block` for the plugin that drives this.
128
159
129
-
Query what's available with `available_blocks()`.
160
+
::: tip
161
+
Per-package convention: opt blocks in by default (`external_ctrl = TRUE`) unless you have a reason not to. Blocks that aren't externally controllable disappear from AI/MCP suggestions.
162
+
:::
130
163
131
164
## Further reading
132
165
166
+
-[blockr.docs patterns](https://github.com/cynkra/blockr.docs/tree/main/patterns): canonical R-driven and JS-driven references
133
167
-[Full create-block vignette](https://bristolmyerssquibb.github.io/blockr.core/articles/create-block.html): detailed walkthrough with advanced examples
134
168
-[Block registry vignette](https://bristolmyerssquibb.github.io/blockr.core/articles/blocks-registry.html): registry system details
135
169
-[Extend blockr vignette](https://bristolmyerssquibb.github.io/blockr.core/articles/extend-blockr.html): plugins and custom UI
The registry is the "supermarket" for blocks. It tracks all available blocks with metadata (name, description, category, package). When you load a blockr extension package, its blocks are automatically registered via `.onLoad()` and appear in the block menu.
54
+
The registry is the "supermarket" for blocks. It tracks all available blocks with metadata (name, description, category, package). When you load a blockr extension package, its blocks are registered via `.onLoad()` and appear in the block menu, and in the AI/MCP discovery surface, which reads the same registry.
55
55
56
56
```r
57
57
# Query available blocks
58
-
available_blocks()
58
+
list_blocks()
59
59
60
-
# Register a new block
60
+
# Register a new block (in R/zzz.R or at runtime)
61
61
register_block(
62
-
constructor=new_my_block,
62
+
ctor="new_my_block",
63
63
name="My block",
64
64
description="Does something useful",
65
65
category="transform"
66
66
)
67
67
68
-
# Unregister a block
69
-
unregister_block("my_block")
68
+
# Register many at once (vectorised)
69
+
register_blocks(
70
+
ctor= c("new_filter_block", "new_select_block"),
71
+
name= c("Filter rows", "Select columns"),
72
+
description= c("Filter by predicate", "Pick a subset of columns"),
73
+
category= c("transform", "transform")
74
+
)
75
+
76
+
# Unregister
77
+
unregister_blocks("my_block")
70
78
```
71
79
72
-
This makes collaboration easy: one team builds a package of domain-specific blocks, registers them, and they appear in every user's block menu.
80
+
`category` must come from `blockr.core::suggested_categories()` (`input`, `transform`, `structured`, `plot`, `table`, `model`, `output`, `utility`, `uncategorized`). Anything else warns. Data-fetching blocks register as `input`, not `data`.
81
+
82
+
This makes collaboration easy: one team builds a package of domain-specific blocks, registers them, and they appear in every user's block menu. They also become available to the AI assistant for configuration.
73
83
74
84
## Further reading
75
85
86
+
-[blockr.docs](https://github.com/cynkra/blockr.docs): canonical block patterns and skills
0 commit comments