bitwrench 2.0.22 → 2.0.24

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 (88) hide show
  1. package/LICENSE.txt +1 -1
  2. package/README.md +4 -3
  3. package/bin/bwmcp.js +3 -0
  4. package/dist/bitwrench-bccl.cjs.js +1 -1
  5. package/dist/bitwrench-bccl.cjs.min.js +1 -1
  6. package/dist/bitwrench-bccl.cjs.min.js.gz +0 -0
  7. package/dist/bitwrench-bccl.esm.js +1 -1
  8. package/dist/bitwrench-bccl.esm.min.js +1 -1
  9. package/dist/bitwrench-bccl.esm.min.js.gz +0 -0
  10. package/dist/bitwrench-bccl.umd.js +1 -1
  11. package/dist/bitwrench-bccl.umd.min.js +1 -1
  12. package/dist/bitwrench-bccl.umd.min.js.gz +0 -0
  13. package/dist/bitwrench-code-edit.cjs.js +1 -1
  14. package/dist/bitwrench-code-edit.cjs.min.js +1 -1
  15. package/dist/bitwrench-code-edit.es5.js +1 -1
  16. package/dist/bitwrench-code-edit.es5.min.js +1 -1
  17. package/dist/bitwrench-code-edit.esm.js +1 -1
  18. package/dist/bitwrench-code-edit.esm.min.js +1 -1
  19. package/dist/bitwrench-code-edit.umd.js +1 -1
  20. package/dist/bitwrench-code-edit.umd.min.js +1 -1
  21. package/dist/bitwrench-code-edit.umd.min.js.gz +0 -0
  22. package/dist/bitwrench-debug.js +1 -1
  23. package/dist/bitwrench-debug.min.js +1 -1
  24. package/dist/bitwrench-lean.cjs.js +3 -3
  25. package/dist/bitwrench-lean.cjs.min.js +2 -2
  26. package/dist/bitwrench-lean.cjs.min.js.gz +0 -0
  27. package/dist/bitwrench-lean.es5.js +3 -3
  28. package/dist/bitwrench-lean.es5.min.js +2 -2
  29. package/dist/bitwrench-lean.es5.min.js.gz +0 -0
  30. package/dist/bitwrench-lean.esm.js +3 -3
  31. package/dist/bitwrench-lean.esm.min.js +2 -2
  32. package/dist/bitwrench-lean.esm.min.js.gz +0 -0
  33. package/dist/bitwrench-lean.umd.js +3 -3
  34. package/dist/bitwrench-lean.umd.min.js +2 -2
  35. package/dist/bitwrench-lean.umd.min.js.gz +0 -0
  36. package/dist/bitwrench-util-css.cjs.js +1 -1
  37. package/dist/bitwrench-util-css.cjs.min.js +1 -1
  38. package/dist/bitwrench-util-css.es5.js +1 -1
  39. package/dist/bitwrench-util-css.es5.min.js +1 -1
  40. package/dist/bitwrench-util-css.esm.js +1 -1
  41. package/dist/bitwrench-util-css.esm.min.js +1 -1
  42. package/dist/bitwrench-util-css.umd.js +1 -1
  43. package/dist/bitwrench-util-css.umd.min.js +1 -1
  44. package/dist/bitwrench-util-css.umd.min.js.gz +0 -0
  45. package/dist/bitwrench.cjs.js +3 -3
  46. package/dist/bitwrench.cjs.min.js +2 -2
  47. package/dist/bitwrench.cjs.min.js.gz +0 -0
  48. package/dist/bitwrench.css +1 -1
  49. package/dist/bitwrench.es5.js +3 -3
  50. package/dist/bitwrench.es5.min.js +2 -2
  51. package/dist/bitwrench.es5.min.js.gz +0 -0
  52. package/dist/bitwrench.esm.js +3 -3
  53. package/dist/bitwrench.esm.min.js +2 -2
  54. package/dist/bitwrench.esm.min.js.gz +0 -0
  55. package/dist/bitwrench.umd.js +3 -3
  56. package/dist/bitwrench.umd.min.js +2 -2
  57. package/dist/bitwrench.umd.min.js.gz +0 -0
  58. package/dist/builds.json +65 -65
  59. package/dist/bwserve.cjs.js +2 -2
  60. package/dist/bwserve.esm.js +2 -2
  61. package/dist/sri.json +45 -45
  62. package/docs/README.md +76 -0
  63. package/docs/app-patterns.md +264 -0
  64. package/docs/bitwrench-mcp.md +426 -0
  65. package/docs/bitwrench_api.md +2232 -0
  66. package/docs/bw-attach.md +399 -0
  67. package/docs/bwserve.md +841 -0
  68. package/docs/cli.md +307 -0
  69. package/docs/component-cheatsheet.md +144 -0
  70. package/docs/component-library.md +1099 -0
  71. package/docs/framework-translation-table.md +33 -0
  72. package/docs/llm-bitwrench-guide.md +672 -0
  73. package/docs/routing.md +562 -0
  74. package/docs/state-management.md +767 -0
  75. package/docs/taco-format.md +373 -0
  76. package/docs/theming.md +309 -0
  77. package/docs/thinking-in-bitwrench.md +1457 -0
  78. package/docs/tutorial-bwserve.md +297 -0
  79. package/docs/tutorial-embedded.md +314 -0
  80. package/docs/tutorial-website.md +255 -0
  81. package/package.json +11 -3
  82. package/readme.html +5 -4
  83. package/src/mcp/knowledge.js +231 -0
  84. package/src/mcp/live.js +226 -0
  85. package/src/mcp/server.js +216 -0
  86. package/src/mcp/tools.js +369 -0
  87. package/src/mcp/transport.js +55 -0
  88. package/src/version.js +3 -3
