claude-artifact-framework 0.15.0 → 0.16.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
@@ -801,10 +801,30 @@ custom: (ctx) => React.createElement("div", {
801
801
  `d.preview` can kill persistence for the entire app. Big things (data
802
802
  URLs, file contents, parse results) live in `React.useState` or come from
803
803
  `ctx.files`; `data` holds what is small and serializable.
804
+ - **Column grids ask `ctx.viewport`.** Half of artifact viewers are on a
805
+ phone. `ctx.viewport` is `"mobile"` (< 720px) or `"desktop"`, and the
806
+ block re-renders when it changes. A layout that lays columns side by side
807
+ (kanban boards, week calendars, dashboards of mini-cards) must stack or
808
+ reduce them on `"mobile"` — 3 columns squeezed into 390px are ~90px each
809
+ and unreadable:
810
+
811
+ ```js
812
+ custom: (ctx) => {
813
+ const cols = ctx.viewport === "mobile" ? "1fr" : "repeat(3, 1fr)";
814
+ return React.createElement("div",
815
+ { style: { display: "grid", gridTemplateColumns: cols, gap: "0.6rem" } },
816
+ /* ...columnas... */);
817
+ }
818
+ ```
819
+
820
+ As a safety net the framework never clips: custom content wider than the
821
+ screen scrolls horizontally instead of disappearing. But scrolling a
822
+ squeezed board is the fallback, not the design — branch on `ctx.viewport`.
804
823
  - Custom re-renders whenever `data` or `view` change; it receives the full
805
- `ctx` (`data`, `update`, `view`, `files`, `viewerId`, `colors`, `go`,
806
- `back`) and lives inside the block error boundary — a bug in your custom
807
- code shows a named error in its slot, never a blank app.
824
+ `ctx` (`data`, `update`, `view`, `files`, `viewerId`, `colors`,
825
+ `viewport`, `go`, `back`) and lives inside the block error boundary — a
826
+ bug in your custom code shows a named error in its slot, never a blank
827
+ app.
808
828
 
809
829
  ## API reference
810
830
 
@@ -963,3 +983,6 @@ The rules that decide whether a generated app works, all in one place
963
983
  13. **Brand is zero-asset-first** — monogram/gradient/emoji defaults before
964
984
  URLs; inline `<svg>` over remote images; one icon style per app
965
985
  (emoji-first); `avatar: true` instead of hand-built initials circles.
986
+ 14. **Column grids in `custom` branch on `ctx.viewport`** — stack kanban
987
+ columns / week grids when it says `"mobile"`; half the viewers are on
988
+ a phone.
package/dist/index.esm.js CHANGED
@@ -1023,7 +1023,7 @@ function ActionsBlock({ spec, ctx }) {
1023
1023
  }
1024
1024
  function CustomBlock({ spec, ctx }) {
1025
1025
  const rendered = spec.custom(ctx);
1026
- const cls = spec.fill ? "caf-block caf-block-fill" : "caf-block";
1026
+ const cls = spec.fill ? "caf-block caf-block-fill caf-custom" : "caf-block caf-custom";
1027
1027
  if (typeof rendered === "string") {
1028
1028
  return createElement("div", { className: cls, dangerouslySetInnerHTML: { __html: rendered } });
1029
1029
  }
@@ -2321,6 +2321,15 @@ function createApp(spec = {}) {
2321
2321
  );
2322
2322
  }
2323
2323
  const [, force] = useState(0);
2324
+ const [narrow, setNarrow] = useState(
2325
+ () => typeof window !== "undefined" && window.matchMedia("(max-width: 719px)").matches
2326
+ );
2327
+ useEffect(() => {
2328
+ const mq = window.matchMedia("(max-width: 719px)");
2329
+ const onChange = (e) => setNarrow(e.matches);
2330
+ mq.addEventListener("change", onChange);
2331
+ return () => mq.removeEventListener("change", onChange);
2332
+ }, []);
2324
2333
  const [libsState, setLibsState] = useState(spec.libs && spec.libs.length ? "loading" : "ready");
2325
2334
  const [libsError, setLibsError] = useState(null);
2326
2335
  useEffect(() => {
@@ -2366,6 +2375,7 @@ function createApp(spec = {}) {
2366
2375
  setTab: (key, i) => state.setView({ tabs: { ...state.getView().tabs, [key]: i } }),
2367
2376
  toggleCollapsed: (key, val) => state.setView({ collapsed: { ...state.getView().collapsed, [key]: val } }),
2368
2377
  colors: (n) => seriesColors(spec.theme && spec.theme.seed, n),
2378
+ viewport: narrow ? "mobile" : "desktop",
2369
2379
  machine: spec.machine || null,
2370
2380
  send: spec.machine ? (eventName, payload) => runMachineEvent(spec.machine, state.update, eventName, payload) : () => {
2371
2381
  throw new Error("claude-artifact-framework: ctx.send() needs a `machine` declared at the top level.");
@@ -2649,6 +2659,14 @@ var BASE_CSS = `
2649
2659
  .caf-block-error { border-color: var(--caf-danger); gap: 0.4rem; }
2650
2660
  .caf-block-error strong { font-size: 0.9rem; color: var(--caf-danger); }
2651
2661
  .caf-block-error code { font-size: 0.78rem; color: var(--caf-muted); word-break: break-word; }
2662
+ /* Never-clip guarantee: content inside a custom block that is wider than the
2663
+ phone (column grids, week views) must scroll, not disappear off-screen. */
2664
+ .caf-custom { overflow-x: auto; }
2665
+ /* Touch devices get tappable framework controls regardless of the spec. */
2666
+ @media (pointer: coarse) {
2667
+ .caf-btn, .caf-tab, .caf-field input, .caf-field select { min-height: 44px; }
2668
+ .caf-btn-sm, .caf-doc-close, .caf-item-remove { min-height: 40px; }
2669
+ }
2652
2670
  `;
2653
2671
  export {
2654
2672
  contrast,