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 CHANGED
@@ -12,7 +12,7 @@ mirrors npm packages automatically.
12
12
  ### As a global script (`<script>` tag)
13
13
 
14
14
  ```html
15
- <script src="https://cdn.jsdelivr.net/npm/claude-artifact-framework@0.5.0/dist/index.global.js"></script>
15
+ <script src="https://cdn.jsdelivr.net/npm/claude-artifact-framework@0.5.1/dist/index.global.js"></script>
16
16
  <script>
17
17
  const { storage, createStore, createPersistedStore, createSharedStore } = ArtifactKit;
18
18
  </script>
@@ -27,11 +27,11 @@ mirrors npm packages automatically.
27
27
  createStore,
28
28
  createPersistedStore,
29
29
  createSharedStore,
30
- } from "https://cdn.jsdelivr.net/npm/claude-artifact-framework@0.5.0/dist/index.esm.js";
30
+ } from "https://cdn.jsdelivr.net/npm/claude-artifact-framework@0.5.1/dist/index.esm.js";
31
31
  </script>
32
32
  ```
33
33
 
34
- Pin a version (`@0.5.0`) for reproducible artifacts, or drop the version
34
+ Pin a version (`@0.5.1`) for reproducible artifacts, or drop the version
35
35
  (`claude-artifact-framework/dist/...`) to always get the latest release.
36
36
 
37
37
  ### Inside a React artifact
