bitwrench 2.0.22 → 2.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.txt +1 -1
- package/README.md +4 -3
- package/bin/bwmcp.js +3 -0
- package/dist/bitwrench-bccl.cjs.js +1 -1
- package/dist/bitwrench-bccl.cjs.min.js +1 -1
- package/dist/bitwrench-bccl.cjs.min.js.gz +0 -0
- package/dist/bitwrench-bccl.esm.js +1 -1
- package/dist/bitwrench-bccl.esm.min.js +1 -1
- package/dist/bitwrench-bccl.esm.min.js.gz +0 -0
- package/dist/bitwrench-bccl.umd.js +1 -1
- package/dist/bitwrench-bccl.umd.min.js +1 -1
- package/dist/bitwrench-bccl.umd.min.js.gz +0 -0
- package/dist/bitwrench-code-edit.cjs.js +1 -1
- package/dist/bitwrench-code-edit.cjs.min.js +1 -1
- package/dist/bitwrench-code-edit.es5.js +1 -1
- package/dist/bitwrench-code-edit.es5.min.js +1 -1
- package/dist/bitwrench-code-edit.esm.js +1 -1
- package/dist/bitwrench-code-edit.esm.min.js +1 -1
- package/dist/bitwrench-code-edit.umd.js +1 -1
- package/dist/bitwrench-code-edit.umd.min.js +1 -1
- package/dist/bitwrench-code-edit.umd.min.js.gz +0 -0
- package/dist/bitwrench-debug.js +1 -1
- package/dist/bitwrench-debug.min.js +1 -1
- package/dist/bitwrench-lean.cjs.js +3 -3
- package/dist/bitwrench-lean.cjs.min.js +2 -2
- package/dist/bitwrench-lean.cjs.min.js.gz +0 -0
- package/dist/bitwrench-lean.es5.js +3 -3
- package/dist/bitwrench-lean.es5.min.js +2 -2
- package/dist/bitwrench-lean.es5.min.js.gz +0 -0
- package/dist/bitwrench-lean.esm.js +3 -3
- package/dist/bitwrench-lean.esm.min.js +2 -2
- package/dist/bitwrench-lean.esm.min.js.gz +0 -0
- package/dist/bitwrench-lean.umd.js +3 -3
- package/dist/bitwrench-lean.umd.min.js +2 -2
- package/dist/bitwrench-lean.umd.min.js.gz +0 -0
- package/dist/bitwrench-util-css.cjs.js +1 -1
- package/dist/bitwrench-util-css.cjs.min.js +1 -1
- package/dist/bitwrench-util-css.es5.js +1 -1
- package/dist/bitwrench-util-css.es5.min.js +1 -1
- package/dist/bitwrench-util-css.esm.js +1 -1
- package/dist/bitwrench-util-css.esm.min.js +1 -1
- package/dist/bitwrench-util-css.umd.js +1 -1
- package/dist/bitwrench-util-css.umd.min.js +1 -1
- package/dist/bitwrench-util-css.umd.min.js.gz +0 -0
- package/dist/bitwrench.cjs.js +3 -3
- package/dist/bitwrench.cjs.min.js +2 -2
- package/dist/bitwrench.cjs.min.js.gz +0 -0
- package/dist/bitwrench.css +1 -1
- package/dist/bitwrench.es5.js +3 -3
- package/dist/bitwrench.es5.min.js +2 -2
- package/dist/bitwrench.es5.min.js.gz +0 -0
- package/dist/bitwrench.esm.js +3 -3
- package/dist/bitwrench.esm.min.js +2 -2
- package/dist/bitwrench.esm.min.js.gz +0 -0
- package/dist/bitwrench.umd.js +3 -3
- package/dist/bitwrench.umd.min.js +2 -2
- package/dist/bitwrench.umd.min.js.gz +0 -0
- package/dist/builds.json +65 -65
- package/dist/bwserve.cjs.js +2 -2
- package/dist/bwserve.esm.js +2 -2
- package/dist/sri.json +45 -45
- package/docs/README.md +76 -0
- package/docs/app-patterns.md +264 -0
- package/docs/bitwrench-mcp.md +426 -0
- package/docs/bitwrench_api.md +2232 -0
- package/docs/bw-attach.md +399 -0
- package/docs/bwserve.md +841 -0
- package/docs/cli.md +307 -0
- package/docs/component-cheatsheet.md +144 -0
- package/docs/component-library.md +1099 -0
- package/docs/framework-translation-table.md +33 -0
- package/docs/llm-bitwrench-guide.md +672 -0
- package/docs/routing.md +562 -0
- package/docs/state-management.md +767 -0
- package/docs/taco-format.md +373 -0
- package/docs/theming.md +309 -0
- package/docs/thinking-in-bitwrench.md +1457 -0
- package/docs/tutorial-bwserve.md +297 -0
- package/docs/tutorial-embedded.md +314 -0
- package/docs/tutorial-website.md +255 -0
- package/package.json +11 -3
- package/readme.html +5 -4
- package/src/mcp/knowledge.js +231 -0
- package/src/mcp/live.js +226 -0
- package/src/mcp/server.js +216 -0
- package/src/mcp/tools.js +369 -0
- package/src/mcp/transport.js +55 -0
- package/src/version.js +3 -3
|
@@ -0,0 +1,672 @@
|
|
|
1
|
+
# Bitwrench LLM Guide
|
|
2
|
+
|
|
3
|
+
> Compact tutorial for AI-assisted bitwrench development.
|
|
4
|
+
> For the full teaching narrative, see `docs/thinking-in-bitwrench.md`.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## Step 1: Build Something
|
|
9
|
+
|
|
10
|
+
Start here. This is a complete bitwrench page:
|
|
11
|
+
|
|
12
|
+
```html
|
|
13
|
+
<!DOCTYPE html>
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="UTF-8">
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
18
|
+
<title>My Page</title>
|
|
19
|
+
<script src="https://cdn.jsdelivr.net/npm/bitwrench@2/dist/bitwrench.umd.min.js"></script>
|
|
20
|
+
</head>
|
|
21
|
+
<body>
|
|
22
|
+
<div id="app"></div>
|
|
23
|
+
<script>
|
|
24
|
+
bw.loadStyles(); // structural CSS (buttons, cards, tables)
|
|
25
|
+
bw.loadStyles({ primary: '#336699', secondary: '#cc6633' }); // themed colors
|
|
26
|
+
|
|
27
|
+
bw.DOM('#app', {
|
|
28
|
+
t: 'div', a: { class: 'bw-container' },
|
|
29
|
+
c: [
|
|
30
|
+
bw.makeNavbar({ brand: 'My App', items: [{ text: 'Home', href: '#' }] }),
|
|
31
|
+
bw.makeCard({ title: 'Hello', content: 'Built with bitwrench.' }),
|
|
32
|
+
bw.makeTable({ data: [
|
|
33
|
+
{ name: 'Alice', role: 'Admin' },
|
|
34
|
+
{ name: 'Bob', role: 'User' }
|
|
35
|
+
], sortable: true })
|
|
36
|
+
]
|
|
37
|
+
});
|
|
38
|
+
</script>
|
|
39
|
+
</body>
|
|
40
|
+
</html>
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
That's it. No build step. No npm install. Open the file in a browser.
|
|
44
|
+
|
|
45
|
+
**What happened:**
|
|
46
|
+
1. `bw.loadStyles()` injected Bootstrap-like CSS
|
|
47
|
+
2. `bw.loadStyles({...})` generated a themed color palette from two seed colors
|
|
48
|
+
3. `bw.makeNavbar()`, `bw.makeCard()`, `bw.makeTable()` returned TACO objects
|
|
49
|
+
4. `bw.DOM('#app', taco)` rendered them into the page
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Step 2: Understand TACO
|
|
54
|
+
|
|
55
|
+
Every UI element is a plain JS object `{t, a, c, o}`:
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
{ t: 'div', a: { class: 'card', id: 'x' }, c: 'Hello world' }
|
|
59
|
+
// tag attributes content
|
|
60
|
+
// => <div class="card" id="x">Hello world</div>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
- `t` -- tag name (defaults to `'div'` if omitted)
|
|
64
|
+
- `a` -- object of HTML attributes (including event handlers)
|
|
65
|
+
- `c` -- string, TACO, or array of TACOs (nested arbitrarily deep)
|
|
66
|
+
- `o` -- bitwrench-only metadata (lifecycle, state) -- never in HTML output
|
|
67
|
+
|
|
68
|
+
Content is HTML-escaped by default. Use `bw.raw(str)` for trusted HTML.
|
|
69
|
+
`null`/`undefined`/`false` in content arrays are silently skipped.
|
|
70
|
+
|
|
71
|
+
### Nesting
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
{ t: 'div', c: [
|
|
75
|
+
{ t: 'h2', c: 'Title' },
|
|
76
|
+
{ t: 'p', c: 'Body text' },
|
|
77
|
+
{ t: 'ul', c: items.map(function(i) { return { t: 'li', c: i }; }) }
|
|
78
|
+
]}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### TACO is computation
|
|
82
|
+
|
|
83
|
+
Every field is a JS expression. This is the key insight:
|
|
84
|
+
|
|
85
|
+
```javascript
|
|
86
|
+
var isAdmin = true;
|
|
87
|
+
var items = ['Apples', 'Bananas'];
|
|
88
|
+
|
|
89
|
+
{
|
|
90
|
+
t: isAdmin ? 'h1' : 'h2', // computed tag
|
|
91
|
+
a: { class: 'header ' + (isAdmin ? 'admin' : '') }, // computed attrs
|
|
92
|
+
c: items.map(function(i) { return { t: 'li', c: i }; }) // .map() => children
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Functions are components. Arrays are slots. `.map()` is iteration. Ternaries are conditionals.**
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
// A "component" is just a function that returns TACO
|
|
100
|
+
function colorCard(title, body, color) {
|
|
101
|
+
return {
|
|
102
|
+
t: 'div', a: { style: 'border-left:4px solid ' + color + '; padding:1rem' },
|
|
103
|
+
c: [{ t: 'h3', c: title }, { t: 'p', c: body }]
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
bw.DOM('#app', { t: 'div', c: [
|
|
108
|
+
colorCard('Warning', 'Disk low', '#e67e22'),
|
|
109
|
+
colorCard('OK', 'All clear', '#27ae60')
|
|
110
|
+
]});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Step 3: Three Levels
|
|
116
|
+
|
|
117
|
+
| Level | What | How | When |
|
|
118
|
+
|-------|------|-----|------|
|
|
119
|
+
| **0 -- Data** | Plain JS object | `bw.makeCard({...})` or `{t,a,c}` | Static content, SSR |
|
|
120
|
+
| **1 -- DOM** | Rendered tree | `bw.DOM('#x', taco)` | Re-render on demand |
|
|
121
|
+
| **2 -- Stateful** | Reactive component | `o.state` + `o.render` + `bw.update()` | Interactive UI |
|
|
122
|
+
|
|
123
|
+
**Most UI should be Level 0.** Escalate only when needed.
|
|
124
|
+
|
|
125
|
+
### Level 1 -- re-render when data changes
|
|
126
|
+
|
|
127
|
+
```javascript
|
|
128
|
+
var filter = 'all';
|
|
129
|
+
function render() {
|
|
130
|
+
var items = filter === 'all' ? all : all.filter(function(i) { return i.type === filter; });
|
|
131
|
+
bw.DOM('#list', { t: 'div', c: items.map(function(i) {
|
|
132
|
+
return bw.makeCard({ title: i.name, content: i.desc });
|
|
133
|
+
})});
|
|
134
|
+
}
|
|
135
|
+
render();
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### Level 2 -- stateful TACO
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
bw.DOM('#app', {
|
|
142
|
+
t: 'div',
|
|
143
|
+
o: {
|
|
144
|
+
state: { count: 0 },
|
|
145
|
+
render: function(el) {
|
|
146
|
+
var s = el._bw_state;
|
|
147
|
+
bw.DOM(el, { t: 'div', c: [
|
|
148
|
+
{ t: 'h3', c: 'Count: ' + s.count },
|
|
149
|
+
bw.makeButton({ text: '+1', onclick: function() {
|
|
150
|
+
s.count++;
|
|
151
|
+
bw.update(el);
|
|
152
|
+
}})
|
|
153
|
+
]});
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**How it works:** `createDOM()` copies `o.state` to `el._bw_state`, stores `o.render` as `el._bw_render`, calls it immediately. On state change, call `bw.update(el)` to re-invoke render.
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## Step 4: Events -- the #1 Mistake
|
|
164
|
+
|
|
165
|
+
**Always put event handlers in `a: { onclick: fn }`, never in `o.mounted`.**
|
|
166
|
+
|
|
167
|
+
When a stateful component re-renders (after `bw.update()`), old DOM children are replaced. Listeners attached via `addEventListener` in `o.mounted` are silently lost.
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
// CORRECT -- re-attached on every render
|
|
171
|
+
{ t: 'button', a: { onclick: function() { save(); } }, c: 'Save' }
|
|
172
|
+
bw.makeButton({ text: 'Save', onclick: function() { save(); } })
|
|
173
|
+
|
|
174
|
+
// WRONG -- silently breaks after first re-render
|
|
175
|
+
{ t: 'button', c: 'Save',
|
|
176
|
+
o: { mounted: function(el) { el.addEventListener('click', save); } } }
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**When is `o.mounted` OK?** Only for non-event setup: IntersectionObserver, ResizeObserver, measuring dimensions. Never for click/input handlers.
|
|
180
|
+
|
|
181
|
+
### Cross-component: pub/sub
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
// Publisher
|
|
185
|
+
bw.pub('cart:updated', { count: cart.length });
|
|
186
|
+
|
|
187
|
+
// Subscriber (auto-cleans when element is removed)
|
|
188
|
+
bw.sub('cart:updated', function(d) {
|
|
189
|
+
el._bw_state.n = d.count;
|
|
190
|
+
bw.update(el);
|
|
191
|
+
}, el);
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Component handles (o.handle / o.slots)
|
|
195
|
+
|
|
196
|
+
**This is the recommended way to update rendered components without re-rendering.** Use `bw.mount()` + `el.bw.method()` instead of re-rendering the entire component when you only need to change a slot, advance a carousel, or toggle an accordion.
|
|
197
|
+
|
|
198
|
+
BCCL components expose methods via `el.bw`:
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
var el = bw.mount('#target', bw.makeCarousel({ items: slides }));
|
|
202
|
+
el.bw.goToSlide(2);
|
|
203
|
+
el.bw.next();
|
|
204
|
+
el.bw.pause();
|
|
205
|
+
|
|
206
|
+
var card = bw.mount('#info', bw.makeCard({ title: 'Stats', content: '0' }));
|
|
207
|
+
card.bw.setTitle('Updated');
|
|
208
|
+
card.bw.setContent({ t: 'b', c: '42' });
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Build your own with `o.handle` and `o.slots`:
|
|
212
|
+
|
|
213
|
+
```javascript
|
|
214
|
+
{
|
|
215
|
+
t: 'div', c: [
|
|
216
|
+
{ t: 'h3', a: { class: 'title' }, c: 'Default' },
|
|
217
|
+
{ t: 'div', a: { class: 'body' }, c: 'Content' }
|
|
218
|
+
],
|
|
219
|
+
o: {
|
|
220
|
+
slots: { title: '.title', body: '.body' }, // => el.bw.setTitle(), el.bw.getTitle()
|
|
221
|
+
handle: {
|
|
222
|
+
reset: function(el) { el.bw.setTitle('Default'); el.bw.setBody('Content'); }
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Step 5: CSS and Theming
|
|
231
|
+
|
|
232
|
+
### Inline styles -- JS variables
|
|
233
|
+
|
|
234
|
+
```javascript
|
|
235
|
+
var brand = '#336699', radius = '12px';
|
|
236
|
+
{ t: 'div', a: { style: 'background:' + brand + '; border-radius:' + radius }, c: 'Hi' }
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### bw.s() -- merge style objects
|
|
240
|
+
|
|
241
|
+
```javascript
|
|
242
|
+
{ t: 'div', a: { style: bw.s({ display: 'flex' }, { gap: '1rem' }, { padding: '1rem' }) }, c: '...' }
|
|
243
|
+
// Conditional: null args skipped
|
|
244
|
+
{ t: 'div', a: { style: bw.s({ padding: '1rem' }, isActive ? { fontWeight: '700' } : null) } }
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### bw.css() -- generate stylesheet from JS objects
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
bw.injectCSS(bw.css({
|
|
251
|
+
'.card': { borderRadius: '12px', padding: '1.5rem', border: '1px solid #ddd' },
|
|
252
|
+
'.card:hover': { boxShadow: '0 4px 12px rgba(0,0,0,.1)' },
|
|
253
|
+
'@keyframes fadeIn': { '0%': { opacity: '0' }, '100%': { opacity: '1' } },
|
|
254
|
+
'@media (max-width: 768px)': { '.card': { padding: '0.75rem' } }
|
|
255
|
+
}));
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
CamelCase auto-converts to kebab-case. All `@`-prefix keys nest recursively.
|
|
259
|
+
|
|
260
|
+
### Functions as CSS generators (like Sass mixins)
|
|
261
|
+
|
|
262
|
+
```javascript
|
|
263
|
+
function cardStyles(accent) {
|
|
264
|
+
var s = bw.deriveShades(accent);
|
|
265
|
+
return { background: s.light, border: '1px solid ' + s.border, color: s.darkText };
|
|
266
|
+
}
|
|
267
|
+
bw.injectCSS(bw.css({ '.warn': cardStyles('#e67e22'), '.ok': cardStyles('#27ae60') }));
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### Theme system -- complete palette from 2 colors
|
|
271
|
+
|
|
272
|
+
```javascript
|
|
273
|
+
bw.loadStyles(); // structural CSS only (call once)
|
|
274
|
+
|
|
275
|
+
bw.loadStyles({
|
|
276
|
+
primary: '#336699', secondary: '#cc6633',
|
|
277
|
+
spacing: 'normal', // 'compact'|'normal'|'spacious'
|
|
278
|
+
radius: 'md', // 'none'|'sm'|'md'|'lg'|'pill'
|
|
279
|
+
elevation: 'md', // 'flat'|'sm'|'md'|'lg'
|
|
280
|
+
harmonize: 0.20 // hue shift semantics toward primary (0-1)
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
bw.toggleStyles(); // switch primary <=> alternate palette
|
|
284
|
+
|
|
285
|
+
// Or generate separately:
|
|
286
|
+
var theme = bw.makeStyles({ primary: '#336699', secondary: '#cc6633' });
|
|
287
|
+
bw.applyStyles(theme);
|
|
288
|
+
// Use tokens: theme.palette.primary.base, theme.palette.secondary.light, etc.
|
|
289
|
+
|
|
290
|
+
// 12 built-in presets:
|
|
291
|
+
bw.loadStyles(bw.THEME_PRESETS.ocean);
|
|
292
|
+
// teal, ocean, sunset, forest, slate, rose, indigo, amber, emerald, nord, coral, midnight
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Responsive breakpoints
|
|
296
|
+
|
|
297
|
+
```javascript
|
|
298
|
+
bw.injectCSS(bw.responsive('.grid', {
|
|
299
|
+
base: { gridTemplateColumns: '1fr' },
|
|
300
|
+
md: { gridTemplateColumns: 'repeat(2, 1fr)' },
|
|
301
|
+
lg: { gridTemplateColumns: 'repeat(4, 1fr)' }
|
|
302
|
+
}));
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### bw.u() -- Tailwind-style shorthand (optional plugin)
|
|
306
|
+
|
|
307
|
+
Load `bitwrench-util-css.umd.min.js` after bitwrench (~1KB gzipped):
|
|
308
|
+
|
|
309
|
+
```javascript
|
|
310
|
+
bw.u('flex gap4 p4 alignCenter') // => { display:'flex', gap:'1rem', ... }
|
|
311
|
+
bw.u.css('flex gap4 p4 alignCenter') // => "display:flex;gap:1rem;padding:1rem;..."
|
|
312
|
+
bw.u.cls('flex gap4 p4') // => "bw_flex bw_gap_4 bw_p_4"
|
|
313
|
+
|
|
314
|
+
// Compose with bw.s()
|
|
315
|
+
a: { style: bw.s(bw.u('flex gap4'), { borderBottom: '2px solid #336699' }) }
|
|
316
|
+
|
|
317
|
+
// Scale: {n} = n * 0.25rem. p4 = 1rem, gap8 = 2rem, m1 = 0.25rem
|
|
318
|
+
// Tokens: p/m/pt/pb/pl/pr/px/py/mt/mb/ml/mr/mx/my + {n}, gap{n}, w{n}, h{n}
|
|
319
|
+
// Keywords: flex, flexCol, flexRow, block, hidden, bold, textCenter, justifyCenter, alignCenter
|
|
320
|
+
// Colors: bg-[#hex], text-[#hex], bg-name, text-name
|
|
321
|
+
// Sizes: textSm, textBase, textLg, textXl, text2xl, text3xl
|
|
322
|
+
|
|
323
|
+
// Custom tokens
|
|
324
|
+
bw.u.extend({ shadow: { boxShadow: '0 2px 8px rgba(0,0,0,0.1)' } });
|
|
325
|
+
bw.u.css('p4 shadow') // includes your custom token
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## Step 6: BCCL Components
|
|
331
|
+
|
|
332
|
+
**Bitwrench ships 50+ ready-made components. Check the table below BEFORE writing custom TACO for common UI patterns.** All `bw.make*()` return Level 0 TACO objects. Factory dispatcher: `bw.make('card', props)`.
|
|
333
|
+
|
|
334
|
+
### Most-Used Components
|
|
335
|
+
|
|
336
|
+
| Component | Key Props | Capabilities |
|
|
337
|
+
|-----------|-----------|-------------|
|
|
338
|
+
| makeTable | data, columns, sortable, pageSize, onRowClick | **Click-to-sort (default!)**, pagination, row selection, column renderers |
|
|
339
|
+
| makeCard | title, content, footer, image, variant | Image positions, shadow variants, **slots: setTitle/setContent/setFooter** |
|
|
340
|
+
| makeModal | title, content, footer, onClose | ESC dismiss, backdrop close, **handles: open(), close()** |
|
|
341
|
+
| makeToast | title, content, variant, delay, position | Auto-dismiss 5s, 6 positions, **handle: dismiss()** |
|
|
342
|
+
| makeTabs | tabs [{label,content}], activeIndex | Arrow/Home/End keys, WAI-ARIA, **handles: setActiveTab(i), getActiveTab()** |
|
|
343
|
+
| makeAccordion | items [{title,content}], multiOpen | Animations, ARIA, **handles: toggle(i), openAll(), closeAll()** |
|
|
344
|
+
| makeCarousel | items, autoPlay, interval | Auto-play, keyboard, **handles: goToSlide(i), next(), prev(), pause(), play()** |
|
|
345
|
+
| makeFormGroup | label, input, help, validation, required | Required indicator, validation feedback -- **don't reinvent this** |
|
|
346
|
+
| makeTextarea | placeholder, value, rows | bw_form_control styling -- **use this, not raw `{t:'textarea'}`** |
|
|
347
|
+
| makeInput | type, placeholder, value, oninput | All HTML5 types with bw_form_control styling |
|
|
348
|
+
| makeSelect | options [{value,text}], value | Dropdown select with bw_form_control styling |
|
|
349
|
+
| makeProgress | value, max, variant, striped, animated | Striped + animated, **handles: setValue(n), getValue()** |
|
|
350
|
+
| makeStatCard | value, label, change, variant | Dashboard KPI with change arrows, **slots: setValue/setLabel** |
|
|
351
|
+
| makeSearchInput | placeholder, onSearch, onInput | Enter to search, clear button |
|
|
352
|
+
| makeChipInput | chips, placeholder, onAdd, onRemove | Enter to add, X to remove, **handles: addChip(), removeChip(), getChips(), clear()** |
|
|
353
|
+
| makeNav | items [{text,href,active}], pills | Tab/pill/vertical navigation |
|
|
354
|
+
| makeNavbar | brand, items, dark | Navigation bar with brand |
|
|
355
|
+
| makeButton | text, variant, size, onclick | 8 variants + outline-* variants |
|
|
356
|
+
| makeDropdown | trigger, items, align | Click menu with outside-click-to-close |
|
|
357
|
+
| makeAlert | content, variant, dismissible | Dismissible notification banner |
|
|
358
|
+
|
|
359
|
+
For the full 47-component table with all props and handles, see [Component Cheat Sheet](component-cheatsheet.md).
|
|
360
|
+
|
|
361
|
+
### Components with Imperative Handles
|
|
362
|
+
|
|
363
|
+
Six components expose `el.bw` methods for direct control. Use `bw.mount()` to get the element:
|
|
364
|
+
|
|
365
|
+
```javascript
|
|
366
|
+
var el = bw.mount('#app', bw.makeCarousel({ items: slides }));
|
|
367
|
+
el.bw.goToSlide(2); // handle method
|
|
368
|
+
el.bw.pause();
|
|
369
|
+
|
|
370
|
+
var card = bw.mount('#info', bw.makeCard({ title: 'Stats', content: '0' }));
|
|
371
|
+
card.bw.setTitle('Revenue'); // slot setter
|
|
372
|
+
card.bw.setContent({ t: 'b', c: '$42k' });
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
| Factory | Handle Methods | Slot Methods |
|
|
376
|
+
|---------|---------------|-------------|
|
|
377
|
+
| makeCarousel | goToSlide, next, prev, getActiveIndex, pause, play | -- |
|
|
378
|
+
| makeTabs | setActiveTab, getActiveTab | -- |
|
|
379
|
+
| makeAccordion | toggle, openAll, closeAll | -- |
|
|
380
|
+
| makeModal | open, close | -- |
|
|
381
|
+
| makeProgress | setValue, getValue | -- |
|
|
382
|
+
| makeChipInput | addChip, removeChip, getChips, clear | -- |
|
|
383
|
+
| makeCard | -- | setTitle/getTitle, setContent/getContent, setFooter/getFooter |
|
|
384
|
+
| makeStatCard | -- | setValue/getValue, setLabel/getLabel |
|
|
385
|
+
|
|
386
|
+
### Other Components
|
|
387
|
+
|
|
388
|
+
**Layout**: makeContainer, makeRow, makeCol ({xs,sm,md,lg,xl}), makeStack
|
|
389
|
+
**Content**: makeBadge, makeHero, makeSection, makeFeatureGrid, makeCTA, makeCodeDemo, makeMediaObject, makeTimeline, makeStepper, makeListGroup, makeAvatar, makeSkeleton, makeSpinner
|
|
390
|
+
**Forms**: makeForm, makeCheckbox, makeRadio, makeSwitch, makeRange, makeFileUpload
|
|
391
|
+
**Buttons**: makeButtonGroup (vertical/horizontal)
|
|
392
|
+
**Tables**: makeTableFromArray (2D arrays), makeDataTable (with title + scroll wrapper), makeBarChart (CSS-only)
|
|
393
|
+
**Overlays**: makeTooltip, makePopover
|
|
394
|
+
**Navigation**: makeBreadcrumb, makePagination
|
|
395
|
+
|
|
396
|
+
### Variants
|
|
397
|
+
`primary`, `secondary`, `success`, `danger`, `warning`, `info`, `light`, `dark`
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
## Step 7: Debugging with bwcli
|
|
402
|
+
|
|
403
|
+
### Browser console
|
|
404
|
+
|
|
405
|
+
```javascript
|
|
406
|
+
var el = bw.$('#app')[0];
|
|
407
|
+
el._bw_state; // current state
|
|
408
|
+
el._bw_render; // render function
|
|
409
|
+
bw.inspect(el); // formatted debug output (el.bw methods, state, classes)
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### bwcli attach -- remote debugging REPL
|
|
413
|
+
|
|
414
|
+
Connect a terminal REPL to any page for live inspection:
|
|
415
|
+
|
|
416
|
+
```bash
|
|
417
|
+
npm install -g bitwrench
|
|
418
|
+
bwcli attach # starts on port 7902
|
|
419
|
+
bwcli attach --port 3000 --allow-screenshot # custom port + screenshots
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
Add this to your page:
|
|
423
|
+
```html
|
|
424
|
+
<script src="http://localhost:7902/bw/attach.js"></script>
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
Then from the terminal:
|
|
428
|
+
```
|
|
429
|
+
> bw.$('#app')[0]._bw_state // evaluate any JS
|
|
430
|
+
> /tree // DOM tree of current page
|
|
431
|
+
> /tree #app // subtree of #app
|
|
432
|
+
> /screenshot // capture page as PNG
|
|
433
|
+
> /screenshot #my-card // capture specific element
|
|
434
|
+
> /listen // stream DOM events
|
|
435
|
+
> /mount #target {t:'h1',c:'Hi'} // mount TACO into page
|
|
436
|
+
> /render {t:'div',c:'test'} // render TACO to HTML string
|
|
437
|
+
> /patch #status "Loading..." // update element text
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
### LLM visual feedback loop
|
|
441
|
+
|
|
442
|
+
Use bwserve screenshots for iterative UI refinement:
|
|
443
|
+
|
|
444
|
+
```javascript
|
|
445
|
+
import bwserve from 'bitwrench/bwserve';
|
|
446
|
+
var app = bwserve.create({ port: 7902, allowScreenshot: true });
|
|
447
|
+
|
|
448
|
+
app.page('/', function(client) {
|
|
449
|
+
client.render('#app', myTaco);
|
|
450
|
+
|
|
451
|
+
// Capture what the user sees
|
|
452
|
+
var img = await client.screenshot('#app', { maxWidth: 800 });
|
|
453
|
+
// img.data is a Buffer (PNG) -- send to vision model for evaluation
|
|
454
|
+
// Vision model says "button is too small" => adjust TACO => re-render => screenshot again
|
|
455
|
+
});
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
---
|
|
459
|
+
|
|
460
|
+
## Step 8: bwserve -- Server-Driven UI
|
|
461
|
+
|
|
462
|
+
Push TACO from any server to the browser via SSE. No client-side app logic needed.
|
|
463
|
+
|
|
464
|
+
```javascript
|
|
465
|
+
import bwserve from 'bitwrench/bwserve';
|
|
466
|
+
var app = bwserve.create({ port: 7902 });
|
|
467
|
+
|
|
468
|
+
app.page('/', function(client) {
|
|
469
|
+
// Render initial UI
|
|
470
|
+
client.render('#app', {
|
|
471
|
+
t: 'div', c: [
|
|
472
|
+
{ t: 'h1', c: 'Hello from server' },
|
|
473
|
+
{ t: 'p', a: { id: 'status' }, c: 'Connected.' },
|
|
474
|
+
{ t: 'button', a: { 'data-bw-action': 'greet' }, c: 'Say hello' }
|
|
475
|
+
]
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
// Incremental updates
|
|
479
|
+
client.patch('#status', 'Processing...');
|
|
480
|
+
client.append('#log', { t: 'p', c: 'New entry' });
|
|
481
|
+
client.remove('.old-item');
|
|
482
|
+
|
|
483
|
+
// Handle user actions (data-bw-action elements)
|
|
484
|
+
client.on('greet', function() { client.patch('#status', 'Hello!'); });
|
|
485
|
+
|
|
486
|
+
// Register + call client-side functions
|
|
487
|
+
client.register('showAlert', 'function(msg) { alert(msg); }');
|
|
488
|
+
client.call('showAlert', 'Server says hi!');
|
|
489
|
+
});
|
|
490
|
+
app.listen();
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
**Protocol**: `replace`, `patch`, `append`, `remove`, `batch`, `message`, `register`, `call`, `exec`.
|
|
494
|
+
**Language-agnostic**: any server that writes SSE works (Python, Go, Rust, C, shell scripts).
|
|
495
|
+
|
|
496
|
+
---
|
|
497
|
+
|
|
498
|
+
## Step 9: Client-Side Routing
|
|
499
|
+
|
|
500
|
+
Map URLs to views with `bw.router()`. Hash mode (default) works everywhere; history mode uses `pushState`.
|
|
501
|
+
|
|
502
|
+
```javascript
|
|
503
|
+
bw.router({
|
|
504
|
+
target: '#app',
|
|
505
|
+
routes: {
|
|
506
|
+
'/': function() { return { t: 'h1', c: 'Home' }; },
|
|
507
|
+
'/about': function() { return { t: 'h1', c: 'About' }; },
|
|
508
|
+
'/users/:id': function(params) { return { t: 'div', c: 'User ' + params.id }; },
|
|
509
|
+
'/docs/*': function(params) { return { t: 'div', c: 'Doc: ' + params._rest }; },
|
|
510
|
+
'*': function() { return { t: 'h1', c: '404' }; }
|
|
511
|
+
},
|
|
512
|
+
before: function(to, from) {
|
|
513
|
+
if (to === '/admin' && !loggedIn) return '/login'; // redirect
|
|
514
|
+
},
|
|
515
|
+
after: function(to) { console.log('navigated to', to); }
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
// Programmatic navigation
|
|
519
|
+
bw.navigate('/users/42');
|
|
520
|
+
bw.navigate('/about', { replace: true });
|
|
521
|
+
|
|
522
|
+
// Navigation links (returns TACO <a> with onclick wired)
|
|
523
|
+
bw.link('/about', 'About Us', { class: 'nav-item' })
|
|
524
|
+
|
|
525
|
+
// Query strings: /search?q=hello => params._query.q === 'hello'
|
|
526
|
+
|
|
527
|
+
// React to route changes via pub/sub
|
|
528
|
+
bw.sub('bw:route', function(d) { navEl.bw.setActive(d.path); }, navEl);
|
|
529
|
+
|
|
530
|
+
// Cleanup
|
|
531
|
+
var r = bw.router({ ... });
|
|
532
|
+
r.destroy(); // remove listeners, stop routing
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
**Route priority**: exact > parameterized (`:id`) > catch-all (`/prefix/*`) > global wildcard (`*`).
|
|
536
|
+
|
|
537
|
+
**Modes**: `mode: 'hash'` (default, `#/path`) or `mode: 'history'` (pushState, needs server SPA fallback). History mode supports `base: '/app'` for prefix stripping.
|
|
538
|
+
|
|
539
|
+
---
|
|
540
|
+
|
|
541
|
+
## Step 10: HTML Generation and CLI
|
|
542
|
+
|
|
543
|
+
### bw.html() -- TACO to HTML string
|
|
544
|
+
|
|
545
|
+
```javascript
|
|
546
|
+
bw.html({ t: 'button', a: { onclick: function() { alert('hi'); } }, c: 'Click' })
|
|
547
|
+
// => '<button onclick="bw.funcGetById(\'bw_fn_0\')(event)">Click</button>'
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
Event handlers are auto-serialized via `funcRegister`.
|
|
551
|
+
|
|
552
|
+
### bw.htmlPage() -- complete standalone document
|
|
553
|
+
|
|
554
|
+
```javascript
|
|
555
|
+
var page = bw.htmlPage({
|
|
556
|
+
title: 'My App',
|
|
557
|
+
body: [{ t: 'h1', c: 'Hello' }, bw.makeButton({ text: 'Click', onclick: fn })],
|
|
558
|
+
runtime: 'shim', // 'inline'(120KB offline)|'cdn'|'shim'(500B)|'none'
|
|
559
|
+
theme: 'ocean', // preset name or { primary: '#hex', secondary: '#hex' }
|
|
560
|
+
css: '.custom { color: red; }'
|
|
561
|
+
});
|
|
562
|
+
// page is a complete <!DOCTYPE html> string with working event handlers
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
### bwcli -- command-line conversion
|
|
566
|
+
|
|
567
|
+
```bash
|
|
568
|
+
bwcli input.md -o output.html # markdown to HTML
|
|
569
|
+
bwcli input.md -o output.html --theme ocean # with theme preset
|
|
570
|
+
bwcli input.md -o output.html --standalone # bitwrench inlined (offline)
|
|
571
|
+
bwcli input.md --theme "#336699,#cc6633" # custom seed colors
|
|
572
|
+
bwcli serve # dev server (port 7902)
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
---
|
|
576
|
+
|
|
577
|
+
## Core API Quick Reference
|
|
578
|
+
|
|
579
|
+
### Rendering
|
|
580
|
+
| Function | Description |
|
|
581
|
+
|----------|-------------|
|
|
582
|
+
| `bw.html(taco)` | TACO to HTML string |
|
|
583
|
+
| `bw.createDOM(taco)` | TACO to detached DOM element |
|
|
584
|
+
| `bw.DOM(sel, taco)` | Mount TACO into existing element |
|
|
585
|
+
| `bw.mount(sel, taco)` | Like DOM() but returns root element (for el.bw access) |
|
|
586
|
+
| `bw.h(tag, a?, c?, o?)` | TACO constructor from positional args |
|
|
587
|
+
| `bw.raw(str)` | Mark string as pre-escaped HTML |
|
|
588
|
+
| `bw.htmlPage(opts)` | TACO to complete HTML document |
|
|
589
|
+
|
|
590
|
+
### CSS and Styling
|
|
591
|
+
| Function | Description |
|
|
592
|
+
|----------|-------------|
|
|
593
|
+
| `bw.css(rules)` | JS object to CSS string (handles @media, @keyframes) |
|
|
594
|
+
| `bw.injectCSS(css, {id})` | Insert CSS into document |
|
|
595
|
+
| `bw.s(...styles)` | Merge style objects into style string |
|
|
596
|
+
| `bw.responsive(sel, bp)` | Generate @media CSS from breakpoint object |
|
|
597
|
+
| `bw.loadStyles()` | Load structural CSS (no args) or generate+apply theme (with config) |
|
|
598
|
+
| `bw.makeStyles(cfg)` | Generate styles from seed colors (returns styles object) |
|
|
599
|
+
| `bw.applyStyles(styles)` | Inject generated styles into document |
|
|
600
|
+
| `bw.toggleStyles()` | Switch primary/alternate palettes |
|
|
601
|
+
|
|
602
|
+
### State and Lifecycle
|
|
603
|
+
| Function | Description |
|
|
604
|
+
|----------|-------------|
|
|
605
|
+
| `o.state` | Initial state (copied to `el._bw_state`) |
|
|
606
|
+
| `o.render(el, state)` | Render function, called on mount and `bw.update()` |
|
|
607
|
+
| `o.handle` | Methods attached to `el.bw` namespace |
|
|
608
|
+
| `o.slots` | `{name: '.selector'}` => auto `el.bw.setName()`/`el.bw.getName()` |
|
|
609
|
+
| `o.mounted(el)` | After DOM insertion (NOT for event handlers) |
|
|
610
|
+
| `o.unmount(el)` | Before DOM removal |
|
|
611
|
+
| `bw.update(el)` | Re-invoke render function |
|
|
612
|
+
| `bw.mount(sel, taco)` | Mount + return root element |
|
|
613
|
+
| `bw.cleanup(el)` | Run unmount hooks, clear subscriptions |
|
|
614
|
+
| `bw.patch(id, content)` | Update element by id or UUID |
|
|
615
|
+
| `bw.inspect(el)` | Debug: log el.bw methods, state, classes |
|
|
616
|
+
|
|
617
|
+
### Communication
|
|
618
|
+
| Function | Description |
|
|
619
|
+
|----------|-------------|
|
|
620
|
+
| `bw.pub(topic, data)` | App-wide publish |
|
|
621
|
+
| `bw.sub(topic, fn, el?)` | Subscribe (optional lifecycle tie to element) |
|
|
622
|
+
| `bw.message(target, action, data)` | Dispatch to `el.bw[action](data)` |
|
|
623
|
+
| `bw.emit(el, event, detail)` | DOM-scoped CustomEvent |
|
|
624
|
+
|
|
625
|
+
### Routing
|
|
626
|
+
| Function | Description |
|
|
627
|
+
|----------|-------------|
|
|
628
|
+
| `bw.router(config)` | Create and start client-side router. Returns `{ navigate, current, destroy }` |
|
|
629
|
+
| `bw.navigate(path, opts)` | Programmatic navigation (delegates to active router) |
|
|
630
|
+
| `bw.link(path, content, attrs)` | Returns TACO `<a>` with navigation wired |
|
|
631
|
+
|
|
632
|
+
### Utilities
|
|
633
|
+
| Function | Description |
|
|
634
|
+
|----------|-------------|
|
|
635
|
+
| `bw.$('selector')` | querySelectorAll as array |
|
|
636
|
+
| `bw.uuid(prefix)` | Generate unique ID |
|
|
637
|
+
| `bw.typeOf(x)` | Enhanced typeof: 'array', 'null', 'date' |
|
|
638
|
+
| `bw.escapeHTML(str)` | Escape HTML special chars |
|
|
639
|
+
| `bw.deriveShades(hex)` | 8 shade variants from one color |
|
|
640
|
+
| `bw.textOnColor(hex)` | Contrast-safe text color ('#fff' or '#000') |
|
|
641
|
+
| `bw.random(min, max)` | Random integer (or array variant) |
|
|
642
|
+
| `bw.loremIpsum(n)` | Placeholder text |
|
|
643
|
+
| `bw.parseRJSON(str)` | Relaxed JSON (unquoted keys, trailing commas) |
|
|
644
|
+
| `bw.saveClientFile(name, data)` | Browser file download |
|
|
645
|
+
|
|
646
|
+
---
|
|
647
|
+
|
|
648
|
+
## Key Rules Summary
|
|
649
|
+
|
|
650
|
+
1. **Events in `a: { onclick: fn }`** -- never in `o.mounted`. This is the #1 mistake.
|
|
651
|
+
2. **Call `bw.loadStyles()`** before rendering. Use `bw.loadStyles(config)` for themed colors.
|
|
652
|
+
3. **Content is escaped by default.** Use `bw.raw(str)` for trusted HTML only.
|
|
653
|
+
4. **All `make*()` return Level 0 TACOs** -- pass to `bw.DOM()` or `bw.html()`.
|
|
654
|
+
5. **TACO is computation** -- every field is a JS expression. Use variables, `.map()`, ternaries.
|
|
655
|
+
6. **CSS is just strings** -- store in variables, compose with `bw.s()`, generate with `bw.css()`.
|
|
656
|
+
7. **Three levels are explicit** -- you always know if you have data (L0), DOM (L1), or stateful (L2).
|
|
657
|
+
8. **No raw DOM** -- use `bw.DOM()`, not `innerHTML` or `document.querySelector`.
|
|
658
|
+
9. **CSS classes use `bw-` prefix**: `bw-card`, `bw-btn`, `bw-container`.
|
|
659
|
+
10. **Routing is built in** -- `bw.router()` for SPAs. Hash mode by default, history mode optional.
|
|
660
|
+
11. **Use `bw.mount()` + `el.bw`** for targeted updates. `o.handle` for methods, `o.slots` for content areas. Avoids re-render side effects (lost focus, scroll reset).
|
|
661
|
+
12. **Debug**: `bw.inspect(el)`, `el._bw_state`, `bwcli attach` for remote REPL.
|
|
662
|
+
|
|
663
|
+
---
|
|
664
|
+
|
|
665
|
+
## Removed APIs (v2.0.19)
|
|
666
|
+
|
|
667
|
+
`bw.component()`, `bw.compile()`, `bw.when()`, `bw.each()` -- all throw Error.
|
|
668
|
+
Replaced by `o.handle` + `o.slots` + `bw.mount()`. See Step 4.
|
|
669
|
+
|
|
670
|
+
---
|
|
671
|
+
|
|
672
|
+
*Bitwrench: 40KB gzipped, zero dependencies, no build step. [github.com/deftio/bitwrench](https://github.com/deftio/bitwrench)*
|