@@ -0,0 +1,767 @@
1
+ # State Management
2
+
3
+ Bitwrench has a three-level component model. Each level adds capability on top of the one below it. You choose the level that fits your use case — there is no single "right" way.
4
+
5
+ | Level | What you get | When to use it |
6
+ |-------|-------------|---------------|
7
+ | Level 0 -- TACO data | A plain JavaScript object describing UI | Static content, server rendering, serialization |
8
+ | Level 1 -- DOM rendering | A live DOM element with optional lifecycle hooks | Render-once UI, manual state management |
9
+ | Level 1.5 -- Component handles | `o.handle` and `o.slots` for imperative control of rendered elements | Update parts of a component without re-rendering |
10
+ | Level 2 -- Stateful TACO | A TACO with `o.state` + `o.render` and `bw.update()` for re-rendering | Interactive components with changing state |
11
+
12
+ This guide covers all three levels, from simplest to most capable.
13
+
14
+ > **Coming from React?** Level 0 is like calling `React.createElement()` to get a virtual element. Level 1 is like `ReactDOM.render()` with no state. Level 2 is like a class component with `this.state` and `this.setState()`, where you call `bw.update(el)` instead of `setState`.
15
+
16
+ > **Coming from Vue?** Level 0 is like a render function's return value. Level 1 is mounting with `createApp().mount()`. Level 2 is like a component with a `setup()` function that manages its own reactivity, where the render function re-runs on `bw.update()`.
17
+
18
+ > **Coming from Svelte?** Level 0 is like the compiled component descriptor. Level 1 is mounting with `new Component({ target })`. Level 2 is a live component with state variables, where `bw.update(el)` triggers the re-render.
19
+
20
+ ---
21
+
22
+ ## Level 0: TACO as Data
23
+
24
+ Every UI element in bitwrench starts as a plain object:
25
+
26
+ ```javascript
27
+ var greeting = { t: 'h1', c: 'Hello World' };
28
+ ```
29
+
30
+ This is a TACO object — **T**ag, **A**ttributes, **C**ontent, **O**ptions. It describes what you want, not how to render it. See [TACO Format](taco-format.md) for the full specification.
31
+
32
+ At Level 0, a TACO object is inert data. You can:
33
+
34
+ - Store it in a variable
35
+ - Put it in an array with other TACOs
36
+ - Pass it to a function
37
+ - Serialize it to JSON and send it over the network
38
+ - Generate it on a server and send it to a browser
39
+
40
+ ```javascript
41
+ // Compose with arrays and functions — standard JavaScript
42
+ var items = data.map(function(d) {
43
+ return { t: 'li', c: d.name };
44
+ });
45
+ var list = { t: 'ul', c: items };
46
+ ```
47
+
48
+ ### The `make*()` factories
49
+
50
+ The component library provides over 50 factory functions that return Level 0 TACO objects:
51
+
52
+ ```javascript
53
+ var card = bw.makeCard({ title: 'Users', content: '1,234 active' });
54
+ var btn = bw.makeButton({ text: 'Save', variant: 'primary' });
55
+ var tbl = bw.makeTable({ data: rows, sortable: true });
56
+ ```
57
+
58
+ Each factory takes a props object and returns a TACO. The TACO is data — no DOM elements are created, no event listeners are attached. This is a deliberate design choice: it keeps factories composable, serializable, and usable in server-side rendering.
59
+
60
+ ```javascript
61
+ // Compose factory output like any other TACO
62
+ var page = {
63
+ t: 'div', c: [
64
+ bw.makeNavbar({ brand: 'My App', items: navItems }),
65
+ bw.makeCard({
66
+ title: 'Dashboard',
67
+ content: bw.makeTable({ data: stats })
68
+ })
69
+ ]
70
+ };
71
+ ```
72
+
73
+ See [Component Library](component-library.md) for all available factories.
74
+
75
+ ### When Level 0 is enough
76
+
77
+ Use Level 0 when the content does not change after rendering:
78
+
79
+ - Static pages and reports
80
+ - Server-rendered HTML (`bw.html(taco)` in Node.js)
81
+ - Email templates
82
+ - Content sent over the network as JSON
83
+ - Building up a UI description before deciding how to render it
84
+
85
+ ---
86
+
87
+ ## Level 1: Fire-and-Forget DOM Rendering
88
+
89
+ To turn a TACO into something visible, pass it to a rendering function:
90
+
91
+ ```javascript
92
+ // Render to HTML string (works in Node.js and browsers)
93
+ var html = bw.html({ t: 'div', c: 'Hello' });
94
+ // '<div>Hello</div>'
95
+
96
+ // Create a detached DOM element (browser only)
97
+ var el = bw.createDOM({ t: 'div', c: 'Hello' });
98
+
99
+ // Mount into an existing DOM element (browser only)
100
+ bw.DOM('#app', { t: 'div', c: 'Hello' });
101
+ ```
102
+
103
+ `bw.DOM()` finds the element matching the selector, cleans up any previous content (running unmount hooks, clearing subscriptions), and mounts the new TACO as live DOM.
104
+
105
+ ### Adding interactivity at Level 1
106
+
107
+ You can make Level 1 components interactive using closures, `o.state`, and `o.render`:
108
+
109
+ ```javascript
110
+ function makeCounter() {
111
+ return {
112
+ t: 'div',
113
+ o: {
114
+ state: { count: 0 },
115
+ render: function(el) {
116
+ bw.DOM(el, {
117
+ t: 'div', c: [
118
+ { t: 'span', c: 'Count: ' + el._bw_state.count },
119
+ { t: 'button', c: '+1', a: {
120
+ onclick: function() {
121
+ el._bw_state.count++;
122
+ bw.update(el);
123
+ }
124
+ }}
125
+ ]
126
+ });
127
+ }
128
+ }
129
+ };
130
+ }
131
+
132
+ bw.DOM('#app', makeCounter());
133
+ ```
134
+
135
+ This is the **manual render pump** pattern:
136
+
137
+ 1. Define state in `o.state`
138
+ 2. Define a render function in `o.render`
139
+ 3. When state changes, call `bw.update(el)` to re-invoke the render function
140
+
141
+ This pattern works and is fully supported. It gives you direct control over when and how re-rendering happens.
142
+
143
+ ### Lifecycle hooks
144
+
145
+ Level 1 components can respond to mount and unmount events:
146
+
147
+ ```javascript
148
+ {
149
+ t: 'div', c: 'I have lifecycle hooks',
150
+ o: {
151
+ mounted: function(el) {
152
+ // Called after the element is inserted into the DOM
153
+ console.log('Mounted:', el);
154
+ },
155
+ unmount: function(el) {
156
+ // Called before the element is removed from the DOM
157
+ // Clean up timers, event listeners, etc.
158
+ console.log('Unmounting:', el);
159
+ }
160
+ }
161
+ }
162
+ ```
163
+
164
+ > **Warning: Never use `o.mounted` to attach event handlers.** When a stateful component re-renders (after `bw.update()`), the old DOM content is replaced and any listeners attached via `addEventListener` in `mounted` are silently lost. Always put event handlers in `a: { onclick: fn }` -- bitwrench re-attaches them on every render. Use `o.mounted` only for non-event setup: timers, observers, third-party library init, measuring dimensions.
165
+
166
+ ### Targeted updates with `bw.patch()`
167
+
168
+ For fine-grained updates without re-rendering an entire component, use `bw.patch()`:
169
+
170
+ ```javascript
171
+ // Give an element a UUID for addressing
172
+ var display = { t: 'span', a: { class: bw.uuid('count') }, c: '0' };
173
+
174
+ // Later, update just that element's content
175
+ bw.patch('count', '42');
176
+
177
+ // Update content and attributes together
178
+ bw.patch('count', '42', { style: 'color: red' });
179
+
180
+ // Batch multiple patches
181
+ bw.patchAll({
182
+ count: '42',
183
+ status: 'Active',
184
+ label: 'Updated'
185
+ });
186
+ ```
187
+
188
+ ### When Level 1 is enough
189
+
190
+ Use Level 1 when:
191
+
192
+ - You need interactivity but want full control over the render cycle
193
+ - You are building a one-off interactive widget
194
+ - You are integrating with external libraries that manage their own state
195
+ - You prefer explicit `bw.update()` calls over automatic re-rendering
196
+ - You are building the transport layer for server-driven UI (bwserve)
197
+
198
+ > **Coming from jQuery?** Level 1 with `o.render` + `bw.update()` is conceptually similar to jQuery's manual DOM updates, but structured. Instead of scattered `$('.count').text(val)` calls, you have a single render function that produces the complete UI from state. When state changes, you call `bw.update()` and the render function runs again.
199
+
200
+ ---
201
+
202
+ ## Level 1.5: Component Handles
203
+
204
+ When you need imperative control of a rendered element -- updating a title, advancing a carousel, or reading a form value -- without re-rendering the entire component, use `o.handle` and `o.slots`.
205
+
206
+ Component handles attach methods directly to the DOM element via `el.bw`. This gives you a clean API to call from outside the component, and it avoids the "re-render kills input focus" problem that plagues full re-render approaches.
207
+
208
+ ### o.handle -- attach methods
209
+
210
+ Define named methods in `o.handle`. Each method receives the element as its first argument (auto-bound by bitwrench):
211
+
212
+ ```javascript
213
+ var carousel = {
214
+ t: 'div', c: '...',
215
+ o: {
216
+ handle: {
217
+ next: function(el) { /* advance slide */ },
218
+ prev: function(el) { /* go back */ },
219
+ goToSlide: function(el, index) { /* jump to slide */ }
220
+ }
221
+ }
222
+ };
223
+ var el = bw.mount('#app', carousel);
224
+ el.bw.next(); // methods are on el.bw
225
+ el.bw.goToSlide(3);
226
+ ```
227
+
228
+ ### o.slots -- auto-generate setters/getters
229
+
230
+ Declare named content areas with CSS selectors. Bitwrench auto-generates `el.bw.setName()` and `el.bw.getName()` pairs:
231
+
232
+ ```javascript
233
+ var card = bw.makeCard({ title: 'Stats', content: '0' });
234
+ // makeCard declares o.slots: { title: '.bw_card_title', content: '.bw_card_body', footer: '.bw_card_footer' }
235
+ var el = bw.mount('#app', card);
236
+ el.bw.setTitle('Updated Title');
237
+ el.bw.setContent({ t: 'strong', c: '42' }); // accepts TACO objects
238
+ var text = el.bw.getTitle(); // returns text content
239
+ ```
240
+
241
+ Slot setters accept strings or TACO objects. They update just the targeted element -- no full re-render, so input focus, scroll position, and animation state are preserved.
242
+
243
+ ### bw.mount() -- get the element back
244
+
245
+ `bw.mount()` works like `bw.DOM()` but returns the created root element instead of the container. This is how you get access to `el.bw`:
246
+
247
+ ```javascript
248
+ var el = bw.mount('#app', bw.makeCarousel({ items: slides }));
249
+ el.bw.goToSlide(2); // direct access to handle methods
250
+ ```
251
+
252
+ ### bw.message() -- dispatch by selector
253
+
254
+ When you don't have a direct reference to the element, use `bw.message()` to dispatch by CSS selector, id, or UUID:
255
+
256
+ ```javascript
257
+ bw.message('#my-card', 'setTitle', 'New Title');
258
+ bw.message('.bw_uuid_abc123', 'next');
259
+ ```
260
+
261
+ ### BCCL factories with handles
262
+
263
+ All BCCL factories include `o.handle` and/or `o.slots`. Examples:
264
+
265
+ | Factory | Handle methods |
266
+ |---------|---------------|
267
+ | makeCarousel | goToSlide, next, prev, getActiveIndex, pause, play |
268
+ | makeTabs | setActiveTab, getActiveTab |
269
+ | makeAccordion | toggle, openAll, closeAll |
270
+ | makeModal | open, close |
271
+ | makeProgress | setValue, getValue |
272
+ | makeChipInput | addChip, removeChip, getChips, clear |
273
+ | makeCard | setTitle/getTitle, setContent/getContent, setFooter/getFooter (slots) |
274
+ | makeStatCard | setValue/getValue, setLabel/getLabel (slots) |
275
+
276
+ ### When to use handles vs Level 2
277
+
278
+ | Situation | Use |
279
+ |-----------|-----|
280
+ | Update a label, badge, or slot text | **Handles** -- `el.bw.setTitle('new')` |
281
+ | Advance a carousel or toggle an accordion | **Handles** -- `el.bw.next()`, `el.bw.toggle(0)` |
282
+ | Component has complex state that triggers full UI rebuild | **Level 2** -- `o.state` + `o.render` + `bw.update()` |
283
+ | Need to preserve input focus during updates | **Handles** -- slot setters don't re-render siblings |
284
+ | External code needs to control an embedded widget | **Handles** -- `bw.mount()` + `el.bw.method()` |
285
+
286
+ ---
287
+
288
+ ## Level 2: Stateful TACO
289
+
290
+ Level 2 adds `o.state` and `o.render` to a TACO, giving it managed state and a render pump. When state changes, you call `bw.update(el)` to re-invoke the render function. This is the recommended pattern for interactive components.
291
+
292
+ ```javascript
293
+ var counter = {
294
+ t: 'div',
295
+ o: {
296
+ state: { count: 0 },
297
+ render: function(el) {
298
+ var s = el._bw_state;
299
+ bw.DOM(el, {
300
+ t: 'div', c: [
301
+ { t: 'h3', c: 'Count: ' + s.count },
302
+ bw.makeButton({ text: '+1', onclick: function() {
303
+ s.count++;
304
+ bw.update(el);
305
+ }})
306
+ ]
307
+ });
308
+ }
309
+ }
310
+ };
311
+
312
+ bw.DOM('#app', counter);
313
+ ```
314
+
315
+ ### How it works
316
+
317
+ 1. `bw.createDOM()` (called internally by `bw.DOM()`) sees `o.state` and copies it to `el._bw_state`
318
+ 2. If `o.render` is defined, it is stored as `el._bw_render` and called immediately: `o.render(el, el._bw_state)`
319
+ 3. When state changes, you call `bw.update(el)` which re-invokes `el._bw_render(el, el._bw_state)` and emits a `bw:statechange` event
320
+ 4. The render function produces new content via `bw.DOM(el, ...)`, replacing the old children
321
+
322
+ ### Accessing state
323
+
324
+ State lives directly on the DOM element as `el._bw_state`:
325
+
326
+ ```javascript
327
+ {
328
+ t: 'div',
329
+ o: {
330
+ state: { count: 0, label: 'Clicks' },
331
+ render: function(el) {
332
+ var s = el._bw_state;
333
+ bw.DOM(el, {
334
+ t: 'div', c: s.label + ': ' + s.count
335
+ });
336
+ }
337
+ }
338
+ }
339
+ ```
340
+
341
+ To read or modify state from outside, get a reference to the element:
342
+
343
+ ```javascript
344
+ var el = bw.$('#my-component')[0];
345
+ el._bw_state.count = 42;
346
+ bw.update(el);
347
+ ```
348
+
349
+ ### Lifecycle hooks
350
+
351
+ Stateful TACOs support two primary lifecycle hooks:
352
+
353
+ | Hook | When it fires | Typical use |
354
+ |------|--------------|-------------|
355
+ | **`mounted`** | After DOM insertion | Start timers, attach observers, measure dimensions |
356
+ | **`unmount`** | Before DOM removal | Clean up timers, detach observers |
357
+
358
+ ```javascript
359
+ var timer = {
360
+ t: 'div',
361
+ o: {
362
+ state: { seconds: 0 },
363
+ mounted: function(el) {
364
+ el._interval = setInterval(function() {
365
+ el._bw_state.seconds++;
366
+ bw.update(el);
367
+ }, 1000);
368
+ },
369
+ unmount: function(el) {
370
+ clearInterval(el._interval);
371
+ },
372
+ render: function(el) {
373
+ bw.DOM(el, { t: 'span', c: 'Elapsed: ' + el._bw_state.seconds + 's' });
374
+ }
375
+ }
376
+ };
377
+ ```
378
+
379
+ > **Warning: Never attach event handlers in `o.mounted`.** When `bw.update()` re-renders a component, the old DOM children are replaced. Any listeners attached via `addEventListener` in `mounted` are silently lost. Always put event handlers in `a: { onclick: fn }` -- bitwrench re-attaches them on every render.
380
+
381
+ ### Targeted updates with `bw.patch()`
382
+
383
+ For fine-grained updates without re-rendering an entire component, use `bw.patch()`:
384
+
385
+ ```javascript
386
+ var dashboard = {
387
+ t: 'div',
388
+ o: {
389
+ state: { temp: 0 },
390
+ render: function(el) {
391
+ var s = el._bw_state;
392
+ bw.DOM(el, {
393
+ t: 'div', c: [
394
+ { t: 'span', a: { class: bw.uuid('temp') }, c: s.temp + ' C' },
395
+ { t: 'span', a: { class: bw.uuid('status') }, c: 'OK' }
396
+ ]
397
+ });
398
+ }
399
+ }
400
+ };
401
+
402
+ bw.DOM('#app', dashboard);
403
+
404
+ // Later, update just specific elements without a full re-render
405
+ bw.patch('temp', '23.5 C');
406
+ bw.patch('status', 'Warning');
407
+ ```
408
+
409
+ ### When to use Level 2
410
+
411
+ Use Level 2 when:
412
+
413
+ - The component has state that changes after initial render
414
+ - You need a render function that re-runs on state changes
415
+ - You need lifecycle management (mount, unmount)
416
+ - You want explicit control over what triggers a re-render
417
+
418
+ > **Coming from React?** Level 2 is like a class component with `this.state` and a manual `forceUpdate()`. The render function rebuilds the component from state each time `bw.update()` is called.
419
+
420
+ > **Coming from jQuery?** Level 2 with `o.render` + `bw.update()` is conceptually similar to jQuery's manual DOM updates, but structured. Instead of scattered `$('.count').text(val)` calls, you have a single render function that produces the complete UI from state.
421
+
422
+ ---
423
+
424
+ ## Escalating Between Levels
425
+
426
+ You can start at any level and escalate when you need more capability.
427
+
428
+ ### Level 0 → Level 1
429
+
430
+ Pass a TACO to a rendering function:
431
+
432
+ ```javascript
433
+ var taco = bw.makeCard({ title: 'Hello' }); // Level 0
434
+ bw.DOM('#app', taco); // Level 1 — now it's in the DOM
435
+ ```
436
+
437
+ ### Level 0 => Level 2
438
+
439
+ Add `o.state` and `o.render` to make a static TACO stateful:
440
+
441
+ ```javascript
442
+ // Start with Level 0
443
+ var card = bw.makeCard({ title: 'Hello' });
444
+
445
+ // Wrap in a stateful container
446
+ var statefulCard = {
447
+ t: 'div',
448
+ o: {
449
+ state: { title: 'Hello' },
450
+ render: function(el) {
451
+ bw.DOM(el, bw.makeCard({ title: el._bw_state.title }));
452
+ }
453
+ }
454
+ };
455
+ bw.DOM('#app', statefulCard);
456
+ ```
457
+
458
+ ### Level 1 => Level 2
459
+
460
+ If you have a Level 1 component using manual `bw.DOM()` calls, add `o.state` and `o.render`:
461
+
462
+ **Before (Level 1 -- manual re-render):**
463
+ ```javascript
464
+ var count = 0;
465
+ function renderCounter() {
466
+ bw.DOM('#app', {
467
+ t: 'div', c: [
468
+ { t: 'span', c: 'Count: ' + count },
469
+ { t: 'button', c: '+1', a: {
470
+ onclick: function() { count++; renderCounter(); }
471
+ }}
472
+ ]
473
+ });
474
+ }
475
+ renderCounter();
476
+ ```
477
+
478
+ **After (Level 2 -- stateful TACO):**
479
+ ```javascript
480
+ bw.DOM('#app', {
481
+ t: 'div',
482
+ o: {
483
+ state: { count: 0 },
484
+ render: function(el) {
485
+ var s = el._bw_state;
486
+ bw.DOM(el, {
487
+ t: 'div', c: [
488
+ { t: 'span', c: 'Count: ' + s.count },
489
+ { t: 'button', c: '+1', a: {
490
+ onclick: function() { s.count++; bw.update(el); }
491
+ }}
492
+ ]
493
+ });
494
+ }
495
+ }
496
+ });
497
+ ```
498
+
499
+ The Level 2 version encapsulates state inside the component. No external variable, no standalone render function. The render function is called automatically on mount and on each `bw.update(el)` call.
500
+
501
+ ---
502
+
503
+ ## Cross-Component Communication
504
+
505
+ Bitwrench provides three mechanisms for components to communicate, each suited to different relationship types.
506
+
507
+ ### Shared state (parent-child)
508
+
509
+ Multiple components can share the same state object. When either calls `bw.update()`, it re-renders with the current shared state:
510
+
511
+ ```javascript
512
+ var appState = { user: { name: 'Alice' }, items: [] };
513
+
514
+ var header = {
515
+ t: 'header',
516
+ o: {
517
+ state: appState,
518
+ render: function(el) {
519
+ bw.DOM(el, { t: 'span', c: 'Hello, ' + el._bw_state.user.name });
520
+ }
521
+ }
522
+ };
523
+
524
+ var main = {
525
+ t: 'main',
526
+ o: {
527
+ state: appState,
528
+ render: function(el) {
529
+ var s = el._bw_state;
530
+ bw.DOM(el, {
531
+ t: 'div', c: s.items.map(function(item) {
532
+ return { t: 'div', c: item.text };
533
+ })
534
+ });
535
+ }
536
+ }
537
+ };
538
+ ```
539
+
540
+ ### Pub/sub (siblings, decoupled)
541
+
542
+ Use `bw.pub()` and `bw.sub()` for app-wide topic-based messaging:
543
+
544
+ ```javascript
545
+ // Publisher
546
+ var searchBox = { t: 'input', a: { oninput: function(e) {
547
+ bw.pub('search:changed', { query: e.target.value });
548
+ }}};
549
+
550
+ // Subscriber
551
+ var results = {
552
+ t: 'div',
553
+ o: {
554
+ state: { query: '' },
555
+ mounted: function(el) {
556
+ bw.sub('search:changed', function(detail) {
557
+ el._bw_state.query = detail.query;
558
+ bw.update(el);
559
+ }, el);
560
+ },
561
+ render: function(el) {
562
+ bw.DOM(el, { t: 'span', c: 'Results for: ' + el._bw_state.query });
563
+ }
564
+ }
565
+ };
566
+ ```
567
+
568
+ Pub/sub is app-scoped -- publishers and subscribers do not need to know about each other. Pass the element as the third argument to `bw.sub()` to tie the subscription's lifetime to the element (auto-cleaned on `bw.cleanup()`).
569
+
570
+ ### Updating child widgets within a parent component
571
+
572
+ When a parent component contains child sub-components (like a progress bar inside a dashboard card), use pub/sub to update the child:
573
+
574
+ ```javascript
575
+ // Dashboard with a progress indicator, updated via pub/sub
576
+ var progressId = bw.uuid('progress');
577
+
578
+ bw.DOM('#app', {
579
+ t: 'div',
580
+ o: {
581
+ state: { pct: 0 },
582
+ mounted: function(el) {
583
+ bw.sub('upload:progress', function(d) {
584
+ el._bw_state.pct = d.pct;
585
+ bw.update(el);
586
+ }, el);
587
+ },
588
+ render: function(el) {
589
+ var s = el._bw_state;
590
+ bw.DOM(el, {
591
+ t: 'div', c: [
592
+ { t: 'h2', c: 'Upload Progress' },
593
+ bw.makeProgress({ value: s.pct, label: s.pct + '%' }),
594
+ bw.makeButton({ text: 'Start', onclick: function() {
595
+ var pct = 0;
596
+ var interval = setInterval(function() {
597
+ pct += 10;
598
+ bw.pub('upload:progress', { pct: pct });
599
+ if (pct >= 100) clearInterval(interval);
600
+ }, 500);
601
+ }})
602
+ ]
603
+ });
604
+ }
605
+ }
606
+ });
607
+ ```
608
+
609
+ ---
610
+
611
+ ## Low-Level Primitives
612
+
613
+ These primitives are the building blocks of the stateful TACO model. They are also useful standalone for server-driven UI transport, integration with external libraries, and performance-critical update paths.
614
+
615
+ ### Quick reference
616
+
617
+ | Function | Purpose |
618
+ |----------|---------|
619
+ | `bw.update(el)` | Re-invoke `el._bw_render(el)` to re-render |
620
+ | `bw.patch(uuid, content, attr)` | Update a single UUID-addressed element |
621
+ | `bw.patchAll(patches)` | Batch-update multiple UUID-addressed elements |
622
+ | `bw.uuid(prefix)` | Generate a UUID class for addressing |
623
+ | `bw.emit(el, event, detail)` | Dispatch a CustomEvent on a DOM element |
624
+ | `bw.on(el, event, handler)` | Listen for a CustomEvent on a DOM element |
625
+ | `bw.pub(topic, detail)` | Publish to app-wide topic |
626
+ | `bw.sub(topic, handler, el?)` | Subscribe to app-wide topic |
627
+ | `bw.unsub(topic, handler)` | Unsubscribe from topic |
628
+ | `bw.cleanup(el)` | Run unmount hooks and clear subscriptions |
629
+
630
+ ### `bw.emit()` / `bw.on()` vs `bw.pub()` / `bw.sub()`
631
+
632
+ Bitwrench has two event systems that serve different purposes:
633
+
634
+ | | `bw.emit()` / `bw.on()` | `bw.pub()` / `bw.sub()` |
635
+ |---|---|---|
636
+ | Scope | DOM element and its ancestors (bubbles) | App-wide (all subscribers) |
637
+ | Addressing | By DOM element reference | By topic string |
638
+ | Use case | Parent-child DOM communication | Decoupled cross-component messaging |
639
+ | Cleanup | Manual or via bw.cleanup() | Auto-cleanup via `handle.sub()` or element lifecycle |
640
+
641
+ ---
642
+
643
+ ## Shared State Across Views
644
+
645
+ When building multi-view apps (SPAs, dashboards with panels, tabbed interfaces), you need shared state that persists across view switches. The canonical bitwrench pattern: a plain object store with topic-scoped pub/sub.
646
+
647
+ ### The pattern
648
+
649
+ ```javascript
650
+ // 1. Store is a plain object
651
+ var store = {
652
+ todos: [],
653
+ projects: [],
654
+ user: { name: 'Alice' }
655
+ };
656
+
657
+ // 2. Update function publishes scoped topics
658
+ function updateStore(key, value) {
659
+ store[key] = value;
660
+ bw.pub('store:' + key, value); // topic per data slice
661
+ }
662
+ ```
663
+
664
+ ### Scoped subscriptions
665
+
666
+ Each view subscribes only to the data it needs. Pass `el` as the third argument so the subscription auto-cleans when the view unmounts:
667
+
668
+ ```javascript
669
+ // Todo view -- only re-renders when todos change
670
+ function renderTodoView(target) {
671
+ var el = bw.mount(target, {
672
+ t: 'div',
673
+ o: {
674
+ state: { items: store.todos },
675
+ mounted: function(el) {
676
+ bw.sub('store:todos', function(todos) {
677
+ el._bw_state.items = todos;
678
+ bw.update(el);
679
+ }, el); // auto-unsubscribes when view is removed
680
+ },
681
+ render: function(el) {
682
+ var s = el._bw_state;
683
+ bw.DOM(el, { t: 'ul', c: s.items.map(function(item) {
684
+ return { t: 'li', c: item.text };
685
+ })});
686
+ }
687
+ }
688
+ });
689
+ }
690
+
691
+ // Project view -- only re-renders when projects change
692
+ function renderProjectView(target) {
693
+ var el = bw.mount(target, {
694
+ t: 'div',
695
+ o: {
696
+ state: { projects: store.projects },
697
+ mounted: function(el) {
698
+ bw.sub('store:projects', function(projects) {
699
+ el._bw_state.projects = projects;
700
+ bw.update(el);
701
+ }, el);
702
+ },
703
+ render: function(el) {
704
+ // ... render projects
705
+ }
706
+ }
707
+ });
708
+ }
709
+ ```
710
+
711
+ ### Anti-pattern: single topic
712
+
713
+ Do NOT use a single `'store:changed'` topic that re-renders everything:
714
+
715
+ ```javascript
716
+ // WRONG -- every view re-renders on every store change
717
+ bw.sub('store:changed', function() {
718
+ renderTodoView('#todos');
719
+ renderProjectView('#projects');
720
+ renderUserHeader('#header');
721
+ }, el);
722
+
723
+ // RIGHT -- each view subscribes to its own data slice
724
+ bw.sub('store:todos', renderTodos, todosEl);
725
+ bw.sub('store:projects', renderProjects, projectsEl);
726
+ ```
727
+
728
+ ### When to use
729
+
730
+ - Multi-view SPAs where views share data
731
+ - Dashboard panels that react to shared metrics
732
+ - Any app with >1 view reading from the same data source
733
+
734
+ For surgical updates within a view (changing a title, updating a counter), use [Level 1.5 handles](state-management.md#level-15-component-handles) instead of a full re-render.
735
+
736
+ For URL-driven view switching, combine with [bw.router()](routing.md):
737
+
738
+ ```javascript
739
+ bw.router({
740
+ target: '#app',
741
+ routes: {
742
+ '/todos': function() { return makeTodoView(); },
743
+ '/projects': function() { return makeProjectView(); }
744
+ }
745
+ });
746
+ ```
747
+
748
+ ---
749
+
750
+ ## Choosing a Pattern
751
+
752
+ | Situation | Recommended approach |
753
+ |-----------|---------------------|
754
+ | Static content, server rendering | Level 0 -- TACO data, `bw.html()` |
755
+ | Interactive widget, full control | Level 1 -- manual `bw.DOM()` re-renders |
756
+ | Update a slot, label, or control a widget | Level 1.5 -- `o.handle` / `o.slots` via `bw.mount()` + `el.bw` |
757
+ | Stateful component with changing data | Level 2 -- `o.state` + `o.render` + `bw.update()` |
758
+ | Server pushes UI updates | Level 1 -- `bw.patch()` / `bw.DOM()` |
759
+ | Components need to talk to each other | `bw.pub()`/`bw.sub()` |
760
+ | URL-driven views (SPA) | `bw.router()` -- see [Routing](routing.md) |
761
+ | Debugging component state | `el._bw_state` in the console, or `bw.inspect(selector)` |
762
+
763
+ ---
764
+
765
+ ## Removed: bw.component() (v2.0.19)
766
+
767
+ `bw.component()`, `bw.compile()`, `bw.when()`, and `bw.each()` were removed in v2.0.19. Calling these functions now throws an Error. Their functionality is replaced by `o.handle`, `o.slots`, and `bw.mount()` -- see [Level 1.5: Component Handles](#level-15-component-handles) above.