@uistate/examples 1.0.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.
Files changed (137) hide show
  1. package/README.md +40 -0
  2. package/cssState/.gitkeep +0 -0
  3. package/eventState/001-counter/README.md +44 -0
  4. package/eventState/001-counter/index.html +33 -0
  5. package/eventState/002-counter-improved/README.md +44 -0
  6. package/eventState/002-counter-improved/index.html +47 -0
  7. package/eventState/003-input-reactive/README.md +44 -0
  8. package/eventState/003-input-reactive/index.html +33 -0
  9. package/eventState/004-computed-state/README.md +45 -0
  10. package/eventState/004-computed-state/index.html +65 -0
  11. package/eventState/005-conditional-rendering/README.md +42 -0
  12. package/eventState/005-conditional-rendering/index.html +39 -0
  13. package/eventState/006-list-rendering/README.md +49 -0
  14. package/eventState/006-list-rendering/index.html +63 -0
  15. package/eventState/007-form-validation/README.md +52 -0
  16. package/eventState/007-form-validation/index.html +102 -0
  17. package/eventState/008-undo-redo/README.md +70 -0
  18. package/eventState/008-undo-redo/index.html +108 -0
  19. package/eventState/009-localStorage-side-effects/README.md +72 -0
  20. package/eventState/009-localStorage-side-effects/index.html +57 -0
  21. package/eventState/010-decoupled-components/README.md +74 -0
  22. package/eventState/010-decoupled-components/index.html +93 -0
  23. package/eventState/011-async-patterns/README.md +98 -0
  24. package/eventState/011-async-patterns/index.html +132 -0
  25. package/eventState/028-counter-improved-eventTest/LICENSE +55 -0
  26. package/eventState/028-counter-improved-eventTest/README.md +131 -0
  27. package/eventState/028-counter-improved-eventTest/app/store.js +9 -0
  28. package/eventState/028-counter-improved-eventTest/index.html +49 -0
  29. package/eventState/028-counter-improved-eventTest/runtime/core/behaviors.runtime.js +282 -0
  30. package/eventState/028-counter-improved-eventTest/runtime/core/eventState.js +100 -0
  31. package/eventState/028-counter-improved-eventTest/runtime/core/eventStateNew.js +149 -0
  32. package/eventState/028-counter-improved-eventTest/runtime/core/helpers.js +212 -0
  33. package/eventState/028-counter-improved-eventTest/runtime/core/router.js +271 -0
  34. package/eventState/028-counter-improved-eventTest/store.d.ts +8 -0
  35. package/eventState/028-counter-improved-eventTest/style.css +170 -0
  36. package/eventState/028-counter-improved-eventTest/tests/README.md +208 -0
  37. package/eventState/028-counter-improved-eventTest/tests/counter.test.js +116 -0
  38. package/eventState/028-counter-improved-eventTest/tests/eventTest.js +176 -0
  39. package/eventState/028-counter-improved-eventTest/tests/generateTypes.js +168 -0
  40. package/eventState/028-counter-improved-eventTest/tests/run.js +20 -0
  41. package/eventState/030-todo-app-with-eventTest/LICENSE +55 -0
  42. package/eventState/030-todo-app-with-eventTest/README.md +121 -0
  43. package/eventState/030-todo-app-with-eventTest/app/router.js +25 -0
  44. package/eventState/030-todo-app-with-eventTest/app/store.js +16 -0
  45. package/eventState/030-todo-app-with-eventTest/app/views/home.js +11 -0
  46. package/eventState/030-todo-app-with-eventTest/app/views/todoDemo.js +88 -0
  47. package/eventState/030-todo-app-with-eventTest/index.html +65 -0
  48. package/eventState/030-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +282 -0
  49. package/eventState/030-todo-app-with-eventTest/runtime/core/eventState.js +100 -0
  50. package/eventState/030-todo-app-with-eventTest/runtime/core/eventStateNew.js +149 -0
  51. package/eventState/030-todo-app-with-eventTest/runtime/core/helpers.js +212 -0
  52. package/eventState/030-todo-app-with-eventTest/runtime/core/router.js +271 -0
  53. package/eventState/030-todo-app-with-eventTest/store.d.ts +18 -0
  54. package/eventState/030-todo-app-with-eventTest/style.css +170 -0
  55. package/eventState/030-todo-app-with-eventTest/tests/README.md +208 -0
  56. package/eventState/030-todo-app-with-eventTest/tests/eventTest.js +176 -0
  57. package/eventState/030-todo-app-with-eventTest/tests/generateTypes.js +189 -0
  58. package/eventState/030-todo-app-with-eventTest/tests/run.js +20 -0
  59. package/eventState/030-todo-app-with-eventTest/tests/todos.test.js +167 -0
  60. package/eventState/031-todo-app-with-eventTest/LICENSE +55 -0
  61. package/eventState/031-todo-app-with-eventTest/README.md +54 -0
  62. package/eventState/031-todo-app-with-eventTest/TUTORIAL.md +390 -0
  63. package/eventState/031-todo-app-with-eventTest/WHY_EVENTSTATE.md +777 -0
  64. package/eventState/031-todo-app-with-eventTest/app/bridges.js +113 -0
  65. package/eventState/031-todo-app-with-eventTest/app/router.js +26 -0
  66. package/eventState/031-todo-app-with-eventTest/app/store.js +15 -0
  67. package/eventState/031-todo-app-with-eventTest/app/views/home.js +46 -0
  68. package/eventState/031-todo-app-with-eventTest/app/views/todoDemo.js +69 -0
  69. package/eventState/031-todo-app-with-eventTest/devtools/dock.js +41 -0
  70. package/eventState/031-todo-app-with-eventTest/devtools/stateTracker.dock.js +10 -0
  71. package/eventState/031-todo-app-with-eventTest/devtools/stateTracker.js +246 -0
  72. package/eventState/031-todo-app-with-eventTest/devtools/telemetry.js +104 -0
  73. package/eventState/031-todo-app-with-eventTest/devtools/typeGenerator.js +339 -0
  74. package/eventState/031-todo-app-with-eventTest/index.html +103 -0
  75. package/eventState/031-todo-app-with-eventTest/package-lock.json +2184 -0
  76. package/eventState/031-todo-app-with-eventTest/package.json +24 -0
  77. package/eventState/031-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +282 -0
  78. package/eventState/031-todo-app-with-eventTest/runtime/core/eventState.js +100 -0
  79. package/eventState/031-todo-app-with-eventTest/runtime/core/eventStateNew.js +149 -0
  80. package/eventState/031-todo-app-with-eventTest/runtime/core/helpers.js +212 -0
  81. package/eventState/031-todo-app-with-eventTest/runtime/core/router.js +271 -0
  82. package/eventState/031-todo-app-with-eventTest/runtime/extensions/boundary.js +36 -0
  83. package/eventState/031-todo-app-with-eventTest/runtime/extensions/converge.js +63 -0
  84. package/eventState/031-todo-app-with-eventTest/runtime/extensions/eventState.plus.js +210 -0
  85. package/eventState/031-todo-app-with-eventTest/runtime/extensions/hydrate.js +157 -0
  86. package/eventState/031-todo-app-with-eventTest/runtime/extensions/queryBinding.js +69 -0
  87. package/eventState/031-todo-app-with-eventTest/runtime/forms/computed.js +78 -0
  88. package/eventState/031-todo-app-with-eventTest/runtime/forms/meta.js +51 -0
  89. package/eventState/031-todo-app-with-eventTest/runtime/forms/submitWithBoundary.js +28 -0
  90. package/eventState/031-todo-app-with-eventTest/runtime/forms/validators.js +55 -0
  91. package/eventState/031-todo-app-with-eventTest/store.d.ts +23 -0
  92. package/eventState/031-todo-app-with-eventTest/style.css +170 -0
  93. package/eventState/031-todo-app-with-eventTest/tests/README.md +208 -0
  94. package/eventState/031-todo-app-with-eventTest/tests/eventTest.js +176 -0
  95. package/eventState/031-todo-app-with-eventTest/tests/generateTypes.js +191 -0
  96. package/eventState/031-todo-app-with-eventTest/tests/run.js +20 -0
  97. package/eventState/031-todo-app-with-eventTest/tests/todos.test.js +192 -0
  98. package/eventState/032-todo-app-with-eventTest/LICENSE +55 -0
  99. package/eventState/032-todo-app-with-eventTest/README.md +54 -0
  100. package/eventState/032-todo-app-with-eventTest/TUTORIAL.md +390 -0
  101. package/eventState/032-todo-app-with-eventTest/WHY_EVENTSTATE.md +777 -0
  102. package/eventState/032-todo-app-with-eventTest/app/actions/index.js +153 -0
  103. package/eventState/032-todo-app-with-eventTest/app/bridges.js +113 -0
  104. package/eventState/032-todo-app-with-eventTest/app/router.js +26 -0
  105. package/eventState/032-todo-app-with-eventTest/app/store.js +15 -0
  106. package/eventState/032-todo-app-with-eventTest/app/views/home.js +46 -0
  107. package/eventState/032-todo-app-with-eventTest/app/views/todoDemo.js +69 -0
  108. package/eventState/032-todo-app-with-eventTest/devtools/dock.js +41 -0
  109. package/eventState/032-todo-app-with-eventTest/devtools/stateTracker.dock.js +10 -0
  110. package/eventState/032-todo-app-with-eventTest/devtools/stateTracker.js +246 -0
  111. package/eventState/032-todo-app-with-eventTest/devtools/telemetry.js +104 -0
  112. package/eventState/032-todo-app-with-eventTest/devtools/typeGenerator.js +339 -0
  113. package/eventState/032-todo-app-with-eventTest/index.html +87 -0
  114. package/eventState/032-todo-app-with-eventTest/package-lock.json +2184 -0
  115. package/eventState/032-todo-app-with-eventTest/package.json +24 -0
  116. package/eventState/032-todo-app-with-eventTest/runtime/core/behaviors.runtime.js +282 -0
  117. package/eventState/032-todo-app-with-eventTest/runtime/core/eventState.js +100 -0
  118. package/eventState/032-todo-app-with-eventTest/runtime/core/eventStateNew.js +149 -0
  119. package/eventState/032-todo-app-with-eventTest/runtime/core/helpers.js +212 -0
  120. package/eventState/032-todo-app-with-eventTest/runtime/core/router.js +271 -0
  121. package/eventState/032-todo-app-with-eventTest/runtime/extensions/boundary.js +36 -0
  122. package/eventState/032-todo-app-with-eventTest/runtime/extensions/converge.js +63 -0
  123. package/eventState/032-todo-app-with-eventTest/runtime/extensions/eventState.plus.js +210 -0
  124. package/eventState/032-todo-app-with-eventTest/runtime/extensions/hydrate.js +157 -0
  125. package/eventState/032-todo-app-with-eventTest/runtime/extensions/queryBinding.js +69 -0
  126. package/eventState/032-todo-app-with-eventTest/runtime/forms/computed.js +78 -0
  127. package/eventState/032-todo-app-with-eventTest/runtime/forms/meta.js +51 -0
  128. package/eventState/032-todo-app-with-eventTest/runtime/forms/submitWithBoundary.js +28 -0
  129. package/eventState/032-todo-app-with-eventTest/runtime/forms/validators.js +55 -0
  130. package/eventState/032-todo-app-with-eventTest/store.d.ts +23 -0
  131. package/eventState/032-todo-app-with-eventTest/style.css +170 -0
  132. package/eventState/032-todo-app-with-eventTest/tests/README.md +208 -0
  133. package/eventState/032-todo-app-with-eventTest/tests/eventTest.js +176 -0
  134. package/eventState/032-todo-app-with-eventTest/tests/generateTypes.js +191 -0
  135. package/eventState/032-todo-app-with-eventTest/tests/run.js +20 -0
  136. package/eventState/032-todo-app-with-eventTest/tests/todos.test.js +192 -0
  137. package/package.json +27 -0
