claude-artifact-framework 0.8.0 → 0.9.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.8.0",
3
+ "version": "0.9.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
@@ -419,7 +419,9 @@ function Panel({ spec, ctx }) {
419
419
  (spec.blocks || []).map((block, i) =>
420
420
  h(
421
421
  "section",
422
- { key: i, className: block.wide ? "caf-slot caf-slot-wide" : "caf-slot" },
422
+ // A collapsible is always full-width: half-width accordions leave an
423
+ // orphaned right edge on desktop and change width across breakpoints.
424
+ { key: i, className: block.wide || block.collapsible ? "caf-slot caf-slot-wide" : "caf-slot" },
423
425
  h(Block, { block, ctx })
424
426
  )
425
427
  )
@@ -444,7 +446,9 @@ function RecordsWithBlocks({ spec, ctx }) {
444
446
  spec.blocks.map((block, i) =>
445
447
  h(
446
448
  "section",
447
- { key: i, className: block.wide ? "caf-slot caf-slot-wide" : "caf-slot" },
449
+ // A collapsible is always full-width: half-width accordions leave an
450
+ // orphaned right edge on desktop and change width across breakpoints.
451
+ { key: i, className: block.wide || block.collapsible ? "caf-slot caf-slot-wide" : "caf-slot" },
448
452
  h(Block, { block, ctx })
449
453
  )
450
454
  )
@@ -482,7 +486,19 @@ function loadScript(src) {
482
486
  const tag = document.createElement("script");
483
487
  tag.src = src;
484
488
  tag.onload = () => { tag.dataset.cafReady = "1"; resolve(); };
485
- tag.onerror = () => reject(new Error(`failed to load ${src}`));
489
+ tag.onerror = () => {
490
+ // Models guess file paths inside packages and get them wrong more often
491
+ // than they get versions wrong (measured: marked@13's UMD lives at
492
+ // lib/marked.umd.js, not the root the model wrote). jsDelivr serves a
493
+ // package's default build at the bare npm/<pkg>@<version> URL, so one
494
+ // retry there absorbs the whole failure class.
495
+ const m = src.match(/^(https:\/\/cdn\.jsdelivr\.net\/npm\/(?:@[^/]+\/)?[^/]+)\/.+/);
496
+ if (m && m[1] !== src) {
497
+ loadScript(m[1]).then(resolve, () => reject(new Error(`failed to load ${src} (also tried package default ${m[1]})`)));
498
+ } else {
499
+ reject(new Error(`failed to load ${src}`));
500
+ }
501
+ };
486
502
  document.head.appendChild(tag);
487
503
  });
488
504
  }
