@ea-lab/reactive-json-docs 2.3.0 → 2.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ea-lab/reactive-json-docs",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "description": "Complete documentation for Reactive-JSON - Components, examples and LLM-parsable guides",
5
5
  "main": "public/rjbuild/docs/index.yaml",
6
6
  "files": [
@@ -0,0 +1,169 @@
1
+ # AutocompleteField
2
+
3
+ The `AutocompleteField` component provides a search-as-you-type input that fetches suggestions from a remote API as the user types. It supports single and multiple value selection, and customizable dropdown and selected-item templates.
4
+
5
+ ## Basic Syntax
6
+
7
+ ```yaml
8
+ - type: AutocompleteField
9
+ dataLocation: ~~.selectedId
10
+ placeholder: "Search…"
11
+ src:
12
+ - "/api/items"
13
+ - param: "search"
14
+ value: "<reactive-json:autocomplete-field:input>"
15
+ - param: "limit"
16
+ value: "10"
17
+ ```
18
+
19
+ ## Properties
20
+
21
+ - `dataLocation` (string, required): Path where the selected value (or array of values in multiple mode) is stored in the data context.
22
+ - `src` (array, required): URL segments used to build the API endpoint, using the same format as `additionalDataSource.src`. The special token `<reactive-json:autocomplete-field:input>` is replaced by the current search text at request time.
23
+ - `placeholder` (string, optional): Placeholder text for the search input.
24
+ - `minChars` (number, optional): Minimum number of characters required before a search is triggered. Defaults to `2`.
25
+ - `debounce` (number, optional): Delay in milliseconds between the last keystroke and the API request. Defaults to `300`.
26
+ - `multiple` (boolean, optional): Enable multiple selection mode. Selected values are stored as an array. Defaults to `false`.
27
+ - `maxItems` (number, optional): Maximum number of items that can be selected in multiple mode. No limit by default.
28
+ - `clearOnSelect` (boolean, optional): Clear the search input and close the dropdown after an item is selected. Defaults to `true`.
29
+ - `itemTemplate` (rjbuild, optional): Template used to render each item in the dropdown. Has access to item data via `~.` (e.g., `~.label`, `~.value`). Defaults to a `div` displaying `~.label`.
30
+ - `selectedTemplate` (rjbuild, optional): Template used to render the selected item (single mode) or each tag (multiple mode). Has access to item data via `~.`. Defaults to a `span` displaying `~.label`.
31
+ - `defaultFieldValue` (any, optional): Default value used when no data is present at `dataLocation`.
32
+ - `attributes` (object, optional): Attributes applied to the root container `div`. Also receives the CSS class `rj-autocomplete` automatically.
33
+ - `actions` (array, optional): Actions to execute based on component state.
34
+
35
+ ## Backend Response Format
36
+
37
+ The endpoint must return a JSON array of objects. Each object must have at minimum a `value` and a `label` key:
38
+
39
+ ```json
40
+ [
41
+ { "value": 42, "label": "First result" },
42
+ { "value": 7, "label": "Second result" }
43
+ ]
44
+ ```
45
+
46
+ Additional keys can be included and are available in `itemTemplate` / `selectedTemplate` via `~.fieldName`.
47
+
48
+ ## The Input Token
49
+
50
+ The string `<reactive-json:autocomplete-field:input>` is a special token that can be placed anywhere in the `src` segments (as a `value`, `param`, or URL segment). It is replaced at request time with the current text in the search field.
51
+
52
+ ```yaml
53
+ src:
54
+ - "/api/search"
55
+ - param: "q"
56
+ value: "<reactive-json:autocomplete-field:input>"
57
+ ```
58
+
59
+ ## CSS Classes
60
+
61
+ The component uses the following CSS classes for styling:
62
+
63
+ - `.rj-autocomplete` — root container
64
+ - `.rj-autocomplete__input` — the search text input
65
+ - `.rj-autocomplete__dropdown` — the suggestions dropdown
66
+ - `.rj-autocomplete__item` — each suggestion item
67
+ - `.rj-autocomplete__item.is-selected` — a suggestion that is already selected (multiple mode)
68
+ - `.rj-autocomplete__selected` — the selected-item area (single mode)
69
+ - `.rj-autocomplete__clear` — the clear button (single mode)
70
+ - `.rj-autocomplete__tag` — a selected tag (multiple mode)
71
+ - `.rj-autocomplete__tag-remove` — the remove button on a tag
72
+ - `.rj-autocomplete__loading` — the loading indicator
73
+
74
+ ## Examples
75
+
76
+ ### Single value selection
77
+
78
+ Stores a single selected value. Shows a clear button once an item is selected.
79
+
80
+ ```yaml
81
+ renderView:
82
+ - type: AutocompleteField
83
+ dataLocation: ~~.selectedId
84
+ placeholder: "Search…"
85
+ src:
86
+ - "/api/items"
87
+ - param: "search"
88
+ value: "<reactive-json:autocomplete-field:input>"
89
+ - param: "limit"
90
+ value: "10"
91
+
92
+ data:
93
+ selectedId: null
94
+ ```
95
+
96
+ ### Multiple selection with a limit
97
+
98
+ Stores up to 3 values as an array. Each selected item is displayed as a removable tag.
99
+
100
+ ```yaml
101
+ renderView:
102
+ - type: AutocompleteField
103
+ dataLocation: ~~.selectedIds
104
+ multiple: true
105
+ maxItems: 3
106
+ placeholder: "Add items (max 3)…"
107
+ src:
108
+ - "/api/items"
109
+ - param: "search"
110
+ value: "<reactive-json:autocomplete-field:input>"
111
+ - param: "limit"
112
+ value: "10"
113
+ - type: p
114
+ content:
115
+ - "Selected IDs: "
116
+ - type: Join
117
+ content: ~~.selectedIds
118
+
119
+ data:
120
+ selectedIds: []
121
+ ```
122
+
123
+ ### Custom templates
124
+
125
+ Use `itemTemplate` and `selectedTemplate` to render richer content. Both templates have access to all fields returned by the backend via `~.`.
126
+
127
+ ```yaml
128
+ - type: AutocompleteField
129
+ dataLocation: ~~.selectedItem
130
+ placeholder: "Search…"
131
+ src:
132
+ - "/api/items"
133
+ - param: "search"
134
+ value: "<reactive-json:autocomplete-field:input>"
135
+ itemTemplate:
136
+ type: div
137
+ content:
138
+ - type: strong
139
+ content: ~.label
140
+ - " (#"
141
+ - ~.value
142
+ - ")"
143
+ selectedTemplate:
144
+ type: span
145
+ content:
146
+ - ~.label
147
+ - " ✓"
148
+ ```
149
+
150
+ ### Keep search text after selection
151
+
152
+ Set `clearOnSelect: false` to keep the typed text and dropdown open after selecting an item (useful in multiple mode when selecting several items quickly).
153
+
154
+ ```yaml
155
+ - type: AutocompleteField
156
+ dataLocation: ~~.selectedIds
157
+ multiple: true
158
+ clearOnSelect: false
159
+ placeholder: "Search…"
160
+ src: [...]
161
+ ```
162
+
163
+ ## Limitations
164
+
165
+ - Requires an external API endpoint; no built-in static options support.
166
+ - The dropdown only renders while the user is actively typing (results are not re-fetched on re-open without re-typing).
167
+ - In multiple mode, selected items' display data is kept in local React state and may be lost on page reload unless persisted separately.
168
+ - Styling must be provided via external CSS targeting the `.rj-autocomplete*` classes, or via `attributes.style`.
169
+ - No built-in keyboard navigation for the dropdown.
@@ -0,0 +1,225 @@
1
+ renderView:
2
+ - type: Markdown
3
+ content: |
4
+ # AutocompleteField
5
+
6
+ The `AutocompleteField` component provides a search-as-you-type input that fetches suggestions from a remote API as the user types. It supports single and multiple value selection, and customizable dropdown and selected-item templates.
7
+
8
+ - type: Markdown
9
+ content: |
10
+ ## Basic Syntax
11
+
12
+ ```yaml
13
+ - type: AutocompleteField
14
+ dataLocation: ~~.selectedId
15
+ placeholder: "Search…"
16
+ src:
17
+ - "/api/items"
18
+ - param: "search"
19
+ value: "<reactive-json:autocomplete-field:input>"
20
+ - param: "limit"
21
+ value: "10"
22
+ ```
23
+
24
+ ## Properties
25
+
26
+ - type: DefinitionList
27
+ content:
28
+ - term:
29
+ code: dataLocation
30
+ after: "(string, required)"
31
+ details: "Path where the selected value (or array of values in multiple mode) is stored in the data context."
32
+ - term:
33
+ code: src
34
+ after: "(array, required)"
35
+ details:
36
+ type: Markdown
37
+ content: "URL segments used to build the API endpoint. The special token `<reactive-json:autocomplete-field:input>` is replaced by the current search text at request time."
38
+ - term:
39
+ code: placeholder
40
+ after: "(string, optional)"
41
+ details: "Placeholder text for the search input."
42
+ - term:
43
+ code: minChars
44
+ after: "(number, optional, default: 2)"
45
+ details: "Minimum number of characters required before a search is triggered."
46
+ - term:
47
+ code: debounce
48
+ after: "(number, optional, default: 300)"
49
+ details: "Delay in milliseconds between the last keystroke and the API request."
50
+ - term:
51
+ code: multiple
52
+ after: "(boolean, optional, default: false)"
53
+ details: "Enable multiple selection mode. Selected values are stored as an array."
54
+ - term:
55
+ code: maxItems
56
+ after: "(number, optional)"
57
+ details: "Maximum number of items selectable in multiple mode. No limit by default."
58
+ - term:
59
+ code: clearOnSelect
60
+ after: "(boolean, optional, default: true)"
61
+ details: "Clear the search input and close the dropdown after an item is selected."
62
+ - term:
63
+ code: itemTemplate
64
+ after: "(rjbuild, optional)"
65
+ details:
66
+ type: Markdown
67
+ content: "Template rendered for each item in the dropdown. Has access to item data via `~.` (e.g., `~.label`, `~.value`). Defaults to a `div` displaying `~.label`."
68
+ - term:
69
+ code: selectedTemplate
70
+ after: "(rjbuild, optional)"
71
+ details:
72
+ type: Markdown
73
+ content: "Template rendered for the selected item (single mode) or each tag (multiple mode). Has access to item data via `~.`. Defaults to a `span` displaying `~.label`."
74
+ - term:
75
+ code: defaultFieldValue
76
+ after: "(any, optional)"
77
+ details: "Default value used when no data is present at dataLocation."
78
+ - term:
79
+ code: attributes
80
+ after: "(object, optional)"
81
+ details:
82
+ type: Markdown
83
+ content: "Attributes applied to the root container `div`. Also receives the CSS class `rj-autocomplete` automatically."
84
+ - term:
85
+ code: actions
86
+ after: "(array, optional)"
87
+ details: "Actions to execute based on component state."
88
+
89
+ - type: Markdown
90
+ content: |
91
+ ## Backend Response Format
92
+
93
+ The endpoint must return a JSON array of objects. Each object must have at minimum a `value` and a `label` key:
94
+
95
+ ```json
96
+ [
97
+ { "value": 42, "label": "First result" },
98
+ { "value": 7, "label": "Second result" }
99
+ ]
100
+ ```
101
+
102
+ Additional keys can be included and are available in `itemTemplate` / `selectedTemplate` via `~.fieldName`.
103
+
104
+ - type: Markdown
105
+ content: |
106
+ ## The Input Token
107
+
108
+ The string `<reactive-json:autocomplete-field:input>` is a special token that can be placed anywhere in the `src` segments. It is replaced at request time with the current text in the search field.
109
+
110
+ ```yaml
111
+ src:
112
+ - "/api/search"
113
+ - param: "q"
114
+ value: "<reactive-json:autocomplete-field:input>"
115
+ ```
116
+
117
+ - type: Markdown
118
+ content: |
119
+ ## CSS Classes
120
+
121
+ The component uses the following CSS classes for styling:
122
+
123
+ | Class | Element |
124
+ |---|---|
125
+ | `.rj-autocomplete` | Root container |
126
+ | `.rj-autocomplete__input` | The search text input |
127
+ | `.rj-autocomplete__dropdown` | The suggestions dropdown |
128
+ | `.rj-autocomplete__item` | Each suggestion item |
129
+ | `.rj-autocomplete__item.is-selected` | A suggestion already selected (multiple mode) |
130
+ | `.rj-autocomplete__selected` | The selected-item area (single mode) |
131
+ | `.rj-autocomplete__clear` | The clear button (single mode) |
132
+ | `.rj-autocomplete__tag` | A selected tag (multiple mode) |
133
+ | `.rj-autocomplete__tag-remove` | The remove button on a tag |
134
+ | `.rj-autocomplete__loading` | The loading indicator |
135
+
136
+ - type: Markdown
137
+ content: |
138
+ ## Examples
139
+
140
+ > **Note**: The following examples require a live API endpoint. They show the YAML configuration without a live demo.
141
+
142
+ ### Single value selection
143
+
144
+ Stores a single selected value. Shows a clear button once an item is selected.
145
+
146
+ ```yaml
147
+ renderView:
148
+ - type: AutocompleteField
149
+ dataLocation: ~~.selectedId
150
+ placeholder: "Search…"
151
+ src:
152
+ - "/api/items"
153
+ - param: "search"
154
+ value: "<reactive-json:autocomplete-field:input>"
155
+ - param: "limit"
156
+ value: "10"
157
+
158
+ data:
159
+ selectedId: null
160
+ ```
161
+
162
+ ### Multiple selection with a limit
163
+
164
+ Stores up to 3 values as an array. Each selected item is displayed as a removable tag.
165
+
166
+ ```yaml
167
+ renderView:
168
+ - type: AutocompleteField
169
+ dataLocation: ~~.selectedIds
170
+ multiple: true
171
+ maxItems: 3
172
+ placeholder: "Add items (max 3)…"
173
+ src:
174
+ - "/api/items"
175
+ - param: "search"
176
+ value: "<reactive-json:autocomplete-field:input>"
177
+ - param: "limit"
178
+ value: "10"
179
+ - type: p
180
+ content:
181
+ - "Selected IDs: "
182
+ - type: Join
183
+ content: ~~.selectedIds
184
+
185
+ data:
186
+ selectedIds: []
187
+ ```
188
+
189
+ ### Custom templates
190
+
191
+ Use `itemTemplate` and `selectedTemplate` to render richer content. Both templates have access to all fields returned by the backend via `~.`.
192
+
193
+ ```yaml
194
+ - type: AutocompleteField
195
+ dataLocation: ~~.selectedItem
196
+ placeholder: "Search…"
197
+ src:
198
+ - "/api/items"
199
+ - param: "search"
200
+ value: "<reactive-json:autocomplete-field:input>"
201
+ itemTemplate:
202
+ type: div
203
+ content:
204
+ - type: strong
205
+ content: ~.label
206
+ - " (#"
207
+ - ~.value
208
+ - ")"
209
+ selectedTemplate:
210
+ type: span
211
+ content:
212
+ - ~.label
213
+ - " ✓"
214
+ ```
215
+
216
+ ## Limitations
217
+
218
+ - Requires an external API endpoint; no built-in static options support.
219
+ - The dropdown only renders while the user is actively typing (results are not re-fetched on re-open without re-typing).
220
+ - In multiple mode, selected items' display data is kept in local React state and may be lost on page reload unless persisted separately.
221
+ - Styling must be provided via external CSS targeting the `.rj-autocomplete*` classes, or via `attributes.style`.
222
+ - No built-in keyboard navigation for the dropdown.
223
+
224
+ templates: {}
225
+ data: {}
@@ -0,0 +1,108 @@
1
+ # Join
2
+
3
+ The `Join` component concatenates an array of values into a single string, with a configurable separator. It is useful for displaying lists of scalar values (IDs, labels, tags, etc.) inline.
4
+
5
+ ## Basic Syntax
6
+
7
+ ```yaml
8
+ - type: Join
9
+ content: ~~.myArray
10
+ separator: ", "
11
+ ```
12
+
13
+ ## Properties
14
+
15
+ - `content` (array, required): The array to join. Supports template references such as `~~.myArray` or `~.items`. Non-array values are coerced to string and displayed as-is.
16
+ - `separator` (string, optional): The string used between each element. Defaults to `", "`.
17
+ - `attributes` (object, optional): Additional attributes for the root element.
18
+ - `actions` (array, optional): Actions to execute based on component state.
19
+
20
+ ## Examples
21
+
22
+ ### Basic join with default separator
23
+
24
+ ```yaml
25
+ renderView:
26
+ - type: Join
27
+ content: ~~.tags
28
+
29
+ data:
30
+ tags:
31
+ - "react"
32
+ - "yaml"
33
+ - "json"
34
+ ```
35
+
36
+ Renders: `react, yaml, json`
37
+
38
+ ### Custom separator
39
+
40
+ ```yaml
41
+ renderView:
42
+ - type: Join
43
+ content: ~~.ids
44
+ separator: " | "
45
+
46
+ data:
47
+ ids:
48
+ - 1
49
+ - 2
50
+ - 3
51
+ ```
52
+
53
+ Renders: `1 | 2 | 3`
54
+
55
+ ### Used inside a sentence
56
+
57
+ ```yaml
58
+ renderView:
59
+ - type: p
60
+ content:
61
+ - "Selected items: "
62
+ - type: Join
63
+ content: ~~.selectedIds
64
+ separator: ", "
65
+
66
+ data:
67
+ selectedIds:
68
+ - 42
69
+ - 7
70
+ - 15
71
+ ```
72
+
73
+ ### With template-scoped data
74
+
75
+ ```yaml
76
+ renderView:
77
+ - type: Switch
78
+ content: ~~.users
79
+ singleOption:
80
+ load: userRow
81
+
82
+ templates:
83
+ userRow:
84
+ type: div
85
+ content:
86
+ - "Roles: "
87
+ - type: Join
88
+ content: ~.roles
89
+ separator: " / "
90
+
91
+ data:
92
+ users:
93
+ - name: "Alice"
94
+ roles: ["admin", "editor"]
95
+ - name: "Bob"
96
+ roles: ["viewer"]
97
+ ```
98
+
99
+ ## Notes
100
+
101
+ - If `content` evaluates to a non-array value, it is rendered as a plain string (no separator applied).
102
+ - Null or undefined `content` renders nothing.
103
+ - Array elements are joined using JavaScript's native `.join()`, so objects will render as `[object Object]`. Use this component with arrays of primitives (strings, numbers).
104
+
105
+ ## Limitations
106
+
107
+ - No support for rendering rich content (HTML, components) between items — use `Switch` with a template for that.
108
+ - No per-item formatting; all elements are joined as plain strings.
@@ -0,0 +1,113 @@
1
+ renderView:
2
+ - type: Markdown
3
+ content: |
4
+ # Join
5
+
6
+ The `Join` component concatenates an array of values into a single string, with a configurable separator. It is useful for displaying lists of scalar values (IDs, labels, tags, etc.) inline.
7
+
8
+ - type: Markdown
9
+ content: |
10
+ ## Basic Syntax
11
+
12
+ ```yaml
13
+ - type: Join
14
+ content: ~~.myArray
15
+ separator: ", "
16
+ ```
17
+
18
+ ## Properties
19
+
20
+ - type: DefinitionList
21
+ content:
22
+ - term:
23
+ code: content
24
+ after: "(array, required)"
25
+ details: "The array to join. Supports template references such as ~~.myArray or ~.items. Non-array values are coerced to string and displayed as-is."
26
+ - term:
27
+ code: separator
28
+ after: '(string, optional, default: ", ")'
29
+ details: "The string used between each element."
30
+ - term:
31
+ code: attributes
32
+ after: "(object, optional)"
33
+ details: "Additional attributes for the root element."
34
+ - term:
35
+ code: actions
36
+ after: "(array, optional)"
37
+ details: "Actions to execute based on component state."
38
+
39
+ - type: RjBuildDescriber
40
+ title: "Basic join with default separator"
41
+ description: "Join an array of strings with the default comma-space separator."
42
+ toDescribe:
43
+ renderView:
44
+ - type: p
45
+ content:
46
+ - "Tags: "
47
+ - type: Join
48
+ content: ~~.tags
49
+ data:
50
+ tags:
51
+ - "react"
52
+ - "yaml"
53
+ - "json"
54
+
55
+ - type: RjBuildDescriber
56
+ title: "Custom separator"
57
+ description: "Use a pipe separator to display a list of IDs."
58
+ toDescribe:
59
+ renderView:
60
+ - type: p
61
+ content:
62
+ - "IDs: "
63
+ - type: Join
64
+ content: ~~.ids
65
+ separator: " | "
66
+ - type: div
67
+ attributes:
68
+ class: "mt-5 p-2.5 rounded border border-gray-300 dark:border-gray-600"
69
+ content:
70
+ - type: strong
71
+ content: "Raw array: "
72
+ - type: Code
73
+ content: ~~.ids
74
+ data:
75
+ ids:
76
+ - 42
77
+ - 7
78
+ - 15
79
+
80
+ - type: RjBuildDescriber
81
+ title: "Inside a sentence"
82
+ description: "Embed a Join inside a paragraph to display selected values inline."
83
+ toDescribe:
84
+ renderView:
85
+ - type: p
86
+ content:
87
+ - "Selected items: "
88
+ - type: strong
89
+ content:
90
+ type: Join
91
+ content: ~~.selectedIds
92
+ separator: ", "
93
+ data:
94
+ selectedIds:
95
+ - 42
96
+ - 7
97
+ - 15
98
+
99
+ - type: Markdown
100
+ content: |
101
+ ## Notes
102
+
103
+ - If `content` evaluates to a non-array value, it is rendered as a plain string (no separator applied).
104
+ - Null or undefined `content` renders nothing.
105
+ - Array elements are joined using JavaScript's native `.join()`, so objects will render as `[object Object]`. Use this component with arrays of primitives (strings, numbers).
106
+
107
+ ## Limitations
108
+
109
+ - No support for rendering rich content (HTML, components) between items — use `Switch` with a template for that.
110
+ - No per-item formatting; all elements are joined as plain strings.
111
+
112
+ templates: {}
113
+ data: {}
@@ -257,4 +257,106 @@ data:
257
257
  - name: "string2"
258
258
  - name: "string3"
259
259
  ```
260
+
261
+ ## Caveats — common pitfalls that fail silently
262
+
263
+ These two patterns *look* correct, type-check, throw no error, and produce
264
+ empty output. They're easy to miss and have cost real debugging time.
265
+
266
+ ### Caveat 1 — `content` must be a template reference, not an inline array
267
+
268
+ Switch iterates whatever you put in `content`, but the per-item template
269
+ context (i.e. what `~.field` resolves against) is fetched from the **reactive
270
+ data store** at the iterated path. An inline array literal in `content` is
271
+ never written to the store, so each iteration's `templateData` ends up
272
+ `undefined` and every `~.field` resolves to nothing.
273
+
274
+ ```yaml
275
+ # ❌ Silent failure — items render but ~.label is undefined
276
+ renderView:
277
+ - type: Switch
278
+ content:
279
+ - {label: "Home"}
280
+ - {label: "About"}
281
+ singleOption:
282
+ load: navLink
283
+
284
+ templates:
285
+ navLink:
286
+ type: a
287
+ content: ~.label
288
+ ```
289
+
290
+ ```yaml
291
+ # ✅ Works — content points into the store
292
+ data:
293
+ navItems:
294
+ - {label: "Home"}
295
+ - {label: "About"}
296
+
297
+ renderView:
298
+ - type: Switch
299
+ content: ~~.navItems
300
+ singleOption:
301
+ load: navLink
302
+
303
+ templates:
304
+ navLink:
305
+ type: a
306
+ content: ~.label
307
+ ```
308
+
309
+ **Rule of thumb:** `Switch.content` is always a path reference (`~~.foo` for
310
+ global, `~.foo` for local), never a literal. If your iteration data is
311
+ "constant" from a page-data perspective, that's fine — just put it in the
312
+ YAML's `data:` section. It will still be reachable via `~~.foo`.
313
+
314
+ ### Caveat 2 — `singleOption` must use `load:`, not an inline template
315
+
316
+ The per-item `TemplateContext` that makes `~.field` resolve against the
317
+ current item is set up by the `load` branch of the `View` engine. Writing
318
+ `singleOption` as an inline `{type: ..., content: ...}` skips that setup —
319
+ the iteration happens, but `~.field` inside the inline template resolves
320
+ against the **outer** context (whatever was active where the Switch sits),
321
+ not the per-item context.
322
+
323
+ ```yaml
324
+ # ❌ ~.name resolves against the outer context, not each user
325
+ renderView:
326
+ - type: Switch
327
+ content: ~~.users
328
+ singleOption:
329
+ type: div
330
+ content: ~.name
331
+ ```
332
+
333
+ ```yaml
334
+ # ✅ Per-item context is correctly pushed
335
+ renderView:
336
+ - type: Switch
337
+ content: ~~.users
338
+ singleOption:
339
+ load: userCard
340
+
341
+ templates:
342
+ userCard:
343
+ type: div
344
+ content: ~.name
345
+ ```
346
+
347
+ **Rule of thumb:** treat `singleOption` and `options.<key>` as
348
+ `{load: <templateName>}` only. If you find yourself writing an inline
349
+ template there, factor it into `templates:` and reference it.
350
+
351
+ ### Debugging checklist when an iteration renders empty
352
+
353
+ 1. Is `Switch.content` a `~~.path` (not an inline literal)?
354
+ 2. Does that path exist in the store? (Defaults in `data:` are merged in
355
+ unless the parent mounts with a `dataOverride` that omits the field.)
356
+ 3. Is `singleOption` / `options.<key>` a `{load: <name>}`?
357
+ 4. Is each item in the data an object (not a primitive)? Switch silently
358
+ skips non-object entries.
359
+ 5. Is the `usePagination` hook available? Switch returns `null` if not —
360
+ normally guaranteed by `ReactiveJsonRoot` auto-merging
361
+ `coreComponentsPlugin`, but worth checking on a stripped-down root.
260
362
 
@@ -118,12 +118,109 @@ renderView:
118
118
 
119
119
  # ✅ This will work:
120
120
  data:
121
- items:
121
+ items:
122
122
  - name: "string1"
123
123
  - name: "string2"
124
124
  - name: "string3"
125
125
  ```
126
126
 
127
+ ## Caveats — common pitfalls that fail silently
128
+
129
+ Two patterns *look* correct, throw no error, and produce empty output.
130
+ They've cost real debugging time.
131
+
132
+ ### Caveat 1 — `content` must be a template reference, not an inline array
133
+
134
+ Switch iterates whatever you put in `content`, but the per-item
135
+ template context is fetched from the **reactive data store** at the
136
+ iterated path. An inline array literal in `content` is never written
137
+ to the store, so each iteration's `templateData` ends up `undefined`
138
+ and every `~.field` resolves to nothing.
139
+
140
+ ```yaml
141
+ # ❌ Silent failure — items render but ~.label is undefined
142
+ renderView:
143
+ - type: Switch
144
+ content:
145
+ - {label: "Home"}
146
+ - {label: "About"}
147
+ singleOption:
148
+ load: navLink
149
+ templates:
150
+ navLink:
151
+ type: a
152
+ content: ~.label
153
+ ```
154
+
155
+ ```yaml
156
+ # ✅ Works — content points into the store
157
+ data:
158
+ navItems:
159
+ - {label: "Home"}
160
+ - {label: "About"}
161
+ renderView:
162
+ - type: Switch
163
+ content: ~~.navItems
164
+ singleOption:
165
+ load: navLink
166
+ templates:
167
+ navLink:
168
+ type: a
169
+ content: ~.label
170
+ ```
171
+
172
+ **Rule of thumb:** `Switch.content` is always a path reference
173
+ (`~~.foo` for global, `~.foo` for local), never a literal. Constant
174
+ iteration data belongs in the YAML's `data:` section.
175
+
176
+ ### Caveat 2 — `singleOption` must use `load:`, not an inline template
177
+
178
+ The per-item TemplateContext that makes `~.field` resolve against the
179
+ current item is set up by the `load` branch of the View engine.
180
+ Writing `singleOption` as an inline `{type: ..., content: ...}` skips
181
+ that setup — the iteration happens, but `~.field` resolves against
182
+ the **outer** context, not the per-item context.
183
+
184
+ ```yaml
185
+ # ❌ ~.name resolves against the outer context
186
+ renderView:
187
+ - type: Switch
188
+ content: ~~.users
189
+ singleOption:
190
+ type: div
191
+ content: ~.name
192
+ ```
193
+
194
+ ```yaml
195
+ # ✅ Per-item context is correctly pushed
196
+ renderView:
197
+ - type: Switch
198
+ content: ~~.users
199
+ singleOption:
200
+ load: userCard
201
+ templates:
202
+ userCard:
203
+ type: div
204
+ content: ~.name
205
+ ```
206
+
207
+ **Rule of thumb:** treat `singleOption` and `options.<key>` as
208
+ `{load: <templateName>}` only. Factor inline templates into
209
+ `templates:`.
210
+
211
+ ### Debugging checklist when an iteration renders empty
212
+
213
+ 1. Is `Switch.content` a `~~.path` (not an inline literal)?
214
+ 2. Does that path exist in the store? (Defaults in `data:` are merged
215
+ in unless the parent mounts with a `dataOverride` that omits the
216
+ field.)
217
+ 3. Is `singleOption` / `options.<key>` a `{load: <name>}`?
218
+ 4. Is each item in the data an object (not a primitive)? Switch
219
+ silently skips non-object entries.
220
+ 5. Is the `usePagination` hook available? Switch returns `null` if
221
+ not — normally guaranteed by `ReactiveJsonRoot` auto-merging
222
+ `coreComponentsPlugin`.
223
+
127
224
  - type: RjBuildDescriber
128
225
  title: "Simple usage with `singleOption` (no pagination)"
129
226
  description: |
@@ -10,6 +10,8 @@
10
10
  ## Properties
11
11
  - `httpMethod` (string, optional): The HTTP method to use (default: "get"). Supports: get, post, put, patch, delete, etc.
12
12
  - `refreshAppOnResponse` (boolean, optional): If true (default), the response will update the application. If false, the response is ignored (webhook mode).
13
+ - `allowConcurrent` (boolean, optional): When true, allows concurrent requests (bypasses the global lock). Default: false. Implicitly true when `requestKey` is set.
14
+ - `requestKey` (string, optional): Identifier used to group requests that should cancel each other. When set, firing a new request with the same key aborts the previous one client-side (via AbortController). Different keys are independent. Bypasses the global lock automatically. See [Concurrency control](#concurrency-control--requestkey) below.
13
15
  - `updateOnlyData` (boolean, optional): When true and `refreshAppOnResponse` is true, only updates the data section instead of replacing the entire RjBuild. Preserves templates and renderView. Default: false.
14
16
  - `updateDataAtLocation` (string, optional): When `updateOnlyData` is true, specifies where to place the response data using template path syntax (e.g., "~~.userProfile", "~.config.settings"). If not specified, replaces the entire data object.
15
17
  - `url` (string, required): The URL to call (must be a static string, dynamic URLs are not supported)
@@ -54,6 +56,40 @@ The response event provides access to the complete server response through `<rea
54
56
 
55
57
  > **⚠️ Important:** When using `updateOnlyData: true`, the server response must contain **data only**, not a complete RjBuild structure. The response should be the raw data object, not wrapped in `{data: {...}, renderView: [...], templates: {...}}`.
56
58
 
59
+ ## Concurrency control — `requestKey`
60
+
61
+ By default, `fetchData` and `submitData` share a **global lock** at the document level: only one HTTP request can be active across the whole app at any time. A second call fired while the first is in flight is **silently dropped**. This is safe for most cases but bites when a user clicks rapidly through buckets in a filter UI, or hits two save buttons quickly — the second request's siblings (e.g. `setData` updating local UI state) still run, but the request itself never goes out, leaving UI and data out of sync.
62
+
63
+ `requestKey` opts the call into per-key cancel-on-new semantics. The library keeps a registry of in-flight requests keyed by `requestKey` and, on each new call:
64
+
65
+ 1. Aborts the previous request with the same key (via `AbortController` on the underlying axios call).
66
+ 2. Replaces the registry entry with the new request.
67
+ 3. Lets the new request proceed.
68
+
69
+ Aborted requests are silently swallowed: their `.catch` doesn't log an error, and their `.finally` skips the `response` event dispatch. The successor will fire its own `response` event normally.
70
+
71
+ ```yaml
72
+ # Rapid clicks on bucket A then B share `facet-refresh` — A's request
73
+ # is aborted as soon as B is fired, so only B's response lands in the
74
+ # store. Without requestKey, B would silently no-op (global lock held
75
+ # by A) and the UI would diverge from the data.
76
+ actions:
77
+ - what: setData
78
+ on: click
79
+ path: ~~.selectedFilter
80
+ value: ~.key
81
+ - what: fetchData
82
+ on: click
83
+ requestKey: facet-refresh
84
+ url: ~~.endpoint
85
+ updateOnlyData: true
86
+ updateDataAtLocation: ~~.result
87
+ ```
88
+
89
+ Different keys are independent: edit modals on multiple rows can each have their own `requestKey: 'save-row-42'` etc. and save simultaneously without interfering.
90
+
91
+ `requestKey` implies `allowConcurrent: true` — the global lock is bypassed automatically when a key is provided, since per-key cancel-on-new already guarantees single-flight within the key.
92
+
57
93
  ## Examples
58
94
 
59
95
  ### Data Loading with GET (Default)
@@ -26,6 +26,18 @@ renderView:
26
26
  details:
27
27
  type: Markdown
28
28
  content: "If true (default), the response will update the application. If false, the response is ignored (webhook mode)"
29
+ - term:
30
+ code: allowConcurrent
31
+ after: "(boolean, optional)"
32
+ details:
33
+ type: Markdown
34
+ content: "When true, allows concurrent requests (bypasses the global lock). Default: false. Implicitly true when `requestKey` is set."
35
+ - term:
36
+ code: requestKey
37
+ after: "(string, optional)"
38
+ details:
39
+ type: Markdown
40
+ content: "Identifier used to group requests that should cancel each other. When set, firing a new request with the same key aborts the previous one client-side (via AbortController). Different keys are independent. Bypasses the global lock automatically."
29
41
  - term:
30
42
  code: updateDataAtLocation
31
43
  after: "(string, optional)"
@@ -11,6 +11,8 @@
11
11
  - `httpMethod` (string, optional): The HTTP method to use (default: "post")
12
12
  - `data` (object, optional): The data to send. If not specified, data is sent in an object with the structure `{ data: globalDataContext.templateData }`. If `__state` exists in the context, it is automatically added.
13
13
  - `refreshAppOnResponse` (boolean, optional): If true (default), reloads the application with the server response. If false, the response is ignored and **no change is made to the application's state or display** (just like `fetchData`).
14
+ - `allowConcurrent` (boolean, optional): When true, allows concurrent requests (bypasses the global lock). Default: false. Implicitly true when `requestKey` is set.
15
+ - `requestKey` (string, optional): Identifier used to group requests that should cancel each other. When set, firing a new submission with the same key aborts the previous one client-side (via AbortController). Different keys are independent. Bypasses the global lock automatically. See the [Concurrency control section in `fetchData`](fetchData.md#concurrency-control--requestkey) — the semantics are identical.
14
16
  - `submitSilently` (boolean, optional): If true, doesn't apply visual disabling styles during submission
15
17
  - `updateOnlyData` (boolean, optional): When true and `refreshAppOnResponse` is true, only updates the data section instead of replacing the entire RjBuild. Preserves templates and renderView. Default: false.
16
18
  - `updateDataAtLocation` (string, optional): When `updateOnlyData` is true, specifies where to place the response data using template path syntax (e.g., "~~.userProfile", "~.config.settings"). If not specified, replaces the entire data object.
@@ -35,6 +35,18 @@ renderView:
35
35
  details:
36
36
  type: Markdown
37
37
  content: "If true (default), reloads the application with the server response. If false, the response is ignored and **no change is made to the application's state or display** (just like `fetchData`)"
38
+ - term:
39
+ code: allowConcurrent
40
+ after: "(boolean, optional)"
41
+ details:
42
+ type: Markdown
43
+ content: "When true, allows concurrent requests (bypasses the global lock). Default: false. Implicitly true when `requestKey` is set."
44
+ - term:
45
+ code: requestKey
46
+ after: "(string, optional)"
47
+ details:
48
+ type: Markdown
49
+ content: "Identifier used to group requests that should cancel each other. When set, firing a new submission with the same key aborts the previous one client-side (via AbortController). Different keys are independent. Bypasses the global lock automatically. See [Concurrency control in `fetchData`](fetchData) — same semantics."
38
50
  - term:
39
51
  code: submitSilently
40
52
  after: "(boolean, optional)"