claude-artifact-framework 0.17.0 → 0.18.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 +22 -8
- package/dist/index.esm.js +54 -15
- package/dist/index.esm.js.map +3 -3
- package/dist/index.global.js +13 -12
- package/dist/index.global.js.map +3 -3
- package/llms.txt +5 -3
- package/package.json +1 -1
- package/src/app.js +21 -3
- package/src/records.js +23 -2
- package/src/steps.js +18 -10
package/llms.txt
CHANGED
|
@@ -32,7 +32,8 @@ https://cdn.jsdelivr.net/npm/claude-artifact-framework/dist/index.global.js
|
|
|
32
32
|
(+ key when used as a block)
|
|
33
33
|
- documents: label, title, blocks OR types, empty; machine: key, initial,
|
|
34
34
|
states, events (event: from, do, then); chat: system, greeting, placeholder,
|
|
35
|
-
tools, model, maxTokens; steps step: title, text, fields, result
|
|
35
|
+
tools, model, maxTokens; steps step: title, text, fields, result,
|
|
36
|
+
blocks (result steps only — end a wizard on charts/text under the stats)
|
|
36
37
|
- Formats: money, percent (0-100), number, date. Output/chart/compute items:
|
|
37
38
|
{ label, value, format, big }
|
|
38
39
|
|
|
@@ -50,8 +51,9 @@ https://cdn.jsdelivr.net/npm/claude-artifact-framework/dist/index.global.js
|
|
|
50
51
|
5. Phases (games, turns, processes) belong to `machine` — ctx.send(event,
|
|
51
52
|
payload) is the only door; events are no-ops outside their `from` states;
|
|
52
53
|
`after: {seconds, then, do}` timers are framework-owned.
|
|
53
|
-
6. Row
|
|
54
|
-
viewerId). Stop early if you like
|
|
54
|
+
6. Row callbacks (when AND run) receive (record, data, viewerId); block
|
|
55
|
+
actions (data, viewerId). Stop early if you like; a 2nd param literally
|
|
56
|
+
named viewerId is matched by name and gets the viewer id.
|
|
55
57
|
7. One big:true per screen; the constructive action takes kind:"primary"; a
|
|
56
58
|
counter changed by pressing is an action, not an editable number field.
|
|
57
59
|
8. Parts of a record are `items` — never JSON in a textarea.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-artifact-framework",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"description": "Utilities for building apps inside Claude Artifacts: platform storage, chat tool-calling loops, and other primitives verified against the real runtime.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.esm.js",
|
package/src/app.js
CHANGED
|
@@ -312,7 +312,7 @@ function validate(spec) {
|
|
|
312
312
|
if (!Array.isArray(steps) || steps.length === 0) {
|
|
313
313
|
throw new Error('claude-artifact-framework: layout "steps" needs `steps: [...]` with at least one step.');
|
|
314
314
|
}
|
|
315
|
-
const STEP_KEYS = ["title", "text", "fields", "result"];
|
|
315
|
+
const STEP_KEYS = ["title", "text", "fields", "result", "blocks"];
|
|
316
316
|
const seen = new Set();
|
|
317
317
|
steps.forEach((st, i) => {
|
|
318
318
|
if (!st || typeof st !== "object") {
|
|
@@ -327,6 +327,15 @@ function validate(spec) {
|
|
|
327
327
|
if (!st.fields && !st.text && !st.result) {
|
|
328
328
|
throw new Error(`claude-artifact-framework: steps[${i}] needs \`fields\`, \`text\`, or \`result\`.`);
|
|
329
329
|
}
|
|
330
|
+
// A result step may close on more than stats: `blocks` renders any
|
|
331
|
+
// block set (charts, text, lists) under the result grid. Measured
|
|
332
|
+
// miss: a wizard that wanted to END on a chart had nowhere to put it.
|
|
333
|
+
if (st.blocks !== undefined) {
|
|
334
|
+
if (typeof st.result !== "function") {
|
|
335
|
+
throw new Error(`claude-artifact-framework: steps[${i}].blocks only renders on a result step — add \`result\` or move the blocks to the step's fields.`);
|
|
336
|
+
}
|
|
337
|
+
checkBlocks(st.blocks);
|
|
338
|
+
}
|
|
330
339
|
checkFields(st.fields, `steps[${i}]`);
|
|
331
340
|
for (const f of st.fields || []) {
|
|
332
341
|
if (seen.has(f.key)) {
|
|
@@ -920,7 +929,10 @@ function DocumentsLayout({ spec, ctx }) {
|
|
|
920
929
|
{ className: "caf-documents" },
|
|
921
930
|
h(DocTabbar, { docs, active, dspec, types, close, onAdd, label, ctx }),
|
|
922
931
|
typePicker,
|
|
923
|
-
|
|
932
|
+
// Keyed by document id: switching tabs REMOUNTS the pane, so DOM-held
|
|
933
|
+
// state can't leak across documents. Measured: the native file input
|
|
934
|
+
// kept showing the previous document's filename.
|
|
935
|
+
h(Panel, { key: active.id, spec: { blocks: aspec.blocks }, ctx: scoped })
|
|
924
936
|
);
|
|
925
937
|
}
|
|
926
938
|
|
|
@@ -1133,6 +1145,7 @@ export function createApp(spec = {}) {
|
|
|
1133
1145
|
colors: (n) => seriesColors(spec.theme && spec.theme.seed, n),
|
|
1134
1146
|
viewport: narrow ? "mobile" : "desktop",
|
|
1135
1147
|
_navTabs: narrow && spec.mobile && spec.mobile.nav === "bottom" ? findNavTabs(spec) : null,
|
|
1148
|
+
_Block: Block, // for layouts in other modules (steps) that render blocks
|
|
1136
1149
|
machine: spec.machine || null,
|
|
1137
1150
|
send: spec.machine
|
|
1138
1151
|
? (eventName, payload) => runMachineEvent(spec.machine, state.update, eventName, payload)
|
|
@@ -1192,7 +1205,11 @@ export function createApp(spec = {}) {
|
|
|
1192
1205
|
)
|
|
1193
1206
|
: !state.isHydrated() || libsState === "loading"
|
|
1194
1207
|
? h("div", { className: "caf-block caf-loading" }, libsState === "loading" ? "Loading libraries…" : "Loading…")
|
|
1195
|
-
:
|
|
1208
|
+
: // The same containment blocks get, one level up: a crash inside a
|
|
1209
|
+
// layout (a throwing sort, a bad row callback) shows a named error
|
|
1210
|
+
// under the header instead of killing the whole app. Measured: an
|
|
1211
|
+
// unhandled row-action throw once blanked an entire blind app.
|
|
1212
|
+
h(boundary(ctx.React), null, h(Layout, { spec, ctx })),
|
|
1196
1213
|
spec.brand && spec.brand.footer ? h("footer", { className: "caf-footer" }, spec.brand.footer) : null
|
|
1197
1214
|
);
|
|
1198
1215
|
};
|
|
@@ -1316,6 +1333,7 @@ const BASE_CSS = `
|
|
|
1316
1333
|
.caf-timer-controls { display: flex; gap: 0.6rem; }
|
|
1317
1334
|
.caf-btn:disabled { opacity: 0.45; cursor: not-allowed; }
|
|
1318
1335
|
.caf-steps-progress { display: flex; flex-direction: column; gap: 0.35rem; margin-bottom: 0.2rem; }
|
|
1336
|
+
.caf-steps-result { display: flex; flex-direction: column; gap: 1rem; min-width: 0; }
|
|
1319
1337
|
.caf-steps-fill { transition: width 0.25s ease; }
|
|
1320
1338
|
.caf-step-text { margin: 0; color: var(--caf-muted); font-size: 0.92rem; line-height: 1.55; }
|
|
1321
1339
|
.caf-step-nav { display: flex; justify-content: space-between; gap: 0.6rem; margin-top: 0.4rem; }
|
package/src/records.js
CHANGED
|
@@ -54,6 +54,27 @@ function makeId() {
|
|
|
54
54
|
return "r" + Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// Call-by-name for row callbacks. The measured 50/50 trap: both
|
|
58
|
+
// `when: (r, viewerId)` (the old documented form) and `run: (r, viewerId)`
|
|
59
|
+
// (the mistake that killed an entire blind app — `data` skipped, so `d`
|
|
60
|
+
// held a viewer-id string) exist in the wild. Positions lie; names don't:
|
|
61
|
+
// if the second declared parameter is named like the viewer id, hand it
|
|
62
|
+
// the viewer id. Every callback gets what its own names ask for, and the
|
|
63
|
+
// uniform (record, data, viewerId) order in the docs stays the one truth.
|
|
64
|
+
const rowCallLegacy = new WeakMap();
|
|
65
|
+
function rowCall(fn, rec, data, viewerId) {
|
|
66
|
+
let legacy = rowCallLegacy.get(fn);
|
|
67
|
+
if (legacy === undefined) {
|
|
68
|
+
const src = String(fn);
|
|
69
|
+
const arrow = src.match(/^\s*(?:async\s+)?([\w$]+)\s*=>/); // one bare param
|
|
70
|
+
const parens = src.match(/^[^(]*\(([^)]*)\)/);
|
|
71
|
+
const params = arrow ? [arrow[1]] : parens ? parens[1].split(",").map((s) => s.trim().split(/[=\s:]/)[0]) : [];
|
|
72
|
+
legacy = /viewer|voter|usuario|user/i.test(params[1] || "");
|
|
73
|
+
rowCallLegacy.set(fn, legacy);
|
|
74
|
+
}
|
|
75
|
+
return legacy ? fn(rec, viewerId) : fn(rec, data, viewerId);
|
|
76
|
+
}
|
|
77
|
+
|
|
57
78
|
// avatar: true — an initials circle per row, colored deterministically from
|
|
58
79
|
// the row title through the theme's series palette. Identity without assets.
|
|
59
80
|
function Avatar({ title, ctx }) {
|
|
@@ -184,7 +205,7 @@ function ListScreen({ spec, ctx }) {
|
|
|
184
205
|
"div",
|
|
185
206
|
{ className: "caf-row-actions" },
|
|
186
207
|
spec.actions
|
|
187
|
-
.filter((a) => !a.when || a.when
|
|
208
|
+
.filter((a) => !a.when || rowCall(a.when, rec, ctx.data, ctx.viewerId))
|
|
188
209
|
.map((a) =>
|
|
189
210
|
h(
|
|
190
211
|
"button",
|
|
@@ -195,7 +216,7 @@ function ListScreen({ spec, ctx }) {
|
|
|
195
216
|
onClick: () =>
|
|
196
217
|
ctx.update((d) => {
|
|
197
218
|
const live = d.records.find((x) => x.id === rec.id);
|
|
198
|
-
if (live) a.run
|
|
219
|
+
if (live) rowCall(a.run, live, d, ctx.viewerId);
|
|
199
220
|
}),
|
|
200
221
|
},
|
|
201
222
|
a.label
|
package/src/steps.js
CHANGED
|
@@ -22,23 +22,31 @@ function stepErrors(step, data) {
|
|
|
22
22
|
|
|
23
23
|
function ResultScreen({ step, ctx }) {
|
|
24
24
|
const items = step.result(ctx.data) || [];
|
|
25
|
+
const Block = ctx._Block;
|
|
25
26
|
return h(
|
|
26
27
|
"div",
|
|
27
|
-
{ className: "caf-
|
|
28
|
-
step.title ? h("h2", { className: "caf-block-title" }, step.title) : null,
|
|
28
|
+
{ className: "caf-steps-result" },
|
|
29
29
|
h(
|
|
30
30
|
"div",
|
|
31
|
-
{ className: "caf-output
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
h(
|
|
38
|
-
|
|
31
|
+
{ className: "caf-block caf-output" },
|
|
32
|
+
step.title ? h("h2", { className: "caf-block-title" }, step.title) : null,
|
|
33
|
+
h(
|
|
34
|
+
"div",
|
|
35
|
+
{ className: "caf-output-grid" },
|
|
36
|
+
items.map((item, i) =>
|
|
37
|
+
h(
|
|
38
|
+
"div",
|
|
39
|
+
{ key: item.label ?? i, className: item.big ? "caf-stat caf-stat-big" : "caf-stat" },
|
|
40
|
+
h("span", { className: "caf-stat-label" }, item.label),
|
|
41
|
+
h("span", { className: "caf-stat-value" }, formatValue(item.value, item.format)),
|
|
42
|
+
item.hint ? h("small", { className: "caf-hint" }, item.hint) : null
|
|
43
|
+
)
|
|
39
44
|
)
|
|
40
45
|
)
|
|
41
46
|
),
|
|
47
|
+
// A result can end on more than stats: any declared blocks (charts,
|
|
48
|
+
// text, lists) render under the grid with full block services.
|
|
49
|
+
(step.blocks || []).map((b, i) => h(Block, { key: "rb" + i, block: b, ctx })),
|
|
42
50
|
h(
|
|
43
51
|
"div",
|
|
44
52
|
{ className: "caf-step-nav" },
|