bitwrench 2.0.32 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/README.md +211 -125
  2. package/dist/bitwrench-bccl.cjs.js +349 -188
  3. package/dist/bitwrench-bccl.cjs.min.js +2 -39
  4. package/dist/bitwrench-bccl.cjs.min.js.gz +0 -0
  5. package/dist/bitwrench-bccl.esm.js +349 -188
  6. package/dist/bitwrench-bccl.esm.min.js +2 -39
  7. package/dist/bitwrench-bccl.esm.min.js.gz +0 -0
  8. package/dist/bitwrench-bccl.umd.js +349 -188
  9. package/dist/bitwrench-bccl.umd.min.js +2 -39
  10. package/dist/bitwrench-bccl.umd.min.js.gz +0 -0
  11. package/dist/bitwrench-code-edit.cjs.js +17 -6
  12. package/dist/bitwrench-code-edit.cjs.min.js +2 -20
  13. package/dist/bitwrench-code-edit.es5.js +8 -3
  14. package/dist/bitwrench-code-edit.es5.min.js +2 -19
  15. package/dist/bitwrench-code-edit.esm.js +17 -6
  16. package/dist/bitwrench-code-edit.esm.min.js +2 -19
  17. package/dist/bitwrench-code-edit.umd.js +17 -6
  18. package/dist/bitwrench-code-edit.umd.min.js +2 -19
  19. package/dist/bitwrench-code-edit.umd.min.js.gz +0 -0
  20. package/dist/bitwrench-debug.js +1 -1
  21. package/dist/bitwrench-debug.min.js +1 -1
  22. package/dist/bitwrench-lean.cjs.js +2492 -1628
  23. package/dist/bitwrench-lean.cjs.min.js +2 -80
  24. package/dist/bitwrench-lean.cjs.min.js.gz +0 -0
  25. package/dist/bitwrench-lean.es5.js +2740 -1838
  26. package/dist/bitwrench-lean.es5.min.js +2 -49
  27. package/dist/bitwrench-lean.es5.min.js.gz +0 -0
  28. package/dist/bitwrench-lean.esm.js +2492 -1628
  29. package/dist/bitwrench-lean.esm.min.js +2 -80
  30. package/dist/bitwrench-lean.esm.min.js.gz +0 -0
  31. package/dist/bitwrench-lean.umd.js +2492 -1628
  32. package/dist/bitwrench-lean.umd.min.js +2 -80
  33. package/dist/bitwrench-lean.umd.min.js.gz +0 -0
  34. package/dist/bitwrench-util-color.cjs.js +251 -0
  35. package/dist/bitwrench-util-color.cjs.min.js +3 -0
  36. package/dist/bitwrench-util-color.es5.js +256 -0
  37. package/dist/bitwrench-util-color.es5.min.js +3 -0
  38. package/dist/bitwrench-util-color.esm.js +241 -0
  39. package/dist/bitwrench-util-color.esm.min.js +3 -0
  40. package/dist/bitwrench-util-color.umd.js +257 -0
  41. package/dist/bitwrench-util-color.umd.min.js +3 -0
  42. package/dist/bitwrench-util-css.cjs.js +2 -1
  43. package/dist/bitwrench-util-css.cjs.min.js +2 -21
  44. package/dist/bitwrench-util-css.es5.js +2 -1
  45. package/dist/bitwrench-util-css.es5.min.js +2 -20
  46. package/dist/bitwrench-util-css.esm.js +2 -1
  47. package/dist/bitwrench-util-css.esm.min.js +1 -19
  48. package/dist/bitwrench-util-css.umd.js +2 -1
  49. package/dist/bitwrench-util-css.umd.min.js +2 -20
  50. package/dist/bitwrench-util-css.umd.min.js.gz +0 -0
  51. package/dist/bitwrench.cjs.js +2826 -1801
  52. package/dist/bitwrench.cjs.min.js +2 -99
  53. package/dist/bitwrench.cjs.min.js.gz +0 -0
  54. package/dist/bitwrench.css +403 -479
  55. package/dist/bitwrench.d.ts +70 -73
  56. package/dist/bitwrench.es5.js +3106 -2020
  57. package/dist/bitwrench.es5.min.js +2 -67
  58. package/dist/bitwrench.es5.min.js.gz +0 -0
  59. package/dist/bitwrench.esm.js +2826 -1801
  60. package/dist/bitwrench.esm.min.js +2 -99
  61. package/dist/bitwrench.esm.min.js.gz +0 -0
  62. package/dist/bitwrench.min.css +1 -1
  63. package/dist/bitwrench.umd.js +2826 -1801
  64. package/dist/bitwrench.umd.min.js +2 -99
  65. package/dist/bitwrench.umd.min.js.gz +0 -0
  66. package/dist/builds.json +222 -134
  67. package/dist/bwserve.cjs.js +289 -282
  68. package/dist/bwserve.d.ts +157 -0
  69. package/dist/bwserve.esm.js +290 -283
  70. package/dist/sri.json +54 -46
  71. package/docs/README.md +6 -3
  72. package/docs/app-patterns.md +7 -6
  73. package/docs/bitwrench-for-wasm.md +53 -54
  74. package/docs/bitwrench-mcp.md +2 -2
  75. package/docs/bitwrench-northstar-principles.md +406 -0
  76. package/docs/bitwrench-taco-schema-discussion.md +2 -2
  77. package/docs/bitwrench_api.md +191 -106
  78. package/docs/bitwrench_typescript_usage.md +5 -5
  79. package/docs/bw-attach.md +29 -75
  80. package/docs/bwserve.md +200 -168
  81. package/docs/cli.md +36 -12
  82. package/docs/component-cheatsheet.md +2 -2
  83. package/docs/component-library.md +4 -4
  84. package/docs/component-lifecycle.md +234 -0
  85. package/docs/drift-lint.md +268 -0
  86. package/docs/framework-translation-table.md +4 -4
  87. package/docs/llm-bitwrench-guide.md +60 -50
  88. package/docs/routing.md +11 -13
  89. package/docs/state-management.md +110 -109
  90. package/docs/taco-format.md +13 -14
  91. package/docs/theming.md +13 -3
  92. package/docs/thinking-in-bitwrench.md +858 -983
  93. package/docs/tutorial-bwserve.md +37 -36
  94. package/docs/tutorial-embedded.md +10 -21
  95. package/docs/tutorial-website.md +2 -2
  96. package/package.json +38 -7
  97. package/readme.html +262 -161
  98. package/src/bitwrench-bccl-entry.js +2 -2
  99. package/src/bitwrench-bccl.js +346 -185
  100. package/src/bitwrench-code-edit.js +16 -5
  101. package/src/bitwrench-color-utils.js +117 -181
  102. package/src/bitwrench-file-ops.js +2 -2
  103. package/src/bitwrench-lean.js +4 -3
  104. package/src/bitwrench-router.js +5 -2
  105. package/src/bitwrench-styles.js +420 -504
  106. package/src/bitwrench-util-color.js +240 -0
  107. package/src/bitwrench-util-css.js +1 -0
  108. package/src/bitwrench-utils.js +4 -0
  109. package/src/bitwrench.d.ts +70 -73
  110. package/src/bitwrench.h +5 -0
  111. package/src/bitwrench.js +1939 -933
  112. package/src/bwserve/attach.js +0 -1
  113. package/src/bwserve/bwclient.js +172 -32
  114. package/src/bwserve/bwshell.js +0 -4
  115. package/src/bwserve/client.js +59 -220
  116. package/src/bwserve/index.js +59 -26
  117. package/src/bwserve.d.ts +157 -0
  118. package/src/bwserve.h +5 -0
  119. package/src/cli/attach.js +12 -75
  120. package/src/cli/convert.js +2 -2
  121. package/src/cli/serve.js +37 -35
  122. package/src/generate-css.js +1 -1
  123. package/src/mcp/knowledge.js +4 -4
  124. package/src/mcp/live.js +21 -13
  125. package/src/mcp/tools.js +0 -1
  126. package/src/version.js +3 -7
@@ -2,86 +2,92 @@
2
2
 
3
3
  ## Table of Contents
4
4
 
