claude-artifact-framework 0.8.1 → 0.9.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
@@ -323,7 +323,9 @@ blocks: [
323
323
 
324
324
  Action keys: `label`, `run`, optional `when` (hides the button when false)
325
325
  and `kind` (`"primary"` / `"danger"`). Row actions receive the record;
326
- block actions receive data. Changes persist like any other update.
326
+ block actions receive data. Changes persist like any other update. Give the
327
+ constructive action `kind: "primary"` — a "Vote"/"Add"/"Done" left as the
328
+ neutral default reads weaker than a `"danger"` next to it.
327
329
 
328
330
  Chat bubbles render the assistant's **markdown** (bold, code, lists,
329
331
  headings) automatically — HTML is escaped first, so model output can't
@@ -345,7 +347,8 @@ ArtifactKit.createApp({
345
347
  { output: (d) => [{ label: "Palabras", value: countWords(d.nota), big: true }] },
346
348
  ],
347
349
  sidebar: [
348
- { chat: {
350
+ { title: "Asistente",
351
+ chat: {
349
352
  system: (d) => "Sos un copiloto de escritura. Nota actual: " + (d.nota || ""),
350
353
  tools: {
351
354
  reescribir_nota: {
@@ -359,6 +362,9 @@ ArtifactKit.createApp({
359
362
  })
360
363
  ```
361
364
 
365
+ Give the chat block a `title` (like every other block) — a chat card that
366
+ opens straight into a bubble is the one block on screen without a heading.
367
+
362
368
  `main` is required; `sidebar` is optional. Both take the same blocks as
363
369
  `panel`. The panes sit side by side and stack on narrow screens. One chat
364
370
  block per app — the conversation persists under `data.chat` and there is
@@ -409,6 +415,19 @@ ArtifactKit.createApp({
409
415
  - **`libs: ["https://..."]`** loads external scripts before the app mounts,
410
416
  deduped by URL, with a "Loading libraries…" state and a named error block
411
417
  if one fails. No hand-written `loadScript` in custom code.
418
+ - **Call the library's method, not its global.** Modern CDN builds expose a
419
+ *namespace object*, and APIs you remember may have moved: `marked` v5+ is
420
+ an object (`marked.parse(text)` works, `marked(text)` throws "marked is
421
+ not a function"). When unsure, prefer the method form — `marked.parse(md)`,
422
+ `Papa.parse(csv)`, `QRCode.toCanvas(...)` or `new QRCode(el, ...)`
423
+ depending on the lib — and pin the version in the URL so the API can't
424
+ drift under you.
425
+ - **Unsure of the file path? Use the bare package URL.** jsDelivr serves the
426
+ package's default build at `https://cdn.jsdelivr.net/npm/<pkg>@<version>`
427
+ with no file path — guessed inner paths (`/marked.umd.js`,
428
+ `/dist/foo.min.js`) 404 more often than versions do. If a full path you
429
+ wrote does 404, the framework retries the bare package URL once before
430
+ showing the error.
412
431
  - **`type: "file"`** fields hand the live `File` to your code at
413
432
  `ctx.files[key]` (in memory only — the 5MB/key storage limit makes real
414
433
  files unpersistable); `data[key]` gets serializable metadata
@@ -533,7 +552,9 @@ keys must be unique across steps.
533
552
 
534
553
  **Formats** (`format:` on output/chart/compute items): `money`, `percent`,
535
554
  `number`, `date`. `percent` expects 0–100 (pass `ratio * 100`, not the raw
536
- ratio).
555
+ ratio). `big: true` renders an item at hero size — reserve it for the ONE
556
+ number that is the screen's main result (a total, a final score), not for
557
+ every stat; more than one hero per screen means no hero at all.
537
558
 
538
559
  **Quick-add pattern** — a lightweight alternative to `records` when you just
539
560
  need "type something, press a button, it lands in a list":
package/dist/index.esm.js CHANGED
@@ -507,7 +507,7 @@ function Bubble({ m }) {
507
507
  dangerouslySetInnerHTML: { __html: renderMarkdown(m.content) }
508
508
  });
509
509
  }
510
- function ChatPanel({ cfg, ctx }) {
510
+ function ChatPanel({ cfg, ctx, title }) {
511
511
  const c = cfg;
512
512
  const [draft, setDraft] = useState("");
513
513
  const [busy, setBusy] = useState(false);
@@ -578,6 +578,9 @@ Continue with this information. If you have everything you need, answer in natur
578
578
  createElement(
579
579
  "div",
580
580
  { className: "caf-block caf-chat-log" },
581
+ // Every other block opens with its title label — a chat card that
582
+ // starts straight at a bubble was the one heading-less block on screen.
583
+ title ? createElement("h2", { className: "caf-block-title" }, title) : null,
581
584
  c.greeting ? createElement("div", { className: "caf-msg caf-msg-assistant", dangerouslySetInnerHTML: { __html: renderMarkdown(c.greeting) } }) : null,
582
585
  log.map((m, i) => createElement(Bubble, { key: i, m })),
583
586
  live !== null ? createElement("div", { className: "caf-msg caf-msg-assistant caf-msg-live", dangerouslySetInnerHTML: { __html: renderMarkdown(live || "\u2026") } }) : null,
@@ -1008,7 +1011,7 @@ var BLOCKS = {
1008
1011
  chart: ChartBlock,
1009
1012
  timer: TimerBlock,
1010
1013
  actions: ActionsBlock,
1011
- chat: ({ spec, ctx }) => createElement(ChatPanel, { cfg: spec.chat, ctx }),
1014
+ chat: ({ spec, ctx }) => createElement(ChatPanel, { cfg: spec.chat, ctx, title: spec.title }),
1012
1015
  custom: CustomBlock
1013
1016
  };
1014
1017
  var BLOCK_NAMES = Object.keys(BLOCKS);
@@ -1717,7 +1720,9 @@ function Panel({ spec, ctx }) {
1717
1720
  (spec.blocks || []).map(
1718
1721
  (block, i) => createElement(
1719
1722
  "section",
1720
- { key: i, className: block.wide ? "caf-slot caf-slot-wide" : "caf-slot" },
1723
+ // A collapsible is always full-width: half-width accordions leave an
1724
+ // orphaned right edge on desktop and change width across breakpoints.
1725
+ { key: i, className: block.wide || block.collapsible ? "caf-slot caf-slot-wide" : "caf-slot" },
1721
1726
  createElement(Block, { block, ctx })
1722
1727
  )
1723
1728
  )
@@ -1735,7 +1740,9 @@ function RecordsWithBlocks({ spec, ctx }) {
1735
1740
  spec.blocks.map(
1736
1741
  (block, i) => createElement(
1737
1742
  "section",
1738
- { key: i, className: block.wide ? "caf-slot caf-slot-wide" : "caf-slot" },
1743
+ // A collapsible is always full-width: half-width accordions leave an
1744
+ // orphaned right edge on desktop and change width across breakpoints.
1745
+ { key: i, className: block.wide || block.collapsible ? "caf-slot caf-slot-wide" : "caf-slot" },
1739
1746
  createElement(Block, { block, ctx })
1740
1747
  )
1741
1748
  )
@@ -1767,7 +1774,14 @@ function loadScript(src) {
1767
1774
  tag.dataset.cafReady = "1";
1768
1775
  resolve();
1769
1776
  };
1770
- tag.onerror = () => reject(new Error(`failed to load ${src}`));
1777
+ tag.onerror = () => {
1778
+ const m = src.match(/^(https:\/\/cdn\.jsdelivr\.net\/npm\/(?:@[^/]+\/)?[^/]+)\/.+/);
1779
+ if (m && m[1] !== src) {
1780
+ loadScript(m[1]).then(resolve, () => reject(new Error(`failed to load ${src} (also tried package default ${m[1]})`)));
1781
+ } else {
1782
+ reject(new Error(`failed to load ${src}`));
1783
+ }
1784
+ };
1771
1785
  document.head.appendChild(tag);
1772
1786
  });
1773
1787
  }
@@ -1817,7 +1831,9 @@ function createApp(spec = {}) {
1817
1831
  const Layout = LAYOUT_COMPONENTS[layout];
1818
1832
  return createElement(
1819
1833
  "div",
1820
- { className: "caf-root" },
1834
+ // The layout class lets shared CSS follow the active container width —
1835
+ // e.g. the header must share the workspace's wider axis, not panel's.
1836
+ { className: `caf-root caf-layout-${layout}` },
1821
1837
  createElement("style", { dangerouslySetInnerHTML: { __html: css } }),
1822
1838
  createElement(
1823
1839
  "header",
@@ -1837,6 +1853,10 @@ function createApp(spec = {}) {
1837
1853
  var BASE_CSS = `
1838
1854
  .caf-root {
1839
1855
  --caf-radius: 10px;
1856
+ --caf-control-radius: 7px; /* one radius for every control: buttons, inputs, rows */
1857
+ --caf-btn-pad-y: 0.45rem;
1858
+ --caf-btn-pad-x: 0.8rem;
1859
+ --caf-num-primary: 2.1rem; /* the one size for a block's dominant number */
1840
1860
  box-sizing: border-box;
1841
1861
  min-height: 100vh;
1842
1862
  padding: 2rem 1.25rem 4rem;
@@ -1847,9 +1867,12 @@ var BASE_CSS = `
1847
1867
  }
1848
1868
  .caf-root *, .caf-root *::before, .caf-root *::after { box-sizing: inherit; }
1849
1869
  .caf-header { max-width: 760px; margin: 0 auto 1.5rem; }
1870
+ /* One left axis per screen: the header adopts the workspace's wider container. */
1871
+ .caf-layout-workspace .caf-header { max-width: 1200px; }
1850
1872
  .caf-header h1 { margin: 0; font-size: 1.5rem; font-weight: 650; letter-spacing: -0.01em; text-wrap: balance; }
1851
1873
  .caf-header p { margin: 0.4rem 0 0; color: var(--caf-muted); font-size: 0.95rem; line-height: 1.5; }
1852
- .caf-panel { max-width: 760px; margin: 0 auto; display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
1874
+ /* Between-card air must beat within-card padding (1.1rem), or groups dissolve. */
1875
+ .caf-panel { max-width: 760px; margin: 0 auto; display: grid; grid-template-columns: 1fr 1fr; gap: 1.75rem 1rem; }
1853
1876
  .caf-slot { grid-column: span 2; min-width: 0; }
1854
1877
  @media (min-width: 720px) { .caf-slot { grid-column: span 1; } .caf-slot-wide { grid-column: span 2; } }
1855
1878
  .caf-block {
@@ -1862,12 +1885,14 @@ var BASE_CSS = `
1862
1885
  gap: 0.8rem;
1863
1886
  height: 100%;
1864
1887
  }
1865
- .caf-block-title { margin: 0; font-size: 0.95rem; font-weight: 650; }
1888
+ .caf-block-title { margin: 0; font-size: 0.78rem; font-weight: 650; text-transform: uppercase; letter-spacing: 0.06em; color: var(--caf-muted); }
1866
1889
  .caf-field { display: flex; flex-direction: column; gap: 0.3rem; }
1867
- .caf-field label { font-size: 0.78rem; font-weight: 600; color: var(--caf-muted); letter-spacing: 0.02em; }
1890
+ /* A full size+weight step below .caf-block-title \u2014 the uppercase alone was
1891
+ not enough to tell "card title" from "field label" at a glance. */
1892
+ .caf-field label { font-size: 0.68rem; font-weight: 500; color: var(--caf-muted); }
1868
1893
  .caf-field input, .caf-field select, .caf-field textarea {
1869
1894
  font: inherit; font-size: 0.95rem; padding: 0.5rem 0.6rem;
1870
- border-radius: 7px; border: 1px solid var(--caf-border);
1895
+ border-radius: var(--caf-control-radius); border: 1px solid var(--caf-border);
1871
1896
  background: var(--caf-sunken); color: var(--caf-text); width: 100%;
1872
1897
  }
1873
1898
  .caf-field input:focus-visible, .caf-field select:focus-visible, .caf-field textarea:focus-visible,
@@ -1876,17 +1901,21 @@ var BASE_CSS = `
1876
1901
  .caf-check { display: flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; }
1877
1902
  .caf-hint { color: var(--caf-muted); font-size: 0.75rem; }
1878
1903
  .caf-error { color: var(--caf-danger); font-size: 0.75rem; font-weight: 600; }
1879
- .caf-output-grid { display: flex; flex-direction: column; gap: 0.7rem; }
1880
- .caf-stat { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
1881
- .caf-stat-label { color: var(--caf-muted); font-size: 0.82rem; }
1882
- .caf-stat-value { font-size: 1rem; font-weight: 600; font-variant-numeric: tabular-nums; }
1883
- .caf-stat-big { flex-direction: column; align-items: flex-start; gap: 0.15rem; padding-bottom: 0.4rem; border-bottom: 1px solid var(--caf-border); }
1884
- .caf-stat-big .caf-stat-value { font-size: 1.9rem; font-weight: 680; letter-spacing: -0.02em; color: var(--caf-accent); }
1904
+ .caf-output-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 0.9rem 1.1rem; }
1905
+ /* Every stat shares one axis: label above value, both flush left. */
1906
+ .caf-stat { display: flex; flex-direction: column; align-items: flex-start; gap: 0.1rem; }
1907
+ .caf-stat-label { color: var(--caf-muted); font-size: 0.78rem; font-weight: 600; letter-spacing: 0.02em; }
1908
+ .caf-stat-value { font-size: 1.05rem; font-weight: 650; font-variant-numeric: tabular-nums; }
1909
+ .caf-stat-big { grid-column: 1 / -1; }
1910
+ /* The divider exists to separate the hero from stats below it \u2014 alone in the
1911
+ grid it would be an orphaned rule, so it only renders when followed. */
1912
+ .caf-stat-big:not(:last-child) { padding-bottom: 0.5rem; border-bottom: 1px solid var(--caf-border); }
1913
+ .caf-stat-big .caf-stat-value { font-size: var(--caf-num-primary); font-weight: 700; letter-spacing: -0.02em; color: var(--caf-accent); line-height: 1.15; }
1885
1914
  .caf-list { padding: 0.4rem; gap: 0; }
1886
1915
  .caf-row {
1887
1916
  font: inherit; text-align: left; cursor: pointer;
1888
1917
  display: flex; flex-direction: column; gap: 0.15rem;
1889
- padding: 0.65rem 0.7rem; border: none; border-radius: 7px;
1918
+ padding: 0.65rem 0.7rem; border: none; border-radius: var(--caf-control-radius);
1890
1919
  background: transparent; color: inherit; width: 100%;
1891
1920
  }
1892
1921
  .caf-row:hover { background: var(--caf-sunken); }
@@ -1894,12 +1923,15 @@ var BASE_CSS = `
1894
1923
  .caf-row-sub { font-size: 0.78rem; color: var(--caf-muted); }
1895
1924
  .caf-panel-single { grid-template-columns: 1fr; }
1896
1925
  .caf-panel-single .caf-slot, .caf-panel-single > * { grid-column: span 1; }
1897
- .caf-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; padding: 0.3rem 0.3rem 0.5rem; }
1926
+ /* Bottom padding matches the inter-row rhythm so the toolbar doesn't sit
1927
+ closer to the first row than rows sit to each other. */
1928
+ .caf-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 0.6rem; padding: 0.3rem 0.3rem 0.75rem; }
1898
1929
  .caf-count { font-size: 0.8rem; color: var(--caf-muted); font-variant-numeric: tabular-nums; }
1899
1930
  .caf-btn {
1900
1931
  font: inherit; font-size: 0.85rem; font-weight: 600; cursor: pointer;
1901
- padding: 0.45rem 0.8rem; border-radius: 7px;
1902
- border: 1px solid var(--caf-border); background: var(--caf-sunken); color: var(--caf-text);
1932
+ padding: var(--caf-btn-pad-y) var(--caf-btn-pad-x); border-radius: var(--caf-control-radius);
1933
+ border: 1px solid var(--caf-border); background: var(--caf-surface); color: var(--caf-text);
1934
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
1903
1935
  }
1904
1936
  .caf-btn:hover { filter: brightness(0.97); }
1905
1937
  .caf-btn:focus-visible { outline: 2px solid var(--caf-accent); outline-offset: 1px; }
@@ -1918,16 +1950,19 @@ var BASE_CSS = `
1918
1950
  .caf-line-meta { display: flex; justify-content: space-between; align-items: baseline; gap: 0.6rem; }
1919
1951
  .caf-line-last { font-weight: 650; font-variant-numeric: tabular-nums; color: var(--caf-accent); }
1920
1952
  .caf-donut { display: flex; align-items: center; gap: 1.1rem; flex-wrap: wrap; }
1921
- .caf-donut-svg { width: 110px; height: 110px; flex: none; transform: rotate(0.001deg); }
1953
+ /* -3px optical correction: a circle beside flush-left text reads indented. */
1954
+ .caf-donut-svg { width: 110px; height: 110px; flex: none; transform: rotate(0.001deg); margin-left: -3px; }
1922
1955
  .caf-donut-seg { fill: none; stroke-width: 6; }
1923
- .caf-donut-legend { display: flex; flex-direction: column; gap: 0.4rem; min-width: 0; flex: 1; }
1956
+ /* Capped width keeps each value NEXT TO its label in wide cards \u2014 the pair
1957
+ must read as a pair regardless of container width. */
1958
+ .caf-donut-legend { display: flex; flex-direction: column; gap: 0.4rem; min-width: 0; flex: 1; max-width: 260px; }
1924
1959
  .caf-legend-row { display: flex; align-items: center; gap: 0.5rem; font-size: 0.84rem; }
1925
1960
  .caf-legend-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }
1926
1961
  .caf-legend-label { color: var(--caf-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1927
1962
  .caf-legend-value { margin-left: auto; font-weight: 600; font-variant-numeric: tabular-nums; }
1928
1963
  .caf-timer { align-items: center; text-align: center; }
1929
1964
  .caf-timer .caf-bar-track { width: 100%; }
1930
- .caf-timer-time { font-size: 2.6rem; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }
1965
+ .caf-timer-time { font-size: var(--caf-num-primary); font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: -0.02em; color: var(--caf-accent); }
1931
1966
  .caf-timer-done { color: var(--caf-accent); }
1932
1967
  .caf-timer-fill { transition: width 0.9s linear; }
1933
1968
  .caf-timer-controls { display: flex; gap: 0.6rem; }
@@ -1944,8 +1979,11 @@ var BASE_CSS = `
1944
1979
  .caf-msg-tools { align-self: flex-start; font-size: 0.74rem; color: var(--caf-muted); padding: 0.15rem 0.55rem; border: 1px solid var(--caf-border); border-radius: 999px; }
1945
1980
  .caf-msg-error { align-self: stretch; max-width: none; display: flex; flex-direction: column; gap: 0.2rem; background: transparent; border: 1px solid var(--caf-danger); }
1946
1981
  .caf-msg-error strong { color: var(--caf-danger); font-size: 0.85rem; }
1947
- .caf-chat-input { display: flex; gap: 0.6rem; margin-top: 0.8rem; }
1948
- .caf-chat-input input { flex: 1; font: inherit; font-size: 0.95rem; padding: 0.55rem 0.7rem; border-radius: 8px; border: 1px solid var(--caf-border); background: var(--caf-surface); color: var(--caf-text); }
1982
+ .caf-chat-input {
1983
+ display: flex; gap: 0.6rem; margin-top: 1rem; padding: 0.6rem;
1984
+ background: var(--caf-surface); border: 1px solid var(--caf-border); border-radius: var(--caf-radius);
1985
+ }
1986
+ .caf-chat-input input { flex: 1; font: inherit; font-size: 0.95rem; padding: 0.55rem 0.7rem; border-radius: var(--caf-control-radius); border: 1px solid var(--caf-border); background: var(--caf-sunken); color: var(--caf-text); }
1949
1987
  .caf-chat-input input:focus-visible { outline: 2px solid var(--caf-accent); outline-offset: 1px; }
1950
1988
  .caf-search {
1951
1989
  font: inherit; font-size: 0.9rem; width: 100%;
@@ -1959,7 +1997,7 @@ var BASE_CSS = `
1959
1997
  .caf-row-wrap { display: flex; align-items: center; gap: 0.4rem; }
1960
1998
  .caf-row-wrap .caf-row { flex: 1; min-width: 0; }
1961
1999
  .caf-row-actions { display: flex; gap: 0.35rem; flex: none; padding-right: 0.3rem; }
1962
- .caf-btn-sm { font-size: 0.78rem; padding: 0.3rem 0.6rem; border-radius: 6px; }
2000
+ .caf-btn-sm { font-size: 0.78rem; padding: calc(var(--caf-btn-pad-y) * 0.67) calc(var(--caf-btn-pad-x) * 0.75); }
1963
2001
  .caf-items { display: flex; flex-direction: column; gap: 0.5rem; border-top: 1px solid var(--caf-border); padding-top: 0.7rem; }
1964
2002
  .caf-item-row { display: flex; align-items: flex-end; gap: 0.5rem; }
1965
2003
  .caf-item-row .caf-field { flex: 1; min-width: 0; }
@@ -1991,17 +2029,24 @@ var BASE_CSS = `
1991
2029
  }
1992
2030
  .caf-tab:hover { color: var(--caf-text); }
1993
2031
  .caf-tab:focus-visible { outline: 2px solid var(--caf-accent); outline-offset: -2px; }
1994
- .caf-tab-active { color: var(--caf-accent); border-bottom-color: var(--caf-accent); }
2032
+ /* :hover on the active tab must not win the specificity race and mute it. */
2033
+ .caf-tab-active, .caf-tab-active:hover { color: var(--caf-accent); border-bottom-color: var(--caf-accent); }
1995
2034
  .caf-tabs-body { display: flex; flex-direction: column; gap: 0.8rem; }
1996
2035
  .caf-collapse { display: flex; flex-direction: column; gap: 0.5rem; min-width: 0; }
1997
2036
  .caf-collapse-head {
1998
- font: inherit; font-size: 0.95rem; font-weight: 650; cursor: pointer;
2037
+ /* Same treatment as .caf-block-title: a collapsible header IS a card title. */
2038
+ font: inherit; font-size: 0.78rem; font-weight: 650; text-transform: uppercase;
2039
+ letter-spacing: 0.06em; color: var(--caf-muted); cursor: pointer;
1999
2040
  display: flex; align-items: center; justify-content: space-between; gap: 0.6rem;
2000
- padding: 0.7rem 1rem; border: 1px solid var(--caf-border); border-radius: var(--caf-radius);
2001
- background: var(--caf-surface); color: var(--caf-text); width: 100%; text-align: left;
2041
+ padding: 0.7rem 1.1rem; border: 1px solid var(--caf-border); border-radius: var(--caf-radius);
2042
+ background: var(--caf-surface); width: 100%; text-align: left;
2002
2043
  }
2044
+ .caf-collapse-head { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); }
2045
+ .caf-collapse-head:hover { filter: brightness(0.97); }
2003
2046
  .caf-collapse-head:focus-visible { outline: 2px solid var(--caf-accent); outline-offset: 1px; }
2004
- .caf-chevron { transition: transform 0.15s ease; color: var(--caf-muted); }
2047
+ /* Accent chevron: the one interaction cue that distinguishes a collapsible
2048
+ header from a static card title without breaking their shared typography. */
2049
+ .caf-chevron { transition: transform 0.15s ease; color: var(--caf-accent); }
2005
2050
  .caf-chevron-closed { transform: rotate(-90deg); }
2006
2051
  .caf-block-fill { border: none; background: transparent; padding: 0; overflow: auto; min-height: 320px; }
2007
2052
  .caf-empty, .caf-loading { color: var(--caf-muted); font-size: 0.9rem; text-align: center; padding: 1.6rem 1rem; }