@hkdigital/lib-core 0.5.88 → 0.5.91

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.
Files changed (27) hide show
  1. package/dist/state/classes/reactive-data-store/README.md +445 -0
  2. package/dist/state/classes/reactive-data-store/ReactiveDataStore.svelte.d.ts +152 -0
  3. package/dist/state/classes/reactive-data-store/ReactiveDataStore.svelte.js +300 -0
  4. package/dist/state/classes.d.ts +1 -0
  5. package/dist/state/classes.js +1 -0
  6. package/dist/state/machines/page-machine/PageMachine.svelte.d.ts +45 -232
  7. package/dist/state/machines/page-machine/PageMachine.svelte.js +61 -308
  8. package/dist/state/machines/page-machine/README.md +79 -20
  9. package/dist/ui/CHANGES-COMPONENT-PROPS.md +121 -0
  10. package/dist/ui/components/game-box/GameBox.svelte +10 -7
  11. package/dist/ui/components/game-box/GameBox.svelte.d.ts +2 -0
  12. package/dist/ui/components/game-box/ScaledContainer.svelte +14 -7
  13. package/dist/ui/components/grid-layers/GridLayers.svelte +9 -7
  14. package/dist/ui/components/grid-layers/GridLayers.svelte.d.ts +2 -0
  15. package/dist/ui/components/image-box/ImageBox.svelte +9 -7
  16. package/dist/ui/components/image-box/ImageBox.svelte.d.ts +2 -0
  17. package/dist/ui/components/presenter/Presenter.svelte +5 -3
  18. package/dist/ui/components/presenter/Presenter.svelte.d.ts +2 -0
  19. package/dist/ui/primitives/buttons/button/Button.svelte +9 -7
  20. package/dist/ui/primitives/buttons/button/Button.svelte.d.ts +2 -0
  21. package/dist/ui/primitives/icons/SteezeIcon.svelte +7 -5
  22. package/dist/ui/primitives/icons/SteezeIcon.svelte.d.ts +2 -0
  23. package/dist/ui/primitives/panels/panel/Panel.svelte +9 -7
  24. package/dist/ui/primitives/panels/panel/Panel.svelte.d.ts +2 -0
  25. package/package.json +1 -1
  26. package/dist/state/classes/subscribers-count/index.d.ts +0 -1
  27. package/dist/state/classes/subscribers-count/index.js +0 -1
@@ -69,8 +69,8 @@
69
69
  * });
70
70
  * ```
71
71
  */
72
- import { SvelteMap, SvelteSet } from 'svelte/reactivity';
73
- import { dev } from '$app/environment';
72
+ import { SvelteSet } from 'svelte/reactivity';
73
+ import { ReactiveDataStore } from '../../classes.js';
74
74
 
75
75
  export default class PageMachine {
76
76
  /**
@@ -99,17 +99,14 @@ export default class PageMachine {
99
99
  #routes = [];
100
100
 
101
101
  /**
102
- * Reactive map for business/domain data
103
- * Uses SvelteMap for fine-grained reactivity
104
- * @type {SvelteMap<string, any>}
102
+ * Reactive data store for business/domain data
103
+ * @type {ReactiveDataStore}
105
104
  */
106
105
  #data;
107
106
 
108
107
  /**
109
- * Reactive map for dev-mode helper data
110
- * Uses SvelteMap for fine-grained reactivity
111
- * Only available in dev mode
112
- * @type {SvelteMap<string, any>|null}
108
+ * Reactive data store for dev-mode helper data
109
+ * @type {ReactiveDataStore}
113
110
  */
114
111
  #devData;
115
112
 
@@ -130,6 +127,8 @@ export default class PageMachine {
130
127
  * Optional list of valid routes (for validation/dev tools)
131
128
  * @param {Record<string, any>} [config.initialData={}]
132
129
  * Initial data properties (use KEY_ constants for keys)
130
+ * @param {Record<string, any>} [config.initialDevData={}]
131
+ * Initial dev data properties (use KEY_DEV_ constants for keys)
133
132
  * @param {import('../../../logging/client.js').Logger} [config.logger]
134
133
  * Logger instance (optional)
135
134
  *
@@ -138,6 +137,7 @@ export default class PageMachine {
138
137
  * const ROUTE_INTRO = '/intro/start';
139
138
  * const KEY_INTRO_COMPLETED = 'intro-completed';
140
139
  * const KEY_SCORE = 'score';
140
+ * const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
141
141
  *
142
142
  * const machine = new PageMachine({
143
143
  * startPath: ROUTE_INTRO,
@@ -145,11 +145,14 @@ export default class PageMachine {
145
145
  * initialData: {
146
146
  * [KEY_INTRO_COMPLETED]: false,
147
147
  * [KEY_SCORE]: 0
148
+ * },
149
+ * initialDevData: {
150
+ * [KEY_DEV_AUTO_NAVIGATION]: false
148
151
  * }
149
152
  * });
150
153
  * ```
