claude-artifact-framework 0.19.1 → 0.19.2
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 +4 -2
- package/dist/index.esm.js +20 -43
- package/dist/index.esm.js.map +2 -2
- package/dist/index.global.js +12 -12
- package/dist/index.global.js.map +3 -3
- package/llms.txt +2 -1
- package/package.json +1 -1
- package/src/blocks.js +28 -11
- package/src/records.js +4 -21
- package/src/steps.js +2 -20
package/llms.txt
CHANGED
|
@@ -34,7 +34,8 @@ https://cdn.jsdelivr.net/npm/claude-artifact-framework/dist/index.global.js
|
|
|
34
34
|
states, events (event: from, do, then); chat: system, greeting, placeholder,
|
|
35
35
|
tools, model, maxTokens; steps step: title, text, fields, result,
|
|
36
36
|
blocks (result steps only — end a wizard on charts/text under the stats)
|
|
37
|
-
- Formats: money, percent (0-100), number, date
|
|
37
|
+
- Formats: money, percent (0-100), number, date, text (no-op; anything else
|
|
38
|
+
throws). NaN/Infinity render as "—". Output/chart/compute items:
|
|
38
39
|
{ label, value, format, big }
|
|
39
40
|
|
|
40
41
|
## Hard rules
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-artifact-framework",
|
|
3
|
-
"version": "0.19.
|
|
3
|
+
"version": "0.19.2",
|
|
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/blocks.js
CHANGED
|
@@ -17,6 +17,9 @@ const FIELD_TYPES = ["text", "textarea", "number", "money", "percent", "check",
|
|
|
17
17
|
|
|
18
18
|
export function formatValue(value, format) {
|
|
19
19
|
if (value === null || value === undefined || value === "") return "—";
|
|
20
|
+
// A computed NaN/Infinity (0/0 on an empty list is the measured case)
|
|
21
|
+
// must read as "no data yet", never as the string "NaN" on screen.
|
|
22
|
+
if (typeof value === "number" && !Number.isFinite(value)) return "—";
|
|
20
23
|
const n = Number(value);
|
|
21
24
|
switch (format) {
|
|
22
25
|
case "money":
|
|
@@ -29,8 +32,17 @@ export function formatValue(value, format) {
|
|
|
29
32
|
return Number.isFinite(n) ? n.toLocaleString(undefined, { maximumFractionDigits: 2 }) : String(value);
|
|
30
33
|
case "date":
|
|
31
34
|
return value instanceof Date ? value.toLocaleDateString() : String(value);
|
|
32
|
-
|
|
35
|
+
case "text": // observed natural usage (round 21) — an explicit no-op
|
|
36
|
+
case undefined:
|
|
37
|
+
case null:
|
|
33
38
|
return String(value);
|
|
39
|
+
default:
|
|
40
|
+
// Same loud-failure rule as every other registry: a typo must name
|
|
41
|
+
// the valid set, not silently un-format the number. The block
|
|
42
|
+
// boundary contains the throw to the offending card.
|
|
43
|
+
throw new Error(
|
|
44
|
+
`claude-artifact-framework: unknown format "${format}". Valid formats: money, percent, number, date, text.`
|
|
45
|
+
);
|
|
34
46
|
}
|
|
35
47
|
}
|
|
36
48
|
|
|
@@ -173,20 +185,17 @@ export function FieldsBlock({ spec, ctx }) {
|
|
|
173
185
|
);
|
|
174
186
|
}
|
|
175
187
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
// differentiated surface — an accent-tinted feature card. Audited miss:
|
|
183
|
-
// with a single card chrome, the most important number on screen weighed
|
|
184
|
-
// exactly the same as everything else.
|
|
188
|
+
// THE stat grid — the single render path for output blocks, records
|
|
189
|
+
// summaries, and steps results. It exists because the feature-card rule was
|
|
190
|
+
// once applied to one of three hand-copied versions of this markup and an
|
|
191
|
+
// audit caught the other two bare (the re-audit CRÍTICO of 0.19.0). The
|
|
192
|
+
// block holding a `big` stat gets the screen's one differentiated surface.
|
|
193
|
+
export function StatGrid({ items, title }) {
|
|
185
194
|
const hero = items.some((it) => it && it.big);
|
|
186
195
|
return h(
|
|
187
196
|
"div",
|
|
188
197
|
{ className: hero ? "caf-block caf-output caf-block-feature" : "caf-block caf-output" },
|
|
189
|
-
|
|
198
|
+
title ? h("h2", { className: "caf-block-title" }, title) : null,
|
|
190
199
|
h(
|
|
191
200
|
"div",
|
|
192
201
|
{ className: "caf-output-grid" },
|
|
@@ -203,6 +212,14 @@ export function OutputBlock({ spec, ctx }) {
|
|
|
203
212
|
);
|
|
204
213
|
}
|
|
205
214
|
|
|
215
|
+
export function OutputBlock({ spec, ctx }) {
|
|
216
|
+
const items = typeof spec.output === "function" ? spec.output(ctx.data) : spec.output || [];
|
|
217
|
+
if (!items.length) {
|
|
218
|
+
return h("div", { className: "caf-block caf-empty" }, spec.empty || "Nothing to show yet.");
|
|
219
|
+
}
|
|
220
|
+
return h(StatGrid, { items, title: spec.title });
|
|
221
|
+
}
|
|
222
|
+
|
|
206
223
|
// Brand elements follow the zero-asset-first principle: every form works
|
|
207
224
|
// with nothing uploaded (artifacts can't persist real images — the ~5MB
|
|
208
225
|
// pool). banner's default face is a gradient derived from the theme seed;
|
package/src/records.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
// simply falls back to the list.
|
|
20
20
|
|
|
21
21
|
import { createElement as h, useState, useEffect } from "react";
|
|
22
|
-
import { Field, formatValue } from "./blocks.js";
|
|
22
|
+
import { Field, formatValue, StatGrid } from "./blocks.js";
|
|
23
23
|
|
|
24
24
|
// Computed fields are derived at render time and never stored, so they can't
|
|
25
25
|
// go stale. Accepts `{ subtotal: (r) => ... }` or
|
|
@@ -61,6 +61,8 @@ function makeId() {
|
|
|
61
61
|
// if the second declared parameter is named like the viewer id, hand it
|
|
62
62
|
// the viewer id. Every callback gets what its own names ask for, and the
|
|
63
63
|
// uniform (record, data, viewerId) order in the docs stays the one truth.
|
|
64
|
+
// KNOWN LIMIT: minified specs rename parameters, silently defeating this
|
|
65
|
+
// detection — one more reason hard rule 1 bans minified output.
|
|
64
66
|
const rowCallLegacy = new WeakMap();
|
|
65
67
|
function rowCall(fn, rec, data, viewerId) {
|
|
66
68
|
let legacy = rowCallLegacy.get(fn);
|
|
@@ -103,26 +105,7 @@ function Summary({ spec, records }) {
|
|
|
103
105
|
if (!spec.summary) return null;
|
|
104
106
|
const items = spec.summary(records) || [];
|
|
105
107
|
if (!items.length) return null;
|
|
106
|
-
|
|
107
|
-
// screen's feature surface. Re-audit caught this second render path
|
|
108
|
-
// missing the treatment — the rule must hold wherever big stats render.
|
|
109
|
-
const hero = items.some((it) => it && it.big);
|
|
110
|
-
return h(
|
|
111
|
-
"div",
|
|
112
|
-
{ className: hero ? "caf-block caf-output caf-block-feature" : "caf-block caf-output" },
|
|
113
|
-
h(
|
|
114
|
-
"div",
|
|
115
|
-
{ className: "caf-output-grid" },
|
|
116
|
-
items.map((item, i) =>
|
|
117
|
-
h(
|
|
118
|
-
"div",
|
|
119
|
-
{ key: item.label || i, className: item.big ? "caf-stat caf-stat-big" : "caf-stat" },
|
|
120
|
-
h("span", { className: "caf-stat-label" }, item.label),
|
|
121
|
-
h("span", { className: "caf-stat-value" }, formatValue(item.value, item.format))
|
|
122
|
-
)
|
|
123
|
-
)
|
|
124
|
-
)
|
|
125
|
-
);
|
|
108
|
+
return h(StatGrid, { items });
|
|
126
109
|
}
|
|
127
110
|
|
|
128
111
|
function ListScreen({ spec, ctx }) {
|
package/src/steps.js
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// on the step the user was on, holding their answers.
|
|
15
15
|
|
|
16
16
|
import { createElement as h } from "react";
|
|
17
|
-
import { Field, validateField,
|
|
17
|
+
import { Field, validateField, writeField, StatGrid } from "./blocks.js";
|
|
18
18
|
|
|
19
19
|
function stepErrors(step, data) {
|
|
20
20
|
return (step.fields || []).some((f) => validateField(f, data[f.key]) !== null);
|
|
@@ -26,25 +26,7 @@ function ResultScreen({ step, ctx }) {
|
|
|
26
26
|
return h(
|
|
27
27
|
"div",
|
|
28
28
|
{ className: "caf-steps-result" },
|
|
29
|
-
h(
|
|
30
|
-
"div",
|
|
31
|
-
// Same rule as OutputBlock: the hero (big) result gets the feature card.
|
|
32
|
-
{ className: items.some((it) => it && it.big) ? "caf-block caf-output caf-block-feature" : "caf-block caf-output" },
|
|
33
|
-
step.title ? h("h2", { className: "caf-block-title" }, step.title) : null,
|
|
34
|
-
h(
|
|
35
|
-
"div",
|
|
36
|
-
{ className: "caf-output-grid" },
|
|
37
|
-
items.map((item, i) =>
|
|
38
|
-
h(
|
|
39
|
-
"div",
|
|
40
|
-
{ key: item.label ?? i, className: item.big ? "caf-stat caf-stat-big" : "caf-stat" },
|
|
41
|
-
h("span", { className: "caf-stat-label" }, item.label),
|
|
42
|
-
h("span", { className: "caf-stat-value" }, formatValue(item.value, item.format)),
|
|
43
|
-
item.hint ? h("small", { className: "caf-hint" }, item.hint) : null
|
|
44
|
-
)
|
|
45
|
-
)
|
|
46
|
-
)
|
|
47
|
-
),
|
|
29
|
+
h(StatGrid, { items, title: step.title }),
|
|
48
30
|
// A result can end on more than stats: any declared blocks (charts,
|
|
49
31
|
// text, lists) render under the grid with full block services.
|
|
50
32
|
(step.blocks || []).map((b, i) => h(Block, { key: "rb" + i, block: b, ctx })),
|