@everystate/examples 1.0.0 → 1.0.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.
Files changed (152) hide show
  1. package/README.md +15 -0
  2. package/everyState-core/001-counter/README.md +44 -0
  3. package/everyState-core/001-counter/index.html +79 -0
  4. package/everyState-core/002-counter-improved/README.md +44 -0
  5. package/everyState-core/002-counter-improved/index.html +83 -0
  6. package/everyState-core/003-input-reactive/README.md +44 -0
  7. package/everyState-core/003-input-reactive/index.html +68 -0
  8. package/everyState-core/004-computed-state/README.md +45 -0
  9. package/everyState-core/004-computed-state/index.html +83 -0
  10. package/everyState-core/005-conditional-rendering/README.md +42 -0
  11. package/everyState-core/005-conditional-rendering/index.html +68 -0
  12. package/everyState-core/006-list-rendering/README.md +49 -0
  13. package/everyState-core/006-list-rendering/index.html +92 -0
  14. package/everyState-core/007-form-validation/README.md +52 -0
  15. package/everyState-core/007-form-validation/index.html +108 -0
  16. package/everyState-core/008-undo-redo/README.md +70 -0
  17. package/everyState-core/008-undo-redo/index.html +133 -0
  18. package/everyState-core/009-localStorage-side-effects/README.md +72 -0
  19. package/everyState-core/009-localStorage-side-effects/index.html +80 -0
  20. package/everyState-core/010-decoupled-components/README.md +74 -0
  21. package/everyState-core/010-decoupled-components/index.html +117 -0
  22. package/everyState-core/011-async-patterns/README.md +98 -0
  23. package/everyState-core/011-async-patterns/index.html +132 -0
  24. package/everyState-css/001-stateDrivenCSS/index.html +377 -0
  25. package/everyState-css/002-cssV2FullDemo/index.html +630 -0
  26. package/everyState-view/001/counter/index.css +31 -0
  27. package/everyState-view/001/counter/index.html +50 -0
  28. package/everyState-view/002/datatable/index.css +70 -0
  29. package/everyState-view/002/datatable/index.html +118 -0
  30. package/everyState-view/003/todo/index.css +260 -0
  31. package/everyState-view/003/todo/index.html +218 -0
  32. package/everyState-view/003-input-reactive/README.md +44 -0
  33. package/everyState-view/003-input-reactive/index.html +68 -0
  34. package/everyState-view/004/quotesFetcher/index.css +124 -0
  35. package/everyState-view/004/quotesFetcher/index.html +108 -0
  36. package/everyState-view/004_01/quotesFetcher/app.js +32 -0
  37. package/everyState-view/004_01/quotesFetcher/components/appHeader/appSubtitle.js +2 -0
  38. package/everyState-view/004_01/quotesFetcher/components/appHeader/appTitle.js +2 -0
  39. package/everyState-view/004_01/quotesFetcher/components/appHeader.js +9 -0
  40. package/everyState-view/004_01/quotesFetcher/components/historyHeading.js +2 -0
  41. package/everyState-view/004_01/quotesFetcher/components/historyList/histAuthor.js +2 -0
  42. package/everyState-view/004_01/quotesFetcher/components/historyList/histQuote.js +2 -0
  43. package/everyState-view/004_01/quotesFetcher/components/historyList.js +14 -0
  44. package/everyState-view/004_01/quotesFetcher/components/quoteCard/fetchButton.js +2 -0
  45. package/everyState-view/004_01/quotesFetcher/components/quoteCard/quoteAuthor.js +2 -0
  46. package/everyState-view/004_01/quotesFetcher/components/quoteCard/quoteMessage.js +2 -0
  47. package/everyState-view/004_01/quotesFetcher/components/quoteCard/quoteText.js +2 -0
  48. package/everyState-view/004_01/quotesFetcher/components/quoteCard.js +11 -0
  49. package/everyState-view/004_01/quotesFetcher/index.css +124 -0
  50. package/everyState-view/004_01/quotesFetcher/index.html +23 -0
  51. package/everyState-view/004_01/quotesFetcher/store.js +35 -0
  52. package/everyState-view/004_02/quotesFetcher/app.js +20 -0
  53. package/everyState-view/004_02/quotesFetcher/components.js +46 -0
  54. package/everyState-view/004_02/quotesFetcher/index.css +124 -0
  55. package/everyState-view/004_02/quotesFetcher/index.html +23 -0
  56. package/everyState-view/004_02/quotesFetcher/store.js +35 -0
  57. package/everyState-view/004_03/quotesFetcher/actions.js +27 -0
  58. package/everyState-view/004_03/quotesFetcher/app.js +19 -0
  59. package/everyState-view/004_03/quotesFetcher/components.js +28 -0
  60. package/everyState-view/004_03/quotesFetcher/index.css +124 -0
  61. package/everyState-view/004_03/quotesFetcher/index.html +23 -0
  62. package/everyState-view/004_03/quotesFetcher/resolve.js +34 -0
  63. package/everyState-view/004_03/quotesFetcher/store.js +11 -0
  64. package/everyState-view/004_04/quotesFetcher/actions.js +66 -0
  65. package/everyState-view/004_04/quotesFetcher/app.js +24 -0
  66. package/everyState-view/004_04/quotesFetcher/components/archive.js +40 -0
  67. package/everyState-view/004_04/quotesFetcher/components/fetcher.js +29 -0
  68. package/everyState-view/004_04/quotesFetcher/components.js +20 -0
  69. package/everyState-view/004_04/quotesFetcher/index.css +283 -0
  70. package/everyState-view/004_04/quotesFetcher/index.html +24 -0
  71. package/everyState-view/004_04/quotesFetcher/resolve.js +34 -0
  72. package/everyState-view/004_04/quotesFetcher/store.js +21 -0
  73. package/everyState-view/004_04/statedump.json +826 -0
  74. package/everyState-view/004_05/quoteExplorer/actions.js +58 -0
  75. package/everyState-view/004_05/quoteExplorer/app.js +27 -0
  76. package/everyState-view/004_05/quoteExplorer/components.js +83 -0
  77. package/everyState-view/004_05/quoteExplorer/index.css +231 -0
  78. package/everyState-view/004_05/quoteExplorer/index.html +23 -0
  79. package/everyState-view/004_05/quoteExplorer/resolve.js +50 -0
  80. package/everyState-view/004_05/quoteExplorer/store.js +33 -0
  81. package/everyState-view/004_06/quoteExplorer/actions.js +21 -0
  82. package/everyState-view/004_06/quoteExplorer/app.js +44 -0
  83. package/everyState-view/004_06/quoteExplorer/components.js +80 -0
  84. package/everyState-view/004_06/quoteExplorer/derived.js +43 -0
  85. package/everyState-view/004_06/quoteExplorer/index.css +346 -0
  86. package/everyState-view/004_06/quoteExplorer/index.html +25 -0
  87. package/everyState-view/004_06/quoteExplorer/intents.js +44 -0
  88. package/everyState-view/004_06/quoteExplorer/policies.js +25 -0
  89. package/everyState-view/004_06/quoteExplorer/resolve.js +51 -0
  90. package/everyState-view/004_06/quoteExplorer/store.js +44 -0
  91. package/everyState-view/004_07/quoteExplorer/app.js +47 -0
  92. package/everyState-view/004_07/quoteExplorer/components.js +85 -0
  93. package/everyState-view/004_07/quoteExplorer/derived.js +43 -0
  94. package/everyState-view/004_07/quoteExplorer/index.css +346 -0
  95. package/everyState-view/004_07/quoteExplorer/index.html +25 -0
  96. package/everyState-view/004_07/quoteExplorer/intents.js +51 -0
  97. package/everyState-view/004_07/quoteExplorer/policies.js +21 -0
  98. package/everyState-view/004_07/quoteExplorer/resolve.js +39 -0
  99. package/everyState-view/004_07/quoteExplorer/store.js +44 -0
  100. package/everyState-view/004_08/quoteExplorer/app.js +78 -0
  101. package/everyState-view/004_08/quoteExplorer/components.js +85 -0
  102. package/everyState-view/004_08/quoteExplorer/derived.js +43 -0
  103. package/everyState-view/004_08/quoteExplorer/index.css +346 -0
  104. package/everyState-view/004_08/quoteExplorer/index.html +25 -0
  105. package/everyState-view/004_08/quoteExplorer/intents.js +51 -0
  106. package/everyState-view/004_08/quoteExplorer/policies.js +21 -0
  107. package/everyState-view/004_08/quoteExplorer/resolve.js +39 -0
  108. package/everyState-view/004_08/quoteExplorer/store.js +44 -0
  109. package/everyState-view/004_08_V2/app.js +78 -0
  110. package/everyState-view/004_08_V2/components/appDetail.js +8 -0
  111. package/everyState-view/004_08_V2/components/appDetailBar.js +7 -0
  112. package/everyState-view/004_08_V2/components/appDetailBarClose.js +8 -0
  113. package/everyState-view/004_08_V2/components/appDetailBarCount.js +7 -0
  114. package/everyState-view/004_08_V2/components/appDetailBarHeading.js +7 -0
  115. package/everyState-view/004_08_V2/components/appDetailQuotes.js +15 -0
  116. package/everyState-view/004_08_V2/components/appHeader.js +7 -0
  117. package/everyState-view/004_08_V2/components/appHeaderSubtitle.js +7 -0
  118. package/everyState-view/004_08_V2/components/appHeaderTitle.js +7 -0
  119. package/everyState-view/004_08_V2/components/appLog.js +7 -0
  120. package/everyState-view/004_08_V2/components/appLogHeading.js +7 -0
  121. package/everyState-view/004_08_V2/components/appLogList.js +16 -0
  122. package/everyState-view/004_08_V2/components/appSearch.js +7 -0
  123. package/everyState-view/004_08_V2/components/appSearchInput.js +9 -0
  124. package/everyState-view/004_08_V2/components/appStats.js +7 -0
  125. package/everyState-view/004_08_V2/components/appStatsContent.js +8 -0
  126. package/everyState-view/004_08_V2/components/appStatsContentFavcount.js +7 -0
  127. package/everyState-view/004_08_V2/components/appStatsContentText.js +7 -0
  128. package/everyState-view/004_08_V2/components/appStatsToggle.js +8 -0
  129. package/everyState-view/004_08_V2/components/appTags.js +7 -0
  130. package/everyState-view/004_08_V2/components/appTagsLabel.js +7 -0
  131. package/everyState-view/004_08_V2/components/appTagsRow.js +15 -0
  132. package/everyState-view/004_08_V2/components/index.js +59 -0
  133. package/everyState-view/004_08_V2/components/utils/css.js +88 -0
  134. package/everyState-view/004_08_V2/components/utils/elements.js +87 -0
  135. package/everyState-view/004_08_V2/components.js +79 -0
  136. package/everyState-view/004_08_V2/derived.js +43 -0
  137. package/everyState-view/004_08_V2/index.css +350 -0
  138. package/everyState-view/004_08_V2/index.html +25 -0
  139. package/everyState-view/004_08_V2/intents.js +51 -0
  140. package/everyState-view/004_08_V2/policies.js +21 -0
  141. package/everyState-view/004_08_V2/resolve.js +39 -0
  142. package/everyState-view/004_08_V2/store.js +44 -0
  143. package/everyState-view/006/api-datatable/index.css +388 -0
  144. package/everyState-view/006/api-datatable/index.html +355 -0
  145. package/everyState-view/007/apiUsers/index.html +307 -0
  146. package/everyState-view/007-form-validation/README.md +52 -0
  147. package/everyState-view/007-form-validation/index.html +108 -0
  148. package/everyState-view/010-decoupled-components/README.md +74 -0
  149. package/everyState-view/010-decoupled-components/index.html +117 -0
  150. package/everyState-view/index.html +36 -0
  151. package/index.js +0 -5
  152. package/package.json +2 -4
