claude-artifact-framework 0.19.0 → 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 +38 -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 +17 -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/README.md
CHANGED
|
@@ -908,8 +908,10 @@ keys must be unique across steps.
|
|
|
908
908
|
`{ description, input, run(input, ctx) }`.
|
|
909
909
|
|
|
910
910
|
**Formats** (`format:` on output/chart/compute items): `money`, `percent`,
|
|
911
|
-
`number`, `date
|
|
912
|
-
|
|
911
|
+
`number`, `date`, `text` (explicit no-op). Anything else throws, contained
|
|
912
|
+
to the block. `percent` expects 0–100 (pass `ratio * 100`, not the raw
|
|
913
|
+
ratio); a computed `NaN`/`Infinity` renders as "—", never as "NaN".
|
|
914
|
+
`big: true` renders an item at hero size AND promotes its block to
|
|
913
915
|
the screen's one feature surface (accent-tinted card) — reserve it for the
|
|
914
916
|
ONE number that is the screen's main result (a total, a final score), not
|
|
915
917
|
for every stat; more than one hero per screen means no hero at all.
|
|
@@ -988,6 +990,40 @@ Conflict handling is last-write-wins: if two viewers change the same key
|
|
|
988
990
|
within one poll window, whichever write reaches storage last overwrites the
|
|
989
991
|
other. There is no merge/CRDT logic.
|
|
990
992
|
|
|
993
|
+
## Design principles — composing a screen that reads
|
|
994
|
+
|
|
995
|
+
The framework owns the CSS, but whether a screen has visual hierarchy is
|
|
996
|
+
decided by choices only the SPEC can make. Every beautiful app follows
|
|
997
|
+
these; every generic-looking one breaks at least two:
|
|
998
|
+
|
|
999
|
+
1. **One dominant element per screen.** Exactly one `big: true` stat — the
|
|
1000
|
+
screen's answer (the total, the score, the net). It automatically
|
|
1001
|
+
becomes the highlighted feature card. Zero heroes reads flat; two
|
|
1002
|
+
heroes read as none.
|
|
1003
|
+
2. **Order blocks by importance, not by data model.** First what the user
|
|
1004
|
+
DOES most (the quick-add, the main fields), then the hero result, then
|
|
1005
|
+
breakdowns and charts, then history. Configuration, branding text, and
|
|
1006
|
+
help go LAST — or inside `collapsible: "collapsed"` — never first.
|
|
1007
|
+
3. **Icons: all siblings or none.** If one tab/section gets an `icon`,
|
|
1008
|
+
every tab/section in that same set gets one, same style (all emoji). A
|
|
1009
|
+
half-iconed set reads as an accident. Don't use emoji as bullet points
|
|
1010
|
+
inside stat labels or titles that already live in an iconed set.
|
|
1011
|
+
4. **Pick a mid-tone, saturated seed that names the domain** — warm
|
|
1012
|
+
orange/red for food, teal/blue for finance, green for health. Grays and
|
|
1013
|
+
near-blacks kill the entire accent system; neons burn it.
|
|
1014
|
+
5. **Never duplicate what the framework already renders.** No text block
|
|
1015
|
+
repeating the app's own title (the header shows it), no hand-built
|
|
1016
|
+
totals when `summary`/`output` computes them, no "empty list" messages.
|
|
1017
|
+
6. **Group; don't multiply cards.** Related inputs belong in ONE `fields`
|
|
1018
|
+
block with one title. Secondary content goes into `tabs` or a
|
|
1019
|
+
collapsible. A first screen with more than ~5 cards has no hierarchy
|
|
1020
|
+
left — and a block whose only content is one lonely button probably
|
|
1021
|
+
belongs next to the fields it acts on.
|
|
1022
|
+
7. **Say state with `when`, not with more blocks.** Hide what doesn't
|
|
1023
|
+
apply right now instead of stacking every possibility on screen.
|
|
1024
|
+
8. **Charts are support, not wallpaper.** At most two per screen; `wide:
|
|
1025
|
+
true` for time series; donuts only up to ~5 slices.
|
|
1026
|
+
|
|
991
1027
|
## Hard rules — the short list
|
|
992
1028
|
|
|
993
1029
|
The rules that decide whether a generated app works, all in one place
|
package/dist/index.esm.js
CHANGED
|
@@ -625,6 +625,7 @@ function ChatLayout({ spec, ctx }) {
|
|
|
625
625
|
var FIELD_TYPES = ["text", "textarea", "number", "money", "percent", "check", "date", "select", "file"];
|
|
626
626
|
function formatValue(value, format) {
|
|
627
627
|
if (value === null || value === void 0 || value === "") return "\u2014";
|
|
628
|
+
if (typeof value === "number" && !Number.isFinite(value)) return "\u2014";
|
|
628
629
|
const n = Number(value);
|
|
629
630
|
switch (format) {
|
|
630
631
|
case "money":
|
|
@@ -635,8 +636,15 @@ function formatValue(value, format) {
|
|
|
635
636
|
return Number.isFinite(n) ? n.toLocaleString(void 0, { maximumFractionDigits: 2 }) : String(value);
|
|
636
637
|
case "date":
|
|
637
638
|
return value instanceof Date ? value.toLocaleDateString() : String(value);
|
|
638
|
-
|
|
639
|
+
case "text":
|
|
640
|
+
// observed natural usage (round 21) — an explicit no-op
|
|
641
|
+
case void 0:
|
|
642
|
+
case null:
|
|
639
643
|
return String(value);
|
|
644
|
+
default:
|
|
645
|
+
throw new Error(
|
|
646
|
+
`claude-artifact-framework: unknown format "${format}". Valid formats: money, percent, number, date, text.`
|
|
647
|
+
);
|
|
640
648
|
}
|
|
641
649
|
}
|
|
642
650
|
function validateField(field, value) {
|
|
@@ -756,16 +764,12 @@ function FieldsBlock({ spec, ctx }) {
|
|
|
756
764
|
)
|
|
757
765
|
);
|
|
758
766
|
}
|
|
759
|
-
function
|
|
760
|
-
const items = typeof spec.output === "function" ? spec.output(ctx.data) : spec.output || [];
|
|
761
|
-
if (!items.length) {
|
|
762
|
-
return createElement("div", { className: "caf-block caf-empty" }, spec.empty || "Nothing to show yet.");
|
|
763
|
-
}
|
|
767
|
+
function StatGrid({ items, title }) {
|
|
764
768
|
const hero = items.some((it) => it && it.big);
|
|
765
769
|
return createElement(
|
|
766
770
|
"div",
|
|
767
771
|
{ className: hero ? "caf-block caf-output caf-block-feature" : "caf-block caf-output" },
|
|
768
|
-
|
|
772
|
+
title ? createElement("h2", { className: "caf-block-title" }, title) : null,
|
|
769
773
|
createElement(
|
|
770
774
|
"div",
|
|
771
775
|
{ className: "caf-output-grid" },
|
|
@@ -781,6 +785,13 @@ function OutputBlock({ spec, ctx }) {
|
|
|
781
785
|
)
|
|
782
786
|
);
|
|
783
787
|
}
|
|
788
|
+
function OutputBlock({ spec, ctx }) {
|
|
789
|
+
const items = typeof spec.output === "function" ? spec.output(ctx.data) : spec.output || [];
|
|
790
|
+
if (!items.length) {
|
|
791
|
+
return createElement("div", { className: "caf-block caf-empty" }, spec.empty || "Nothing to show yet.");
|
|
792
|
+
}
|
|
793
|
+
return createElement(StatGrid, { items, title: spec.title });
|
|
794
|
+
}
|
|
784
795
|
function BannerBlock({ spec }) {
|
|
785
796
|
const b = typeof spec.banner === "string" ? { title: spec.banner } : spec.banner || {};
|
|
786
797
|
const background = b.image ? `linear-gradient(rgba(0,0,0,0.35), rgba(0,0,0,0.35)), url(${JSON.stringify(b.image)}) center / cover` : `linear-gradient(135deg, color-mix(in srgb, var(--caf-accent) 82%, #000), var(--caf-accent) 55%, color-mix(in srgb, var(--caf-accent) 72%, #fff))`;
|
|
@@ -1100,23 +1111,7 @@ function Summary({ spec, records }) {
|
|
|
1100
1111
|
if (!spec.summary) return null;
|
|
1101
1112
|
const items = spec.summary(records) || [];
|
|
1102
1113
|
if (!items.length) return null;
|
|
1103
|
-
|
|
1104
|
-
return createElement(
|
|
1105
|
-
"div",
|
|
1106
|
-
{ className: hero ? "caf-block caf-output caf-block-feature" : "caf-block caf-output" },
|
|
1107
|
-
createElement(
|
|
1108
|
-
"div",
|
|
1109
|
-
{ className: "caf-output-grid" },
|
|
1110
|
-
items.map(
|
|
1111
|
-
(item, i) => createElement(
|
|
1112
|
-
"div",
|
|
1113
|
-
{ key: item.label || i, className: item.big ? "caf-stat caf-stat-big" : "caf-stat" },
|
|
1114
|
-
createElement("span", { className: "caf-stat-label" }, item.label),
|
|
1115
|
-
createElement("span", { className: "caf-stat-value" }, formatValue(item.value, item.format))
|
|
1116
|
-
)
|
|
1117
|
-
)
|
|
1118
|
-
)
|
|
1119
|
-
);
|
|
1114
|
+
return createElement(StatGrid, { items });
|
|
1120
1115
|
}
|
|
1121
1116
|
function ListScreen({ spec, ctx }) {
|
|
1122
1117
|
const label = spec.label || "item";
|
|
@@ -1372,25 +1367,7 @@ function ResultScreen({ step, ctx }) {
|
|
|
1372
1367
|
return createElement(
|
|
1373
1368
|
"div",
|
|
1374
1369
|
{ className: "caf-steps-result" },
|
|
1375
|
-
createElement(
|
|
1376
|
-
"div",
|
|
1377
|
-
// Same rule as OutputBlock: the hero (big) result gets the feature card.
|
|
1378
|
-
{ className: items.some((it) => it && it.big) ? "caf-block caf-output caf-block-feature" : "caf-block caf-output" },
|
|
1379
|
-
step.title ? createElement("h2", { className: "caf-block-title" }, step.title) : null,
|
|
1380
|
-
createElement(
|
|
1381
|
-
"div",
|
|
1382
|
-
{ className: "caf-output-grid" },
|
|
1383
|
-
items.map(
|
|
1384
|
-
(item, i) => createElement(
|
|
1385
|
-
"div",
|
|
1386
|
-
{ key: item.label ?? i, className: item.big ? "caf-stat caf-stat-big" : "caf-stat" },
|
|
1387
|
-
createElement("span", { className: "caf-stat-label" }, item.label),
|
|
1388
|
-
createElement("span", { className: "caf-stat-value" }, formatValue(item.value, item.format)),
|
|
1389
|
-
item.hint ? createElement("small", { className: "caf-hint" }, item.hint) : null
|
|
1390
|
-
)
|
|
1391
|
-
)
|
|
1392
|
-
)
|
|
1393
|
-
),
|
|
1370
|
+
createElement(StatGrid, { items, title: step.title }),
|
|
1394
1371
|
// A result can end on more than stats: any declared blocks (charts,
|
|
1395
1372
|
// text, lists) render under the grid with full block services.
|
|
1396
1373
|
(step.blocks || []).map((b, i) => createElement(Block2, { key: "rb" + i, block: b, ctx })),
|