@@ -254,6 +254,43 @@ ArtifactKit.createApp({
254
254
  that haven't been persisted yet. View state (which screen is open) stays
255
255
  personal per viewer.
256
256
 
257
+ ## API reference
258
+
259
+ Everything an app can declare, in one place.
260
+
261
+ **Top-level keys**: `title`, `subtitle`, `theme: { seed }`, `layout`, `blocks`,
262
+ `records`, `steps`, `chat`, `data`, `shared`. Anything else throws.
263
+
264
+ **Layouts** (`layout:`): `panel` (default — grid of blocks), `records` (CRUD
265
+ list), `steps` (wizard), `chat` (conversation with Claude). Each requires its
266
+ matching key (`blocks` / `records` / `steps` / `chat`).
267
+
268
+ **Block types** (entries of `blocks`): `fields`, `output`, `text`, `list`,
269
+ `chart`, `timer`, `custom`. One type per entry, plus optional `title`, `empty`,
270
+ `wide`. In the `records` layout, `blocks` render below the list (never on the
271
+ detail screen); their functions receive the same `data` object as everywhere
272
+ else — the collection lives at `d.records`.
273
+
274
+ **Field types** (`type:` on any field): `text` (default), `textarea`,
275
+ `number`, `money`, `percent`, `check` (checkbox), `date`, `select` (needs
276
+ `options`). Other field keys: `key` (required), `label`, `value` (initial),
277
+ `required`, `min`, `max`, `step`, `placeholder`, `hint`, `rows`, `options`.
278
+ An unknown `type` throws naming the valid ones.
279
+
280
+ **`records` keys**: `label`, `fields`, `title(r)`, `subtitle(r)`, `sort(a,b)`,
281
+ `summary(all)`, `empty`, `search`, `compute`. `id` is framework-assigned and
282
+ reserved.
283
+
284
+ **`steps` keys** (per step): `title`, `text`, `fields`, `result(d)`. Field
285
+ keys must be unique across steps.
286
+
287
+ **`chat` keys**: `system` (string or `(d) => string`), `greeting`,
288
+ `placeholder`, `tools`, `model`, `maxTokens`. Each tool:
289
+ `{ description, input, run(input, ctx) }`.
290
+
291
+ **Formats** (`format:` on output/chart/compute items): `money`, `percent`,
292
+ `number`, `date`.
293
+
257
294
  ## `storage`
258
295
 
259
296
  Thin wrapper over `window.storage`, the persistence API Claude injects into a
package/dist/index.esm.js CHANGED
@@ -373,6 +373,7 @@ function seriesColors(seed, n) {
373
373
  }
374
374
 
375
375
  // src/blocks.js
376
+ var FIELD_TYPES = ["text", "textarea", "number", "money", "percent", "check", "date", "select"];
376
377
  function formatValue(value, format) {
377
378
  if (value === null || value === void 0 || value === "") return "\u2014";
378
379
  const n = Number(value);
@@ -1165,6 +1166,41 @@ Continue with this information. If you have everything you need, answer in natur
1165
1166
  var LAYOUTS = ["panel", "records", "steps", "chat"];
1166
1167
  var RESERVED = ["title", "empty", "wide", "onSelect", "subtitle"];
1167
1168
  var TOP_KEYS = ["title", "subtitle", "theme", "layout", "blocks", "records", "steps", "chat", "data", "shared"];
1169
+ function checkFields(fields, where) {
1170
+ for (const f of fields || []) {
1171
+ if (!f || !f.key) throw new Error(`claude-artifact-framework: every field in ${where} needs a \`key\`.`);
1172
+ if (f.type !== void 0 && !FIELD_TYPES.includes(f.type)) {
1173
+ throw new Error(
1174
+ `claude-artifact-framework: field "${f.key}" in ${where} has unknown type "${f.type}". Valid types: ${FIELD_TYPES.join(", ")}.`
1175
+ );
1176
+ }
1177
+ }
1178
+ }
1179
+ function checkBlocks(blocks) {
1180
+ if (!Array.isArray(blocks)) {
1181
+ throw new Error("claude-artifact-framework: `blocks` must be an array.");
1182
+ }
1183
+ blocks.forEach((block, i) => {
1184
+ if (!block || typeof block !== "object") {
1185
+ throw new Error(`claude-artifact-framework: blocks[${i}] must be an object like { fields: [...] }.`);
1186
+ }
1187
+ const keys = Object.keys(block).filter((k) => !RESERVED.includes(k));
1188
+ const known = keys.filter((k) => BLOCK_NAMES.includes(k));
1189
+ if (known.length === 0) {
1190
+ throw new Error(
1191
+ `claude-artifact-framework: blocks[${i}] has no recognised block type (found: ${keys.join(", ") || "nothing"}). Valid block types: ${BLOCK_NAMES.join(", ")}.`
1192
+ );
1193
+ }
1194
+ if (known.length > 1) {
1195
+ throw new Error(
1196
+ `claude-artifact-framework: blocks[${i}] declares more than one block type (${known.join(
1197
+ ", "
1198
+ )}). Split them into separate entries of \`blocks\`.`
1199
+ );
1200
+ }
1201
+ checkFields(block.fields, `blocks[${i}]`);
1202
+ });
1203
+ }
1168
1204
  function validate(spec) {
1169
1205
  const unknownTop = Object.keys(spec).filter((k) => !TOP_KEYS.includes(k));
1170
1206
  if (unknownTop.length) {
@@ -1198,8 +1234,8 @@ function validate(spec) {
1198
1234
  if (!st.fields && !st.text && !st.result) {
1199
1235
  throw new Error(`claude-artifact-framework: steps[${i}] needs \`fields\`, \`text\`, or \`result\`.`);
1200
1236
  }
1237
+ checkFields(st.fields, `steps[${i}]`);
1201
1238
  for (const f of st.fields || []) {
1202
- if (!f || !f.key) throw new Error(`claude-artifact-framework: every field in steps[${i}] needs a \`key\`.`);
1203
1239
  if (seen.has(f.key)) {
1204
1240
  throw new Error(
1205
1241
  `claude-artifact-framework: field key "${f.key}" appears in more than one step \u2014 keys share one data pool and must be unique.`
@@ -1246,13 +1282,14 @@ function validate(spec) {
1246
1282
  `claude-artifact-framework: records has unknown keys (${unknownR.join(", ")}). Valid keys: ${RECORDS_KEYS.join(", ")}.`
1247
1283
  );
1248
1284
  }
1285
+ checkFields(r.fields, "records");
1249
1286
  const seen = /* @__PURE__ */ new Set();
1250
1287
  for (const f of r.fields) {
1251
- if (!f || !f.key) throw new Error("claude-artifact-framework: every records field needs a `key`.");
1252
1288
  if (f.key === "id") throw new Error('claude-artifact-framework: "id" is reserved on records \u2014 the framework assigns it.');
1253
1289
  if (seen.has(f.key)) throw new Error(`claude-artifact-framework: duplicate records field key "${f.key}".`);
1254
1290
  seen.add(f.key);
1255
1291
  }
1292
+ if (spec.blocks) checkBlocks(spec.blocks);
1256
1293
  for (const [key, c] of Object.entries(r.compute || {})) {
1257
1294
  if (seen.has(key) || key === "id") {
1258
1295
  throw new Error(
@@ -1267,29 +1304,7 @@ function validate(spec) {
1267
1304
  }
1268
1305
  return layout;
1269
1306
  }
1270
- const blocks = spec.blocks || [];
1271
- if (!Array.isArray(blocks)) {
1272
- throw new Error("claude-artifact-framework: `blocks` must be an array.");
1273
- }
1274
- blocks.forEach((block, i) => {
1275
- if (!block || typeof block !== "object") {
1276
- throw new Error(`claude-artifact-framework: blocks[${i}] must be an object like { fields: [...] }.`);
1277
- }
1278
- const keys = Object.keys(block).filter((k) => !RESERVED.includes(k));
1279
- const known = keys.filter((k) => BLOCK_NAMES.includes(k));
1280
- if (known.length === 0) {
1281
- throw new Error(
1282
- `claude-artifact-framework: blocks[${i}] has no recognised block type (found: ${keys.join(", ") || "nothing"}). Valid block types: ${BLOCK_NAMES.join(", ")}.`
1283
- );
1284
- }
1285
- if (known.length > 1) {
1286
- throw new Error(
1287
- `claude-artifact-framework: blocks[${i}] declares more than one block type (${known.join(
1288
- ", "
1289
- )}). Split them into separate entries of \`blocks\`.`
1290
- );
1291
- }
1292
- });
1307
+ checkBlocks(spec.blocks || []);
1293
1308
  return layout;
1294
1309
  }
1295
1310
  function defaultsFrom(spec) {
@@ -1353,7 +1368,26 @@ function Panel({ spec, ctx }) {
1353
1368
  )
1354
1369
  );
1355
1370
  }
1356
- var LAYOUT_COMPONENTS = { panel: Panel, records: RecordsLayout, steps: StepsLayout, chat: ChatLayout };
1371
+ function RecordsWithBlocks({ spec, ctx }) {
1372
+ const detailOpen = ctx.view.screen === "record" && (ctx.data.records || []).some((r) => r.id === ctx.view.arg);
1373
+ return createElement(
1374
+ "div",
1375
+ null,
1376
+ createElement(RecordsLayout, { spec, ctx }),
1377
+ !detailOpen && Array.isArray(spec.blocks) && spec.blocks.length ? createElement(
1378
+ "div",
1379
+ { className: "caf-panel caf-records-blocks" },
1380
+ spec.blocks.map(
1381
+ (block, i) => createElement(
1382
+ "section",
1383
+ { key: i, className: block.wide ? "caf-slot caf-slot-wide" : "caf-slot" },
1384
+ createElement(Block, { block, ctx })
1385
+ )
1386
+ )
1387
+ ) : null
1388
+ );
1389
+ }
1390
+ var LAYOUT_COMPONENTS = { panel: Panel, records: RecordsWithBlocks, steps: StepsLayout, chat: ChatLayout };
1357
1391
  function createApp(spec = {}) {
1358
1392
  const layout = validate(spec);
1359
1393
  const state = createAppState(defaultsFrom(spec), { shared: Boolean(spec.shared) });
@@ -1516,6 +1550,7 @@ var BASE_CSS = `
1516
1550
  }
1517
1551
  .caf-search:focus-visible { outline: 2px solid var(--caf-accent); outline-offset: 1px; }
1518
1552
  .caf-computed { border-top: 1px solid var(--caf-border); padding-top: 0.7rem; }
1553
+ .caf-records-blocks { margin-top: 1rem; }
1519
1554
  .caf-empty, .caf-loading { color: var(--caf-muted); font-size: 0.9rem; text-align: center; padding: 1.6rem 1rem; }
1520
1555
  .caf-block-error { border-color: var(--caf-danger); gap: 0.4rem; }
1521
1556
  .caf-block-error strong { font-size: 0.9rem; color: var(--caf-danger); }