@@ -0,0 +1,92 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>006 List Rendering - EveryState</title>
7
+ <script type="importmap">
8
+ { "imports": {
9
+ "@everystate/core": "../../../everystate-core/index.js",
10
+ "@everystate/perf": "../../../everystate-perf/index.js"
11
+ }}
12
+ </script>
13
+ </head>
14
+ <body>
15
+ <h1>Shopping List</h1>
16
+
17
+ <input type="text" id="itemInput" placeholder="Add item...">
18
+ <button id="addBtn">Add</button>
19
+
20
+ <ul id="itemList"></ul>
21
+ <hr>
22
+ <button id="runTests">Run Tests</button>
23
+ <pre id="testLog"></pre>
24
+
25
+ <script type="module">
26
+ import { createEveryState } from '@everystate/core';
27
+ import { createPerfMonitor, mountOverlay } from '@everystate/perf';
28
+
29
+ const store = createEveryState({ items: [] });
30
+ const perf = createPerfMonitor(store);
31
+ mountOverlay(perf, document.body);
32
+
33
+ const renderList = () => {
34
+ const items = store.get('items');
35
+ const list = document.getElementById('itemList');
36
+ if (items.length === 0) {
37
+ list.innerHTML = '<li><em>No items yet</em></li>';
38
+ } else {
39
+ list.innerHTML = items.map(item => `<li>${item}</li>`).join('');
40
+ }
41
+ };
42
+
43
+ store.subscribe('items', renderList);
44
+
45
+ document.getElementById('addBtn').onclick = () => {
46
+ const input = document.getElementById('itemInput');
47
+ const value = input.value.trim();
48
+ if (value) {
49
+ store.set('items', [...store.get('items'), value]);
50
+ input.value = '';
51
+ }
52
+ };
53
+
54
+ document.getElementById('itemInput').onkeypress = (e) => {
55
+ if (e.key === 'Enter') document.getElementById('addBtn').click();
56
+ };
57
+
58
+ renderList();
59
+
60
+ // Event Sequence Tests
61
+ document.getElementById('runTests').onclick = () => {
62
+ const results = [];
63
+ let pass = 0, fail = 0;
64
+ function assert(label, cond) {
65
+ if (cond) { pass++; results.push(' OK ' + label); }
66
+ else { fail++; results.push(' FAIL ' + label); }
67
+ }
68
+
69
+ store.set('items', []);
70
+ assert('starts empty', store.get('items').length === 0);
71
+
72
+ store.set('items', ['Milk']);
73
+ assert('add one item', store.get('items').length === 1);
74
+ assert('DOM has 1 li', document.getElementById('itemList').querySelectorAll('li').length === 1);
75
+
76
+ store.set('items', ['Milk', 'Bread', 'Eggs']);
77
+ assert('set 3 items', store.get('items').length === 3);
78
+ assert('DOM has 3 li', document.getElementById('itemList').querySelectorAll('li').length === 3);
79
+
80
+ let fired = false;
81
+ const unsub = store.subscribe('items', () => { fired = true; });
82
+ store.set('items', []);
83
+ assert('subscriber fires on clear', fired);
84
+ unsub();
85
+
86
+ assert('perf tracked', perf.report().summary.totalSets > 0);
87
+
88
+ document.getElementById('testLog').textContent = results.join('\n') + `\n\n${pass} passed, ${fail} failed`;
89
+ };
90
+ </script>
91
+ </body>
92
+ </html>
@@ -0,0 +1,52 @@
1
+ # 007 Form Validation - Real-time Feedback
2
+
3
+ Demonstrates form validation with multiple state paths and computed validity.
4
+
5
+ ## What's Here
6
+
7
+ - Email and password inputs
8
+ - Real-time validation messages
9
+ - Submit button enabled only when form is valid
10
+ - **No form library needed** - just state and functions
11
+
12
+ ## How It Works
13
+
14
+ ```javascript
15
+ // 1. Store input values AND validation state
16
+ const store = createEveryState({
17
+ email: '',
18
+ password: '',
19
+ emailValid: false,
20
+ passwordValid: false
21
+ });
22
+
23
+ // 2. Validate on input change
24
+ store.subscribe('email', (value) => {
25
+ const isValid = validateEmail(value);
26
+ store.set('emailValid', isValid);
27
+ // Show error message
28
+ });
29
+
30
+ // 3. Enable submit when all fields valid
31
+ store.subscribe('emailValid', updateSubmitButton);
32
+ store.subscribe('passwordValid', updateSubmitButton);
33
+ ```
34
+
35
+ ## Key Insight
36
+
37
+ **Validation is just derived state.**
38
+
39
+ - Input values are **source state**
40
+ - Validation flags are **derived state**
41
+ - Submit button state is **computed from derived state**
42
+
43
+ Other frameworks need:
44
+ - React: `useForm` hooks, validation libraries
45
+ - Vue: `v-model` + validation plugins
46
+ - Svelte: Stores + validation actions
47
+
48
+ **EveryState:** Just subscribe to inputs, validate, and update state. That's it.
49
+
50
+ ## Run It
51
+
52
+ Open `index.html` in a browser. Type invalid data to see error messages. Submit button enables when both fields are valid.
@@ -0,0 +1,108 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>007 Form Validation - EveryState</title>
7
+ <script type="importmap">
8
+ { "imports": {
9
+ "@everystate/core": "../../../everystate-core/index.js",
10
+ "@everystate/perf": "../../../everystate-perf/index.js"
11
+ }}
12
+ </script>
13
+ </head>
14
+ <body>
15
+ <h1>Sign Up Form</h1>
16
+
17
+ <label>Email: <input type="email" id="email" placeholder="you@example.com"></label>
18
+ <p id="emailError" style="color: red;"></p>
19
+
20
+ <label>Password: <input type="password" id="password" placeholder="Min 8 characters"></label>
21
+ <p id="passwordError" style="color: red;"></p>
22
+
23
+ <button id="submitBtn" disabled>Submit</button>
24
+ <p id="submitMessage" style="color: green;"></p>
25
+ <hr>
26
+ <button id="runTests">Run Tests</button>
27
+ <pre id="testLog"></pre>
28
+
29
+ <script type="module">
30
+ import { createEveryState } from '@everystate/core';
31
+ import { createPerfMonitor, mountOverlay } from '@everystate/perf';
32
+
33
+ const store = createEveryState({ email: '', password: '', emailValid: false, passwordValid: false });
34
+ const perf = createPerfMonitor(store);
35
+ mountOverlay(perf, document.body);
36
+
37
+ const validateEmail = (email) => email.includes('@') && email.includes('.');
38
+ const validatePassword = (password) => password.length >= 8;
39
+
40
+ const updateSubmitButton = () => {
41
+ document.getElementById('submitBtn').disabled = !(store.get('emailValid') && store.get('passwordValid'));
42
+ };
43
+
44
+ store.subscribe('email', (value) => {
45
+ const isValid = validateEmail(value);
46
+ store.set('emailValid', isValid);
47
+ document.getElementById('emailError').textContent = value && !isValid ? 'Invalid email address' : '';
48
+ });
49
+
50
+ store.subscribe('password', (value) => {
51
+ const isValid = validatePassword(value);
52
+ store.set('passwordValid', isValid);
53
+ document.getElementById('passwordError').textContent = value && !isValid ? 'Password must be at least 8 characters' : '';
54
+ });
55
+
56
+ store.subscribe('emailValid', updateSubmitButton);
57
+ store.subscribe('passwordValid', updateSubmitButton);
58
+
59
+ document.getElementById('email').oninput = (e) => { store.set('email', e.target.value); };
60
+ document.getElementById('password').oninput = (e) => { store.set('password', e.target.value); };
61
+
62
+ document.getElementById('submitBtn').onclick = () => {
63
+ document.getElementById('submitMessage').textContent = `Form submitted for ${store.get('email')}`;
64
+ document.getElementById('email').value = '';
65
+ document.getElementById('password').value = '';
66
+ store.set('email', '');
67
+ store.set('password', '');
68
+ };
69
+
70
+ // Event Sequence Tests
71
+ document.getElementById('runTests').onclick = () => {
72
+ const results = [];
73
+ let pass = 0, fail = 0;
74
+ function assert(label, cond) {
75
+ if (cond) { pass++; results.push(' OK ' + label); }
76
+ else { fail++; results.push(' FAIL ' + label); }
77
+ }
78
+
79
+ store.set('email', 'bad');
80
+ assert('invalid email sets emailValid=false', store.get('emailValid') === false);
81
+
82
+ store.set('email', 'a@b.c');
83
+ assert('valid email sets emailValid=true', store.get('emailValid') === true);
84
+
85
+ store.set('password', 'short');
86
+ assert('short password invalid', store.get('passwordValid') === false);
87
+ assert('submit disabled when invalid', document.getElementById('submitBtn').disabled === true);
88
+
89
+ store.set('password', 'longpassword');
90
+ assert('long password valid', store.get('passwordValid') === true);
91
+ assert('submit enabled when both valid', document.getElementById('submitBtn').disabled === false);
92
+
93
+ // Cascading: email fires emailValid fires updateSubmitButton
94
+ const seq = [];
95
+ const u1 = store.subscribe('emailValid', () => seq.push('emailValid'));
96
+ store.set('email', 'x@y.z');
97
+ assert('cascading: email -> emailValid', seq.includes('emailValid'));
98
+ u1();
99
+
100
+ assert('perf tracked', perf.report().summary.totalSets > 0);
101
+
102
+ store.set('email', '');
103
+ store.set('password', '');
104
+ document.getElementById('testLog').textContent = results.join('\n') + `\n\n${pass} passed, ${fail} failed`;
105
+ };
106
+ </script>
107
+ </body>
108
+ </html>
@@ -0,0 +1,70 @@
1
+ # 008 Undo/Redo - Time Travel Debugging
2
+
3
+ Demonstrates how EveryState's event-driven architecture makes undo/redo trivial.
4
+
5
+ ## What's Here
6
+
7
+ - Counter with multiple operations
8
+ - Undo/Redo buttons
9
+ - History tracking showing current position
10
+ - **No special library needed** - just an array
11
+
12
+ ## How It Works
13
+
14
+ ```javascript
15
+ // 1. Track history in an array
16
+ let history = [0];
17
+ let historyIndex = 0;
18
+
19
+ // 2. Record state changes via subscription
20
+ store.subscribe('count', (value) => {
21
+ if (!isTimeTravel) {
22
+ history.push(value);
23
+ historyIndex = history.length - 1;
24
+ }
25
+ });
26
+
27
+ // 3. Undo = go back in history
28
+ document.getElementById('undo').onclick = () => {
29
+ historyIndex--;
30
+ isTimeTravel = true;
31
+ store.set('count', history[historyIndex]);
32
+ isTimeTravel = false;
33
+ };
34
+ ```
35
+
36
+ ## Key Insight
37
+
38
+ **Time travel is just replaying state.**
39
+
40
+ Because EveryState uses **events** for all state changes, you can:
41
+ - ✅ Record every change automatically (wildcard subscription)
42
+ - ✅ Replay any previous state
43
+ - ✅ Build undo/redo in ~30 lines
44
+
45
+ Other frameworks need:
46
+ - React: Redux DevTools, Immer, or custom middleware
47
+ - Vue: Vuex plugins or manual history tracking
48
+ - Svelte: Custom stores with history logic
49
+
50
+ **EveryState:** Just subscribe to `'*'` and push to an array. That's it.
51
+
52
+ ## This is Telemetry
53
+
54
+ This example shows the **foundation of telemetry**:
55
+ - Every state change is an event
56
+ - Events can be logged, replayed, or analyzed
57
+ - Time-travel debugging comes for free
58
+
59
+ In the full EveryState framework, this becomes:
60
+ - Automatic telemetry logging
61
+ - Event sequence testing (`eventTest.js`)
62
+ - Production debugging and replay
63
+
64
+ ## Run It
65
+
66
+ Open `index.html` in a browser. Click operations, then use Undo/Redo to travel through time.
67
+
68
+ ## Try This
69
+
70
+ Make multiple changes, undo halfway, then make a new change. Notice how the "future" states are discarded (like Git branches).
@@ -0,0 +1,133 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>008 Undo/Redo - EveryState</title>
7
+ <script type="importmap">
8
+ { "imports": {
9
+ "@everystate/core": "../../../everystate-core/index.js",
10
+ "@everystate/perf": "../../../everystate-perf/index.js"
11
+ }}
12
+ </script>
13
+ </head>
14
+ <body>
15
+ <h1>Counter with Time Travel</h1>
16
+
17
+ <h2>Count: <span id="count">0</span></h2>
18
+
19
+ <button id="increment">+1</button>
20
+ <button id="decrement">-1</button>
21
+ <button id="double">x2</button>
22
+
23
+ <hr>
24
+
25
+ <button id="undo" disabled>Undo</button>
26
+ <button id="redo" disabled>Redo</button>
27
+
28
+ <p><small>History: <span id="historyInfo">0 states</span></small></p>
29
+ <hr>
30
+ <button id="runTests">Run Tests</button>
31
+ <pre id="testLog"></pre>
32
+
33
+ <script type="module">
34
+ import { createEveryState } from '@everystate/core';
35
+ import { createPerfMonitor, mountOverlay } from '@everystate/perf';
36
+
37
+ const store = createEveryState({ count: 0 });
38
+ const perf = createPerfMonitor(store);
39
+ mountOverlay(perf, document.body);
40
+
41
+ let history = [0];
42
+ let historyIndex = 0;
43
+ let isTimeTravel = false;
44
+
45
+ const updateUI = () => {
46
+ document.getElementById('count').textContent = store.get('count');
47
+ document.getElementById('undo').disabled = historyIndex === 0;
48
+ document.getElementById('redo').disabled = historyIndex === history.length - 1;
49
+ document.getElementById('historyInfo').textContent = `${historyIndex + 1} of ${history.length} states`;
50
+ };
51
+
52
+ store.subscribe('count', (value) => {
53
+ updateUI();
54
+ if (!isTimeTravel) {
55
+ history = history.slice(0, historyIndex + 1);
56
+ history.push(value);
57
+ historyIndex = history.length - 1;
58
+ updateUI();
59
+ }
60
+ });
61
+
62
+ document.getElementById('increment').onclick = () => { store.set('count', store.get('count') + 1); };
63
+ document.getElementById('decrement').onclick = () => { store.set('count', store.get('count') - 1); };
64
+ document.getElementById('double').onclick = () => { store.set('count', store.get('count') * 2); };
65
+
66
+ document.getElementById('undo').onclick = () => {
67
+ if (historyIndex > 0) {
68
+ historyIndex--;
69
+ isTimeTravel = true;
70
+ store.set('count', history[historyIndex]);
71
+ isTimeTravel = false;
72
+ updateUI();
73
+ }
74
+ };
75
+
76
+ document.getElementById('redo').onclick = () => {
77
+ if (historyIndex < history.length - 1) {
78
+ historyIndex++;
79
+ isTimeTravel = true;
80
+ store.set('count', history[historyIndex]);
81
+ isTimeTravel = false;
82
+ updateUI();
83
+ }
84
+ };
85
+
86
+ updateUI();
87
+
88
+ // Event Sequence Tests
89
+ document.getElementById('runTests').onclick = () => {
90
+ const results = [];
91
+ let pass = 0, fail = 0;
92
+ function assert(label, cond) {
93
+ if (cond) { pass++; results.push(' OK ' + label); }
94
+ else { fail++; results.push(' FAIL ' + label); }
95
+ }
96
+
97
+ // Reset
98
+ history = [0]; historyIndex = 0; isTimeTravel = false;
99
+ store.set('count', 0);
100
+ // clear history added by the set above
101
+ history = [0]; historyIndex = 0;
102
+
103
+ store.set('count', 1);
104
+ store.set('count', 2);
105
+ store.set('count', 3);
106
+ assert('history has 4 entries', history.length === 4);
107
+ assert('current is 3', store.get('count') === 3);
108
+
109
+ // Undo
110
+ historyIndex--;
111
+ isTimeTravel = true;
112
+ store.set('count', history[historyIndex]);
113
+ isTimeTravel = false;
114
+ assert('undo to 2', store.get('count') === 2);
115
+
116
+ // Redo
117
+ historyIndex++;
118
+ isTimeTravel = true;
119
+ store.set('count', history[historyIndex]);
120
+ isTimeTravel = false;
121
+ assert('redo to 3', store.get('count') === 3);
122
+
123
+ assert('perf tracked', perf.report().summary.totalSets > 0);
124
+
125
+ history = [0]; historyIndex = 0;
126
+ store.set('count', 0);
127
+ history = [0]; historyIndex = 0;
128
+ updateUI();
129
+ document.getElementById('testLog').textContent = results.join('\n') + `\n\n${pass} passed, ${fail} failed`;
130
+ };
131
+ </script>
132
+ </body>
133
+ </html>
@@ -0,0 +1,72 @@
1
+ # 009 localStorage Side Effects - Persistent State
2
+
3
+ Demonstrates how to sync state with localStorage for persistence across page reloads.
4
+
5
+ ## What's Here
6
+
7
+ - Counter that persists across browser sessions
8
+ - Automatic save on every state change
9
+ - Load saved state on page load
10
+ - **No persistence library needed** - just localStorage API
11
+
12
+ ## How It Works
13
+
14
+ ```javascript
15
+ // 1. Load initial state from localStorage
16
+ const savedCount = localStorage.getItem('counter');
17
+ const initialCount = savedCount !== null ? parseInt(savedCount, 10) : 0;
18
+
19
+ // 2. Create store with saved state
20
+ const store = createEveryState({ count: initialCount });
21
+
22
+ // 3. Side effect: save on every change
23
+ store.subscribe('count', (value) => {
24
+ localStorage.setItem('counter', value);
25
+ // Also update UI
26
+ document.getElementById('count').textContent = value;
27
+ });
28
+ ```
29
+
30
+ ## Key Insight
31
+
32
+ **Side effects are just subscribers.**
33
+
34
+ Subscriptions aren't only for UI updates. They're for:
35
+ - ✅ Saving to localStorage
36
+ - ✅ Logging to analytics
37
+ - ✅ Syncing to server
38
+ - ✅ Triggering other actions
39
+
40
+ Other frameworks need:
41
+ - React: `useEffect` with dependency arrays
42
+ - Vue: `watch` or `watchEffect`
43
+ - Svelte: Reactive statements with side effects
44
+
45
+ **EveryState:** Just subscribe and do whatever you want. That's it.
46
+
47
+ ## Pattern: Separation of Concerns
48
+
49
+ Notice the subscriber does TWO things:
50
+ 1. Saves to localStorage (side effect)
51
+ 2. Updates the DOM (UI effect)
52
+
53
+ You could split these into separate subscribers:
54
+ ```javascript
55
+ store.subscribe('count', (value) => {
56
+ localStorage.setItem('counter', value);
57
+ });
58
+
59
+ store.subscribe('count', (value) => {
60
+ document.getElementById('count').textContent = value;
61
+ });
62
+ ```
63
+
64
+ This is the **single responsibility principle** in action.
65
+
66
+ ## Run It
67
+
68
+ Open `index.html`, increment the counter, then reload the page. Your count persists!
69
+
70
+ ## Try This
71
+
72
+ Open DevTools → Application → Local Storage to see the saved value update in real-time.
@@ -0,0 +1,80 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>009 localStorage Side Effects - EveryState</title>
7
+ <script type="importmap">
8
+ { "imports": {
9
+ "@everystate/core": "../../../everystate-core/index.js",
10
+ "@everystate/perf": "../../../everystate-perf/index.js"
11
+ }}
12
+ </script>
13
+ </head>
14
+ <body>
15
+ <h1>Persistent Counter</h1>
16
+
17
+ <h2>Count: <span id="count">0</span></h2>
18
+
19
+ <button id="increment">+1</button>
20
+ <button id="decrement">-1</button>
21
+ <button id="reset">Reset</button>
22
+
23
+ <p><small>Reload the page - your count persists!</small></p>
24
+ <hr>
25
+ <button id="runTests">Run Tests</button>
26
+ <pre id="testLog"></pre>
27
+
28
+ <script type="module">
29
+ import { createEveryState } from '@everystate/core';
30
+ import { createPerfMonitor, mountOverlay } from '@everystate/perf';
31
+
32
+ const STORAGE_KEY = 'everystate-counter';
33
+
34
+ const savedCount = localStorage.getItem(STORAGE_KEY);
35
+ const initialCount = savedCount !== null ? parseInt(savedCount, 10) : 0;
36
+
37
+ const store = createEveryState({ count: initialCount });
38
+ const perf = createPerfMonitor(store);
39
+ mountOverlay(perf, document.body);
40
+
41
+ store.subscribe('count', (value) => {
42
+ localStorage.setItem(STORAGE_KEY, value);
43
+ document.getElementById('count').textContent = value;
44
+ });
45
+
46
+ document.getElementById('increment').onclick = () => { store.set('count', store.get('count') + 1); };
47
+ document.getElementById('decrement').onclick = () => { store.set('count', store.get('count') - 1); };
48
+ document.getElementById('reset').onclick = () => { store.set('count', 0); };
49
+
50
+ document.getElementById('count').textContent = initialCount;
51
+
52
+ // Event Sequence Tests
53
+ document.getElementById('runTests').onclick = () => {
54
+ const results = [];
55
+ let pass = 0, fail = 0;
56
+ function assert(label, cond) {
57
+ if (cond) { pass++; results.push(' OK ' + label); }
58
+ else { fail++; results.push(' FAIL ' + label); }
59
+ }
60
+
61
+ store.set('count', 42);
62
+ assert('localStorage synced', localStorage.getItem(STORAGE_KEY) === '42');
63
+
64
+ store.set('count', 0);
65
+ assert('reset syncs localStorage', localStorage.getItem(STORAGE_KEY) === '0');
66
+
67
+ let fired = false;
68
+ const unsub = store.subscribe('count', () => { fired = true; });
69
+ store.set('count', 1);
70
+ assert('subscriber fires', fired);
71
+ unsub();
72
+
73
+ assert('perf tracked', perf.report().summary.totalSets > 0);
74
+
75
+ store.set('count', 0);
76
+ document.getElementById('testLog').textContent = results.join('\n') + `\n\n${pass} passed, ${fail} failed`;
77
+ };
78
+ </script>
79
+ </body>
80
+ </html>
@@ -0,0 +1,74 @@
1
+ # 010 Decoupled Components - Shared State
2
+
3
+ Demonstrates how components can communicate through shared state without direct coupling.
4
+
5
+ ## What's Here
6
+
7
+ - **Component A (Writer):** Sends messages
8
+ - **Component B (Reader):** Displays current message
9
+ - **Component C (Logger):** Logs all messages with timestamps
10
+ - **Zero coupling:** Components don't know about each other
11
+
12
+ ## How It Works
13
+
14
+ ```javascript
15
+ // Shared store - the ONLY connection
16
+ const store = createEveryState({ message: '' });
17
+
18
+ // Component A: Writes to state
19
+ document.getElementById('sendBtn').onclick = () => {
20
+ store.set('message', inputValue);
21
+ };
22
+
23
+ // Component B: Reads from state
24
+ store.subscribe('message', (value) => {
25
+ document.getElementById('display').textContent = value;
26
+ });
27
+
28
+ // Component C: Also reads from state
29
+ store.subscribe('message', (value) => {
30
+ logMessage(value);
31
+ });
32
+ ```
33
+
34
+ ## Key Insight
35
+
36
+ **State is the interface between components.**
37
+
38
+ Components communicate by:
39
+ 1. Writing to shared state
40
+ 2. Subscribing to shared state
41
+ 3. **Never** calling each other directly
42
+
43
+ This is the **pub/sub pattern** in action:
44
+ - ✅ Components are decoupled
45
+ - ✅ Easy to add/remove components
46
+ - ✅ No prop drilling
47
+ - ✅ No callbacks passed down
48
+
49
+ Other frameworks need:
50
+ - React: Context API, prop drilling, or state management libraries
51
+ - Vue: Provide/inject or Vuex
52
+ - Svelte: Context API or stores
53
+
54
+ **EveryState:** Just share the store. That's it.
55
+
56
+ ## Architecture Pattern
57
+
58
+ This demonstrates the **mediator pattern**:
59
+ - Components don't talk to each other
60
+ - They talk to the store
61
+ - The store mediates all communication
62
+
63
+ This is how the full EveryState framework works:
64
+ - Intents → State changes
65
+ - State changes → UI updates
66
+ - Everything is decoupled through events
67
+
68
+ ## Run It
69
+
70
+ Open `index.html`, type messages, and watch all three components react independently.
71
+
72
+ ## Try This
73
+
74
+ Add a fourth component that counts vowels in messages. It only needs to subscribe to `'message'` - no other code changes needed.