151
154
  */
152
- constructor({ startPath, routes = [], initialData = {}, logger = null }) {
155
+ constructor({ startPath, routes = [], initialData = {}, initialDevData = {}, logger = null }) {
153
156
  if (!startPath) {
154
157
  throw new Error('PageMachine requires startPath parameter');
155
158
  }
@@ -160,18 +163,17 @@ export default class PageMachine {
160
163
  this.#current = startPath;
161
164
 
162
165
  // Initialize reactive data structures
163
- this.#data = new SvelteMap();
164
- this.#visitedRoutes = new SvelteSet();
166
+ this.#data = new ReactiveDataStore({
167
+ initialData
168
+ });
165
169
 
166
- // Initialize dev data (only in dev mode)
167
- if (dev) {
168
- this.#devData = new SvelteMap();
169
- }
170
+ this.#devData = new ReactiveDataStore({
171
+ initialData: initialDevData,
172
+ productionGuard: true,
173
+ errorPrefix: 'Dev data key'
174
+ });
170
175
 
171
- // Populate initial data
172
- for (const [key, value] of Object.entries(initialData)) {
173
- this.#data.set(key, value);
174
- }
176
+ this.#visitedRoutes = new SvelteSet();
175
177
 
176
178
  // Mark start path as visited
177
179
  this.#visitedRoutes.add(startPath);
