@chalky/chalky 0.1.4 → 0.1.6
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 +16 -5
- package/agent/SYSTEM.md +36 -0
- package/agent/extensions/app/README.md +72 -0
- package/{app/extensions/ui → agent/extensions/app}/agents.js +22 -16
- package/agent/extensions/app/guide.md +5 -0
- package/agent/extensions/app/index.ts +66 -0
- package/{app/extensions/ui/skills/agent-apps → agent/extensions/app/skills/app-agents}/SKILL.md +17 -18
- package/agent/extensions/app/skills/test-your-app/SKILL.md +23 -0
- package/{app/extensions/boards → agent/extensions/board}/browser.js +30 -32
- package/{app/extensions/boards → agent/extensions/board}/guide.md +1 -1
- package/{app/extensions/boards → agent/extensions/board}/index.ts +32 -30
- package/agent/extensions/card/README.md +88 -0
- package/agent/extensions/card/blocks/Button.js +34 -0
- package/{app/extensions/ui/components → agent/extensions/card/blocks}/Column.js +2 -2
- package/agent/extensions/card/blocks/Markdown.js +37 -0
- package/{app/extensions/ui/components/Card.js → agent/extensions/card/blocks/Panel.js} +3 -3
- package/{app/extensions/ui/components → agent/extensions/card/blocks}/Row.js +2 -2
- package/{app/extensions/ui/components → agent/extensions/card/blocks}/TextField.js +3 -3
- package/{app/extensions/ui → agent/extensions/card}/browser.js +155 -129
- package/agent/extensions/card/guide.md +13 -0
- package/agent/extensions/card/index.ts +370 -0
- package/{app/extensions/commands → agent/extensions/command}/browser.js +3 -3
- package/agent/extensions/command/guide.md +2 -0
- package/{app/extensions/commands → agent/extensions/command}/index.ts +14 -16
- package/agent/extensions/doodle/README.md +57 -0
- package/agent/extensions/doodle/browser.js +1013 -0
- package/agent/extensions/doodle/guide.md +22 -0
- package/agent/extensions/doodle/index.ts +274 -0
- package/agent/extensions/file/browser.js +182 -0
- package/agent/extensions/file/guide.md +5 -0
- package/{app/extensions/files → agent/extensions/file}/index.ts +37 -18
- package/{app/extensions/frames → agent/extensions/frame}/README.md +13 -13
- package/{app/extensions/frames → agent/extensions/frame}/browser.js +20 -18
- package/agent/extensions/frame/guide.md +3 -0
- package/agent/extensions/frame/index.ts +90 -0
- package/{app/extensions/ui_history → agent/extensions/history}/browser.js +18 -18
- package/{app/extensions/ui_history → agent/extensions/history}/index.ts +34 -33
- package/{app → agent}/extensions/mcp/browser.js +1 -1
- package/agent/extensions/mcp/guide.md +3 -0
- package/{app → agent}/extensions/mcp/index.ts +2 -4
- package/{app/extensions/login → agent/extensions/model}/browser.js +67 -67
- package/{app → agent}/extensions/model/index.ts +8 -4
- package/{app/extensions/login/index.ts → agent/extensions/model/login.ts} +47 -46
- package/agent/extensions/note/README.md +37 -0
- package/agent/extensions/note/browser.js +382 -0
- package/agent/extensions/note/guide.md +9 -0
- package/agent/extensions/note/index.ts +215 -0
- package/agent/extensions/page/browser.js +453 -0
- package/{app → agent}/extensions/page/index.ts +72 -3
- package/{app → agent}/extensions/page/page.html +102 -49
- package/agent/extensions/page/skills/interact-with-page/SKILL.md +59 -0
- package/agent/extensions/prompt/index.ts +38 -0
- package/{app → agent}/extensions/reload/index.ts +3 -3
- package/{app → agent}/extensions/screen/browser.js +10 -23
- package/{app → agent}/extensions/screen/index.ts +11 -11
- package/{app/extensions/steps → agent/extensions/step}/index.ts +1 -1
- package/{app/extensions/swarm → agent/extensions/team}/README.md +19 -19
- package/{app/extensions/swarm → agent/extensions/team}/guide.md +4 -4
- package/{app/extensions/swarm → agent/extensions/team}/index.ts +41 -66
- package/{app/extensions/versions → agent/extensions/version}/browser.js +19 -10
- package/agent/extensions/version/index.ts +205 -0
- package/{app → agent}/extensions/viewer/README.md +4 -4
- package/{app → agent}/extensions/viewer/browser.js +8 -8
- package/agent/extensions/viewer/guide.md +3 -0
- package/{app → agent}/extensions/viewer/index.ts +22 -17
- package/{app → agent}/extensions/voice/browser.js +4 -3
- package/{app → agent}/extensions/voice/index.ts +8 -6
- package/{app/extensions/ui → agent/extensions/voice}/voice.js +5 -5
- package/agent/settings.json +8 -0
- package/{app/extensions/ui/skills/ui-check → agent/skills/browser-check}/SKILL.md +4 -4
- package/{app/extensions/ui/skills/ui-check → agent/skills/browser-check}/scripts/check.mjs +1 -1
- package/{app/extensions/ui/skills/ui-design → agent/skills/design}/SKILL.md +15 -15
- package/agent/skills/extension-map/SKILL.md +56 -0
- package/agent/skills/show-me/SKILL.md +66 -0
- package/{app → agent}/skills/write-skill/SKILL.md +1 -1
- package/bin/chalky.mjs +2 -2
- package/package.json +9 -8
- package/server.ts +313 -58
- package/app/SYSTEM.md +0 -31
- package/app/extensions/appdata/README.md +0 -10
- package/app/extensions/appdata/guide.md +0 -4
- package/app/extensions/appdata/index.ts +0 -47
- package/app/extensions/commands/guide.md +0 -3
- package/app/extensions/doodle/README.md +0 -15
- package/app/extensions/doodle/browser.js +0 -398
- package/app/extensions/doodle/guide.md +0 -6
- package/app/extensions/doodle/index.ts +0 -54
- package/app/extensions/files/browser.js +0 -96
- package/app/extensions/files/guide.md +0 -7
- package/app/extensions/frames/guide.md +0 -7
- package/app/extensions/frames/index.ts +0 -85
- package/app/extensions/mcp/guide.md +0 -5
- package/app/extensions/point/README.md +0 -22
- package/app/extensions/point/browser.js +0 -247
- package/app/extensions/point/guide.md +0 -6
- package/app/extensions/point/index.ts +0 -112
- package/app/extensions/prompt/index.ts +0 -19
- package/app/extensions/ui/README.md +0 -69
- package/app/extensions/ui/components/App.js +0 -28
- package/app/extensions/ui/components/Button.js +0 -28
- package/app/extensions/ui/components/Markdown.js +0 -37
- package/app/extensions/ui/guide.md +0 -7
- package/app/extensions/ui/index.ts +0 -291
- package/app/extensions/versions/index.ts +0 -168
- package/app/extensions/viewer/guide.md +0 -5
- package/app/mcp.json +0 -3
- package/app/settings.json +0 -3
- package/app/skills/plugin-map/SKILL.md +0 -49
- package/app/ui/apps/.keep +0 -0
- package/node_modules/xlsx/CHANGELOG.md +0 -361
- package/node_modules/xlsx/LICENSE +0 -201
- package/node_modules/xlsx/README.md +0 -41
- package/node_modules/xlsx/bin/xlsx.njs +0 -310
- package/node_modules/xlsx/bower.json +0 -22
- package/node_modules/xlsx/dist/LICENSE +0 -201
- package/node_modules/xlsx/dist/cpexcel.d.ts +0 -39
- package/node_modules/xlsx/dist/cpexcel.full.mjs +0 -1502
- package/node_modules/xlsx/dist/cpexcel.js +0 -1506
- package/node_modules/xlsx/dist/shim.min.js +0 -2
- package/node_modules/xlsx/dist/xlsx.core.min.js +0 -18
- package/node_modules/xlsx/dist/xlsx.core.min.map +0 -1
- package/node_modules/xlsx/dist/xlsx.extendscript.js +0 -28259
- package/node_modules/xlsx/dist/xlsx.full.min.js +0 -24
- package/node_modules/xlsx/dist/xlsx.full.min.map +0 -1
- package/node_modules/xlsx/dist/xlsx.mini.min.js +0 -10
- package/node_modules/xlsx/dist/xlsx.mini.min.map +0 -1
- package/node_modules/xlsx/dist/xlsx.zahl.js +0 -4
- package/node_modules/xlsx/dist/xlsx.zahl.mjs +0 -2
- package/node_modules/xlsx/dist/zahl.d.ts +0 -4
- package/node_modules/xlsx/package.json +0 -183
- package/node_modules/xlsx/types/index.d.ts +0 -1065
- package/node_modules/xlsx/types/tsconfig.json +0 -15
- package/node_modules/xlsx/xlsx.js +0 -28103
- package/node_modules/xlsx/xlsx.mjs +0 -28228
- package/node_modules/xlsx/xlsxworker.js +0 -14
- /package/{app/extensions/ui/components → agent/extensions/card/blocks}/Table.js +0 -0
- /package/{app/extensions/ui/components → agent/extensions/card/blocks}/Text.js +0 -0
|
@@ -1,73 +1,75 @@
|
|
|
1
|
-
//
|
|
2
|
-
//
|
|
3
|
-
// A
|
|
1
|
+
// The page side of cards (index.ts here: the record, the tools, the messages). Draws the cards of the main space (the
|
|
2
|
+
// "top" slot): each shows its blocks, or its app (built/apps/..., served at /built/...) in a frame.
|
|
3
|
+
// A card is bare: only what the agent built, no frame (its label shows on hover: name, owner, ✕). It is
|
|
4
4
|
// stacked in the main space, its content keeping its own size; or pinned at place {x, y, width?, height?}
|
|
5
|
-
// (px from the main space's top-left, so it scrolls with the page), its root
|
|
5
|
+
// (px from the main space's top-left, so it scrolls with the page), its root block then filling it.
|
|
6
6
|
// What the user can do with the label (the handle, so clicks inside still work):
|
|
7
|
-
// drag move it; a drop sends {type: "
|
|
8
|
-
// drag and hold still over another
|
|
9
|
-
// fires a "
|
|
10
|
-
// {a, b, x, y} (
|
|
11
|
-
// Onto a pinned
|
|
12
|
-
// hold pin or unpin it ({type: "
|
|
7
|
+
// drag move it; a drop sends {type: "card_move", card, place} (unless draggable: false)
|
|
8
|
+
// drag and hold still over another card (it lights up), release combine: it slides back and the page
|
|
9
|
+
// fires a "card-combine" DOM event. A plain drop over another card just moves it (they overlap)
|
|
10
|
+
// {a, b, x, y} (note/browser.js opens a note box; the agent then changes b, and a stays).
|
|
11
|
+
// Onto a pinned card: refused (it would change), with a note in the chat
|
|
12
|
+
// hold pin or unpin it ({type: "card_pin"}): agents can't change a pinned card, and it stays put
|
|
13
13
|
// double-click turn it over: the back side shows what it is for (about), what it was made from, and
|
|
14
|
-
// each
|
|
15
|
-
// ✕ close it; if other
|
|
16
|
-
// The corner resizes it (unless resizable: false). Moving or resizing one first pins every stacked
|
|
14
|
+
// each block's levers (sentences from its about block; the words in them change it)
|
|
15
|
+
// ✕ close it; if other cards read from it, the label asks first (they get a copy)
|
|
16
|
+
// The corner resizes it (unless resizable: false). Moving or resizing one first pins every stacked card
|
|
17
17
|
// where it is (else the ones below would jump up).
|
|
18
|
-
// Typing into a bound field changes the data at once and sends {type: "
|
|
19
|
-
// no agent is woken
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
18
|
+
// Typing into a bound field changes the data at once and sends {type: "card_set"} shortly after: saved, and
|
|
19
|
+
// no agent is woken; so does a click on a Button with set (card.write). A block with
|
|
20
|
+
// when: {"path", "is"} is hidden while that data is something else (tabs, steps, "show more"). Props can read other cards' data ({"ref", "path"}); those redraw when it changes.
|
|
21
|
+
// Alternatives (options): drawn as a stack with a bar to flip through them; "Keep" sends {type: "card_keep"}.
|
|
22
|
+
// Blocks come from the page's catalog (page.load defines <block-name>; see page.html); one saved with
|
|
23
|
+
// an older version of its props goes through the block's upgrade(props, fromVersion) first.
|
|
24
|
+
// Elements are kept per id across redraws, so a block keeps its state (focus, a running game).
|
|
24
25
|
const get = (data, path) => path.split("/").filter(Boolean).reduce((o, k) => o?.[k], data);
|
|
25
26
|
const isBinding = (v) => v && typeof v === "object" && !Array.isArray(v) && Object.keys(v).join() === "path";
|
|
26
27
|
const isRef = (v) => v && typeof v === "object" && !Array.isArray(v) && Object.keys(v).sort().join() === "path,ref";
|
|
27
|
-
const propsOf = (c) => Object.fromEntries(Object.entries(c).filter(([k]) => !["id", "
|
|
28
|
-
const refsIn = (s) => new Set(s.
|
|
28
|
+
const propsOf = (c) => Object.fromEntries(Object.entries(c).filter(([k]) => !["id", "block", "children", "child", "when"].includes(k)));
|
|
29
|
+
const refsIn = (s) => new Set((s.blocks ?? []).flatMap((c) => Object.values(propsOf(c)).filter(isRef).map((v) => v.ref)));
|
|
29
30
|
const HOLD_MS = 550; // hold the label this long, without moving, to pin or unpin
|
|
30
|
-
const COMBINE_MS = 600; // hold a dragged
|
|
31
|
+
const COMBINE_MS = 600; // hold a dragged card still over another this long to combine with it
|
|
31
32
|
|
|
32
33
|
const CSS = `
|
|
33
|
-
[data-
|
|
34
|
-
[data-
|
|
35
|
-
[data-
|
|
36
|
-
[data-
|
|
37
|
-
[data-
|
|
38
|
-
[data-
|
|
34
|
+
[data-card] { position: relative; display: flex; flex-direction: column; width: fit-content; max-width: 100%; margin: 0 0 20px; }
|
|
35
|
+
[data-card] > .card-body { flex: 1; min-height: 0; display: flex; flex-direction: column; align-items: flex-start; }
|
|
36
|
+
[data-card] > .card-body > * { max-width: 100%; }
|
|
37
|
+
[data-card] > .card-body [hidden] { display: none !important; }
|
|
38
|
+
[data-card][data-placed] > .card-body { align-items: stretch; }
|
|
39
|
+
[data-card][data-placed] > .card-body > * { flex: 1; min-height: 0; }
|
|
40
|
+
[data-card] > .card-label { position: absolute; bottom: 100%; left: 0; z-index: 2; display: flex; gap: 8px; align-items: center;
|
|
39
41
|
padding: 2px 8px; border-radius: 8px; font-size: 11px; white-space: nowrap; color: var(--muted, #888); cursor: grab;
|
|
40
42
|
background: var(--glass-strong, #fff); opacity: 0; pointer-events: none; transition: opacity 0.15s; user-select: none; }
|
|
41
|
-
[data-
|
|
42
|
-
[data-
|
|
43
|
-
[data-
|
|
44
|
-
[data-
|
|
43
|
+
[data-card]:hover > .card-label, [data-card] > .card-label.asking { opacity: 1; pointer-events: auto; }
|
|
44
|
+
[data-card] > .card-label button { color: inherit; font: inherit; background: none; border: 0; cursor: pointer; padding: 0 2px; }
|
|
45
|
+
[data-card] > .card-label .ask { color: var(--ink, #1d1d1f); }
|
|
46
|
+
[data-card] > .card-resize { position: absolute; right: 0; bottom: 0; z-index: 1; width: 10px; height: 10px;
|
|
45
47
|
border: solid var(--muted, #888); border-width: 0 2px 2px 0; border-radius: 0 0 4px 0; cursor: nwse-resize;
|
|
46
48
|
opacity: 0; pointer-events: none; transition: opacity 0.15s; }
|
|
47
|
-
[data-
|
|
48
|
-
[data-
|
|
49
|
-
[data-
|
|
50
|
-
[data-
|
|
51
|
-
[data-
|
|
52
|
-
[data-
|
|
53
|
-
[data-
|
|
54
|
-
[data-
|
|
49
|
+
[data-card]:hover > .card-resize { opacity: 1; pointer-events: auto; }
|
|
50
|
+
[data-card][data-pinned] { outline: 2px solid #d4691e; outline-offset: 4px; border-radius: 6px; }
|
|
51
|
+
[data-card][data-pinned] > .card-label { cursor: default; }
|
|
52
|
+
[data-card][data-pinned] > .card-resize { display: none; }
|
|
53
|
+
[data-card][data-drop] { outline: 3px solid var(--accent, #0071e3); outline-offset: 4px; border-radius: 6px; }
|
|
54
|
+
[data-card][data-flipped] { z-index: 5; } /* the back side stays above its neighbours */
|
|
55
|
+
[data-card][data-flipped] > .card-body { visibility: hidden; }
|
|
56
|
+
[data-card] > .card-back { position: absolute; left: 0; top: 0; z-index: 1; box-sizing: border-box; min-width: 260px; min-height: 100%;
|
|
55
57
|
max-width: 420px; padding: 12px 14px; border-radius: 12px; font-size: 13px; line-height: 1.5; color: #e8eeea; background: #1f2a25;
|
|
56
58
|
box-shadow: 0 8px 30px rgba(0,0,0,.18); display: flex; flex-direction: column; gap: 8px; }
|
|
57
|
-
[data-
|
|
58
|
-
[data-
|
|
59
|
-
[data-
|
|
59
|
+
[data-card] > .card-back p { margin: 0; }
|
|
60
|
+
[data-card] > .card-back ul { margin: 0; padding-left: 18px; display: flex; flex-direction: column; gap: 4px; }
|
|
61
|
+
[data-card] > .card-back .lever { font: inherit; color: #1f2a25; background: #f4d35e; border: 0; border-radius: 4px;
|
|
60
62
|
padding: 0 5px; cursor: pointer; outline-offset: 1px; }
|
|
61
|
-
[data-
|
|
62
|
-
[data-
|
|
63
|
-
[data-
|
|
64
|
-
[data-
|
|
63
|
+
[data-card] > .card-back span.lever { cursor: text; }
|
|
64
|
+
[data-card] > .card-back .lever.ref { background: #c7d3cd; cursor: default; }
|
|
65
|
+
[data-card] > .card-back .muted { opacity: .7; }
|
|
66
|
+
[data-card] > .card-back > button { align-self: flex-start; font: inherit; color: inherit; background: none;
|
|
65
67
|
border: 1px solid currentColor; border-radius: 8px; padding: 3px 10px; cursor: pointer; }
|
|
66
|
-
[data-
|
|
67
|
-
[data-
|
|
68
|
-
[data-
|
|
68
|
+
[data-card][data-options] > .card-body { box-shadow: 8px 8px 0 -2px rgba(0,0,0,.07), 16px 16px 0 -4px rgba(0,0,0,.05); border-radius: 10px; }
|
|
69
|
+
[data-card] > .card-options { display: flex; gap: 6px; align-items: center; margin-bottom: 6px; font-size: 12px; color: var(--muted, #888); }
|
|
70
|
+
[data-card] > .card-options button { font: inherit; color: inherit; background: var(--glass-strong, #fff);
|
|
69
71
|
border: 1px solid var(--line, #ddd); border-radius: 8px; padding: 1px 8px; cursor: pointer; }
|
|
70
|
-
[data-
|
|
72
|
+
[data-card] > .card-options button.keep { color: #fff; background: var(--accent, #0071e3); border-color: transparent; }`;
|
|
71
73
|
|
|
72
74
|
function setPath(data, path, value) {
|
|
73
75
|
const keys = path.split("/").filter(Boolean), last = keys.pop();
|
|
@@ -83,50 +85,50 @@ function el(tag, attrs = {}, ...children) { // a small element builder (textCon
|
|
|
83
85
|
}
|
|
84
86
|
|
|
85
87
|
export default async function (page) {
|
|
86
|
-
if (!page.load) return; // an older page (e.g. a Versions preview) without the
|
|
87
|
-
document.head.insertAdjacentHTML("beforeend", `<style id="
|
|
88
|
+
if (!page.load) return; // an older page (e.g. a Versions preview) without the block runtime
|
|
89
|
+
document.head.insertAdjacentHTML("beforeend", `<style id="card-css">${CSS}</style>`);
|
|
88
90
|
const area = page.slot("top");
|
|
89
|
-
const
|
|
91
|
+
const cards = {}; // id -> the record (index.ts), plus what only this page has: box, body, nodes, frame, flipped, option
|
|
90
92
|
let abouts = {}, mtimes = {};
|
|
91
|
-
const modules = {}; //
|
|
93
|
+
const modules = {}; // block name -> its module (for upgrade())
|
|
92
94
|
const loadAbouts = async () => {
|
|
93
|
-
try { [abouts, mtimes] = await Promise.all(["/
|
|
95
|
+
try { [abouts, mtimes] = await Promise.all(["/card/about", "/card/blocks"].map(async (u) => (await fetch(u)).json())); } catch {}
|
|
94
96
|
};
|
|
95
|
-
page.on("version", loadAbouts); // an agent may have changed a
|
|
96
|
-
const pending = {}; // "
|
|
97
|
+
page.on("version", loadAbouts); // an agent may have changed a block
|
|
98
|
+
const pending = {}; // "card path" -> timer: card_set waits until the typing pauses
|
|
97
99
|
const sendSet = (id, path, value) => {
|
|
98
100
|
clearTimeout(pending[`${id} ${path}`]);
|
|
99
|
-
pending[`${id} ${path}`] = setTimeout(() => page.send({ type: "
|
|
101
|
+
pending[`${id} ${path}`] = setTimeout(() => page.send({ type: "card_set", card: id, path, value }), 500);
|
|
100
102
|
};
|
|
101
103
|
|
|
102
104
|
async function upgraded(name, props, s) { // props saved with an older about.version: through upgrade() first
|
|
103
105
|
const from = s.kinds?.[name], now = abouts[name]?.version;
|
|
104
106
|
if (!from || !now || from >= now) return props;
|
|
105
107
|
try {
|
|
106
|
-
const mod = await (modules[name] ??= import(`/
|
|
108
|
+
const mod = await (modules[name] ??= import(`/card/blocks/${name}.js?v=${mtimes[name]}`));
|
|
107
109
|
return mod.upgrade ? mod.upgrade(props, from) : props;
|
|
108
110
|
} catch (e) { console.warn(`upgrade of ${name}:`, e); return props; }
|
|
109
111
|
}
|
|
110
112
|
|
|
111
113
|
async function draw(id, chain = new Set()) {
|
|
112
|
-
const s =
|
|
113
|
-
if (!s || chain.has(id)) return; // chain:
|
|
114
|
+
const s = cards[id];
|
|
115
|
+
if (!s || chain.has(id)) return; // chain: cards reading from each other are drawn once each
|
|
114
116
|
chain.add(id);
|
|
115
|
-
if (!s.box) { // the
|
|
117
|
+
if (!s.box) { // the card, with a label (name, pin, ✕) that shows on hover
|
|
116
118
|
s.box = el("div");
|
|
117
|
-
s.box.dataset.
|
|
118
|
-
s.label = el("div", { class: "
|
|
119
|
-
s.body = el("div", { class: "
|
|
120
|
-
s.box.append(s.label, s.body, el("div", { class: "
|
|
119
|
+
s.box.dataset.card = id; // lets the page work out what a click points at (note/browser.js)
|
|
120
|
+
s.label = el("div", { class: "card-label" });
|
|
121
|
+
s.body = el("div", { class: "card-body" });
|
|
122
|
+
s.box.append(s.label, s.body, el("div", { class: "card-resize", title: "Drag to resize", onpointerdown: (e) => resize(s, id, e) }));
|
|
121
123
|
s.label.onpointerdown = (e) => drag(s, id, e);
|
|
122
124
|
s.label.ondblclick = () => { s.flipped = !s.flipped; draw(id); };
|
|
123
125
|
s.nodes = {};
|
|
124
126
|
area.append(s.box);
|
|
125
127
|
}
|
|
126
128
|
drawLabel(s, id);
|
|
127
|
-
s.box.dataset.owner = s.owner; // who a note on this
|
|
128
|
-
s.box.dataset.board = s.board ?? 1; // its board:
|
|
129
|
-
s.box.querySelector(".
|
|
129
|
+
s.box.dataset.owner = s.owner; // who a note on this card goes to (note/browser.js)
|
|
130
|
+
s.box.dataset.board = s.board ?? 1; // its board: board/browser.js shows it there
|
|
131
|
+
s.box.querySelector(".card-resize").hidden = s.resizable === false;
|
|
130
132
|
s.box.toggleAttribute("data-pinned", !!s.pinned);
|
|
131
133
|
s.box.toggleAttribute("data-flipped", !!s.flipped);
|
|
132
134
|
s.box.toggleAttribute("data-options", !!s.options);
|
|
@@ -136,24 +138,26 @@ export default async function (page) {
|
|
|
136
138
|
width: p.width ? `${p.width}px` : "", height: p.height ? `${p.height}px` : "" }
|
|
137
139
|
: { position: "", left: "", top: "", margin: "", width: "", height: "" });
|
|
138
140
|
drawOptionsBar(s, id);
|
|
139
|
-
const shown = s.options ? s.options[s.option ?? 0] : s; // an alternative, or the
|
|
141
|
+
const shown = s.options ? s.options[s.option ?? 0] : s; // an alternative, or the card itself
|
|
142
|
+
if (shown.app) return drawApp(s, id, shown);
|
|
143
|
+
s.frame = null;
|
|
140
144
|
try {
|
|
141
|
-
for (const c of shown.
|
|
145
|
+
for (const c of shown.blocks) if (!(await page.load(c.block))) throw new Error(`no block ${c.block}`);
|
|
142
146
|
} catch (e) {
|
|
143
|
-
s.body.textContent = `Could not load a
|
|
147
|
+
s.body.textContent = `Could not load a block: ${e}`;
|
|
144
148
|
return;
|
|
145
149
|
}
|
|
146
|
-
const byId = Object.fromEntries(shown.
|
|
147
|
-
const resolve = (v) => (isBinding(v) ? get(shown.data, v.path) : isRef(v) ? get(
|
|
150
|
+
const byId = Object.fromEntries(shown.blocks.map((c) => [c.id, c]));
|
|
151
|
+
const resolve = (v) => (isBinding(v) ? get(shown.data, v.path) : isRef(v) ? get(cards[v.ref]?.data ?? {}, v.path) : v);
|
|
148
152
|
const props = {};
|
|
149
|
-
for (const c of shown.
|
|
153
|
+
for (const c of shown.blocks) props[c.id] = await upgraded(c.block, Object.fromEntries(
|
|
150
154
|
Object.entries(propsOf(c)).map(([k, v]) => [k, resolve(v)])), s);
|
|
151
155
|
|
|
152
156
|
const build = (cid) => {
|
|
153
|
-
const c = byId[cid], tag = `
|
|
157
|
+
const c = byId[cid], tag = `block-${c.block.toLowerCase()}`;
|
|
154
158
|
const node = s.nodes[cid]?.localName === tag ? s.nodes[cid] : document.createElement(tag);
|
|
155
159
|
s.nodes[cid] = node;
|
|
156
|
-
node.dataset.
|
|
160
|
+
node.dataset.block = cid;
|
|
157
161
|
const children = (c.children || (c.child ? [c.child] : [])).map(build);
|
|
158
162
|
node.update(props[cid], {
|
|
159
163
|
children,
|
|
@@ -161,67 +165,89 @@ export default async function (page) {
|
|
|
161
165
|
if (container.children.length !== children.length || children.some((ch, i) => container.children[i] !== ch))
|
|
162
166
|
container.replaceChildren(...children);
|
|
163
167
|
},
|
|
164
|
-
set(prop, value) { // write a bound prop into the data: at once here, saved shortly after (
|
|
165
|
-
if (!isBinding(c[prop])) return; // a plain value or another
|
|
168
|
+
set(prop, value) { // write a bound prop into the data: at once here, saved shortly after (card_set)
|
|
169
|
+
if (!isBinding(c[prop])) return; // a plain value or another card's data: not this field's to write
|
|
166
170
|
setPath(shown.data, c[prop].path, value);
|
|
167
171
|
if (!s.options) sendSet(id, c[prop].path, value);
|
|
168
172
|
draw(id);
|
|
169
173
|
},
|
|
174
|
+
data: shown.data, // this card's data, to read (write through set or write)
|
|
175
|
+
write(values) { // {path: value, ...} into this card's data: at once here, saved shortly after (card_set)
|
|
176
|
+
for (const [path, value] of Object.entries(values)) {
|
|
177
|
+
setPath(shown.data, path, value);
|
|
178
|
+
if (!s.options) sendSet(id, path, value);
|
|
179
|
+
}
|
|
180
|
+
draw(id);
|
|
181
|
+
},
|
|
170
182
|
action(name, context = {}) {
|
|
171
183
|
const ctx = Object.fromEntries(Object.entries(context).map(([k, v]) => [k, resolve(v)]));
|
|
172
|
-
page.send({ type: "
|
|
184
|
+
page.send({ type: "card_action", card: id, block: cid, name, context: ctx, data: s.data });
|
|
173
185
|
},
|
|
174
186
|
});
|
|
187
|
+
node.hidden = !!c.when && get(shown.data, c.when.path) !== c.when.is; // shown only while its data matches
|
|
175
188
|
return node;
|
|
176
189
|
};
|
|
177
190
|
try {
|
|
178
191
|
const root = build("root");
|
|
179
192
|
if (s.body.firstChild !== root) s.body.replaceChildren(root);
|
|
180
193
|
} catch (e) {
|
|
181
|
-
s.body.textContent = `Could not draw this
|
|
194
|
+
s.body.textContent = `Could not draw this card: ${e}`;
|
|
182
195
|
}
|
|
183
196
|
drawBack(s, id, shown, resolve);
|
|
184
|
-
for (const [other, o] of Object.entries(
|
|
197
|
+
for (const [other, o] of Object.entries(cards)) if (o.blocks && refsIn(o).has(id)) draw(other, chain);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function drawApp(s, id, shown) { // an app: its page in a frame, 640px wide (at most the space there); it fills a
|
|
201
|
+
if (!s.frame) { // placed card, else it is height px tall. The same frame across redraws: a running app isn't restarted
|
|
202
|
+
s.frame = el("iframe");
|
|
203
|
+
s.frame.style.cssText = "display:block;width:100%;min-height:0;border:0;background:transparent;color-scheme:normal";
|
|
204
|
+
}
|
|
205
|
+
s.frame.style.flex = `1 1 ${s.height ?? 400}px`;
|
|
206
|
+
s.frame.style.width = s.place?.width ? "100%" : "640px"; // a frame has no width of its own (else 300px)
|
|
207
|
+
if (s.body.firstChild !== s.frame) s.body.replaceChildren(s.frame);
|
|
208
|
+
const url = `/built/${shown.app}`;
|
|
209
|
+
if (s.frame.getAttribute("src") !== url) s.frame.setAttribute("src", url);
|
|
210
|
+
drawBack(s, id, shown, (v) => v);
|
|
185
211
|
}
|
|
186
212
|
|
|
187
213
|
function drawLabel(s, id) { // name · owner, pinned or not, ✕ (which asks first if others read from it)
|
|
188
|
-
const users = Object.keys(
|
|
214
|
+
const users = Object.keys(cards).filter((o) => o !== id && cards[o].blocks && refsIn(cards[o]).has(id));
|
|
189
215
|
s.label.classList.remove("asking");
|
|
190
216
|
s.label.replaceChildren(
|
|
191
217
|
el("span", { text: `${id} · ${s.owner}${s.pinned ? " · pinned" : ""}`, title: "Drag to move; hold still over another to combine; hold to pin; double-click to turn over" }),
|
|
192
218
|
el("button", { text: "✕", title: "Close", onclick: () => {
|
|
193
|
-
if (!users.length) return page.send({ type: "
|
|
219
|
+
if (!users.length) return page.send({ type: "card_close", card: id });
|
|
194
220
|
s.label.classList.add("asking");
|
|
195
221
|
s.label.replaceChildren(
|
|
196
222
|
el("span", { class: "ask", text: `${users.join(", ")} ${users.length > 1 ? "read" : "reads"} from this. Copy it in, then close?` }),
|
|
197
|
-
el("button", { text: "Copy & close", onclick: () => page.send({ type: "
|
|
223
|
+
el("button", { text: "Copy & close", onclick: () => page.send({ type: "card_close", card: id }) }),
|
|
198
224
|
el("button", { text: "Cancel", onclick: () => drawLabel(s, id) }));
|
|
199
225
|
} }));
|
|
200
226
|
}
|
|
201
227
|
|
|
202
228
|
function drawOptionsBar(s, id) {
|
|
203
|
-
s.box.querySelector(".
|
|
229
|
+
s.box.querySelector(".card-options")?.remove();
|
|
204
230
|
if (!s.options) return;
|
|
205
231
|
const i = s.option ?? 0, n = s.options.length;
|
|
206
232
|
const flip = (d) => { s.option = (i + d + n) % n; draw(id); };
|
|
207
|
-
const bar = el("div", { class: "
|
|
233
|
+
const bar = el("div", { class: "card-options" },
|
|
208
234
|
el("button", { text: "‹", title: "Previous", "aria-label": "Previous option", onclick: () => flip(-1) }),
|
|
209
235
|
el("span", { text: `${i + 1} of ${n}: ${s.options[i].label}` }),
|
|
210
236
|
el("button", { text: "›", title: "Next", "aria-label": "Next option", onclick: () => flip(1) }),
|
|
211
|
-
el("button", { class: "keep", text: "Keep this", onclick: () => page.send({ type: "
|
|
237
|
+
el("button", { class: "keep", text: "Keep this", onclick: () => page.send({ type: "card_keep", card: id, index: i }) }));
|
|
212
238
|
bar.onwheel = (e) => { e.preventDefault(); flip(Math.sign(e.deltaY || e.deltaX)); };
|
|
213
239
|
s.box.insertBefore(bar, s.body);
|
|
214
240
|
}
|
|
215
241
|
|
|
216
242
|
function drawBack(s, id, shown, resolve) { // the back side: about, made from, levers, pinned
|
|
217
|
-
s.box.querySelector(".
|
|
243
|
+
s.box.querySelector(".card-back")?.remove();
|
|
218
244
|
if (!s.flipped) return;
|
|
219
|
-
const back = el("div", { class: "
|
|
245
|
+
const back = el("div", { class: "card-back" }, el("b", { text: `How ${id} works` }),
|
|
220
246
|
el("p", { class: s.about ? "" : "muted", text: s.about || "No description yet. Ask its agent to write one." }));
|
|
221
247
|
if (s.made_from) back.append(el("p", { text: `Made from ${s.made_from.join(" and ")}` + (s.instruction ? `: “${s.instruction}”` : ".") }));
|
|
222
248
|
const list = el("ul");
|
|
223
|
-
for (const c of shown.
|
|
224
|
-
const spec = abouts[c.
|
|
249
|
+
for (const c of shown.blocks ?? []) {
|
|
250
|
+
const spec = abouts[c.block];
|
|
225
251
|
for (const sentence of spec?.levers || []) {
|
|
226
252
|
const li = el("li", {}, el("span", { class: "muted", text: `${c.id}: ` }));
|
|
227
253
|
for (const [i, part] of sentence.split(/\{(\w+)\}/).entries()) li.append(i % 2 ? lever(s, id, shown, c, part, spec.props?.[part] || {}, resolve) : part);
|
|
@@ -230,7 +256,7 @@ export default async function (page) {
|
|
|
230
256
|
}
|
|
231
257
|
if (list.children.length) back.append(list);
|
|
232
258
|
back.append(el("p", {}, "It is ", el("button", { class: "lever", text: s.pinned ? "pinned" : "not pinned",
|
|
233
|
-
onclick: () => page.send({ type: "
|
|
259
|
+
onclick: () => page.send({ type: "card_pin", card: id, pinned: !s.pinned }) }),
|
|
234
260
|
`: agents ${s.pinned ? "can't" : "may"} change it.`));
|
|
235
261
|
back.append(el("button", { text: "Turn back", onclick: () => { s.flipped = false; draw(id); } }));
|
|
236
262
|
s.box.append(back);
|
|
@@ -240,8 +266,8 @@ export default async function (page) {
|
|
|
240
266
|
const raw = c[prop], value = resolve(raw) ?? spec.default ?? "";
|
|
241
267
|
if (isRef(raw)) return el("span", { class: "lever ref", text: String(value), title: `read from ${raw.ref}` });
|
|
242
268
|
const commit = (v) => {
|
|
243
|
-
if (isBinding(raw)) { setPath(shown.data, raw.path, v); page.send({ type: "
|
|
244
|
-
else { c[prop] = v; page.send({ type: "
|
|
269
|
+
if (isBinding(raw)) { setPath(shown.data, raw.path, v); page.send({ type: "card_set", card: id, path: raw.path, value: v }); }
|
|
270
|
+
else { c[prop] = v; page.send({ type: "card_prop", card: id, block: c.id, prop, value: v }); }
|
|
245
271
|
draw(id);
|
|
246
272
|
};
|
|
247
273
|
if (spec.type === "choice" && spec.options?.length)
|
|
@@ -259,48 +285,48 @@ export default async function (page) {
|
|
|
259
285
|
return el("span", { class: "lever ref", text: JSON.stringify(value) }); // lists, objects: shown, changed by the agent
|
|
260
286
|
}
|
|
261
287
|
|
|
262
|
-
page.on("
|
|
288
|
+
page.on("card", (ev) => {
|
|
263
289
|
if (ev.op === "render") {
|
|
264
|
-
const old =
|
|
265
|
-
const { op, type,
|
|
266
|
-
|
|
290
|
+
const old = cards[ev.card] || {};
|
|
291
|
+
const { op, type, card, ...record } = ev;
|
|
292
|
+
cards[ev.card] = { ...record, box: old.box, label: old.label, body: old.body, nodes: old.nodes, frame: old.frame,
|
|
267
293
|
flipped: old.flipped, option: record.options ? old.option : 0 };
|
|
268
|
-
draw(ev.
|
|
269
|
-
} else if (ev.op === "delete" &&
|
|
270
|
-
|
|
271
|
-
delete
|
|
272
|
-
for (const id of Object.keys(
|
|
294
|
+
draw(ev.card);
|
|
295
|
+
} else if (ev.op === "delete" && cards[ev.card]) {
|
|
296
|
+
cards[ev.card].box?.remove();
|
|
297
|
+
delete cards[ev.card];
|
|
298
|
+
for (const id of Object.keys(cards)) drawLabel(cards[id], id);
|
|
273
299
|
} else if (ev.op === "reset") { // a whole saved state (undo, redo, rewind)
|
|
274
|
-
for (const id of Object.keys(
|
|
275
|
-
for (const [id, record] of Object.entries(ev.
|
|
276
|
-
const old =
|
|
277
|
-
|
|
300
|
+
for (const id of Object.keys(cards)) if (!(id in ev.cards)) { cards[id].box?.remove(); delete cards[id]; }
|
|
301
|
+
for (const [id, record] of Object.entries(ev.cards)) {
|
|
302
|
+
const old = cards[id] || {};
|
|
303
|
+
cards[id] = { ...record, box: old.box, label: old.label, body: old.body, nodes: old.nodes, frame: old.frame, flipped: old.flipped, option: 0 };
|
|
278
304
|
}
|
|
279
|
-
for (const id of Object.keys(
|
|
305
|
+
for (const id of Object.keys(cards)) draw(id);
|
|
280
306
|
}
|
|
281
307
|
});
|
|
282
308
|
|
|
283
|
-
function pinStacked() { // pin each stacked
|
|
284
|
-
const stacked = Object.entries(
|
|
309
|
+
function pinStacked() { // pin each stacked card where it is now, so taking one out of the stack moves none
|
|
310
|
+
const stacked = Object.entries(cards).filter(([, s]) => !s.place && s.box);
|
|
285
311
|
for (const [, s] of stacked) s.place = { x: s.box.offsetLeft, y: s.box.offsetTop }; // read all, then move
|
|
286
312
|
for (const [id, s] of stacked) {
|
|
287
313
|
s.box.toggleAttribute("data-placed", true);
|
|
288
314
|
Object.assign(s.box.style, { position: "absolute", margin: "0", left: `${s.place.x}px`, top: `${s.place.y}px` });
|
|
289
|
-
page.send({ type: "
|
|
315
|
+
page.send({ type: "card_move", card: id, place: s.place });
|
|
290
316
|
}
|
|
291
317
|
}
|
|
292
318
|
|
|
293
|
-
function
|
|
294
|
-
return document.elementsFromPoint(x, y).map((e) => e.closest?.("[data-
|
|
319
|
+
function cardAt(x, y, except) { // the card under a window point, other than `except`
|
|
320
|
+
return document.elementsFromPoint(x, y).map((e) => e.closest?.("[data-card]")).find((b) => b && b !== except) || null;
|
|
295
321
|
}
|
|
296
322
|
|
|
297
|
-
function drag(s, id, e) { // hold: pin; move: drag it; hold still over another
|
|
323
|
+
function drag(s, id, e) { // hold: pin; move: drag it; hold still over another card, then release: combine
|
|
298
324
|
if (e.target.closest("button")) return;
|
|
299
325
|
const handle = e.currentTarget, box = s.box;
|
|
300
|
-
handle.setPointerCapture(e.pointerId); // keep getting moves over iframes and other
|
|
326
|
+
handle.setPointerCapture(e.pointerId); // keep getting moves over iframes and other cards
|
|
301
327
|
let place = null, held = false, x0 = 0, y0 = 0;
|
|
302
|
-
let under = null, armed = null, wait = null, sx = 0, sy = 0; // the
|
|
303
|
-
const hold = setTimeout(() => { held = true; page.send({ type: "
|
|
328
|
+
let under = null, armed = null, wait = null, sx = 0, sy = 0; // the card under the pointer; armed: lit, ready to combine
|
|
329
|
+
const hold = setTimeout(() => { held = true; page.send({ type: "card_pin", card: id, pinned: !s.pinned }); }, HOLD_MS);
|
|
304
330
|
handle.onpointermove = (ev) => {
|
|
305
331
|
if (held || Math.hypot(ev.clientX - e.clientX, ev.clientY - e.clientY) < 5) return;
|
|
306
332
|
clearTimeout(hold);
|
|
@@ -309,7 +335,7 @@ export default async function (page) {
|
|
|
309
335
|
place = { ...s.place, x: Math.round(x0 + ev.clientX - e.clientX), y: Math.round(y0 + ev.clientY - e.clientY) };
|
|
310
336
|
box.toggleAttribute("data-placed", true);
|
|
311
337
|
Object.assign(box.style, { position: "absolute", margin: "0", left: `${place.x}px`, top: `${place.y}px` });
|
|
312
|
-
const now =
|
|
338
|
+
const now = cardAt(ev.clientX, ev.clientY, box);
|
|
313
339
|
if (now !== under || Math.hypot(ev.clientX - sx, ev.clientY - sy) > 6) { // moved: start waiting again
|
|
314
340
|
clearTimeout(wait);
|
|
315
341
|
armed?.removeAttribute("data-drop");
|
|
@@ -324,19 +350,19 @@ export default async function (page) {
|
|
|
324
350
|
armed?.removeAttribute("data-drop");
|
|
325
351
|
if (!place) return; // a click or a hold, not a drag
|
|
326
352
|
box.style.zIndex = "";
|
|
327
|
-
if (armed) { // combine: put this one back, and ask how (
|
|
353
|
+
if (armed) { // combine: put this one back, and ask how (note/browser.js); b is the one that changes
|
|
328
354
|
Object.assign(box.style, { left: `${s.place.x}px`, top: `${s.place.y}px` });
|
|
329
|
-
const b = armed.dataset.
|
|
330
|
-
if (
|
|
331
|
-
document.dispatchEvent(new CustomEvent("
|
|
355
|
+
const b = armed.dataset.card;
|
|
356
|
+
if (cards[b]?.pinned) return page.note(`${b} is pinned, so it can't take ${id} in. Hold its label to unpin it first.`);
|
|
357
|
+
document.dispatchEvent(new CustomEvent("card-combine", { detail: { a: id, b, x: ev.clientX, y: ev.clientY } }));
|
|
332
358
|
return;
|
|
333
359
|
}
|
|
334
360
|
s.place = place;
|
|
335
|
-
page.send({ type: "
|
|
361
|
+
page.send({ type: "card_move", card: id, place });
|
|
336
362
|
};
|
|
337
363
|
}
|
|
338
364
|
|
|
339
|
-
function resize(s, id, e) { // drag the corner: the
|
|
365
|
+
function resize(s, id, e) { // drag the corner: the card's new width and height; on release, pin it so
|
|
340
366
|
if (s.pinned) return;
|
|
341
367
|
pinStacked();
|
|
342
368
|
const handle = e.currentTarget, box = s.box;
|
|
@@ -354,11 +380,11 @@ export default async function (page) {
|
|
|
354
380
|
handle.onpointermove = handle.onpointerup = null;
|
|
355
381
|
if (!place) return;
|
|
356
382
|
s.place = place;
|
|
357
|
-
page.send({ type: "
|
|
383
|
+
page.send({ type: "card_move", card: id, place });
|
|
358
384
|
};
|
|
359
385
|
}
|
|
360
386
|
|
|
361
387
|
await loadAbouts();
|
|
362
|
-
for (const [id, s] of Object.entries(await (await fetch("/
|
|
363
|
-
for (const id of Object.keys(
|
|
388
|
+
for (const [id, s] of Object.entries(await (await fetch("/card/all")).json())) cards[id] = { ...s, option: 0 };
|
|
389
|
+
for (const id of Object.keys(cards)) draw(id);
|
|
364
390
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Cards
|
|
2
|
+
Show what you build as cards on the boards in the page's main space: each shows blocks (JSON) or an app (a page you
|
|
3
|
+
write in {built}/apps/), with card_show, card_update and card_remove, rather than by editing the page.
|
|
4
|
+
For a chart, a diagram, a game's art or a restyle, load the design skill first.
|
|
5
|
+
- Name what you build (card ids, block ids, element ids): the user points at things by name.
|
|
6
|
+
- The user moves and resizes cards: build things that fill their box (%, flex, a viewBox), not fixed px.
|
|
7
|
+
- Blocks when clicks and typing only store values; an app when the page must calculate, add or remove items, follow
|
|
8
|
+
rules or draw.
|
|
9
|
+
- card_show(card, app=<name>) checks an app on the board and reports errors and whether it fits: fix those. Before
|
|
10
|
+
telling the user what a card does, try its main action (the app README says how) and describe only what worked.
|
|
11
|
+
- A card the user pinned: don't work around it (make a new one, made_from=[it], or ask).
|
|
12
|
+
Before writing an app, read {agent}/extensions/app/README.md: it is all an app needs. For blocks, load card_blocks
|
|
13
|
+
(tool_search): it lists them and says what to read.
|