@@ -0,0 +1,339 @@
1
+ // typeGenerator.js — Generate TypeScript definitions from telemetry
2
+ // Analyzes runtime telemetry to extract state shape and generate .d.ts files
3
+
4
+ import store from '../app/store.js';
5
+
6
+ class TypeGenerator {
7
+ constructor() {
8
+ this.pathTypes = new Map(); // path → type info
9
+ this.pathSamples = new Map(); // path → sample values
10
+ }
11
+
12
+ /**
13
+ * Observe a state change and infer its type
14
+ */
15
+ observe(path, value) {
16
+ const type = this.inferType(value);
17
+
18
+ // Store sample for complex types
19
+ if (typeof value === 'object' && value !== null) {
20
+ if (!this.pathSamples.has(path)) {
21
+ this.pathSamples.set(path, []);
22
+ }
23
+ const samples = this.pathSamples.get(path);
24
+ if (samples.length < 10) {
25
+ samples.push(value);
26
+ }
27
+ }
28
+
29
+ // Merge with existing type info
30
+ if (this.pathTypes.has(path)) {
31
+ const existing = this.pathTypes.get(path);
32
+ this.pathTypes.set(path, this.mergeTypes(existing, type));
33
+ } else {
34
+ this.pathTypes.set(path, type);
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Infer TypeScript type from a value
40
+ */
41
+ inferType(value) {
42
+ if (value === null) return 'null';
43
+ if (value === undefined) return 'undefined';
44
+
45
+ if (Array.isArray(value)) {
46
+ if (value.length === 0) return 'Array<unknown>';
47
+
48
+ // Sample first few elements and merge their types
49
+ const samples = value.slice(0, 10);
50
+ let mergedElementType = null;
51
+
52
+ for (const sample of samples) {
53
+ const elementType = this.inferType(sample);
54
+ if (mergedElementType === null) {
55
+ mergedElementType = elementType;
56
+ } else {
57
+ mergedElementType = this.mergeTypes(mergedElementType, elementType);
58
+ }
59
+ }
60
+
61
+ return `Array<${this.typeToString(mergedElementType)}>`;
62
+ }
63
+
64
+ if (typeof value === 'object') {
65
+ // Infer object shape
66
+ const shape = {};
67
+ for (const [key, val] of Object.entries(value)) {
68
+ shape[key] = this.inferType(val);
69
+ }
70
+ return shape;
71
+ }
72
+
73
+ // Primitives
74
+ if (typeof value === 'string') return 'string';
75
+ if (typeof value === 'number') return 'number';
76
+ if (typeof value === 'boolean') return 'boolean';
77
+
78
+ return 'unknown';
79
+ }
80
+
81
+ /**
82
+ * Convert type representation to string
83
+ */
84
+ typeToString(type) {
85
+ if (typeof type === 'string') return type;
86
+
87
+ if (typeof type === 'object' && !Array.isArray(type)) {
88
+ const props = Object.entries(type)
89
+ .map(([k, v]) => `${k}: ${this.typeToString(v)}`)
90
+ .join('; ');
91
+ return `{ ${props} }`;
92
+ }
93
+
94
+ return 'unknown';
95
+ }
96
+
97
+ /**
98
+ * Merge two type definitions (for union types)
99
+ */
100
+ mergeTypes(type1, type2) {
101
+ // Normalize for comparison
102
+ const str1 = this.normalizeType(type1);
103
+ const str2 = this.normalizeType(type2);
104
+
105
+ if (str1 === str2) return type1;
106
+
107
+ // Handle string types
108
+ if (typeof type1 === 'string' && typeof type2 === 'string') {
109
+ if (type1 === type2) return type1;
110
+ // Avoid duplicate unions
111
+ if (type1.includes(type2)) return type1;
112
+ if (type2.includes(type1)) return type2;
113
+ return `${type1} | ${type2}`;
114
+ }
115
+
116
+ // Handle object merging (merge properties, not create union)
117
+ if (typeof type1 === 'object' && typeof type2 === 'object' &&
118
+ !Array.isArray(type1) && !Array.isArray(type2)) {
119
+ const merged = { ...type1 };
120
+ for (const [key, val] of Object.entries(type2)) {
121
+ if (key in merged) {
122
+ merged[key] = this.mergeTypes(merged[key], val);
123
+ } else {
124
+ merged[key] = val;
125
+ }
126
+ }
127
+ return merged;
128
+ }
129
+
130
+ // Mixed types: create union
131
+ if (typeof type1 === 'string' && typeof type2 === 'object') {
132
+ return `${type1} | ${this.typeToString(type2)}`;
133
+ }
134
+ if (typeof type1 === 'object' && typeof type2 === 'string') {
135
+ return `${this.typeToString(type1)} | ${type2}`;
136
+ }
137
+
138
+ return type1; // Default: keep first type
139
+ }
140
+
141
+ /**
142
+ * Normalize type for comparison
143
+ */
144
+ normalizeType(type) {
145
+ if (typeof type === 'string') return type;
146
+ return JSON.stringify(type, Object.keys(type).sort());
147
+ }
148
+
149
+ /**
150
+ * Build hierarchical tree from flat paths
151
+ */
152
+ buildTree() {
153
+ const tree = {};
154
+
155
+ for (const [path, type] of this.pathTypes) {
156
+ const parts = path.split('.');
157
+ let current = tree;
158
+
159
+ for (let i = 0; i < parts.length; i++) {
160
+ const part = parts[i];
161
+
162
+ if (i === parts.length - 1) {
163
+ // Leaf node
164
+ current[part] = { __type: type };
165
+ } else {
166
+ // Branch node
167
+ if (!current[part]) {
168
+ current[part] = {};
169
+ }
170
+ current = current[part];
171
+ }
172
+ }
173
+ }
174
+
175
+ return tree;
176
+ }
177
+
178
+ /**
179
+ * Generate TypeScript interface from type info
180
+ */
181
+ generateInterface(obj, indent = 0) {
182
+ const lines = [];
183
+ const spaces = ' '.repeat(indent);
184
+
185
+ for (const [key, value] of Object.entries(obj)) {
186
+ if (value.__type !== undefined) {
187
+ // Leaf node with type
188
+ const type = this.formatType(value.__type);
189
+ lines.push(`${spaces}${key}: ${type};`);
190
+ } else {
191
+ // Nested object
192
+ lines.push(`${spaces}${key}: {`);
193
+ lines.push(this.generateInterface(value, indent + 1));
194
+ lines.push(`${spaces}};`);
195
+ }
196
+ }
197
+
198
+ return lines.join('\n');
199
+ }
200
+
201
+ /**
202
+ * Format type for TypeScript output
203
+ */
204
+ formatType(type) {
205
+ if (typeof type === 'string') {
206
+ return type;
207
+ }
208
+
209
+ if (typeof type === 'object' && !Array.isArray(type)) {
210
+ // Inline object type
211
+ const props = Object.entries(type)
212
+ .map(([k, v]) => `${k}: ${this.formatType(v)}`)
213
+ .join('; ');
214
+ return `{ ${props} }`;
215
+ }
216
+
217
+ return 'unknown';
218
+ }
219
+
220
+ /**
221
+ * Generate complete .d.ts file
222
+ */
223
+ generateDTS() {
224
+ const lines = [
225
+ '// Auto-generated from runtime telemetry',
226
+ '// DO NOT EDIT - regenerate by using the app and clicking "Generate Types"',
227
+ '',
228
+ 'export interface StoreState {',
229
+ ];
230
+
231
+ const tree = this.buildTree();
232
+ lines.push(this.generateInterface(tree, 1));
233
+ lines.push('}');
234
+ lines.push('');
235
+ lines.push('export default StoreState;');
236
+ lines.push('');
237
+
238
+ return lines.join('\n');
239
+ }
240
+
241
+ /**
242
+ * Parse telemetry buffer and extract types
243
+ */
244
+ parseBuffer(buffer) {
245
+ for (const entry of buffer) {
246
+ if (entry.level !== 'log') continue;
247
+
248
+ const args = entry.args;
249
+ if (!args || args.length < 2) continue;
250
+
251
+ const tag = args[0];
252
+
253
+ // Parse [state] entries
254
+ if (typeof tag === 'string' && tag.startsWith('[state]')) {
255
+ const path = tag.replace('[state] ', '');
256
+ const value = args[1];
257
+ this.observe(path, value);
258
+ }
259
+
260
+ // Parse [intent] entries
261
+ if (typeof tag === 'string' && tag.startsWith('[intent]')) {
262
+ const intentName = tag.replace('[intent] ', '');
263
+ const path = `intent.${intentName}`;
264
+ const value = args[1];
265
+ this.observe(path, value);
266
+ }
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Parse test assertions and extract types
272
+ */
273
+ parseTestAssertions(assertions) {
274
+ for (const assertion of assertions) {
275
+ const { path, type, elementShape, shape } = assertion;
276
+
277
+ if (type === 'array' && elementShape) {
278
+ // Array type with element shape
279
+ this.pathTypes.set(path, `Array<${this.typeToString(elementShape)}>`);
280
+ } else if (type === 'object' && shape) {
281
+ // Object type with shape
282
+ this.pathTypes.set(path, shape);
283
+ } else if (type) {
284
+ // Primitive type
285
+ this.pathTypes.set(path, type);
286
+ }
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Save .d.ts file (browser download)
292
+ */
293
+ save(filename = 'store.d.ts') {
294
+ const dts = this.generateDTS();
295
+ const blob = new Blob([dts], { type: 'text/plain' });
296
+ const url = URL.createObjectURL(blob);
297
+ const a = document.createElement('a');
298
+ a.href = url;
299
+ a.download = filename;
300
+ a.click();
301
+ URL.revokeObjectURL(url);
302
+ }
303
+ }
304
+
305
+ // Create and expose type generator
306
+ const typeGenerator = new TypeGenerator();
307
+ window.__typeGenerator = typeGenerator;
308
+
309
+ // Subscribe to all state changes for live type inference
310
+ if (store && store.subscribe) {
311
+ store.subscribe('*', (detail) => {
312
+ const { path, value } = detail;
313
+ typeGenerator.observe(path, value);
314
+ });
315
+
316
+ console.info('[typeGenerator] Live type inference enabled');
317
+ }
318
+
319
+ // Register with dev dock
320
+ if (window.__devdock && typeof window.__devdock.register === 'function') {
321
+ window.__devdock.register({
322
+ id: 'generate-types',
323
+ label: 'Types',
324
+ title: 'Generate TypeScript definitions',
325
+ onClick: () => {
326
+ // Also parse telemetry buffer for historical data
327
+ if (window.__telemetry) {
328
+ const buffer = window.__telemetry.get();
329
+ typeGenerator.parseBuffer(buffer);
330
+ }
331
+
332
+ typeGenerator.save('store.d.ts');
333
+ console.info('[typeGenerator] Generated store.d.ts');
334
+ }
335
+ });
336
+ }
337
+
338
+ export default typeGenerator;
339
+ export { TypeGenerator };
@@ -0,0 +1,103 @@
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" />
6
+ <title>009 Todo App with eventTest</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ </head>
9
+ <body data-theme="dark" data-bind="data-theme: ui.theme">
10
+ <nav style="display:flex; gap:10px; align-items:center; padding:10px 12px;">
11
+ <a href="/" data-link>Home</a>
12
+ <a href="/todo-demo" data-link>Todo app demo</a>
13
+ <span class="loading-badge" aria-live="polite">Loading…</span>
14
+ <span style="flex:1"></span>
15
+ <button class="btn" title="Toggle theme" data-on="click: toggleTheme() | log('toggle theme') | logPath('ui.theme')">Theme</button>
16
+ </nav>
17
+ <div data-route-root></div>
18
+
19
+ <script type="module" src="./app/router.js"></script>
20
+ <!-- Expose the store globally so stateTracker (optional) can bind without tight coupling -->
21
+ <script type="module">
22
+ import store from './app/store.js';
23
+ window.stateTrackerStore = store;
24
+
25
+ // Bind loading badge to route transitioning state (after DOM ready)
26
+ requestAnimationFrame(() => {
27
+ const badge = document.querySelector('.loading-badge');
28
+ if (badge) {
29
+ store.subscribe('ui.route.transitioning', (isTransitioning) => {
30
+ badge.style.display = isTransitioning ? 'inline' : 'none';
31
+ });
32
+ // Set initial state (default hidden in CSS)
33
+ badge.style.display = store.get('ui.route.transitioning') ? 'inline' : 'none';
34
+ }
35
+ });
36
+ </script>
37
+ <!-- Install behaviors runtime with inline actions (no separate registry file) -->
38
+ <script type="module">
39
+ import store from './app/store.js';
40
+ import { installBehaviors } from './runtime/core/behaviors.runtime.js';
41
+
42
+ // Inline action registry - just the actions used in this example
43
+ const registry = {
44
+ toggleTheme(ctx) {
45
+ const cur = ctx.get('ui.theme');
46
+ const next = (cur === 'dark') ? 'light' : 'dark';
47
+ ctx.set('ui.theme', next);
48
+ console.log('[action] theme ->', next);
49
+ },
50
+ log(ctx, ...args) {
51
+ console.log('[action]', ...args);
52
+ },
53
+ logPath(ctx, path) {
54
+ console.log('[action]', String(path), ctx.get(String(path)));
55
+ }
56
+ };
57
+
58
+ window.behaviorsWhitelistDefault = ['ui.counter', 'ui.name', 'ui.items', 'ui.newItem', 'ui.itemsCount', 'ui.guard.whitelist', 'ui.theme', 'intent.**'];
59
+ window.behaviorsWhitelist = [...window.behaviorsWhitelistDefault];
60
+ const recUn = window.__recorder?.install?.(store);
61
+ window.behaviorsUninstall = installBehaviors(store, {
62
+ registry,
63
+ root: document,
64
+ writablePrefixes: ['ui.','intent.'],
65
+ writableWhitelist: window.behaviorsWhitelist,
66
+ debug: true,
67
+ onStep: (e) => {
68
+ try { window.__recorder?.onStep?.(e); } catch{}
69
+ // Log all behavior steps to telemetry
70
+ try {
71
+ if (e.phase === 'started') {
72
+ console.log(`[action] ${e.name}(${e.args?.join(', ') || ''})`, { event: e.event?.type, element: e.el?.tagName });
73
+ }
74
+ if (e.phase === 'applied' && e.write) {
75
+ console.log(`[write] ${e.write}`, store.get(e.write));
76
+ }
77
+ if (e.phase === 'blocked') {
78
+ console.warn(`[blocked] ${e.name} tried to write ${e.write}`, e.reason);
79
+ }
80
+ } catch{}
81
+ },
82
+ });
83
+ try { store.set('ui.guard.whitelist', [...window.behaviorsWhitelist]); } catch {}
84
+ try {
85
+ const cur = store.get('ui.theme');
86
+ if (!cur) {
87
+ const prefersDark = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
88
+ store.set('ui.theme', prefersDark ? 'dark' : 'light');
89
+ }
90
+ try { store.subscribe && store.subscribe('ui.theme', () => { try { localStorage.setItem('ui.theme', store.get('ui.theme')); } catch {} }); } catch {}
91
+ } catch {}
92
+ </script>
93
+ <script type="module" src="./devtools/stateTracker.js"></script>
94
+ <!-- Dev dock MUST load first so __devdock is available -->
95
+ <script type="module" src="./devtools/dock.js"></script>
96
+ <!-- Now load tools that register with the dock -->
97
+ <script type="module" src="./devtools/telemetry.js"></script>
98
+ <script type="module" src="./devtools/typeGenerator.js"></script>
99
+ <script type="module" src="./devtools/stateTracker.dock.js"></script>
100
+ <!-- App bridges: intent→domain and derived wildcards -->
101
+ <script type="module" src="./app/bridges.js"></script>
102
+ </body>
103
+ </html>