yaml-flow 2.7.0 → 3.0.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.
Files changed (49) hide show
  1. package/README.md +168 -3
  2. package/browser/ingest-board.js +296 -0
  3. package/browser/live-cards.js +303 -0
  4. package/browser/live-cards.schema.json +22 -2
  5. package/dist/card-compute/index.cjs +6751 -0
  6. package/dist/card-compute/index.cjs.map +1 -1
  7. package/dist/card-compute/index.d.cts +24 -1
  8. package/dist/card-compute/index.d.ts +24 -1
  9. package/dist/card-compute/index.js +6747 -1
  10. package/dist/card-compute/index.js.map +1 -1
  11. package/dist/{constants-BEbO2_OK.d.ts → constants-B_ftYTTE.d.ts} +36 -6
  12. package/dist/{constants-BNjeIlZ8.d.cts → constants-CiyHX8L-.d.cts} +36 -6
  13. package/dist/continuous-event-graph/index.cjs +399 -42
  14. package/dist/continuous-event-graph/index.cjs.map +1 -1
  15. package/dist/continuous-event-graph/index.d.cts +124 -5
  16. package/dist/continuous-event-graph/index.d.ts +124 -5
  17. package/dist/continuous-event-graph/index.js +396 -43
  18. package/dist/continuous-event-graph/index.js.map +1 -1
  19. package/dist/event-graph/index.cjs +6784 -44
  20. package/dist/event-graph/index.cjs.map +1 -1
  21. package/dist/event-graph/index.d.cts +5 -5
  22. package/dist/event-graph/index.d.ts +5 -5
  23. package/dist/event-graph/index.js +6777 -43
  24. package/dist/event-graph/index.js.map +1 -1
  25. package/dist/index.cjs +7678 -73
  26. package/dist/index.cjs.map +1 -1
  27. package/dist/index.d.cts +6 -6
  28. package/dist/index.d.ts +6 -6
  29. package/dist/index.js +7665 -73
  30. package/dist/index.js.map +1 -1
  31. package/dist/inference/index.cjs +17 -8
  32. package/dist/inference/index.cjs.map +1 -1
  33. package/dist/inference/index.d.cts +2 -2
  34. package/dist/inference/index.d.ts +2 -2
  35. package/dist/inference/index.js +17 -8
  36. package/dist/inference/index.js.map +1 -1
  37. package/dist/step-machine/index.cjs +6600 -0
  38. package/dist/step-machine/index.cjs.map +1 -1
  39. package/dist/step-machine/index.d.cts +26 -1
  40. package/dist/step-machine/index.d.ts +26 -1
  41. package/dist/step-machine/index.js +6596 -1
  42. package/dist/step-machine/index.js.map +1 -1
  43. package/dist/{types-DAI_a2as.d.ts → types-BpWrH1sf.d.cts} +16 -7
  44. package/dist/{types-DAI_a2as.d.cts → types-BpWrH1sf.d.ts} +16 -7
  45. package/dist/{types-mS_pPftm.d.ts → types-BuEo3wVG.d.ts} +1 -1
  46. package/dist/{types-C2lOwquM.d.cts → types-CxJg9Jrt.d.cts} +1 -1
  47. package/package.json +3 -2
  48. package/schema/event-graph.schema.json +254 -0
  49. package/schema/live-cards.schema.json +22 -2