5
- 0. [The Problem and the Idea](#0-the-problem-and-the-idea)
6
- 1. [TACO: the Shape of a UI Element](#1-taco-the-shape-of-a-ui-element)
7
- 2. [Styling -- CSS Is Just Strings](#2-styling--css-is-just-strings)
8
- 3. [It's Just JavaScript -- the Core Insight](#3-its-just-javascript--the-core-insight)
9
- 4. [The BCCL: Ready-Made Components](#4-the-bccl-ready-made-components)
10
- 5. [Three Levels of Commitment](#5-three-levels-of-commitment)
11
- 6. [Events and Communication](#6-events-and-communication)
12
- 7. [Server-Driven UI (bwserve)](#7-server-driven-ui-bwserve)
13
- 8. [Routing](#8-routing)
14
- 9. [Utilities and Color Functions](#9-utilities-and-color-functions)
15
- 10. [Putting It All Together -- Patterns](#10-putting-it-all-together--patterns)
16
- 11. [What Bitwrench Doesn't Do](#11-what-bitwrench-doesnt-do)
5
+ 1. [The Problem and the Idea](#1-the-problem-and-the-idea)
6
+ 2. [TACO to HTML String](#2-taco-to-html-string)
7
+ 3. [TACO to Live DOM](#3-taco-to-live-dom)
8
+ 4. [JavaScript Makes TACOs Composable](#4-javascript-makes-tacos-composable)
9
+ 5. [Styling Grows Naturally](#5-styling-grows-naturally)
10
+ 6. [Events and Behavior](#6-events-and-behavior)
11
+ 7. [Lifecycle: `o:` Options](#7-lifecycle-o-options)
12
+ 8. [BCCL: Ready-Made Components](#8-bccl-ready-made-components)
13
+ 9. [Routing, Utilities, Advanced](#9-routing-utilities-advanced)
14
+ 10. [What Bitwrench Does Instead](#10-what-bitwrench-does-instead)
15
+ 11. [Server-Driven UI and CLI](#11-server-driven-ui-and-cli)
17
16
  12. [Quick Reference](#12-quick-reference)
18
17
  - [Framework Translation Table](#appendix-framework-translation-table)
18
+ - [Postscript: Validation](#postscript-validation)
19
19
 
20
- **Related docs:** [Component Cheat Sheet](component-cheatsheet.md) | [State Management](state-management.md) | [Component Library](component-library.md) | [LLM Guide](llm-bitwrench-guide.md)
20
+ **Related docs:** [North Star Principles](bitwrench-northstar-principles.md) | [Component Cheat Sheet](component-cheatsheet.md) | [State Management](state-management.md) | [Component Library](component-library.md) | [LLM Guide](llm-bitwrench-guide.md)
21
21
 
22
- ---
23
22
 
24
- ## 0. The Problem and the Idea
23
+ ## 1. The Problem and the Idea
25
24
 
26
- Building web UIs with raw HTML, CSS, and JavaScript works but it's painful. HTML is verbose. Styling the same element across a page means copying CSS rules or managing class hierarchies. Adding interactivity means wiring up event listeners, tracking state in variables, and manually updating the DOM when things change. The more complex the UI, the more copy-paste, the more boilerplate, the more places things can go wrong.
25
+ Building web UIs with raw HTML, CSS, and JavaScript works -- but it is painful. HTML is verbose. Styling the same element across a page means copying CSS rules or managing class hierarchies. Adding interactivity means wiring up event listeners, tracking state in variables, and manually updating the DOM when things change. The more complex the UI, the more copy-paste, the more boilerplate, the more places things can go wrong.
27
26
 
28
- Different paradigms emerged to manage this complexity:
27
+ Frameworks emerged to manage this -- React, Vue, Svelte for rendering; Sass, Tailwind for styling; Redux, Zustand for state -- each adding a new syntax, a new tool, a new layer. Each solves a real problem. But each also adds an abstraction to learn, configure, and maintain.
29
28
 
30
- - **Markup generation**: JSX (React), templates (Vue, Svelte, Angular) describe UI declaratively, let a compiler or runtime translate it to DOM operations.
31
- - **Styling**: Sass and Less added variables and mixins. Tailwind invented utility classes. CSS-in-JS libraries generate styles at runtime. CSS Modules scope class names to avoid conflicts.
32
- - **State management**: React hooks, Vue reactivity, Svelte stores, Redux, Zustand — track application state and automatically re-render when it changes.
33
- - **Build tooling**: Babel, webpack, Vite, esbuild — transpile, bundle, tree-shake, hot-reload. Required infrastructure to connect the pieces.
29
+ Bitwrench takes a different approach. Instead of adding layers, it leans into what the browser already provides -- the DOM for structure, CSS for styling, JavaScript for behavior -- and uses the JavaScript language itself to manage all three concerns.
34
30
 
35
- Each of these solves a real problem. But each also adds a layer a new syntax, a new tool, a new abstraction to learn, configure, and maintain.
31
+ The mechanism is a plain JavaScript object called a **TACO**: `{t, a, c, o}` -- Tag, Attributes, Content, Options. A `{taco}` describes a UI element the same way HTML does, but because it is a JavaScript object, you get the full language at every point: variables, functions, loops, conditionals, composition. No special syntax. No compiler. No build step.
36
32
 
37
- Bitwrench takes a different approach. Instead of adding layers, it leans into what the browser already provides the DOM for structure, CSS for styling, JavaScript for behavior and uses the JavaScript language itself to manage all three concerns.
33
+ > **"If you know JavaScript, you already know bitwrench. Everything else is just learning the shape of the objects -- and a small set of conventions for mounting, lifecycle, and updates that the rest of this document covers."**
38
34
 
39
- The mechanism is a plain JavaScript object called a TACO: `{t, a, c, o}` Tag, Attributes, Content, Options. A TACO describes a UI element the same way HTML does, but because it's a JavaScript object, you get the full language at every point: variables, functions, loops, conditionals, composition. No special syntax. No compiler. No build step.
35
+ This document walks a single example forward -- from a static HTML string to a live, interactive, server-driven application -- one layer at a time.
40
36
 
41
- > **"If you know JavaScript, you already know bitwrench. Everything else is just learning the shape of the objects."**
42
37
 
43
- ---
44
-
45
- ## 1. TACO: the Shape of a UI Element
38
+ ## 2. TACO to HTML String
46
39
 
47
40
  ### From HTML to TACO
48
41
 
49
- Every HTML element has a tag, attributes, and content. A TACO object mirrors this directly:
42
+ Every HTML element has a tag, attributes, and content. A `{taco}` object mirrors this directly:
50
43
 
51
44
  ```html
52
45
  <!-- HTML -->
53
- <div class="card" id="x">Hello world</div>
46
+ <div class="greeting" id="hero">Hello world</div>
54
47
  ```
55
48
 
56
49
  ```js
57
- // TACO the same element as a JavaScript object
58
- { t: 'div', a: { class: 'card', id: 'x' }, c: 'Hello world' }
50
+ // TACO -- the same element as a JavaScript object
51
+ { t: 'div', a: { class: 'greeting', id: 'hero' }, c: 'Hello world' }
59
52
  ```
60
53
 
61
- The mapping is direct: `t` is the tag name, `a` is an object of HTML attributes, `c` is the content. If we had a function to convert this object to real HTML, we could render it in the browser. That's exactly what bitwrench provides:
54
+ The mapping is direct: `t` is the tag name, `a` is an object of HTML attributes, `c` is the content. The function `bw.html()` converts this object to an HTML string:
62
55
 
63
56
  ```js
64
- var card = { t: 'div', a: { class: 'card', id: 'x' }, c: 'Hello world' };
57
+ var greeting = { t: 'div', a: { class: 'greeting', id: 'hero' }, c: 'Hello world' };
65
58
 
66
- bw.html(card); // → '<div class="card" id="x">Hello world</div>'
67
- bw.createDOM(card); // HTMLDivElement (a real DOM node, ready to insert)
68
- bw.DOM('#target', card); // mount directly into an existing element on the page
59
+ bw.html(greeting);
60
+ // => '<div class="greeting" id="hero">Hello world</div>'
69
61
  ```
70
62
 
71
- Three output modes from one input:
63
+ That is the entire concept. Everything else builds on it.
64
+
65
+ > **A note on syntax:** Examples in this document use `var` and `function()` to show that bitwrench requires no transpiler -- it runs in any JS environment as-is. In your own code, use `const`, `let`, and arrow functions freely.
66
+
67
+ Content strings are HTML-escaped by default. `c: '<b>bold</b>'` renders as visible text, not markup. When you need actual HTML, use `bw.raw()` (covered in Section 9).
68
+
69
+ We will use a **contact card** as a running example throughout this document, building it up one capability at a time. Here is the card as a `{taco}`:
70
+
71
+ ```js
72
+ var card = {
73
+ t: 'div', a: { class: 'card' }, c: [
74
+ { t: 'h3', c: 'Alice' },
75
+ { t: 'p', c: 'alice@example.com' }
76
+ ]
77
+ };
72
78
 
73
- - `bw.html()` — returns an HTML string. Useful for server-side rendering, Node.js scripts, email templates.
74
- - `bw.createDOM()` returns a detached DOM element. Useful when you need to manipulate it before inserting.
75
- - `bw.DOM()` — mounts the result into an existing page element. The most common use.
79
+ bw.html(card);
80
+ // => '<div class="card"><h3>Alice</h3><p>alice@example.com</p></div>'
81
+ ```
76
82
 
77
- The TACO is data. The rendering is a separate step. You decide when and how it becomes real.
83
+ A tag, attributes, nested content -- the same shape as the greeting, just with children. We will mount this card to the DOM in Section 3, make it reusable in Section 4, style it in Section 5, and add interactivity in Section 6.
78
84
 
79
85
  ### Minimal cases
80
86
 
81
87
  Every key is optional. These are all valid TACOs:
82
88
 
83
89
  ```js
84
- { t: 'br' } // self-closing tag
90
+ { t: 'br' } // void element
85
91
  { t: 'h1', c: 'Hello' } // tag + text content
86
92
  { t: 'input', a: { type: 'email', required: true } } // tag + attributes, no content
87
93
  { t: 'div' } // empty div
@@ -89,27 +95,9 @@ Every key is optional. These are all valid TACOs:
89
95
 
90
96
  If `t` is omitted, it defaults to `'div'`.
91
97
 
92
- ### Raw HTML in content — bw.raw()
93
-
94
- By default, bitwrench escapes all content — `<b>bold</b>` renders as the literal text `<b>bold</b>`, not bold text. This prevents XSS and is almost always what you want.
98
+ ### Nesting -- TACOs inside TACOs
95
99
 
96
- When you need actual HTML inside a TACO (line breaks, inline formatting, HTML entities), use `bw.raw()`:
97
-
98
- ```js
99
- // Without bw.raw() — the <br> and <span> are escaped to visible text
100
- { t: 'h1', c: 'Coffee That<br>Tells a <span>Story</span>' }
101
- // Renders: Coffee That&lt;br&gt;Tells a &lt;span&gt;Story&lt;/span&gt;
102
-
103
- // With bw.raw() — the HTML is rendered as-is
104
- { t: 'h1', c: bw.raw('Coffee That<br>Tells a <span class="accent">Story</span>') }
105
- // Renders: Coffee That (line break) Tells a Story (styled)
106
- ```
107
-
108
- `bw.raw()` returns a sentinel object `{ __bw_raw: true, v: str }` — it doesn't modify the string, it marks it. Bitwrench checks for this marker during rendering and skips escaping. Never use `bw.raw()` on user-provided input.
109
-
110
- ### Nesting — TACOs inside TACOs
111
-
112
- Content (`c:`) can be a string, another TACO, or an array of both. This nesting is recursive — TACOs go as deep as your UI requires:
100
+ Content (`c:`) can be a string, another `{taco}`, or an array of both:
113
101
 
114
102
  ```js
115
103
  // A single child
@@ -120,9 +108,12 @@ Content (`c:`) can be a string, another TACO, or an array of both. This nesting
120
108
  { t: 'h2', c: 'Title' },
121
109
  { t: 'p', c: 'Body text' }
122
110
  ]}
111
+ ```
123
112
 
124
- // Three levels deep: page section card button
125
- { t: 'div', a: { class: 'page' }, c: [
113
+ Let's build something more substantial -- a page with a nav and a card:
114
+
115
+ ```js
116
+ var page = { t: 'div', a: { class: 'page' }, c: [
126
117
  { t: 'nav', c: [
127
118
  { t: 'a', a: { href: '/' }, c: 'Home' },
128
119
  { t: 'a', a: { href: '/about' }, c: 'About' }
@@ -130,16 +121,17 @@ Content (`c:`) can be a string, another TACO, or an array of both. This nesting
130
121
  { t: 'section', a: { class: 'content' }, c: [
131
122
  { t: 'div', a: { class: 'card' }, c: [
132
123
  { t: 'h3', c: 'Welcome' },
133
- { t: 'p', c: 'This is three levels deep.' },
124
+ { t: 'p', c: 'This is a card inside a section inside a page.' },
134
125
  { t: 'button', c: 'Click me' }
135
126
  ]}
136
127
  ]}
137
- ]}
138
- ```
128
+ ]};
139
129
 
140
- The HTML equivalent would be around a dozen lines of nested tags with closing tags to match. The TACO is the same structure, but as data you can store in a variable, pass to a function, or build from a loop.
130
+ bw.html(page);
131
+ // => full HTML string with all nested tags
132
+ ```
141
133
 
142
- ### Skipping content nulls and conditionals
134
+ ### Conditionals -- nulls are skipped
143
135
 
144
136
  `null`, `undefined`, and `false` in content arrays are silently skipped. This makes conditional rendering natural:
145
137
 
@@ -147,911 +139,880 @@ The HTML equivalent would be around a dozen lines of nested tags with closing ta
147
139
  var showHeader = true;
148
140
  var isAdmin = false;
149
141
 
150
- { t: 'div', c: [
142
+ var dashboard = { t: 'div', c: [
151
143
  showHeader ? { t: 'h1', c: 'Dashboard' } : null,
152
144
  { t: 'p', c: 'Always visible' },
153
145
  isAdmin ? { t: 'a', c: 'Admin Panel' } : null
154
- ]}
155
- // Renders: <h1>Dashboard</h1><p>Always visible</p>
156
- // The admin link is skipped entirely.
157
- ```
146
+ ]};
158
147
 
159
- ### The fourth key — `o:` options
148
+ bw.html(dashboard);
149
+ // => '<div><h1>Dashboard</h1><p>Always visible</p></div>'
150
+ // The admin link is omitted entirely.
151
+ ```
160
152
 
161
- The `o:` field is where non-HTML concerns live — lifecycle hooks, component state, rendering behavior. It was added to the format because bitwrench-specific metadata shouldn't go in `a:` (that compiles directly to HTML attributes), and the `data-*` attribute namespace was being used inconsistently across libraries. A separate `o:` key keeps the library's concerns cleanly separated from the DOM's, with zero risk of namespace collisions.
153
+ ### Arrays and .map() -- lists from data
162
154
 
163
155
  ```js
164
- {
165
- t: 'div',
166
- a: { class: 'widget' }, // becomes HTML attributes
167
- c: 'Hello', // becomes element content
168
- o: { // → bitwrench-only, never in HTML output
169
- mounted: function(el) { }, // called after element enters the DOM
170
- unmount: function(el) { }, // called before element is removed
171
- state: { count: 0 } // component state (used with o.render)
172
- }
173
- }
156
+ var items = ['Apples', 'Bananas', 'Cherries'];
157
+
158
+ var list = { t: 'ul', c: items.map(function(item) {
159
+ return { t: 'li', c: item };
160
+ })};
161
+
162
+ bw.html(list);
163
+ // => '<ul><li>Apples</li><li>Bananas</li><li>Cherries</li></ul>'
174
164
  ```
175
165
 
176
- We'll cover `o:` in detail in Section 5 (Three Levels of Commitment). For now, just know it's where non-DOM concerns live.
166
+ No `v-for`, no `{#each}`, no special syntax. Just `.map()`.
177
167
 
178
- ---
168
+ ### The TACO shape: `{t, a, c, o}`
179
169
 
180
- ## 2. Styling CSS Is Just Strings
170
+ You have seen `t`, `a`, and `c`. There is a fourth key -- `o` (options) -- for lifecycle hooks, component state, and behavior. We will cover it in Section 7. For now, know the full shape:
181
171
 
182
- You've made a TACO and rendered it. The next question is: how do I style it?
172
+ | Key | Purpose | Goes to HTML? |
173
+ |-----|---------|---------------|
174
+ | `t` | Tag name | Yes |
175
+ | `a` | HTML attributes | Yes |
176
+ | `c` | Content (text, TACOs, arrays) | Yes |
177
+ | `o` | Options (state, lifecycle, handles) | No -- bitwrench only |
183
178
 
184
- Bitwrench doesn't care where your CSS comes from. You can use an external stylesheet, bitwrench's built-in classes, or generate CSS entirely from JavaScript. All three work together. But the JavaScript approach is where bitwrench's philosophy shines — because CSS values are just strings, and strings are something JavaScript handles naturally.
179
+ If you prefer positional arguments: `bw.h('div', {class: 'card'}, 'Hello')` returns the same `{taco}` object. Use whichever is clearer in context.
185
180
 
186
- ### Start simple — inline styles
187
181
 
188
- The `style` attribute in a TACO works exactly like the HTML `style` attribute:
182
+ ## 3. TACO to Live DOM
189
183
 
190
- ```js
191
- { t: 'div',
192
- a: { style: 'padding:1.5rem; background:#f5f5f5; border-radius:12px' },
193
- c: 'A styled box'
194
- }
195
- ```
184
+ We can generate HTML strings, but a live page needs DOM elements.
196
185
 
197
- Nothing new here. But now put that style in a variable:
186
+ ### bw.mount() -- the primary path
198
187
 
199
- ```js
200
- var boxStyle = 'padding:1.5rem; background:#f5f5f5; border-radius:12px';
188
+ `bw.mount()` takes a CSS selector and a `{taco}`. It unmounts and removes the target's existing children, creates the new `{taco}` tree, inserts it as the target's content, and returns the newly created root element:
201
189
 
202
- { t: 'div', a: { style: boxStyle }, c: 'Box one' }
203
- { t: 'div', a: { style: boxStyle }, c: 'Box two' }
190
+ ```html
191
+ <div id="app"></div>
192
+ <script>
193
+ bw.mount('#app', { t: 'h1', c: 'Hello from bitwrench' });
194
+ // The #app div now contains: <h1>Hello from bitwrench</h1>
195
+ </script>
204
196
  ```
205
197
 
206
- Change `boxStyle` once, both boxes update. No Sass variables. No CSS custom properties. Just a JavaScript variable.
198
+ `bw.mount()` returns the root element it created. This is the most common way to render UI.
207
199
 
208
- ### Shared styles across nested TACOs
200
+ Let's mount the page we built in Section 2:
209
201
 
210
202
  ```js
211
- var cardStyle = 'border:1px solid #ddd; border-radius:12px; overflow:hidden';
212
- var headerStyle = 'padding:1rem; background:#336699; color:#fff';
213
- var bodyStyle = 'padding:1.5rem';
203
+ var page = { t: 'div', a: { class: 'page' }, c: [
204
+ { t: 'nav', c: [
205
+ { t: 'a', a: { href: '/' }, c: 'Home' },
206
+ { t: 'a', a: { href: '/about' }, c: 'About' }
207
+ ]},
208
+ { t: 'section', c: [
209
+ { t: 'h3', c: 'Welcome' },
210
+ { t: 'p', c: 'Live in the browser.' }
211
+ ]}
212
+ ]};
214
213
 
215
- { t: 'div', a: { style: cardStyle }, c: [
216
- { t: 'div', a: { style: headerStyle }, c: 'Card Title' },
217
- { t: 'div', a: { style: bodyStyle }, c: 'Card content goes here.' }
218
- ]}
214
+ bw.mount('#app', page);
219
215
  ```
220
216
 
221
- Three style variables, one card. Reuse `cardStyle` on every card across your page.
222
-
223
- ### Base style + overrides
224
-
225
- For simple variations, string concatenation works:
217
+ Continuing our contact card: mounting it is a one-liner.
226
218
 
227
219
  ```js
228
- var base = 'border-radius:12px; padding:1rem; border:1px solid #ddd';
229
-
230
- { t: 'div', a: { style: base + '; background:#e8f5e9' }, c: 'Success' }
231
- { t: 'div', a: { style: base + '; background:#ffebee' }, c: 'Error' }
232
- { t: 'div', a: { style: base + '; background:#e3f2fd' }, c: 'Info' }
220
+ bw.mount('#app', {
221
+ t: 'div', a: { class: 'card' }, c: [
222
+ { t: 'h3', c: 'Alice' },
223
+ { t: 'p', c: 'alice@example.com' }
224
+ ]
225
+ });
226
+ // The #app div now contains a live card element in the DOM.
233
227
  ```
234
228
 
235
- For more complex composition, use objects with `Object.assign`:
229
+ ### bw.create() -- detached elements
236
230
 
237
- ```js
238
- var baseObj = { borderRadius: '12px', padding: '1rem', border: '1px solid #ddd' };
231
+ Sometimes you need a DOM element before inserting it. `bw.create()` returns a detached DOM node:
239
232
 
240
- var success = Object.assign({}, baseObj, { background: '#e8f5e9' });
241
- var error = Object.assign({}, baseObj, { background: '#ffebee' });
242
- var info = Object.assign({}, baseObj, { background: '#e3f2fd' });
233
+ ```js
234
+ var el = bw.create({ t: 'div', c: 'Not in the page yet' });
235
+ // el is an HTMLDivElement, but it is not attached to the document.
236
+ // You can inspect it, modify it, then insert it manually.
237
+ // In practice, prefer bw.mount('#target', taco) instead of manual insertion.
243
238
  ```
244
239
 
245
- This is Sass `@extend` without Sass.
240
+ Use `bw.create()` when you need to manipulate the element before it goes into the page. For most cases, `bw.mount()` is simpler and handles lifecycle automatically.
246
241
 
247
- ### CSS classes — `bw.css()` generates stylesheets from objects
242
+ ### bw.DOM() and bw.el()
248
243
 
249
- When inline styles aren't enough you need pseudo-classes, media queries, or want to reuse styles by class name — generate CSS from JavaScript objects:
244
+ `bw.DOM()` is an exact alias for `bw.mount()`. Use whichever reads better in context.
245
+
246
+ `bw.el()` resolves an element by selector and can optionally update or transform it:
250
247
 
251
248
  ```js
252
- bw.injectCSS(bw.css({
253
- '.card': {
254
- padding: '1.5rem',
255
- borderRadius: '12px',
256
- border: '1px solid #ddd'
257
- },
258
- '.card:hover': {
259
- boxShadow: '0 4px 12px rgba(0,0,0,.1)'
260
- },
261
- '@media (max-width: 768px)': {
262
- '.card': { padding: '0.75rem' }
263
- }
264
- }));
249
+ bw.el('#title'); // find element
250
+ bw.el('#title', 'New text'); // set text content
251
+ bw.el('#app', { t: 'h1', c: 'Hi' }); // mount a {taco}
252
+ bw.el('.card', function(el) { // apply a function
253
+ el.style.opacity = '0.5';
254
+ });
265
255
  ```
266
256
 
267
- `bw.css()` takes a JavaScript object and returns a CSS string. `bw.injectCSS()` inserts it into the document. CamelCase properties (`borderRadius`) auto-convert to kebab-case (`border-radius`). Pseudo-classes (`:hover`, `:focus`, `:active`) and `@media` queries work as top-level keys.
257
+ ### Three output modes, one input
268
258
 
269
- ### CSS variables are just JS variables
259
+ | Function | Returns | Use when |
260
+ |----------|---------|----------|
261
+ | `bw.html(taco)` | HTML string | SSR, Node.js scripts, email templates |
262
+ | `bw.create(taco)` | Detached DOM element | Need to manipulate before inserting |
263
+ | `bw.mount(sel, taco)` | Mounted DOM element | Most common -- render into the page |
270
264
 
271
- ```js
272
- var brand = '#8B4513';
273
- var radius = '12px';
274
- var shadow = '0 4px 12px rgba(0,0,0,.08)';
265
+ The `{taco}` is data. The rendering step is separate. You decide when and how it becomes real.
275
266
 
276
- bw.injectCSS(bw.css({
277
- '.card': { borderRadius: radius, boxShadow: shadow, borderColor: brand },
278
- '.badge': { borderRadius: radius, background: brand, color: '#fff' },
279
- '.btn': { borderRadius: radius, background: brand }
280
- }));
281
- ```
282
267
 
283
- Change `brand` once, every rule that references it updates. No build step, no preprocessor.
268
+ ## 4. JavaScript Makes TACOs Composable
284
269
 
285
- ### Functions generate CSS rules
270
+ Mounting a single `{taco}` is useful, but real pages have many elements. Because `{taco}` objects are plain JavaScript, composition is natural -- every field is a JavaScript expression. This is the most important thing to understand about bitwrench.
271
+
272
+ ### Functions are your components
273
+
274
+ A function that returns a `{taco}` is a component. No class, no decorator, no registration:
286
275
 
287
276
  ```js
288
- function cardStyles(accentColor) {
289
- var shades = bw.deriveShades(accentColor);
290
- return {
291
- background: shades.light,
292
- border: '1px solid ' + shades.border,
293
- color: shades.darkText,
294
- borderRadius: '12px'
295
- };
277
+ function greeting(name) {
278
+ return { t: 'h2', c: 'Hello, ' + name + '!' };
296
279
  }
297
280
 
298
- bw.injectCSS(bw.css({
299
- '.warning-card': cardStyles('#e67e22'),
300
- '.success-card': cardStyles('#27ae60'),
301
- '.info-card': cardStyles('#3498db')
302
- }));
281
+ bw.mount('#app', { t: 'div', c: [
282
+ greeting('Alice'),
283
+ greeting('Bob')
284
+ ]});
303
285
  ```
304
286
 
305
- This is Sass mixins without Sass. And it's more powerful — the function can do arbitrary computation, derive colors algorithmically, or read configuration.
306
-
307
- ### Theme palettes — complete design systems from two colors
287
+ Continuing our card: make it a function, and it works for any contact.
308
288
 
309
289
  ```js
310
- var theme = bw.makeStyles({ primary: '#336699', secondary: '#cc6633' });
311
- bw.applyStyles(theme);
290
+ function contactCard(name, email) {
291
+ return {
292
+ t: 'div', a: { class: 'card' }, c: [
293
+ { t: 'h3', c: name },
294
+ { t: 'p', c: email }
295
+ ]
296
+ };
297
+ }
312
298
 
313
- // theme.palette has every derived color as JS values
314
- bw.injectCSS(bw.css({
315
- '.my-header': {
316
- background: theme.palette.primary.base,
317
- color: theme.palette.primary.textOn,
318
- borderBottom: '3px solid ' + theme.palette.secondary.base
319
- }
320
- }));
299
+ bw.mount('#app', { t: 'div', c: [
300
+ contactCard('Alice', 'alice@example.com'),
301
+ contactCard('Bob', 'bob@example.com')
302
+ ]});
321
303
  ```
322
304
 
323
- `makeStyles()` isn't a black box. It returns the full palette as JavaScript values. You can mix theme-generated CSS with your own `bw.css()` rules using the same colors.
305
+ Same pattern as the `greeting()` function, but now it produces structured content. This is the entire component model.
324
306
 
325
- ### @keyframes and nested at-rules
307
+ ### Component factory pattern
326
308
 
327
- `bw.css()` handles `@media`, `@keyframes`, and all `@`-prefix rules recursively:
309
+ Build reusable components as factory functions with options:
328
310
 
329
311
  ```js
330
- bw.injectCSS(bw.css({
331
- '@keyframes fadeIn': {
332
- '0%': { opacity: '0', transform: 'translateY(-10px)' },
333
- '100%': { opacity: '1', transform: 'translateY(0)' }
334
- },
335
- '.toast': {
336
- animation: 'fadeIn 0.3s ease-out',
337
- padding: '0.75rem 1rem',
338
- borderRadius: '8px'
339
- },
340
- '@media (prefers-reduced-motion: reduce)': {
341
- '.toast': { animation: 'none' }
342
- }
343
- }));
344
- ```
312
+ function colorCard(title, body, color) {
313
+ return {
314
+ t: 'div',
315
+ a: { class: 'card', style: 'border-left:4px solid ' + color },
316
+ c: [
317
+ { t: 'h3', c: title },
318
+ { t: 'p', c: body }
319
+ ]
320
+ };
321
+ }
345
322
 
346
- All `@`-prefix keys are treated as nested blocks. No special syntax needed — it's the same JS object structure used everywhere else.
323
+ bw.mount('#app', { t: 'div', c: [
324
+ colorCard('Warning', 'Disk space low', '#e67e22'),
325
+ colorCard('Success', 'Backup complete', '#27ae60'),
326
+ colorCard('Info', '3 updates available', '#3498db')
327
+ ]});
328
+ ```
347
329
 
348
- ### Style composition — bw.s()
330
+ ### .map() for lists
349
331
 
350
- When inline styles get complex, string concatenation becomes fragile. `bw.s()` merges any number of style objects into a style string:
332
+ Render arrays of data using `.map()`:
351
333
 
352
334
  ```js
353
- // Compose style objects — bw.s() merges them left-to-right
354
- { t: 'div', a: { style: bw.s({ display: 'flex' }, { alignItems: 'center' }, { gap: '1rem' }) }, c: [
355
- { t: 'img', a: { src: 'avatar.png', style: bw.s({ borderRadius: '0.375rem' }, { width: '40px' }) } },
356
- { t: 'span', c: 'Alice' }
357
- ]}
358
-
359
- // Store base styles in variables, merge with custom properties
360
- var cardHeader = bw.s({ display: 'flex' }, { justifyContent: 'space-between' }, { padding: '1rem' }, {
361
- borderBottom: '1px solid #eee',
362
- background: theme.palette.primary.light
363
- });
335
+ var users = [
336
+ { name: 'Alice', role: 'admin' },
337
+ { name: 'Bob', role: 'user' },
338
+ { name: 'Carol', role: 'user' }
339
+ ];
364
340
 
365
- // Conditional styles null/undefined args are skipped
366
- { t: 'div', a: {
367
- style: bw.s({ padding: '1rem' }, isActive ? { fontWeight: '700' } : null, { color: accent })
368
- }, c: 'Status'
369
- }
341
+ bw.mount('#app', { t: 'table', c: [
342
+ { t: 'thead', c: { t: 'tr', c: [
343
+ { t: 'th', c: 'Name' }, { t: 'th', c: 'Role' }
344
+ ]}},
345
+ { t: 'tbody', c: users.map(function(u) {
346
+ return { t: 'tr', c: [
347
+ { t: 'td', c: u.name },
348
+ { t: 'td', c: u.role }
349
+ ]};
350
+ })}
351
+ ]});
370
352
  ```
371
353
 
372
- `bw.s()` skips `null`/`undefined` arguments, so conditional composition works cleanly. It's `Object.assign` for CSS with a string output — runtime-composable. Unlike Tailwind class strings, you can store base styles in variables, merge them with `bw.s()`, and override individual properties.
373
-
374
- ### Responsive breakpoints — bw.responsive()
375
-
376
- `bw.responsive()` generates `@media` rules from a JavaScript object, using bitwrench's standard breakpoints:
354
+ ### Conditionals -- three ways
377
355
 
378
356
  ```js
379
- bw.injectCSS([
380
- bw.css({ '.hero h1': { fontSize: '1.5rem', padding: '1rem' } }),
381
- bw.responsive('.hero h1', {
382
- md: { fontSize: '2.5rem', padding: '2rem' },
383
- xl: { fontSize: '3.5rem' }
384
- })
385
- ].join('\n'));
386
- ```
387
-
388
- The breakpoints (`sm`, `md`, `lg`, `xl`) match bitwrench's grid system. `bw.responsive()` returns a CSS string — join it with `bw.css()` output and pass the combined string to `bw.injectCSS()`.
357
+ // Ternary (inline)
358
+ { t: 'div', c: loggedIn ? 'Welcome back' : 'Please sign in' }
389
359
 
390
- ### Built-in styles
360
+ // null filtering (in arrays)
361
+ { t: 'nav', c: [
362
+ { t: 'a', c: 'Home' },
363
+ isAdmin ? { t: 'a', c: 'Admin' } : null,
364
+ { t: 'a', c: 'About' }
365
+ ]}
391
366
 
392
- Bitwrench ships with Bootstrap-inspired classes (`bw-card`, `bw-btn`, `bw-table`, etc.) that you can load with `bw.loadStyles()`. Use them, ignore them, or override them.
367
+ // IIFE for complex logic
368
+ { t: 'div', c: (function() {
369
+ if (status === 'loading') return { t: 'span', c: 'Loading...' };
370
+ if (status === 'error') return { t: 'span', a: { class: 'error' }, c: errorMsg };
371
+ return results.map(function(r) { return { t: 'li', c: r.name }; });
372
+ })()
373
+ }
374
+ ```
393
375
 
394
- ### Utility shorthand — bw.u() (optional plugin)
376
+ ### Composing larger pages
395
377
 
396
- If you prefer Tailwind-style terse tokens, the `bitwrench-util-css` plugin (~1KB gzipped, loaded separately) adds `bw.u()`:
378
+ Functions compose into full page layouts:
397
379
 
398
380
  ```js
399
- // Returns a style object — composes with bw.s()
400
- bw.u('flex gap4 p4 alignCenter')
401
- // => { display: 'flex', gap: '1rem', padding: '1rem', alignItems: 'center' }
381
+ function makeHeader(title) {
382
+ return { t: 'header', c: { t: 'h1', c: title } };
383
+ }
402
384
 
403
- // Or as a CSS string for inline styles
404
- { t: 'div', a: { style: bw.u.css('flex gap4 p4') }, c: '...' }
385
+ function makeFooter() {
386
+ return { t: 'footer', c: '(c) 2026' };
387
+ }
405
388
 
406
- // Mix shorthand with explicit properties
407
- a: { style: bw.s(bw.u('flex gap4'), { borderBottom: '2px solid ' + accent }) }
389
+ bw.mount('#app', { t: 'div', c: [
390
+ makeHeader('My App'),
391
+ { t: 'main', c: users.map(function(u) {
392
+ return colorCard(u.name, u.role, u.role === 'admin' ? '#e67e22' : '#3498db');
393
+ })},
394
+ makeFooter()
395
+ ]});
408
396
  ```
409
397
 
410
- The scale is `{n} * 0.25rem`, so `p4` = 1rem, `gap8` = 2rem. Tokens cover padding, margin, gap, width, height, flex, alignment, font sizes, and colors (`bg-[#hex]`, `text-[#hex]`). Add custom tokens with `bw.u.extend({ name: styleObj })`.
398
+ No template language needed. JavaScript already has functions (components), arrays (children), and `.map()` (iteration). The TACO format gives these a shape that maps to the DOM.
411
399
 
412
- This is entirely optional — `bw.s()` and `bw.css()` handle everything without it. But for rapid prototyping, shorter token strings mean less typing and (for LLMs) fewer tokens.
413
400
 
414
- ### The key insight
401
+ ## 5. Styling Grows Naturally
415
402
 
416
- Every CSS framework — Sass, Tailwind, CSS-in-JS exists because CSS alone lacks variables, composition, and computation. JavaScript has all three. If your UI is already described in JavaScript objects, then CSS is just another set of string properties on those objects.
403
+ We can compose structure, but unstyled HTML is not a UI. Because styles are just strings and objects in JavaScript, bitwrench handles CSS the same way it handles markup -- with plain JS.
417
404
 
418
- ---
405
+ ### Start simple -- class and style attributes
419
406
 
420
- ## 3. It's Just JavaScript the Core Insight
407
+ The `style` and `class` attributes in a `{taco}` work exactly like their HTML counterparts:
421
408
 
422
- This section is about something you already know but may not have noticed: because a TACO is a JavaScript object literal, every field is a JavaScript expression. This is the most important thing to understand about bitwrench, and the most commonly overlooked.
409
+ ```js
410
+ bw.mount('#app', { t: 'div',
411
+ a: { class: 'card', style: 'padding:1.5rem; background:#f5f5f5; border-radius:12px' },
412
+ c: 'A styled card'
413
+ });
414
+ ```
423
415
 
424
- ### Every value is an expression
416
+ Put the style in a variable and reuse it:
425
417
 
426
418
  ```js
427
- var title = 'Dashboard';
428
- var isAdmin = true;
429
- var items = ['Apples', 'Bananas', 'Cherries'];
419
+ var cardStyle = 'padding:1.5rem; background:#f5f5f5; border-radius:12px';
430
420
 
431
- {
432
- t: isAdmin ? 'h1' : 'h2', // computed tag
433
- a: {
434
- class: 'header ' + (isAdmin ? 'admin' : ''), // computed class
435
- style: 'color:' + (isAdmin ? 'red' : 'black') // computed style
436
- },
437
- c: [
438
- title, // variable as content
439
- ...items.map(function(i) { return { t: 'li', c: i }; }) // .map() → children
440
- ]
441
- }
421
+ bw.mount('#app', { t: 'div', c: [
422
+ { t: 'div', a: { style: cardStyle }, c: 'Card one' },
423
+ { t: 'div', a: { style: cardStyle }, c: 'Card two' }
424
+ ]});
442
425
  ```
443
426
 
444
- This isn't a special feature. This is just how JavaScript object literals work. Bitwrench doesn't add anything here — it just doesn't take it away. JSX requires a compiler to turn markup back into function calls. Template strings lose structure. TACO objects are native JavaScript from start to finish.
445
-
446
- ### Functions as values — two timing modes
427
+ Change `cardStyle` once, and the next time your render function runs, both cards reflect the update. No preprocessor needed.
447
428
 
448
- One of the unusual properties of TACO is that it supports two timing modes in the same object: authoring time and rendering time.
429
+ ### bw.s() -- merge style objects
449
430
 
450
- **Authoring time (IIFE)** the function runs immediately when the object is created. The result is baked in as static data:
431
+ When inline styles get complex, string concatenation becomes fragile. `bw.s()` merges style objects into a style string:
451
432
 
452
433
  ```js
453
- // Content computed when the TACO object is created
454
- { t: 'div', c: (function() {
455
- var data = getExpensiveData();
456
- return data.map(function(d) { return { t: 'p', c: d.summary }; });
457
- })()
458
- }
434
+ var flex = { display: 'flex', alignItems: 'center', gap: '1rem' };
435
+ var padded = { padding: '1rem' };
459
436
 
460
- // Style computed from window size at creation time
461
- { t: 'div', a: {
462
- style: (function() {
463
- var w = window.innerWidth;
464
- return 'padding:' + (w < 768 ? '8px' : '24px');
465
- })()
466
- }, c: 'Responsive without @media'
467
- }
437
+ bw.mount('#app', { t: 'div', a: { style: bw.s(flex, padded) }, c: [
438
+ { t: 'img', a: { src: 'avatar.png', style: bw.s({ borderRadius: '50%', width: '40px' }) } },
439
+ { t: 'span', c: 'Alice' }
440
+ ]});
468
441
  ```
469
442
 
470
- **Rendering time (function reference)** the function is stored as-is and evaluated when bitwrench processes the tree:
443
+ `bw.s()` skips `null` and `undefined` arguments, so conditional composition works cleanly:
471
444
 
472
445
  ```js
473
- // This function runs when bw.createDOM() or bw.DOM() encounters it
474
446
  { t: 'div', a: {
475
- style: function() { return 'opacity:' + getOpacity(); }
476
- }
477
- }
447
+ style: bw.s(
448
+ { padding: '1rem' },
449
+ isActive ? { fontWeight: '700' } : null,
450
+ { color: accent }
451
+ )
452
+ }, c: 'Status' }
478
453
  ```
479
454
 
480
- The distinction matters:
455
+ ### bw.css() and bw.injectCSS() -- generate stylesheets
481
456
 
482
- - **Authoring time** produces data. The TACO is serializable — you can send it over the wire, cache it, render it to an HTML string. This is what bwserve uses to push UI from server to client.
483
- - **Rendering time** produces behavior. The function executes at the moment the UI is rendered. It can access the current window size, user preferences, sensor readings, the current time — anything available in the browser at that moment.
457
+ When you need pseudo-classes, media queries, or reusable class names, generate CSS from JavaScript objects:
484
458
 
485
- Most template systems are either fully static (Mustache, Handlebars) or fully live (React JSX). TACO lets you choose per-field, in the same object. You decide what's fixed and what's deferred by choosing whether to call the function (IIFE) or pass it as a reference.
459
+ ```js
460
+ bw.injectCSS(bw.css({
461
+ '.card': {
462
+ padding: '1.5rem',
463
+ borderRadius: '12px',
464
+ border: '1px solid #ddd'
465
+ },
466
+ '.card:hover': {
467
+ boxShadow: '0 4px 12px rgba(0,0,0,.1)'
468
+ },
469
+ '@media (max-width: 768px)': {
470
+ '.card': { padding: '0.75rem' }
471
+ }
472
+ }));
473
+ ```
486
474
 
487
- ### Composition patterns
475
+ `bw.css()` converts a JavaScript object to a CSS string. CamelCase properties (`borderRadius`) auto-convert to kebab-case (`border-radius`). `bw.injectCSS()` inserts the result into the document.
488
476
 
489
- **Arrays compose content:**
477
+ ### Use JavaScript variables as style tokens
490
478
 
491
479
  ```js
492
- function makeHeader(title) {
493
- return { t: 'header', c: { t: 'h1', c: title } };
494
- }
495
- function makeFooter() {
496
- return { t: 'footer', c: '(c) 2026' };
497
- }
480
+ var brand = '#336699';
481
+ var radius = '12px';
498
482
 
499
- { t: 'div', c: [
500
- makeHeader('My App'),
501
- ...pages[currentPage].sections,
502
- showFooter ? makeFooter() : null
503
- ].filter(Boolean)
504
- }
483
+ bw.injectCSS(bw.css({
484
+ '.card': { borderRadius: radius, borderColor: brand },
485
+ '.badge': { borderRadius: radius, background: brand, color: '#fff' },
486
+ '.btn': { borderRadius: radius, background: brand }
487
+ }));
505
488
  ```
506
489
 
507
- **Object.assign composes attributes:**
490
+ Change `brand` once and re-run `bw.injectCSS(bw.css(...))` -- every rule that references it updates. No build step needed to generate the CSS; just call the functions again.
491
+
492
+ Continuing our card: let's style the contact card with `bw.css()`.
508
493
 
509
494
  ```js
510
- var baseAttrs = { class: 'card', style: 'border-radius:12px' };
511
- var clickable = { onclick: function() { alert('clicked'); }, style: 'cursor:pointer' };
495
+ bw.injectCSS(bw.css({
496
+ '.card': {
497
+ padding: '1.5rem',
498
+ borderRadius: '12px',
499
+ border: '1px solid #ddd',
500
+ maxWidth: '300px'
501
+ },
502
+ '.card h3': { margin: '0 0 0.5rem 0' },
503
+ '.card p': { margin: '0', color: '#666' }
504
+ }));
512
505
 
513
- { t: 'div', a: Object.assign({}, baseAttrs, clickable), c: 'Click me' }
506
+ bw.mount('#app', contactCard('Alice', 'alice@example.com'));
507
+ // Now the card renders with rounded corners, padding, and subtle text.
514
508
  ```
515
509
 
516
- **Functions are your "components":**
510
+ ### Functions generate CSS rules
517
511
 
518
512
  ```js
519
- function colorCard(title, body, color) {
520
- return {
521
- t: 'div',
522
- a: { class: 'card', style: 'border-left:4px solid ' + color },
523
- c: [
524
- { t: 'h3', c: title },
525
- { t: 'p', c: body }
526
- ]
527
- };
513
+ function cardStyles(accentColor) {
514
+ var shades = bw.deriveShades(accentColor);
515
+ return {
516
+ background: shades.light,
517
+ border: '1px solid ' + shades.border,
518
+ color: shades.darkText,
519
+ borderRadius: '12px'
520
+ };
528
521
  }
529
522
 
530
- { t: 'div', c: [
531
- colorCard('Warning', 'Disk space low', '#e67e22'),
532
- colorCard('Success', 'Backup complete', '#27ae60'),
533
- colorCard('Info', '3 updates available', '#3498db')
534
- ]}
523
+ bw.injectCSS(bw.css({
524
+ '.warning-card': cardStyles('#e67e22'),
525
+ '.success-card': cardStyles('#27ae60'),
526
+ '.info-card': cardStyles('#3498db')
527
+ }));
535
528
  ```
536
529
 
537
- You don't need React's component concept, Vue's slots, or Svelte's `{#each}` syntax. JavaScript already has functions (components), arrays (slots), and `.map()` (iteration). TACO just gives these a shape that maps to the DOM.
530
+ This accomplishes what Sass mixins do, but with plain JavaScript functions -- no extra compilation step or grammar to learn.
538
531
 
539
- ### Conditionals three ways
532
+ ### bw.makeStyles() and bw.applyStyles() -- theme and component style system
540
533
 
541
- ```js
542
- // Ternary (inline)
543
- { t: 'div', c: loggedIn ? 'Welcome back' : 'Please sign in' }
534
+ Generate a coherent theme from two seed colors:
544
535
 
545
- // null filtering (in arrays)
546
- { t: 'nav', c: [
547
- { t: 'a', c: 'Home' },
548
- isAdmin ? { t: 'a', c: 'Admin' } : null,
549
- { t: 'a', c: 'About' }
550
- ].filter(Boolean)
551
- }
536
+ ```js
537
+ var theme = bw.makeStyles({ primary: '#336699', secondary: '#cc6633' });
538
+ bw.applyStyles(theme);
552
539
 
553
- // IIFE for complex logic
554
- { t: 'div', c: (function() {
555
- if (status === 'loading') return { t: 'span', c: 'Loading...' };
556
- if (status === 'error') return { t: 'span', a: { class: 'error' }, c: errorMsg };
557
- return resultList.map(function(r) { return { t: 'li', c: r.name }; });
558
- })()
559
- }
540
+ // theme.palette has every derived color as JS values
541
+ bw.injectCSS(bw.css({
542
+ '.my-header': {
543
+ background: theme.palette.primary.base,
544
+ color: theme.palette.primary.textOn,
545
+ borderBottom: '3px solid ' + theme.palette.secondary.base
546
+ }
547
+ }));
560
548
  ```
561
549
 
562
- ### Iteration it's just .map()
550
+ `bw.loadStyles()` is a shorthand that generates and applies in one call:
563
551
 
564
552
  ```js
565
- var users = [
566
- { name: 'Alice', role: 'admin' },
567
- { name: 'Bob', role: 'user' }
568
- ];
569
-
570
- { t: 'table', c: [
571
- { t: 'thead', c: { t: 'tr', c: [
572
- { t: 'th', c: 'Name' }, { t: 'th', c: 'Role' }
573
- ]}},
574
- { t: 'tbody', c: users.map(function(u) {
575
- return { t: 'tr', c: [
576
- { t: 'td', c: u.name },
577
- { t: 'td', c: u.role }
578
- ]};
579
- })}
580
- ]}
553
+ bw.loadStyles({ primary: '#336699', secondary: '#cc6633' });
554
+ // With no arguments, loads structural CSS only (no colors)
555
+ bw.loadStyles();
581
556
  ```
582
557
 
583
- No `v-for`, no `{#each}`, no special key rules. Just JavaScript.
558
+ ### @keyframes and at-rules
584
559
 
585
- ---
560
+ `bw.css()` handles `@media`, `@keyframes`, and all `@`-prefix rules recursively:
586
561
 
587
- ## 4. The BCCL: Ready-Made Components
562
+ ```js
563
+ bw.injectCSS(bw.css({
564
+ '@keyframes fadeIn': {
565
+ '0%': { opacity: '0', transform: 'translateY(-10px)' },
566
+ '100%': { opacity: '1', transform: 'translateY(0)' }
567
+ },
568
+ '.toast': {
569
+ animation: 'fadeIn 0.3s ease-out',
570
+ padding: '0.75rem 1rem',
571
+ borderRadius: '8px'
572
+ }
573
+ }));
574
+ ```
588
575
 
589
- ### What BCCL is and why it exists
576
+ ### The styling ladder
590
577
 
591
- BCCL (Bitwrench Common Component Library) is a set of factory functions that return TACO objects for common UI patterns — cards, buttons, navbars, tables, forms, modals, alerts, and more. Think of it as Bootstrap or shadcn/ui, but instead of HTML templates you get plain JavaScript objects.
578
+ | Need | Tool | Example |
579
+ |------|------|---------|
580
+ | One-off inline style | `a: { style: '...' }` | Quick prototyping |
581
+ | Composed inline styles | `bw.s(obj1, obj2)` | Reusable style objects |
582
+ | Class-based CSS | `bw.css()` + `bw.injectCSS()` | Pseudo-classes, media queries |
583
+ | Theme + component styles | `bw.makeStyles()` + `bw.applyStyles()` | Consistent theming from seed colors |
592
584
 
593
- The point: you can build a complete, styled page without writing a single line of CSS or HTML. Load the default styles, call the factories, render. Great for quick UIs, prototyping, embedded device interfaces, and internal tools.
585
+ Start at the top. Move down when you need more power. Each level builds on the one before.
594
586
 
595
- Three things to know about BCCL:
596
587
 
597
- 1. **Every factory returns a TACO object.** The output is a plain `{t, a, c, o}` object. You can inspect it, modify any part, nest it, or pass it to any function that takes a TACO.
598
- 2. **There are no tricks.** BCCL factories are regular functions that construct TACO objects. They don't use private APIs or special rendering paths. Anything a BCCL factory does, you can do by hand.
599
- 3. **BCCL is optional.** You can use it for everything, use it selectively, or ignore it entirely and build your own components from scratch.
588
+ ## 6. Events and Behavior
600
589
 
601
- ### Factories return TACO, not DOM
590
+ A styled card looks right but does nothing. Adding behavior means adding event handler functions to `a:` -- the same place every other attribute lives.
602
591
 
603
- ```js
604
- var card = bw.makeCard({ title: 'Users', content: '42 online' });
605
- // card is { t:'div', a:{class:'bw-card'}, c:[...] }
592
+ ### Inline event handlers
606
593
 
607
- var page = { t: 'div', c: [
608
- bw.makeNavbar({ brand: 'My App', items: [
609
- { text: 'Home', href: '#' },
610
- { text: 'About', href: '#about' }
611
- ]}),
612
- { t: 'div', a: { class: 'content' }, c: [
613
- bw.makeAlert({ content: 'Welcome!', variant: 'success' }),
614
- card
615
- ]},
616
- bw.makeTable({ data: users, sortable: true })
617
- ]};
594
+ Event handlers go in `a:` as function values:
618
595
 
619
- bw.DOM('#app', page);
596
+ ```js
597
+ bw.mount('#app', {
598
+ t: 'button',
599
+ a: { onclick: function() { alert('Clicked!'); } },
600
+ c: 'Click me'
601
+ });
620
602
  ```
621
603
 
622
- ### Quick inventory
604
+ All standard DOM events work: `onclick`, `onchange`, `oninput`, `onsubmit`, `onkeydown`, etc.
623
605
 
624
- | Category | Components |
625
- |----------|-----------|
626
- | Layout | makeNavbar, makeContainer, makeRow, makeCol, makeStack, makeSection |
627
- | Content | makeCard, makeAlert, makeBadge, makeStatCard, makeTimeline, makeHero |
628
- | Forms | makeInput, makeSelect, makeTextarea, makeForm, makeFormGroup, makeSearchInput |
629
- | Data | makeTable, makeTableFromArray, makeBarChart, makeProgress, makePagination |
630
- | Interactive | makeButton, makeAccordion, makeTabs, makeModal, makeCarousel, makeTooltip, makeDropdown |
631
-
632
- See `docs/component-library.md` for full signatures and options.
633
-
634
- ### Mix BCCL with your own TACOs
606
+ Continuing our card: add a click handler that copies the email address.
635
607
 
636
608
  ```js
637
- { t: 'div', c: [
638
- bw.makeCard({ title: 'Stats' }),
639
- { t: 'div', a: { class: 'custom-widget', style: 'padding:2rem' }, c: [
640
- { t: 'h3', c: 'Custom Section' },
641
- { t: 'p', c: 'Hand-written TACO next to a BCCL card.' }
642
- ]}
643
- ]}
609
+ function contactCard(name, email) {
610
+ return {
611
+ t: 'div', a: {
612
+ class: 'card',
613
+ style: 'cursor:pointer',
614
+ onclick: function() { alert('Email: ' + email); }
615
+ },
616
+ c: [
617
+ { t: 'h3', c: name },
618
+ { t: 'p', c: email }
619
+ ]
620
+ };
621
+ }
644
622
  ```
645
623
 
646
- ### Modifying BCCL output
624
+ The handler closes over `email` -- no data binding, no state management. Just a closure.
625
+
626
+ ### Re-rendering on interaction
647
627
 
648
- Since BCCL returns plain objects, you can modify them before rendering:
628
+ Combine event handlers with `bw.mount()` to build interactive UIs without any state framework:
649
629
 
650
630
  ```js
651
- var card = bw.makeCard({ title: 'Users', content: '42 online' });
652
- card.a.style = 'border-left:4px solid #336699';
653
- card.c.push({ t: 'small', c: 'Updated 5m ago' });
654
- bw.DOM('#app', card);
655
- ```
631
+ var filter = 'all';
632
+ var items = [
633
+ { name: 'Widget A', type: 'widget' },
634
+ { name: 'Gadget B', type: 'gadget' },
635
+ { name: 'Widget C', type: 'widget' }
636
+ ];
656
637
 
657
- ---
638
+ function renderApp() {
639
+ var filtered = filter === 'all'
640
+ ? items
641
+ : items.filter(function(i) { return i.type === filter; });
642
+
643
+ bw.mount('#app', { t: 'div', c: [
644
+ { t: 'div', c: ['all', 'widget', 'gadget'].map(function(f) {
645
+ return {
646
+ t: 'button',
647
+ a: {
648
+ onclick: function() { filter = f; renderApp(); },
649
+ style: 'margin:0.25rem; font-weight:' + (filter === f ? '700' : '400')
650
+ },
651
+ c: f
652
+ };
653
+ })},
654
+ { t: 'ul', c: filtered.map(function(i) {
655
+ return { t: 'li', c: i.name };
656
+ })}
657
+ ]});
658
+ }
658
659
 
659
- ## 5. Three Levels of Commitment
660
+ renderApp();
661
+ ```
660
662
 
661
- | Level | What you get | What you write | When to use |
662
- |-------|-------------|---------------|-------------|
663
- | **0 -- Data** | A plain JS object | `makeCard({...})` or `{t,a,c}` | Static content, SSR, data-driven lists |
664
- | **1 -- DOM** | A rendered DOM tree | `bw.DOM('#x', taco)` | One-shot renders, manual re-renders |
665
- | **2 -- Stateful** | A reactive component | `o.state` + `o.render` + `bw.update()` | State that changes, re-rendering UI |
663
+ This is the simplest interactive pattern: a render function that calls `bw.mount()`. When data changes, call the function again. No framework magic.
666
664
 
667
- Most of your UI should be Level 0. Only escalate when you need interactivity. Level 0 TACOs are composable, serializable, and free. Level 2 components have overhead — use them for the parts that actually change.
665
+ ### Handling form data
668
666
 
669
- ### Level 0 pure data
667
+ `bw.$()` returns an array of matching DOM elements (like `querySelectorAll` but always an array), so we use `[0]` to get the first match:
670
668
 
671
669
  ```js
672
- var listing = {
673
- t: 'div', a: { class: 'products' },
674
- c: products.map(function(p) {
675
- return {
676
- t: 'div', a: { class: 'card' }, c: [
677
- { t: 'h3', c: p.name },
678
- { t: 'p', c: '$' + p.price.toFixed(2) }
679
- ]
680
- };
681
- })
682
- };
683
-
684
- bw.DOM('#products', listing); // render once, done
670
+ bw.mount('#app', {
671
+ t: 'form',
672
+ a: { onsubmit: function(e) {
673
+ e.preventDefault();
674
+ var name = bw.$('#name-input')[0].value;
675
+ var email = bw.$('#email-input')[0].value;
676
+ console.log('Submitted:', name, email);
677
+ }},
678
+ c: [
679
+ { t: 'input', a: { id: 'name-input', type: 'text', placeholder: 'Name' } },
680
+ { t: 'input', a: { id: 'email-input', type: 'email', placeholder: 'Email' } },
681
+ { t: 'button', a: { type: 'submit' }, c: 'Submit' }
682
+ ]
683
+ });
685
684
  ```
686
685
 
687
- ### Level 1 — render and re-render
688
686
 
689
- ```js
690
- function renderClock() {
691
- bw.DOM('#clock', { t: 'div', c: new Date().toLocaleTimeString() });
692
- }
693
- setInterval(renderClock, 1000);
694
- renderClock();
695
- ```
687
+ ## 7. Lifecycle: `o:` Options
688
+
689
+ Event handlers give us interactivity, but real components need state that persists across renders and cleanup logic for resources. That is what the `o:` (options) key provides.
696
690
 
697
- You own the render loop. Call `bw.DOM()` whenever you want. Simple, explicit, good enough for many use cases.
691
+ The `o:` key is where non-HTML concerns live. It was separated from `a:` because `a:` compiles directly to HTML attributes -- library metadata should not leak into the DOM.
698
692
 
699
- ### Level 2 -- stateful TACO with o.state + o.render
693
+ Bitwrench supports two broad update styles. Use `o.render` with `bw.refresh()` when a component should rebuild its contents from state. Use handles, slots, patching, or replacement when a component can update a specific part of the DOM directly. Pub/sub is available for communication between components that should not hold direct references to one another.
694
+
695
+ ### o.state -- component state
700
696
 
701
697
  ```js
702
- var counter = {
703
- t: 'div',
704
- o: {
705
- state: { count: 0 },
706
- render: function(el) {
707
- var s = el._bw_state;
708
- bw.DOM(el, {
709
- t: 'div', c: [
710
- { t: 'span', c: 'Count: ' + s.count },
711
- bw.makeButton({ text: '+1', onclick: function() {
712
- s.count++;
713
- bw.update(el);
714
- }}),
715
- bw.makeButton({ text: 'Reset', onclick: function() {
716
- s.count = 0;
717
- bw.update(el);
718
- }})
719
- ]
720
- });
698
+ bw.mount('#app', {
699
+ t: 'div',
700
+ o: {
701
+ state: { count: 0 },
702
+ render: function(el, state) {
703
+ bw.mount(el, { t: 'div', c: [
704
+ { t: 'span', c: 'Count: ' + state.count },
705
+ { t: 'button', a: { onclick: function() {
706
+ state.count++;
707
+ bw.refresh(el);
708
+ }}, c: '+1' },
709
+ { t: 'button', a: { onclick: function() {
710
+ state.count = 0;
711
+ bw.refresh(el);
712
+ }}, c: 'Reset' }
713
+ ]});
714
+ }
721
715
  }
722
- }
723
- };
724
-
725
- bw.DOM('#app', counter);
716
+ });
726
717
  ```
727
718
 
728
- When state changes, call `bw.update(el)` to re-invoke the render function. The component owns its update logic -- event handlers modify `el._bw_state` directly and trigger re-render.
719
+ `o.state` is assigned by reference to `el._bw_state` at creation time (not cloned). `o.render` is called with `(el, state)` on mount and on every `bw.refresh(el)`. When state changes, mutate the state object and call `bw.refresh(el)` to re-render.
729
720
 
730
- ### When to use which level
721
+ Continuing our card: let's add an expanded/collapsed state to the contact card.
722
+
723
+ ```js
724
+ function contactCard(name, email, phone) {
725
+ return {
726
+ t: 'div', a: { class: 'card' },
727
+ o: {
728
+ state: { expanded: false },
729
+ render: function(el, state) {
730
+ bw.mount(el, { t: 'div', c: [
731
+ { t: 'h3', a: { onclick: function() {
732
+ state.expanded = !state.expanded;
733
+ bw.refresh(el);
734
+ }, style: 'cursor:pointer' }, c: name + (state.expanded ? ' \u25B2' : ' \u25BC') },
735
+ { t: 'p', c: email },
736
+ state.expanded ? { t: 'p', c: phone } : null
737
+ ]});
738
+ }
739
+ }
740
+ };
741
+ }
731
742
 
743
+ bw.mount('#app', contactCard('Alice', 'alice@example.com', '+1-555-0100'));
744
+ // Clicking the name toggles the phone number.
732
745
  ```
733
- Is the content static or computed once from data?
734
- => Level 0. Use make*() or hand-write TACO. Render with bw.DOM().
735
746
 
736
- Does the content change, but you control when?
737
- => Level 1. Call bw.DOM() again when data changes.
747
+ The card now has real state. The render function receives `(el, state)`, reads `state.expanded`, and conditionally shows the phone number. `bw.refresh(el)` re-renders when state changes.
738
748
 
739
- Does the content change in response to user interaction or external events,
740
- and you want structured state management?
741
- => Level 2. Use o.state + o.render + bw.update().
742
- ```
749
+ ### o.mounted and o.unmount -- lifecycle hooks
743
750
 
744
- ---
751
+ ```js
752
+ {
753
+ t: 'div', a: { class: 'sensor-display' },
754
+ o: {
755
+ mounted: function(el) {
756
+ // Called after the element enters the DOM.
757
+ // Good for: observers, timers, third-party library init.
758
+ el._resizeObs = new ResizeObserver(function(entries) {
759
+ console.log('Resized:', entries[0].contentRect.width);
760
+ });
761
+ el._resizeObs.observe(el);
762
+ },
763
+ unmount: function(el) {
764
+ // Called before the element is removed from the DOM.
765
+ // Good for: cleanup of observers, timers, connections.
766
+ if (el._resizeObs) el._resizeObs.disconnect();
767
+ }
768
+ }
769
+ }
770
+ ```
745
771
 
746
- ## 6. Events and Communication
772
+ Use `mounted` for setup that needs the real DOM element -- observers, measuring dimensions, initializing third-party libraries. Use `unmount` to clean up.
747
773
 
748
- ### Event handlers use onclick, not o.mounted
774
+ **Automatic cleanup:** bitwrench runs a document-level MutationObserver that detects when elements are removed from the DOM -- even by third-party code or raw DOM operations. When a component disappears, its `unmount` hooks fire and bitwrench releases its lifecycle registrations and pub/sub subscriptions so that it does not retain the removed subtree. You do not need to detect DOM removal manually -- declare cleanup in `o.unmount`, and bitwrench invokes it when the component leaves the DOM.
749
775
 
750
- > **Warning: Never attach event handlers in `o.mounted`.** When a stateful component re-renders (after `bw.update()`), the old DOM children are replaced. Any listeners attached via `addEventListener` in `o.mounted` are silently lost -- no error, no warning. The click handler simply stops working after the first state change. This is the most common mistake new bitwrench developers make.
776
+ **Prefer event handlers in `a:`.** Listeners attached directly to child nodes during `o.mounted` will be lost if those children are replaced by `bw.refresh()`. Handlers in `a:` are re-attached automatically on every render. Reserve imperative listeners in `o.mounted` for integrations that require them (e.g. window-level events), and clean them up in `o.unmount`:
751
777
 
752
778
  ```js
753
- // CORRECT onclick in attributes. Re-attached automatically on every render.
754
- bw.makeButton({
755
- text: 'Save',
756
- onclick: function() { save(); }
757
- })
758
-
759
- // CORRECT — inline in TACO
779
+ // CORRECT -- handler in attributes, survives re-renders
760
780
  { t: 'button', a: { onclick: function() { save(); } }, c: 'Save' }
761
781
 
762
- // WRONG handler silently lost when component re-renders
782
+ // WRONG -- handler lost after first re-render
763
783
  { t: 'button', c: 'Save',
764
784
  o: { mounted: function(el) { el.addEventListener('click', save); } }
765
785
  }
766
786
  ```
767
787
 
768
- `onclick` (and `onchange`, `oninput`, `onsubmit`, etc.) in `a:` is the only safe event pattern for components that re-render. Bitwrench re-attaches attribute handlers on every render automatically.
769
-
770
- **When is `o.mounted` appropriate?** Only for non-event setup that needs the actual DOM element reference — IntersectionObserver, ResizeObserver, third-party library initialization, measuring element dimensions. Never for click/input/change handlers.
788
+ ### o.handle -- component methods
771
789
 
772
- ### Cross-component communication pub/sub
790
+ `o.handle` attaches named methods to `el.bw`. These are the cheap update path -- surgical DOM updates without tearing down and rebuilding:
773
791
 
774
792
  ```js
775
- // Publisher (cart module)
776
- function addToCart(item) {
777
- cart.push(item);
778
- bw.pub('cart:updated', { count: cart.length });
779
- }
793
+ var counterEl;
780
794
 
781
- // Subscriber (navbar badge) -- auto-cleans when element is removed
782
- bw.sub('cart:updated', function(data) {
783
- navbarEl._bw_state.cartCount = data.count;
784
- bw.update(navbarEl);
785
- }, navbarEl);
786
- ```
787
-
788
- `bw.pub()` and `bw.sub()` are app-wide -- not scoped to the DOM tree. Any component can publish, any component can subscribe. Pass the element as the third argument to `bw.sub()` to tie the subscription lifetime to that element (auto-cleaned on `bw.cleanup()`).
789
-
790
- ### Component handles -- o.handle and o.slots
795
+ var counter = {
796
+ t: 'div', c: [
797
+ { t: 'span', a: { class: 'count' }, c: '0' },
798
+ { t: 'button', a: { onclick: function() {
799
+ counterEl.bw.increment();
800
+ }}, c: '+' }
801
+ ],
802
+ o: {
803
+ handle: {
804
+ increment: function(el) {
805
+ var span = el.querySelector('.count');
806
+ span.textContent = String(Number(span.textContent) + 1);
807
+ },
808
+ reset: function(el) {
809
+ el.querySelector('.count').textContent = '0';
810
+ }
811
+ }
812
+ }
813
+ };
791
814
 
792
- When you need to update part of a rendered component without re-rendering the whole thing -- change a title, advance a carousel, read a form value -- use component handles.
815
+ counterEl = bw.mount('#app', counter);
816
+ counterEl.bw.increment(); // updates count without re-rendering the tree
817
+ counterEl.bw.reset(); // same -- surgical, targeted
818
+ ```
793
819
 
794
- **When to use handles vs pub/sub:** Pub/sub is for decoupled cross-component messaging ("something happened, anyone who cares can react"). Handles are for direct imperative control of a specific element ("carousel, go to slide 3"). If you have a reference to the element, use handles. If you don't know who should respond, use pub/sub.
820
+ ### o.slots -- auto-generated setters and getters
795
821
 
796
- **o.handle** attaches named methods to `el.bw`:
822
+ `o.slots` maps names to CSS selectors. Bitwrench auto-generates `el.bw.setName()` and `el.bw.getName()` for each:
797
823
 
798
824
  ```js
799
- var widget = {
800
- t: 'div', c: [
801
- { t: 'span', a: { class: 'count' }, c: '0' },
802
- { t: 'button', a: { onclick: function() { /* ... */ } }, c: '+' }
803
- ],
804
- o: {
805
- handle: {
806
- increment: function(el) {
807
- var span = el.querySelector('.count');
808
- span.textContent = String(Number(span.textContent) + 1);
809
- },
810
- reset: function(el) {
811
- el.querySelector('.count').textContent = '0';
812
- }
825
+ var card = {
826
+ t: 'div', a: { class: 'card' }, c: [
827
+ { t: 'h3', a: { class: 'card-title' }, c: 'Initial Title' },
828
+ { t: 'div', a: { class: 'card-body' }, c: 'Initial content' }
829
+ ],
830
+ o: {
831
+ slots: {
832
+ title: '.card-title',
833
+ body: '.card-body'
834
+ }
813
835
  }
814
- }
815
836
  };
816
837
 
817
- var el = bw.mount('#app', widget);
818
- el.bw.increment(); // updates the count without re-rendering the whole component
819
- el.bw.reset();
820
- ```
821
-
822
- **o.slots** auto-generates setters and getters for named content areas:
823
-
824
- ```js
825
- var card = bw.makeCard({ title: 'Stats', content: '0' });
826
838
  var el = bw.mount('#app', card);
827
- el.bw.setTitle('Revenue'); // update just the title
828
- el.bw.setContent({ t: 'b', c: '$42k' }); // accepts TACO objects
829
- el.bw.getTitle(); // returns 'Revenue'
839
+ el.bw.setTitle('Revenue'); // update just the title
840
+ el.bw.setBody({ t: 'b', c: '$42,000' }); // accepts TACOs
841
+ el.bw.getTitle(); // returns 'Revenue'
830
842
  ```
831
843
 
832
- Slot setters update only the targeted child element. Input focus, scroll position, and animation state in sibling elements are preserved -- this is the key advantage over a full `bw.update()` re-render.
844
+ Slot setters update only the targeted element. Input focus, scroll position, and animation state in siblings are preserved -- this is the key advantage over `bw.refresh()`.
833
845
 
834
- **bw.mount()** is the entry point: it works like `bw.DOM()` but returns the root element so you can access `el.bw`. Use `bw.message(selector, action, data)` when you don't have a direct reference.
846
+ ### Why o: is separate from a:
835
847
 
836
- All BCCL factories (`makeCard`, `makeCarousel`, `makeTabs`, `makeAccordion`, `makeModal`, `makeProgress`, `makeChipInput`, `makeStatCard`) include handles and/or slots. See [Component Library](component-library.md) for the full method table.
848
+ | Key | Compiles to HTML | Purpose |
849
+ |-----|-----------------|---------|
850
+ | `a:` | Yes | HTML attributes: class, id, style, onclick, data-*, href, src, etc. |
851
+ | `o:` | No | Bitwrench concerns: state, render, handle, slots, mounted, unmount |
837
852
 
838
- ---
853
+ If `o:` properties went in `a:`, they would appear as HTML attributes in the rendered output -- or worse, in `bw.html()` string output. The separation keeps framework metadata out of generated HTML and makes the serializable subset of a `{taco}` easier to identify. (A `{taco}` is serializable when it contains only JSON-compatible values -- no event-handler functions in `a:`, no lifecycle or render functions in `o:`, no DOM references.)
839
854
 
840
- ## 7. Server-Driven UI (bwserve)
855
+ ### The update cost spectrum
841
856
 
842
- ### The idea
857
+ | Operation | Cost | What happens |
858
+ |-----------|------|-------------|
859
+ | `el.bw.method()` | Surgical | Component updates its own DOM |
860
+ | Slot setters (`el.bw.setTitle()`) | Targeted | Replaces content at a cached DOM target |
861
+ | `bw.message(ref, action, data)` | Dispatch | Calls `el.bw[action](data)` by selector |
862
+ | `bw.update(ref, data)` | Dispatch | Calls `el.bw.update(data)`, warns if missing |
863
+ | `bw.patch(id, content)` | Targeted | Updates a single element's content |
864
+ | `bw.replace(ref, taco)` | Element swap | Unmounts old, mounts new at same position |
865
+ | `bw.refresh(ref)` | Full rebuild | Unmounts children, re-renders from `o.render` |
843
866
 
844
- Any server that can write JSON to an HTTP response can drive a bitwrench UI. The server sends TACO objects over SSE (Server-Sent Events). The browser renders them. No client-side application logic required.
867
+ Prefer methods at the top of this list. Use `bw.refresh()` only when you need a full re-render.
845
868
 
846
- ```
847
- Server (any language) Browser
848
- | |
849
- |-- SSE: {replace, #app, taco} --> bw.apply() --> DOM update
850
- |-- SSE: {patch, #counter, "42"} -> targeted text update
851
- |-- SSE: {append, #log, taco} ---> new child added
852
- | |
853
- |<-- POST: {action: "click"} --+ user interaction
854
- ```
855
-
856
- ### Initial UI delivery
857
-
858
- The server sends a `replace` message with a TACO that becomes the page content:
869
+ ### Cross-component communication -- pub/sub
859
870
 
860
871
  ```js
861
- import bwserve from 'bitwrench/bwserve';
872
+ // Publisher
873
+ function addToCart(item) {
874
+ cart.push(item);
875
+ bw.pub('cart:updated', { count: cart.length });
876
+ }
862
877
 
863
- var app = bwserve.create({ port: 8080 });
864
- app.page('/', function(client) {
865
- client.render('#app', {
866
- t: 'div', c: [
867
- { t: 'h1', c: 'Hello from the server' },
868
- { t: 'p', a: { id: 'status' }, c: 'Connected.' },
869
- { t: 'button', a: { 'data-bw-action': 'greet' }, c: 'Say hello' }
870
- ]
871
- });
872
- });
873
- app.listen();
878
+ // Subscriber -- navEl is the element returned by bw.mount() for a nav component
879
+ // Auto-cleans when element is removed
880
+ bw.sub('cart:updated', function(data) {
881
+ navEl._bw_state.cartCount = data.count;
882
+ bw.refresh(navEl);
883
+ }, navEl);
874
884
  ```
875
885
 
876
- The browser receives one HTML page (the "shell") with bitwrench loaded. Everything after that arrives as JSON messages over SSE.
886
+ `bw.pub()` and `bw.sub()` are app-wide. Pass the element as the third argument to `bw.sub()` to tie the subscription lifetime to that element.
877
887
 
878
- ### Incremental updates from server
879
888
 
880
- ```js
881
- client.patch('#status', 'Processing...');
882
- client.append('#log', { t: 'div', c: 'Event at ' + new Date().toISOString() });
883
- client.remove('#old-notification');
884
- client.batch([
885
- { type: 'patch', target: '#status', content: 'Done.' },
886
- { type: 'remove', target: '#spinner' }
887
- ]);
888
- ```
889
-
890
- ### Client events back to server
889
+ ## 8. BCCL: Ready-Made Components
891
890
 
892
- When a user clicks a `data-bw-action` element, the browser POSTs the action name to the server:
893
-
894
- ```js
895
- client.on('greet', function(data) {
896
- client.patch('#status', 'Hello, user!');
897
- });
898
- ```
891
+ Building every component from scratch teaches you the model, but for common patterns -- cards, tables, modals, alerts -- bitwrench ships ready-made factories.
899
892
 
900
- ### Server-side lifecycle register and call
893
+ BCCL (Bitwrench Common Component Library) is a set of factory functions that return `{taco}` objects for common UI patterns. Think of it as Bootstrap, but instead of HTML templates you get JavaScript objects.
901
894
 
902
- Register JavaScript functions on the client, then invoke them by name:
895
+ ### Factories return TACOs, not DOM
903
896
 
904
897
  ```js
905
- client.register('showAlert', 'function(msg) { alert(msg); }');
906
- client.call('showAlert', 'Server says hi!');
907
-
908
- // Built-in calls
909
- client.call('scrollTo', '#section-2');
910
- client.call('redirect', '/dashboard');
911
- ```
912
-
913
- ### Addressing modes
914
-
915
- Target any element by CSS selector or UUID:
898
+ var card = bw.makeCard({ title: 'Users', content: '42 online' });
899
+ // card is { t:'div', a:{class:'bw_bccl_card'}, c:[...] }
900
+ // It is a plain {taco} -- inspect it, modify it, nest it.
916
901
 
917
- ```js
918
- client.patch('#my-id', 'by ID');
919
- client.patch('.status-bar', 'by class');
920
- client.patch('[data-role="header"]', 'by attribute');
921
-
922
- // UUID for stable addressing of dynamic content
923
- var itemId = bw.uuid('item');
924
- client.render('#list', { t: 'div', a: { class: itemId }, c: 'Dynamic item' });
925
- client.patch('.' + itemId, 'Updated content');
902
+ bw.mount('#app', { t: 'div', c: [
903
+ bw.makeNavbar({ brand: 'My App', items: [
904
+ { text: 'Home', href: '#' },
905
+ { text: 'About', href: '#about' }
906
+ ]}),
907
+ { t: 'div', a: { class: 'content' }, c: [
908
+ bw.makeAlert({ content: 'Welcome!', variant: 'success' }),
909
+ card
910
+ ]},
911
+ bw.makeTable({ data: users, sortable: true })
912
+ ]});
926
913
  ```
927
914
 
928
- ### Why this matters
929
-
930
- - **Language-agnostic**: any server that writes SSE can do this — Python, Go, Rust, C, shell scripts.
931
- - **LLMs**: an AI can emit TACO objects directly — orders of magnitude fewer tokens than HTML/JSX.
932
- - **Embedded**: an ESP32 serves one HTML page with bitwrench, then pushes sensor data as patches over SSE.
933
- - **Replaces Streamlit/Gradio**: same server-driven pattern, not locked to Python, full TACO composition model.
934
- - **Relaxed JSON**: bwserve accepts unquoted keys, single quotes, trailing commas — convenient for embedded C code.
935
-
936
- ---
915
+ ### Quick inventory
937
916
 
938
- ## 8. Routing
917
+ | Category | Components |
918
+ |----------|-----------|
919
+ | Layout | makeNavbar, makeContainer, makeRow, makeCol, makeStack, makeSection |
920
+ | Content | makeCard, makeAlert, makeBadge, makeStatCard, makeTimeline, makeHero |
921
+ | Forms | makeInput, makeSelect, makeTextarea, makeForm, makeFormGroup, makeSearchInput |
922
+ | Data | makeTable, makeTableFromArray, makeBarChart, makeProgress, makePagination |
923
+ | Interactive | makeButton, makeAccordion, makeTabs, makeModal, makeCarousel, makeTooltip, makeDropdown |
939
924
 
940
- Bitwrench includes a built-in client-side router. It maps URLs to view functions, supports hash mode and History API mode, and integrates with pub/sub.
925
+ See `docs/component-library.md` for full signatures.
941
926
 
942
- ### Basic SPA routing
927
+ ### Mix BCCL with hand-written TACOs
943
928
 
944
929
  ```js
945
- bw.router({
946
- target: '#app',
947
- routes: {
948
- '/': function() { return { t: 'h1', c: 'Home' }; },
949
- '/about': function() { return { t: 'h1', c: 'About' }; },
950
- '/users/:id': function(params) {
951
- return bw.makeCard({ title: 'User ' + params.id });
952
- },
953
- '*': function() { return { t: 'h1', c: '404 Not Found' }; }
954
- }
955
- });
930
+ bw.mount('#app', { t: 'div', c: [
931
+ bw.makeCard({ title: 'Stats' }),
932
+ { t: 'div', a: { style: 'padding:2rem' }, c: [
933
+ { t: 'h3', c: 'Custom Section' },
934
+ { t: 'p', c: 'Hand-written {taco} next to a BCCL card.' }
935
+ ]}
936
+ ]});
956
937
  ```
957
938
 
958
- The router reads the current URL, matches a route, calls the handler, and mounts the returned TACO into `#app`. It listens for URL changes and re-renders automatically.
939
+ ### Modify BCCL output before rendering
959
940
 
960
- ### Programmatic navigation
941
+ Since BCCL returns plain objects, you can modify them:
961
942
 
962
943
  ```js
963
- bw.navigate('/users/123');
964
- bw.navigate('/about', { replace: true }); // replace history entry
944
+ var card = bw.makeCard({ title: 'Users', content: '42 online' });
945
+ card.a.style = 'border-left:4px solid #336699';
946
+ card.c.push({ t: 'small', c: 'Updated 5m ago' });
947
+ bw.mount('#app', card);
965
948
  ```
966
949
 
967
- ### Navigation links
950
+ ### BCCL components include handles and slots
968
951
 
969
- ```js
970
- // bw.link() returns a TACO <a> with onclick wired to bw.navigate()
971
- bw.link('/about', 'About Us', { class: 'nav-item' })
972
- ```
973
-
974
- ### Route parameters and query strings
952
+ The stateful and interactive BCCL components (cards, tables, tabs, modals, toasts, accordions, and others) wire up `o.handle` and/or `o.slots` automatically. Purely structural factories (rows, containers, buttons) return plain TACOs with no handles -- they don't need any:
975
953
 
976
954
  ```js
977
- // /users/42?tab=posts
978
- '/users/:id': function(params) {
979
- params.id; // '42'
980
- params._query.tab; // 'posts'
981
- }
982
-
983
- // /docs/api/colors (catch-all)
984
- '/docs/*': function(params) {
985
- params._rest; // 'api/colors'
986
- }
955
+ var el = bw.mount('#app', bw.makeCard({ title: 'Stats', content: '0' }));
956
+ el.bw.setTitle('Revenue');
957
+ el.bw.setContent({ t: 'b', c: '$42k' });
987
958
  ```
988
959
 
989
- ### Guards and hooks
960
+ See [Component Library](component-library.md) for the full method table per component.
990
961
 
991
- ```js
992
- bw.router({
993
- target: '#app',
994
- routes: { ... },
995
- before: function(to, from) {
996
- if (to === '/admin' && !loggedIn) return '/login'; // redirect
997
- if (to === '/locked') return false; // block
998
- },
999
- after: function(to, from) {
1000
- window.scrollTo(0, 0);
1001
- }
1002
- });
1003
- ```
962
+ ### Three things to know
1004
963
 
1005
- ### Pub/sub integration
964
+ 1. Every factory returns a `{taco}`. The output is a plain `{t, a, c, o}` object.
965
+ 2. There are no tricks. BCCL factories are regular functions. Anything they do, you can do by hand.
966
+ 3. BCCL is optional. Use it for everything, use it selectively, or ignore it entirely.
1006
967
 
1007
- Every route change publishes `bw:route`:
1008
968
 
1009
- ```js
1010
- bw.sub('bw:route', function(data) {
1011
- // data.path, data.params, data.query, data.from
1012
- navEl.bw.setActive(data.path);
1013
- }, navEl);
1014
- ```
969
+ ## 9. Routing, Utilities, Advanced
1015
970
 
1016
- ### Hash vs history mode
971
+ Sections 2-8 cover the core model. This section covers the remaining tools bitwrench provides: routing, declared dataflow, color utilities, and general-purpose helpers.
1017
972
 
1018
- Hash mode (default): URLs like `#/users/123`. Works everywhere, no server config needed.
973
+ ### Client-side routing
1019
974
 
1020
- History mode: URLs like `/users/123`. Requires SPA fallback on the server.
975
+ Bitwrench includes a built-in client-side router:
1021
976
 
1022
977
  ```js
1023
- bw.router({ mode: 'history', base: '/app', target: '#app', routes: { ... } });
1024
- ```
1025
-
1026
- ### When you don't need routing
978
+ bw.router({
979
+ target: '#app',
980
+ routes: {
981
+ '/': function() { return { t: 'h1', c: 'Home' }; },
982
+ '/about': function() { return { t: 'h1', c: 'About' }; },
983
+ '/users/:id': function(params) {
984
+ return bw.makeCard({ title: 'User ' + params.id });
985
+ },
986
+ '*': function() { return { t: 'h1', c: '404 Not Found' }; }
987
+ }
988
+ });
1027
989
 
1028
- Many bitwrench apps are single-page dashboards where tab-switching is enough:
990
+ // Programmatic navigation
991
+ bw.navigate('/users/123');
1029
992
 
1030
- ```js
1031
- bw.DOM('#app', bw.makeTabs({
1032
- tabs: [
1033
- { label: 'Overview', content: makeOverview() },
1034
- { label: 'Analytics', content: makeAnalytics() }
1035
- ]
1036
- }));
993
+ // Navigation links (returns {taco})
994
+ bw.link('/about', 'About Us', { class: 'nav-item' })
1037
995
  ```
1038
996
 
1039
- ### Server-side routing with bwserve
997
+ Route parameters (`/users/:id`), query strings (`params._query.tab`), catch-all routes (`/docs/*` with `params._rest`), guards (`before`/`after` hooks), and hash vs. history mode are all supported. See [Routing Guide](routing.md) for the full API.
1040
998
 
1041
- The client router complements bwserve's `app.page()`:
999
+ ### bw.derive() -- declared dataflow
1000
+
1001
+ `bw.derive()` recomputes a derived value when its input topics publish:
1042
1002
 
1043
1003
  ```js
1044
- app.page('/', function(client) { client.render('#app', makeHomePage()); });
1045
- app.page('/dashboard', function(client) { client.render('#app', makeDashboard()); });
1004
+ bw.derive(['cart:updated', 'discount:changed'], function(cartData, discountData) {
1005
+ var total = cartData.total * (1 - discountData.rate);
1006
+ return { total: total };
1007
+ }, 'order:total');
1008
+
1009
+ // Subscribes automatically; fires when either input publishes
1010
+ bw.sub('order:total', function(data) {
1011
+ bw.el('#total', '$' + data.total.toFixed(2));
1012
+ });
1046
1013
  ```
1047
1014
 
1048
- See [Routing Guide](routing.md) for the full API reference, patterns, and examples.
1049
-
1050
- ---
1051
-
1052
- ## 9. Utilities and Color Functions
1053
-
1054
- Bitwrench includes utility functions that show up regularly in UI work. These aren't the main attraction, but they eliminate common boilerplate.
1015
+ The combiner function receives the latest value from each input topic as positional arguments, in the same order as the `inputs` array. The dependency graph is explicit and written in source code -- not assembled by getter traps at runtime.
1055
1016
 
1056
1017
  ### Color functions
1057
1018
 
@@ -1061,345 +1022,261 @@ bw.hslToHex([210, 50, 40]); // '#336699'
1061
1022
  bw.adjustLightness('#336699', 20); // lighten by 20%
1062
1023
  bw.mixColor('#336699', '#cc6633', 0.5); // blend two colors
1063
1024
  bw.textOnColor('#336699'); // '#fff' (contrast-safe text color)
1064
- bw.relativeLuminance('#336699'); // WCAG 2.0 luminance value
1065
1025
  bw.deriveShades('#336699'); // { base, hover, active, light, darkText, border, focus, textOn }
1066
- bw.derivePalette({ primary: '#336699', secondary: '#cc6633' }); // full 9-group palette
1026
+ bw.derivePalette({ primary: '#336699', secondary: '#cc6633' }); // full palette
1067
1027
  ```
1068
1028
 
1069
- `deriveShades()` and `derivePalette()` are the building blocks behind `makeStyles()`. You can use them directly for custom color systems.
1070
-
1071
- ### URL and data utilities
1029
+ ### Utility functions
1072
1030
 
1073
1031
  ```js
1074
- bw.getURLParam('page', 'home'); // read ?page=... from URL, default 'home'
1075
- bw.typeOf(x); // enhanced typeof: 'array', 'null', 'date', etc.
1076
- bw.uuid('widget'); // 'bw_widget_a3f2c1' (unique ID with prefix)
1032
+ bw.typeOf([1, 2, 3]); // 'array' (enhanced typeof)
1033
+ bw.uuid('widget'); // 'bw_uuid_widget_a3f2c1' (unique ID)
1034
+ bw.escapeHTML('<script>'); // '&lt;script&gt;'
1035
+ bw.getURLParam('page', 'home'); // read ?page=... from URL
1077
1036
  bw.loremIpsum(200); // 200 characters of placeholder text
1078
- bw.random(1, 100); // random integer
1079
- bw.random(5, 1, 100); // array of 5 random integers
1080
- bw.mapScale(75, 0, 100, 0, 255); // map value between ranges (191.25)
1037
+ bw.mapScale(75, 0, 100, 0, 255); // map between ranges (191.25)
1081
1038
  bw.clip(150, 0, 100); // clamp to range (100)
1082
1039
  bw.naturalCompare('item2', 'item10'); // natural sort comparison
1040
+ bw.parseJSONFlex("{ name: 'Alice' }"); // flexible JSON (unquoted keys, single quotes, r-prefix)
1083
1041
  ```
1084
1042
 
1085
- ### File I/O
1043
+ ### Raw HTML -- bw.raw()
1044
+
1045
+ By default, bitwrench escapes all content to prevent XSS. When you need actual HTML inside a `{taco}`, use `bw.raw()`:
1086
1046
 
1087
1047
  ```js
1088
- // Browser save/load via download dialog or FileReader
1089
- bw.saveClientFile('report.txt', content);
1090
- bw.saveClientJSON('data.json', obj);
1091
- bw.loadClientFile(function(data) { /* file contents */ });
1092
- bw.loadClientJSON(function(obj) { /* parsed JSON */ });
1048
+ // Without bw.raw() -- <br> is escaped to visible text
1049
+ { t: 'h1', c: 'Line One<br>Line Two' }
1050
+ // Renders: Line One&lt;br&gt;Line Two
1093
1051
 
1094
- // Node.js same API names, uses fs
1095
- bw.loadLocalFile('config.json').then(function(data) { /* ... */ });
1096
- bw.saveLocalFile('output.txt', content);
1052
+ // With bw.raw() -- HTML rendered as-is
1053
+ { t: 'h1', c: bw.raw('Line One<br>Line Two') }
1054
+ // Renders: Line One (line break) Line Two
1097
1055
  ```
1098
1056
 
1099
- ### Relaxed JSON
1057
+ Never use `bw.raw()` on user-provided input.
1100
1058
 
1101
- Standard JSON requires double-quoted keys and no trailing commas. Bitwrench's relaxed JSON parser is more forgiving:
1059
+ ### File I/O
1102
1060
 
1103
1061
  ```js
1104
- bw.parseRJSON("{ name: 'Alice', age: 30, }");
1105
- // { name: 'Alice', age: 30 }
1062
+ // Browser
1063
+ bw.saveClientFile('report.txt', content);
1064
+ bw.loadClientJSON(function(obj) { /* parsed JSON */ });
1065
+
1066
+ // Node.js
1067
+ bw.loadClientFile('config.json', function(data) { /* ... */ });
1106
1068
  ```
1107
1069
 
1108
- Unquoted keys, single quotes, trailing commas — all accepted. Especially useful for embedded systems where producing strict JSON is awkward, and for bwserve protocol messages from simple scripts.
1109
1070
 
1110
- ---
1071
+ ## 10. What Bitwrench Does Instead
1111
1072
 
1112
- ## 10. Putting It All Together Patterns
1073
+ | You might expect | What bitwrench does |
1074
+ |------------------|---------------------|
1075
+ | Virtual DOM + diffing | Components update their own DOM directly via handles and cached slots, without a virtual-tree walk or full component rebuild |
1076
+ | CSS purging / tree-shaking | `bw.css()` generates only the rules you write -- nothing unused exists to purge |
1077
+ | SSR hydration | `bw.html()` renders TACOs to strings in Node; `bw.mount()` renders to DOM in the browser -- same input, two outputs |
1078
+ | Build step / bundler | Load via `<script>` tag, CDN, or ESM `import` -- works without tooling, benefits from it optionally |
1079
+ | Automatic state tracking | Explicit updates via methods, `bw.refresh()`, and pub/sub -- all wiring is visible in source |
1080
+ | TypeScript required | Ships `dist/bitwrench.d.ts` for full type checking -- supported, never required. See [TypeScript Usage Guide](bitwrench_typescript_usage.md) |
1113
1081
 
1114
- ### Static page composition
1115
1082
 
1116
- ```js
1117
- bw.loadStyles();
1118
- bw.loadStyles({ primary: '#336699', secondary: '#cc6633' });
1083
+ ## 11. Server-Driven UI and CLI
1119
1084
 
1120
- bw.DOM('#app', [
1121
- bw.makeNavbar({ brand: 'Acme', items: [
1122
- { text: 'Home', href: '#' }, { text: 'About', href: '#about' }
1123
- ]}),
1124
- makeHeroSection(data.hero),
1125
- makeFeatureGrid(data.features),
1126
- bw.makeTable({ data: data.pricing, sortable: true }),
1127
- makeFooter()
1128
- ]);
1129
- ```
1085
+ Everything in Sections 1-10 runs in the browser. But a `{taco}` without function values in `o:` is pure data -- it serializes to JSON. This means any program, in any language, can generate `{taco}` objects and send them to a browser for rendering. Bitwrench ships two tools that build on this property.
1130
1086
 
1131
- ### Data-driven filtered list
1087
+ ### bwserve -- server-driven UI over SSE
1132
1088
 
1133
- ```js
1134
- var allItems = [/* ... */];
1135
- var filter = 'all';
1089
+ bwserve is a protocol that turns the browser into a display and input surface for a program running elsewhere. The server pushes `{taco}` objects and patches over Server-Sent Events; user interactions come back as named actions.
1136
1090
 
1137
- function renderList() {
1138
- var items = filter === 'all' ? allItems : allItems.filter(function(i) {
1139
- return i.type === filter;
1140
- });
1141
- bw.DOM('#list', { t: 'div', c: items.map(function(i) {
1142
- return { t: 'div', a: { class: 'item' }, c: [
1143
- { t: 'h3', c: i.name },
1144
- { t: 'p', c: i.description }
1145
- ]};
1146
- })});
1147
- }
1148
-
1149
- bw.DOM('#filters', { t: 'div', c: ['all', 'widget', 'gadget'].map(function(f) {
1150
- return bw.makeButton({
1151
- text: f,
1152
- variant: filter === f ? 'primary' : 'outline-secondary',
1153
- onclick: function() { filter = f; renderList(); }
1154
- });
1155
- })});
1156
- renderList();
1091
+ ```
1092
+ Server (any language) Browser
1093
+ | |
1094
+ |-- SSE: {replace, #app, taco} --> bw.apply() --> DOM update
1095
+ |-- SSE: {patch, #counter, "42"} -> targeted text update
1096
+ |-- SSE: {append, #log, taco} ---> new child added
1097
+ | |
1098
+ |<-- POST: {action: "click"} ---+ user clicks bw_act_*
1157
1099
  ```
1158
1100
 
1159
- Level 1 -- you own the render loop. No stateful TACO needed.
1160
-
1161
- ### Reactive component with state
1101
+ A minimal server in Node.js:
1162
1102
 
1163
1103
  ```js
1164
- bw.DOM('#contact', {
1165
- t: 'div',
1166
- o: {
1167
- state: { statusMsg: '', showStatus: false },
1168
- render: function(el) {
1169
- var s = el._bw_state;
1170
- bw.DOM(el, {
1104
+ import bwserve from 'bitwrench/bwserve';
1105
+
1106
+ var app = bwserve.create({ port: 8080 });
1107
+ app.page('/', function(client) {
1108
+ client.mount('#app', {
1171
1109
  t: 'div', c: [
1172
- s.showStatus ? { t: 'div', c: s.statusMsg } : null,
1173
- bw.makeForm({ children: [
1174
- bw.makeFormGroup({ label: 'Email', input: bw.makeInput({ type: 'email', id: 'email' }) }),
1175
- bw.makeFormGroup({ label: 'Message', input: bw.makeTextarea({ id: 'msg', rows: 4 }) }),
1176
- bw.makeButton({ text: 'Send', type: 'submit', variant: 'primary' })
1177
- ], onsubmit: function(e) {
1178
- e.preventDefault();
1179
- s.statusMsg = 'Sent!';
1180
- s.showStatus = true;
1181
- bw.update(el);
1182
- }})
1110
+ { t: 'h1', c: 'Hello from the server' },
1111
+ { t: 'p', a: { id: 'status' }, c: 'Connected.' },
1112
+ { t: 'button', a: { class: 'bw_act_greet' }, c: 'Say hello' }
1183
1113
  ]
1184
- });
1185
- }
1186
- }
1114
+ });
1187
1115
  });
1116
+ app.listen();
1188
1117
  ```
1189
1118
 
1190
- ### Cross-component coordination
1119
+ The browser receives one HTML shell page with bitwrench loaded. Everything after that arrives as JSON messages over SSE.
1191
1120
 
1192
- ```js
1193
- var cartBadge = {
1194
- t: 'span',
1195
- o: {
1196
- state: { count: 0 },
1197
- mounted: function(el) {
1198
- bw.sub('cart:updated', function(d) {
1199
- el._bw_state.count = d.count;
1200
- bw.update(el);
1201
- }, el);
1202
- },
1203
- render: function(el) {
1204
- bw.DOM(el, { t: 'span', c: 'Cart (' + el._bw_state.count + ')' });
1205
- }
1206
- }
1207
- };
1121
+ ### Incremental updates
1208
1122
 
1209
- function addToCart(item) {
1210
- cart.push(item);
1211
- bw.pub('cart:updated', { count: cart.length, items: cart });
1212
- }
1123
+ ```js
1124
+ client.patch('#status', { text: 'Processing...' });
1125
+ client.append('#log', { t: 'div', c: 'Event at ' + new Date().toISOString() });
1126
+ client.remove('#old-notification');
1127
+ client.batch([
1128
+ { type: 'patch', ref: '#status', text: 'Done.', v: 1 },
1129
+ { type: 'remove', ref: '#spinner', v: 1 }
1130
+ ]);
1213
1131
  ```
1214
1132
 
1215
- ### Theme + custom CSS
1133
+ ### Client events with bw_act_*
1134
+
1135
+ In Sections 6-7, every event handler was a local JavaScript function (`onclick: function() { ... }`). In server-driven mode, there is a second path: the click is forwarded to the server instead. Add a `bw_act_*` CSS class to any element, and when the user clicks it, the browser POSTs the action name to the server. No `onclick` handler is needed on the client:
1216
1136
 
1217
1137
  ```js
1218
- var theme = bw.makeStyles({ primary: '#336699', secondary: '#cc6633' });
1219
- bw.applyStyles(theme);
1220
- var accent = theme.palette.secondary.base;
1221
- var accentLight = theme.palette.secondary.light;
1138
+ // Client-side {taco} (no onclick handler needed)
1139
+ { t: 'button', a: { class: 'bw_act_greet' }, c: 'Say hello' }
1222
1140
 
1223
- bw.injectCSS(bw.css({
1224
- '.hero': {
1225
- background: 'linear-gradient(135deg, ' + accent + ', ' + accentLight + ')',
1226
- padding: '4rem 2rem', color: '#fff'
1227
- },
1228
- '.hero h1': { fontSize: 'clamp(2rem, 5vw, 3.5rem)' }
1229
- }));
1141
+ // Server-side handler
1142
+ client.on('greet', function(data) {
1143
+ client.patch('#status', { text: 'Hello, user!' });
1144
+ });
1230
1145
  ```
1231
1146
 
1232
- ### Ephemeral UI (toasts, notifications)
1147
+ In a fully server-driven application, domain logic can remain on the server. The browser renders messages and relays named user actions back. Local handlers and server actions can coexist in the same page; the `{taco}` shape is the same either way.
1233
1148
 
1234
- ```js
1235
- // Add a toast container to your page layout (once)
1236
- bw.DOM('#app', [
1237
- { t: 'div', a: { id: 'toast-container',
1238
- style: 'position:fixed;top:1rem;right:1rem;z-index:9999' } },
1239
- // ... rest of your page
1240
- ]);
1149
+ ### bwcli -- file conversion and pipe server
1241
1150
 
1242
- // Show a toast by appending to the container
1243
- function showToast(message, variant) {
1244
- var toastId = bw.uuid('toast');
1245
- var toast = {
1246
- t: 'div', a: { class: toastId, style: 'min-width:280px;margin-bottom:0.5rem' },
1247
- c: bw.makeAlert({ content: message, variant: variant || 'info', dismissible: true })
1248
- };
1249
- var container = bw.$('#toast-container')[0];
1250
- if (container) {
1251
- container.appendChild(bw.createDOM(toast));
1252
- setTimeout(function() {
1253
- var el = bw.$('.' + toastId)[0];
1254
- if (el) { bw.cleanup(el); el.remove(); }
1255
- }, 3500);
1256
- }
1257
- }
1258
- showToast('Item added to cart', 'success');
1151
+ `bwcli` is a command-line tool that converts files to styled standalone HTML pages and acts as a bridge between any language and the bwserve protocol.
1152
+
1153
+ ```bash
1154
+ # Convert Markdown to a self-contained HTML page
1155
+ bwcli README.md -o index.html --standalone
1156
+
1157
+ # Apply a theme preset
1158
+ bwcli doc.md -o doc.html --standalone --theme ocean
1159
+
1160
+ # Pipe server -- any language becomes a bwserve backend
1161
+ bwcli serve --port 8080 --input-port 9000
1162
+ curl -X POST http://localhost:9000 -d '{"type":"patch","ref":"temp","content":"23.5 C"}'
1259
1163
  ```
1260
1164
 
1261
- The toast container is part of the TACO tree. Individual toasts append into it and auto-remove after a delay. `bw.cleanup()` ensures any lifecycle hooks are properly torn down.
1165
+ With `bwcli serve`, a Python script, a shell loop, or a C program on a microcontroller can push UI updates to connected browsers by POSTing JSON to the pipe server's input port. No JavaScript on the server side at all.
1262
1166
 
1263
- ### Dashboard card theme tokens + bw.s() + responsive + component
1167
+ ### When to use bwserve
1264
1168
 
1265
- This compact example combines the key patterns: theme palette tokens (no hardcoded hex), `bw.s()` for inline style composition, `bw.responsive()` for breakpoints, and Level 1 re-rendering for live-updating stat cards.
1169
+ - **Language-agnostic**: any server that writes SSE can drive the UI -- Python, Go, Rust, C, shell scripts.
1170
+ - **LLM-native**: an AI emits `{taco}` JSON directly -- potentially more compact and easier to validate than generated HTML or JSX.
1171
+ - **Embedded**: an ESP32 serves one HTML page with bitwrench, then pushes sensor data as patches. C macros ship in `embedded_c/`.
1172
+ - **Streamlit/Gradio-style applications**: the same broad server-driven pattern, but language-neutral and based on the full TACO composition model.
1266
1173
 
1267
- ```js
1268
- var theme = bw.makeStyles({ primary: '#1e40af', secondary: '#059669' });
1269
- bw.applyStyles(theme);
1270
- var P = theme.palette;
1174
+ See the [bwserve docs](bwserve.md) for the full protocol, the [CLI docs](cli.md) for all flags and options, and the [ESP32 tutorial](tutorial-embedded.md) for a complete embedded walkthrough.
1271
1175
 
1272
- // Responsive grid — base stacks, md goes 2-col, lg goes 4-col
1273
- bw.injectCSS(bw.css({
1274
- '.stat-grid': { display: 'grid', gap: '1rem', marginBottom: '1.5rem' }
1275
- }));
1276
- bw.injectCSS(bw.responsive('.stat-grid', {
1277
- base: { gridTemplateColumns: '1fr' },
1278
- md: { gridTemplateColumns: 'repeat(2, 1fr)' },
1279
- lg: { gridTemplateColumns: 'repeat(4, 1fr)' }
1280
- }));
1281
1176
 
1282
- // Stat cards — palette tokens, not hex literals
1283
- var metrics = { users: 2847, revenue: 48920, orders: 384, rate: 3.2 };
1177
+ ## 12. Quick Reference
1284
1178
 
1285
- function renderStats() {
1286
- bw.DOM('#stats', { t: 'div', a: { class: 'stat-grid' }, c: [
1287
- bw.makeStatCard({ value: metrics.users.toLocaleString(), label: 'Users', variant: 'primary' }),
1288
- bw.makeStatCard({ value: '$' + metrics.revenue.toLocaleString(), label: 'Revenue', variant: 'success' }),
1289
- bw.makeStatCard({ value: metrics.orders.toString(), label: 'Orders', variant: 'info' }),
1290
- bw.makeStatCard({ value: metrics.rate + '%', label: 'Conversion' })
1291
- ]});
1292
- }
1179
+ The tutorial ends here. The following tables are a compact lookup reference for the APIs introduced above.
1293
1180
 
1294
- // Layout uses bw.s() — no inline style strings
1295
- bw.DOM('#app', { t: 'div', c: [
1296
- { t: 'div', a: { style: bw.s({ display: 'flex' }, { justifyContent: 'space-between' },
1297
- { alignItems: 'center' }, { background: P.primary.base, color: '#fff', padding: '1.5rem 2rem' }) },
1298
- c: [
1299
- { t: 'h1', a: { style: bw.s({ margin: '0', fontSize: '1.5rem' }) }, c: 'Dashboard' },
1300
- { t: 'span', a: { style: bw.s({ opacity: '0.8', fontSize: '0.85rem' }) }, c: 'Live' }
1301
- ]
1302
- },
1303
- { t: 'div', a: { id: 'stats', style: bw.s({ padding: '1rem' }, { maxWidth: '1200px', margin: '0 auto' }) } }
1304
- ]});
1181
+ ### The lifecycle
1305
1182
 
1306
- renderStats();
1307
- setInterval(function() {
1308
- metrics.users += Math.round(Math.random() * 20 - 5);
1309
- metrics.revenue += Math.round(Math.random() * 500 - 100);
1310
- renderStats();
1311
- }, 3000);
1183
+ ```
1184
+ define -> create -> hydrate -> mount -> update -> unmount
1312
1185
  ```
1313
1186
 
1314
- Key things this example proves:
1315
- - **No hardcoded hex in layout** -- colors come from `theme.palette`
1316
- - **No inline style strings** — `bw.s({ display: 'flex' }, { padding: '1rem' }, ...)` composes style objects
1317
- - **Responsive without media queries in HTML** — `bw.responsive()` generates `@media` CSS
1318
- - **Re-render is just calling `bw.DOM()` again** — Level 1, no framework magic
1187
+ Each phase has one verb. In practice, create+hydrate are fused for efficiency. Convenience verbs (`bw.mount`, `bw.append`, `bw.replace`, `bw.remove`, `bw.refresh`) compose phase verbs -- they never reimplement them.
1319
1188
 
1320
- ---
1189
+ ### Core rendering
1321
1190
 
1322
- ## 11. What Bitwrench Doesn't Do
1191
+ | Function | What it does |
1192
+ |----------|-------------|
1193
+ | `bw.html(taco)` | `{taco}` to HTML string |
1194
+ | `bw.create(taco)` | `{taco}` to detached, hydrated DOM element |
1195
+ | `bw.mount(sel, taco)` | Mount `{taco}` into existing element; returns root element |
1196
+ | `bw.DOM(sel, taco)` | Alias for `bw.mount()` |
1197
+ | `bw.el(sel, apply)` | Find element by selector; optionally apply content/function |
1198
+ | `bw.h(tag, attrs, c, o)` | `{taco}` constructor from positional args |
1199
+ | `bw.raw(str)` | Mark string as pre-escaped HTML |
1323
1200
 
1324
- | Feature | Why not | What to use instead |
1325
- |---------|---------|-------------------|
1326
- | TypeScript types | Ships `dist/bitwrench.d.ts` with full type declarations | See [TypeScript Usage Guide](bitwrench_typescript_usage.md) |
1327
- | Virtual DOM | Targeted patches via UUID refs are sufficient | `bw.patch()`, `o.render` + `bw.update()` |
1328
- | CSS purging | You generate only what you use via `bw.css()` | N/A |
1329
- | SSR hydration | `bw.html()` for SSR, `bw.DOM()` for client | Full page render via `bw.html()` in Node |
1330
- | Module bundling | No build step required | `<script>` tag, CDN, or ESM `import` |
1201
+ ### Lifecycle verbs (atomic)
1331
1202
 
1332
- ---
1203
+ These are low-level lifecycle primitives. Most applications only need `bw.mount()`, `bw.refresh()`, and `bw.unmount()`.
1333
1204
 
1334
- ## 12. Quick Reference
1205
+ | Function | What it does |
1206
+ |----------|-------------|
1207
+ | `bw.create(taco)` | Build hydrated, detached DOM from `{taco}` |
1208
+ | `bw.hydrate(el, taco)` | Wire lifecycle from `{taco}` onto existing DOM node |
1209
+ | `bw.mountTree(el)` | Register inserted subtree; fire `mounted` hooks |
1210
+ | `bw.unmount(el)` | Tear down subtree lifecycle; fire `unmount` hooks |
1211
+ | `bw.unmountChildren(el)` | Unmount descendants only; element untouched |
1212
+ | `bw.detach(el)` | Remove from document, keep lifecycle intact (keep-alive) |
1335
1213
 
1336
- ### Core rendering
1214
+ ### Convenience verbs (compound)
1337
1215
 
1338
1216
  | Function | What it does |
1339
1217
  |----------|-------------|
1340
- | `bw.html(taco)` | TACO to HTML string |
1341
- | `bw.createDOM(taco)` | TACO to detached DOM element |
1342
- | `bw.DOM(sel, taco)` | Mount TACO into existing element |
1343
- | `bw.h(tag, attrs, c, o)` | TACO constructor returns plain `{t,a,c,o}` from positional args |
1344
- | `bw.raw(str)` | Mark string as pre-escaped HTML |
1218
+ | `bw.mount(sel, taco)` | = `unmountChildren` + `create` + insert + `mountTree` |
1219
+ | `bw.append(target, taco)` | Add child without touching existing; returns new element |
1220
+ | `bw.replace(ref, taco)` | Swap element at DOM position; returns new element |
1221
+ | `bw.remove(ref)` | = `unmount` + `el.remove()` |
1222
+ | `bw.refresh(ref)` | = `unmountChildren` + re-render + `mountTree` (the heavy path) |
1345
1223
 
1346
1224
  ### CSS
1347
1225
 
1348
1226
  | Function | What it does |
1349
1227
  |----------|-------------|
1350
- | `bw.css(rules)` | JS object to CSS string (supports `@media`, `@keyframes` recursively) |
1228
+ | `bw.css(rules)` | JS object to CSS string (supports `@media`, `@keyframes`) |
1351
1229
  | `bw.injectCSS(css)` | Insert CSS string into document |
1352
1230
  | `bw.s(...styles)` | Merge style objects into a style string |
1353
1231
  | `bw.responsive(sel, bp)` | Generate responsive `@media` CSS from breakpoint object |
1354
- | `bw.loadStyles()` | Load built-in structural CSS |
1355
- | `bw.makeStyles(cfg)` | Generate styled CSS from seed colors (returns styles object) |
1356
- | `bw.applyStyles(styles)` | Inject generated styles' CSS into the document |
1357
- | `bw.loadStyles(cfg)` | Generate and apply styles in one call |
1358
- | `bw.toggleStyles()` | Switch between primary and alternate palettes |
1232
+ | `bw.loadStyles(cfg?)` | Generate + apply styles (no args = structural CSS only) |
1233
+ | `bw.makeStyles(cfg)` | Generate styles from seed colors (returns styles object) |
1234
+ | `bw.applyStyles(styles)` | Inject generated styles into document |
1235
+ | `bw.toggleThemeMode(scope?)` | Switch between primary and alternate palettes |
1359
1236
  | `bw.clearStyles()` | Remove injected styles |
1360
1237
 
1361
- ### State (Level 2)
1238
+ ### State and updates
1362
1239
 
1363
1240
  | Function | What it does |
1364
1241
  |----------|-------------|
1365
- | `o.state` | Initial state object (copied to `el._bw_state`) |
1366
- | `o.render(el, state)` | Render function called on mount and `bw.update()` |
1367
- | `bw.update(el)` | Re-invoke `el._bw_render(el, el._bw_state)` |
1368
- | `bw.patch(uuid, content)` | Update a single UUID-addressed element |
1369
- | `bw.cleanup(el)` | Run unmount hooks, clear subscriptions |
1242
+ | `o.state` | Initial state object (assigned by reference to `el._bw_state`) |
1243
+ | `o.render(el, state)` | Render function; called on mount and `bw.refresh()` |
1244
+ | `bw.refresh(ref)` | Re-invoke render -- tears down and rebuilds children |
1245
+ | `bw.update(ref, data)` | Dispatch to `el.bw.update(data)` -- never falls back to rebuild |
1246
+ | `bw.patch(id, content)` | Update addressed element's content |
1370
1247
 
1371
- ### Component Handles
1248
+ ### Component handles
1372
1249
 
1373
1250
  | Function | What it does |
1374
1251
  |----------|-------------|
1375
- | `o.handle` | Object of methods attached to `el.bw` on createDOM |
1376
- | `o.slots` | `{name: '.selector'}` -- auto-generates `el.bw.setName()` / `el.bw.getName()` |
1377
- | `bw.mount(sel, taco)` | Like DOM() but returns the root element for `el.bw` access |
1378
- | `bw.message(target, action, data)` | Dispatch to `el.bw[action](data)` by id, UUID, or selector |
1252
+ | `o.handle` | Object of methods attached to `el.bw` at create time |
1253
+ | `o.slots` | `{name: '.selector'}` -- auto-generates `el.bw.setName()`/`getName()` |
1254
+ | `bw.mount(sel, taco)` | Returns root element for `el.bw` access |
1255
+ | `bw.message(target, action, data)` | Dispatch to `el.bw[action](data)` by selector |
1379
1256
 
1380
1257
  ### Communication
1381
1258
 
1382
1259
  | Function | What it does |
1383
1260
  |----------|-------------|
1384
- | `bw.pub(topic, data)` | Publish to all subscribers (exact + wildcard) |
1385
- | `bw.sub(topic, fn)` | Subscribe (returns unsub function; supports wildcard `'ns:*'`) |
1386
- | `bw.sub(topic, fn, owner)` | Subscribe with auto-cleanup when owner is removed |
1387
- | `bw.once(topic, fn, el?)` | One-shot subscribe (auto-unsub after first fire) |
1261
+ | `bw.pub(topic, data)` | Publish to all subscribers |
1262
+ | `bw.sub(topic, fn, owner?)` | Subscribe (with optional auto-cleanup on unmount) |
1263
+ | `bw.once(topic, fn, el?)` | One-shot subscribe |
1264
+ | `bw.derive(inputs, fn, output)` | Declared dataflow between topics |
1388
1265
 
1389
1266
  ### Routing
1390
1267
 
1391
1268
  | Function | What it does |
1392
1269
  |----------|-------------|
1393
1270
  | `bw.router(config)` | Create and start a client-side router |
1394
- | `bw.navigate(path, opts)` | Programmatic navigation (delegates to active router) |
1395
- | `bw.link(path, content, attrs)` | Returns TACO `<a>` with navigation wired |
1271
+ | `bw.navigate(path, opts)` | Programmatic navigation |
1272
+ | `bw.link(path, content, attrs)` | Returns `{taco}` `<a>` with navigation wired |
1396
1273
 
1397
1274
  ### Color
1398
1275
 
1399
1276
  | Function | What it does |
1400
1277
  |----------|-------------|
1401
- | `bw.hexToHsl(hex)` | Hex to [h, s, l] |
1402
- | `bw.hslToHex(hsl)` | [h, s, l] to hex |
1278
+ | `bw.hexToHsl(hex)` | Hex to `[h, s, l]` |
1279
+ | `bw.hslToHex(hsl)` | `[h, s, l]` to hex |
1403
1280
  | `bw.deriveShades(hex)` | 8 shade variants from one color |
1404
1281
  | `bw.derivePalette(cfg)` | Full palette from seed colors |
1405
1282
  | `bw.textOnColor(hex)` | Contrast-safe text color |
@@ -1410,49 +1287,47 @@ Key things this example proves:
1410
1287
  | Function | What it does |
1411
1288
  |----------|-------------|
1412
1289
  | `bw.$('selector')` | querySelectorAll as array |
1413
- | `bw.h(tag, attrs, c, o)` | TACO constructor (positional args `{t,a,c,o}`) |
1290
+ | `bw.el(sel, apply?)` | Find + optionally apply content/function |
1414
1291
  | `bw.escapeHTML(str)` | Escape HTML special chars |
1415
1292
  | `bw.uuid(prefix)` | Generate unique ID |
1416
1293
  | `bw.typeOf(x)` | Enhanced typeof |
1417
1294
  | `bw.getURLParam(key, def)` | Read URL query parameter |
1418
- | `bw.random(min, max)` | Random integer (or array) |
1419
1295
  | `bw.loremIpsum(n)` | Placeholder text |
1420
1296
  | `bw.mapScale(x, i0, i1, o0, o1)` | Map value between ranges |
1421
- | `bw.parseRJSON(str)` | Parse relaxed JSON |
1297
+ | `bw.parseJSONFlex(str)` | Parse flexible JSON (unquoted keys, single quotes, r-prefix) |
1422
1298
  | `bw.saveClientFile(name, data)` | Browser file download |
1423
1299
  | `bw.loadClientJSON(cb)` | Browser file upload (JSON) |
1424
1300
 
1425
- ---
1426
1301
 
1427
1302
  ## Appendix: Framework Translation Table
1428
1303
 
1429
- How common UI operations map across frameworks. Each cell is the idiomatic one-liner for that framework.
1430
-
1431
- | Operation | What it is | React | Vue 3 | Vanilla JS | Svelte 5 | Solid | Bitwrench |
1432
- |-----------|-----------|-------|-------|------------|----------|-------|-----------|
1433
- | **Render element** | Create and display a UI element | `<div className="card">Hi</div>` | `<div class="card">Hi</div>` | `el.innerHTML = '<div>Hi</div>'` | `<div class="card">Hi</div>` | `<div class="card">Hi</div>` | `bw.DOM('#x', {t:'div', a:{class:'card'}, c:'Hi'})` |
1434
- | **Update text** | Change text content after render | `setText('new')` via `useState` | `msg.value = 'new'` | `el.textContent = 'new'` | `msg = 'new'` | `setMsg('new')` | `el._bw_state.msg = 'new'; bw.update(el)` or `bw.patch(id, 'new')` |
1435
- | **Conditional render** | Show/hide based on state | `{show && <Comp/>}` | `v-if="show"` | `if (show) el.style.display = ''` | `{#if show}<Comp/>{/if}` | `<Show when={show}><Comp/></Show>` | `show ? taco : null` in `c:` array |
1436
- | **List rendering** | Render array of items | `{items.map(i => <Li key={i.id}/>)}` | `v-for="i in items" :key="i.id"` | `el.innerHTML = items.map(...)` | `{#each items as i (i.id)}` | `<For each={items}>{i => ...}</For>` | `c: items.map(function(i) { return {t:'li', c:i.name} })` |
1437
- | **Event handler** | Attach click/input handler | `onClick={handler}` | `@click="handler"` | `el.addEventListener('click', fn)` | `onclick={handler}` | `onClick={handler}` | `a: { onclick: fn }` |
1438
- | **State declaration** | Declare reactive state | `const [x, setX] = useState(0)` | `const x = ref(0)` | `let x = 0` | `let x = $state(0)` | `const [x, setX] = createSignal(0)` | `o: { state: { x: 0 } }` |
1439
- | **State update** | Change state and trigger re-render | `setX(42)` | `x.value = 42` | `x = 42; render()` | `x = 42` | `setX(42)` | `el._bw_state.x = 42; bw.update(el)` |
1440
- | **Computed / derived** | Derive value from state | `useMemo(() => x * 2, [x])` | `computed(() => x.value * 2)` | `function get() { return x * 2; }` | `let d = $derived(x * 2)` | `const d = () => x() * 2` | `c: '${x}'` with Tier 2: `'${x * 2}'` |
1441
- | **Side effect** | Run code on mount/change | `useEffect(() => {...}, [])` | `onMounted(() => {...})` | `window.addEventListener('load', fn)` | `$effect(() => {...})` | `onMount(() => {...})` | `o: { mounted: function(el) {...} }` |
1442
- | **Cleanup on unmount** | Tear down timers/listeners | `useEffect return cleanup` | `onUnmounted(() => {...})` | manual | `return () => {...}` in `$effect` | `onCleanup(() => {...})` | `o: { unmount: fn }` or `bw.cleanup(el)` |
1443
- | **Style inline** | Apply inline styles | `style={{color: 'red'}}` | `:style="{color: 'red'}"` | `el.style.color = 'red'` | `style="color:red"` | `style={{color: 'red'}}` | `a: { style: bw.s({ color: 'red' }) }` |
1444
- | **Style composition** | Compose/merge styles | `{...base, ...override}` | `[baseStyle, overrideStyle]` | `Object.assign({}, base, over)` | `{...base, ...override}` | `{...base, ...override}` | `bw.s({ display: 'flex' }, { padding: '1rem' }, { color: accent })` |
1445
- | **CSS class conditional** | Toggle classes | `className={active ? 'on' : ''}` | `:class="{on: active}"` | `el.classList.toggle('on')` | `class:on={active}` | `classList={{on: active()}}` | `a: { class: 'btn ' + (active ? 'on' : '') }` |
1446
- | **Generate stylesheet** | Create CSS rules in JS | styled-components / emotion | `<style scoped>` | `style.textContent = css` | `<style>` block | `css\`...\`` | `bw.injectCSS(bw.css({ '.card': { padding: '1rem' } }))` |
1447
- | **Responsive styles** | Breakpoint-based CSS | media query in CSS/styled | `@media` in `<style>` | `@media` in CSS file | `@media` in `<style>` | `@media` in CSS | `bw.responsive('.grid', { md: { columns: '1fr 1fr' } })` |
1448
- | **Animation** | CSS keyframe animation | `@keyframes` in CSS file | `@keyframes` in `<style>` | `@keyframes` in CSS | `animate:fn` or CSS | CSS or WAAPI | `bw.css({ '@keyframes fade': { '0%': {opacity:'0'}, '100%': {opacity:'1'} } })` |
1449
- | **Raw HTML** | Render unescaped HTML | `dangerouslySetInnerHTML` | `v-html="str"` | `el.innerHTML = str` | `{@html str}` | `innerHTML={str}` | `bw.raw(str)` in `c:` |
1450
- | **Cross-component events** | Decouple communication | Context + useReducer / Zustand | provide/inject or Pinia | CustomEvent / EventTarget | stores | Context or signals | `bw.pub(topic, data)` / `bw.sub(topic, fn)` |
1451
- | **Form input binding** | Read form values | `value={x} onChange={...}` | `v-model="x"` | `input.value` | `bind:value={x}` | `value={x()} onInput={...}` | `bw.$('#id')[0].value` or `bw.makeInput({oninput:fn})` |
1452
- | **Theme / design tokens** | Apply consistent theming | ThemeProvider / CSS vars | CSS vars / provide | CSS custom properties | CSS vars | CSS vars / createContext | `bw.loadStyles({ primary: '#hex' })` or `bw.makeStyles(cfg)` => `theme.palette` |
1453
- | **Build step required** | Required toolchain | Yes (Babel/Vite/webpack) | Yes (Vite or Vue CLI) | No | Yes (Svelte compiler) | Yes (Vite/Babel) | **No** — open the HTML file |
1454
- | **Bundle size** | Shipped JS size | ~45KB (React + ReactDOM) | ~33KB (Vue 3) | 0KB | ~2KB (runtime) | ~7KB | **~40KB** (bitwrench UMD gzipped, includes components + CSS gen) |
1455
-
1456
- ---
1457
-
1458
- *Bitwrench is maintained by [Manu Chatterjee](https://github.com/deftio) (deftio). BSD-2-Clause license.*
1304
+ How common UI operations map across frameworks:
1305
+
1306
+ | Operation | React | Vue 3 | Bitwrench |
1307
+ |-----------|-------|-------|-----------|
1308
+ | Render element | `<div className="card">Hi</div>` | `<div class="card">Hi</div>` | `bw.mount('#x', {t:'div', a:{class:'card'}, c:'Hi'})` |
1309
+ | Update text | `setText('new')` via `useState` | `msg.value = 'new'` | `el.bw.setContent('new')` or `bw.patch(id, 'new')` |
1310
+ | Conditional | `{show && <Comp/>}` | `v-if="show"` | `show ? taco : null` in `c:` array |
1311
+ | List render | `{items.map(i => <Li key={i.id}/>)}` | `v-for="i in items"` | `c: items.map(function(i) { return {t:'li', c:i.name} })` |
1312
+ | Event handler | `onClick={handler}` | `@click="handler"` | `a: { onclick: fn }` |
1313
+ | Declare state | `const [x, setX] = useState(0)` | `const x = ref(0)` | `o: { state: { x: 0 } }` |
1314
+ | Update state | `setX(42)` | `x.value = 42` | `el._bw_state.x = 42; bw.refresh(el)` |
1315
+ | Side effect | `useEffect(() => {...}, [])` | `onMounted(() => {...})` | `o: { mounted: function(el) {...} }` |
1316
+ | Cleanup | `useEffect return cleanup` | `onUnmounted(() => {...})` | `o: { unmount: fn }` |
1317
+ | Inline style | `style={{color: 'red'}}` | `:style="{color: 'red'}"` | `a: { style: bw.s({color:'red'}) }` |
1318
+ | Generate CSS | styled-components / emotion | `<style scoped>` | `bw.injectCSS(bw.css({'.card': {padding:'1rem'}}))` |
1319
+ | Raw HTML | `dangerouslySetInnerHTML` | `v-html="str"` | `bw.raw(str)` in `c:` |
1320
+ | Cross-component | Context / Zustand | provide/inject / Pinia | `bw.pub(topic, data)` / `bw.sub(topic, fn)` |
1321
+ | Theme tokens | ThemeProvider / CSS vars | CSS vars / provide | `bw.makeStyles(cfg)` => `theme.palette` |
1322
+ | Build step? | Yes (Babel/Vite) | Yes (Vite/CLI) | **No** -- open the HTML file |
1323
+
1324
+
1325
+ ## Postscript: Validation
1326
+
1327
+ A `{taco}` is a plain JavaScript object with a known shape: `t` is a string, `a` is an object of attributes, `c` is a string, array, or nested `{taco}`, and `o` is an object with specific keys (`state`, `render`, `handle`, `slots`, `mounted`, `unmount`). That shape is simple enough to describe as a JSON Schema, a TypeScript interface, or a validation function.
1328
+
1329
+ This means you can validate `{taco}` objects before they reach the DOM. A server can check that wire-format TACOs conform to an allowed subset before sending them to the client. A test suite can assert that a component factory returns well-formed output. An LLM generating UI can have its output validated against the schema before it is rendered. A content management system can enforce that editors produce valid `{taco}` structures.
1330
+
1331
+ Bitwrench does not ship a schema or enforce one at runtime -- the library is permissive by design, and validation has a cost. But the fact that the entire UI description is a plain object with a documented shape means validation is always available as an option. This is a property that template strings, JSX, and HTML do not have without a parser.
1332
+
1333
+ *Bitwrench is maintained by [deftio](https://github.com/deftio). BSD-2-Clause license.*