@adriansteffan/reactive 0.0.44 → 0.1.1

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.
@@ -11,7 +11,14 @@
11
11
  "Bash(npx tsc:*)",
12
12
  "Bash(grep:*)",
13
13
  "Bash(cp:*)",
14
- "Bash(cd:*)"
14
+ "Bash(cd:*)",
15
+ "mcp__playwright__browser_take_screenshot",
16
+ "mcp__playwright__browser_evaluate",
17
+ "mcp__playwright__browser_run_code",
18
+ "Bash(npm test:*)",
19
+ "WebFetch(domain:raw.githubusercontent.com)",
20
+ "Bash(ls /Users/adriansteffan/Projects/reactive/README*)",
21
+ "Bash(ls /Users/adriansteffan/Projects/reactive/*.md)"
15
22
  ],
16
23
  "deny": [],
17
24
  "ask": []
package/README.md CHANGED
@@ -141,6 +141,142 @@ Hybrid mode is enabled by default during development. For production, set `VITE_
141
141
  | EnterFullscreen, ExitFullscreen, MicrophoneCheck, ProlificEnding, RequestFilePermission | *(none)* | No-op, advances immediately |
142
142
 
143
143
 
144
+ ## Data Saving
145
+
146
+ Reactive automatically builds CSV files from experiment data using a registry-based system. Each component type registers a default CSV target, and the Upload component discovers these at the end of the experiment.
147
+
148
+ ### How it works
149
+
150
+ Each component type registers where its data should go via `registerFlattener`:
151
+
152
+ ```tsx
153
+ registerFlattener('PlainInput', 'session'); // merge into session CSV
154
+ registerFlattener('CanvasBlock', 'canvas', flattenFn); // own CSV with custom flattener
155
+ registerFlattener('ProlificEnding', null); // no CSV output
156
+ ```
157
+
158
+ Built-in components come pre-registered. The Upload component produces CSVs automatically with no props needed:
159
+
160
+ ```tsx
161
+ { name: 'upload', type: 'Upload' }
162
+ ```
163
+
164
+ ### Built-in defaults
165
+
166
+ | Component | Default CSV | Notes |
167
+ |---|---|---|
168
+ | PlainInput, Quest, CheckDevice, EnterFullscreen, ExitFullscreen, MicrophoneCheck | `session` | Merged into single session row, namespaced by trial name |
169
+ | Text | `text` | One row per Text component |
170
+ | CanvasBlock | `canvas` | One row per slide, with built-in flattener |
171
+ | StoreUI | `storeui` | One row per StoreUI occurrence |
172
+ | ProlificEnding, Upload | *(none)* | No CSV output |
173
+
174
+ ### Output files
175
+
176
+ For a session `abc123`, the Upload component produces:
177
+ - `abc123.raw.json` — full raw data
178
+ - `session.abc123.{timestamp}.csv` — one row with params, userAgent, and all session-level trial data namespaced by trial name (e.g. `nickname_value`, `devicecheck_browser`)
179
+ - `canvas.abc123.{timestamp}.csv` — multi-row CSV from CanvasBlock trials
180
+ - One CSV per additional group (text, storeui, or any custom group)
181
+
182
+ ### Per-item CSV override
183
+
184
+ Override the default target on any timeline item:
185
+
186
+ ```tsx
187
+ { name: 'practice', type: 'CanvasBlock', csv: 'practice', ... } // separate from main canvas
188
+ { name: 'main', type: 'CanvasBlock', ... } // uses default 'canvas'
189
+ ```
190
+
191
+ Route a trial to multiple CSVs with an array:
192
+
193
+ ```tsx
194
+ { name: 'survey', type: 'Quest', csv: ['session', 'survey'], ... } // both session row and own file
195
+ ```
196
+
197
+ ### Adding session-level data
198
+
199
+ Use `sessionData` on Upload to inject extra fields into the session CSV:
200
+
201
+ ```tsx
202
+ // Static
203
+ {
204
+ type: 'Upload',
205
+ props: {
206
+ sessionData: { group: 'control', experimentVersion: 2 },
207
+ },
208
+ }
209
+
210
+ // Dynamic (computed from store/data)
211
+ {
212
+ type: 'Upload',
213
+ props: (data, store) => ({
214
+ sessionData: { group: store.assignedGroup, condition: store.condition },
215
+ }),
216
+ }
217
+ ```
218
+
219
+ ### Custom flatteners
220
+
221
+ Register a flattener for custom components to control how `responseData` becomes CSV rows:
222
+
223
+ ```tsx
224
+ registerFlattener('MyGame', 'games', (item) => {
225
+ return item.responseData.moves.map((move) => ({
226
+ moveType: move.type,
227
+ score: move.score,
228
+ }));
229
+ });
230
+ ```
231
+
232
+ Each row automatically gets standard trial fields prefixed with `trial_` (`trial_index`, `trial_name`, `trial_start`, etc.) plus any metadata from the timeline item. The flattener output overwrites these if keys collide.
233
+
234
+ ### Array flattener
235
+
236
+ For components whose `responseData` is an array of objects (like CanvasBlock), use the built-in `arrayFlattener` instead of writing your own. Each array element becomes a CSV row with a `block` column set to the trial name:
237
+
238
+ ```tsx
239
+ import { registerFlattener, arrayFlattener } from '@adriansteffan/reactive';
240
+
241
+ registerFlattener('MyBlockTrial', 'blocks', arrayFlattener);
242
+ ```
243
+
244
+ ### Multi-CSV components
245
+
246
+ Call `registerFlattener` multiple times for one component to produce multiple CSV files:
247
+
248
+ ```tsx
249
+ registerFlattener('SportsGame', 'sports_actions', (item) => flattenActions(item.responseData));
250
+ registerFlattener('SportsGame', 'sports_players', (item) => flattenPlayers(item.responseData));
251
+ registerFlattener('SportsGame', 'sports_matches', (item) => flattenMatches(item.responseData));
252
+ ```
253
+
254
+ ### Upload props
255
+
256
+ | Prop | Type | Default | Description |
257
+ |---|---|---|---|
258
+ | `sessionID` | `string` | random UUID | Custom session identifier used in filenames and folder names |
259
+ | `sessionData` | `Record<string, any>` | — | Extra key-value pairs added to the session CSV row |
260
+ | `generateFiles` | `(sessionID, data, store) => FileUpload[]` | — | Produce custom files alongside auto-generated CSVs |
261
+ | `uploadRaw` | `boolean` | `true` | Include raw JSON dump of all trial data |
262
+ | `autoUpload` | `boolean` | `false` | Upload immediately on mount instead of showing a submit button |
263
+
264
+ ### Metadata
265
+
266
+ Add `metadata` to timeline items to include extra columns in every CSV row that trial produces:
267
+
268
+ ```tsx
269
+ {
270
+ name: 'block1',
271
+ type: 'CanvasBlock',
272
+ metadata: { difficulty: 'hard', block: 2 },
273
+ props: { ... },
274
+ }
275
+ ```
276
+
277
+ For session-level items, metadata is namespaced by trial name (e.g. `block1_difficulty`). For non-session items, metadata columns appear unprefixed.
278
+
279
+
144
280
  ## Development
145
281
 
146
282