yaml-flow 4.0.0 → 5.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/browser/board-livegraph-runtime.js +1453 -0
- package/browser/board-livegraph-runtime.js.map +1 -0
- package/browser/card-compute.js +36 -17
- package/browser/live-cards.js +848 -109
- package/browser/live-cards.schema.json +46 -21
- package/dist/board-livegraph-runtime/index.cjs +1448 -0
- package/dist/board-livegraph-runtime/index.cjs.map +1 -0
- package/dist/board-livegraph-runtime/index.d.cts +101 -0
- package/dist/board-livegraph-runtime/index.d.ts +101 -0
- package/dist/board-livegraph-runtime/index.js +1441 -0
- package/dist/board-livegraph-runtime/index.js.map +1 -0
- package/dist/card-compute/index.cjs +159 -44
- package/dist/card-compute/index.cjs.map +1 -1
- package/dist/card-compute/index.d.cts +36 -11
- package/dist/card-compute/index.d.ts +36 -11
- package/dist/card-compute/index.js +156 -44
- package/dist/card-compute/index.js.map +1 -1
- package/dist/cli/board-live-cards-cli.cjs +476 -105
- package/dist/cli/board-live-cards-cli.cjs.map +1 -1
- package/dist/cli/board-live-cards-cli.d.cts +8 -16
- package/dist/cli/board-live-cards-cli.d.ts +8 -16
- package/dist/cli/board-live-cards-cli.js +476 -106
- package/dist/cli/board-live-cards-cli.js.map +1 -1
- package/dist/continuous-event-graph/index.cjs +74 -33
- package/dist/continuous-event-graph/index.cjs.map +1 -1
- package/dist/continuous-event-graph/index.d.cts +7 -23
- package/dist/continuous-event-graph/index.d.ts +7 -23
- package/dist/continuous-event-graph/index.js +73 -32
- package/dist/continuous-event-graph/index.js.map +1 -1
- package/dist/index.cjs +1440 -56
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -3
- package/dist/index.d.ts +21 -3
- package/dist/index.js +1434 -56
- package/dist/index.js.map +1 -1
- package/dist/journal-DRfJiheM.d.cts +28 -0
- package/dist/journal-NLYuqege.d.ts +28 -0
- package/dist/{journal-B_2JnBMF.d.ts → live-cards-bridge-Or7fdEJV.d.ts} +5 -32
- package/dist/{journal-BJDjWb5Q.d.cts → live-cards-bridge-vGJ6tMzN.d.cts} +5 -32
- package/dist/schedule-CMcZe5Ny.d.ts +21 -0
- package/dist/schedule-CiucyCan.d.cts +21 -0
- package/examples/browser/boards/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-form.json +3 -3
- package/examples/browser/boards/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/browser/boards/portfolio-tracker/cards/price-fetch.json +3 -3
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker-task-executor.cjs +96 -0
- package/examples/browser/boards/portfolio-tracker/portfolio-tracker.js +33 -5
- package/examples/browser/livecards-browser/index.html +37 -684
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +3 -3
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/cli/step-machine-cli/portfolio-tracker/cards/price-fetch.json +3 -3
- package/examples/cli/step-machine-cli/portfolio-tracker/handlers/update-holdings-cli.js +2 -2
- package/examples/example-board/board.yaml +23 -0
- package/examples/example-board/bootstrap_payload.json +1 -0
- package/examples/example-board/cards/card-chain-region-alert.json +39 -0
- package/examples/example-board/cards/card-chain-region-totals.json +26 -0
- package/examples/example-board/cards/card-chain-top-region.json +24 -0
- package/examples/example-board/cards/card-ex-actions.json +32 -0
- package/examples/example-board/cards/card-ex-chart.json +30 -0
- package/examples/example-board/cards/card-ex-filter.json +36 -0
- package/examples/example-board/cards/card-ex-filtered-by-preference.json +59 -0
- package/examples/example-board/cards/card-ex-form.json +91 -0
- package/examples/example-board/cards/card-ex-list.json +22 -0
- package/examples/example-board/cards/card-ex-markdown.json +17 -0
- package/examples/example-board/cards/card-ex-metric.json +19 -0
- package/examples/example-board/cards/card-ex-narrative.json +36 -0
- package/examples/example-board/cards/card-ex-source-http.json +28 -0
- package/examples/example-board/cards/card-ex-source.json +21 -0
- package/examples/example-board/cards/card-ex-status.json +35 -0
- package/examples/example-board/cards/card-ex-table.json +30 -0
- package/examples/example-board/cards/card-ex-todo.json +29 -0
- package/examples/example-board/demo-chat-handler.js +69 -0
- package/examples/example-board/demo-server-config.json +7 -0
- package/examples/example-board/demo-server.js +124 -0
- package/examples/example-board/demo-shell-browser.html +806 -0
- package/examples/example-board/demo-shell-with-server.html +280 -0
- package/examples/example-board/demo-shell.html +62 -0
- package/examples/example-board/demo-task-executor.js +255 -0
- package/examples/example-board/mock.db +15 -0
- package/examples/example-board/reusable-board-runtime-client.js +265 -0
- package/examples/example-board/reusable-runtime-artifacts-adapter.js +233 -0
- package/examples/example-board/reusable-server-runtime.js +1341 -0
- package/examples/index.html +16 -9
- package/examples/npm-libs/continuous-event-graph/live-cards-board.ts +17 -17
- package/examples/npm-libs/continuous-event-graph/live-portfolio-dashboard.ts +23 -23
- package/examples/step-machine-cli/portfolio-tracker/cards/holdings-table.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-form.json +3 -3
- package/examples/step-machine-cli/portfolio-tracker/cards/portfolio-value.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/cards/price-fetch.json +1 -1
- package/examples/step-machine-cli/portfolio-tracker/portfolio-tracker-task-executor.cjs +96 -0
- package/package.json +16 -2
- package/schema/card-runtime.schema.json +25 -0
- package/schema/live-cards.schema.json +46 -21
- package/browser/ingest-board.js +0 -296
- package/examples/ingest.js +0 -733
|
@@ -1,688 +1,41 @@
|
|
|
1
|
-
<!
|
|
1
|
+
<!doctype html>
|
|
2
2
|
<html lang="en">
|
|
3
3
|
<head>
|
|
4
|
-
<meta charset="
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1"
|
|
6
|
-
<title>LiveCards
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
<!-- LiveCards v3 engine -->
|
|
35
|
-
<script src="../../../browser/live-cards.js"></script>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>LiveCards Browser Demo Moved</title>
|
|
7
|
+
<meta http-equiv="refresh" content="0; url=../../example-board/demo-shell.html" />
|
|
8
|
+
<style>
|
|
9
|
+
body {
|
|
10
|
+
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
11
|
+
background: #f5f1e8;
|
|
12
|
+
color: #1f2937;
|
|
13
|
+
margin: 0;
|
|
14
|
+
min-height: 100vh;
|
|
15
|
+
display: grid;
|
|
16
|
+
place-items: center;
|
|
17
|
+
padding: 24px;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
main {
|
|
21
|
+
max-width: 680px;
|
|
22
|
+
background: #fffdf8;
|
|
23
|
+
border: 1px solid #d6cdbd;
|
|
24
|
+
border-radius: 16px;
|
|
25
|
+
padding: 28px;
|
|
26
|
+
box-shadow: 0 20px 50px rgba(56, 46, 33, 0.08);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
a {
|
|
30
|
+
color: #8a3b12;
|
|
31
|
+
}
|
|
32
|
+
</style>
|
|
36
33
|
</head>
|
|
37
|
-
<body
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
<details class="mb-4">
|
|
45
|
-
<summary class="h5 mb-0" style="cursor:pointer">LiveCards v3 — API Reference</summary>
|
|
46
|
-
<div class="row g-4 mt-2">
|
|
47
|
-
|
|
48
|
-
<!-- ---- Node Schema ---- -->
|
|
49
|
-
<div class="col-12">
|
|
50
|
-
<div class="card">
|
|
51
|
-
<div class="card-header fw-bold">Node Schema</div>
|
|
52
|
-
<div class="card-body small">
|
|
53
|
-
<p>Every node shares a common shape. Two types:</p>
|
|
54
|
-
<table class="table table-sm table-bordered mb-2">
|
|
55
|
-
<thead><tr><th>Key</th><th>Card (<code>type:"card"</code>)</th><th>Source (<code>type:"source"</code>)</th></tr></thead>
|
|
56
|
-
<tbody>
|
|
57
|
-
<tr><td><code>id</code></td><td colspan="2">Unique string identifier</td></tr>
|
|
58
|
-
<tr><td><code>meta</code></td><td colspan="2"><code>{ title, tags[] }</code></td></tr>
|
|
59
|
-
<tr><td><code>state</code></td><td colspan="2">Mutable persisted state object. Element binds read from <code>state.*</code></td></tr>
|
|
60
|
-
<tr><td><code>requires</code></td><td colspan="2"><code>[nodeId, ...]</code> — upstream dependencies, engine auto-subscribes</td></tr>
|
|
61
|
-
<tr><td><code>provides</code></td><td colspan="2"><code>[{ bindTo, src }]</code> — explicit downstream token bindings. <code>src</code> is a <code>state.*</code> / <code>computed_values.*</code> path</td></tr>
|
|
62
|
-
<tr><td><code>sources</code></td><td colspan="2"><code>[{ bindTo, outputFile, script?, optional?, kind?, ... }]</code> — data sources. <code>optional: true</code> sources don’t gate completion; absence of <code>outputFile</code> = undelivered.</td></tr>
|
|
63
|
-
<tr><td><code>compute</code></td><td colspan="2"><code>[{ bindTo, expr }]</code> — ordered JSONata steps writing to ephemeral <code>computed_values[bindTo]</code>. Never persisted.</td></tr>
|
|
64
|
-
<tr><td><code>view</code></td><td colspan="2"><code>{ elements[], layout, features }</code> — present on renderable cards; absent on source-only nodes</td></tr>
|
|
65
|
-
</tbody>
|
|
66
|
-
</table>
|
|
67
|
-
</div>
|
|
68
|
-
</div>
|
|
69
|
-
</div>
|
|
70
|
-
|
|
71
|
-
<!-- ---- LiveCard Engine API ---- -->
|
|
72
|
-
<div class="col-12 col-lg-6">
|
|
73
|
-
<div class="card h-100">
|
|
74
|
-
<div class="card-header fw-bold">LiveCard — Engine API</div>
|
|
75
|
-
<div class="card-body small">
|
|
76
|
-
<pre class="mb-0"><code>const engine = LiveCard.init({
|
|
77
|
-
resolve: (id) => node, // look up node by id
|
|
78
|
-
onPatch: (id, data) => {}, // persist config changes
|
|
79
|
-
onPatchState: (id, data) => {}, // persist state changes
|
|
80
|
-
onRefresh: (id) => {}, // trigger external fetch
|
|
81
|
-
onChat: (id, msg) => {}, // send chat message
|
|
82
|
-
markdown: (text) => html, // e.g. marked.parse
|
|
83
|
-
sanitize: (html) => safe, // e.g. DOMPurify.sanitize
|
|
84
|
-
chartLib: Chart, // Chart.js constructor
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
engine.render(node, el, opts?) // render a card node
|
|
88
|
-
engine.update(nodeId, patch) // in-place update (status, data)
|
|
89
|
-
engine.destroy(nodeId) // tear down one node
|
|
90
|
-
engine.destroyAll() // tear down all
|
|
91
|
-
engine.notify(nodeId, data?) // signal change → downstream
|
|
92
|
-
engine.subscribe(nodeId, cb) // listen; returns unsub fn
|
|
93
|
-
engine.appendChatMessage(id, role, text)
|
|
94
|
-
engine.registerRenderer(name, fn) // add custom element kind</code></pre>
|
|
95
|
-
</div>
|
|
96
|
-
</div>
|
|
97
|
-
</div>
|
|
98
|
-
|
|
99
|
-
<!-- ---- Board API ---- -->
|
|
100
|
-
<div class="col-12 col-lg-6">
|
|
101
|
-
<div class="card h-100">
|
|
102
|
-
<div class="card-header fw-bold">LiveCard.Board — Layout API</div>
|
|
103
|
-
<div class="card-body small">
|
|
104
|
-
<pre class="mb-0"><code>const board = LiveCard.Board(engine, el, {
|
|
105
|
-
nodes: [...], // Card + Source nodes
|
|
106
|
-
positions: { id: {x,y,w} },// canvas overrides for sources
|
|
107
|
-
mode: 'board', // 'board' (grid) | 'canvas' (DAG)
|
|
108
|
-
showNotes: true,
|
|
109
|
-
showChat: true,
|
|
110
|
-
defaultCol: 6, // Bootstrap col width fallback
|
|
111
|
-
canvas: {
|
|
112
|
-
snap: 20, edges: true,
|
|
113
|
-
zoom: { min: 0.25, max: 2, initial: 1 },
|
|
114
|
-
height: '600px', overflow: 'auto',
|
|
115
|
-
minWidth: 220, maxWidth: 450, defaultW: 350,
|
|
116
|
-
gapX: 280, gapY: 320, padX: 20, padY: 20,
|
|
117
|
-
cardMaxH: 300,
|
|
118
|
-
},
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
board.setMode('board' | 'canvas')
|
|
122
|
-
board.autoLayout() // topological L→R arrange
|
|
123
|
-
board.add(node) // add a node dynamically
|
|
124
|
-
board.remove(nodeId) // remove + destroy
|
|
125
|
-
board.reorder([ids]) // reorder in board mode
|
|
126
|
-
board.refresh() // re-render current mode
|
|
127
|
-
board.clear() // destroy all, empty board
|
|
128
|
-
board.destroy() // full teardown + DOM removal
|
|
129
|
-
board.mode // current mode (getter)
|
|
130
|
-
board.nodes // shallow copy of node list</code></pre>
|
|
131
|
-
</div>
|
|
132
|
-
</div>
|
|
133
|
-
</div>
|
|
134
|
-
|
|
135
|
-
<!-- ---- Element Kinds ---- -->
|
|
136
|
-
<div class="col-12 col-lg-6">
|
|
137
|
-
<div class="card h-100">
|
|
138
|
-
<div class="card-header fw-bold">Element Kinds (view.elements[])</div>
|
|
139
|
-
<div class="card-body small">
|
|
140
|
-
<table class="table table-sm table-bordered mb-2">
|
|
141
|
-
<thead><tr><th>Kind</th><th>Key <code>data.*</code> options</th></tr></thead>
|
|
142
|
-
<tbody>
|
|
143
|
-
<tr><td><code>table</code></td><td><code>bind, columns[], maxRows, sortable, placeholder</code></td></tr>
|
|
144
|
-
<tr><td><code>filter</code></td><td><code>bind, writeTo, fields</code></td></tr>
|
|
145
|
-
<tr><td><code>metric</code></td><td><code>bind</code></td></tr>
|
|
146
|
-
<tr><td><code>list</code></td><td><code>bind, maxRows, placeholder</code></td></tr>
|
|
147
|
-
<tr><td><code>chart</code></td><td><code>bind, chartType, chartOptions</code></td></tr>
|
|
148
|
-
<tr><td><code>form</code></td><td><code>writeTo, fields (JSON Schema)</code></td></tr>
|
|
149
|
-
<tr><td><code>notes</code></td><td><code>bind, writeTo</code></td></tr>
|
|
150
|
-
<tr><td><code>todo</code></td><td><code>bind, writeTo</code></td></tr>
|
|
151
|
-
<tr><td><code>alert</code></td><td><code>bind, thresholds: { green, amber }</code></td></tr>
|
|
152
|
-
<tr><td><code>narrative</code></td><td><code>bind</code></td></tr>
|
|
153
|
-
<tr><td><code>badge</code></td><td><code>bind, colorMap</code></td></tr>
|
|
154
|
-
<tr><td><code>text</code></td><td><code>bind, style: heading|muted|default</code></td></tr>
|
|
155
|
-
<tr><td><code>markdown</code></td><td><code>bind</code></td></tr>
|
|
156
|
-
<tr><td><code>custom</code></td><td><code>bind</code> (renders JSON)</td></tr>
|
|
157
|
-
</tbody>
|
|
158
|
-
</table>
|
|
159
|
-
<p class="mb-0">Common props: <code>kind, label, className, visible, data.bind, data.writeTo</code></p>
|
|
160
|
-
</div>
|
|
161
|
-
</div>
|
|
162
|
-
</div>
|
|
163
|
-
|
|
164
|
-
<!-- ---- CardCompute API ---- -->
|
|
165
|
-
<div class="col-12 col-lg-6">
|
|
166
|
-
<div class="card h-100">
|
|
167
|
-
<div class="card-header fw-bold">CardCompute — Compute API</div>
|
|
168
|
-
<div class="card-body small">
|
|
169
|
-
<pre><code>await CardCompute.run(node) // eval all node.compute → computed_values
|
|
170
|
-
CardCompute.eval(expr, node) // eval single expression
|
|
171
|
-
CardCompute.resolve(node, path) // deep-get "state.foo.bar"
|
|
172
|
-
CardCompute.registerFunction(name, fn) // add custom fn</code></pre>
|
|
173
|
-
<p class="fw-medium mb-1">53 built-in functions:</p>
|
|
174
|
-
<table class="table table-sm table-bordered mb-2">
|
|
175
|
-
<tbody>
|
|
176
|
-
<tr><td class="text-muted">Aggregates</td><td><code>sum avg min max count first last</code></td></tr>
|
|
177
|
-
<tr><td class="text-muted">Math</td><td><code>add sub mul div round abs mod</code></td></tr>
|
|
178
|
-
<tr><td class="text-muted">Compare</td><td><code>gt gte lt lte eq neq</code></td></tr>
|
|
179
|
-
<tr><td class="text-muted">Logic</td><td><code>and or not if</code></td></tr>
|
|
180
|
-
<tr><td class="text-muted">String</td><td><code>concat upper lower template join split trim</code></td></tr>
|
|
181
|
-
<tr><td class="text-muted">Collection</td><td><code>pluck filter map sort slice flat unique group flatten_keys entries from_entries length</code></td></tr>
|
|
182
|
-
<tr><td class="text-muted">Lookup</td><td><code>get default coalesce</code></td></tr>
|
|
183
|
-
<tr><td class="text-muted">Date</td><td><code>now diff_days format_date parse_date</code></td></tr>
|
|
184
|
-
<tr><td class="text-muted">Type</td><td><code>to_number to_string to_bool type_of is_null is_empty</code></td></tr>
|
|
185
|
-
</tbody>
|
|
186
|
-
</table>
|
|
187
|
-
<pre class="mb-0"><code>// JSONata expression — computed against { state, requires, computed_values }
|
|
188
|
-
{ "bindTo": "avg_revenue",
|
|
189
|
-
"expr": "$round($average(state.sales.revenue), 2)" }</code></pre>
|
|
190
|
-
</div>
|
|
191
|
-
</div>
|
|
192
|
-
</div>
|
|
193
|
-
|
|
194
|
-
<!-- ---- Files ---- -->
|
|
195
|
-
<div class="col-12">
|
|
196
|
-
<div class="card">
|
|
197
|
-
<div class="card-header fw-bold">Files</div>
|
|
198
|
-
<div class="card-body small">
|
|
199
|
-
<table class="table table-sm mb-0">
|
|
200
|
-
<tbody>
|
|
201
|
-
<tr><td><code>live-cards.js</code></td><td>Rendering engine + Board layout (browser, no dependencies except Bootstrap 5 CSS)</td></tr>
|
|
202
|
-
<tr><td><code>card-compute.js</code></td><td>Isomorphic compute module — UMD (browser global / Node.js / AMD). No DOM needed.</td></tr>
|
|
203
|
-
<tr><td><code>live-cards.schema.json</code></td><td>JSON Schema (draft-07) for Card and ExternalSource nodes</td></tr>
|
|
204
|
-
<tr><td><code>index.html</code></td><td>This demo page — all element kinds + ExternalSource nodes</td></tr>
|
|
205
|
-
</tbody>
|
|
206
|
-
</table>
|
|
207
|
-
<p class="mt-2 mb-0">Optional libs: <code>Chart.js</code> (charts), <code>marked</code> (markdown), <code>DOMPurify</code> (sanitize). Pass via <code>init(config)</code>.</p>
|
|
208
|
-
</div>
|
|
209
|
-
</div>
|
|
210
|
-
</div>
|
|
211
|
-
|
|
212
|
-
<div class="col-12">
|
|
213
|
-
<div class="card">
|
|
214
|
-
<div class="card-header fw-bold">Related Docs</div>
|
|
215
|
-
<div class="card-body small">
|
|
216
|
-
<p class="mb-2">This page focuses on the LiveCards browser runtime. Broader yaml-flow architecture and non-LiveCards APIs live in the main docs.</p>
|
|
217
|
-
<ul class="mb-0">
|
|
218
|
-
<li><a href="../../../docs/index.html">yaml-flow docs index</a> — framework modules and product surfaces</li>
|
|
219
|
-
<li><a href="../../../docs/browser-runtime-livecards-boards.html">browser runtime docs</a> — setup and browser integration details</li>
|
|
220
|
-
<li><a href="../../../docs/board-live-cards-cli.html">board-live-cards CLI reference</a> — server/runtime orchestration contracts</li>
|
|
221
|
-
<li><a href="../../index.html">examples index</a> — broader example catalog across yaml-flow</li>
|
|
222
|
-
</ul>
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
</div>
|
|
226
|
-
|
|
227
|
-
</div>
|
|
228
|
-
</details>
|
|
229
|
-
|
|
230
|
-
<!-- ====================================================================
|
|
231
|
-
Board Demo
|
|
232
|
-
==================================================================== -->
|
|
233
|
-
<div class="d-flex align-items-center gap-3 mb-3">
|
|
234
|
-
<h4 class="mb-0">LiveCards v3 — Node-based Board</h4>
|
|
235
|
-
<div class="btn-group btn-group-sm ms-auto" id="modeToggle">
|
|
236
|
-
<button class="btn btn-primary" data-mode="board">Board</button>
|
|
237
|
-
<button class="btn btn-outline-primary" data-mode="canvas">Canvas</button>
|
|
238
|
-
</div>
|
|
239
|
-
<button class="btn btn-sm btn-outline-secondary" id="autoLayoutBtn" style="display:none">Auto Layout</button>
|
|
240
|
-
</div>
|
|
241
|
-
<div id="boardRoot"></div>
|
|
242
|
-
</div>
|
|
243
|
-
|
|
244
|
-
<script>
|
|
245
|
-
(function () {
|
|
246
|
-
'use strict';
|
|
247
|
-
|
|
248
|
-
// =========================================================================
|
|
249
|
-
// Node definitions (v3 schema: id, type, meta, data, view, source, state, compute)
|
|
250
|
-
// =========================================================================
|
|
251
|
-
|
|
252
|
-
var nodes = {};
|
|
253
|
-
|
|
254
|
-
// ---- ExternalSource nodes (data-only, no view) ----
|
|
255
|
-
|
|
256
|
-
nodes['src-sales'] = {
|
|
257
|
-
id: 'src-sales',
|
|
258
|
-
meta: { title: 'Sales API', tags: ['api', 'sales'] },
|
|
259
|
-
provides: [{ bindTo: 'sales', src: 'state.raw' }],
|
|
260
|
-
sources: [{
|
|
261
|
-
bindTo: 'raw',
|
|
262
|
-
kind: 'api',
|
|
263
|
-
url_template: 'https://api.example.com/v1/sales?region={{region}}',
|
|
264
|
-
template_vars: { region: 'all' },
|
|
265
|
-
poll_interval: 300,
|
|
266
|
-
transform: 'data.items'
|
|
267
|
-
}],
|
|
268
|
-
state: {
|
|
269
|
-
status: 'fresh',
|
|
270
|
-
lastRun: new Date().toISOString(),
|
|
271
|
-
raw: [
|
|
272
|
-
{ region: 'North', product: 'Widget A', revenue: 12400, units: 310 },
|
|
273
|
-
{ region: 'South', product: 'Widget B', revenue: 8700, units: 220 },
|
|
274
|
-
{ region: 'East', product: 'Widget A', revenue: 15200, units: 380 },
|
|
275
|
-
{ region: 'West', product: 'Widget C', revenue: 6300, units: 125 },
|
|
276
|
-
{ region: 'North', product: 'Widget B', revenue: 9100, units: 230 },
|
|
277
|
-
]
|
|
278
|
-
}
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
nodes['src-metrics'] = {
|
|
282
|
-
id: 'src-metrics',
|
|
283
|
-
meta: { title: 'Metrics API', tags: ['api', 'monitoring'] },
|
|
284
|
-
provides: [{ bindTo: 'metrics', src: 'state.raw' }],
|
|
285
|
-
sources: [{
|
|
286
|
-
bindTo: 'raw',
|
|
287
|
-
kind: 'api',
|
|
288
|
-
url_template: 'https://api.example.com/v1/metrics',
|
|
289
|
-
poll_interval: 60
|
|
290
|
-
}],
|
|
291
|
-
state: {
|
|
292
|
-
status: 'fresh',
|
|
293
|
-
lastRun: new Date().toISOString(),
|
|
294
|
-
raw: { cpu: 72, memory: 45, disk: 38, latency_p50: 12, latency_p95: 45 }
|
|
295
|
-
}
|
|
296
|
-
};
|
|
297
|
-
|
|
298
|
-
// ---- Card nodes ----
|
|
299
|
-
|
|
300
|
-
nodes['flt1'] = {
|
|
301
|
-
id: 'flt1',
|
|
302
|
-
meta: { title: 'Filters' },
|
|
303
|
-
view: {
|
|
304
|
-
elements: [{
|
|
305
|
-
kind: 'filter',
|
|
306
|
-
className: 'col-12',
|
|
307
|
-
data: {
|
|
308
|
-
bind: 'state.options',
|
|
309
|
-
writeTo: 'state.selected',
|
|
310
|
-
fields: { properties: { region: { title: 'Region' }, product: { title: 'Product' } } }
|
|
311
|
-
}
|
|
312
|
-
}],
|
|
313
|
-
layout: { board: { col: 12, order: 0 }, canvas: { x: 40, y: 40, w: 400 } }
|
|
314
|
-
},
|
|
315
|
-
state: {
|
|
316
|
-
status: 'fresh',
|
|
317
|
-
options: { region: ['North','South','East','West'], product: ['Widget A','Widget B','Widget C'] },
|
|
318
|
-
selected: {}
|
|
319
|
-
}
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
nodes['tbl1'] = {
|
|
323
|
-
id: 'tbl1',
|
|
324
|
-
meta: { title: 'Sales Data', tags: ['table'] },
|
|
325
|
-
requires: ['src-sales'],
|
|
326
|
-
view: {
|
|
327
|
-
elements: [{
|
|
328
|
-
kind: 'table',
|
|
329
|
-
className: 'col-12',
|
|
330
|
-
data: { bind: 'state.sales', sortable: true }
|
|
331
|
-
}],
|
|
332
|
-
layout: { board: { col: 8, order: 1 }, canvas: { x: 500, y: 40, w: 450 } },
|
|
333
|
-
features: { refresh: true }
|
|
334
|
-
},
|
|
335
|
-
state: {
|
|
336
|
-
status: 'fresh',
|
|
337
|
-
lastRun: new Date().toISOString(),
|
|
338
|
-
sales: [
|
|
339
|
-
{ region: 'North', product: 'Widget A', revenue: 12400, units: 310 },
|
|
340
|
-
{ region: 'South', product: 'Widget B', revenue: 8700, units: 220 },
|
|
341
|
-
{ region: 'East', product: 'Widget A', revenue: 15200, units: 380 },
|
|
342
|
-
{ region: 'West', product: 'Widget C', revenue: 6300, units: 125 },
|
|
343
|
-
{ region: 'North', product: 'Widget B', revenue: 9100, units: 230 },
|
|
344
|
-
]
|
|
345
|
-
}
|
|
346
|
-
};
|
|
347
|
-
|
|
348
|
-
nodes['met1'] = {
|
|
349
|
-
id: 'met1',
|
|
350
|
-
meta: { title: 'Revenue KPIs' },
|
|
351
|
-
requires: ['tbl1'],
|
|
352
|
-
compute: [
|
|
353
|
-
{ bindTo: 'total_revenue', expr: '$sum(state.sales.revenue)' },
|
|
354
|
-
{ bindTo: 'avg_revenue', expr: '$round($average(state.sales.revenue), 0)' }
|
|
355
|
-
],
|
|
356
|
-
view: {
|
|
357
|
-
elements: [
|
|
358
|
-
{ kind: 'metric', label: 'Total Revenue', className: 'col-6', data: { bind: 'computed_values.total_revenue' } },
|
|
359
|
-
{ kind: 'metric', label: 'Avg per Region', className: 'col-6', data: { bind: 'computed_values.avg_revenue' } }
|
|
360
|
-
],
|
|
361
|
-
layout: { board: { col: 4, order: 2 }, canvas: { x: 500, y: 380, w: 300 } }
|
|
362
|
-
},
|
|
363
|
-
state: {
|
|
364
|
-
status: 'fresh',
|
|
365
|
-
sales: [
|
|
366
|
-
{ region: 'North', product: 'Widget A', revenue: 12400, units: 310 },
|
|
367
|
-
{ region: 'South', product: 'Widget B', revenue: 8700, units: 220 },
|
|
368
|
-
{ region: 'East', product: 'Widget A', revenue: 15200, units: 380 },
|
|
369
|
-
{ region: 'West', product: 'Widget C', revenue: 6300, units: 125 },
|
|
370
|
-
{ region: 'North', product: 'Widget B', revenue: 9100, units: 230 },
|
|
371
|
-
]
|
|
372
|
-
}
|
|
373
|
-
};
|
|
374
|
-
|
|
375
|
-
nodes['cht1'] = {
|
|
376
|
-
id: 'cht1',
|
|
377
|
-
meta: { title: 'Revenue by Region' },
|
|
378
|
-
requires: ['tbl1'],
|
|
379
|
-
view: {
|
|
380
|
-
elements: [{
|
|
381
|
-
kind: 'chart',
|
|
382
|
-
className: 'col-12',
|
|
383
|
-
data: { bind: 'state.chartData' }
|
|
384
|
-
}],
|
|
385
|
-
layout: { board: { col: 6, order: 3 }, canvas: { x: 40, y: 360, w: 400 } }
|
|
386
|
-
},
|
|
387
|
-
state: {
|
|
388
|
-
status: 'fresh',
|
|
389
|
-
chartData: [
|
|
390
|
-
{ label: 'North', value: 21500 },
|
|
391
|
-
{ label: 'South', value: 8700 },
|
|
392
|
-
{ label: 'East', value: 15200 },
|
|
393
|
-
{ label: 'West', value: 6300 },
|
|
394
|
-
]
|
|
395
|
-
}
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
nodes['cht2'] = {
|
|
399
|
-
id: 'cht2',
|
|
400
|
-
meta: { title: 'Weekly Trend' },
|
|
401
|
-
view: {
|
|
402
|
-
elements: [{
|
|
403
|
-
kind: 'chart',
|
|
404
|
-
className: 'col-12',
|
|
405
|
-
data: { bind: 'state.trend', chartType: 'line' }
|
|
406
|
-
}],
|
|
407
|
-
layout: { board: { col: 6, order: 4 }, canvas: { x: 860, y: 380, w: 400 } }
|
|
408
|
-
},
|
|
409
|
-
state: {
|
|
410
|
-
status: 'fresh',
|
|
411
|
-
trend: [
|
|
412
|
-
{ x: 'Mon', y: 120 }, { x: 'Tue', y: 190 }, { x: 'Wed', y: 150 },
|
|
413
|
-
{ x: 'Thu', y: 210 }, { x: 'Fri', y: 280 }, { x: 'Sat', y: 170 }, { x: 'Sun', y: 90 },
|
|
414
|
-
]
|
|
415
|
-
}
|
|
416
|
-
};
|
|
417
|
-
|
|
418
|
-
nodes['lst1'] = {
|
|
419
|
-
id: 'lst1',
|
|
420
|
-
meta: { title: 'Config Summary' },
|
|
421
|
-
view: {
|
|
422
|
-
elements: [{
|
|
423
|
-
kind: 'list',
|
|
424
|
-
className: 'col-12',
|
|
425
|
-
data: { bind: 'state.config' }
|
|
426
|
-
}],
|
|
427
|
-
layout: { board: { col: 4, order: 5 }, canvas: { x: 1000, y: 40, w: 280 } }
|
|
428
|
-
},
|
|
429
|
-
state: {
|
|
430
|
-
status: 'fresh',
|
|
431
|
-
config: { Environment: 'Production', Region: 'US-West-2', Version: '3.4.1', Replicas: 3 }
|
|
432
|
-
}
|
|
433
|
-
};
|
|
434
|
-
|
|
435
|
-
nodes['alt1'] = {
|
|
436
|
-
id: 'alt1',
|
|
437
|
-
meta: { title: 'CPU Alert' },
|
|
438
|
-
requires: ['src-metrics'],
|
|
439
|
-
view: {
|
|
440
|
-
elements: [{
|
|
441
|
-
kind: 'alert',
|
|
442
|
-
label: 'CPU %',
|
|
443
|
-
className: 'col-12',
|
|
444
|
-
data: { bind: 'state.cpu', thresholds: { green: '< 60', amber: '< 85' } }
|
|
445
|
-
}],
|
|
446
|
-
layout: { board: { col: 4, order: 6 }, canvas: { x: 40, y: 680 } }
|
|
447
|
-
},
|
|
448
|
-
state: { status: 'fresh', cpu: 72 }
|
|
449
|
-
};
|
|
450
|
-
|
|
451
|
-
nodes['alt2'] = {
|
|
452
|
-
id: 'alt2',
|
|
453
|
-
meta: { title: 'Memory Alert' },
|
|
454
|
-
requires: ['src-metrics'],
|
|
455
|
-
view: {
|
|
456
|
-
elements: [{
|
|
457
|
-
kind: 'alert',
|
|
458
|
-
label: 'Memory %',
|
|
459
|
-
className: 'col-12',
|
|
460
|
-
data: { bind: 'state.memory', thresholds: { green: '< 70', amber: '< 90' } }
|
|
461
|
-
}],
|
|
462
|
-
layout: { board: { col: 4, order: 7 }, canvas: { x: 340, y: 680 } }
|
|
463
|
-
},
|
|
464
|
-
state: { status: 'fresh', memory: 45 }
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
nodes['frm1'] = {
|
|
468
|
-
id: 'frm1',
|
|
469
|
-
meta: { title: 'New Alert Rule' },
|
|
470
|
-
view: {
|
|
471
|
-
elements: [{
|
|
472
|
-
kind: 'form',
|
|
473
|
-
className: 'col-12',
|
|
474
|
-
data: {
|
|
475
|
-
writeTo: 'state.formValues',
|
|
476
|
-
fields: {
|
|
477
|
-
properties: {
|
|
478
|
-
name: { type: 'string', title: 'Rule Name', placeholder: 'e.g. High CPU' },
|
|
479
|
-
metric: { type: 'string', title: 'Metric', enum: ['cpu_percent','memory_mb','disk_io','latency_ms'] },
|
|
480
|
-
threshold: { type: 'number', title: 'Threshold', minimum: 0, maximum: 100 },
|
|
481
|
-
severity: { type: 'string', title: 'Severity', enum: ['low','medium','high','critical'] },
|
|
482
|
-
enabled: { type: 'boolean', title: 'Enabled' },
|
|
483
|
-
},
|
|
484
|
-
required: ['name', 'metric', 'threshold'],
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
}],
|
|
488
|
-
layout: { board: { col: 6, order: 8 }, canvas: { x: 640, y: 680, w: 380 } }
|
|
489
|
-
},
|
|
490
|
-
state: { status: 'fresh', formValues: { enabled: true } }
|
|
491
|
-
};
|
|
492
|
-
|
|
493
|
-
nodes['nts1'] = {
|
|
494
|
-
id: 'nts1',
|
|
495
|
-
meta: { title: 'Investigation Notes' },
|
|
496
|
-
view: {
|
|
497
|
-
elements: [{
|
|
498
|
-
kind: 'notes',
|
|
499
|
-
className: 'col-12',
|
|
500
|
-
data: { bind: 'state.content', writeTo: 'state.content' }
|
|
501
|
-
}],
|
|
502
|
-
layout: { board: { col: 6, order: 9 }, canvas: { x: 1060, y: 680, w: 380 } }
|
|
503
|
-
},
|
|
504
|
-
state: {
|
|
505
|
-
status: 'fresh',
|
|
506
|
-
content: '## Incident 4821\n\n- Root cause: **memory leak** in worker pool\n- Mitigated at 14:32 UTC\n- Follow-up: capacity test scheduled Friday'
|
|
507
|
-
}
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
nodes['td1'] = {
|
|
511
|
-
id: 'td1',
|
|
512
|
-
meta: { title: 'Sprint Tasks' },
|
|
513
|
-
view: {
|
|
514
|
-
elements: [{
|
|
515
|
-
kind: 'todo',
|
|
516
|
-
className: 'col-12',
|
|
517
|
-
data: { bind: 'state.items', writeTo: 'state.items' }
|
|
518
|
-
}],
|
|
519
|
-
layout: { board: { col: 6, order: 10 }, canvas: { x: 40, y: 960, w: 350 } }
|
|
520
|
-
},
|
|
521
|
-
state: {
|
|
522
|
-
status: 'fresh',
|
|
523
|
-
items: [
|
|
524
|
-
{ text: 'Finish LiveCards v3 engine', done: true },
|
|
525
|
-
{ text: 'Write integration tests', done: false },
|
|
526
|
-
{ text: 'Update overlay to use new engine', done: false },
|
|
527
|
-
{ text: 'Deploy to staging', done: false },
|
|
528
|
-
]
|
|
529
|
-
}
|
|
530
|
-
};
|
|
531
|
-
|
|
532
|
-
nodes['nar1'] = {
|
|
533
|
-
id: 'nar1',
|
|
534
|
-
meta: { title: 'Weekly Summary', tags: ['llm'] },
|
|
535
|
-
requires: ['tbl1', 'met1'],
|
|
536
|
-
view: {
|
|
537
|
-
elements: [{
|
|
538
|
-
kind: 'narrative',
|
|
539
|
-
className: 'col-12',
|
|
540
|
-
data: { bind: 'state.narrative' }
|
|
541
|
-
}],
|
|
542
|
-
layout: { board: { col: 12, order: 11 }, canvas: { x: 440, y: 960, w: 500 } },
|
|
543
|
-
features: { refresh: true, chat: true }
|
|
544
|
-
},
|
|
545
|
-
state: {
|
|
546
|
-
status: 'fresh',
|
|
547
|
-
lastRun: new Date(Date.now() - 3600000).toISOString(),
|
|
548
|
-
narrative: '### Week 16 Summary\n\nRevenue grew **18%** week-over-week driven by the North region. Widget A continues to lead with 690 units sold.\n\n> Action: Consider increasing inventory allocation for Widget A in East region where demand is highest.'
|
|
549
|
-
}
|
|
550
|
-
};
|
|
551
|
-
|
|
552
|
-
nodes['cst1'] = {
|
|
553
|
-
id: 'cst1',
|
|
554
|
-
meta: { title: 'Service Health' },
|
|
555
|
-
view: {
|
|
556
|
-
elements: [
|
|
557
|
-
{ kind: 'badge', className: 'col-6', label: 'Status', data: { bind: 'state.health.status', colorMap: { healthy: 'green', degraded: 'amber', down: 'red' } } },
|
|
558
|
-
{ kind: 'metric', className: 'col-6', label: 'Uptime', data: { bind: 'state.health.uptime' } },
|
|
559
|
-
{ kind: 'list', className: 'col-12 col-md-6', label: 'Latency', data: { bind: 'state.health.latency' } },
|
|
560
|
-
{ kind: 'table', className: 'col-12 col-md-6', label: 'Recent', data: { bind: 'state.health.recent', columns: ['time','requests','errors'] } },
|
|
561
|
-
],
|
|
562
|
-
layout: { board: { col: 12, order: 12 }, canvas: { x: 40, y: 1260, w: 500 } }
|
|
563
|
-
},
|
|
564
|
-
state: {
|
|
565
|
-
status: 'fresh',
|
|
566
|
-
health: {
|
|
567
|
-
status: 'healthy',
|
|
568
|
-
uptime: '99.97%',
|
|
569
|
-
latency: { p50: 12, p95: 45, p99: 120 },
|
|
570
|
-
recent: [
|
|
571
|
-
{ time: '14:00', requests: 1240, errors: 2 },
|
|
572
|
-
{ time: '14:05', requests: 1380, errors: 0 },
|
|
573
|
-
{ time: '14:10', requests: 1100, errors: 1 },
|
|
574
|
-
]
|
|
575
|
-
}
|
|
576
|
-
}
|
|
577
|
-
};
|
|
578
|
-
|
|
579
|
-
nodes['load1'] = {
|
|
580
|
-
id: 'load1',
|
|
581
|
-
meta: { title: 'Loading Example' },
|
|
582
|
-
view: {
|
|
583
|
-
elements: [{
|
|
584
|
-
kind: 'table',
|
|
585
|
-
className: 'col-12',
|
|
586
|
-
data: { bind: 'state.data', placeholder: 'Fetching data...' }
|
|
587
|
-
}],
|
|
588
|
-
layout: { board: { col: 6, order: 13 }, canvas: { x: 600, y: 1260, w: 350 } },
|
|
589
|
-
features: { refresh: true }
|
|
590
|
-
},
|
|
591
|
-
state: { status: 'loading', lastRun: new Date().toISOString() }
|
|
592
|
-
};
|
|
593
|
-
|
|
594
|
-
nodes['err1'] = {
|
|
595
|
-
id: 'err1',
|
|
596
|
-
meta: { title: 'Error Example' },
|
|
597
|
-
view: {
|
|
598
|
-
elements: [{
|
|
599
|
-
kind: 'narrative',
|
|
600
|
-
className: 'col-12',
|
|
601
|
-
data: { bind: 'state.text' }
|
|
602
|
-
}],
|
|
603
|
-
layout: { board: { col: 6, order: 14 }, canvas: { x: 1000, y: 1260, w: 350 } },
|
|
604
|
-
features: { refresh: true }
|
|
605
|
-
},
|
|
606
|
-
state: {
|
|
607
|
-
status: 'error',
|
|
608
|
-
lastRun: new Date().toISOString(),
|
|
609
|
-
error: 'LLM timeout after 30s — model overloaded, retry in 60s'
|
|
610
|
-
}
|
|
611
|
-
};
|
|
612
|
-
|
|
613
|
-
// =========================================================================
|
|
614
|
-
// Init engine
|
|
615
|
-
// =========================================================================
|
|
616
|
-
|
|
617
|
-
var engine = LiveCard.init({
|
|
618
|
-
resolve: function (id) { return nodes[id]; },
|
|
619
|
-
onPatch: function (id, data) { Object.assign(nodes[id], data); console.log('patch', id, data); },
|
|
620
|
-
onPatchState: function (id, data) { Object.assign(nodes[id].state, data); console.log('patchState', id, data); },
|
|
621
|
-
onRefresh: function (id) {
|
|
622
|
-
console.log('refresh', id);
|
|
623
|
-
engine.update(id, { status: 'loading' });
|
|
624
|
-
setTimeout(function () {
|
|
625
|
-
nodes[id].state.status = 'fresh';
|
|
626
|
-
nodes[id].state.lastRun = new Date().toISOString();
|
|
627
|
-
engine.update(id, { status: 'fresh', lastRun: nodes[id].state.lastRun });
|
|
628
|
-
}, 1500);
|
|
629
|
-
},
|
|
630
|
-
onChat: function (id, msg) {
|
|
631
|
-
console.log('chat', id, msg);
|
|
632
|
-
setTimeout(function () {
|
|
633
|
-
engine.appendChatMessage(id, 'assistant', 'This is a simulated response to: *' + msg + '*');
|
|
634
|
-
}, 800);
|
|
635
|
-
},
|
|
636
|
-
markdown: function (t) { return marked.parse(t); },
|
|
637
|
-
sanitize: function (h) { return DOMPurify.sanitize(h); },
|
|
638
|
-
chartLib: Chart,
|
|
639
|
-
});
|
|
640
|
-
|
|
641
|
-
// =========================================================================
|
|
642
|
-
// Create board — pass all nodes including sources
|
|
643
|
-
// =========================================================================
|
|
644
|
-
|
|
645
|
-
var board = LiveCard.Board(engine, document.getElementById('boardRoot'), {
|
|
646
|
-
nodes: Object.values(nodes),
|
|
647
|
-
positions: {
|
|
648
|
-
'src-sales': { x: 200, y: 0 },
|
|
649
|
-
'src-metrics': { x: 200, y: 80 },
|
|
650
|
-
},
|
|
651
|
-
mode: 'board',
|
|
652
|
-
showNotes: true,
|
|
653
|
-
showChat: true,
|
|
654
|
-
canvas: { snap: 20, zoom: { min: 0.25, max: 2, initial: 0.75 }, edges: true, height: '1800px' },
|
|
655
|
-
});
|
|
656
|
-
|
|
657
|
-
// =========================================================================
|
|
658
|
-
// Mode toggle
|
|
659
|
-
// =========================================================================
|
|
660
|
-
|
|
661
|
-
var toggle = document.getElementById('modeToggle');
|
|
662
|
-
var autoBtn = document.getElementById('autoLayoutBtn');
|
|
663
|
-
toggle.addEventListener('click', function (e) {
|
|
664
|
-
var btn = e.target.closest('[data-mode]');
|
|
665
|
-
if (!btn) return;
|
|
666
|
-
var m = btn.dataset.mode;
|
|
667
|
-
board.setMode(m);
|
|
668
|
-
toggle.querySelectorAll('button').forEach(function (b) {
|
|
669
|
-
b.className = b.dataset.mode === m ? 'btn btn-primary' : 'btn btn-outline-primary';
|
|
670
|
-
});
|
|
671
|
-
autoBtn.style.display = m === 'canvas' ? '' : 'none';
|
|
672
|
-
});
|
|
673
|
-
autoBtn.addEventListener('click', function () { board.autoLayout(); });
|
|
674
|
-
|
|
675
|
-
// Expose for console testing
|
|
676
|
-
window._board = board;
|
|
677
|
-
window._engine = engine;
|
|
678
|
-
window._nodes = nodes;
|
|
679
|
-
console.log('LiveCards v3 Board demo ready.');
|
|
680
|
-
console.log(' _board.setMode("canvas") — switch to canvas (shows source nodes)');
|
|
681
|
-
console.log(' _board.autoLayout() — auto-arrange DAG');
|
|
682
|
-
console.log(' _board.add(node) — add a node');
|
|
683
|
-
console.log(' _board.remove("tbl1") — remove a node');
|
|
684
|
-
console.log(' await CardCompute.eval("$sum(state.sales.revenue)", _nodes["met1"])');
|
|
685
|
-
})();
|
|
686
|
-
</script>
|
|
34
|
+
<body>
|
|
35
|
+
<main>
|
|
36
|
+
<h1>LiveCards browser demo moved</h1>
|
|
37
|
+
<p>The maintained browser demo is now the example-board shell so there is a single LiveCards example path to keep current.</p>
|
|
38
|
+
<p><a href="../../example-board/demo-shell.html">Open the example-board demo shell</a></p>
|
|
39
|
+
</main>
|
|
687
40
|
</body>
|
|
688
|
-
</html>
|
|
41
|
+
</html>
|