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,373 @@
|
|
|
1
|
+
# The TACO Format
|
|
2
|
+
|
|
3
|
+
Every UI element in bitwrench is a plain JavaScript object with up to four keys:
|
|
4
|
+
|
|
5
|
+
```javascript
|
|
6
|
+
{
|
|
7
|
+
t: 'div', // Tag — HTML element name
|
|
8
|
+
a: { class: 'card', id: 'main' }, // Attributes — HTML attributes
|
|
9
|
+
c: 'Hello World', // Content — text, TACO, or array
|
|
10
|
+
o: { state: { count: 0 } } // Options — state, lifecycle, behavior
|
|
11
|
+
}
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
That's it. **T**ag, **A**ttributes, **C**ontent, **O**ptions — TACO.
|
|
15
|
+
|
|
16
|
+
## Why plain objects?
|
|
17
|
+
|
|
18
|
+
TACO objects are regular JavaScript. You can store them in variables, pass them to functions, put them in arrays, serialize them to JSON, send them over the network, and generate them on a server. No special syntax, no compiler, no toolchain.
|
|
19
|
+
|
|
20
|
+
```javascript
|
|
21
|
+
// It's just data — compose with standard JavaScript
|
|
22
|
+
const header = { t: 'h1', c: 'Welcome' };
|
|
23
|
+
const items = data.map(d => ({ t: 'li', c: d.name }));
|
|
24
|
+
const page = { t: 'div', c: [header, { t: 'ul', c: items }] };
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
> **Coming from React?** A TACO object is analogous to what `React.createElement()` returns — a description of UI, not the UI itself. The difference is that TACO is plain data you write directly, while JSX requires a compiler to produce React elements.
|
|
28
|
+
|
|
29
|
+
> **Coming from Vue?** TACO is similar to Vue's render function `h('div', { class: 'card' }, children)`, but as a data literal rather than a function call. Vue's `<template>` blocks compile down to something similar internally.
|
|
30
|
+
|
|
31
|
+
## The four keys
|
|
32
|
+
|
|
33
|
+
### `t` — Tag
|
|
34
|
+
|
|
35
|
+
The HTML element name. Required for rendering.
|
|
36
|
+
|
|
37
|
+
```javascript
|
|
38
|
+
{ t: 'div' } // <div></div>
|
|
39
|
+
{ t: 'button' } // <button></button>
|
|
40
|
+
{ t: 'input' } // <input>
|
|
41
|
+
{ t: 'h1' } // <h1></h1>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### `a` — Attributes
|
|
45
|
+
|
|
46
|
+
An object of HTML attributes. All standard attributes work, including event handlers.
|
|
47
|
+
|
|
48
|
+
```javascript
|
|
49
|
+
{
|
|
50
|
+
t: 'button',
|
|
51
|
+
a: {
|
|
52
|
+
class: 'bw-btn bw-btn-primary',
|
|
53
|
+
id: 'save-btn',
|
|
54
|
+
disabled: true,
|
|
55
|
+
onclick: function() { console.log('clicked'); },
|
|
56
|
+
'data-action': 'save',
|
|
57
|
+
style: 'margin-top: 1rem'
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Style can also be an object:
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
a: { style: { marginTop: '1rem', color: '#333' } }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
For composable styles, use `bw.s()` to merge multiple style objects:
|
|
69
|
+
|
|
70
|
+
```javascript
|
|
71
|
+
a: { style: bw.s({ display: 'flex' }, { alignItems: 'center' }, { gap: '1rem' }, { marginTop: '1rem' }) }
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`bw.s()` merges style objects into a style string — it's `Object.assign` for CSS with a string output. See [Thinking in Bitwrench](thinking-in-bitwrench.md) for full coverage.
|
|
75
|
+
|
|
76
|
+
> **Coming from React?** Attribute names use standard HTML casing (`class`, `onclick`, `tabindex`), not React's camelCase (`className`, `onClick`, `tabIndex`).
|
|
77
|
+
|
|
78
|
+
### `c` — Content
|
|
79
|
+
|
|
80
|
+
The element's content. This can be:
|
|
81
|
+
|
|
82
|
+
- **A string** — rendered as text content (HTML-escaped by default)
|
|
83
|
+
- **A TACO object** — rendered as a child element
|
|
84
|
+
- **An array** — each item rendered in order (strings, TACOs, or nested arrays)
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
// String content
|
|
88
|
+
{ t: 'p', c: 'Hello World' }
|
|
89
|
+
|
|
90
|
+
// Child TACO
|
|
91
|
+
{ t: 'div', c: { t: 'span', c: 'nested' } }
|
|
92
|
+
|
|
93
|
+
// Array of children
|
|
94
|
+
{ t: 'ul', c: [
|
|
95
|
+
{ t: 'li', c: 'First' },
|
|
96
|
+
{ t: 'li', c: 'Second' },
|
|
97
|
+
{ t: 'li', c: 'Third' }
|
|
98
|
+
]}
|
|
99
|
+
|
|
100
|
+
// Mixed content
|
|
101
|
+
{ t: 'div', c: [
|
|
102
|
+
{ t: 'h2', c: 'Title' },
|
|
103
|
+
'Some paragraph text',
|
|
104
|
+
{ t: 'button', c: 'Click me' }
|
|
105
|
+
]}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### Content escaping
|
|
109
|
+
|
|
110
|
+
Content is HTML-escaped by default. The text `<script>alert('xss')</script>` renders as literal text, not as a script tag.
|
|
111
|
+
|
|
112
|
+
To include raw HTML, use `bw.raw()` or set `o: { raw: true }`:
|
|
113
|
+
|
|
114
|
+
```javascript
|
|
115
|
+
// Escaped (safe, default)
|
|
116
|
+
{ t: 'div', c: '<b>bold</b>' }
|
|
117
|
+
// Renders: <b>bold</b>
|
|
118
|
+
|
|
119
|
+
// Raw HTML (opt-in)
|
|
120
|
+
{ t: 'div', c: bw.raw('<b>bold</b>') }
|
|
121
|
+
// Renders: <b>bold</b>
|
|
122
|
+
|
|
123
|
+
// Or via options
|
|
124
|
+
{ t: 'div', c: '<b>bold</b>', o: { raw: true } }
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### `o` — Options
|
|
128
|
+
|
|
129
|
+
The options key carries state, lifecycle hooks, and component behavior. This is where TACO goes beyond simple HTML description.
|
|
130
|
+
|
|
131
|
+
```javascript
|
|
132
|
+
{
|
|
133
|
+
t: 'div',
|
|
134
|
+
o: {
|
|
135
|
+
// Component state
|
|
136
|
+
state: { count: 0, items: [] },
|
|
137
|
+
|
|
138
|
+
// Render function (called by bw.update)
|
|
139
|
+
render: function(el) {
|
|
140
|
+
bw.DOM(el, { t: 'span', c: 'Count: ' + el._bw_state.count });
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// Lifecycle hooks
|
|
144
|
+
mounted: function(el) { console.log('in the DOM'); },
|
|
145
|
+
unmount: function(el) { console.log('being removed'); },
|
|
146
|
+
|
|
147
|
+
// Skip content escaping
|
|
148
|
+
raw: true,
|
|
149
|
+
|
|
150
|
+
// Component handle -- methods exposed on el.bw (v2.0.19+)
|
|
151
|
+
handle: {
|
|
152
|
+
increment: function(el) { el._bw_state.count++; bw.update(el); }
|
|
153
|
+
},
|
|
154
|
+
|
|
155
|
+
// Content slots -- auto-generates el.bw.setX()/getX() (v2.0.19+)
|
|
156
|
+
slots: {
|
|
157
|
+
title: '.my_title',
|
|
158
|
+
content: '.my_body'
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
See [State Management](state-management.md) for detailed coverage of `o.state`, `o.render`, lifecycle hooks, `o.handle`, and `o.slots`.
|
|
165
|
+
|
|
166
|
+
## Rendering TACO objects
|
|
167
|
+
|
|
168
|
+
TACO objects are data. To produce actual output, pass them to a rendering function:
|
|
169
|
+
|
|
170
|
+
```javascript
|
|
171
|
+
// Render to HTML string (works in Node.js and browsers)
|
|
172
|
+
const html = bw.html({ t: 'div', c: 'Hello' });
|
|
173
|
+
// '<div>Hello</div>'
|
|
174
|
+
|
|
175
|
+
// Create a live DOM element (browser only)
|
|
176
|
+
const el = bw.createDOM({ t: 'div', c: 'Hello' });
|
|
177
|
+
// HTMLDivElement
|
|
178
|
+
|
|
179
|
+
// Mount into an existing DOM element (browser only)
|
|
180
|
+
bw.DOM('#app', { t: 'div', c: 'Hello' });
|
|
181
|
+
// Replaces contents of #app
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
`bw.DOM()` cleans up any previous content (running unmount hooks, clearing state) before mounting new content. This prevents memory leaks when re-rendering.
|
|
185
|
+
|
|
186
|
+
## bw.h() — concise TACO constructor
|
|
187
|
+
|
|
188
|
+
`bw.h()` is a helper that produces TACO objects from positional arguments. It's a convenience — the return value is a plain `{t, a, c, o}` object, identical to what you'd write by hand.
|
|
189
|
+
|
|
190
|
+
```javascript
|
|
191
|
+
bw.h('div') // { t: 'div' }
|
|
192
|
+
bw.h('p', { class: 'intro' }, 'Hello') // { t: 'p', a: { class: 'intro' }, c: 'Hello' }
|
|
193
|
+
bw.h('span', null, 'text') // { t: 'span', c: 'text' } (null attrs omitted)
|
|
194
|
+
bw.h('div', null, [ // { t: 'div', c: [...] }
|
|
195
|
+
bw.h('li', null, 'one'),
|
|
196
|
+
bw.h('li', null, 'two')
|
|
197
|
+
])
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
A common pattern is to alias `bw.h` for compact code:
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
var h = bw.h;
|
|
204
|
+
|
|
205
|
+
var footer = h('footer', { class: 'bw_bg_dark bw_text_light bw_py_4' }, [
|
|
206
|
+
h('p', null, 'Built with bitwrench.'),
|
|
207
|
+
h('p', null, h('a', { href: '/about' }, 'About'))
|
|
208
|
+
]);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The output is serializable (assuming no function values), works with `bw.html()`, `bw.DOM()`, `bw.createDOM()`, bwserve, and everywhere else TACO is accepted. Mix `bw.h()` calls freely with `make*()` returns and hand-written TACO — they all produce the same thing.
|
|
212
|
+
|
|
213
|
+
> **When to use `bw.h()` vs. hand-written TACO**: Use `bw.h()` for structural glue — wrapper divs, footers, headings — where the `{t:, a:, c:}` key syntax feels heavy. For complex nodes with `o:` (state, lifecycle), write full TACO — the named keys are clearer.
|
|
214
|
+
|
|
215
|
+
## Composition patterns
|
|
216
|
+
|
|
217
|
+
Because TACO is just JavaScript, you compose UI with standard language features:
|
|
218
|
+
|
|
219
|
+
### Variables
|
|
220
|
+
|
|
221
|
+
```javascript
|
|
222
|
+
const title = { t: 'h1', c: 'Dashboard' };
|
|
223
|
+
const sidebar = { t: 'aside', c: 'Menu' };
|
|
224
|
+
const main = { t: 'main', c: [title, { t: 'p', c: 'Content' }] };
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
### Functions
|
|
228
|
+
|
|
229
|
+
```javascript
|
|
230
|
+
function userCard(user) {
|
|
231
|
+
return {
|
|
232
|
+
t: 'div', a: { class: 'card' },
|
|
233
|
+
c: [
|
|
234
|
+
{ t: 'h3', c: user.name },
|
|
235
|
+
{ t: 'p', c: user.email }
|
|
236
|
+
]
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const cards = users.map(userCard);
|
|
241
|
+
bw.DOM('#list', { t: 'div', c: cards });
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Conditionals
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
const content = isLoggedIn
|
|
248
|
+
? { t: 'div', c: 'Welcome back' }
|
|
249
|
+
: { t: 'div', c: 'Please log in' };
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### Loops
|
|
253
|
+
|
|
254
|
+
```javascript
|
|
255
|
+
const rows = data.map(item => ({
|
|
256
|
+
t: 'tr', c: [
|
|
257
|
+
{ t: 'td', c: item.name },
|
|
258
|
+
{ t: 'td', c: String(item.value) }
|
|
259
|
+
]
|
|
260
|
+
}));
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## TACO and the component library
|
|
264
|
+
|
|
265
|
+
The `make*()` functions in bitwrench's component library return TACO objects:
|
|
266
|
+
|
|
267
|
+
```javascript
|
|
268
|
+
const card = bw.makeCard({ title: 'Users', content: '1,234' });
|
|
269
|
+
// Returns a TACO object — not HTML, not DOM
|
|
270
|
+
|
|
271
|
+
bw.DOM('#app', card); // Mount it
|
|
272
|
+
const html = bw.html(card); // Or render to string
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
This means you can compose library components the same way you compose raw TACOs:
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
bw.DOM('#app', {
|
|
279
|
+
t: 'div', c: [
|
|
280
|
+
bw.makeNavbar({ brand: 'My App', items: [...] }),
|
|
281
|
+
bw.makeContainer({ children: [
|
|
282
|
+
bw.makeCard({ title: 'Stats', content: '42' }),
|
|
283
|
+
bw.makeTable({ data: rows, sortable: true })
|
|
284
|
+
]})
|
|
285
|
+
]
|
|
286
|
+
});
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
See [Component Library](component-library.md) for all available components.
|
|
290
|
+
|
|
291
|
+
## Custom components without BCCL
|
|
292
|
+
|
|
293
|
+
The `make*()` component library (BCCL) is a convenience, not a requirement. You can write your own component factories that return TACO objects — bitwrench doesn't care where the TACO comes from.
|
|
294
|
+
|
|
295
|
+
```javascript
|
|
296
|
+
// Your own component factory — no BCCL needed
|
|
297
|
+
function myStatusBadge(status) {
|
|
298
|
+
var colors = { ok: '#4caf50', warn: '#ff9800', error: '#f44336' };
|
|
299
|
+
return {
|
|
300
|
+
t: 'span',
|
|
301
|
+
a: {
|
|
302
|
+
class: 'status-badge',
|
|
303
|
+
style: 'background: ' + (colors[status] || '#999')
|
|
304
|
+
+ '; color: #fff; padding: 0.25rem 0.75rem;'
|
|
305
|
+
+ ' border-radius: 999px; font-size: 0.8rem;'
|
|
306
|
+
},
|
|
307
|
+
c: status.toUpperCase()
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
bw.DOM('#app', myStatusBadge('ok'));
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
You can also wrap existing CSS frameworks (Bootstrap, Tailwind, etc.) in TACO objects:
|
|
315
|
+
|
|
316
|
+
```javascript
|
|
317
|
+
// Bootstrap button as a TACO
|
|
318
|
+
{ t: 'button', a: { class: 'btn btn-primary' }, c: 'Save' }
|
|
319
|
+
|
|
320
|
+
// Tailwind card as a TACO
|
|
321
|
+
{
|
|
322
|
+
t: 'div',
|
|
323
|
+
a: { class: 'bg-white rounded-lg shadow-md p-6' },
|
|
324
|
+
c: [
|
|
325
|
+
{ t: 'h3', a: { class: 'text-lg font-semibold' }, c: 'Title' },
|
|
326
|
+
{ t: 'p', a: { class: 'text-gray-600 mt-2' }, c: 'Content' }
|
|
327
|
+
]
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Bitwrench doesn't care where your CSS classes come from — it just renders the TACO to HTML/DOM. The `make*()` functions use bitwrench's built-in CSS classes (`bw_card`, `bw_btn`, etc.), but that's a choice, not a constraint.
|
|
332
|
+
|
|
333
|
+
To make a custom component reactive, add `o.state` and `o.render`:
|
|
334
|
+
|
|
335
|
+
```javascript
|
|
336
|
+
bw.DOM('#app', {
|
|
337
|
+
t: 'span',
|
|
338
|
+
o: {
|
|
339
|
+
state: { label: 'OK', color: '#4caf50' },
|
|
340
|
+
render: function(el) {
|
|
341
|
+
var s = el._bw_state;
|
|
342
|
+
bw.DOM(el, {
|
|
343
|
+
t: 'span',
|
|
344
|
+
a: { class: 'status-badge',
|
|
345
|
+
style: 'background:' + s.color + '; color:#fff; padding:0.25rem 0.75rem; border-radius:999px;' },
|
|
346
|
+
c: s.label
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
// Later, update state from outside:
|
|
353
|
+
var el = bw.$('.status-badge')[0].parentElement;
|
|
354
|
+
el._bw_state.label = 'ERROR';
|
|
355
|
+
el._bw_state.color = '#f44336';
|
|
356
|
+
bw.update(el);
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
## TACO beyond the browser
|
|
360
|
+
|
|
361
|
+
Because TACO objects are plain data, they work in contexts beyond browser rendering:
|
|
362
|
+
|
|
363
|
+
- **Server-side rendering**: `bw.html(taco)` produces HTML strings in Node.js
|
|
364
|
+
- **Static site generation**: The `bwcli` command converts files to styled HTML pages
|
|
365
|
+
- **Server-driven UI**: A server can push TACO objects to the browser via SSE, and the client renders them with `bw.DOM()`
|
|
366
|
+
- **Serialization**: TACO objects (without function values) can be JSON-serialized and sent over the network
|
|
367
|
+
- **Testing**: TACO objects can be inspected and compared as data without needing a DOM
|
|
368
|
+
|
|
369
|
+
This is a deliberate design choice. The separation between "what to render" (TACO) and "how to render it" (bw.DOM, bw.html) means the same component definition works across all these contexts.
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
For a deeper exploration of bitwrench's design philosophy, see [Thinking in Bitwrench](thinking-in-bitwrench.md).
|
package/docs/theming.md
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
# Theming
|
|
2
|
+
|
|
3
|
+
Bitwrench generates CSS at runtime from seed colors. You provide two or three hex colors, and the theme engine derives a complete design system — buttons, cards, alerts, tables, and every other component get consistent, coordinated styling.
|
|
4
|
+
|
|
5
|
+
There is no CSS preprocessor, no build step, and no external stylesheet to manage. The generated CSS is injected into the document as a `<style>` element.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
bw.loadStyles({
|
|
11
|
+
primary: '#0077b6',
|
|
12
|
+
secondary: '#90e0ef'
|
|
13
|
+
});
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This single call:
|
|
17
|
+
|
|
18
|
+
1. Derives a full color palette from your two seed colors (9 color families, 8 shades each)
|
|
19
|
+
2. Generates scoped CSS for all bitwrench components
|
|
20
|
+
3. Generates an alternate palette (light/dark counterpart)
|
|
21
|
+
4. Injects both stylesheets into the document
|
|
22
|
+
|
|
23
|
+
Every bitwrench component rendered after this call uses the generated theme.
|
|
24
|
+
|
|
25
|
+
## Theme presets
|
|
26
|
+
|
|
27
|
+
Bitwrench ships with 12 built-in presets:
|
|
28
|
+
|
|
29
|
+
| Preset | Primary | Secondary | Character |
|
|
30
|
+
|--------|---------|-----------|-----------|
|
|
31
|
+
| `teal` | `#006666` | `#6c757d` | Default, neutral |
|
|
32
|
+
| `ocean` | `#0077b6` | `#90e0ef` | Cool, professional |
|
|
33
|
+
| `sunset` | `#e76f51` | `#264653` | Warm, bold |
|
|
34
|
+
| `forest` | `#2d6a4f` | `#95d5b2` | Natural, calm |
|
|
35
|
+
| `slate` | `#343a40` | `#adb5bd` | Minimal, gray |
|
|
36
|
+
| `rose` | `#e11d48` | `#fda4af` | Vibrant, modern |
|
|
37
|
+
| `indigo` | `#4f46e5` | `#a5b4fc` | Deep, technical |
|
|
38
|
+
| `amber` | `#d97706` | `#fbbf24` | Energetic, warm |
|
|
39
|
+
| `emerald` | `#059669` | `#6ee7b7` | Fresh, growth |
|
|
40
|
+
| `nord` | `#5e81ac` | `#88c0d0` | Muted, Scandinavian |
|
|
41
|
+
| `coral` | `#ef6461` | `#4a7c7e` | Friendly, balanced |
|
|
42
|
+
| `midnight` | `#1e3a5f` | `#7c8db5` | Dark, serious |
|
|
43
|
+
|
|
44
|
+
Use a preset by name:
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
bw.loadStyles(bw.THEME_PRESETS.ocean);
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or pass the preset colors directly:
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
bw.loadStyles({
|
|
54
|
+
primary: '#0077b6',
|
|
55
|
+
secondary: '#90e0ef',
|
|
56
|
+
tertiary: '#00b4d8'
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Configuration options
|
|
61
|
+
|
|
62
|
+
The full config object:
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
bw.loadStyles({
|
|
66
|
+
// Seed colors (primary and secondary are required)
|
|
67
|
+
primary: '#0077b6', // Brand color
|
|
68
|
+
secondary: '#90e0ef', // Accent color
|
|
69
|
+
tertiary: '#00b4d8', // Third accent (defaults to primary)
|
|
70
|
+
|
|
71
|
+
// Semantic colors (optional — sensible defaults derived from seeds)
|
|
72
|
+
success: '#198754',
|
|
73
|
+
danger: '#dc3545',
|
|
74
|
+
warning: '#b38600',
|
|
75
|
+
info: '#0891b2',
|
|
76
|
+
light: '#f8f9fa',
|
|
77
|
+
dark: '#212529',
|
|
78
|
+
|
|
79
|
+
// Surface colors (optional)
|
|
80
|
+
background: '#ffffff', // Page background
|
|
81
|
+
surface: '#f8f9fa', // Card/panel background
|
|
82
|
+
|
|
83
|
+
// Layout tokens
|
|
84
|
+
spacing: 'normal', // 'compact' | 'normal' | 'spacious'
|
|
85
|
+
radius: 'md', // 'none' | 'sm' | 'md' | 'lg' | 'pill'
|
|
86
|
+
|
|
87
|
+
// Typography
|
|
88
|
+
fontSize: 1.0, // Base font size scale factor
|
|
89
|
+
typeRatio: 'normal', // 'tight' | 'normal' | 'relaxed' | 'dramatic'
|
|
90
|
+
|
|
91
|
+
// Visual depth
|
|
92
|
+
elevation: 'md', // 'flat' | 'sm' | 'md' | 'lg'
|
|
93
|
+
motion: 'standard', // 'reduced' | 'standard' | 'expressive'
|
|
94
|
+
|
|
95
|
+
// Color harmonization (0 to 1)
|
|
96
|
+
harmonize: 0.20, // Shift semantic colors toward primary hue
|
|
97
|
+
|
|
98
|
+
// Injection behavior
|
|
99
|
+
inject: true // Set to false to get CSS without injecting
|
|
100
|
+
});
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Spacing presets
|
|
104
|
+
|
|
105
|
+
Control padding across all components:
|
|
106
|
+
|
|
107
|
+
| Preset | Button | Card | Alert | Table cell | Input |
|
|
108
|
+
|--------|--------|------|-------|------------|-------|
|
|
109
|
+
| `compact` | 0.25rem 0.75rem | 0.75rem 1rem | 0.5rem 1rem | 0.5rem 0.75rem | 0.25rem 0.75rem |
|
|
110
|
+
| `normal` | 0.5rem 1rem | 1.5rem 1.5rem | 0.75rem 1.5rem | 0.75rem 1rem | 0.5rem 0.75rem |
|
|
111
|
+
| `spacious` | 0.75rem 1.5rem | 2rem 2rem | 1rem 1.5rem | 1rem 1.5rem | 0.75rem 1rem |
|
|
112
|
+
|
|
113
|
+
### Radius presets
|
|
114
|
+
|
|
115
|
+
Control border-radius across all components:
|
|
116
|
+
|
|
117
|
+
| Preset | Button | Card | Badge | Alert | Input |
|
|
118
|
+
|--------|--------|------|-------|-------|-------|
|
|
119
|
+
| `none` | 0 | 0 | 0 | 0 | 0 |
|
|
120
|
+
| `sm` | 4px | 4px | 0.25rem | 4px | 4px |
|
|
121
|
+
| `md` | 6px | 8px | 0.375rem | 8px | 6px |
|
|
122
|
+
| `lg` | 10px | 12px | 0.5rem | 12px | 10px |
|
|
123
|
+
| `pill` | 50rem | 1rem | 50rem | 1rem | 50rem |
|
|
124
|
+
|
|
125
|
+
### Type ratio presets
|
|
126
|
+
|
|
127
|
+
Control the modular scale for heading sizes:
|
|
128
|
+
|
|
129
|
+
| Preset | Ratio | Effect |
|
|
130
|
+
|--------|-------|--------|
|
|
131
|
+
| `tight` | 1.2 | Subtle size differences between headings |
|
|
132
|
+
| `normal` | 1.33 | Balanced (minor third scale) |
|
|
133
|
+
| `relaxed` | 1.5 | Pronounced heading hierarchy |
|
|
134
|
+
| `dramatic` | 1.618 | Golden ratio — large headings, compact body |
|
|
135
|
+
|
|
136
|
+
### Elevation presets
|
|
137
|
+
|
|
138
|
+
Control box-shadow depth:
|
|
139
|
+
|
|
140
|
+
| Preset | Description |
|
|
141
|
+
|--------|-------------|
|
|
142
|
+
| `flat` | No shadows |
|
|
143
|
+
| `sm` | Subtle shadows |
|
|
144
|
+
| `md` | Standard depth (default) |
|
|
145
|
+
| `lg` | Pronounced shadows |
|
|
146
|
+
|
|
147
|
+
### Motion presets
|
|
148
|
+
|
|
149
|
+
Control transition timing:
|
|
150
|
+
|
|
151
|
+
| Preset | Fast | Normal | Slow | Easing |
|
|
152
|
+
|--------|------|--------|------|--------|
|
|
153
|
+
| `reduced` | 100ms | 150ms | 200ms | ease-out |
|
|
154
|
+
| `standard` | 100ms | 200ms | 300ms | ease-out |
|
|
155
|
+
| `expressive` | 150ms | 300ms | 500ms | cubic-bezier(0.34, 1.56, 0.64, 1) |
|
|
156
|
+
|
|
157
|
+
## The palette
|
|
158
|
+
|
|
159
|
+
`bw.makeStyles()` returns an object with the full generated palette:
|
|
160
|
+
|
|
161
|
+
```javascript
|
|
162
|
+
var theme = bw.makeStyles({
|
|
163
|
+
primary: '#0077b6',
|
|
164
|
+
secondary: '#90e0ef'
|
|
165
|
+
});
|
|
166
|
+
bw.applyStyles(theme);
|
|
167
|
+
|
|
168
|
+
// theme.palette contains 9 color families, each with 8 shades:
|
|
169
|
+
theme.palette.primary.base; // '#0077b6' — the seed color
|
|
170
|
+
theme.palette.primary.hover; // darker variant for hover states
|
|
171
|
+
theme.palette.primary.active; // darker still for active/pressed states
|
|
172
|
+
theme.palette.primary.light; // very light tint for backgrounds
|
|
173
|
+
theme.palette.primary.darkText; // dark variant for text
|
|
174
|
+
theme.palette.primary.border; // medium-light for borders
|
|
175
|
+
theme.palette.primary.focus; // semi-transparent for focus rings
|
|
176
|
+
theme.palette.primary.textOn; // '#fff' or '#000' — readable text on base
|
|
177
|
+
|
|
178
|
+
// Same 8 shades available for:
|
|
179
|
+
// secondary, tertiary, success, danger, warning, info, light, dark
|
|
180
|
+
|
|
181
|
+
// Surface colors (raw hex strings):
|
|
182
|
+
theme.palette.background; // '#ffffff'
|
|
183
|
+
theme.palette.surface; // '#f8f9fa'
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### How shade derivation works
|
|
187
|
+
|
|
188
|
+
From a single seed color, `bw.deriveShades()` produces eight coordinated variants:
|
|
189
|
+
|
|
190
|
+
| Shade | Derivation | Purpose |
|
|
191
|
+
|-------|-----------|---------|
|
|
192
|
+
| `base` | The seed color itself | Default button/badge/alert fill |
|
|
193
|
+
| `hover` | 10% darker | Hover state |
|
|
194
|
+
| `active` | 15% darker | Active/pressed state |
|
|
195
|
+
| `light` | 85% tinted with white | Light background (alert bg, card highlight) |
|
|
196
|
+
| `darkText` | 40% darker | Text color for dark-on-light layouts |
|
|
197
|
+
| `border` | 60% tinted with white | Border color |
|
|
198
|
+
| `focus` | 25% opacity of seed | Focus ring |
|
|
199
|
+
| `textOn` | `#fff` or `#000` | Readable text on the base color (WCAG contrast) |
|
|
200
|
+
|
|
201
|
+
### Color harmonization
|
|
202
|
+
|
|
203
|
+
When `harmonize` is set (default: 0.20), semantic colors (success, danger, warning, info) have their hue shifted slightly toward the primary color. This creates visual cohesion — a forest-themed app's success green will lean toward the forest primary, while an ocean-themed app's success green will lean toward blue.
|
|
204
|
+
|
|
205
|
+
Set `harmonize: 0` for pure, unmodified semantic colors.
|
|
206
|
+
|
|
207
|
+
## Primary and alternate palettes
|
|
208
|
+
|
|
209
|
+
Every theme has two palettes: primary and alternate. The alternate is derived automatically by inverting the luminance of each seed color.
|
|
210
|
+
|
|
211
|
+
- If your primary palette is light, the alternate will be dark
|
|
212
|
+
- If your primary palette is dark, the alternate will be light
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
var theme = bw.makeStyles({
|
|
216
|
+
primary: '#0077b6',
|
|
217
|
+
secondary: '#90e0ef'
|
|
218
|
+
});
|
|
219
|
+
bw.applyStyles(theme);
|
|
220
|
+
|
|
221
|
+
theme.isLightPrimary; // false — ocean primary is a dark blue
|
|
222
|
+
theme.alternate.palette; // light-inverted version of ocean
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Switching between palettes
|
|
226
|
+
|
|
227
|
+
```javascript
|
|
228
|
+
// Toggle between primary and alternate palettes
|
|
229
|
+
bw.toggleStyles();
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The toggle works by adding or removing the CSS class `.bw_theme_alt` on the `<html>` element. Both primary and alternate stylesheets are injected at theme generation time, so switching is instant -- no re-generation needed.
|
|
233
|
+
|
|
234
|
+
### Clearing a theme
|
|
235
|
+
|
|
236
|
+
```javascript
|
|
237
|
+
bw.clearStyles();
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
This removes the injected `<style>` elements and clears the internal theme cache. Call this before generating a new theme with different colors to prevent CSS accumulation.
|
|
241
|
+
|
|
242
|
+
## Using themes without injection
|
|
243
|
+
|
|
244
|
+
Set `inject: false` to get the CSS without adding it to the document:
|
|
245
|
+
|
|
246
|
+
```javascript
|
|
247
|
+
var theme = bw.makeStyles({
|
|
248
|
+
primary: '#0077b6',
|
|
249
|
+
secondary: '#90e0ef',
|
|
250
|
+
inject: false
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
// Use the CSS string however you want
|
|
254
|
+
console.log(theme.css); // primary CSS
|
|
255
|
+
console.log(theme.alternate.css); // alternate CSS
|
|
256
|
+
|
|
257
|
+
// Write to a file in Node.js
|
|
258
|
+
fs.writeFileSync('theme.css', theme.css + '\n' + theme.alternate.css);
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
This is useful for:
|
|
262
|
+
|
|
263
|
+
- Static site generation (write CSS to a file)
|
|
264
|
+
- Server-side rendering (include CSS in the HTML response)
|
|
265
|
+
- Theme export tools (let users download their theme)
|
|
266
|
+
|
|
267
|
+
## Multiple themes on one page
|
|
268
|
+
|
|
269
|
+
Themes are scoped by CSS class name. You can have multiple themes active simultaneously:
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
bw.loadStyles({ primary: '#0077b6', secondary: '#90e0ef' });
|
|
273
|
+
bw.loadStyles({ primary: '#e76f51', secondary: '#264653' });
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Apply themes to different sections by adding the theme name as a class:
|
|
277
|
+
|
|
278
|
+
```javascript
|
|
279
|
+
bw.DOM('#header', {
|
|
280
|
+
t: 'div', a: { class: 'ocean' },
|
|
281
|
+
c: bw.makeNavbar({ brand: 'App', dark: true })
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
bw.DOM('#sidebar', {
|
|
285
|
+
t: 'div', a: { class: 'sunset' },
|
|
286
|
+
c: bw.makeCard({ title: 'Sidebar' })
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
Components inside an `ocean`-classed container use ocean colors; components inside a `sunset`-classed container use sunset colors.
|
|
291
|
+
|
|
292
|
+
## Color utility functions
|
|
293
|
+
|
|
294
|
+
These functions are available for custom color work:
|
|
295
|
+
|
|
296
|
+
| Function | Description |
|
|
297
|
+
|----------|-------------|
|
|
298
|
+
| `bw.hexToHsl(hex)` | Convert hex to `[h, s, l]` array |
|
|
299
|
+
| `bw.hslToHex([h, s, l])` | Convert HSL array to hex |
|
|
300
|
+
| `bw.adjustLightness(hex, amount)` | Shift lightness by percentage points |
|
|
301
|
+
| `bw.mixColor(hex1, hex2, ratio)` | Linear interpolation between two colors |
|
|
302
|
+
| `bw.relativeLuminance(hex)` | WCAG 2.0 relative luminance (0–1) |
|
|
303
|
+
| `bw.textOnColor(hex)` | Returns `'#fff'` or `'#000'` for readable text |
|
|
304
|
+
| `bw.deriveShades(hex)` | Generate 8 shade variants from one color |
|
|
305
|
+
| `bw.derivePalette(config)` | Generate full palette from seed config |
|
|
306
|
+
|
|
307
|
+
> **Coming from Tailwind?** Bitwrench's shade derivation is similar to Tailwind's color scale (50–900), but generated algorithmically from a single seed rather than hand-tuned. The 8 shades map to specific UI roles (hover, active, focus ring) rather than numeric levels.
|
|
308
|
+
|
|
309
|
+
> **Coming from Bootstrap?** Bitwrench's theme generation replaces Bootstrap's Sass `$theme-colors` map and `tint-color()`/`shade-color()` functions. Instead of a build step with Sass variables, you call `bw.loadStyles()` at runtime.
|