claude-artifact-framework 0.2.0 → 0.3.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 +185 -0
- package/dist/index.esm.js +776 -1
- package/dist/index.esm.js.map +4 -4
- package/dist/index.global.js +89 -1
- package/dist/index.global.js.map +4 -4
- package/package.json +1 -1
- package/src/app.js +271 -0
- package/src/appState.js +127 -0
- package/src/blocks.js +215 -0
- package/src/index.js +3 -0
- package/src/react-runtime.js +80 -0
- package/src/records.js +158 -0
- package/src/theme.js +148 -0
package/README.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# claude-artifact-framework
|
|
2
|
+
|
|
3
|
+
Utilities for building apps inside Claude Artifacts: platform storage, reactive
|
|
4
|
+
state, and other primitives verified against the real runtime. Zero
|
|
5
|
+
dependencies, no build step — drop it into an artifact straight from a CDN.
|
|
6
|
+
|
|
7
|
+
## Usage via CDN
|
|
8
|
+
|
|
9
|
+
Everything is served through [jsDelivr](https://www.jsdelivr.com/), which
|
|
10
|
+
mirrors npm packages automatically.
|
|
11
|
+
|
|
12
|
+
### As a global script (`<script>` tag)
|
|
13
|
+
|
|
14
|
+
```html
|
|
15
|
+
<script src="https://cdn.jsdelivr.net/npm/claude-artifact-framework@0.3.0/dist/index.global.js"></script>
|
|
16
|
+
<script>
|
|
17
|
+
const { storage, createStore, createPersistedStore, createSharedStore } = ArtifactKit;
|
|
18
|
+
</script>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### As an ES module
|
|
22
|
+
|
|
23
|
+
```html
|
|
24
|
+
<script type="module">
|
|
25
|
+
import {
|
|
26
|
+
storage,
|
|
27
|
+
createStore,
|
|
28
|
+
createPersistedStore,
|
|
29
|
+
createSharedStore,
|
|
30
|
+
} from "https://cdn.jsdelivr.net/npm/claude-artifact-framework@0.3.0/dist/index.esm.js";
|
|
31
|
+
</script>
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Pin a version (`@0.3.0`) for reproducible artifacts, or drop the version
|
|
35
|
+
(`claude-artifact-framework/dist/...`) to always get the latest release.
|
|
36
|
+
|
|
37
|
+
### Inside a React artifact
|
|
38
|
+
|
|
39
|
+
A React artifact loads external code the same way any artifact does — by
|
|
40
|
+
injecting a classic `<script>` tag, not by importing a module URL. Hand the
|
|
41
|
+
framework the React the artifact already imported, so there is only ever one
|
|
42
|
+
React on the page:
|
|
43
|
+
|
|
44
|
+
```jsx
|
|
45
|
+
import React, { useState, useEffect } from "react";
|
|
46
|
+
|
|
47
|
+
const KIT = "https://cdn.jsdelivr.net/npm/claude-artifact-framework/dist/index.global.js";
|
|
48
|
+
|
|
49
|
+
function load(src) {
|
|
50
|
+
return new Promise((resolve, reject) => {
|
|
51
|
+
if (document.querySelector(`script[src="${src}"]`)) return resolve();
|
|
52
|
+
const tag = document.createElement("script");
|
|
53
|
+
tag.src = src;
|
|
54
|
+
tag.onload = resolve;
|
|
55
|
+
tag.onerror = reject;
|
|
56
|
+
document.head.appendChild(tag);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export default function Artifact() {
|
|
61
|
+
const [App, setApp] = useState(null);
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
load(KIT).then(() => {
|
|
65
|
+
ArtifactKit.useReact(React);
|
|
66
|
+
setApp(() => ArtifactKit.createApp({
|
|
67
|
+
title: "Mortgage calculator",
|
|
68
|
+
theme: { seed: "#0c8b8d" },
|
|
69
|
+
blocks: [
|
|
70
|
+
{ fields: [
|
|
71
|
+
{ key: "amount", label: "Amount", type: "money", value: 300000 },
|
|
72
|
+
{ key: "rate", label: "Rate", type: "percent", value: 5.5 },
|
|
73
|
+
{ key: "years", label: "Term", type: "number", value: 30, min: 1, max: 40 },
|
|
74
|
+
]},
|
|
75
|
+
{ output: (d) => {
|
|
76
|
+
const i = d.rate / 100 / 12, n = d.years * 12;
|
|
77
|
+
const payment = d.amount * i / (1 - Math.pow(1 + i, -n));
|
|
78
|
+
return [{ label: "Monthly payment", value: payment, format: "money", big: true }];
|
|
79
|
+
}},
|
|
80
|
+
],
|
|
81
|
+
}));
|
|
82
|
+
});
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
return App ? <App /> : null;
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`useReact` is the one thing the framework asks for, and only because a runtime
|
|
90
|
+
may keep React in a private module registry the bundle can't reach. If React is
|
|
91
|
+
already on `globalThis`, it is picked up automatically and the call is a no-op.
|
|
92
|
+
|
|
93
|
+
### The `records` layout — a full CRUD app from a declaration
|
|
94
|
+
|
|
95
|
+
For "list of things" apps (expenses, habits, notes, todos), declare what a
|
|
96
|
+
record is and the whole lifecycle — list, add, edit, delete, empty state,
|
|
97
|
+
selection, persistence — is framework-owned:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
ArtifactKit.createApp({
|
|
101
|
+
title: "Gastos",
|
|
102
|
+
theme: { seed: "#c2410c" },
|
|
103
|
+
layout: "records",
|
|
104
|
+
records: {
|
|
105
|
+
label: "expense",
|
|
106
|
+
fields: [
|
|
107
|
+
{ key: "concepto", label: "Concepto", type: "text", required: true },
|
|
108
|
+
{ key: "monto", label: "Monto", type: "money", value: 0, min: 0 },
|
|
109
|
+
{ key: "categoria", label: "Categoría", type: "select", options: ["comida", "transporte", "otros"] },
|
|
110
|
+
],
|
|
111
|
+
subtitle: (r) => r.categoria || "sin categoría",
|
|
112
|
+
summary: (all) => [
|
|
113
|
+
{ label: "Total", value: all.reduce((s, r) => s + (Number(r.monto) || 0), 0), format: "money", big: true },
|
|
114
|
+
{ label: "Gastos", value: all.length, format: "number" },
|
|
115
|
+
],
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
"Add" creates the record from field defaults and opens it (no draft object to
|
|
121
|
+
lose); detail fields edit the record in place through the same debounced
|
|
122
|
+
persistence path as everything else. `id` is assigned by the framework and
|
|
123
|
+
reserved. Optional: `title(r)` / `subtitle(r)` for rows, `sort(a, b)`,
|
|
124
|
+
`summary(records)` for stats above the list, `empty` for the empty-state text.
|
|
125
|
+
|
|
126
|
+
## `storage`
|
|
127
|
+
|
|
128
|
+
Thin wrapper over `window.storage`, the persistence API Claude injects into a
|
|
129
|
+
**published** Chat Artifact. Values are JSON-serialized for you. Two scopes:
|
|
130
|
+
personal (`storage.*`, one copy per viewer) and shared (`storage.shared.*`,
|
|
131
|
+
one copy for every viewer of the artifact).
|
|
132
|
+
|
|
133
|
+
```js
|
|
134
|
+
await storage.set("theme", "dark");
|
|
135
|
+
await storage.get("theme", "light"); // "dark" — second arg is the fallback
|
|
136
|
+
await storage.list(); // ["theme", ...]
|
|
137
|
+
await storage.delete("theme");
|
|
138
|
+
|
|
139
|
+
await storage.shared.set("board", { count: 0 });
|
|
140
|
+
await storage.shared.get("board", null);
|
|
141
|
+
|
|
142
|
+
storage.available(); // false in draft mode / outside a Chat Artifact
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## `createStore(initialState)`
|
|
146
|
+
|
|
147
|
+
In-memory reactive store. No persistence.
|
|
148
|
+
|
|
149
|
+
```js
|
|
150
|
+
const counter = createStore(0);
|
|
151
|
+
counter.subscribe((value) => console.log("count is now", value));
|
|
152
|
+
counter.set((n) => n + 1);
|
|
153
|
+
counter.get(); // 1
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## `createPersistedStore(key, initialState, options?)`
|
|
157
|
+
|
|
158
|
+
Same as `createStore`, but loads from `storage` once on creation and saves
|
|
159
|
+
(debounced) on every change. Personal scope only.
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
const prefs = createPersistedStore("prefs", { theme: "light" });
|
|
163
|
+
await prefs.ready; // resolves once the initial load finishes
|
|
164
|
+
prefs.set({ theme: "dark" }); // persisted automatically
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
`options.debounceMs` (default `300`) controls how long to wait after the last
|
|
168
|
+
change before writing to storage.
|
|
169
|
+
|
|
170
|
+
## `createSharedStore(key, initialState, options?)`
|
|
171
|
+
|
|
172
|
+
Same as `createPersistedStore`, but backed by `storage.shared` and polled on
|
|
173
|
+
an interval, so every viewer of a published artifact converges on the same
|
|
174
|
+
state.
|
|
175
|
+
|
|
176
|
+
```js
|
|
177
|
+
const board = createSharedStore("board", { count: 0 }, { pollMs: 2000 });
|
|
178
|
+
await board.ready;
|
|
179
|
+
board.set({ count: board.get().count + 1 }); // pushed to every viewer
|
|
180
|
+
board.stop(); // stop polling, e.g. on teardown
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Conflict handling is last-write-wins: if two viewers change the same key
|
|
184
|
+
within one poll window, whichever write reaches storage last overwrites the
|
|
185
|
+
other. There is no merge/CRDT logic.
|