@@ -540,7 +556,9 @@ export function createApp(spec = {}) {
540
556
 
541
557
  return h(
542
558
  "div",
543
- { className: "caf-root" },
559
+ // The layout class lets shared CSS follow the active container width —
560
+ // e.g. the header must share the workspace's wider axis, not panel's.
561
+ { className: `caf-root caf-layout-${layout}` },
544
562
  h("style", { dangerouslySetInnerHTML: { __html: css } }),
545
563
  h(
546
564
  "header",
@@ -565,6 +583,10 @@ export function createApp(spec = {}) {
565
583
  const BASE_CSS = `
566
584
  .caf-root {
567
585
  --caf-radius: 10px;
586
+ --caf-control-radius: 7px; /* one radius for every control: buttons, inputs, rows */
587
+ --caf-btn-pad-y: 0.45rem;
588
+ --caf-btn-pad-x: 0.8rem;
589
+ --caf-num-primary: 2.1rem; /* the one size for a block's dominant number */
568
590
  box-sizing: border-box;
569
591
  min-height: 100vh;
570
592
  padding: 2rem 1.25rem 4rem;
@@ -575,9 +597,12 @@ const BASE_CSS = `
575
597
  }
576
598
  .caf-root *, .caf-root *::before, .caf-root *::after { box-sizing: inherit; }
577
599
  .caf-header { max-width: 760px; margin: 0 auto 1.5rem; }
600
+ /* One left axis per screen: the header adopts the workspace's wider container. */
601
+ .caf-layout-workspace .caf-header { max-width: 1200px; }
578
602
  .caf-header h1 { margin: 0; font-size: 1.5rem; font-weight: 650; letter-spacing: -0.01em; text-wrap: balance; }
579
603
  .caf-header p { margin: 0.4rem 0 0; color: var(--caf-muted); font-size: 0.95rem; line-height: 1.5; }
580
- .caf-panel { max-width: 760px; margin: 0 auto; display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
604
+ /* Between-card air must beat within-card padding (1.1rem), or groups dissolve. */
605
+ .caf-panel { max-width: 760px; margin: 0 auto; display: grid; grid-template-columns: 1fr 1fr; gap: 1.75rem 1rem; }
581
606
  .caf-slot { grid-column: span 2; min-width: 0; }
582
607
  @media (min-width: 720px) { .caf-slot { grid-column: span 1; } .caf-slot-wide { grid-column: span 2; } }
583
608
  .caf-block {
@@ -590,12 +615,12 @@ const BASE_CSS = `
590
615
  gap: 0.8rem;
591
616
  height: 100%;
592
617
  }
593
- .caf-block-title { margin: 0; font-size: 0.95rem; font-weight: 650; }
618
+ .caf-block-title { margin: 0; font-size: 0.78rem; font-weight: 650; text-transform: uppercase; letter-spacing: 0.06em; color: var(--caf-muted); }
594
619
  .caf-field { display: flex; flex-direction: column; gap: 0.3rem; }
595
- .caf-field label { font-size: 0.78rem; font-weight: 600; color: var(--caf-muted); letter-spacing: 0.02em; }
620
+ .caf-field label { font-size: 0.72rem; font-weight: 500; color: var(--caf-muted); letter-spacing: 0.02em; }
596
621
  .caf-field input, .caf-field select, .caf-field textarea {
597
622
  font: inherit; font-size: 0.95rem; padding: 0.5rem 0.6rem;
598
- border-radius: 7px; border: 1px solid var(--caf-border);
623
+ border-radius: var(--caf-control-radius); border: 1px solid var(--caf-border);
599
624
  background: var(--caf-sunken); color: var(--caf-text); width: 100%;
600
625
  }
601
626
  .caf-field input:focus-visible, .caf-field select:focus-visible, .caf-field textarea:focus-visible,
@@ -604,17 +629,21 @@ const BASE_CSS = `
604
629
  .caf-check { display: flex; align-items: center; gap: 0.5rem; font-size: 0.9rem; }
605
630
  .caf-hint { color: var(--caf-muted); font-size: 0.75rem; }
606
631
  .caf-error { color: var(--caf-danger); font-size: 0.75rem; font-weight: 600; }
607
- .caf-output-grid { display: flex; flex-direction: column; gap: 0.7rem; }
608
- .caf-stat { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
609
- .caf-stat-label { color: var(--caf-muted); font-size: 0.82rem; }
610
- .caf-stat-value { font-size: 1rem; font-weight: 600; font-variant-numeric: tabular-nums; }
611
- .caf-stat-big { flex-direction: column; align-items: flex-start; gap: 0.15rem; padding-bottom: 0.4rem; border-bottom: 1px solid var(--caf-border); }
612
- .caf-stat-big .caf-stat-value { font-size: 1.9rem; font-weight: 680; letter-spacing: -0.02em; color: var(--caf-accent); }
632
+ .caf-output-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 0.9rem 1.1rem; }
633
+ /* Every stat shares one axis: label above value, both flush left. */
634
+ .caf-stat { display: flex; flex-direction: column; align-items: flex-start; gap: 0.1rem; }
635
+ .caf-stat-label { color: var(--caf-muted); font-size: 0.78rem; font-weight: 600; letter-spacing: 0.02em; }
636
+ .caf-stat-value { font-size: 1.05rem; font-weight: 650; font-variant-numeric: tabular-nums; }
637
+ .caf-stat-big { grid-column: 1 / -1; }
638
+ /* The divider exists to separate the hero from stats below it — alone in the
639
+ grid it would be an orphaned rule, so it only renders when followed. */
640
+ .caf-stat-big:not(:last-child) { padding-bottom: 0.5rem; border-bottom: 1px solid var(--caf-border); }
641
+ .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; }
613
642
  .caf-list { padding: 0.4rem; gap: 0; }
614
643
  .caf-row {
615
644
  font: inherit; text-align: left; cursor: pointer;
616
645
  display: flex; flex-direction: column; gap: 0.15rem;
617
- padding: 0.65rem 0.7rem; border: none; border-radius: 7px;
646
+ padding: 0.65rem 0.7rem; border: none; border-radius: var(--caf-control-radius);
618
647
  background: transparent; color: inherit; width: 100%;
619
648
  }
620
649
  .caf-row:hover { background: var(--caf-sunken); }
@@ -626,8 +655,9 @@ const BASE_CSS = `
626
655
  .caf-count { font-size: 0.8rem; color: var(--caf-muted); font-variant-numeric: tabular-nums; }
627
656
  .caf-btn {
628
657
  font: inherit; font-size: 0.85rem; font-weight: 600; cursor: pointer;
629
- padding: 0.45rem 0.8rem; border-radius: 7px;
630
- border: 1px solid var(--caf-border); background: var(--caf-sunken); color: var(--caf-text);
658
+ padding: var(--caf-btn-pad-y) var(--caf-btn-pad-x); border-radius: var(--caf-control-radius);
659
+ border: 1px solid var(--caf-border); background: var(--caf-surface); color: var(--caf-text);
660
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
631
661
  }
632
662
  .caf-btn:hover { filter: brightness(0.97); }
633
663
  .caf-btn:focus-visible { outline: 2px solid var(--caf-accent); outline-offset: 1px; }
@@ -648,14 +678,16 @@ const BASE_CSS = `
648
678
  .caf-donut { display: flex; align-items: center; gap: 1.1rem; flex-wrap: wrap; }
649
679
  .caf-donut-svg { width: 110px; height: 110px; flex: none; transform: rotate(0.001deg); }
650
680
  .caf-donut-seg { fill: none; stroke-width: 6; }
651
- .caf-donut-legend { display: flex; flex-direction: column; gap: 0.4rem; min-width: 0; flex: 1; }
681
+ /* Capped width keeps each value NEXT TO its label in wide cards — the pair
682
+ must read as a pair regardless of container width. */
683
+ .caf-donut-legend { display: flex; flex-direction: column; gap: 0.4rem; min-width: 0; flex: 1; max-width: 260px; }
652
684
  .caf-legend-row { display: flex; align-items: center; gap: 0.5rem; font-size: 0.84rem; }
653
685
  .caf-legend-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }
654
686
  .caf-legend-label { color: var(--caf-muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
655
687
  .caf-legend-value { margin-left: auto; font-weight: 600; font-variant-numeric: tabular-nums; }
656
688
  .caf-timer { align-items: center; text-align: center; }
657
689
  .caf-timer .caf-bar-track { width: 100%; }
658
- .caf-timer-time { font-size: 2.6rem; font-weight: 700; font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }
690
+ .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); }
659
691
  .caf-timer-done { color: var(--caf-accent); }
660
692
  .caf-timer-fill { transition: width 0.9s linear; }
661
693
  .caf-timer-controls { display: flex; gap: 0.6rem; }
@@ -672,8 +704,11 @@ const BASE_CSS = `
672
704
  .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; }
673
705
  .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); }
674
706
  .caf-msg-error strong { color: var(--caf-danger); font-size: 0.85rem; }
675
- .caf-chat-input { display: flex; gap: 0.6rem; margin-top: 0.8rem; }
676
- .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); }
707
+ .caf-chat-input {
708
+ display: flex; gap: 0.6rem; margin-top: 1rem; padding: 0.6rem;
709
+ background: var(--caf-surface); border: 1px solid var(--caf-border); border-radius: var(--caf-radius);
710
+ }
711
+ .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); }
677
712
  .caf-chat-input input:focus-visible { outline: 2px solid var(--caf-accent); outline-offset: 1px; }
678
713
  .caf-search {
679
714
  font: inherit; font-size: 0.9rem; width: 100%;
@@ -687,7 +722,7 @@ const BASE_CSS = `
687
722
  .caf-row-wrap { display: flex; align-items: center; gap: 0.4rem; }
688
723
  .caf-row-wrap .caf-row { flex: 1; min-width: 0; }
689
724
  .caf-row-actions { display: flex; gap: 0.35rem; flex: none; padding-right: 0.3rem; }
690
- .caf-btn-sm { font-size: 0.78rem; padding: 0.3rem 0.6rem; border-radius: 6px; }
725
+ .caf-btn-sm { font-size: 0.78rem; padding: calc(var(--caf-btn-pad-y) * 0.67) calc(var(--caf-btn-pad-x) * 0.75); }
691
726
  .caf-items { display: flex; flex-direction: column; gap: 0.5rem; border-top: 1px solid var(--caf-border); padding-top: 0.7rem; }
692
727
  .caf-item-row { display: flex; align-items: flex-end; gap: 0.5rem; }
693
728
  .caf-item-row .caf-field { flex: 1; min-width: 0; }
@@ -719,15 +754,20 @@ const BASE_CSS = `
719
754
  }
720
755
  .caf-tab:hover { color: var(--caf-text); }
721
756
  .caf-tab:focus-visible { outline: 2px solid var(--caf-accent); outline-offset: -2px; }
722
- .caf-tab-active { color: var(--caf-accent); border-bottom-color: var(--caf-accent); }
757
+ /* :hover on the active tab must not win the specificity race and mute it. */
758
+ .caf-tab-active, .caf-tab-active:hover { color: var(--caf-accent); border-bottom-color: var(--caf-accent); }
723
759
  .caf-tabs-body { display: flex; flex-direction: column; gap: 0.8rem; }
724
760
  .caf-collapse { display: flex; flex-direction: column; gap: 0.5rem; min-width: 0; }
725
761
  .caf-collapse-head {
726
- font: inherit; font-size: 0.95rem; font-weight: 650; cursor: pointer;
762
+ /* Same treatment as .caf-block-title: a collapsible header IS a card title. */
763
+ font: inherit; font-size: 0.78rem; font-weight: 650; text-transform: uppercase;
764
+ letter-spacing: 0.06em; color: var(--caf-muted); cursor: pointer;
727
765
  display: flex; align-items: center; justify-content: space-between; gap: 0.6rem;
728
- padding: 0.7rem 1rem; border: 1px solid var(--caf-border); border-radius: var(--caf-radius);
729
- background: var(--caf-surface); color: var(--caf-text); width: 100%; text-align: left;
766
+ padding: 0.7rem 1.1rem; border: 1px solid var(--caf-border); border-radius: var(--caf-radius);
767
+ background: var(--caf-surface); width: 100%; text-align: left;
730
768
  }
769
+ .caf-collapse-head { box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08); }
770
+ .caf-collapse-head:hover { filter: brightness(0.97); }
731
771
  .caf-collapse-head:focus-visible { outline: 2px solid var(--caf-accent); outline-offset: 1px; }
732
772
  .caf-chevron { transition: transform 0.15s ease; color: var(--caf-muted); }
733
773
  .caf-chevron-closed { transform: rotate(-90deg); }