claude-artifact-framework 0.5.0 → 0.5.1
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 +40 -3
- package/dist/index.esm.js +61 -26
- package/dist/index.esm.js.map +2 -2
- package/dist/index.global.js +10 -9
- package/dist/index.global.js.map +3 -3
- package/package.json +1 -1
- package/src/app.js +76 -29
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-artifact-framework",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
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
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { createElement as h, useState, useEffect, hasReact, getReact } from "react";
|
|
13
13
|
import { createAppState } from "./appState.js";
|
|
14
14
|
import { themeCss, seriesColors } from "./theme.js";
|
|
15
|
-
import { BLOCKS, BLOCK_NAMES } from "./blocks.js";
|
|
15
|
+
import { BLOCKS, BLOCK_NAMES, FIELD_TYPES } from "./blocks.js";
|
|
16
16
|
import { RecordsLayout } from "./records.js";
|
|
17
17
|
import { StepsLayout } from "./steps.js";
|
|
18
18
|
import { ChatLayout, CHAT_KEYS } from "./chat.js";
|
|
@@ -25,6 +25,48 @@ const RESERVED = ["title", "empty", "wide", "onSelect", "subtitle"];
|
|
|
25
25
|
// silently rendering nothing — a blank pane is indistinguishable from a bug.
|
|
26
26
|
const TOP_KEYS = ["title", "subtitle", "theme", "layout", "blocks", "records", "steps", "chat", "data", "shared"];
|
|
27
27
|
|
|
28
|
+
// An unknown field type used to fall through silently to a text input — the
|
|
29
|
+
// exact quiet degradation the framework exists to prevent (a blind-tested
|
|
30
|
+
// Haiku wrote "checkbox" for "check" and shipped a text field).
|
|
31
|
+
function checkFields(fields, where) {
|
|
32
|
+
for (const f of fields || []) {
|
|
33
|
+
if (!f || !f.key) throw new Error(`claude-artifact-framework: every field in ${where} needs a \`key\`.`);
|
|
34
|
+
if (f.type !== undefined && !FIELD_TYPES.includes(f.type)) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`claude-artifact-framework: field "${f.key}" in ${where} has unknown type "${f.type}". Valid types: ${FIELD_TYPES.join(", ")}.`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function checkBlocks(blocks) {
|
|
43
|
+
if (!Array.isArray(blocks)) {
|
|
44
|
+
throw new Error("claude-artifact-framework: `blocks` must be an array.");
|
|
45
|
+
}
|
|
46
|
+
blocks.forEach((block, i) => {
|
|
47
|
+
if (!block || typeof block !== "object") {
|
|
48
|
+
throw new Error(`claude-artifact-framework: blocks[${i}] must be an object like { fields: [...] }.`);
|
|
49
|
+
}
|
|
50
|
+
const keys = Object.keys(block).filter((k) => !RESERVED.includes(k));
|
|
51
|
+
const known = keys.filter((k) => BLOCK_NAMES.includes(k));
|
|
52
|
+
if (known.length === 0) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`claude-artifact-framework: blocks[${i}] has no recognised block type (found: ${
|
|
55
|
+
keys.join(", ") || "nothing"
|
|
56
|
+
}). Valid block types: ${BLOCK_NAMES.join(", ")}.`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
if (known.length > 1) {
|
|
60
|
+
throw new Error(
|
|
61
|
+
`claude-artifact-framework: blocks[${i}] declares more than one block type (${known.join(
|
|
62
|
+
", "
|
|
63
|
+
)}). Split them into separate entries of \`blocks\`.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
checkFields(block.fields, `blocks[${i}]`);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
28
70
|
function validate(spec) {
|
|
29
71
|
const unknownTop = Object.keys(spec).filter((k) => !TOP_KEYS.includes(k));
|
|
30
72
|
if (unknownTop.length) {
|
|
@@ -58,8 +100,8 @@ function validate(spec) {
|
|
|
58
100
|
if (!st.fields && !st.text && !st.result) {
|
|
59
101
|
throw new Error(`claude-artifact-framework: steps[${i}] needs \`fields\`, \`text\`, or \`result\`.`);
|
|
60
102
|
}
|
|
103
|
+
checkFields(st.fields, `steps[${i}]`);
|
|
61
104
|
for (const f of st.fields || []) {
|
|
62
|
-
if (!f || !f.key) throw new Error(`claude-artifact-framework: every field in steps[${i}] needs a \`key\`.`);
|
|
63
105
|
if (seen.has(f.key)) {
|
|
64
106
|
throw new Error(
|
|
65
107
|
`claude-artifact-framework: field key "${f.key}" appears in more than one step — keys share one data pool and must be unique.`
|
|
@@ -106,13 +148,14 @@ function validate(spec) {
|
|
|
106
148
|
`claude-artifact-framework: records has unknown keys (${unknownR.join(", ")}). Valid keys: ${RECORDS_KEYS.join(", ")}.`
|
|
107
149
|
);
|
|
108
150
|
}
|
|
151
|
+
checkFields(r.fields, "records");
|
|
109
152
|
const seen = new Set();
|
|
110
153
|
for (const f of r.fields) {
|
|
111
|
-
if (!f || !f.key) throw new Error("claude-artifact-framework: every records field needs a `key`.");
|
|
112
154
|
if (f.key === "id") throw new Error('claude-artifact-framework: "id" is reserved on records — the framework assigns it.');
|
|
113
155
|
if (seen.has(f.key)) throw new Error(`claude-artifact-framework: duplicate records field key "${f.key}".`);
|
|
114
156
|
seen.add(f.key);
|
|
115
157
|
}
|
|
158
|
+
if (spec.blocks) checkBlocks(spec.blocks);
|
|
116
159
|
for (const [key, c] of Object.entries(r.compute || {})) {
|
|
117
160
|
if (seen.has(key) || key === "id") {
|
|
118
161
|
throw new Error(
|
|
@@ -127,31 +170,7 @@ function validate(spec) {
|
|
|
127
170
|
}
|
|
128
171
|
return layout;
|
|
129
172
|
}
|
|
130
|
-
|
|
131
|
-
if (!Array.isArray(blocks)) {
|
|
132
|
-
throw new Error("claude-artifact-framework: `blocks` must be an array.");
|
|
133
|
-
}
|
|
134
|
-
blocks.forEach((block, i) => {
|
|
135
|
-
if (!block || typeof block !== "object") {
|
|
136
|
-
throw new Error(`claude-artifact-framework: blocks[${i}] must be an object like { fields: [...] }.`);
|
|
137
|
-
}
|
|
138
|
-
const keys = Object.keys(block).filter((k) => !RESERVED.includes(k));
|
|
139
|
-
const known = keys.filter((k) => BLOCK_NAMES.includes(k));
|
|
140
|
-
if (known.length === 0) {
|
|
141
|
-
throw new Error(
|
|
142
|
-
`claude-artifact-framework: blocks[${i}] has no recognised block type (found: ${
|
|
143
|
-
keys.join(", ") || "nothing"
|
|
144
|
-
}). Valid block types: ${BLOCK_NAMES.join(", ")}.`
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
if (known.length > 1) {
|
|
148
|
-
throw new Error(
|
|
149
|
-
`claude-artifact-framework: blocks[${i}] declares more than one block type (${known.join(
|
|
150
|
-
", "
|
|
151
|
-
)}). Split them into separate entries of \`blocks\`.`
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
});
|
|
173
|
+
checkBlocks(spec.blocks || []);
|
|
155
174
|
return layout;
|
|
156
175
|
}
|
|
157
176
|
|
|
@@ -226,7 +245,34 @@ function Panel({ spec, ctx }) {
|
|
|
226
245
|
);
|
|
227
246
|
}
|
|
228
247
|
|
|
229
|
-
|
|
248
|
+
// Records + extra blocks is a natural combo (a list with a chart of itself
|
|
249
|
+
// below). Blocks render under the list, never under the detail screen, and
|
|
250
|
+
// their functions receive the same data object as everywhere else — the
|
|
251
|
+
// collection lives at d.records.
|
|
252
|
+
function RecordsWithBlocks({ spec, ctx }) {
|
|
253
|
+
const detailOpen =
|
|
254
|
+
ctx.view.screen === "record" && (ctx.data.records || []).some((r) => r.id === ctx.view.arg);
|
|
255
|
+
return h(
|
|
256
|
+
"div",
|
|
257
|
+
null,
|
|
258
|
+
h(RecordsLayout, { spec, ctx }),
|
|
259
|
+
!detailOpen && Array.isArray(spec.blocks) && spec.blocks.length
|
|
260
|
+
? h(
|
|
261
|
+
"div",
|
|
262
|
+
{ className: "caf-panel caf-records-blocks" },
|
|
263
|
+
spec.blocks.map((block, i) =>
|
|
264
|
+
h(
|
|
265
|
+
"section",
|
|
266
|
+
{ key: i, className: block.wide ? "caf-slot caf-slot-wide" : "caf-slot" },
|
|
267
|
+
h(Block, { block, ctx })
|
|
268
|
+
)
|
|
269
|
+
)
|
|
270
|
+
)
|
|
271
|
+
: null
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const LAYOUT_COMPONENTS = { panel: Panel, records: RecordsWithBlocks, steps: StepsLayout, chat: ChatLayout };
|
|
230
276
|
|
|
231
277
|
export function createApp(spec = {}) {
|
|
232
278
|
const layout = validate(spec);
|
|
@@ -402,6 +448,7 @@ const BASE_CSS = `
|
|
|
402
448
|
}
|
|
403
449
|
.caf-search:focus-visible { outline: 2px solid var(--caf-accent); outline-offset: 1px; }
|
|
404
450
|
.caf-computed { border-top: 1px solid var(--caf-border); padding-top: 0.7rem; }
|
|
451
|
+
.caf-records-blocks { margin-top: 1rem; }
|
|
405
452
|
.caf-empty, .caf-loading { color: var(--caf-muted); font-size: 0.9rem; text-align: center; padding: 1.6rem 1rem; }
|
|
406
453
|
.caf-block-error { border-color: var(--caf-danger); gap: 0.4rem; }
|
|
407
454
|
.caf-block-error strong { font-size: 0.9rem; color: var(--caf-danger); }
|