bitwrench 2.0.31 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +210 -125
- package/dist/bitwrench-bccl.cjs.js +349 -188
- package/dist/bitwrench-bccl.cjs.min.js +2 -39
- package/dist/bitwrench-bccl.cjs.min.js.gz +0 -0
- package/dist/bitwrench-bccl.esm.js +349 -188
- package/dist/bitwrench-bccl.esm.min.js +2 -39
- package/dist/bitwrench-bccl.esm.min.js.gz +0 -0
- package/dist/bitwrench-bccl.umd.js +349 -188
- package/dist/bitwrench-bccl.umd.min.js +2 -39
- package/dist/bitwrench-bccl.umd.min.js.gz +0 -0
- package/dist/bitwrench-code-edit.cjs.js +17 -6
- package/dist/bitwrench-code-edit.cjs.min.js +2 -20
- package/dist/bitwrench-code-edit.es5.js +8 -3
- package/dist/bitwrench-code-edit.es5.min.js +2 -19
- package/dist/bitwrench-code-edit.esm.js +17 -6
- package/dist/bitwrench-code-edit.esm.min.js +2 -19
- package/dist/bitwrench-code-edit.umd.js +17 -6
- package/dist/bitwrench-code-edit.umd.min.js +2 -19
- 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 +2492 -1628
- package/dist/bitwrench-lean.cjs.min.js +2 -80
- package/dist/bitwrench-lean.cjs.min.js.gz +0 -0
- package/dist/bitwrench-lean.es5.js +2740 -1838
- package/dist/bitwrench-lean.es5.min.js +2 -49
- package/dist/bitwrench-lean.es5.min.js.gz +0 -0
- package/dist/bitwrench-lean.esm.js +2492 -1628
- package/dist/bitwrench-lean.esm.min.js +2 -80
- package/dist/bitwrench-lean.esm.min.js.gz +0 -0
- package/dist/bitwrench-lean.umd.js +2492 -1628
- package/dist/bitwrench-lean.umd.min.js +2 -80
- package/dist/bitwrench-lean.umd.min.js.gz +0 -0
- package/dist/bitwrench-util-color.cjs.js +251 -0
- package/dist/bitwrench-util-color.cjs.min.js +3 -0
- package/dist/bitwrench-util-color.es5.js +256 -0
- package/dist/bitwrench-util-color.es5.min.js +3 -0
- package/dist/bitwrench-util-color.esm.js +241 -0
- package/dist/bitwrench-util-color.esm.min.js +3 -0
- package/dist/bitwrench-util-color.umd.js +257 -0
- package/dist/bitwrench-util-color.umd.min.js +3 -0
- package/dist/bitwrench-util-css.cjs.js +2 -1
- package/dist/bitwrench-util-css.cjs.min.js +2 -21
- package/dist/bitwrench-util-css.es5.js +2 -1
- package/dist/bitwrench-util-css.es5.min.js +2 -20
- package/dist/bitwrench-util-css.esm.js +2 -1
- package/dist/bitwrench-util-css.esm.min.js +1 -19
- package/dist/bitwrench-util-css.umd.js +2 -1
- package/dist/bitwrench-util-css.umd.min.js +2 -20
- package/dist/bitwrench-util-css.umd.min.js.gz +0 -0
- package/dist/bitwrench.cjs.js +2826 -1801
- package/dist/bitwrench.cjs.min.js +2 -99
- package/dist/bitwrench.cjs.min.js.gz +0 -0
- package/dist/bitwrench.css +403 -479
- package/dist/bitwrench.d.ts +70 -73
- package/dist/bitwrench.es5.js +3106 -2020
- package/dist/bitwrench.es5.min.js +2 -67
- package/dist/bitwrench.es5.min.js.gz +0 -0
- package/dist/bitwrench.esm.js +2826 -1801
- package/dist/bitwrench.esm.min.js +2 -99
- package/dist/bitwrench.esm.min.js.gz +0 -0
- package/dist/bitwrench.min.css +1 -1
- package/dist/bitwrench.umd.js +2826 -1801
- package/dist/bitwrench.umd.min.js +2 -99
- package/dist/bitwrench.umd.min.js.gz +0 -0
- package/dist/builds.json +222 -134
- package/dist/bwserve.cjs.js +308 -298
- package/dist/bwserve.d.ts +157 -0
- package/dist/bwserve.esm.js +309 -299
- package/dist/sri.json +54 -46
- package/docs/README.md +6 -3
- package/docs/app-patterns.md +7 -6
- package/docs/bitwrench-for-wasm.md +53 -54
- package/docs/bitwrench-mcp.md +2 -2
- package/docs/bitwrench-northstar-principles.md +406 -0
- package/docs/bitwrench-taco-schema-discussion.md +2 -2
- package/docs/bitwrench_api.md +191 -106
- package/docs/bitwrench_typescript_usage.md +5 -5
- package/docs/bw-attach.md +29 -75
- package/docs/bwserve.md +200 -168
- package/docs/cli.md +36 -12
- package/docs/component-cheatsheet.md +2 -2
- package/docs/component-library.md +4 -4
- package/docs/component-lifecycle.md +234 -0
- package/docs/drift-lint.md +268 -0
- package/docs/framework-translation-table.md +4 -4
- package/docs/llm-bitwrench-guide.md +60 -50
- package/docs/routing.md +11 -13
- package/docs/state-management.md +110 -109
- package/docs/taco-format.md +13 -14
- package/docs/theming.md +13 -3
- package/docs/thinking-in-bitwrench.md +858 -983
- package/docs/tutorial-bwserve.md +37 -36
- package/docs/tutorial-embedded.md +10 -21
- package/docs/tutorial-website.md +2 -2
- package/package.json +38 -7
- package/readme.html +262 -160
- package/src/bitwrench-bccl-entry.js +2 -2
- package/src/bitwrench-bccl.js +346 -185
- package/src/bitwrench-code-edit.js +16 -5
- package/src/bitwrench-color-utils.js +117 -181
- package/src/bitwrench-file-ops.js +2 -2
- package/src/bitwrench-lean.js +4 -3
- package/src/bitwrench-router.js +5 -2
- package/src/bitwrench-styles.js +420 -504
- package/src/bitwrench-util-color.js +240 -0
- package/src/bitwrench-util-css.js +1 -0
- package/src/bitwrench-utils.js +4 -0
- package/src/bitwrench.d.ts +70 -73
- package/src/bitwrench.h +5 -0
- package/src/bitwrench.js +1939 -933
- package/src/bwserve/attach.js +0 -1
- package/src/bwserve/bwclient.js +172 -32
- package/src/bwserve/bwshell.js +0 -4
- package/src/bwserve/client.js +59 -220
- package/src/bwserve/index.js +78 -42
- package/src/bwserve.d.ts +157 -0
- package/src/bwserve.h +5 -0
- package/src/cli/attach.js +12 -75
- package/src/cli/convert.js +2 -2
- package/src/cli/serve.js +37 -35
- package/src/generate-css.js +1 -1
- package/src/mcp/knowledge.js +4 -4
- package/src/mcp/live.js +21 -13
- package/src/mcp/tools.js +0 -1
- package/src/version.js +3 -7
package/docs/bwserve.md
CHANGED
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
bwserve is a server-side library that lets you build interactive web UIs entirely from Node.js. Your application state lives on the server, and the server pushes rendering commands to the browser over SSE (Server-Sent Events). User interactions are sent back as actions via HTTP POST.
|
|
6
6
|
|
|
7
|
-
This is the same pattern as Streamlit (Python), Phoenix LiveView (Elixir), and htmx — but bwserve sends TACO objects (bitwrench's {t, a, c, o} format), not HTML strings. The browser already has bitwrench loaded (~
|
|
7
|
+
This is the same pattern as Streamlit (Python), Phoenix LiveView (Elixir), and htmx — but bwserve sends TACO objects (bitwrench's {t, a, c, o} format), not HTML strings. The browser already has bitwrench loaded (~45KB), so it renders TACO natively with no build step.
|
|
8
8
|
|
|
9
9
|
**Key characteristics:**
|
|
10
10
|
- Zero runtime dependencies — only Node.js stdlib (`http`, `fs`, `path`)
|
|
11
11
|
- Auto-generates the client page (loads bitwrench, opens SSE, wires actions)
|
|
12
12
|
- Same protocol works for Node.js servers and ESP32/Arduino embedded devices
|
|
13
|
-
-
|
|
13
|
+
- 13 protocol message types (hello, mount, patch, append, replace, remove, refresh, update, message, batch, call, listen, unlisten)
|
|
14
14
|
|
|
15
15
|
## Architecture
|
|
16
16
|
|
|
@@ -22,18 +22,16 @@ This is the same pattern as Streamlit (Python), Phoenix LiveView (Elixir), and h
|
|
|
22
22
|
│ bwclient.js ├──────────────> app.page('/', fn) │
|
|
23
23
|
│ (opens SSE) │ │ │
|
|
24
24
|
│ │<──SSE──────── │ DOM operations: │
|
|
25
|
-
│ bw.apply() │ {type,
|
|
25
|
+
│ bw.apply() │ {type,taco} │ client.mount() │
|
|
26
26
|
│ -> bw.DOM() │ │ client.patch() │
|
|
27
27
|
│ -> bw.patch() │ │ client.append() │
|
|
28
28
|
│ │ │ client.remove() │
|
|
29
29
|
│ │ │ client.batch() │
|
|
30
30
|
│ │ │ │
|
|
31
|
-
│ │ │
|
|
32
|
-
│ │ │ client.register() │
|
|
31
|
+
│ │ │ Other: │
|
|
33
32
|
│ │ │ client.call() │
|
|
34
|
-
│ │ │ client.
|
|
35
|
-
│
|
|
36
|
-
│ data-bw-action │ │ │
|
|
33
|
+
│ │ │ client.message() │
|
|
34
|
+
│ bw_act_* class │ │ client.listen() │
|
|
37
35
|
│ conn.sendAction │──POST───────> │ client.on(action,fn) │
|
|
38
36
|
│ │ {action,data}│ │
|
|
39
37
|
└──────────────────┘ └──────────────────────┘
|
|
@@ -44,7 +42,7 @@ This is the same pattern as Streamlit (Python), Phoenix LiveView (Elixir), and h
|
|
|
44
42
|
2. Shell loads bitwrench from `/bw/lib/bitwrench.umd.js` and opens SSE
|
|
45
43
|
3. SSE triggers page handler → server sends TACO rendering commands
|
|
46
44
|
4. Browser applies each message via `bw.apply()` → DOM updates
|
|
47
|
-
5. User clicks → `
|
|
45
|
+
5. User clicks → `bw_act_*` class triggers POST → server handler runs
|
|
48
46
|
6. Server sends more messages → browser updates. Loop continues.
|
|
49
47
|
|
|
50
48
|
## Quick Start
|
|
@@ -57,28 +55,28 @@ var app = bwserve.create({ port: 7902 });
|
|
|
57
55
|
var count = 0;
|
|
58
56
|
|
|
59
57
|
app.page('/', function(client) {
|
|
60
|
-
// 1.
|
|
61
|
-
client.
|
|
58
|
+
// 1. Mount the initial UI as a TACO tree
|
|
59
|
+
client.mount('#app', {
|
|
62
60
|
t: 'div', a: { style: 'padding:24px' }, c: [
|
|
63
61
|
{ t: 'h1', c: 'Counter' },
|
|
64
62
|
{ t: 'p', c: [
|
|
65
63
|
'Count: ',
|
|
66
64
|
{ t: 'span', a: { id: 'count', style: 'font-weight:bold' }, c: '0' }
|
|
67
65
|
]},
|
|
68
|
-
{ t: 'button', a: {
|
|
69
|
-
{ t: 'button', a: {
|
|
66
|
+
{ t: 'button', a: { class: 'bw_bccl_btn bw_primary bw_act_increment' }, c: '+1' },
|
|
67
|
+
{ t: 'button', a: { class: 'bw_bccl_btn bw_bccl_btn_outline bw_secondary bw_act_reset' }, c: 'Reset' }
|
|
70
68
|
]
|
|
71
69
|
});
|
|
72
70
|
|
|
73
71
|
// 2. Handle user actions
|
|
74
72
|
client.on('increment', function() {
|
|
75
73
|
count++;
|
|
76
|
-
client.patch('count', String(count));
|
|
74
|
+
client.patch('count', { text: String(count) });
|
|
77
75
|
});
|
|
78
76
|
|
|
79
77
|
client.on('reset', function() {
|
|
80
78
|
count = 0;
|
|
81
|
-
client.patch('count', '0');
|
|
79
|
+
client.patch('count', { text: '0' });
|
|
82
80
|
});
|
|
83
81
|
});
|
|
84
82
|
|
|
@@ -91,7 +89,7 @@ Save as `server.js`, run `node server.js`, open `http://localhost:7902`.
|
|
|
91
89
|
|
|
92
90
|
## Protocol Messages
|
|
93
91
|
|
|
94
|
-
bwserve uses
|
|
92
|
+
bwserve uses 13 message types. All messages are stamped with `v: 1` (wire protocol version) and use `ref` for target references and `taco` for TACO payloads.
|
|
95
93
|
|
|
96
94
|
### DOM Operations (5 types)
|
|
97
95
|
|
|
@@ -99,64 +97,64 @@ These modify the browser's DOM tree:
|
|
|
99
97
|
|
|
100
98
|
| Type | Purpose | Server Method | Client Action |
|
|
101
99
|
|------|---------|---------------|---------------|
|
|
102
|
-
| `
|
|
103
|
-
| `patch` | Update text/attributes | `client.patch(
|
|
104
|
-
| `append` | Add child element | `client.append(
|
|
105
|
-
| `remove` | Remove element | `client.remove(
|
|
100
|
+
| `mount` | Mount TACO at selector | `client.mount(ref, taco)` | `bw.DOM(ref, taco)` |
|
|
101
|
+
| `patch` | Update text/attributes | `client.patch(ref, fields)` | `bw.patch(ref, fields)` |
|
|
102
|
+
| `append` | Add child element | `client.append(ref, taco)` | `ref.appendChild(bw.create(taco))` |
|
|
103
|
+
| `remove` | Remove element | `client.remove(ref)` | `bw.unmount(el); el.remove()` |
|
|
106
104
|
| `batch` | Multiple operations | `client.batch(ops)` | Execute each op in sequence |
|
|
107
105
|
|
|
108
|
-
###
|
|
109
|
-
|
|
110
|
-
These invoke functions or execute code on the client:
|
|
106
|
+
### Communication Operations (5 types)
|
|
111
107
|
|
|
112
108
|
| Type | Purpose | Server Method | Client Action |
|
|
113
109
|
|------|---------|---------------|---------------|
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
|
|
118
|
-
|
|
110
|
+
| `call` | Invoke built-in function | `client.call(name, ...args)` | Call built-in function (scrollTo, focus, download, etc.) |
|
|
111
|
+
| `message` | Component dispatch | `client.message(ref, action, data)` | `bw.message(ref, action, data)` |
|
|
112
|
+
| `listen` | Subscribe to topic | `client.listen(topic, handler)` | Forward matching events to server |
|
|
113
|
+
| `unlisten` | Unsubscribe from topic | (automatic on disconnect) | Stop forwarding events |
|
|
114
|
+
| `hello` | Handshake | (automatic on connect) | Establish connection |
|
|
119
115
|
|
|
120
|
-
|
|
121
|
-
|------|---------|---------------|---------------|
|
|
122
|
-
| `message` | Component dispatch | `client.message(target, action, data)` | `bw.message(target, action, data)` |
|
|
116
|
+
> **Note:** `exec` and `register` were removed in v2.1 for security reasons (arbitrary server-defined code should never cross the wire). `query` remains as a safe eval of caller-supplied expressions. Use `bw_act_*` class patterns for user actions, and `client.call()` for built-in client-side operations like scrollTo, focus, download, clipboard, and redirect.
|
|
123
117
|
|
|
124
118
|
### Message Schemas
|
|
125
119
|
|
|
120
|
+
All messages include `v: 1` (wire protocol version).
|
|
121
|
+
|
|
126
122
|
```json
|
|
127
123
|
// --- DOM Operations ---
|
|
128
124
|
|
|
129
|
-
//
|
|
130
|
-
{ "type": "
|
|
125
|
+
// mount — full subtree mount
|
|
126
|
+
{ "v": 1, "type": "mount", "ref": "#app", "taco": {"t":"div","c":"Hello"} }
|
|
131
127
|
|
|
132
|
-
// patch — lightweight text/attribute update
|
|
133
|
-
{ "type": "patch", "
|
|
134
|
-
{ "type": "patch", "
|
|
128
|
+
// patch — lightweight text/attribute update (discriminated fields)
|
|
129
|
+
{ "v": 1, "type": "patch", "ref": "counter", "text": "42" }
|
|
130
|
+
{ "v": 1, "type": "patch", "ref": "status", "text": "active", "attrs": {"class": "done"} }
|
|
135
131
|
|
|
136
132
|
// append — add a child
|
|
137
|
-
{ "type": "append", "
|
|
133
|
+
{ "v": 1, "type": "append", "ref": "#list", "taco": {"t":"li","c":"New item"} }
|
|
138
134
|
|
|
139
135
|
// remove — delete from DOM
|
|
140
|
-
{ "type": "remove", "
|
|
136
|
+
{ "v": 1, "type": "remove", "ref": "#old-item" }
|
|
141
137
|
|
|
142
138
|
// batch — multi-update
|
|
143
|
-
{ "type": "batch", "ops": [
|
|
144
|
-
{ "type": "patch", "
|
|
145
|
-
{ "type": "patch", "
|
|
139
|
+
{ "v": 1, "type": "batch", "ops": [
|
|
140
|
+
{ "type": "patch", "ref": "a", "text": "1" },
|
|
141
|
+
{ "type": "patch", "ref": "b", "text": "2" }
|
|
146
142
|
]}
|
|
147
143
|
|
|
148
|
-
// ---
|
|
144
|
+
// --- Communication Operations ---
|
|
145
|
+
|
|
146
|
+
// call — invoke a built-in function
|
|
147
|
+
{ "v": 1, "type": "call", "name": "focus", "args": ["#search-input"] }
|
|
148
|
+
{ "v": 1, "type": "call", "name": "download", "args": ["report.csv", "id,name\n1,Alice", "text/csv"] }
|
|
149
149
|
|
|
150
|
-
//
|
|
151
|
-
{ "type": "
|
|
150
|
+
// message — component dispatch
|
|
151
|
+
{ "v": 1, "type": "message", "ref": "#my-component", "action": "update", "data": {"value": 42} }
|
|
152
152
|
|
|
153
|
-
//
|
|
154
|
-
{ "
|
|
155
|
-
{ "type": "call", "name": "focus", "args": ["#search-input"] }
|
|
156
|
-
{ "type": "call", "name": "download", "args": ["report.csv", "id,name\n1,Alice", "text/csv"] }
|
|
153
|
+
// listen — subscribe to topic
|
|
154
|
+
{ "v": 1, "type": "listen", "topic": "bw:lifecycle" }
|
|
157
155
|
|
|
158
|
-
//
|
|
159
|
-
{ "
|
|
156
|
+
// hello — handshake (sent automatically on connect)
|
|
157
|
+
{ "v": 1, "type": "hello" }
|
|
160
158
|
```
|
|
161
159
|
|
|
162
160
|
### Target Resolution
|
|
@@ -166,16 +164,16 @@ All DOM operation targets are resolved using:
|
|
|
166
164
|
| Pattern | Resolution | Example |
|
|
167
165
|
|---------|-----------|---------|
|
|
168
166
|
| `#selector` | CSS selector via `querySelector` | `#app`, `#counter` |
|
|
169
|
-
| `.selector` | CSS class selector | `.
|
|
170
|
-
| `bare-string` | `getElementById`, then `bw.
|
|
167
|
+
| `.selector` | CSS class selector | `.bw_bccl_card` |
|
|
168
|
+
| `bare-string` | `getElementById`, then `bw.el()` fallback | `counter` |
|
|
171
169
|
|
|
172
170
|
**Best practice:** Use simple `id` attributes for patchable elements:
|
|
173
171
|
```javascript
|
|
174
|
-
// Server
|
|
175
|
-
client.
|
|
172
|
+
// Server mounts with id:
|
|
173
|
+
client.mount('#app', { t: 'span', a: { id: 'count' }, c: '0' });
|
|
176
174
|
|
|
177
175
|
// Later, server patches by id:
|
|
178
|
-
client.patch('count', '42');
|
|
176
|
+
client.patch('count', { text: '42' });
|
|
179
177
|
```
|
|
180
178
|
|
|
181
179
|
### Actions (Client → Server)
|
|
@@ -189,18 +187,18 @@ Content-Type: application/json
|
|
|
189
187
|
{ "action": "increment", "data": { "inputValue": "hello" } }
|
|
190
188
|
```
|
|
191
189
|
|
|
192
|
-
**Wiring actions** —
|
|
190
|
+
**Wiring actions** — use `bw_act_*` CSS classes on elements:
|
|
193
191
|
|
|
194
192
|
```javascript
|
|
195
|
-
// Server sends this TACO:
|
|
196
|
-
{ t: 'button', a: {
|
|
193
|
+
// Server sends this TACO with bw_act_* class:
|
|
194
|
+
{ t: 'button', a: { class: 'bw_bccl_btn bw_act_save' }, c: 'Save' }
|
|
197
195
|
|
|
198
196
|
// When clicked, client auto-POSTs:
|
|
199
197
|
{ action: 'save', data: {} }
|
|
200
198
|
|
|
201
199
|
// Server handles it:
|
|
202
200
|
client.on('save', function(data) {
|
|
203
|
-
client.patch('status', 'Saved!');
|
|
201
|
+
client.patch('status', { text: 'Saved!' });
|
|
204
202
|
});
|
|
205
203
|
```
|
|
206
204
|
|
|
@@ -219,19 +217,16 @@ Create a bwserve application.
|
|
|
219
217
|
| `static` | string | null | Static file directory |
|
|
220
218
|
| `theme` | string/object | null | Theme preset name or config |
|
|
221
219
|
| `injectBitwrench` | boolean | true | Auto-inject bitwrench UMD + CSS |
|
|
222
|
-
| `allowExec` | boolean | false | Enable `exec` messages on client (see warning below) |
|
|
223
220
|
| `allowScreenshot` | boolean | false | Enable `client.screenshot()` capability |
|
|
224
221
|
| `keepAliveInterval` | number | 15000 | SSE keep-alive interval in ms |
|
|
225
222
|
|
|
226
|
-
> **Start without `allowExec`.** The `register/call` pattern (Tier 1 + Tier 2) handles 95% of use cases — send named functions once, invoke them by name with safe argument passing. Only enable `allowExec: true` if you genuinely need to evaluate arbitrary code strings on the client. When in doubt, leave it off.
|
|
227
|
-
|
|
228
223
|
### `app.page(path, handler)`
|
|
229
224
|
|
|
230
225
|
Register a page handler. The `handler` function is called with a `BwServeClient` when a browser connects via SSE.
|
|
231
226
|
|
|
232
227
|
```javascript
|
|
233
228
|
app.page('/', function(client) {
|
|
234
|
-
client.
|
|
229
|
+
client.mount('#app', { t: 'div', c: 'Hello' });
|
|
235
230
|
});
|
|
236
231
|
|
|
237
232
|
app.page('/dashboard', function(client) {
|
|
@@ -258,16 +253,16 @@ Send a protocol message to all connected clients. Useful for dashboards, notific
|
|
|
258
253
|
|
|
259
254
|
```javascript
|
|
260
255
|
// Broadcast a patch to all browsers:
|
|
261
|
-
app.broadcast({ type: 'patch',
|
|
256
|
+
app.broadcast({ type: 'patch', ref: 'status', text: 'System OK' });
|
|
262
257
|
|
|
263
258
|
// Target a specific client by setting clientId:
|
|
264
|
-
app.broadcast({ type: 'patch',
|
|
259
|
+
app.broadcast({ type: 'patch', ref: 'msg', text: 'Hello', clientId: 'c1' });
|
|
265
260
|
|
|
266
261
|
// Broadcast a batch update:
|
|
267
262
|
app.broadcast({
|
|
268
263
|
type: 'batch', ops: [
|
|
269
|
-
{ type: 'patch',
|
|
270
|
-
{ type: 'patch',
|
|
264
|
+
{ type: 'patch', ref: 'users', text: '342' },
|
|
265
|
+
{ type: 'patch', ref: 'orders', text: '28' }
|
|
271
266
|
]
|
|
272
267
|
});
|
|
273
268
|
```
|
|
@@ -278,20 +273,19 @@ app.broadcast({
|
|
|
278
273
|
|
|
279
274
|
| Method | Protocol Type | Description |
|
|
280
275
|
|--------|--------------|-------------|
|
|
281
|
-
| `client.
|
|
282
|
-
| `client.patch(
|
|
283
|
-
| `client.append(
|
|
284
|
-
| `client.remove(
|
|
276
|
+
| `client.mount(ref, taco)` | `mount` | Mount TACO tree at ref |
|
|
277
|
+
| `client.patch(ref, fields)` | `patch` | Update element with discriminated fields (e.g. `{text:'...'}`, `{attrs:{...}}`) |
|
|
278
|
+
| `client.append(ref, taco)` | `append` | Add TACO as child of ref |
|
|
279
|
+
| `client.remove(ref)` | `remove` | Remove element from DOM |
|
|
285
280
|
| `client.batch(ops)` | `batch` | Send multiple operations atomically |
|
|
286
|
-
| `client.message(target, action, data)` | `message` | Dispatch to el.bw[action] |
|
|
287
281
|
|
|
288
|
-
####
|
|
282
|
+
#### Communication
|
|
289
283
|
|
|
290
284
|
| Method | Protocol Type | Description |
|
|
291
285
|
|--------|--------------|-------------|
|
|
292
|
-
| `client.
|
|
293
|
-
| `client.
|
|
294
|
-
| `client.
|
|
286
|
+
| `client.call(name, ...args)` | `call` | Invoke built-in function |
|
|
287
|
+
| `client.message(ref, action, data)` | `message` | Dispatch to el.bw[action] |
|
|
288
|
+
| `client.listen(topic, handler)` | `listen` | Subscribe to client-side topic |
|
|
295
289
|
|
|
296
290
|
#### Connection Management
|
|
297
291
|
|
|
@@ -343,7 +337,7 @@ var img = await client.screenshot('#dashboard', {
|
|
|
343
337
|
});
|
|
344
338
|
|
|
345
339
|
// LLM visual feedback loop
|
|
346
|
-
client.
|
|
340
|
+
client.mount('#app', myCard);
|
|
347
341
|
var img = await client.screenshot('#app', { maxWidth: 800 });
|
|
348
342
|
var feedback = await visionModel.evaluate(img.data);
|
|
349
343
|
// refine TACO based on feedback...
|
|
@@ -352,7 +346,7 @@ var feedback = await visionModel.evaluate(img.data);
|
|
|
352
346
|
### How it works
|
|
353
347
|
|
|
354
348
|
1. Server calls `client.screenshot(selector, options)` — returns a Promise
|
|
355
|
-
2. On first call, a capture function is
|
|
349
|
+
2. On first call, a capture function is set up on the client
|
|
356
350
|
3. The capture function is invoked via `call` with the selector and options
|
|
357
351
|
4. Client lazy-loads html2canvas (vendored, served from `/bw/lib/vendor/html2canvas.min.js`)
|
|
358
352
|
5. html2canvas renders the DOM element to a `<canvas>`
|
|
@@ -365,34 +359,16 @@ var feedback = await visionModel.evaluate(img.data);
|
|
|
365
359
|
- **DOM-level capture:** html2canvas reads the DOM — it cannot see other tabs, OS windows, or anything outside the page
|
|
366
360
|
- **No external requests:** html2canvas is vendored locally, not loaded from a CDN
|
|
367
361
|
|
|
368
|
-
## Server-to-Client
|
|
369
|
-
|
|
370
|
-
Beyond DOM operations, the server can invoke functions and execute code on the client. This is organized in three tiers:
|
|
371
|
-
|
|
372
|
-
### Tier 1: `client.register(name, body)`
|
|
362
|
+
## Server-to-Client Operations
|
|
373
363
|
|
|
374
|
-
|
|
364
|
+
Beyond DOM operations, the server can invoke functions on the client.
|
|
375
365
|
|
|
376
|
-
|
|
377
|
-
// Register an auto-scroll function on connect
|
|
378
|
-
client.register('autoScroll',
|
|
379
|
-
'function(sel) { var el = document.querySelector(sel); if (el) el.scrollTop = el.scrollHeight; }');
|
|
380
|
-
|
|
381
|
-
// Register a formatter
|
|
382
|
-
client.register('formatCurrency',
|
|
383
|
-
'function(id, val) { var el = document.getElementById(id); if (el) el.textContent = "$" + Number(val).toFixed(2); }');
|
|
384
|
-
```
|
|
366
|
+
### `client.call(name, ...args)`
|
|
385
367
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
Invoke a previously registered function or a built-in function by name. This is the workhorse for non-DOM operations — safe, lightweight, no code transfer.
|
|
368
|
+
Invoke a built-in function by name. This is the workhorse for non-DOM operations — safe, lightweight, no code transfer.
|
|
389
369
|
|
|
390
370
|
```javascript
|
|
391
|
-
// Call
|
|
392
|
-
client.call('autoScroll', '#chat');
|
|
393
|
-
client.call('formatCurrency', 'total', 42.5);
|
|
394
|
-
|
|
395
|
-
// Call built-in functions (always available, no registration needed)
|
|
371
|
+
// Call built-in functions (always available)
|
|
396
372
|
client.call('focus', '#search-input');
|
|
397
373
|
client.call('scrollTo', '#bottom');
|
|
398
374
|
client.call('download', 'report.csv', csvContent, 'text/csv');
|
|
@@ -412,33 +388,37 @@ client.call('log', 'Debug: user count =', users.length);
|
|
|
412
388
|
| `redirect` | `url` | Navigate to a URL |
|
|
413
389
|
| `log` | `...args` | console.log from the server |
|
|
414
390
|
|
|
415
|
-
###
|
|
391
|
+
### `client.message(ref, action, data)`
|
|
416
392
|
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
> **You probably don't need this.** If you're reaching for `exec`, consider whether `register` + `call` would work instead. `register` sends the function once; `call` invokes it by name with arguments. This covers scroll, focus, download, format, animate — essentially any reusable client-side behavior. `exec` is for truly one-off operations where registering a function would be wasteful.
|
|
393
|
+
Dispatch to a BCCL component's handle method. The client calls `bw.message(ref, action, data)` which invokes `el.bw[action](data)` on the target component.
|
|
420
394
|
|
|
421
395
|
```javascript
|
|
422
|
-
|
|
423
|
-
client.
|
|
424
|
-
client.exec("window.scrollTo(0, 0)");
|
|
396
|
+
client.message('#my-slider', 'setValue', 75);
|
|
397
|
+
client.message('#my-modal', 'open', { title: 'Confirm' });
|
|
425
398
|
```
|
|
426
399
|
|
|
427
|
-
|
|
400
|
+
### `client.listen(topic, handler)`
|
|
401
|
+
|
|
402
|
+
Subscribe to a client-side topic. The client forwards matching events back to the server via the return route.
|
|
403
|
+
|
|
404
|
+
```javascript
|
|
405
|
+
client.listen('bw:lifecycle', function(data) {
|
|
406
|
+
console.log('Lifecycle event:', data);
|
|
407
|
+
});
|
|
408
|
+
```
|
|
428
409
|
|
|
429
410
|
### When to Use Each Tier
|
|
430
411
|
|
|
431
412
|
| Need | Use | Why |
|
|
432
413
|
|------|-----|-----|
|
|
433
|
-
| Update the DOM |
|
|
434
|
-
| Simple button click | `
|
|
435
|
-
| Scroll after append | `call("scrollTo", sel)` | Built-in, no
|
|
414
|
+
| Update the DOM | mount/patch/append/remove | Declarative, inspectable, safe |
|
|
415
|
+
| Simple button click | `bw_act_*` class | Zero code, just a class |
|
|
416
|
+
| Scroll after append | `call("scrollTo", sel)` | Built-in, no code transfer |
|
|
436
417
|
| Trigger file download | `call("download", ...)` | Built-in, safe |
|
|
437
|
-
|
|
|
438
|
-
|
|
|
439
|
-
| Production security | `call` (never `exec`) | Arguments can't inject code |
|
|
418
|
+
| Component interaction | `message(ref, action, data)` | Dispatches to el.bw[action] |
|
|
419
|
+
| Event subscription | `listen(topic, handler)` | Server observes client events |
|
|
440
420
|
|
|
441
|
-
> **Rule of thumb:** Start with `
|
|
421
|
+
> **Rule of thumb:** Start with `bw_act_*` classes + DOM operations. When you need client-side behavior, use `call` with built-in functions. For component communication, use `message`.
|
|
442
422
|
|
|
443
423
|
## Client API Reference
|
|
444
424
|
|
|
@@ -448,20 +428,19 @@ SSE connection management has moved to `bwclient.js`, which is auto-generated by
|
|
|
448
428
|
|
|
449
429
|
### `bw.apply(msg)`
|
|
450
430
|
|
|
451
|
-
Apply a single protocol message to the DOM. Called automatically by the shell connection, but also usable standalone for testing or custom transports. Handles all
|
|
431
|
+
Apply a single protocol message to the DOM. Called automatically by the shell connection, but also usable standalone for testing or custom transports. Handles all 13 message types.
|
|
452
432
|
|
|
453
433
|
```javascript
|
|
454
434
|
// DOM operations
|
|
455
|
-
bw.apply({ type: '
|
|
456
|
-
bw.apply({ type: 'patch',
|
|
457
|
-
bw.apply({ type: 'append',
|
|
458
|
-
bw.apply({ type: 'remove',
|
|
459
|
-
bw.apply({ type: 'batch', ops: [msg1, msg2] });
|
|
435
|
+
bw.apply({ v: 1, type: 'mount', ref: '#app', taco: { t: 'div', c: 'Hi' } });
|
|
436
|
+
bw.apply({ v: 1, type: 'patch', ref: 'counter', text: '42' });
|
|
437
|
+
bw.apply({ v: 1, type: 'append', ref: '#list', taco: { t: 'li', c: 'New' } });
|
|
438
|
+
bw.apply({ v: 1, type: 'remove', ref: '#old' });
|
|
439
|
+
bw.apply({ v: 1, type: 'batch', ops: [msg1, msg2] });
|
|
460
440
|
|
|
461
|
-
//
|
|
462
|
-
bw.apply({ type: '
|
|
463
|
-
bw.apply({ type: '
|
|
464
|
-
bw.apply({ type: 'exec', code: 'alert(1)' }); // needs allowExec
|
|
441
|
+
// Communication operations
|
|
442
|
+
bw.apply({ v: 1, type: 'call', name: 'scrollTo', args: ['#chat'] });
|
|
443
|
+
bw.apply({ v: 1, type: 'message', ref: '#comp', action: 'update', data: {} });
|
|
465
444
|
```
|
|
466
445
|
|
|
467
446
|
Returns `true` if the message was applied successfully, `false` otherwise.
|
|
@@ -472,14 +451,14 @@ Parse both strict JSON and r-prefix relaxed JSON. This is a state-machine parser
|
|
|
472
451
|
|
|
473
452
|
```javascript
|
|
474
453
|
// Strict JSON — passes through to JSON.parse():
|
|
475
|
-
bw.parseJSONFlex('{"type":"patch","
|
|
454
|
+
bw.parseJSONFlex('{"type":"patch","ref":"t","text":"42"}');
|
|
476
455
|
|
|
477
456
|
// r-prefix relaxed JSON (from ESP32 / C macros):
|
|
478
|
-
bw.parseJSONFlex("r{'type':'patch','
|
|
457
|
+
bw.parseJSONFlex("r{'type':'patch','ref':'t','text':'42'}");
|
|
479
458
|
|
|
480
459
|
// Handles apostrophes in values:
|
|
481
|
-
bw.parseJSONFlex("r{'
|
|
482
|
-
// → {
|
|
460
|
+
bw.parseJSONFlex("r{'text':'Barry\\'s Room'}");
|
|
461
|
+
// → { text: "Barry's Room" }
|
|
483
462
|
```
|
|
484
463
|
|
|
485
464
|
The shell connection calls `bw.parseJSONFlex()` on every incoming SSE message automatically. You only need to call it directly if you're building a custom transport or testing.
|
|
@@ -508,7 +487,7 @@ bwserve supports multiple transports:
|
|
|
508
487
|
|
|
509
488
|
Each protocol message is sent as a single SSE data frame:
|
|
510
489
|
```
|
|
511
|
-
data: {"type":"
|
|
490
|
+
data: {"v":1,"type":"mount","ref":"#app","taco":{"t":"div","c":"Hello"}}
|
|
512
491
|
|
|
513
492
|
```
|
|
514
493
|
|
|
@@ -539,10 +518,10 @@ For embedded C/C++ systems, composing JSON with double-quoted strings is painful
|
|
|
539
518
|
|
|
540
519
|
```
|
|
541
520
|
// Standard JSON in C (painful):
|
|
542
|
-
"{\"type\":\"patch\",\"
|
|
521
|
+
"{\"type\":\"patch\",\"ref\":\"temp\",\"text\":\"23.5\"}"
|
|
543
522
|
|
|
544
523
|
// r-prefix relaxed JSON (natural):
|
|
545
|
-
"r{'type':'patch','
|
|
524
|
+
"r{'type':'patch','ref':'temp','text':'23.5'}"
|
|
546
525
|
```
|
|
547
526
|
|
|
548
527
|
The `r` prefix tells the parser to convert single quotes to double quotes before parsing. The browser's `bw.parseJSONFlex()` handles this automatically.
|
|
@@ -599,10 +578,10 @@ bwcli serve -v
|
|
|
599
578
|
# Patch a value via curl:
|
|
600
579
|
curl -X POST http://localhost:9000 \
|
|
601
580
|
-H "Content-Type: application/json" \
|
|
602
|
-
-d '{"type":"patch","
|
|
581
|
+
-d '{"type":"patch","ref":"temp","text":"23.5 C"}'
|
|
603
582
|
|
|
604
583
|
# r-prefix relaxed JSON is also accepted:
|
|
605
|
-
curl -X POST http://localhost:9000 -d "r{'type':'patch','
|
|
584
|
+
curl -X POST http://localhost:9000 -d "r{'type':'patch','ref':'temp','text':'23.5'}"
|
|
606
585
|
```
|
|
607
586
|
|
|
608
587
|
### From Python
|
|
@@ -614,17 +593,77 @@ while True:
|
|
|
614
593
|
temp = 20 + random.random() * 10
|
|
615
594
|
requests.post("http://localhost:9000", json={
|
|
616
595
|
"type": "patch",
|
|
617
|
-
"
|
|
618
|
-
"
|
|
596
|
+
"ref": "temp",
|
|
597
|
+
"text": f"{temp:.1f} C"
|
|
619
598
|
})
|
|
620
599
|
time.sleep(2)
|
|
621
600
|
```
|
|
622
601
|
|
|
623
602
|
Both the input port and stdin mode accept strict JSON and r-prefix relaxed JSON. All messages are broadcast to every connected browser.
|
|
624
603
|
|
|
604
|
+
### Interactive Commands
|
|
605
|
+
|
|
606
|
+
Messages with a `"command"` field are interactive requests -- they return a JSON response instead of broadcasting. Use these to inspect or query connected clients.
|
|
607
|
+
|
|
608
|
+
```bash
|
|
609
|
+
# List connected clients
|
|
610
|
+
curl -X POST http://localhost:9000 -d '{"command":"clients"}'
|
|
611
|
+
# => {"ok":true,"clients":["att_x7k2m"]}
|
|
612
|
+
|
|
613
|
+
# Evaluate a JS expression in the browser
|
|
614
|
+
curl -X POST http://localhost:9000 -d '{"command":"query","code":"document.title"}'
|
|
615
|
+
# => {"ok":true,"result":"My Page","clientId":"att_x7k2m"}
|
|
616
|
+
|
|
617
|
+
# Get a DOM tree snapshot
|
|
618
|
+
curl -X POST http://localhost:9000 -d '{"command":"tree","selector":"#app","depth":2}'
|
|
619
|
+
# => {"ok":true,"result":{"tag":"div","id":"app","children":[...]},"clientId":"att_x7k2m"}
|
|
620
|
+
|
|
621
|
+
# Take a screenshot (requires --allow-screenshot)
|
|
622
|
+
curl -X POST http://localhost:9000 -d '{"command":"screenshot","selector":"body"}'
|
|
623
|
+
# => {"ok":true,"result":{"data":"<base64>","width":1024,"height":768,"format":"png"},"clientId":"att_x7k2m"}
|
|
624
|
+
|
|
625
|
+
# Mount a component
|
|
626
|
+
curl -X POST http://localhost:9000 -d '{"command":"mount","ref":"#app","taco":{"t":"h1","c":"Hello"}}'
|
|
627
|
+
# => {"ok":true,"clientId":"att_x7k2m"}
|
|
628
|
+
|
|
629
|
+
# Target a specific client
|
|
630
|
+
curl -X POST http://localhost:9000 -d '{"command":"query","code":"location.href","clientId":"att_x7k2m"}'
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
| Command | Required Fields | Returns | Notes |
|
|
634
|
+
|---------|----------------|---------|-------|
|
|
635
|
+
| `clients` | -- | `{ok, clients: [...]}` | List all connected client IDs |
|
|
636
|
+
| `query` | `code` | `{ok, result, clientId}` | Eval expression in browser; expressions auto-wrapped in `return()`, statements work too |
|
|
637
|
+
| `tree` | -- | `{ok, result, clientId}` | DOM tree snapshot; optional `selector` (default: body), `depth` (default: 3) |
|
|
638
|
+
| `screenshot` | -- | `{ok, result, clientId}` | Requires `--allow-screenshot` flag; optional `selector` |
|
|
639
|
+
| `mount` | `ref`, `taco` | `{ok, clientId}` | Mount TACO at selector |
|
|
640
|
+
| `patch` | `ref` | `{ok, clientId}` | Update text/attrs; extra fields forwarded |
|
|
641
|
+
| `listen` | `topic` | `{ok, clientId}` | Subscribe to DOM events |
|
|
642
|
+
| `unlisten` | `topic` | `{ok, clientId}` | Unsubscribe from events |
|
|
643
|
+
|
|
644
|
+
When no `clientId` is specified, the command routes to the most recently connected client.
|
|
645
|
+
|
|
646
|
+
### CLI Options
|
|
647
|
+
|
|
648
|
+
```
|
|
649
|
+
bwcli serve [dir] [options]
|
|
650
|
+
|
|
651
|
+
-p, --port <number> Browser-facing web port (default: 8080)
|
|
652
|
+
-l, --listen <number> Input port for protocol messages (default: 9000)
|
|
653
|
+
-b, --bind <address> Host/address to bind to (default: 0.0.0.0)
|
|
654
|
+
--stdin Read protocol messages from stdin
|
|
655
|
+
-t, --theme <name> Theme preset or hex colors ("#pri,#sec")
|
|
656
|
+
--title <string> Page title (default: "bwcli serve")
|
|
657
|
+
--no-dir-list Disable directory listings
|
|
658
|
+
--allow-screenshot Enable client.screenshot() capability
|
|
659
|
+
--open Open browser on start
|
|
660
|
+
-v, --verbose Verbose output
|
|
661
|
+
-h, --help Print help
|
|
662
|
+
```
|
|
663
|
+
|
|
625
664
|
## Complete Examples
|
|
626
665
|
|
|
627
|
-
### Counter (
|
|
666
|
+
### Counter (mount + patch + actions)
|
|
628
667
|
|
|
629
668
|
```javascript
|
|
630
669
|
import bwserve from 'bitwrench/bwserve';
|
|
@@ -633,16 +672,16 @@ var app = bwserve.create({ port: 7902 });
|
|
|
633
672
|
var count = 0;
|
|
634
673
|
|
|
635
674
|
app.page('/', function(client) {
|
|
636
|
-
client.
|
|
675
|
+
client.mount('#app', {
|
|
637
676
|
t: 'div', c: [
|
|
638
677
|
{ t: 'h2', c: 'Counter' },
|
|
639
678
|
{ t: 'span', a: { id: 'count' }, c: '0' },
|
|
640
|
-
{ t: 'button', a: {
|
|
679
|
+
{ t: 'button', a: { class: 'bw_act_inc' }, c: '+1' }
|
|
641
680
|
]
|
|
642
681
|
});
|
|
643
682
|
|
|
644
683
|
client.on('inc', function() {
|
|
645
|
-
client.patch('count', String(++count));
|
|
684
|
+
client.patch('count', { text: String(++count) });
|
|
646
685
|
});
|
|
647
686
|
});
|
|
648
687
|
|
|
@@ -655,10 +694,10 @@ app.listen();
|
|
|
655
694
|
app.page('/', function(client) {
|
|
656
695
|
var nextId = 1;
|
|
657
696
|
|
|
658
|
-
client.
|
|
697
|
+
client.mount('#app', {
|
|
659
698
|
t: 'div', c: [
|
|
660
699
|
{ t: 'input', a: { type: 'text', id: 'inp' } },
|
|
661
|
-
{ t: 'button', a: {
|
|
700
|
+
{ t: 'button', a: { class: 'bw_act_add' }, c: 'Add' },
|
|
662
701
|
{ t: 'ul', a: { id: 'list' } }
|
|
663
702
|
]
|
|
664
703
|
});
|
|
@@ -668,22 +707,25 @@ app.page('/', function(client) {
|
|
|
668
707
|
client.append('#list', {
|
|
669
708
|
t: 'li', a: { id: id }, c: [
|
|
670
709
|
data.inputValue || 'item',
|
|
671
|
-
{ t: 'button', a: {
|
|
710
|
+
{ t: 'button', a: { id: id + '-del', class: 'bw_act_del' }, c: 'x' }
|
|
672
711
|
]
|
|
673
712
|
});
|
|
674
713
|
});
|
|
675
714
|
|
|
676
715
|
client.on('del', function(data) {
|
|
677
|
-
if (data.
|
|
716
|
+
if (data.id) {
|
|
717
|
+
var itemId = data.id.replace('-del', '');
|
|
718
|
+
client.remove('#' + itemId);
|
|
719
|
+
}
|
|
678
720
|
});
|
|
679
721
|
});
|
|
680
722
|
```
|
|
681
723
|
|
|
682
|
-
### Dashboard (batch +
|
|
724
|
+
### Dashboard (batch + call)
|
|
683
725
|
|
|
684
726
|
```javascript
|
|
685
727
|
app.page('/', function(client) {
|
|
686
|
-
client.
|
|
728
|
+
client.mount('#app', {
|
|
687
729
|
t: 'div', c: [
|
|
688
730
|
{ t: 'span', a: { id: 'users' }, c: '0' },
|
|
689
731
|
{ t: 'span', a: { id: 'orders' }, c: '0' },
|
|
@@ -692,41 +734,33 @@ app.page('/', function(client) {
|
|
|
692
734
|
]
|
|
693
735
|
});
|
|
694
736
|
|
|
695
|
-
// Register a format function on the client
|
|
696
|
-
client.register('formatNum',
|
|
697
|
-
'function(id, val) { var el = document.getElementById(id); if (el) el.textContent = Number(val).toLocaleString(); }');
|
|
698
|
-
|
|
699
737
|
// Update every second
|
|
700
738
|
setInterval(function() {
|
|
701
739
|
var users = Math.floor(Math.random() * 500);
|
|
702
740
|
var orders = Math.floor(Math.random() * 50);
|
|
703
741
|
client.batch([
|
|
704
|
-
{ type: '
|
|
705
|
-
{ type: '
|
|
706
|
-
{ type: 'patch',
|
|
742
|
+
{ type: 'patch', ref: 'users', text: String(users) },
|
|
743
|
+
{ type: 'patch', ref: 'orders', text: String(orders) },
|
|
744
|
+
{ type: 'patch', ref: 'revenue', text: '$' + Math.floor(Math.random() * 10000) }
|
|
707
745
|
]);
|
|
708
746
|
}, 1000);
|
|
709
747
|
});
|
|
710
748
|
```
|
|
711
749
|
|
|
712
|
-
### Chat with Auto-scroll (append +
|
|
750
|
+
### Chat with Auto-scroll (append + call)
|
|
713
751
|
|
|
714
752
|
```javascript
|
|
715
753
|
app.page('/', function(client) {
|
|
716
|
-
client.
|
|
754
|
+
client.mount('#app', {
|
|
717
755
|
t: 'div', c: [
|
|
718
756
|
{ t: 'div', a: { id: 'chat', style: 'max-height:400px;overflow-y:auto' } },
|
|
719
757
|
{ t: 'div', a: { style: 'display:flex;gap:8px' }, c: [
|
|
720
758
|
{ t: 'input', a: { type: 'text', id: 'msg-input' } },
|
|
721
|
-
{ t: 'button', a: {
|
|
759
|
+
{ t: 'button', a: { class: 'bw_act_send' }, c: 'Send' }
|
|
722
760
|
]}
|
|
723
761
|
]
|
|
724
762
|
});
|
|
725
763
|
|
|
726
|
-
// Register auto-scroll for reuse after each message
|
|
727
|
-
client.register('scrollChat',
|
|
728
|
-
'function() { var el = document.getElementById("chat"); if (el) el.scrollTop = el.scrollHeight; }');
|
|
729
|
-
|
|
730
764
|
client.on('send', function(data) {
|
|
731
765
|
if (!data.inputValue) return;
|
|
732
766
|
|
|
@@ -734,7 +768,7 @@ app.page('/', function(client) {
|
|
|
734
768
|
client.append('#chat', {
|
|
735
769
|
t: 'div', a: { style: 'padding:4px' }, c: data.inputValue
|
|
736
770
|
});
|
|
737
|
-
client.call('
|
|
771
|
+
client.call('scrollTo', '#chat');
|
|
738
772
|
|
|
739
773
|
// Focus back on the input
|
|
740
774
|
client.call('focus', '#msg-input');
|
|
@@ -784,7 +818,7 @@ Once connected, you get a REPL:
|
|
|
784
818
|
bw> document.title
|
|
785
819
|
"My Page"
|
|
786
820
|
|
|
787
|
-
bw> bw.$('.
|
|
821
|
+
bw> bw.$('.bw_bccl_card').length
|
|
788
822
|
3
|
|
789
823
|
|
|
790
824
|
bw> /tree #app 2
|
|
@@ -812,7 +846,6 @@ Saved: page.png (1440x900, 245832 bytes)
|
|
|
812
846
|
|
|
813
847
|
| Command | Description |
|
|
814
848
|
|---------|-------------|
|
|
815
|
-
| `<expression>` | Evaluate JS in the browser (e.g., `document.title`) |
|
|
816
849
|
| `/tree [sel] [depth]` | DOM tree summary (default: body, depth 3) |
|
|
817
850
|
| `/screenshot [sel] [file]` | Capture element to PNG (requires `--allow-screenshot`) |
|
|
818
851
|
| `/mount <sel> <comp> [json]` | Mount a BCCL component |
|
|
@@ -820,22 +853,21 @@ Saved: page.png (1440x900, 245832 bytes)
|
|
|
820
853
|
| `/patch <id> <content>` | Update element text by ID |
|
|
821
854
|
| `/listen <sel> <event>` | Watch for DOM events |
|
|
822
855
|
| `/unlisten <sel> <event>` | Stop watching events |
|
|
823
|
-
| `/exec <code>` | Execute JS (fire-and-forget) |
|
|
824
856
|
| `/clients` | List connected clients |
|
|
825
857
|
| `/help`, `/quit` | Help / exit |
|
|
826
858
|
|
|
827
859
|
### Security
|
|
828
860
|
|
|
829
|
-
Attach mode
|
|
861
|
+
Attach mode is a debugging tool. The server binds to `localhost` by default. **Never expose to the public internet.**
|
|
830
862
|
|
|
831
863
|
For the full attach guide, see [bwcli attach documentation](bw-attach.md).
|
|
832
864
|
|
|
833
865
|
## Related
|
|
834
866
|
|
|
835
|
-
- [Protocol Reference Page](../pages/12-bwserve-protocol.html) — Interactive protocol reference with all
|
|
867
|
+
- [Protocol Reference Page](../pages/12-bwserve-protocol.html) — Interactive protocol reference with all 13 message types
|
|
836
868
|
- [Sandbox](../pages/14-bwserve-sandbox.html) — Try bwserve protocol in the browser (no server needed)
|
|
837
869
|
- [Screenshot Example](../examples/client-server/screenshot-server.js) — Runnable screenshot demo
|
|
838
|
-
- [Design Document](../dev/bw-client-server.md) —
|
|
870
|
+
- [Design Document](../dev/archive/bw-client-server.md) — Original (pre-v2.1) protocol design exploration, kept for history
|
|
839
871
|
- [CLI](cli.md) — The `bwcli` command for file conversion and pipe server
|
|
840
872
|
- [Attach Mode](bw-attach.md) — Full remote debugging REPL documentation
|
|
841
873
|
- [Embedded Tutorial](tutorial-embedded.md) — ESP32 IoT dashboard with C macros and r-prefix JSON
|