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/README.md CHANGED
@@ -428,9 +428,39 @@ blank pane), the per-document data pools (`d` inside any block, `output`,
428
428
  `custom`, or `when` is THAT document's data; `ctx.update` writes there),
429
429
  the empty state, and persistence — all documents and which one is open
430
430
  survive reload. Documents keys: `label`, `title`, `blocks`, `empty`.
431
- Inside `documents`, blocks work as in `panel`; `chat` blocks and `file`
432
- fields are not supported there (both are app-wide, not per-document) and
433
- throw at creation.
431
+
432
+ Inside `documents`, blocks work as in `panel` **including `chat` and
433
+ `file`, which become per-document**:
434
+
435
+ - A `chat` block inside `documents` gives **each document its own
436
+ conversation**, persisted with that document (`data.chat` is scoped). Its
437
+ `system` function receives the document's data, so the copilot talks
438
+ about THIS document. One chat block per document (a second throws).
439
+ - A `file` field inside `documents` scopes `ctx.files[key]` to the active
440
+ document — two documents never collide on the same field key. As
441
+ everywhere, the live `File` is memory-only: metadata survives reload,
442
+ the file is re-picked per document.
443
+
444
+ That combination — one file plus one conversation per tab — is the
445
+ multi-document annotator shape (PDFs, images, CSVs) declared in full:
446
+
447
+ ```js
448
+ ArtifactKit.createApp({
449
+ title: "Anotador de archivos",
450
+ layout: "documents",
451
+ documents: {
452
+ label: "archivo",
453
+ title: (d) => (d.archivo && d.archivo.name) || "Sin archivo",
454
+ blocks: [
455
+ { fields: [{ key: "archivo", label: "Archivo", type: "file" }] },
456
+ { custom: (ctx) => { /* render ctx.files.archivo */ }, fill: true },
457
+ { title: "Copiloto", chat: {
458
+ system: (d) => "Ayudás con el archivo " + ((d.archivo && d.archivo.name) || "(ninguno)"),
459
+ }},
460
+ ],
461
+ },
462
+ })
463
+ ```
434
464
 
435
465
  ### External libraries, uploaded files, full-bleed custom, viewer identity
436
466
 
package/dist/index.esm.js CHANGED
@@ -1550,15 +1550,10 @@ function validate(spec) {
1550
1550
  throw new Error("claude-artifact-framework: documents.title must be a function of the document's data returning the tab label.");
1551
1551
  }
1552
1552
  checkBlocks(d.blocks);
1553
- const flat = flattenBlocks(d.blocks);
1554
- if (flat.some((b) => b && b.chat)) {
1555
- throw new Error(
1556
- 'claude-artifact-framework: a chat block inside "documents" is not supported \u2014 the conversation store is app-wide, not per document.'
1557
- );
1558
- }
1559
- if (flat.some((b) => (b.fields || []).some((f) => f.type === "file"))) {
1553
+ const chats = flattenBlocks(d.blocks).filter((b) => b && b.chat);
1554
+ if (chats.length > 1) {
1560
1555
  throw new Error(
1561
- "claude-artifact-framework: `file` fields live in the flat data pool \u2014 they are not supported inside documents (the live File cannot be scoped per document)."
1556
+ "claude-artifact-framework: only one chat block per document \u2014 each document's conversation persists under its own data.chat and there is exactly one."
1562
1557
  );
1563
1558
  }
1564
1559
  return layout;
@@ -1845,6 +1840,7 @@ function DocumentsLayout({ spec, ctx }) {
1845
1840
  ctx.update((d) => {
1846
1841
  d.docs = d.docs.filter((doc) => doc.id !== id);
1847
1842
  });
1843
+ delete ctx.files["doc:" + id];
1848
1844
  if (active && active.id === id) {
1849
1845
  const next = docs[idx + 1] || docs[idx - 1];
1850
1846
  if (next) ctx.setTab(DOC_TAB_KEY, next.id);
@@ -1869,7 +1865,12 @@ function DocumentsLayout({ spec, ctx }) {
1869
1865
  update: (fn) => ctx.update((d) => {
1870
1866
  const doc = (d.docs || []).find((x) => x.id === active.id);
1871
1867
  if (doc) fn(doc.data);
1872
- })
1868
+ }),
1869
+ // Live File objects scoped per document: each doc gets its own sub-map,
1870
+ // so two documents with the same file field never collide. Like the flat
1871
+ // pool, the File itself is memory-only — metadata survives reload, the
1872
+ // file is re-picked per document.
1873
+ files: ctx.files["doc:" + active.id] = ctx.files["doc:" + active.id] || {}
1873
1874
  };
1874
1875
  return createElement(
1875
1876
  "div",