@@ -51,10 +51,25 @@ var LiveCard = (function () {
51
51
  .lc-todo-item:last-child { border-bottom:none; }
52
52
  .lc-notes-preview { min-height:80px; }
53
53
  .lc-source-pill { display:inline-flex; align-items:center; gap:0.5rem; padding:0.5rem 0.75rem; border-radius:2rem; font-size:0.8rem; background:var(--bs-light,#f8f9fa); border:1px solid var(--bs-border-color,#dee2e6); }
54
+ .lc-dropzone { border:2px dashed var(--bs-border-color,#dee2e6); border-radius:.5rem; padding:1.5rem; text-align:center; cursor:pointer; transition:border-color .15s,background .15s; }
55
+ .lc-dropzone:hover { border-color:var(--bs-primary,#0d6efd); }
56
+ .lc-dropzone.lc-drag-over { border-color:var(--bs-primary,#0d6efd); background:rgba(13,110,253,.05); }
57
+ .lc-dropzone.lc-disabled { pointer-events:none; opacity:.5; }
58
+ .lc-staged-file { display:flex; align-items:center; gap:.5rem; padding:.125rem 0; }
59
+ .lc-chat-el { display:flex; flex-direction:column; }
60
+ .lc-chat-body { flex:1; overflow-y:auto; max-height:300px; padding:.25rem; }
61
+ .lc-chat-bubble { padding:.375rem .625rem; margin:.25rem 0; border-radius:.75rem; max-width:85%; word-wrap:break-word; font-size:.875rem; }
62
+ .lc-chat-bubble-user { background:var(--bs-primary-bg-subtle,#cfe2ff); margin-left:auto; }
63
+ .lc-chat-bubble-assistant { background:var(--bs-light,#f8f9fa); }
64
+ .lc-chat-bubble-system { background:transparent; color:var(--bs-secondary,#6c757d); font-style:italic; text-align:center; max-width:100%; font-size:.8rem; }
65
+ .lc-chat-input-bar { display:flex; gap:.25rem; align-items:center; }
66
+ .lc-chat-processing { display:flex; align-items:center; gap:.5rem; padding:.25rem .5rem; color:var(--bs-secondary,#6c757d); font-size:.8rem; }
54
67
  @media (max-width:576px) {
55
68
  .lc-metric-value { font-size:1.5rem; }
56
69
  .lc-chart-wrap { min-height:150px; }
57
70
  .lc-chat-msg { max-width:95%; }
71
+ .lc-chat-body { max-height:200px; }
72
+ .lc-chat-bubble { max-width:95%; }
58
73
  }
59
74
  `;
60
75
  document.head.appendChild(s);
@@ -150,6 +165,7 @@ var LiveCard = (function () {
150
165
  markdown: config.markdown || null,
151
166
  sanitize: config.sanitize || null,
152
167
  chartLib: config.chartLib || null,
168
+ onAction: config.onAction || function () {},
153
169
  };
154
170
 
155
171
  const _cleanup = {}; // nodeId → { ac, timers, charts, unsubs }
@@ -671,6 +687,276 @@ var LiveCard = (function () {
671
687
  el.innerHTML = `<pre class="small mb-0">${_esc(JSON.stringify(data, null, 2))}</pre>`;
672
688
  }
673
689
 
690
+ // ---- file-upload ----
691
+
692
+ function _renderFileUpload(data, el, elemDef, node) {
693
+ const cleanup = _getCleanup(node.id);
694
+ const signal = cleanup.ac.signal;
695
+ const ed = elemDef.data || {};
696
+ const uploaded = Array.isArray(data) ? data : [];
697
+ const showUpload = ed.upload !== false;
698
+ const accept = ed.accept || ['.txt','.csv','.md','.json','.html','.xml','.pdf','.xlsx','.docx','.pptx','.png','.jpg','.jpeg'];
699
+ const acceptSet = new Set(accept.map(e => e.toLowerCase()));
700
+ const multiple = ed.multiple !== false;
701
+ const placeholder = ed.placeholder || 'Drop files here or click to browse';
702
+ const uid = 'lc-fu-' + (elemDef.id || Math.random().toString(36).slice(2, 8));
703
+
704
+ let stagedFiles = el._stagedFiles || [];
705
+ el._stagedFiles = stagedFiles;
706
+
707
+ let h = '';
708
+
709
+ // Drop zone
710
+ if (showUpload) {
711
+ h += `<div class="lc-dropzone mb-2" id="${uid}-dz">`;
712
+ h += '<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" class="text-muted mb-1"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>';
713
+ h += `<div class="small text-muted">${_esc(placeholder)}</div>`;
714
+ h += `<input type="file" id="${uid}-fi" class="d-none"${multiple ? ' multiple' : ''} accept="${accept.join(',')}">`;
715
+ h += '</div>';
716
+ h += `<div id="${uid}-staged"></div>`;
717
+ }
718
+
719
+ // Uploaded files list
720
+ if (uploaded.length) {
721
+ h += '<div class="lc-uploaded-files">';
722
+ uploaded.forEach(f => {
723
+ const name = typeof f === 'string' ? f : (f.name || '');
724
+ const url = typeof f === 'string' ? null : f.url;
725
+ h += '<div class="d-flex align-items-center gap-1 small mb-1">';
726
+ h += '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>';
727
+ if (url) h += `<a href="${_esc(url)}" class="text-truncate" target="_blank" download>${_esc(name)}</a>`;
728
+ else h += `<span class="text-truncate">${_esc(name)}</span>`;
729
+ h += '</div>';
730
+ });
731
+ h += '</div>';
732
+ }
733
+
734
+ if (!showUpload && !uploaded.length) {
735
+ h = `<p class="text-muted small">${_esc(ed.placeholder || 'No files')}</p>`;
736
+ }
737
+
738
+ el.innerHTML = h;
739
+
740
+ if (!showUpload) {
741
+ el._fileUpload = { getFiles: () => [], clear: () => {} };
742
+ return;
743
+ }
744
+
745
+ const dz = document.getElementById(uid + '-dz');
746
+ const fi = document.getElementById(uid + '-fi');
747
+ const stagedEl = document.getElementById(uid + '-staged');
748
+ if (!dz) return;
749
+
750
+ function addFiles(fileList) {
751
+ for (const f of fileList) {
752
+ const ext = '.' + f.name.split('.').pop().toLowerCase();
753
+ if (!acceptSet.has(ext)) continue;
754
+ if (!stagedFiles.find(s => s.name === f.name)) stagedFiles.push(f);
755
+ }
756
+ renderStaged();
757
+ cfg.onPatchState(node.id, { _stagedFiles: stagedFiles.map(f => ({ name: f.name, size: f.size })) });
758
+ }
759
+
760
+ function renderStaged() {
761
+ if (!stagedFiles.length) { stagedEl.innerHTML = ''; return; }
762
+ let sh = '';
763
+ stagedFiles.forEach((f, i) => {
764
+ sh += '<div class="lc-staged-file">';
765
+ sh += '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>';
766
+ sh += `<span class="small flex-grow-1 text-truncate">${_esc(f.name)}</span>`;
767
+ sh += `<button class="btn btn-sm btn-link text-danger p-0 lc-rm-staged" data-idx="${i}">&times;</button>`;
768
+ sh += '</div>';
769
+ });
770
+ stagedEl.innerHTML = sh;
771
+ stagedEl.querySelectorAll('.lc-rm-staged').forEach(btn => {
772
+ btn.addEventListener('click', () => {
773
+ stagedFiles.splice(parseInt(btn.dataset.idx), 1);
774
+ el._stagedFiles = stagedFiles;
775
+ renderStaged();
776
+ }, { signal });
777
+ });
778
+ }
779
+
780
+ dz.addEventListener('click', () => fi.click(), { signal });
781
+ dz.addEventListener('dragover', e => { e.preventDefault(); dz.classList.add('lc-drag-over'); }, { signal });
782
+ dz.addEventListener('dragleave', () => dz.classList.remove('lc-drag-over'), { signal });
783
+ dz.addEventListener('drop', e => { e.preventDefault(); dz.classList.remove('lc-drag-over'); addFiles(e.dataTransfer.files); }, { signal });
784
+ fi.addEventListener('change', e => { addFiles(e.target.files); e.target.value = ''; }, { signal });
785
+
786
+ renderStaged();
787
+
788
+ el._fileUpload = {
789
+ getFiles: () => stagedFiles,
790
+ clear: () => { stagedFiles = []; el._stagedFiles = []; renderStaged(); },
791
+ disable: () => { dz.classList.add('lc-disabled'); fi.disabled = true; },
792
+ enable: () => { dz.classList.remove('lc-disabled'); fi.disabled = false; },
793
+ };
794
+ }
795
+
796
+ // ---- chat (element kind) ----
797
+
798
+ function _renderChatEl(data, el, elemDef, node) {
799
+ const cleanup = _getCleanup(node.id);
800
+ const signal = cleanup.ac.signal;
801
+ const ed = elemDef.data || {};
802
+ const messages = Array.isArray(data) ? data : [];
803
+ const placeholder = ed.placeholder || 'Type a message...';
804
+ const canAttach = ed.fileAttach === true;
805
+ const accept = ed.fileAccept || ['.txt','.csv','.md','.json','.html','.xml','.pdf','.xlsx','.docx','.pptx','.png','.jpg','.jpeg'];
806
+ const uid = 'lc-ch-' + (elemDef.id || Math.random().toString(36).slice(2, 8));
807
+
808
+ let h = '<div class="lc-chat-el">';
809
+ h += `<div class="lc-chat-body" id="${uid}-body"></div>`;
810
+ h += '<div class="lc-chat-input-bar">';
811
+ if (canAttach) {
812
+ h += `<input type="file" id="${uid}-fi" class="d-none" multiple accept="${accept.join(',')}">`;
813
+ h += `<button class="btn btn-sm btn-outline-secondary" id="${uid}-attach" title="Attach files" type="button">`;
814
+ h += '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48"/></svg>';
815
+ h += '</button>';
816
+ }
817
+ h += `<input type="text" class="form-control form-control-sm flex-grow-1" id="${uid}-input" placeholder="${_esc(placeholder)}">`;
818
+ h += `<button class="btn btn-sm btn-outline-primary" id="${uid}-send" type="button">`;
819
+ h += '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>';
820
+ h += '</button></div>';
821
+ if (canAttach) h += `<div id="${uid}-staged" class="mt-1"></div>`;
822
+ h += '</div>';
823
+
824
+ el.innerHTML = h;
825
+
826
+ const body = document.getElementById(uid + '-body');
827
+ const input = document.getElementById(uid + '-input');
828
+ const sendBtn = document.getElementById(uid + '-send');
829
+ const attachBtn = canAttach ? document.getElementById(uid + '-attach') : null;
830
+ const fileInput = canAttach ? document.getElementById(uid + '-fi') : null;
831
+ const stagedEl = canAttach ? document.getElementById(uid + '-staged') : null;
832
+
833
+ let stagedFiles = [];
834
+
835
+ function appendMsg(msg) {
836
+ const bub = document.createElement('div');
837
+ const roleClass = msg.role === 'user' ? 'lc-chat-bubble-user'
838
+ : msg.role === 'assistant' ? 'lc-chat-bubble-assistant'
839
+ : 'lc-chat-bubble-system';
840
+ bub.className = 'lc-chat-bubble ' + roleClass;
841
+ if (msg.role === 'assistant') {
842
+ bub.innerHTML = _renderMd(msg.text || '');
843
+ } else {
844
+ bub.textContent = msg.text || '';
845
+ }
846
+ if (msg.files && msg.files.length) {
847
+ const fDiv = document.createElement('div');
848
+ fDiv.className = 'small mt-1';
849
+ msg.files.forEach(f => {
850
+ const name = typeof f === 'string' ? f : f.name;
851
+ fDiv.innerHTML += '\uD83D\uDCCE ' + _esc(name) + '<br>';
852
+ });
853
+ bub.appendChild(fDiv);
854
+ }
855
+ body.appendChild(bub);
856
+ }
857
+
858
+ messages.forEach(appendMsg);
859
+ body.scrollTop = body.scrollHeight;
860
+
861
+ function renderStaged() {
862
+ if (!stagedEl) return;
863
+ if (!stagedFiles.length) { stagedEl.innerHTML = ''; return; }
864
+ stagedEl.innerHTML = stagedFiles.map((f, i) =>
865
+ `<div class="d-flex align-items-center gap-1 small"><span>\uD83D\uDCCE ${_esc(f.name)}</span><button class="btn btn-sm btn-link text-danger p-0 lc-rm-cs" data-idx="${i}">&times;</button></div>`
866
+ ).join('');
867
+ stagedEl.querySelectorAll('.lc-rm-cs').forEach(btn => {
868
+ btn.addEventListener('click', () => { stagedFiles.splice(parseInt(btn.dataset.idx), 1); renderStaged(); }, { signal });
869
+ });
870
+ }
871
+
872
+ if (attachBtn && fileInput) {
873
+ const acceptS = new Set(accept.map(x => x.toLowerCase()));
874
+ attachBtn.addEventListener('click', () => fileInput.click(), { signal });
875
+ fileInput.addEventListener('change', e => {
876
+ for (const f of e.target.files) {
877
+ const ext = '.' + f.name.split('.').pop().toLowerCase();
878
+ if (acceptS.has(ext) && !stagedFiles.find(s => s.name === f.name)) stagedFiles.push(f);
879
+ }
880
+ e.target.value = '';
881
+ renderStaged();
882
+ }, { signal });
883
+ }
884
+
885
+ function doSend() {
886
+ const text = input.value.trim();
887
+ if (!text && !stagedFiles.length) return;
888
+ const msg = { role: 'user', text: text || '' };
889
+ if (stagedFiles.length) msg.files = stagedFiles.map(f => ({ name: f.name, size: f.size }));
890
+ appendMsg(msg);
891
+ body.scrollTop = body.scrollHeight;
892
+ input.value = '';
893
+ const filesToSend = stagedFiles.slice();
894
+ stagedFiles = [];
895
+ renderStaged();
896
+ cfg.onAction(node.id, 'chat-send', { text: msg.text, files: filesToSend, elemId: elemDef.id });
897
+ }
898
+
899
+ sendBtn.addEventListener('click', doSend, { signal });
900
+ input.addEventListener('keydown', e => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); doSend(); } }, { signal });
901
+
902
+ el._chat = {
903
+ appendMessage: (role, text, files) => { appendMsg({ role, text, files }); body.scrollTop = body.scrollHeight; },
904
+ showProcessing: (text) => {
905
+ let ind = body.querySelector('.lc-chat-processing');
906
+ if (!ind) {
907
+ ind = document.createElement('div');
908
+ ind.className = 'lc-chat-processing';
909
+ ind.innerHTML = '<span class="spinner-border spinner-border-sm"></span><span class="small">Processing...</span>';
910
+ body.appendChild(ind);
911
+ }
912
+ if (text) ind.querySelector('.small').textContent = text;
913
+ body.scrollTop = body.scrollHeight;
914
+ },
915
+ removeProcessing: () => { const ind = body.querySelector('.lc-chat-processing'); if (ind) ind.remove(); },
916
+ disable: () => { input.disabled = true; sendBtn.disabled = true; if (attachBtn) attachBtn.disabled = true; },
917
+ enable: () => { input.disabled = false; sendBtn.disabled = false; if (attachBtn) attachBtn.disabled = false; },
918
+ };
919
+ }
920
+
921
+ // ---- actions ----
922
+
923
+ function _renderActions(data, el, elemDef, node) {
924
+ const cleanup = _getCleanup(node.id);
925
+ const signal = cleanup.ac.signal;
926
+ const ed = elemDef.data || {};
927
+ const buttons = ed.buttons || (Array.isArray(data) ? data : []);
928
+ if (!buttons.length) { el.innerHTML = ''; return; }
929
+
930
+ let h = '<div class="d-flex gap-2 flex-wrap">';
931
+ buttons.forEach(btn => {
932
+ const style = btn.style || 'outline-secondary';
933
+ const size = btn.size || 'sm';
934
+ const dis = typeof btn.disabled === 'string' ? _resolveBind(node, btn.disabled) : btn.disabled;
935
+ h += `<button class="btn btn-${_esc(style)} btn-${size}" data-action-id="${_esc(btn.id)}"${dis ? ' disabled' : ''}>`;
936
+ h += _esc(btn.label || btn.id);
937
+ h += '</button>';
938
+ });
939
+ h += '</div>';
940
+ el.innerHTML = h;
941
+
942
+ el.querySelectorAll('[data-action-id]').forEach(btnEl => {
943
+ btnEl.addEventListener('click', () => {
944
+ cfg.onAction(node.id, 'action', { buttonId: btnEl.dataset.actionId, elemId: elemDef.id });
945
+ }, { signal });
946
+ });
947
+
948
+ el._actions = {
949
+ setDisabled: (buttonId, disabled) => {
950
+ const b = el.querySelector(`[data-action-id="${buttonId}"]`);
951
+ if (b) b.disabled = disabled;
952
+ },
953
+ setLabel: (buttonId, label) => {
954
+ const b = el.querySelector(`[data-action-id="${buttonId}"]`);
955
+ if (b) b.textContent = label;
956
+ },
957
+ };
958
+ }
959
+
674
960
  // ---- Register built-in renderers ----
675
961
 
676
962
  _renderers.table = _renderTable;
@@ -687,6 +973,9 @@ var LiveCard = (function () {
687
973
  _renderers.text = _renderText;
688
974
  _renderers.markdown = _renderMarkdown;
689
975
  _renderers.custom = _renderCustom;
976
+ _renderers['file-upload'] = _renderFileUpload;
977
+ _renderers['chat'] = _renderChatEl;
978
+ _renderers.actions = _renderActions;
690
979
 
691
980
  // ===========================================================================
692
981
  // _renderElements — render all view.elements for a card node
@@ -696,6 +985,8 @@ var LiveCard = (function () {
696
985
  const view = node.view;
697
986
  if (!view || !Array.isArray(view.elements)) { containerEl.innerHTML = ''; return; }
698
987
 
988
+ if (_nodeEls[node.id]) _nodeEls[node.id].elements = {};
989
+
699
990
  const container = document.createElement('div');
700
991
  container.className = 'row g-2';
701
992
 
@@ -729,6 +1020,8 @@ var LiveCard = (function () {
729
1020
  inner.innerHTML = `<div class="text-danger small">Render error: ${_esc(e.message)}</div>`;
730
1021
  }
731
1022
 
1023
+ if (elemDef.id && _nodeEls[node.id]) _nodeEls[node.id].elements[elemDef.id] = inner;
1024
+
732
1025
  container.appendChild(col);
733
1026
  });
734
1027
 
@@ -929,6 +1222,15 @@ var LiveCard = (function () {
929
1222
  chatEl.scrollTop = chatEl.scrollHeight;
930
1223
  }
931
1224
 
1225
+ // ===========================================================================
1226
+ // Element access
1227
+ // ===========================================================================
1228
+
1229
+ function getElement(nodeId, elemId) {
1230
+ const info = _nodeEls[nodeId];
1231
+ return (info && info.elements && info.elements[elemId]) || null;
1232
+ }
1233
+
932
1234
  // ===========================================================================
933
1235
  // Return engine
934
1236
  // ===========================================================================
@@ -941,6 +1243,7 @@ var LiveCard = (function () {
941
1243
  notify,
942
1244
  subscribe,
943
1245
  appendChatMessage,
1246
+ getElement,
944
1247
  registerRenderer(name, fn) { _renderers[name] = fn; },
945
1248
  renderers: _renderers,
946
1249
  };
@@ -116,7 +116,7 @@
116
116
  "kind": {
117
117
  "enum": ["metric", "table", "chart", "form", "filter", "list",
118
118
  "notes", "todo", "alert", "narrative", "badge", "text",
119
- "markdown", "custom"]
119
+ "markdown", "custom", "file-upload", "chat", "actions"]
120
120
  },
121
121
  "label": { "type": "string", "description": "Heading above this element" },
122
122
  "className": { "type": "string", "description": "Bootstrap grid class, e.g. 'col-12 col-md-6'" },
@@ -141,7 +141,27 @@
141
141
  }
142
142
  },
143
143
  "colorMap": { "type": "object", "description": "badge: value → Bootstrap color" },
144
- "style": { "enum": ["heading", "muted", "default"], "description": "text: display style" }
144
+ "style": { "enum": ["heading", "muted", "default"], "description": "text: display style" },
145
+ "upload": { "type": "boolean", "default": true, "description": "file-upload: show drop zone (false = read-only file list)" },
146
+ "accept": { "type": "array", "items": { "type": "string" }, "description": "file-upload: allowed extensions" },
147
+ "multiple": { "type": "boolean", "default": true, "description": "file-upload: allow multiple files" },
148
+ "fileAttach": { "type": "boolean", "default": false, "description": "chat: enable inline file attachments" },
149
+ "fileAccept": { "type": "array", "items": { "type": "string" }, "description": "chat: allowed attachment extensions" },
150
+ "buttons": {
151
+ "type": "array",
152
+ "description": "actions: button definitions",
153
+ "items": {
154
+ "type": "object",
155
+ "required": ["id", "label"],
156
+ "properties": {
157
+ "id": { "type": "string" },
158
+ "label": { "type": "string" },
159
+ "style": { "type": "string", "description": "Bootstrap button variant, e.g. 'success', 'outline-danger'" },
160
+ "size": { "type": "string", "default": "sm" },
161
+ "disabled": { "oneOf": [{ "type": "boolean" }, { "type": "string", "description": "state path — truthy = disabled" }] }
162
+ }
163
+ }
164
+ }
145
165
  }
146
166
  }
147
167
  }