claude-artifact-framework 0.20.0 → 0.20.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-artifact-framework",
3
- "version": "0.20.0",
3
+ "version": "0.20.1",
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
@@ -1477,7 +1477,10 @@ const BASE_CSS = `
1477
1477
  .caf-doc-close:hover { color: var(--caf-danger); }
1478
1478
  .caf-doc-add { font-size: 1rem; font-weight: 650; }
1479
1479
  .caf-empty .caf-btn { margin-top: 0.6rem; }
1480
- .caf-records-block { display: flex; flex-direction: column; gap: 1rem; min-width: 0; }
1480
+ /* Tight gap: the block title is a SECTION head for the cards below — it
1481
+ must hug its group, not float equidistant (audited on the retro board). */
1482
+ .caf-records-block { display: flex; flex-direction: column; gap: 0.6rem; min-width: 0; }
1483
+ .caf-stat-neg, .caf-stat-big .caf-stat-value.caf-stat-neg { color: var(--caf-danger); }
1481
1484
  .caf-doc-typemenu { display: flex; gap: 0.5rem; flex-wrap: wrap; }
1482
1485
  .caf-header-brand { display: flex; align-items: center; gap: 0.8rem; }
1483
1486
  /* Where the page has a gutter, the logo lives in it: the title keeps the
@@ -1489,7 +1492,7 @@ const BASE_CSS = `
1489
1492
  .caf-header-brand .caf-logo { position: absolute; right: 100%; margin-right: 0.85rem; top: 0.1rem; }
1490
1493
  }
1491
1494
  .caf-header-text { min-width: 0; }
1492
- .caf-logo { flex: none; width: 40px; height: 40px; border-radius: 9px; object-fit: contain; }
1495
+ .caf-logo { flex: none; width: 40px; height: 40px; border-radius: 9px; object-fit: contain; overflow: hidden; }
1493
1496
  .caf-logo-emoji { display: flex; align-items: center; justify-content: center; font-size: 1.7rem; background: var(--caf-surface); border: 1px solid var(--caf-border); }
1494
1497
  .caf-logo-mono {
1495
1498
  display: flex; align-items: center; justify-content: center;
package/src/blocks.js CHANGED
@@ -63,7 +63,13 @@ export function validateField(field, value) {
63
63
  }
64
64
 
65
65
  export function Field({ field, value, onChange, data }) {
66
- const error = validateField(field, value);
66
+ // A pristine empty field must not shout "Required" before the user ever
67
+ // touched it (audited on first load of a quick-add form). Range/format
68
+ // errors imply a non-empty value, so only the empty-required case waits.
69
+ const [touched, setTouched] = useState(false);
70
+ const rawError = validateField(field, value);
71
+ const pristineEmpty = !touched && (value === "" || value === null || value === undefined);
72
+ const error = pristineEmpty ? null : rawError;
67
73
  const id = `caf-f-${field.key}`;
68
74
  const numeric = field.type === "number" || field.type === "money" || field.type === "percent";
69
75
 
@@ -108,6 +114,7 @@ export function Field({ field, value, onChange, data }) {
108
114
  id,
109
115
  value: value === null || value === undefined ? "" : value,
110
116
  "aria-invalid": error ? "true" : undefined,
117
+ onBlur: () => setTouched(true),
111
118
  onChange: (e) => onChange(numeric ? toNumber(e.target.value) : e.target.value),
112
119
  };
113
120
 
@@ -229,15 +236,19 @@ export function StatGrid({ items, title }) {
229
236
  h(
230
237
  "div",
231
238
  { className: "caf-output-grid" },
232
- items.map((item, i) =>
233
- h(
239
+ items.map((item, i) => {
240
+ // A negative money/number stat reads as a loss everywhere else on
241
+ // screen (list rows already color by sign) — the hero must agree,
242
+ // not paint a deficit in cheerful brand accent (audited).
243
+ const neg = (item.format === "money" || item.format === "number") && Number(item.value) < 0;
244
+ return h(
234
245
  "div",
235
246
  { key: item.label || i, className: item.big ? "caf-stat caf-stat-big" : "caf-stat" },
236
247
  h("span", { className: "caf-stat-label" }, item.label),
237
- h("span", { className: "caf-stat-value" }, formatValue(item.value, item.format)),
248
+ h("span", { className: neg ? "caf-stat-value caf-stat-neg" : "caf-stat-value" }, formatValue(item.value, item.format)),
238
249
  item.hint ? h("small", { className: "caf-hint" }, item.hint) : null
239
- )
240
- )
250
+ );
251
+ })
241
252
  )
242
253
  );
243
254
  }