@@ -256,324 +258,75 @@ export default class PageMachine {
256
258
  /* ===== Data Properties (Business/Domain State) ===== */
257
259
 
258
260
  /**
259
- * Set a data property value
260
- *
261
- * Automatically reactive - effects watching this key will re-run.
262
- * Uses fine-grained reactivity, so only effects watching this specific
263
- * key will be triggered.
264
- *
265
- * @param {string} key - Property key (use KEY_ constant)
266
- * @param {any} value - Property value
267
- *
268
- * @example
269
- * ```javascript
270
- * const KEY_HAS_STRONG_PROFILE = 'has-strong-profile';
271
- * const KEY_PROFILE_SCORE = 'profile-score';
272
- *
273
- * machine.setData(KEY_HAS_STRONG_PROFILE, true);
274
- * machine.setData(KEY_PROFILE_SCORE, 85);
275
- * ```
276
- */
277
- setData(key, value) {
278
- this.#data.set(key, value);
279
- }
280
-
281
- /**
282
- * Get a data property value
261
+ * Get the reactive data store
283
262
  *
284
- * Automatically reactive - creates a dependency on this specific key.
285
- * The effect will only re-run when THIS key changes, not when other
286
- * keys change.
263
+ * Provides read-only access to the data store instance.
264
+ * Access all data methods through this property.
287
265
  *
288
- * @param {string} key - Property key (use KEY_ constant)
289
- *
290
- * @returns {any} Property value or undefined
266
+ * @returns {ReactiveDataStore} The data store
291
267
  *
292
268
  * @example
293
269
  * ```javascript
294
270
  * const KEY_SCORE = 'score';
295
271
  *
296
- * // Reactive - re-runs only when KEY_SCORE changes
272
+ * // Set data
273
+ * machine.data.set(KEY_SCORE, 100);
274
+ *
275
+ * // Get data (reactive)
297
276
  * $effect(() => {
298
- * const score = machine.getData(KEY_SCORE);
277
+ * const score = machine.data.get(KEY_SCORE);
299
278
  * console.log('Score:', score);
300
279
  * });
301
- * ```
302
- */
303
- getData(key) {
304
- return this.#data.get(key);
305
- }
306
-
307
- /**
308
- * Get all data properties as plain object
309
- *
310
- * Note: This returns a snapshot (plain object), not a reactive map.
311
- * Use this for serialization or server sync, not for reactive tracking.
312
- *
313
- * @returns {Record<string, any>} Plain object with all data
314
- *
315
- * @example
316
- * ```javascript
317
- * const allData = machine.getAllData();
318
- * await playerService.saveData(allData);
319
- * ```
320
- */
321
- getAllData() {
322
- return Object.fromEntries(this.#data);
323
- }
324
-
325
- /**
326
- * Update multiple data properties at once
327
- *
328
- * Each property update triggers fine-grained reactivity.
329
- *
330
- * @param {Record<string, any>} dataUpdates
331
- * Object with key-value pairs (use KEY_ constants for keys)
332
- *
333
- * @example
334
- * ```javascript
335
- * const KEY_HAS_STRONG_PROFILE = 'has-strong-profile';
336
- * const KEY_PROFILE_SCORE = 'profile-score';
337
- * const KEY_MATCHED_SECTOR = 'matched-sector';
338
- *
339
- * machine.updateData({
340
- * [KEY_HAS_STRONG_PROFILE]: true,
341
- * [KEY_PROFILE_SCORE]: 85,
342
- * [KEY_MATCHED_SECTOR]: 'technology'
343
- * });
344
- * ```
345
- */
346
- updateData(dataUpdates) {
347
- for (const [key, value] of Object.entries(dataUpdates)) {
348
- this.#data.set(key, value);
349
- }
350
- }
351
-
352
- /**
353
- * Delete a data property
354
- *
355
- * @param {string} key - Property key to delete (use KEY_ constant)
356
- *
357
- * @returns {boolean} True if the key existed and was deleted
358
280
  *
359
- * @example
360
- * ```javascript
361
- * const KEY_TEMPORARY_FLAG = 'temporary-flag';
362
- *
363
- * machine.deleteData(KEY_TEMPORARY_FLAG);
281
+ * // Other operations
282
+ * machine.data.update({ [KEY_SCORE]: 200 });
283
+ * machine.data.has(KEY_SCORE);
284
+ * machine.data.delete(KEY_SCORE);
285
+ * machine.data.clear();
286
+ * console.log(machine.data.size);
364
287
  * ```
365
288
  */
366
- deleteData(key) {
367
- return this.#data.delete(key);
368
- }
369
-
370
- /**
371
- * Check if data property exists
372
- *
373
- * @param {string} key - Property key to check (use KEY_ constant)
374
- *
375
- * @returns {boolean} True if the key exists
376
- *
377
- * @example
378
- * ```javascript
379
- * const KEY_TUTORIAL_SEEN = 'tutorial-seen';
380
- *
381
- * if (machine.hasData(KEY_TUTORIAL_SEEN)) {
382
- * // Skip tutorial
383
- * }
384
- * ```
385
- */
386
- hasData(key) {
387
- return this.#data.has(key);
388
- }
389
-
390
- /**
391
- * Clear all data properties
392
- *
393
- * @example
394
- * ```javascript
395
- * machine.clearData(); // Reset all game data
396
- * ```
397
- */
398
- clearData() {
399
- this.#data.clear();
400
- }
401
-
402
- /**
403
- * Get number of data properties
404
- *
405
- * @returns {number} Number of data entries
406
- */
407
- get dataSize() {
408
- return this.#data.size;
289
+ get data() {
290
+ return this.#data;
409
291
  }
410
292
 
411
293
  /* ===== Dev Data Properties (Dev-Mode Helpers) ===== */
412
294
 
413
295
  /**
414
- * Set a dev data property value
296
+ * Get the reactive dev data store
415
297
  *
416
- * Automatically reactive - effects watching this key will re-run.
417
- * Only available in dev mode - no-op in production.
298
+ * Provides read-only access to the dev data store instance.
299
+ * Access all dev data methods through this property.
418
300
  *
419
- * @param {string} key - Property key (use KEY_DEV_ constant)
420
- * @param {any} value - Property value
301
+ * Dev data is only available in development mode. In production:
302
+ * - SET operations are silent no-ops
303
+ * - GET/HAS operations throw errors (programming errors)
421
304
  *
422
- * @example
423
- * ```javascript
424
- * const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
425
- * const KEY_DEV_SKIP_ANIMATIONS = 'dev-skip-animations';
426
- *
427
- * machine.setDevData(KEY_DEV_AUTO_NAVIGATION, true);
428
- * machine.setDevData(KEY_DEV_SKIP_ANIMATIONS, false);
429
- * ```
430
- */
431
- setDevData(key, value) {
432
- if (!dev) return;
433
- this.#devData.set(key, value);
434
- }
435
-
436
- /**
437
- * Get a dev data property value
438
- *
439
- * Automatically reactive - creates a dependency on this specific key.
440
- * Only available in dev mode - returns undefined in production.
441
- *
442
- * @param {string} key - Property key (use KEY_DEV_ constant)
443
- *
444
- * @returns {any} Property value or undefined
305
+ * @returns {ReactiveDataStore} The dev data store
445
306
  *
446
307
  * @example
447
308
  * ```javascript
448
- * const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
309
+ * const KEY_DEV_AUTO_NAV = 'dev-auto-navigation';
310
+ *
311
+ * // Set dev data (no-op in production)
312
+ * machine.devData.set(KEY_DEV_AUTO_NAV, true);
449
313
  *
450
- * // Reactive - re-runs only when KEY_DEV_AUTO_NAVIGATION changes
314
+ * // Get dev data (throws in production)
451
315
  * $effect(() => {
452
- * const autoNav = machine.getDevData(KEY_DEV_AUTO_NAVIGATION);
453
- * console.log('Auto-navigation:', autoNav);
316
+ * const autoNav = machine.devData.get(KEY_DEV_AUTO_NAV);
317
+ * console.log('Auto-nav:', autoNav);
454
318
  * });
455
- * ```
456
- */
457
- getDevData(key) {
458
- if (!dev) return undefined;
459
- return this.#devData.get(key);
460
- }
461
-
462
- /**
463
- * Get all dev data properties as plain object
464
- *
465
- * Note: Returns a snapshot (plain object), not reactive.
466
- * Only available in dev mode - returns empty object in production.
467
- *
468
- * @returns {Record<string, any>} Plain object with all dev data
469
319
  *
470
- * @example
471
- * ```javascript
472
- * const allDevData = machine.getAllDevData();
473
- * console.log('Dev settings:', allDevData);
320
+ * // Other operations
321
+ * machine.devData.update({ [KEY_DEV_AUTO_NAV]: false });
322
+ * machine.devData.has(KEY_DEV_AUTO_NAV);
323
+ * machine.devData.delete(KEY_DEV_AUTO_NAV);
324
+ * machine.devData.clear();
325
+ * console.log(machine.devData.size);
474
326
  * ```
475
327
  */
476
- getAllDevData() {
477
- if (!dev) return {};
478
- return Object.fromEntries(this.#devData);
479
- }
480
-
481
- /**
482
- * Update multiple dev data properties at once
483
- *
484
- * Each property update triggers fine-grained reactivity.
485
- * Only available in dev mode - no-op in production.
486
- *
487
- * @param {Record<string, any>} dataUpdates
488
- * Object with key-value pairs (use KEY_DEV_ constants for keys)
489
- *
490
- * @example
491
- * ```javascript
492
- * const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
493
- * const KEY_DEV_SKIP_ANIMATIONS = 'dev-skip-animations';
494
- *
495
- * machine.updateDevData({
496
- * [KEY_DEV_AUTO_NAVIGATION]: true,
497
- * [KEY_DEV_SKIP_ANIMATIONS]: false
498
- * });
499
- * ```
500
- */
501
- updateDevData(dataUpdates) {
502
- if (!dev) return;
503
- for (const [key, value] of Object.entries(dataUpdates)) {
504
- this.#devData.set(key, value);
505
- }
506
- }
507
-
508
- /**
509
- * Delete a dev data property
510
- *
511
- * Only available in dev mode - no-op in production.
512
- *
513
- * @param {string} key - Property key to delete (use KEY_DEV_ constant)
514
- *
515
- * @returns {boolean} True if the key existed and was deleted
516
- *
517
- * @example
518
- * ```javascript
519
- * const KEY_DEV_TEMP_FLAG = 'dev-temp-flag';
520
- *
521
- * machine.deleteDevData(KEY_DEV_TEMP_FLAG);
522
- * ```
523
- */
524
- deleteDevData(key) {
525
- if (!dev) return false;
526
- return this.#devData.delete(key);
527
- }
528
-
529
- /**
530
- * Check if dev data property exists
531
- *
532
- * Only available in dev mode - returns false in production.
533
- *
534
- * @param {string} key - Property key to check (use KEY_DEV_ constant)
535
- *
536
- * @returns {boolean} True if the key exists
537
- *
538
- * @example
539
- * ```javascript
540
- * const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
541
- *
542
- * if (machine.hasDevData(KEY_DEV_AUTO_NAVIGATION)) {
543
- * // Dev setting exists
544
- * }
545
- * ```
546
- */
547
- hasDevData(key) {
548
- if (!dev) return false;
549
- return this.#devData.has(key);
550
- }
551
-
552
- /**
553
- * Clear all dev data properties
554
- *
555
- * Only available in dev mode - no-op in production.
556
- *
557
- * @example
558
- * ```javascript
559
- * machine.clearDevData(); // Reset all dev settings
560
- * ```
561
- */
562
- clearDevData() {
563
- if (!dev) return;
564
- this.#devData.clear();
565
- }
566
-
567
- /**
568
- * Get number of dev data properties
569
- *
570
- * Only available in dev mode - returns 0 in production.
571
- *
572
- * @returns {number} Number of dev data entries
573
- */
574
- get devDataSize() {
575
- if (!dev) return 0;
576
- return this.#devData.size;
328
+ get devData() {
329
+ return this.#devData;
577
330
  }
578
331
 
579
332
  /* ===== Visited Routes Tracking ===== */
@@ -78,6 +78,10 @@ const KEY_TUTORIAL_SEEN = 'tutorial-seen';
78
78
  const KEY_HIGHEST_LEVEL = 'highest-level';
79
79
  const KEY_DIFFICULTY = 'difficulty';
80
80
 
81
+ // Dev data keys (use KEY_DEV_ prefix)
82
+ const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
83
+ const KEY_DEV_SKIP_ANIMATIONS = 'dev-skip-animations';
84
+
81
85
  export class PuzzleState extends PageMachine {
82
86
  #logic;
83
87
 
@@ -96,6 +100,10 @@ export class PuzzleState extends PageMachine {
96
100
  [KEY_TUTORIAL_SEEN]: false,
97
101
  [KEY_HIGHEST_LEVEL]: 1,
98
102
  [KEY_DIFFICULTY]: 'normal'
103
+ },
104
+ initialDevData: {
105
+ [KEY_DEV_AUTO_NAVIGATION]: false,
106
+ [KEY_DEV_SKIP_ANIMATIONS]: false
99
107
  }
100
108
  });
101
109
 
@@ -127,32 +135,32 @@ export class PuzzleState extends PageMachine {
127
135
  return this.current === ROUTE_COMPLETE;
128
136
  }
129
137
 
130
- // Persistent settings/progress (use getData/setData)
138
+ // Persistent settings/progress (use machine.data)
131
139
  get hasSeenTutorial() {
132
- return this.getData(KEY_TUTORIAL_SEEN) || false;
140
+ return this.data.get(KEY_TUTORIAL_SEEN) || false;
133
141
  }
134
142
 
135
143
  markTutorialComplete() {
136
- this.setData(KEY_TUTORIAL_SEEN, true);
144
+ this.data.set(KEY_TUTORIAL_SEEN, true);
137
145
  }
138
146
 
139
147
  get highestLevel() {
140
- return this.getData(KEY_HIGHEST_LEVEL) || 1;
148
+ return this.data.get(KEY_HIGHEST_LEVEL) || 1;
141
149
  }
142
150
 
143
151
  updateHighestLevel(level) {
144
152
  const current = this.highestLevel;
145
153
  if (level > current) {
146
- this.setData(KEY_HIGHEST_LEVEL, level);
154
+ this.data.set(KEY_HIGHEST_LEVEL, level);
147
155
  }
148
156
  }
149
157
 
150
158
  get difficulty() {
151
- return this.getData(KEY_DIFFICULTY) || 'normal';
159
+ return this.data.get(KEY_DIFFICULTY) || 'normal';
152
160
  }
153
161
 
154
162
  setDifficulty(level) {
155
- this.setData(KEY_DIFFICULTY, level);
163
+ this.data.set(KEY_DIFFICULTY, level);
156
164
  }
157
165
 
158
166
  // Optional: Lifecycle methods
@@ -265,11 +273,25 @@ puzzleState.isStartPath(path) // Check if path is start path
265
273
  puzzleState.isOnStartPath // Check if on start path
266
274
  puzzleState.redirectToStartPath() // Navigate to start path
267
275
 
268
- // Persistent data properties
269
- puzzleState.setData(KEY_NAME, value)
270
- puzzleState.getData(KEY_NAME)
271
- puzzleState.getAllData()
272
- puzzleState.updateData({ KEY1: val1, KEY2: val2 })
276
+ // Persistent data properties (via ReactiveDataStore)
277
+ puzzleState.data.set(KEY_NAME, value)
278
+ puzzleState.data.get(KEY_NAME)
279
+ puzzleState.data.getAll()
280
+ puzzleState.data.update({ KEY1: val1, KEY2: val2 })
281
+ puzzleState.data.has(KEY_NAME)
282
+ puzzleState.data.delete(KEY_NAME)
283
+ puzzleState.data.clear()
284
+ puzzleState.data.size
285
+
286
+ // Dev data properties (dev-only, via ReactiveDataStore)
287
+ puzzleState.devData.set(KEY_DEV_NAME, value)
288
+ puzzleState.devData.get(KEY_DEV_NAME)
289
+ puzzleState.devData.getAll()
290
+ puzzleState.devData.update({ KEY1: val1 })
291
+ puzzleState.devData.has(KEY_DEV_NAME)
292
+ puzzleState.devData.delete(KEY_DEV_NAME)
293
+ puzzleState.devData.clear()
294
+ puzzleState.devData.size
273
295
 
274
296
  // Visited routes tracking
275
297
  puzzleState.hasVisited(route) // e.g., hasVisited('/puzzle/intro')
@@ -288,9 +310,9 @@ puzzleState.hasSeenTutorial
288
310
 
289
311
  ## Data Storage Guidelines
290
312
 
291
- ### When to use `getData/setData` (PageMachine)
313
+ ### When to use `machine.data` (ReactiveDataStore)
292
314
 
293
- Use PageMachine's data properties for **persistent settings and progress**:
315
+ Use PageMachine's data store for **persistent settings and progress**:
294
316
 
295
317
  - ✅ Tutorial completion flags
296
318
  - ✅ User preferences (difficulty, language, sound)
@@ -311,13 +333,39 @@ const KEY_DIFFICULTY = 'difficulty';
311
333
  const KEY_HIGHEST_LEVEL = 'highest-level';
312
334
 
313
335
  // Use constants (not strings!)
314
- pageMachine.setData(KEY_TUTORIAL_SEEN, true); // ✅ Good
315
- pageMachine.setData(KEY_DIFFICULTY, 'hard'); // ✅ Good
316
- pageMachine.setData(KEY_HIGHEST_LEVEL, 5); // ✅ Good
336
+ pageMachine.data.set(KEY_TUTORIAL_SEEN, true); // ✅ Good
337
+ pageMachine.data.set(KEY_DIFFICULTY, 'hard'); // ✅ Good
338
+ pageMachine.data.set(KEY_HIGHEST_LEVEL, 5); // ✅ Good
317
339
 
318
340
  // DON'T use magic strings
319
- pageMachine.setData('tutorial-seen', true); // ❌ Avoid
320
- pageMachine.setData('TUTORIAL_SEEN', true); // ❌ Avoid
341
+ pageMachine.data.set('tutorial-seen', true); // ❌ Avoid
342
+ pageMachine.data.set('TUTORIAL_SEEN', true); // ❌ Avoid
343
+ ```
344
+
345
+ ### When to use `machine.devData` (Dev-only ReactiveDataStore)
346
+
347
+ Use PageMachine's devData store for **development helpers**:
348
+
349
+ - ✅ Auto-navigation flags
350
+ - ✅ Skip animations/delays
351
+ - ✅ Mock API endpoints
352
+ - ✅ Debug mode flags
353
+ - ✅ Development-only settings
354
+
355
+ **IMPORTANT**: Use KEY_DEV_ prefix for dev data constants:
356
+
357
+ ```javascript
358
+ // Define constants (at top of file)
359
+ const KEY_DEV_AUTO_NAVIGATION = 'dev-auto-navigation';
360
+ const KEY_DEV_SKIP_ANIMATIONS = 'dev-skip-animations';
361
+ const KEY_DEV_MOCK_API = 'dev-mock-api';
362
+
363
+ // Use in development (no-op in production)
364
+ pageMachine.devData.set(KEY_DEV_AUTO_NAVIGATION, true); // ✅ Good
365
+ pageMachine.devData.set(KEY_DEV_SKIP_ANIMATIONS, false); // ✅ Good
366
+
367
+ // Reading in production throws error (programming bug)
368
+ const autoNav = pageMachine.devData.get(KEY_DEV_AUTO_NAVIGATION);
321
369
  ```
322
370
 
323
371
  ### When to use GameLogic with `$state`
@@ -350,6 +398,17 @@ export class PuzzleGameLogic {
350
398
  - Routes are the source of truth (no state abstraction layer)
351
399
  - Use route constants for clarity and maintainability
352
400
  - Always sync in `$effect` watching `$page.url.pathname`
353
- - Use constants for data keys (e.g., `KEY_TUTORIAL_SEEN`)
401
+ - Use constants for data keys (e.g., `KEY_TUTORIAL_SEEN`, `KEY_DEV_AUTO_NAV`)
354
402
  - Separate persistent data (PageMachine) from reactive state (GameLogic)
355
403
  - Handle animations in pages using `$effect` or `onMount`
404
+ - Data and devData use [ReactiveDataStore](../../classes/reactive-data-store/README.md)
405
+ for fine-grained reactivity
406
+ - Access data via read-only getters: `machine.data.get()`, `machine.devData.set()`
407
+
408
+ ## See Also
409
+
410
+ - [ReactiveDataStore](../../classes/reactive-data-store/README.md) - The
411
+ underlying reactive data storage implementation
412
+ - [FiniteStateMachine](../finite-state-machine/README.md) - For enforced state
413
+ transitions
414
+ - [State Context](../../context/README.md) - For providing state to components