claude-artifact-framework 0.10.0 → 0.11.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-artifact-framework",
3
- "version": "0.10.0",
3
+ "version": "0.11.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
@@ -229,15 +229,13 @@ function validate(spec) {
229
229
  throw new Error("claude-artifact-framework: documents.title must be a function of the document's data returning the tab label.");
230
230
  }
231
231
  checkBlocks(d.blocks);
232
- const flat = flattenBlocks(d.blocks);
233
- if (flat.some((b) => b && b.chat)) {
234
- throw new Error(
235
- 'claude-artifact-framework: a chat block inside "documents" is not supported — the conversation store is app-wide, not per document.'
236
- );
237
- }
238
- if (flat.some((b) => (b.fields || []).some((f) => f.type === "file"))) {
232
+ // Inside documents both chat and file are PER DOCUMENT: the scoped ctx
233
+ // gives each document its own data.chat and its own ctx.files. One chat
234
+ // block in the declaration = one conversation per document.
235
+ const chats = flattenBlocks(d.blocks).filter((b) => b && b.chat);
236
+ if (chats.length > 1) {
239
237
  throw new Error(
240
- 'claude-artifact-framework: `file` fields live in the flat data pool they are not supported inside documents (the live File cannot be scoped per document).'
238
+ "claude-artifact-framework: only one chat block per documenteach document's conversation persists under its own data.chat and there is exactly one."
241
239
  );
242
240
  }
243
241
  return layout;
@@ -573,6 +571,7 @@ function DocumentsLayout({ spec, ctx }) {
573
571
  ctx.update((d) => {
574
572
  d.docs = d.docs.filter((doc) => doc.id !== id);
575
573
  });
574
+ delete ctx.files["doc:" + id];
576
575
  if (active && active.id === id) {
577
576
  const next = docs[idx + 1] || docs[idx - 1];
578
577
  if (next) ctx.setTab(DOC_TAB_KEY, next.id);
@@ -603,6 +602,11 @@ function DocumentsLayout({ spec, ctx }) {
603
602
  const doc = (d.docs || []).find((x) => x.id === active.id);
604
603
  if (doc) fn(doc.data);
605
604
  }),
605
+ // Live File objects scoped per document: each doc gets its own sub-map,
606
+ // so two documents with the same file field never collide. Like the flat
607
+ // pool, the File itself is memory-only — metadata survives reload, the
608
+ // file is re-picked per document.
609
+ files: (ctx.files["doc:" + active.id] = ctx.files["doc:" + active.id] || {}),
606
610
  };
607
611
 
608
612
  return h(