isorite 0.10.0 → 0.12.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 +2 -5
- package/dist/core.js +184 -13
- package/dist/main.js +1018 -200
- package/docs/setup-for-agents.md +11 -36
- package/docs/setup.md +20 -61
- package/docs/terminal.md +28 -80
- package/package.json +2 -1
- package/web/_app/immutable/assets/0.BXPxx6Q0.css +1 -0
- package/web/_app/immutable/assets/5.rBvyRuST.css +1 -0
- package/web/_app/immutable/assets/{7.ljiu9bn5.css → 7.C00vyVPo.css} +1 -1
- package/web/_app/immutable/assets/Drawing.Q7J9MyIo.css +1 -0
- package/web/_app/immutable/assets/NoteEditor.S3q5lmKq.css +1 -0
- package/web/_app/immutable/assets/{NoteList.F579Zvj_.css → NoteList.E6jC-AtZ.css} +1 -1
- package/web/_app/immutable/assets/Timeline.LsRXYIBv.css +1 -0
- package/web/_app/immutable/assets/ground.hxi5N5fC.css +1 -0
- package/web/_app/immutable/chunks/4kgFUGOH.js +1 -0
- package/web/_app/immutable/chunks/8SS1q0nk.js +7 -0
- package/web/_app/immutable/chunks/{C5MPRibb.js → BK6pLgdi.js} +1 -1
- package/web/_app/immutable/chunks/{CiU6tYJv.js → Bk_JinGf.js} +1 -1
- package/web/_app/immutable/chunks/CFEI5lV9.js +1 -0
- package/web/_app/immutable/chunks/{6RXH8WsD.js → CiMp12cC.js} +1 -1
- package/web/_app/immutable/chunks/{BcgO-fBX.js → D4YEq54N.js} +1 -1
- package/web/_app/immutable/chunks/{CsRPJjQS.js → DDCg82xL.js} +1 -1
- package/web/_app/immutable/chunks/DThNJvmp.js +1 -0
- package/web/_app/immutable/chunks/{DQjIi5Jv.js → D_jbSWJl.js} +1 -1
- package/web/_app/immutable/chunks/DbeMVOH4.js +1 -0
- package/web/_app/immutable/chunks/DsQj0LyB.js +523 -0
- package/web/_app/immutable/chunks/{CBkjDkEN.js → Uo6irDQW.js} +80 -79
- package/web/_app/immutable/chunks/{DCD8_jrW.js → jTXVas70.js} +1 -1
- package/web/_app/immutable/entry/app.CvK8zypr.js +2 -0
- package/web/_app/immutable/entry/start.Dcgm6sQd.js +1 -0
- package/web/_app/immutable/nodes/0.0TdRBEoR.js +26 -0
- package/web/_app/immutable/nodes/{1.rzuq9VRh.js → 1.Dchqgt4j.js} +1 -1
- package/web/_app/immutable/nodes/2.C0CMUdSu.js +1 -0
- package/web/_app/immutable/nodes/3.MjtWDMWd.js +1 -0
- package/web/_app/immutable/nodes/4.hhS0SQta.js +2 -0
- package/web/_app/immutable/nodes/5.C33_cw2J.js +9 -0
- package/web/_app/immutable/nodes/6.owNfwwRl.js +1 -0
- package/web/_app/immutable/nodes/7.BpxHVHoY.js +1 -0
- package/web/_app/version.json +1 -1
- package/web/index.html +20 -18
- package/web/kit/chart.js +55 -0
- package/web/kit/isorite.css +32 -0
- package/web/kit/kit.css +80 -0
- package/web/kit/kit.js +543 -0
- package/web/kit/licenses/d3.txt +13 -0
- package/web/kit/licenses/interval-tree-1d.txt +22 -0
- package/web/kit/licenses/isoformat.txt +13 -0
- package/web/kit/licenses/katex.txt +21 -0
- package/web/kit/licenses/observablehq-plot.txt +13 -0
- package/web/kit/licenses/three.txt +21 -0
- package/web/kit/math.css +1 -0
- package/web/kit/math.js +257 -0
- package/web/kit/model.js +4236 -0
- package/web/kit/plain.css +16 -0
- package/web/_app/immutable/assets/0.CPW9fjPu.css +0 -1
- package/web/_app/immutable/assets/5.TsqJFEFF.css +0 -1
- package/web/_app/immutable/assets/NoteEditor.Wt5vU9Tb.css +0 -1
- package/web/_app/immutable/assets/Timeline.KTc3LKuK.css +0 -1
- package/web/_app/immutable/chunks/B0A4Snxv.js +0 -6
- package/web/_app/immutable/chunks/B8Qvvm53.js +0 -1
- package/web/_app/immutable/chunks/BJo70SOc.js +0 -1
- package/web/_app/immutable/chunks/BYSDTLqw.js +0 -1
- package/web/_app/immutable/chunks/BimZp6R0.js +0 -523
- package/web/_app/immutable/chunks/DlKQFacy.js +0 -1
- package/web/_app/immutable/chunks/c7PFFMl4.js +0 -1
- package/web/_app/immutable/entry/app.iw6ZIOh2.js +0 -2
- package/web/_app/immutable/entry/start.CFnKMSWs.js +0 -1
- package/web/_app/immutable/nodes/0.BzEgwgsa.js +0 -26
- package/web/_app/immutable/nodes/2.D_jWsQLW.js +0 -1
- package/web/_app/immutable/nodes/3.BzRx3SiD.js +0 -1
- package/web/_app/immutable/nodes/4.DP2MGav1.js +0 -2
- package/web/_app/immutable/nodes/5.BPS-CCQT.js +0 -5
- package/web/_app/immutable/nodes/6.DQMCyNJ3.js +0 -1
- package/web/_app/immutable/nodes/7.MWEn-qPC.js +0 -1
package/dist/main.js
CHANGED
|
@@ -43,6 +43,342 @@ var __exportAll = (all, no_symbols) => {
|
|
|
43
43
|
return target;
|
|
44
44
|
};
|
|
45
45
|
//#endregion
|
|
46
|
+
//#region packages/kit/src/assemble.ts
|
|
47
|
+
function meta(html, name) {
|
|
48
|
+
for (const m of html.matchAll(/<meta\b[^>]*>/gi)) {
|
|
49
|
+
const tag = m[0];
|
|
50
|
+
if ((tag.match(/\bname\s*=\s*["']([^"']*)["']/i)?.[1])?.toLowerCase() === name) return unescape(tag.match(/\bcontent\s*=\s*"([^"]*)"|\bcontent\s*=\s*'([^']*)'/i)?.slice(1).find((x) => x !== void 0) ?? "");
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
function manifestOf(html) {
|
|
55
|
+
return {
|
|
56
|
+
title: unescape(html.match(/<title[^>]*>([\s\S]*?)<\/title>/i)?.[1]?.trim() ?? ""),
|
|
57
|
+
description: meta(html, "description") ?? "",
|
|
58
|
+
author: meta(html, "author") ?? "",
|
|
59
|
+
date: meta(html, "date") ?? "",
|
|
60
|
+
kit: meta(html, "isorite:kit"),
|
|
61
|
+
uses: (meta(html, "isorite:uses") ?? "").split(/[\s,]+/).filter(Boolean)
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** The artifact with its manifest as given (write_artifact, plan-m23 §3.5): its title, description, author, date, kit
|
|
65
|
+
* and uses set in its head, put in where missing, so they're always right whatever the file said. A file with no
|
|
66
|
+
* head or no document around it is given one. */
|
|
67
|
+
function withManifest(html, m) {
|
|
68
|
+
const esc = (s) => s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
69
|
+
let doc = html.trim();
|
|
70
|
+
if (!/<html\b/i.test(doc)) doc = /<head\b/i.test(doc) ? `<!doctype html>\n<html lang="en">\n${doc}\n</html>` : `<!doctype html>\n<html lang="en">\n<head>\n</head>\n<body>\n${doc.replace(/<\/?body\b[^>]*>/gi, "")}\n</body>\n</html>`;
|
|
71
|
+
if (!/<head\b/i.test(doc)) doc = doc.replace(/<html\b[^>]*>/i, (h) => `${h}\n<head>\n</head>`);
|
|
72
|
+
if (!/^<!doctype/i.test(doc)) doc = `<!doctype html>\n${doc}`;
|
|
73
|
+
const metas = [
|
|
74
|
+
["description", m.description],
|
|
75
|
+
["author", m.author],
|
|
76
|
+
["date", m.date],
|
|
77
|
+
["isorite:kit", m.kit ?? void 0],
|
|
78
|
+
["isorite:uses", m.uses?.join(" ")]
|
|
79
|
+
];
|
|
80
|
+
for (const [name, value] of metas) {
|
|
81
|
+
if (value === void 0) continue;
|
|
82
|
+
const tag = `<meta name="${name}" content="${esc(value)}">`, re = new RegExp(`<meta\\b[^>]*\\bname\\s*=\\s*["']${name.replace(":", "\\:")}["'][^>]*>`, "i");
|
|
83
|
+
doc = re.test(doc) ? doc.replace(re, () => tag) : doc.replace(/<head\b[^>]*>/i, (h) => `${h}\n${tag}`);
|
|
84
|
+
}
|
|
85
|
+
if (m.title !== void 0) {
|
|
86
|
+
const t = `<title>${esc(m.title)}</title>`;
|
|
87
|
+
doc = /<title\b[^>]*>[\s\S]*?<\/title>/i.test(doc) ? doc.replace(/<title\b[^>]*>[\s\S]*?<\/title>/i, () => t) : doc.replace(/<head\b[^>]*>/i, (h) => `${h}\n${t}`);
|
|
88
|
+
}
|
|
89
|
+
if (!/<meta\b[^>]*charset/i.test(doc)) doc = doc.replace(/<head\b[^>]*>/i, (h) => `${h}\n<meta charset="utf-8">`);
|
|
90
|
+
return doc.endsWith("\n") ? doc : doc + "\n";
|
|
91
|
+
}
|
|
92
|
+
/** The kit's parts an artifact uses beyond the core: Plot with a chart, three.js with 3D. */
|
|
93
|
+
function partsUsed(html) {
|
|
94
|
+
const parts = [];
|
|
95
|
+
if (/<iso-chart[\s>]|\bPlot\.\w/.test(html)) parts.push("chart");
|
|
96
|
+
if (/<iso-model[\s>]|<iso-chart3d[\s>]|\bTHREE\.\w/.test(html)) parts.push("model");
|
|
97
|
+
const text = html.replace(/<script\b[\s\S]*?<\/script>/gi, "").replace(/<style\b[\s\S]*?<\/style>/gi, "");
|
|
98
|
+
if (/<iso-math[\s>]|\biso\.math\(|\$\$[\s\S]+?\$\$|\\\(|\$(?=\S)[^$\n]+?(?<=\S)\$(?!\d)/.test(text) || /\biso\.math\(/.test(html)) parts.push("math");
|
|
99
|
+
return parts;
|
|
100
|
+
}
|
|
101
|
+
/** The artifact as one document: the CSP, the kit's themes, components and the parts it uses, inlined at the top of
|
|
102
|
+
* its head (any kit <link> or <script src> it wrote is taken out), with its data too for a standalone export. */
|
|
103
|
+
function assemble(html, kit, opts = {}) {
|
|
104
|
+
const look = opts.look ?? "isorite", ground = opts.ground ?? "lapis";
|
|
105
|
+
const body = html.replace(/<link\b[^>]*\bhref\s*=\s*["'](?:\.\/)?kit\/[^"']*["'][^>]*>\s*/gi, "").replace(/<script\b[^>]*\bsrc\s*=\s*["'](?:\.\/)?kit\/[^"']*["'][^>]*>\s*<\/script>\s*/gi, "");
|
|
106
|
+
const data = Object.entries(opts.data ?? {}).map(([p, v]) => typeof v === "string" ? `<script type="text/plain" data-path="${attr(p)}">${safe(v)}<\/script>` : `<script type="application/octet-stream" data-encoding="base64" data-path="${attr(p)}">${base64(v)}<\/script>`);
|
|
107
|
+
const parts = partsUsed(body).map((p) => `${p === "math" ? `<style>\n${safe(kit["math.css"])}\n</style>\n` : ""}<script>\n${safe(kit[`${p}.js`])}\n<\/script>`);
|
|
108
|
+
const head = [
|
|
109
|
+
CSP,
|
|
110
|
+
`<style data-look="isorite">\n${safe(kit["isorite.css"])}\n</style>`,
|
|
111
|
+
`<style data-look="plain">\n${safe(kit["plain.css"])}\n</style>`,
|
|
112
|
+
`<style>\n${safe(kit["kit.css"])}\n</style>`,
|
|
113
|
+
...data,
|
|
114
|
+
`<script>\n${safe(kit["kit.js"])}\n<\/script>`,
|
|
115
|
+
...parts
|
|
116
|
+
].join("\n");
|
|
117
|
+
return (/<head\b[^>]*>/i.test(body) ? body.replace(/<head\b[^>]*>/i, (h) => `${h}\n${head}\n`) : `<!doctype html><html><head><meta charset="utf-8">\n${head}\n</head><body>${body}</body></html>`).replace(/<html\b([^>]*)>/i, (_, a) => `<html${a.replace(/\s+data-(?:look|ground)\s*=\s*("[^"]*"|'[^']*'|\S+)/gi, "")} data-look="${look}" data-ground="${ground}">`);
|
|
118
|
+
}
|
|
119
|
+
function base64(bytes) {
|
|
120
|
+
let s = "";
|
|
121
|
+
for (let i = 0; i < bytes.length; i += 32768) s += String.fromCharCode(...bytes.subarray(i, i + 32768));
|
|
122
|
+
return btoa(s);
|
|
123
|
+
}
|
|
124
|
+
var KIT_FILES, unescape, CSP, safe, attr;
|
|
125
|
+
var init_assemble = __esmMin((() => {
|
|
126
|
+
KIT_FILES = [
|
|
127
|
+
"kit.js",
|
|
128
|
+
"chart.js",
|
|
129
|
+
"model.js",
|
|
130
|
+
"math.js",
|
|
131
|
+
"math.css",
|
|
132
|
+
"isorite.css",
|
|
133
|
+
"plain.css",
|
|
134
|
+
"kit.css"
|
|
135
|
+
];
|
|
136
|
+
unescape = (s) => s.replace(/"/g, "\"").replace(/'/g, "'").replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&");
|
|
137
|
+
CSP = `<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data: blob:; font-src data:; media-src data: blob:; connect-src 'none'">`;
|
|
138
|
+
safe = (t) => t.replace(/<\/(script|style)/gi, "<\\/$1");
|
|
139
|
+
attr = (s) => s.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
140
|
+
}));
|
|
141
|
+
//#endregion
|
|
142
|
+
//#region packages/kit/src/rules.ts
|
|
143
|
+
/** The files an artifact reads, from its markup and script: data="…", an iso-model's src, iso.data/text/bytes("…"). */
|
|
144
|
+
function readsOf(html) {
|
|
145
|
+
const own = html.replace(/<!--[\s\S]*?-->/g, ""), reads = /* @__PURE__ */ new Set();
|
|
146
|
+
for (const r of own.matchAll(/iso\.(?:data|text|bytes)\(\s*["'`]([^"'`$]+)["'`]/g)) reads.add(r[1]);
|
|
147
|
+
for (const t of own.matchAll(TAG)) {
|
|
148
|
+
const a = attrsOf(t[2]);
|
|
149
|
+
(a.data ?? "").split(/[\s,]+/).filter(Boolean).forEach((f) => reads.add(f));
|
|
150
|
+
if (t[1].toLowerCase() === "iso-model" && a.src) reads.add(a.src);
|
|
151
|
+
}
|
|
152
|
+
return [...reads];
|
|
153
|
+
}
|
|
154
|
+
function checkArtifact(html, ctx = {}) {
|
|
155
|
+
const out = [], m = manifestOf(html);
|
|
156
|
+
const err = (rule, message) => out.push({
|
|
157
|
+
rule,
|
|
158
|
+
level: "error",
|
|
159
|
+
message
|
|
160
|
+
});
|
|
161
|
+
const warn = (rule, message) => out.push({
|
|
162
|
+
rule,
|
|
163
|
+
level: "warning",
|
|
164
|
+
message
|
|
165
|
+
});
|
|
166
|
+
const own = html.replace(/<!--[\s\S]*?-->/g, "");
|
|
167
|
+
if (m.kit === null) err(1, "no <meta name=\"isorite:kit\" content=\"1\">: without it isorite gives it no kit");
|
|
168
|
+
if (!m.title) err(1, "no <title>");
|
|
169
|
+
if (!m.description) err(1, "no <meta name=\"description\" content=\"…\">: one sentence on what it shows");
|
|
170
|
+
const reads = /* @__PURE__ */ new Set();
|
|
171
|
+
for (const r of own.matchAll(/iso\.(?:data|text|bytes)\(\s*["'`]([^"'`$]+)["'`]/g)) reads.add(r[1]);
|
|
172
|
+
const files = (a) => (a.data ?? "").split(/[\s,]+/).filter(Boolean);
|
|
173
|
+
for (const t of own.matchAll(TAG)) {
|
|
174
|
+
const a = attrsOf(t[2]);
|
|
175
|
+
files(a).forEach((f) => reads.add(f));
|
|
176
|
+
if (t[1].toLowerCase() === "iso-model" && a.src) reads.add(a.src);
|
|
177
|
+
}
|
|
178
|
+
for (const r of reads) if (!m.uses.includes(r)) err(2, `it reads ${r}, which isn't in <meta name="isorite:uses">`);
|
|
179
|
+
if (ctx.files) {
|
|
180
|
+
for (const u of m.uses) if (!ctx.files.includes(u)) err(2, `${u} is in isorite:uses but there's no such file beside it${ctx.files.length ? ` (there are: ${ctx.files.join(", ")})` : ""}`);
|
|
181
|
+
}
|
|
182
|
+
if (ctx.uses && [...ctx.uses].sort().join() !== [...m.uses].sort().join()) err(2, `isorite:uses (${m.uses.join(" ") || "none"}) and the uses given (${ctx.uses.join(" ") || "none"}) differ`);
|
|
183
|
+
if (ctx.columns) for (const t of own.matchAll(TAG)) {
|
|
184
|
+
const a = attrsOf(t[2]), fs = files(a), known = fs.map((f) => ctx.columns[f]);
|
|
185
|
+
if (!fs.length || known.some((k) => !k)) continue;
|
|
186
|
+
const have = [...new Set(known.flat()), ...fs.length > 1 ? ["file"] : []];
|
|
187
|
+
for (const k of COLUMN_ATTRS) for (const c of (a[k] ?? "").split(/[\s,]+/).filter(Boolean)) {
|
|
188
|
+
const name = c.replace(/^abs\((.+)\)$/, "$1");
|
|
189
|
+
if (k === "at" && t[1].toLowerCase() !== "iso-chart3d" && !have.includes(name)) continue;
|
|
190
|
+
if (!have.includes(name)) err(3, `<${t[1]} data="${a.data}" ${k}="${a[k]}">: ${a.data} has no column "${name}" (it has ${have.join(", ")})`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
const code = own.replace(/<meta\b[^>]*>/gi, "").replace(/\btone\s*=\s*"[^"]*"/g, "");
|
|
194
|
+
const colour = code.match(/#[0-9a-fA-F]{3,8}\b(?![-\w])|\b(?:rgba?|hsla?|oklch|oklab|lab|lch)\(\s*[\d.]/i) ?? code.match(/\b(?:fill|stroke|fillStyle|strokeStyle|color|background(?:-color)?|shadowColor|border(?:-color)?)\s*[:=]\s*["'`]?(?:white|black|red|blue|green|gray|grey|orange|yellow|purple|pink|navy|teal|silver)\b/i);
|
|
195
|
+
if (colour) err(4, `a colour of its own (${colour[0].trim().slice(0, 40)}): use the theme's, var(--lattice) in CSS or iso.tokens().lattice in script`);
|
|
196
|
+
const font = code.match(/font-family\s*:\s*[^;"}]*|\bfont\s*[:=]\s*["'`]?[^;"'`}]*(?<![.\w-])(?:sans-serif|serif|monospace|Arial|Helvetica|system-ui|Georgia|Times|Courier|Inter|Roboto)\b/i);
|
|
197
|
+
if (font && !/var\(--font|\$\{/.test(font[0])) err(4, `a font of its own (${font[0].slice(0, 50)}): use var(--font-ui), var(--font-text) or iso.tokens().ui`);
|
|
198
|
+
if (/border-radius\s*:/i.test(code)) err(4, "border-radius: the theme decides corners");
|
|
199
|
+
const urls = code.replace(/http:\/\/www\.w3\.org\/[\w/.-]+/g, "").match(/\bhttps?:\/\/[^\s"'`)<]+/);
|
|
200
|
+
if (urls) err(5, `a web address (${urls[0].slice(0, 60)}): an artifact reaches nothing outside the file`);
|
|
201
|
+
if (/<script\b[^>]*\bsrc\s*=/i.test(code.replace(/<script\b[^>]*\bsrc\s*=\s*["'](?:\.\/)?kit\/[^"']*["'][^>]*>/gi, ""))) err(5, "a <script src>: everything is in the file, and the kit is given");
|
|
202
|
+
if (/<link\b[^>]*\bhref\s*=\s*["'](?!(?:\.\/)?kit\/)/i.test(code)) err(5, "a <link href>: everything is in the file, and the kit is given");
|
|
203
|
+
if (/\b(?:fetch|XMLHttpRequest|WebSocket|EventSource|importScripts)\s*\(|\bimport\s*\(/.test(code)) err(5, "fetch, XMLHttpRequest or import(): read files with iso.data(path)");
|
|
204
|
+
if (html.length > 2e5) err(6, `${Math.round(html.length / 1024)} kB, over 200 kB: keep data in a data file, not in the page`);
|
|
205
|
+
const directive = own.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]*>/g, " ").match(/\b(?:drag(?:ging)? to|click (?:here|to|on)|tap to|scroll to|hover (?:to|over)|use the slider|press (?:the|play)|try (?:dragging|moving|clicking))\b[^.]*/i);
|
|
206
|
+
if (directive) warn(7, `text that explains how to use it ("${directive[0].trim().slice(0, 60)}"): say what a thing is, not what to do with it`);
|
|
207
|
+
return out;
|
|
208
|
+
}
|
|
209
|
+
var COLUMN_ATTRS, TAG, attrsOf;
|
|
210
|
+
var init_rules = __esmMin((() => {
|
|
211
|
+
init_assemble();
|
|
212
|
+
COLUMN_ATTRS = [
|
|
213
|
+
"x",
|
|
214
|
+
"y",
|
|
215
|
+
"z",
|
|
216
|
+
"by",
|
|
217
|
+
"column",
|
|
218
|
+
"columns",
|
|
219
|
+
"at"
|
|
220
|
+
];
|
|
221
|
+
TAG = /<(iso-[\w-]+)\b([^>]*)>/gi;
|
|
222
|
+
attrsOf = (s) => Object.fromEntries([...s.matchAll(/([\w-]+)\s*=\s*"([^"]*)"/g)].map((m) => [m[1].toLowerCase(), m[2]]));
|
|
223
|
+
}));
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region packages/kit/src/data.ts
|
|
226
|
+
/** One line of delimited text into fields; quotes may hold the delimiter, newlines and doubled quotes. */
|
|
227
|
+
function* records(text, sep) {
|
|
228
|
+
let field = "", row = [], quoted = false;
|
|
229
|
+
for (let i = 0; i < text.length; i++) {
|
|
230
|
+
const c = text[i];
|
|
231
|
+
if (quoted) {
|
|
232
|
+
if (c === "\"") {
|
|
233
|
+
if (text[i + 1] === "\"") {
|
|
234
|
+
field += "\"";
|
|
235
|
+
i++;
|
|
236
|
+
} else quoted = false;
|
|
237
|
+
} else field += c;
|
|
238
|
+
} else if (c === "\"" && field === "") quoted = true;
|
|
239
|
+
else if (c === sep) {
|
|
240
|
+
row.push(field);
|
|
241
|
+
field = "";
|
|
242
|
+
} else if (c === "\n" || c === "\r") {
|
|
243
|
+
if (c === "\r" && text[i + 1] === "\n") i++;
|
|
244
|
+
row.push(field);
|
|
245
|
+
field = "";
|
|
246
|
+
if (row.length > 1 || row[0] !== "") yield row;
|
|
247
|
+
row = [];
|
|
248
|
+
} else field += c;
|
|
249
|
+
}
|
|
250
|
+
row.push(field);
|
|
251
|
+
if (row.length > 1 || row[0] !== "") yield row;
|
|
252
|
+
}
|
|
253
|
+
function parseDelimited(text, sep = ",") {
|
|
254
|
+
const it = records(text.replace(/^/, ""), sep), head = it.next();
|
|
255
|
+
if (head.done) return [];
|
|
256
|
+
const cols = head.value.map((c) => c.trim());
|
|
257
|
+
const out = [];
|
|
258
|
+
for (const r of it) out.push(Object.fromEntries(cols.map((c, i) => [c, value(r[i] ?? "")])));
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
function parseData(path, text) {
|
|
262
|
+
const ext = path.toLowerCase().split(".").pop();
|
|
263
|
+
if (ext === "json") {
|
|
264
|
+
const j = JSON.parse(text);
|
|
265
|
+
if (Array.isArray(j)) return j;
|
|
266
|
+
const inner = j && typeof j === "object" ? Object.values(j).find(Array.isArray) : null;
|
|
267
|
+
if (inner) return inner;
|
|
268
|
+
throw new Error(`${path}: not an array of rows`);
|
|
269
|
+
}
|
|
270
|
+
if (ext === "jsonl" || ext === "ndjson") return text.split(/\r?\n/).filter((l) => l.trim()).map((l) => JSON.parse(l));
|
|
271
|
+
if (ext === "tsv") return parseDelimited(text, " ");
|
|
272
|
+
if (ext === "csv") return parseDelimited(text, ",");
|
|
273
|
+
throw new Error(`${path}: the kit reads CSV, TSV, JSON and JSON lines`);
|
|
274
|
+
}
|
|
275
|
+
/** Every column any row has, in the order they first appear. */
|
|
276
|
+
function columnsOf(rows) {
|
|
277
|
+
const seen = /* @__PURE__ */ new Set();
|
|
278
|
+
for (const r of rows.slice(0, 1e3)) for (const k of Object.keys(r)) seen.add(k);
|
|
279
|
+
return [...seen];
|
|
280
|
+
}
|
|
281
|
+
/** What read_data tells Iso: enough to build on a file without reading it. */
|
|
282
|
+
function shapeOf(path, rows, head = 5) {
|
|
283
|
+
return {
|
|
284
|
+
path,
|
|
285
|
+
rows: rows.length,
|
|
286
|
+
head: rows.slice(0, head),
|
|
287
|
+
columns: columnsOf(rows).map((name) => {
|
|
288
|
+
const vs = rows.map((r) => r[name]), present = vs.filter((v) => v !== null && v !== void 0 && v !== ""), empty = vs.length - present.length;
|
|
289
|
+
if (!present.length) return {
|
|
290
|
+
name,
|
|
291
|
+
type: "empty",
|
|
292
|
+
empty
|
|
293
|
+
};
|
|
294
|
+
if (present.every((v) => typeof v === "number")) {
|
|
295
|
+
let min = Infinity, max = -Infinity, sum = 0;
|
|
296
|
+
for (const v of present) {
|
|
297
|
+
if (v < min) min = v;
|
|
298
|
+
if (v > max) max = v;
|
|
299
|
+
sum += v;
|
|
300
|
+
}
|
|
301
|
+
return {
|
|
302
|
+
name,
|
|
303
|
+
type: "number",
|
|
304
|
+
min,
|
|
305
|
+
max,
|
|
306
|
+
mean: sum / present.length,
|
|
307
|
+
empty
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
if (present.every((v) => typeof v === "boolean")) return {
|
|
311
|
+
name,
|
|
312
|
+
type: "boolean",
|
|
313
|
+
empty
|
|
314
|
+
};
|
|
315
|
+
const distinct = [...new Set(present.map(String))];
|
|
316
|
+
return {
|
|
317
|
+
name,
|
|
318
|
+
type: "text",
|
|
319
|
+
distinct: distinct.length,
|
|
320
|
+
values: distinct.slice(0, 12),
|
|
321
|
+
empty
|
|
322
|
+
};
|
|
323
|
+
})
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
/** The shape as text, the way Iso reads it. */
|
|
327
|
+
function describeShape(s) {
|
|
328
|
+
const g = (x) => Number.isInteger(x) ? String(x) : String(+x.toPrecision(4));
|
|
329
|
+
const lines = [`${s.path}: ${s.rows} rows, ${s.columns.length} columns`];
|
|
330
|
+
for (const c of s.columns) {
|
|
331
|
+
const empty = c.empty ? `, ${c.empty} empty` : "";
|
|
332
|
+
if (c.type === "number") lines.push(`- ${c.name}: number, ${g(c.min)} to ${g(c.max)} (mean ${g(c.mean)})${empty}`);
|
|
333
|
+
else if (c.type === "text") lines.push(`- ${c.name}: text, ${c.distinct} distinct: ${c.values.join(", ")}${c.distinct > c.values.length ? ", …" : ""}${empty}`);
|
|
334
|
+
else lines.push(`- ${c.name}: ${c.type}${empty}`);
|
|
335
|
+
}
|
|
336
|
+
const cols = s.columns.map((c) => c.name);
|
|
337
|
+
lines.push("First rows:", cols.join(","), ...s.head.map((r) => cols.map((c) => r[c] ?? "").join(",")));
|
|
338
|
+
return lines.join("\n");
|
|
339
|
+
}
|
|
340
|
+
var NUMBER, value;
|
|
341
|
+
var init_data = __esmMin((() => {
|
|
342
|
+
NUMBER = /^[-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?$/;
|
|
343
|
+
value = (s) => s === "" ? null : NUMBER.test(s.trim()) ? +s : s;
|
|
344
|
+
}));
|
|
345
|
+
//#endregion
|
|
346
|
+
//#region packages/kit/src/context.gen.ts
|
|
347
|
+
var REFERENCE, RECIPES;
|
|
348
|
+
var init_context_gen = __esmMin((() => {
|
|
349
|
+
REFERENCE = "# The kit (isorite kit 1)\n\nAn artifact is one HTML file: markup, perhaps a script, and a manifest in its `<head>`. isorite gives it the kit: the look (every colour, font and corner), the elements below, and `window.iso`. Write what it shows and does, never how it looks: no colour, font family or border-radius of your own.\n\n## The file\n\n```html\n<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Title</title>\n<meta name=\"description\" content=\"One sentence: what it shows, and what can be done with it.\">\n<meta name=\"isorite:kit\" content=\"1\">\n<meta name=\"isorite:uses\" content=\"runs.csv other.csv\">\n<style>/* layout only, if needed */</style>\n</head>\n<body>\n<iso-page>\n <header><h1>Title</h1><p>The question it answers, in a sentence or two.</p></header>\n <iso-grid>\n <iso-panel class=\"wide\"><h2>Name <span>a short reading</span></h2> … </iso-panel>\n <iso-panel><h2>…</h2> … </iso-panel>\n </iso-grid>\n</iso-page>\n</body>\n</html>\n```\n\n`write_artifact` fills in the title, description and manifest from its arguments, so they are always right.\n\n## Layout\n\n- `<iso-grid>`: panels in two columns (`style=\"--cols: 3\"` for three); `<iso-panel class=\"wide\">` spans the row.\n- `<iso-panel>`: its first child is `<h2>`; a `<span>` in the `h2` is a soft aside. A `<p>` in a panel is a caption: what the panel shows, in a sentence.\n- `<iso-row>`: controls side by side. `<iso-stats>`: big numbers side by side.\n\n## Data, with no script\n\nEvery element with `data=\"file.csv\"` reads it itself. Several files are one table: `data=\"euler.csv leapfrog.csv\"` adds a column `file` (`euler`, `leapfrog`) to compare them with `by=\"file\"`. `where=\"integrator=leapfrog\"` keeps some rows; `where=\"t<=$t\"` compares with a control's value. `abs(col)` is a column's size.\n\n- `<iso-chart data=\"f.csv\" x=\"t\" y=\"energy\" by=\"body\">`: a chart.\n - `type`: `line` (default), `dot`, `bar`, `area`, `step`.\n - `y` may name several columns: `y=\"kinetic potential total\"`, each its own line.\n - `by`: a column whose values are the series.\n - `x-scale`, `y-scale`: `linear` or `log`, or `$name` to follow a control (`y-scale=\"$scale\"`).\n - `x-label`, `y-label`: the axis with its unit (`\"time (s)\"`). `height` in pixels (260).\n - `at=\"t\"`: follows the control `t`. A rule where x is its value; or, when the data has a column `t` and x is another column (a path in x and y over time), a dot on each series at the row whose `t` is nearest it.\n- `<iso-table data=\"f.csv\" columns=\"a b c\" limit=\"50\">`: the rows.\n- `<iso-stat data=\"f.csv\" column=\"err\" of=\"max\" label=\"worst error\" tone=\"gold\">`: one number. `of`: `min`, `max`, `mean`, `sum`, `count`, `first`, `last`. Tones: `gold`, `blue`, `green`, `rose`. Without `data`, set it from a script: `el.value = 0.0023`.\n- `<iso-chart3d data=\"f.csv\" x=\"x\" y=\"y\" z=\"z\" by=\"body\" at=\"t\">`: points or paths in 3D, turned by dragging.\n- `<iso-model src=\"part.glb\">`: a 3D model (glTF, GLB, STL, OBJ), turned by dragging. List it in isorite:uses.\n\n## Text and maths\n\n- A `<p>` in a panel is its caption. Maths is written as in a note: `$E = \\tfrac12 m v^2$` inline, `$$…$$` on a line of its own; the kit sets it. `<iso-math>\\frac{a}{b}</iso-math>` is a formula alone.\n- Never name the theme's colours in text (\"the lattice line\"): a chart's legend says which series is which.\n\n## Controls\n\nEach writes its value to `iso.state[name]`; elements that name it (`at=\"t\"`, `$t`) follow it.\n\n- `<iso-slider name=\"dt\" label=\"time step\" min=\"0.001\" max=\"0.05\" step=\"0.001\" value=\"0.01\" digits=\"3\">`\n- `<iso-select name=\"scale\" label=\"scale\" options=\"linear log\">`: one of several.\n- `<iso-toggle name=\"softened\" label=\"softened\" value=\"true\">`: on or off.\n- `<iso-play name=\"t\" min=\"0\" max=\"12\" speed=\"1.5\">`: plays `iso.state.t` from min to max, and pauses.\n- `<iso-note href=\"physics/softening\">softening</iso-note>`: opens a note in isorite.\n\n## Script, when markup isn't enough\n\nFor a simulation or anything computed. Put it at the end of the body.\n\n- `const rows = await iso.data(\"f.csv\")`: the rows as objects, numbers as numbers.\n- `iso.state.name`, `iso.watch((name) => …)`: the shared state; `watch` runs after any change.\n- `iso.loop((dt, t) => …)`: runs every frame (dt in seconds), paused when hidden. Returns a stop function.\n- `<iso-canvas height=\"320\" id=\"sim\">` and `document.getElementById(\"sim\").draw = (ctx, w, h, t) => { … }`: draw on a canvas sized to its panel; call `.redraw()` after a change. `t` is the theme: `t.text`, `t.soft`, `t.faint`, `t.line`, `t.lattice` (blue), `t.growth` (gold), `t.malachite` (green), `t.rose`, `t.series[i]`, `t.ui` (the font).\n- A chart of what the script computes: `chart.rows = [{ t: 0, energy: -1.3 }, …]` on an `<iso-chart>` with no `data`.\n- `iso.format(x)`: a number to read (3 figures, 2.3×10⁻³). `iso.tokens()`: the theme, as `t` above.\n- In CSS: `var(--text)`, `var(--text-soft)`, `var(--text-faint)`, `var(--line)`, `var(--lattice)`, `var(--growth)`, `var(--malachite)`, `var(--rose)`, `var(--font-ui)`, `var(--font-text)`.\n\n## Rules (write_artifact checks them, and refuses a file that breaks one)\n\n1. Every file read (`data=\"…\"`, `src=\"…\"`, `iso.data(\"…\")`) is in `uses`, and exists: call `read_data` first.\n2. Every column named is in its file, spelt as `read_data` gives it.\n3. No colour, font family or border-radius of its own: no `#hex`, `rgb(…)`, `white`, `Arial`. Use the tokens.\n4. No network: no `http…` address, no `<script src>`, no `fetch`. The kit is given; everything else is in the file.\n5. Under 200 kB: data stays in data files.\n6. No text that tells how to use it (\"drag to rotate\", \"click here\"): a label says what a thing is.\n\n## What good is\n\nEvery panel answers a question about the data, with a caption saying what to see. Numbers are read out. Axes say what they measure, with units. Controls change what is drawn at once. Nothing is decoration.\n";
|
|
350
|
+
RECIPES = {
|
|
351
|
+
"dashboard": {
|
|
352
|
+
"about": "a dashboard of one data file: numbers, charts and a table, in markup only",
|
|
353
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>The pendulum's period</title>\n<meta name=\"description\" content=\"Forty timed swings at eight lengths: how the period grows with length, how close it comes to 2π√(L/g), and how much the trials scatter.\">\n<meta name=\"isorite:kit\" content=\"1\">\n<meta name=\"isorite:uses\" content=\"pendulum.csv\">\n</head>\n<body>\n<!-- Recipe 1: a dashboard of one data file, in markup only. Change: the file, the numbers, the charts' columns, the\n captions. Every column named here must be one read_data gave. -->\n<iso-page>\n <header>\n <h1>The pendulum's period</h1>\n <p>Does a longer pendulum swing slower, and by as much as theory says?</p>\n </header>\n <iso-grid>\n <iso-panel class=\"wide\">\n <h2>In numbers <span>40 swings</span></h2>\n <iso-stats>\n <iso-stat data=\"pendulum.csv\" column=\"period_s\" of=\"min\" label=\"shortest period (s)\" tone=\"blue\"></iso-stat>\n <iso-stat data=\"pendulum.csv\" column=\"period_s\" of=\"max\" label=\"longest period (s)\" tone=\"gold\"></iso-stat>\n <iso-stat data=\"pendulum.csv\" column=\"temp_c\" of=\"mean\" label=\"mean temperature (°C)\"></iso-stat>\n <iso-stat data=\"pendulum.csv\" column=\"trial\" of=\"count\" label=\"swings timed\"></iso-stat>\n </iso-stats>\n </iso-panel>\n <iso-panel>\n <h2>Period against length</h2>\n <iso-chart data=\"pendulum.csv\" type=\"dot\" x=\"length_m\" y=\"period_s\" x-label=\"length (m)\" y-label=\"period (s)\"></iso-chart>\n <p>The period rises with length, more slowly as the pendulum gets longer: a square root.</p>\n </iso-panel>\n <iso-panel>\n <h2>On log axes <span>slope ½ is √L</span></h2>\n <iso-chart data=\"pendulum.csv\" type=\"dot\" x=\"length_m\" y=\"period_s\" x-scale=\"log\" y-scale=\"log\" x-label=\"length (m)\" y-label=\"period (s)\"></iso-chart>\n <p>A straight line here is a power law; its slope, near one half, is the exponent.</p>\n </iso-panel>\n <iso-panel class=\"wide\">\n <h2>Each trial <span>the scatter is the stopwatch</span></h2>\n <iso-chart data=\"pendulum.csv\" type=\"line\" x=\"length_m\" y=\"period_s\" by=\"trial\" height=\"200\" x-label=\"length (m)\" y-label=\"period (s)\"></iso-chart>\n </iso-panel>\n <iso-panel class=\"wide\">\n <h2>The measurements</h2>\n <iso-table data=\"pendulum.csv\" columns=\"length_m trial period_s temp_c\" limit=\"40\"></iso-table>\n </iso-panel>\n </iso-grid>\n</iso-page>\n</body>\n</html>\n"
|
|
354
|
+
},
|
|
355
|
+
"compare": {
|
|
356
|
+
"about": "two data files with the same columns, compared: together on one chart, and side by side",
|
|
357
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>A lid against an open mug</title>\n<meta name=\"description\" content=\"Two mugs of the same coffee cooling for an hour, one with a lid: their temperature together, the gap between them, and where each stands at a chosen minute.\">\n<meta name=\"isorite:kit\" content=\"1\">\n<meta name=\"isorite:uses\" content=\"mug-open.csv mug-lid.csv\">\n</head>\n<body>\n<!-- Recipe 2: comparing two data files with the same columns. data=\"a.csv b.csv\" reads both as one table with a\n column `file` (\"mug-open\", \"mug-lid\"), so by=\"file\" draws one series each and where=\"file=…\" picks one.\n Change: the files, the columns, the captions. -->\n<iso-page>\n <header>\n <h1>A lid against an open mug</h1>\n <p>How much longer does a lid keep coffee hot?</p>\n </header>\n <iso-grid>\n <iso-panel class=\"wide\">\n <h2>At one minute</h2>\n <iso-row>\n <iso-slider name=\"t\" label=\"minute\" min=\"0\" max=\"60\" step=\"2\" value=\"20\" digits=\"0\"></iso-slider>\n <iso-select name=\"scale\" label=\"temperature scale\" options=\"linear log\"></iso-select>\n </iso-row>\n <iso-stats>\n <iso-stat data=\"mug-open.csv\" column=\"temp_c\" of=\"last\" where=\"t_min<=$t\" label=\"open (°C)\" tone=\"blue\"></iso-stat>\n <iso-stat data=\"mug-lid.csv\" column=\"temp_c\" of=\"last\" where=\"t_min<=$t\" label=\"with a lid (°C)\" tone=\"gold\"></iso-stat>\n <iso-stat data=\"mug-open.csv\" column=\"temp_c\" of=\"last\" label=\"open after the hour (°C)\"></iso-stat>\n <iso-stat data=\"mug-lid.csv\" column=\"temp_c\" of=\"last\" label=\"lid after the hour (°C)\"></iso-stat>\n </iso-stats>\n </iso-panel>\n <iso-panel class=\"wide\">\n <h2>Both, cooling</h2>\n <iso-chart data=\"mug-open.csv mug-lid.csv\" x=\"t_min\" y=\"temp_c\" by=\"file\" at=\"t\" y-scale=\"$scale\" x-label=\"time (min)\" y-label=\"temperature (°C)\" height=\"300\"></iso-chart>\n <p>Both fall fast at first and slower as they near the room's 21 °C; the lid halves the early loss.</p>\n </iso-panel>\n <iso-panel>\n <h2>Open</h2>\n <iso-table data=\"mug-open.csv\" limit=\"12\"></iso-table>\n </iso-panel>\n <iso-panel>\n <h2>With a lid</h2>\n <iso-table data=\"mug-lid.csv\" limit=\"12\"></iso-table>\n </iso-panel>\n </iso-grid>\n</iso-page>\n</body>\n</html>\n"
|
|
358
|
+
},
|
|
359
|
+
"simulation": {
|
|
360
|
+
"about": "a simulation with parameters, play and reset, drawn on a canvas, with a live chart (a script)",
|
|
361
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>A damped spring</title>\n<meta name=\"description\" content=\"A mass on a spring, integrated live: change the stiffness and the damping and see the motion, the energy draining away, and the period against theory's.\">\n<meta name=\"isorite:kit\" content=\"1\">\n<meta name=\"isorite:uses\" content=\"\">\n<style>\n /* Layout only. */\n .readout { margin-top: 12px; }\n</style>\n</head>\n<body>\n<!-- Recipe 3: a simulation with parameters and play. The parts to change are marked CHANGE: the state (what the\n model is), step() (how it moves), draw() (what's on the canvas), and what's measured. The rest stays. -->\n<iso-page>\n <header>\n <h1>A damped spring</h1>\n <p>How do stiffness and damping shape an oscillation?</p>\n </header>\n <iso-grid>\n <iso-panel class=\"wide\">\n <h2>The model</h2>\n <iso-row>\n <iso-slider name=\"k\" label=\"stiffness k (N/m)\" min=\"1\" max=\"40\" step=\"0.5\" value=\"10\" digits=\"1\"></iso-slider>\n <iso-slider name=\"c\" label=\"damping c (kg/s)\" min=\"0\" max=\"2\" step=\"0.05\" value=\"0.3\" digits=\"2\"></iso-slider>\n </iso-row>\n <iso-row>\n <iso-toggle name=\"playing\" label=\"play\" value=\"true\"></iso-toggle>\n <button type=\"button\" class=\"iso-button\" id=\"reset\">reset</button>\n </iso-row>\n <iso-stats class=\"readout\">\n <iso-stat id=\"time\" label=\"time (s)\"></iso-stat>\n <iso-stat id=\"energy\" label=\"energy left\" tone=\"gold\"></iso-stat>\n <iso-stat id=\"period\" label=\"period, theory (s)\" tone=\"blue\"></iso-stat>\n </iso-stats>\n </iso-panel>\n <iso-panel>\n <h2>The mass</h2>\n <iso-canvas id=\"scene\" height=\"300\"></iso-canvas>\n </iso-panel>\n <iso-panel>\n <h2>Energy <span>a share of the start</span></h2>\n <iso-chart id=\"chart\" x=\"t\" y=\"energy\" x-label=\"time (s)\" y-label=\"energy left\" height=\"300\"></iso-chart>\n <p>With no damping the energy stays; damping drains it, faster the larger c is.</p>\n </iso-panel>\n </iso-grid>\n</iso-page>\n<script>\n // CHANGE: the model's state and its start.\n const m = 1;\n let s, history;\n function reset() { s = { t: 0, x: 1, v: 0 }; history = []; }\n reset();\n const energy = () => 0.5 * m * s.v * s.v + 0.5 * iso.state.k * s.x * s.x;\n let e0 = energy();\n\n // CHANGE: one step of the model (here semi-implicit Euler, small steps).\n function step(dt) {\n const a = (-iso.state.k * s.x - iso.state.c * s.v) / m;\n s.v += a * dt;\n s.x += s.v * dt;\n s.t += dt;\n }\n\n // CHANGE: what's drawn. t is the theme: t.line, t.text, t.soft, t.faint, t.lattice, t.growth, t.ui.\n const scene = document.getElementById(\"scene\");\n scene.draw = (ctx, w, h, t) => {\n const cx = w / 2, y0 = 24, rest = h * 0.5, y = rest + s.x * h * 0.3;\n ctx.strokeStyle = t.line; ctx.lineWidth = 1;\n ctx.beginPath(); ctx.moveTo(cx - 60, y0); ctx.lineTo(cx + 60, y0); ctx.stroke();\n ctx.setLineDash([3, 4]); ctx.beginPath(); ctx.moveTo(cx - 80, rest); ctx.lineTo(cx + 80, rest); ctx.stroke(); ctx.setLineDash([]);\n // The spring, as a zigzag from the ceiling to the mass.\n ctx.strokeStyle = t.soft; ctx.lineWidth = 1.5; ctx.beginPath(); ctx.moveTo(cx, y0);\n for (let i = 1; i < 16; i++) ctx.lineTo(cx + (i % 2 ? 12 : -12), y0 + (y - 20 - y0) * i / 16);\n ctx.lineTo(cx, y - 20); ctx.stroke();\n ctx.fillStyle = t.lattice; ctx.fillRect(cx - 20, y - 20, 40, 40);\n ctx.fillStyle = t.faint; ctx.font = `11px ${t.ui}`; ctx.fillText(\"rest\", cx + 84, rest + 4);\n };\n\n // Measured, and shown: the readouts and the chart, a few times a second.\n const chart = document.getElementById(\"chart\");\n let since = 0;\n function show() {\n document.getElementById(\"time\").value = s.t.toFixed(1);\n document.getElementById(\"energy\").value = iso.format(energy() / e0);\n document.getElementById(\"period\").value = iso.format(2 * Math.PI * Math.sqrt(m / iso.state.k));\n chart.rows = history;\n }\n\n iso.loop((dt) => {\n if (!iso.state.playing) return;\n for (let i = 0; i < 20; i++) step(dt / 20);\n scene.redraw();\n since += dt;\n if (since > 0.1) { since = 0; history.push({ t: +s.t.toFixed(2), energy: energy() / e0 }); if (history.length > 600) history.shift(); show(); }\n });\n iso.watch((name) => { if (name === \"k\") { e0 = energy(); } show(); scene.redraw(); });\n document.getElementById(\"reset\").addEventListener(\"click\", () => { reset(); e0 = energy(); show(); scene.redraw(); });\n show();\n<\/script>\n</body>\n</html>\n"
|
|
362
|
+
},
|
|
363
|
+
"explorable": {
|
|
364
|
+
"about": "an explorable explanation: inputs, numbers derived from them, and linked views (a script)",
|
|
365
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>Why a fast wheel turns backwards</title>\n<meta name=\"description\" content=\"Sample a sine too slowly and a slower one appears: choose the signal's frequency and the sampling rate, and see the samples, the wave they seem to come from, and where every frequency lands.\">\n<meta name=\"isorite:kit\" content=\"1\">\n<meta name=\"isorite:uses\" content=\"\">\n</head>\n<body>\n<!-- Recipe 4: an explorable explanation. An input, numbers derived from it, and linked views that all follow the same\n state; captions say what to see. CHANGE: the inputs (sliders), derive() (the numbers), the drawing, and the\n chart's rows. Keep: every view redrawn from iso.state in one place, update(). -->\n<iso-page>\n <header>\n <h1>Why a fast wheel turns backwards</h1>\n <p>Sampled too slowly, a signal is indistinguishable from a slower one. Which one, and when?</p>\n </header>\n <iso-grid>\n <iso-panel class=\"wide\">\n <h2>The signal and the sampling</h2>\n <iso-row>\n <iso-slider name=\"f\" label=\"signal (Hz)\" min=\"0.2\" max=\"12\" step=\"0.1\" value=\"7\" digits=\"1\"></iso-slider>\n <iso-slider name=\"fs\" label=\"samples per second\" min=\"2\" max=\"24\" step=\"1\" value=\"8\" digits=\"0\"></iso-slider>\n </iso-row>\n <iso-stats>\n <iso-stat id=\"nyquist\" label=\"Nyquist limit (Hz)\" tone=\"blue\"></iso-stat>\n <iso-stat id=\"apparent\" label=\"what the samples show (Hz)\" tone=\"gold\"></iso-stat>\n <iso-stat id=\"verdict\" label=\"faithful?\"></iso-stat>\n </iso-stats>\n </iso-panel>\n <iso-panel class=\"wide\">\n <h2>One second <span>the signal, its samples, and the slower wave through them</span></h2>\n <iso-canvas id=\"wave\" height=\"260\"></iso-canvas>\n <p>Gold dots are the samples. Above the Nyquist limit the blue wave, slower, passes through every one of them as\n well as the true signal does: from the samples alone, the two can't be told apart.</p>\n </iso-panel>\n <iso-panel class=\"wide\">\n <h2>Where every frequency lands</h2>\n <iso-chart id=\"fold\" x=\"signal\" y=\"apparent\" at=\"f\" x-label=\"signal (Hz)\" y-label=\"frequency the samples show (Hz)\" height=\"220\"></iso-chart>\n <p>The frequency seen climbs with the signal's up to half the sampling rate, then folds back down:\n $f_\\text{seen} = |f - f_s \\operatorname{round}(f / f_s)|$. See <iso-note href=\"signals/sampling\">sampling</iso-note>.</p>\n </iso-panel>\n </iso-grid>\n</iso-page>\n<script>\n // CHANGE: the numbers derived from the inputs.\n function derive(f, fs) {\n const nyquist = fs / 2;\n const folded = Math.abs(f - fs * Math.round(f / fs));\n return { nyquist, apparent: folded, faithful: f < nyquist };\n }\n\n // CHANGE: the drawing. t is the theme (t.line, t.soft, t.faint, t.lattice, t.growth, t.ui).\n const wave = document.getElementById(\"wave\");\n wave.draw = (ctx, w, h, t) => {\n const { f, fs } = iso.state, d = derive(f, fs), mid = h / 2, amp = h * 0.38;\n const X = (s) => 40 + s * (w - 60), Y = (v) => mid - v * amp;\n ctx.strokeStyle = t.line; ctx.lineWidth = 1;\n ctx.beginPath(); ctx.moveTo(X(0), mid); ctx.lineTo(X(1), mid); ctx.stroke();\n const curve = (freq, colour, width) => {\n ctx.strokeStyle = colour; ctx.lineWidth = width; ctx.beginPath();\n for (let i = 0; i <= 600; i++) { const s = i / 600, v = Math.sin(2 * Math.PI * freq * s); i ? ctx.lineTo(X(s), Y(v)) : ctx.moveTo(X(s), Y(v)); }\n ctx.stroke();\n };\n curve(f, t.soft, 1);\n // The slower wave through the same samples: the signal less the nearest whole number of sampling rates.\n if (!d.faithful) curve(f - fs * Math.round(f / fs), t.lattice, 2);\n ctx.fillStyle = t.growth;\n for (let k = 0; k <= fs; k++) { const s = k / fs; ctx.beginPath(); ctx.arc(X(s), Y(Math.sin(2 * Math.PI * f * s)), 4, 0, 2 * Math.PI); ctx.fill(); }\n ctx.fillStyle = t.faint; ctx.font = `11px ${t.ui}`; ctx.fillText(\"0 s\", X(0) - 8, h - 6); ctx.fillText(\"1 s\", X(1) - 8, h - 6);\n };\n\n // One place redraws every view from the state.\n const fold = document.getElementById(\"fold\");\n function update() {\n const { f, fs } = iso.state, d = derive(f, fs);\n document.getElementById(\"nyquist\").value = iso.format(d.nyquist);\n document.getElementById(\"apparent\").value = iso.format(d.apparent);\n document.getElementById(\"verdict\").value = d.faithful ? \"yes\" : \"no: aliased\";\n // CHANGE: the chart's rows, computed from the state.\n const rows = [];\n for (let s = 0; s <= 12; s += 0.05) rows.push({ signal: +s.toFixed(2), apparent: derive(s, fs).apparent });\n fold.rows = rows;\n wave.redraw();\n }\n iso.watch(update);\n update();\n<\/script>\n</body>\n</html>\n"
|
|
366
|
+
},
|
|
367
|
+
"viewer": {
|
|
368
|
+
"about": "a 3D model's viewer (glTF, GLB, STL, OBJ), with what it is beside it",
|
|
369
|
+
"html": "<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>The corner bracket</title>\n<meta name=\"description\" content=\"The printed corner bracket in 3D, with its dimensions and the notes on how it was designed and tested.\">\n<meta name=\"isorite:kit\" content=\"1\">\n<meta name=\"isorite:uses\" content=\"bracket.stl\">\n</head>\n<body>\n<!-- Recipe 5: a 3D model's viewer, with what it is beside it. iso-model shows glTF, GLB, STL and OBJ; list the file\n in isorite:uses. CHANGE: the model's file, the numbers, the notes it links to. -->\n<iso-page>\n <header>\n <h1>The corner bracket</h1>\n <p>The part as printed: an L of two plates, 6 cm by 5 cm.</p>\n </header>\n <iso-grid style=\"--cols: 3\">\n <iso-panel class=\"wide\">\n <h2>The part <span>bracket.stl</span></h2>\n <iso-model src=\"bracket.stl\" height=\"380\"></iso-model>\n </iso-panel>\n <iso-panel>\n <h2>Size</h2>\n <iso-stats>\n <iso-stat label=\"long arm (mm)\" value=\"60\" tone=\"blue\"></iso-stat>\n <iso-stat label=\"short arm (mm)\" value=\"50\"></iso-stat>\n <iso-stat label=\"thickness (mm)\" value=\"10\"></iso-stat>\n </iso-stats>\n </iso-panel>\n <iso-panel>\n <h2>Printed</h2>\n <iso-stats>\n <iso-stat label=\"infill\" value=\"40%\"></iso-stat>\n <iso-stat label=\"mass (g)\" value=\"31\" tone=\"gold\"></iso-stat>\n </iso-stats>\n </iso-panel>\n <iso-panel>\n <h2>Notes</h2>\n <p><iso-note href=\"design/bracket-loads\">The loads it takes</iso-note>, and\n <iso-note href=\"design/print-settings\">how it was printed</iso-note>.</p>\n </iso-panel>\n </iso-grid>\n</iso-page>\n</body>\n</html>\n"
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
}));
|
|
373
|
+
//#endregion
|
|
374
|
+
//#region packages/kit/src/index.ts
|
|
375
|
+
var init_src$2 = __esmMin((() => {
|
|
376
|
+
init_assemble();
|
|
377
|
+
init_rules();
|
|
378
|
+
init_data();
|
|
379
|
+
init_context_gen();
|
|
380
|
+
}));
|
|
381
|
+
//#endregion
|
|
46
382
|
//#region packages/core/src/text.ts
|
|
47
383
|
/** Compare by code point, as Python sorts strings. */
|
|
48
384
|
function cmp(a, b) {
|
|
@@ -488,7 +824,7 @@ function pictureSize(bytes, type) {
|
|
|
488
824
|
} catch {}
|
|
489
825
|
return null;
|
|
490
826
|
}
|
|
491
|
-
var TYPES$1, pictureType;
|
|
827
|
+
var TYPES$1, pictureType, OTHER, fileType;
|
|
492
828
|
var init_picture = __esmMin((() => {
|
|
493
829
|
TYPES$1 = {
|
|
494
830
|
png: "image/png",
|
|
@@ -503,6 +839,36 @@ var init_picture = __esmMin((() => {
|
|
|
503
839
|
const dot = path.lastIndexOf(".");
|
|
504
840
|
return dot < 0 || path.lastIndexOf("/") > dot ? null : TYPES$1[path.slice(dot + 1).toLowerCase()] ?? null;
|
|
505
841
|
};
|
|
842
|
+
OTHER = {
|
|
843
|
+
html: "text/html",
|
|
844
|
+
htm: "text/html",
|
|
845
|
+
csv: "text/csv",
|
|
846
|
+
tsv: "text/tab-separated-values",
|
|
847
|
+
json: "application/json",
|
|
848
|
+
jsonl: "application/jsonl",
|
|
849
|
+
ndjson: "application/x-ndjson",
|
|
850
|
+
txt: "text/plain",
|
|
851
|
+
tex: "text/plain",
|
|
852
|
+
pdf: "application/pdf",
|
|
853
|
+
glb: "model/gltf-binary",
|
|
854
|
+
gltf: "model/gltf+json",
|
|
855
|
+
stl: "model/stl",
|
|
856
|
+
obj: "model/obj",
|
|
857
|
+
zip: "application/zip",
|
|
858
|
+
parquet: "application/vnd.apache.parquet",
|
|
859
|
+
arrow: "application/vnd.apache.arrow.file",
|
|
860
|
+
npy: "application/octet-stream",
|
|
861
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
862
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
863
|
+
epub: "application/epub+zip",
|
|
864
|
+
odt: "application/vnd.oasis.opendocument.text"
|
|
865
|
+
};
|
|
866
|
+
fileType = (path) => {
|
|
867
|
+
const dot = path.lastIndexOf(".");
|
|
868
|
+
if (dot < 0 || path.lastIndexOf("/") > dot) return null;
|
|
869
|
+
const ext = path.slice(dot + 1).toLowerCase();
|
|
870
|
+
return ext === "md" ? null : pictureType(path) ?? OTHER[ext] ?? "application/octet-stream";
|
|
871
|
+
};
|
|
506
872
|
}));
|
|
507
873
|
//#endregion
|
|
508
874
|
//#region packages/core/src/kinds.ts
|
|
@@ -613,16 +979,24 @@ var init_kinds = __esmMin((() => {
|
|
|
613
979
|
function comparePaths(a, b) {
|
|
614
980
|
return cmpTuple(a.split("/"), b.split("/"));
|
|
615
981
|
}
|
|
616
|
-
var AGENTS_FILE,
|
|
982
|
+
var AGENTS_FILE, KINDS$2, citeKind, isEntry, Concept, Directory, SCHEME, LOG_DATE, Bundle, oneLine, escape;
|
|
617
983
|
var init_bundle = __esmMin((() => {
|
|
618
984
|
init_frontmatter();
|
|
619
985
|
init_markdown();
|
|
620
986
|
init_kinds();
|
|
621
987
|
init_text();
|
|
622
988
|
AGENTS_FILE = "AGENTS.md";
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
989
|
+
KINDS$2 = [
|
|
990
|
+
["artifact", /\.html?$/i],
|
|
991
|
+
["image", /\.(png|jpe?g|gif|svg|webp|avif)$/i],
|
|
992
|
+
["data", /\.(csv|tsv|json|jsonl|ndjson|parquet|arrow|feather|npy|npz|xlsx|xls|h5|hdf5|nc)$/i],
|
|
993
|
+
["document", /\.(pdf|docx?|odt|rtf|epub|pptx?|odp|txt|tex)$/i]
|
|
994
|
+
];
|
|
995
|
+
citeKind = (path) => {
|
|
996
|
+
const name = basename$1(path);
|
|
997
|
+
if (/\.md$/i.test(name) || !/\.[^.]+$/.test(name.replace(/^\./, ""))) return null;
|
|
998
|
+
return KINDS$2.find(([, re]) => re.test(name))?.[0] ?? "misc";
|
|
999
|
+
};
|
|
626
1000
|
isEntry = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
627
1001
|
Concept = class {
|
|
628
1002
|
id;
|
|
@@ -1196,6 +1570,79 @@ var init_source = __esmMin((() => {
|
|
|
1196
1570
|
};
|
|
1197
1571
|
}));
|
|
1198
1572
|
//#endregion
|
|
1573
|
+
//#region packages/core/src/unwrap.ts
|
|
1574
|
+
function unwrap(text) {
|
|
1575
|
+
const lines = text.split("\n"), out = [];
|
|
1576
|
+
let i = 0, fence = "", maths = false;
|
|
1577
|
+
/** What the last line written may be continued by: prose, a quoted line, or nothing. */
|
|
1578
|
+
let open = "";
|
|
1579
|
+
if (lines[0] === "---") {
|
|
1580
|
+
const end = lines.indexOf("---", 1);
|
|
1581
|
+
if (end > 0) {
|
|
1582
|
+
out.push(...lines.slice(0, end + 1));
|
|
1583
|
+
i = end + 1;
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
for (; i < lines.length; i++) {
|
|
1587
|
+
const l = lines[i], t = l.trim();
|
|
1588
|
+
if (fence) {
|
|
1589
|
+
out.push(l);
|
|
1590
|
+
if (t.startsWith(fence)) fence = "";
|
|
1591
|
+
continue;
|
|
1592
|
+
}
|
|
1593
|
+
if (maths) {
|
|
1594
|
+
out.push(l);
|
|
1595
|
+
if (t.endsWith("$$")) maths = false;
|
|
1596
|
+
continue;
|
|
1597
|
+
}
|
|
1598
|
+
if (t.startsWith("```") || t.startsWith("~~~")) {
|
|
1599
|
+
fence = t.slice(0, 3);
|
|
1600
|
+
out.push(l);
|
|
1601
|
+
open = "";
|
|
1602
|
+
continue;
|
|
1603
|
+
}
|
|
1604
|
+
if (t.startsWith("$$") && !t.slice(2).includes("$$")) {
|
|
1605
|
+
maths = true;
|
|
1606
|
+
out.push(l);
|
|
1607
|
+
open = "";
|
|
1608
|
+
continue;
|
|
1609
|
+
}
|
|
1610
|
+
if (!t) {
|
|
1611
|
+
out.push(l);
|
|
1612
|
+
open = "";
|
|
1613
|
+
continue;
|
|
1614
|
+
}
|
|
1615
|
+
const hard = /( {2}|\\)$/.test(out.at(-1) ?? "");
|
|
1616
|
+
if (APART.test(l)) {
|
|
1617
|
+
if (open === "quote" && QUOTED.test(l) && !/^\s*>\s*([-*+]|\d+[.)]|#)/.test(l) && !hard) {
|
|
1618
|
+
out[out.length - 1] += " " + l.replace(/^\s*>\s?/, "").trim();
|
|
1619
|
+
continue;
|
|
1620
|
+
}
|
|
1621
|
+
out.push(l);
|
|
1622
|
+
open = QUOTED.test(l) ? "quote" : "";
|
|
1623
|
+
continue;
|
|
1624
|
+
}
|
|
1625
|
+
if (ITEM.test(l)) {
|
|
1626
|
+
out.push(l);
|
|
1627
|
+
open = "prose";
|
|
1628
|
+
continue;
|
|
1629
|
+
}
|
|
1630
|
+
if (open === "prose" && !hard) {
|
|
1631
|
+
out[out.length - 1] += " " + t;
|
|
1632
|
+
continue;
|
|
1633
|
+
}
|
|
1634
|
+
out.push(l);
|
|
1635
|
+
open = open === "" && /^( {4}|\t)/.test(l) ? "" : "prose";
|
|
1636
|
+
}
|
|
1637
|
+
return out.join("\n");
|
|
1638
|
+
}
|
|
1639
|
+
var ITEM, APART, QUOTED;
|
|
1640
|
+
var init_unwrap = __esmMin((() => {
|
|
1641
|
+
ITEM = /^\s*([-*+]|\d+[.)])\s+/;
|
|
1642
|
+
APART = /^\s*(#{1,6}\s|\||<|```|~~~|\$\$|>|---\s*$|\*\*\*\s*$|___\s*$|\[[^\]]+\]:\s)/;
|
|
1643
|
+
QUOTED = /^\s*>\s?\S/;
|
|
1644
|
+
}));
|
|
1645
|
+
//#endregion
|
|
1199
1646
|
//#region packages/core/src/search.ts
|
|
1200
1647
|
function stem(token) {
|
|
1201
1648
|
for (const suffix of ["ness", "ing"]) if (token.endsWith(suffix) && token.length - suffix.length >= 4) return token.slice(0, -suffix.length);
|
|
@@ -1719,16 +2166,21 @@ function written$1(note, p, region) {
|
|
|
1719
2166
|
lines.splice(at, 0, ...blank(at - 1) ? [] : [""], ...ins, ...blank(at) ? [] : [""]);
|
|
1720
2167
|
return lines.join("\n");
|
|
1721
2168
|
}
|
|
1722
|
-
/** The journal as the timeline shows it. Your own edits in a row within a session are one moment
|
|
1723
|
-
* one moment whose `what` says how it ended. Revisions are not
|
|
2169
|
+
/** The journal as the timeline shows it. Your own edits in a row within a session are one moment, until ten minutes
|
|
2170
|
+
* pass with none (LAYER_IDLE_MS); a proposal is one moment whose `what` says how it ended. Revisions are not
|
|
2171
|
+
* moments. */
|
|
1724
2172
|
function moments(entries) {
|
|
1725
2173
|
const out = [], offered = /* @__PURE__ */ new Map(), asked = /* @__PURE__ */ new Map();
|
|
2174
|
+
/** Lines a change put in and took out: a note's first entry, all of its text. */
|
|
2175
|
+
const size = (c) => c.why === "start" || !c.hunks.length && c.before === null ? [(c.text ?? "").split("\n").length, 0] : [c.hunks.reduce((n, h) => n + h.ins.length, 0), c.hunks.reduce((n, h) => n + h.del.length, 0)];
|
|
1726
2176
|
for (const e of entries) {
|
|
1727
2177
|
const base = {
|
|
1728
2178
|
ids: [e.id],
|
|
1729
2179
|
at: e.at,
|
|
1730
2180
|
session: e.session,
|
|
1731
|
-
who: e.who
|
|
2181
|
+
who: e.who,
|
|
2182
|
+
added: 0,
|
|
2183
|
+
removed: 0
|
|
1732
2184
|
};
|
|
1733
2185
|
if (e.kind === "proposal") {
|
|
1734
2186
|
const m = {
|
|
@@ -1747,22 +2199,29 @@ function moments(entries) {
|
|
|
1747
2199
|
}
|
|
1748
2200
|
} else if (e.kind === "change") {
|
|
1749
2201
|
const m = e.proposal ? offered.get(e.proposal) : void 0;
|
|
2202
|
+
const [added, removed] = size(e);
|
|
1750
2203
|
if (e.why === "keep" && m) {
|
|
1751
2204
|
m.what = "kept";
|
|
1752
2205
|
m.ids.push(e.id);
|
|
1753
2206
|
m.at = e.at;
|
|
2207
|
+
m.added += added;
|
|
2208
|
+
m.removed += removed;
|
|
1754
2209
|
continue;
|
|
1755
2210
|
}
|
|
1756
2211
|
const last = out[out.length - 1];
|
|
1757
|
-
if (e.why === "edit" && last && last.what === "edited" && last.who === e.who && last.session === e.session) {
|
|
2212
|
+
if (e.why === "edit" && last && last.what === "edited" && last.who === e.who && last.session === e.session && Date.parse(e.at) - Date.parse(last.at) <= 6e5) {
|
|
1758
2213
|
last.ids.push(e.id);
|
|
1759
2214
|
last.at = e.at;
|
|
2215
|
+
last.added += added;
|
|
2216
|
+
last.removed += removed;
|
|
1760
2217
|
continue;
|
|
1761
2218
|
}
|
|
1762
2219
|
const what = e.why === "start" ? e.who === "external" ? "found" : "wrote" : e.why === "edit" ? "edited" : e.why === "restore" ? "restored" : e.why === "keep" ? "kept" : e.why === "move" ? "moved" : "changed elsewhere";
|
|
1763
2220
|
out.push({
|
|
1764
2221
|
...base,
|
|
1765
2222
|
what,
|
|
2223
|
+
added,
|
|
2224
|
+
removed,
|
|
1766
2225
|
...e.asked ? { asked: e.asked } : {}
|
|
1767
2226
|
});
|
|
1768
2227
|
}
|
|
@@ -1884,10 +2343,54 @@ function mapModel(base) {
|
|
|
1884
2343
|
]);
|
|
1885
2344
|
}
|
|
1886
2345
|
const holdsFiles = /* @__PURE__ */ new Set();
|
|
1887
|
-
|
|
2346
|
+
const parentOf = (p) => p.includes("/") ? p.slice(0, p.lastIndexOf("/")) : "";
|
|
2347
|
+
for (const f of base.files) holdsFiles.add(parentOf(f));
|
|
2348
|
+
const pictureFolder = (d) => {
|
|
2349
|
+
const dd = base.directories.get(d);
|
|
2350
|
+
const here = [...base.files].filter((f) => parentOf(f) === d);
|
|
2351
|
+
return !!d && (!dd || !dd.concepts.length && !dd.children.length) && here.length > 0 && here.every((f) => citeKind(f) === "image");
|
|
2352
|
+
};
|
|
2353
|
+
const refOf = (f) => citeKind(f) === "image" && pictureFolder(parentOf(f)) ? parentOf(f) + "/" : f;
|
|
2354
|
+
const fileEdges = /* @__PURE__ */ new Map();
|
|
2355
|
+
for (const c of base.concepts.values()) for (const x of c.cites) {
|
|
2356
|
+
if (x.broken || !base.files.has(x.target)) continue;
|
|
2357
|
+
const ref = refOf(x.target);
|
|
2358
|
+
if (!fileEdges.has(c.id)) fileEdges.set(c.id, /* @__PURE__ */ new Set());
|
|
2359
|
+
fileEdges.get(c.id).add(ref);
|
|
2360
|
+
}
|
|
2361
|
+
const rankOf = /* @__PURE__ */ new Map();
|
|
2362
|
+
for (const [from, refs] of fileEdges) for (const ref of refs) {
|
|
2363
|
+
edges.push([
|
|
2364
|
+
from,
|
|
2365
|
+
ref,
|
|
2366
|
+
STRENGTH.uses
|
|
2367
|
+
]);
|
|
2368
|
+
rankOf.set(ref, Math.max(rankOf.get(ref) ?? 0, order.get(from)?.depth ?? 0));
|
|
2369
|
+
}
|
|
2370
|
+
const filesIn = /* @__PURE__ */ new Map();
|
|
2371
|
+
for (const f of [...base.files].sort()) {
|
|
2372
|
+
const of = citeKind(f), ref = of ? refOf(f) : null;
|
|
2373
|
+
if (!of || !ref) continue;
|
|
2374
|
+
const at = ref.endsWith("/") ? parentOf(ref.slice(0, -1)) : parentOf(f);
|
|
2375
|
+
const list = filesIn.get(at) ?? [];
|
|
2376
|
+
if (!list.some((n) => n.ref === ref)) list.push({
|
|
2377
|
+
kind: "concept",
|
|
2378
|
+
id: "f:" + ref,
|
|
2379
|
+
ref,
|
|
2380
|
+
weight: 1,
|
|
2381
|
+
rank: rankOf.get(ref) ?? 0,
|
|
2382
|
+
of
|
|
2383
|
+
});
|
|
2384
|
+
filesIn.set(at, list);
|
|
2385
|
+
}
|
|
1888
2386
|
const dir = (id) => {
|
|
1889
2387
|
const d = base.directories.get(id);
|
|
1890
|
-
|
|
2388
|
+
if (id && pictureFolder(id)) return null;
|
|
2389
|
+
const children = [
|
|
2390
|
+
...[...d.children].sort().map(dir).filter((c) => c !== null),
|
|
2391
|
+
...[...d.concepts].sort().map(note),
|
|
2392
|
+
...filesIn.get(id) ?? []
|
|
2393
|
+
];
|
|
1891
2394
|
return id && !children.length && (holdsFiles.has(id) || d.children.length) ? null : {
|
|
1892
2395
|
kind: "dir",
|
|
1893
2396
|
id: "d:" + id,
|
|
@@ -1901,6 +2404,7 @@ function mapModel(base) {
|
|
|
1901
2404
|
};
|
|
1902
2405
|
}
|
|
1903
2406
|
var init_map = __esmMin((() => {
|
|
2407
|
+
init_bundle();
|
|
1904
2408
|
init_graph();
|
|
1905
2409
|
}));
|
|
1906
2410
|
//#endregion
|
|
@@ -1911,6 +2415,7 @@ var init_src$1 = __esmMin((() => {
|
|
|
1911
2415
|
init_frontmatter();
|
|
1912
2416
|
init_source();
|
|
1913
2417
|
init_markdown();
|
|
2418
|
+
init_unwrap();
|
|
1914
2419
|
init_text();
|
|
1915
2420
|
init_search();
|
|
1916
2421
|
init_record();
|
|
@@ -1925,6 +2430,7 @@ var init_src$1 = __esmMin((() => {
|
|
|
1925
2430
|
}));
|
|
1926
2431
|
//#endregion
|
|
1927
2432
|
//#region packages/core/src/node.ts
|
|
2433
|
+
init_src$2();
|
|
1928
2434
|
init_src$1();
|
|
1929
2435
|
init_bundle();
|
|
1930
2436
|
init_text();
|
|
@@ -2111,7 +2617,8 @@ async function completeOnce(http, p, key, call, heard) {
|
|
|
2111
2617
|
...call.tools?.length ? {
|
|
2112
2618
|
tools: call.tools,
|
|
2113
2619
|
tool_choice: call.toolChoice ?? "auto"
|
|
2114
|
-
} : {}
|
|
2620
|
+
} : {},
|
|
2621
|
+
...call.effort && p.name === OPENROUTER.name ? { reasoning: { effort: call.effort } } : {}
|
|
2115
2622
|
});
|
|
2116
2623
|
let res;
|
|
2117
2624
|
try {
|
|
@@ -2369,13 +2876,14 @@ async function rounds(o) {
|
|
|
2369
2876
|
let firstIn = 0;
|
|
2370
2877
|
for (let round = 0, calls = 1;; round++, calls++) {
|
|
2371
2878
|
let first = true;
|
|
2372
|
-
const ask = (maxTokens) => complete(o.http, o.provider, o.key, {
|
|
2879
|
+
const ask = (maxTokens, effort) => complete(o.http, o.provider, o.key, {
|
|
2373
2880
|
model,
|
|
2374
2881
|
messages,
|
|
2375
2882
|
maxTokens,
|
|
2376
2883
|
tools: most ? o.lookup.defs() : [],
|
|
2377
2884
|
toolChoice: round < most ? "auto" : "none",
|
|
2378
2885
|
signal: o.signal,
|
|
2886
|
+
...effort ? { effort } : {},
|
|
2379
2887
|
onText: (piece) => {
|
|
2380
2888
|
o.onText(piece, first && round > 0);
|
|
2381
2889
|
first = false;
|
|
@@ -2392,10 +2900,14 @@ async function rounds(o) {
|
|
|
2392
2900
|
add(r);
|
|
2393
2901
|
if (round === 0) firstIn = r.usage.tokensIn;
|
|
2394
2902
|
if (empty(r)) {
|
|
2395
|
-
r = await ask(o.maxTokens * 3);
|
|
2903
|
+
r = await ask(o.maxTokens * 3, "low");
|
|
2396
2904
|
add(r);
|
|
2397
2905
|
if (empty(r)) throw new ModelError(`${model} used its whole output budget (${Math.max(o.maxTokens * 3, o.provider.maxTokens ?? 0)} tokens) thinking, and wrote nothing. Try a lower tier, or raise max_tokens under [provider] in config.toml.`, 502, { kind: "cut" });
|
|
2398
2906
|
}
|
|
2907
|
+
if (!r.text.trim() && !r.calls.length && !r.cut) {
|
|
2908
|
+
r = await ask(o.maxTokens);
|
|
2909
|
+
add(r);
|
|
2910
|
+
}
|
|
2399
2911
|
if (!r.calls.length || round >= most) return {
|
|
2400
2912
|
text: r.text,
|
|
2401
2913
|
usage,
|
|
@@ -2627,7 +3139,7 @@ function parseRewrite(reply, count) {
|
|
|
2627
3139
|
/** A passage's new text, set between the same neighbours as the old: it keeps the old one's space and line
|
|
2628
3140
|
* breaks at either end, whatever the model put round what it wrote. An empty one takes the passage out. */
|
|
2629
3141
|
function fitted(old, text) {
|
|
2630
|
-
const core = text.trim();
|
|
3142
|
+
const core = unwrap(text.trim());
|
|
2631
3143
|
if (!core) return "";
|
|
2632
3144
|
return /^\s*/.exec(old)[0] + core + /\s*$/.exec(old)[0];
|
|
2633
3145
|
}
|
|
@@ -2686,7 +3198,7 @@ function replyText(reply) {
|
|
|
2686
3198
|
let text = reply.replaceAll(HERE, "").replace(/^\s*\n/, "").trimEnd();
|
|
2687
3199
|
const fenced = /^```(?:markdown|md)?\n([\s\S]*)\n```$/.exec(text.trim());
|
|
2688
3200
|
if (fenced) text = fenced[1];
|
|
2689
|
-
return text.trim();
|
|
3201
|
+
return unwrap(text.trim());
|
|
2690
3202
|
}
|
|
2691
3203
|
var HERE, WHO$1, INSERT, OPEN, CLOSE, SAY_OPEN, SAY_CLOSE, REWRITE, NOTE, FORMAT, FORMAT_RULES, LOOKUP, estimate, CONVERSATION_BUDGET, NOTE_BUDGET, OLD_EXCHANGE, NO_LOOKUP, looked, LINKED_BUDGET;
|
|
2692
3204
|
var init_iso = __esmMin((() => {
|
|
@@ -2781,6 +3293,8 @@ something at one place in the note they have open.`;
|
|
|
2781
3293
|
- A picture is shown as . You can't see it: what
|
|
2782
3294
|
you know of it is the words in the brackets. Keep a picture's line as it is
|
|
2783
3295
|
when you rewrite around it, and never make up a picture's path.
|
|
3296
|
+
- A paragraph or a list item is one line, however long: do not break lines at
|
|
3297
|
+
a width. The editor wraps them to the pane.
|
|
2784
3298
|
- Ordinary Markdown otherwise. No HTML.`;
|
|
2785
3299
|
FORMAT_RULES = FORMAT;
|
|
2786
3300
|
LOOKUP = (rounds, meaning) => `# Looking things up
|
|
@@ -3098,38 +3612,6 @@ var init_lookup = __esmMin((() => {
|
|
|
3098
3612
|
};
|
|
3099
3613
|
}));
|
|
3100
3614
|
//#endregion
|
|
3101
|
-
//#region packages/engine/src/understand.ts
|
|
3102
|
-
/** The two parts of an Understanding note's file. A file in another shape is read as all scratchpad. */
|
|
3103
|
-
function parseUnderstanding(text) {
|
|
3104
|
-
if (!text) return {
|
|
3105
|
-
reading: "",
|
|
3106
|
-
scratchpad: ""
|
|
3107
|
-
};
|
|
3108
|
-
const body = text.replace(/^---\n[\s\S]*?\n---\n/, "");
|
|
3109
|
-
const r = body.indexOf(READING$1), p = body.indexOf(PAD);
|
|
3110
|
-
if (r < 0 && p < 0) return {
|
|
3111
|
-
reading: "",
|
|
3112
|
-
scratchpad: body.trim()
|
|
3113
|
-
};
|
|
3114
|
-
return {
|
|
3115
|
-
reading: r < 0 ? "" : body.slice(r + 16, p > r ? p : void 0).trim(),
|
|
3116
|
-
scratchpad: p < 0 ? "" : body.slice(p + 13, r > p ? r : void 0).trim()
|
|
3117
|
-
};
|
|
3118
|
-
}
|
|
3119
|
-
/** An Understanding note's file, from its parts. */
|
|
3120
|
-
function understandingText(note, title, parts) {
|
|
3121
|
-
return `---\ntype: Understanding\ntitle: ${JSON.stringify(`Understanding · ${title}`)}\nof: ${note}\n---\n\n${READING$1}\n\n${parts.reading.trim()}\n\n${PAD}\n\n${parts.scratchpad.trim()}\n`;
|
|
3122
|
-
}
|
|
3123
|
-
var UNDERSTANDING_PREFIX, UNDERSTANDING_DIR, isUnderstandingId, understandingId, READING$1, PAD;
|
|
3124
|
-
var init_understand = __esmMin((() => {
|
|
3125
|
-
UNDERSTANDING_PREFIX = "@understanding/";
|
|
3126
|
-
UNDERSTANDING_DIR = "understanding";
|
|
3127
|
-
isUnderstandingId = (id) => id.startsWith(UNDERSTANDING_PREFIX);
|
|
3128
|
-
understandingId = (note) => UNDERSTANDING_PREFIX + note;
|
|
3129
|
-
READING$1 = "## Iso's reading";
|
|
3130
|
-
PAD = "## Scratchpad";
|
|
3131
|
-
}));
|
|
3132
|
-
//#endregion
|
|
3133
3615
|
//#region packages/engine/src/hand.ts
|
|
3134
3616
|
/** The lines about the working set in a conversation's file, in order. Turns' own lines are passed over. */
|
|
3135
3617
|
function readHand(text) {
|
|
@@ -3250,6 +3732,7 @@ function readTurns(text) {
|
|
|
3250
3732
|
turns.set(t.id, {
|
|
3251
3733
|
...t,
|
|
3252
3734
|
mode: MODES.includes(t.mode) ? t.mode : "ask",
|
|
3735
|
+
given: (t.given ?? []).filter((g) => GIVEN.includes(g.kind)),
|
|
3253
3736
|
items: [],
|
|
3254
3737
|
state: "running"
|
|
3255
3738
|
});
|
|
@@ -3258,7 +3741,7 @@ function readTurns(text) {
|
|
|
3258
3741
|
const t = turns.get(line.turn);
|
|
3259
3742
|
if (!t) continue;
|
|
3260
3743
|
if (line.kind === "item") {
|
|
3261
|
-
if (ITEMS.includes(line.item.kind)) t.items.push(line.item);
|
|
3744
|
+
if (ITEMS.includes(line.item.kind) && !(line.item.kind === "change" && line.item.note.startsWith("@understanding/"))) t.items.push(line.item);
|
|
3262
3745
|
} else if (line.kind === "answer") {
|
|
3263
3746
|
for (const i of t.items) if (i.kind === "permission" && i.id === line.permission) i.answer = line.decision;
|
|
3264
3747
|
} else if (line.kind === "taken") {
|
|
@@ -3275,9 +3758,9 @@ function readTurns(text) {
|
|
|
3275
3758
|
state: "stopped"
|
|
3276
3759
|
} : t).sort((a, b) => a.id < b.id ? -1 : 1);
|
|
3277
3760
|
}
|
|
3278
|
-
/** A turn's own tools: its mode's
|
|
3279
|
-
function turnTools(mode
|
|
3280
|
-
return
|
|
3761
|
+
/** A turn's own tools: its mode's. */
|
|
3762
|
+
function turnTools(mode) {
|
|
3763
|
+
return MODE_TOOLS[mode];
|
|
3281
3764
|
}
|
|
3282
3765
|
function sentText(s) {
|
|
3283
3766
|
if (s.kind === "note") {
|
|
@@ -3286,15 +3769,14 @@ function sentText(s) {
|
|
|
3286
3769
|
}
|
|
3287
3770
|
if (s.kind === "passage") return `## A passage they chose in ${s.title} (/${s.id}.md)\n\n${s.text.split("\n").map((l) => `> ${l}`).join("\n")}`;
|
|
3288
3771
|
if (s.kind === "read") return `## ${s.title} (/${s.id}.md)${s.sections === "whole" ? "" : `, ${s.sections.length === 1 ? "the section" : "the sections"} ${s.sections.map((x) => `"${x}"`).join(", ")}`}\n\n${s.text}`;
|
|
3289
|
-
|
|
3290
|
-
return `## Their understanding of ${s.title} (/${s.id}.md)\n\nYour reading so far: ${s.reading || "none yet."}`;
|
|
3772
|
+
return `## The folder ${s.id || "at the top of the base"}: its notes\n\n${s.notes.map((n) => `- ${n.title} (/${n.id}.md)${n.description ? `: ${n.description}` : ""}`).join("\n") || "(empty)"}`;
|
|
3291
3773
|
}
|
|
3292
3774
|
/** An earlier turn as it is sent: in the shape `conversation()` shortens. */
|
|
3293
3775
|
function turnExchange(t) {
|
|
3294
3776
|
const text = t.items.filter((i) => i.kind === "text").map((i) => i.text).join("\n\n").trim();
|
|
3295
3777
|
const changed = t.items.flatMap((i) => i.kind === "change" ? [`${i.why === "create" ? "made" : i.why === "delete" ? "deleted" : i.why === "move" ? "moved" : "changed"} ${i.title} (/${i.note}.md)`] : []);
|
|
3296
3778
|
const asked = t.items.flatMap((i) => i.kind === "question" ? [`You asked them: ${i.title}${i.made ? ` (/${i.made}.md)` : ""}.`] : i.kind === "offer" ? [`You offered them a passage for /${i.note}.md, which they ${i.taken ? "inserted" : "have not inserted"}.`] : []);
|
|
3297
|
-
const gave = t.given.
|
|
3779
|
+
const gave = t.given.map((g) => g.kind === "passage" ? `a passage of /${g.id}.md` : `/${g.id}${g.kind === "note" ? ".md" : "/"}`);
|
|
3298
3780
|
return {
|
|
3299
3781
|
id: t.id,
|
|
3300
3782
|
note: "",
|
|
@@ -3303,7 +3785,6 @@ function turnExchange(t) {
|
|
|
3303
3785
|
}
|
|
3304
3786
|
/** The messages for a turn, and what they hold, part by part. */
|
|
3305
3787
|
function turnMessages(a) {
|
|
3306
|
-
const understood = a.sent.filter((s) => s.kind === "understanding");
|
|
3307
3788
|
const checking = a.sent.some((s) => s.kind === "note" && s.of === "problem");
|
|
3308
3789
|
const system = [
|
|
3309
3790
|
WHO,
|
|
@@ -3311,22 +3792,21 @@ function turnMessages(a) {
|
|
|
3311
3792
|
PRACTICE,
|
|
3312
3793
|
LEARNING,
|
|
3313
3794
|
MODE_RULES[a.mode],
|
|
3795
|
+
...a.mode === "write" ? [ARTIFACTS] : [],
|
|
3314
3796
|
ASKING(a.mode === "write"),
|
|
3315
3797
|
...checking ? [CHECKING] : [],
|
|
3316
|
-
...understood.length ? [UNDERSTANDING] : [],
|
|
3317
3798
|
FORMAT_RULES,
|
|
3318
|
-
LOOKING(ROUNDS[a.tier], a.meaning, a.lookup)
|
|
3799
|
+
LOOKING(a.mode === "write" ? Math.max(ROUNDS[a.tier], 6) : ROUNDS[a.tier], a.meaning, a.lookup)
|
|
3319
3800
|
].join("\n\n");
|
|
3320
3801
|
const briefed = a.briefings?.length ? `# Earlier in this conversation, summed up\n\n${a.briefings.join("\n\n---\n\n")}` : "";
|
|
3321
3802
|
const talk = a.earlier.map((t) => turnExchange(t).text).join("\n\n---\n\n");
|
|
3322
3803
|
const earlier = [briefed, talk ? `# Earlier in this conversation\n\n${talk}` : ""].filter(Boolean).join("\n\n");
|
|
3323
|
-
const given = a.sent.filter((s) => s.kind !== "
|
|
3804
|
+
const given = a.sent.filter((s) => s.kind !== "read"), reads = a.sent.filter((s) => s.kind === "read");
|
|
3324
3805
|
const gave = given.length ? `# What they gave you\n\n${given.map(sentText).join("\n\n")}` : "";
|
|
3325
3806
|
const held = reads.length ? `# Notes you read earlier in this conversation\n\n${reads.map(sentText).join("\n\n")}` : "";
|
|
3326
3807
|
const aside = a.aside?.length ? `# Read earlier, set aside\n\n${a.aside.map((n) => `- ${n.title} (/${n.id}.md)`).join("\n")}` : "";
|
|
3327
3808
|
const agents = a.agents?.trim() ? `# Their instructions for this knowledge base (AGENTS.md)\n\n${a.agents.trim()}` : "";
|
|
3328
3809
|
const lent = a.lookup && a.lent?.length ? `# Other knowledge bases lent to you\n\nYou may read these with the same look-up tools, giving their base; you change notes only in this one. Look in them where what is asked may be there: how the person works, their tools, what was settled before.\n\n${a.lent.map((l) => `- ${l.name}: ${l.about}`).join("\n")}` : "";
|
|
3329
|
-
const known = understood.length ? `# Their understanding, as you have recorded it\n\n${understood.map(sentText).join("\n\n")}` : "";
|
|
3330
3810
|
const broken = a.broken?.length ? `# Code links that no longer resolve\n\nSince you were last told, these links in their notes point to files no longer in their repository. Mention it only where it bears on what they ask.\n\n${a.broken.map((b) => `- in ${b.title} (/${b.id}.md): ${b.href}`).join("\n")}` : "";
|
|
3331
3811
|
const request = `# What they ask now\n\n${a.asked}`;
|
|
3332
3812
|
return {
|
|
@@ -3342,7 +3822,6 @@ function turnMessages(a) {
|
|
|
3342
3822
|
agents,
|
|
3343
3823
|
lent,
|
|
3344
3824
|
earlier,
|
|
3345
|
-
known,
|
|
3346
3825
|
gave,
|
|
3347
3826
|
held,
|
|
3348
3827
|
aside,
|
|
@@ -3354,7 +3833,7 @@ function turnMessages(a) {
|
|
|
3354
3833
|
{
|
|
3355
3834
|
name: "instructions and tools",
|
|
3356
3835
|
detail: `${a.mode} mode${checking ? ", checking" : ""}${a.lookup ? "" : ", no look-ups"}`,
|
|
3357
|
-
tokens: estimate(system) + estimate(lent) + (a.lookup ? 500 : 0) + turnTools(a.mode
|
|
3836
|
+
tokens: estimate(system) + estimate(lent) + (a.lookup ? 500 : 0) + turnTools(a.mode).length * 120
|
|
3358
3837
|
},
|
|
3359
3838
|
{
|
|
3360
3839
|
name: "earlier in this conversation",
|
|
@@ -3376,11 +3855,6 @@ function turnMessages(a) {
|
|
|
3376
3855
|
detail: `${reads.length} in hand${a.aside?.length ? `, ${a.aside.length} set aside` : ""}`,
|
|
3377
3856
|
tokens: estimate(held) + estimate(aside)
|
|
3378
3857
|
}] : [],
|
|
3379
|
-
...understood.length ? [{
|
|
3380
|
-
name: "your understanding",
|
|
3381
|
-
detail: `${understood.length} ${understood.length === 1 ? "note" : "notes"}`,
|
|
3382
|
-
tokens: estimate(known)
|
|
3383
|
-
}] : [],
|
|
3384
3858
|
...broken ? [{
|
|
3385
3859
|
name: "code links broken",
|
|
3386
3860
|
detail: `${a.broken.length}`,
|
|
@@ -3394,7 +3868,7 @@ function turnMessages(a) {
|
|
|
3394
3868
|
]
|
|
3395
3869
|
};
|
|
3396
3870
|
}
|
|
3397
|
-
var MODES, ITEMS, CONVERSATIONS_DIR, conversationPath, WHO, ISORITE, PRACTICE, LEARNING, ASK, WRITE,
|
|
3871
|
+
var MODES, GIVEN, ITEMS, CONVERSATIONS_DIR, conversationPath, WHO, ISORITE, PRACTICE, LEARNING, ASK, WRITE, ARTIFACTS, ASKING, CHECKING, MODE_RULES, LOOKING, COMPACT$1, str, fn, POINT_TO_NOTE, EDIT_NOTE$1, WRITE_NOTE$1, MOVE_NOTE, DELETE_NOTE, ASK_QUESTION, READ_DATA, ARTIFACT_RECIPE, WRITE_ARTIFACT, READ_ARTIFACT, OFFER_TEXT, MODE_TOOLS;
|
|
3398
3872
|
var init_turn = __esmMin((() => {
|
|
3399
3873
|
init_iso();
|
|
3400
3874
|
init_loop();
|
|
@@ -3403,6 +3877,11 @@ var init_turn = __esmMin((() => {
|
|
|
3403
3877
|
"write",
|
|
3404
3878
|
"build"
|
|
3405
3879
|
];
|
|
3880
|
+
GIVEN = [
|
|
3881
|
+
"note",
|
|
3882
|
+
"folder",
|
|
3883
|
+
"passage"
|
|
3884
|
+
];
|
|
3406
3885
|
ITEMS = [
|
|
3407
3886
|
"text",
|
|
3408
3887
|
"look",
|
|
@@ -3432,6 +3911,11 @@ what you change is shown to them there as you do it.`;
|
|
|
3432
3911
|
problem note is practice that \`uses\` notes. Theirs alone to mark:
|
|
3433
3912
|
\`resolved\` or \`solved\`, with the day. \`[[!name]]\` alone on a line
|
|
3434
3913
|
embeds one in a note.
|
|
3914
|
+
- A problem of several steps is several problem notes, each linked to the one
|
|
3915
|
+
before it as "requires", in a folder of their own when they belong
|
|
3916
|
+
together. A problem has no steps inside it.
|
|
3917
|
+
- Files beside the notes that are not notes (data, documents, pictures,
|
|
3918
|
+
artifacts) are linked as any note is: [runs](/simulation/runs.csv).
|
|
3435
3919
|
- Every change you make is marked as yours, in the note's history, and they
|
|
3436
3920
|
can undo it.
|
|
3437
3921
|
|
|
@@ -3468,6 +3952,9 @@ It follows from [surprise](/basics/surprise.md "requires"), but how?
|
|
|
3468
3952
|
stop. Explain once they have answered. When they ask for a fact, a check or
|
|
3469
3953
|
a change, just answer.
|
|
3470
3954
|
- Prefer a question back, or a problem to try, to a summary.
|
|
3955
|
+
- Where they stand shows in their notes: what they wrote, their open questions,
|
|
3956
|
+
the problems they have solved and not. Where you find it stops, offer it to
|
|
3957
|
+
them as a question to keep (ask_question) or a problem to try.
|
|
3471
3958
|
- Check a solution; never solve it for them.`;
|
|
3472
3959
|
ASK = `# Ask mode: what you may do
|
|
3473
3960
|
|
|
@@ -3481,6 +3968,9 @@ note, no folder.
|
|
|
3481
3968
|
the notes you used, so they can open them. Where the base does not hold what
|
|
3482
3969
|
is asked, answer from what you know of the subject, exactly, and say that
|
|
3483
3970
|
their notes do not cover it.
|
|
3971
|
+
- Cite in the sentence: the phrase that rests on a note is the link to it,
|
|
3972
|
+
with its kind. No numbered references and no list of sources at the end;
|
|
3973
|
+
the notes you read are shown under your reply.
|
|
3484
3974
|
- When one of their notes would help them and they may not know it, point it
|
|
3485
3975
|
out with point_to_note. Use it sparingly: one or two notes.
|
|
3486
3976
|
- Never invent a note, a link or a quotation.`;
|
|
@@ -3498,25 +3988,29 @@ change marked as yours, and can undo it.
|
|
|
3498
3988
|
- A new note (write_note) is written as one of theirs: frontmatter with type
|
|
3499
3989
|
and title, their notation, their level, plain words, linked to the notes it
|
|
3500
3990
|
rests on.
|
|
3991
|
+
- Write each paragraph and each list item on one line. Do not break lines at a
|
|
3992
|
+
width: the editor wraps them to the pane.
|
|
3501
3993
|
- Moving or deleting a note is asked of them before it happens. Do it only
|
|
3502
3994
|
when they asked for it.
|
|
3503
3995
|
- When you are done, say in a sentence or two what you changed and why. Do not
|
|
3504
3996
|
repeat the text you wrote: they see it in the note.
|
|
3505
3997
|
- Never invent a note, a link or a quotation.`;
|
|
3506
|
-
|
|
3998
|
+
ARTIFACTS = `# Artifacts
|
|
3507
3999
|
|
|
3508
|
-
|
|
3509
|
-
|
|
4000
|
+
An artifact is an HTML page beside their notes that isorite shows working: a
|
|
4001
|
+
dashboard of their data, a comparison, a simulation, an explorable
|
|
4002
|
+
explanation, a 3D model's viewer. Make one when they ask for something to see
|
|
4003
|
+
or try; not for what an answer or a note says as well.
|
|
3510
4004
|
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3518
|
-
|
|
3519
|
-
|
|
4005
|
+
1. artifact_recipe with the recipe nearest what they asked: dashboard,
|
|
4006
|
+
compare, simulation, explorable or viewer. It gives a complete working
|
|
4007
|
+
artifact and the kit's reference. Start from the recipe and change what it
|
|
4008
|
+
marks; keep the rest.
|
|
4009
|
+
2. read_data for every data file it will read. Use the columns as given.
|
|
4010
|
+
3. write_artifact with the whole file. It is checked and run. If it reports a
|
|
4011
|
+
problem, fix that and write the whole file again.
|
|
4012
|
+
4. Say in a sentence what it shows, linked: [what it shows](/folder/name.html).
|
|
4013
|
+
Link it from the note it belongs to if they want it there.`;
|
|
3520
4014
|
ASKING = (write) => `# Asking back, and their own answers
|
|
3521
4015
|
|
|
3522
4016
|
- Where a question to them would do more than an answer (what is unclear in
|
|
@@ -3638,13 +4132,6 @@ was not said.`;
|
|
|
3638
4132
|
}
|
|
3639
4133
|
}, ["id", "to"]);
|
|
3640
4134
|
DELETE_NOTE = fn("delete_note", "Delete a note. They are asked first. Its history stays.", { id: str }, ["id"]);
|
|
3641
|
-
WRITE_UNDERSTANDING = fn("write_understanding", "Rewrite your reading of their understanding of one note: a few plain sentences on what they can do with it and what they have not yet shown, each resting on evidence linked as [their words](/id.md), [a question](/id.md) or [a problem](/id.md). Replaces the reading you had.", {
|
|
3642
|
-
id: {
|
|
3643
|
-
...str,
|
|
3644
|
-
description: "The note's id."
|
|
3645
|
-
},
|
|
3646
|
-
reading: str
|
|
3647
|
-
}, ["id", "reading"]);
|
|
3648
4135
|
ASK_QUESTION = fn("ask_question", "Ask them a question back, kept as an open question in their base, about one or more of their notes.", {
|
|
3649
4136
|
title: {
|
|
3650
4137
|
...str,
|
|
@@ -3664,6 +4151,50 @@ was not said.`;
|
|
|
3664
4151
|
"text",
|
|
3665
4152
|
"about"
|
|
3666
4153
|
]);
|
|
4154
|
+
READ_DATA = fn("read_data", "A data file's shape: its columns with their types and ranges, the row count and the first rows. Read it before writing anything that uses the file, so the column names are real.", { path: {
|
|
4155
|
+
...str,
|
|
4156
|
+
description: "The file's path in the base: folder/name.csv."
|
|
4157
|
+
} }, ["path"]);
|
|
4158
|
+
ARTIFACT_RECIPE = fn("artifact_recipe", "A recipe for an artifact: a complete working one to start from, the kit's reference, and the rules write_artifact checks. dashboard: numbers, charts and a table of one data file. compare: two data files side by side. simulation: a model with parameters and play. explorable: inputs, derived numbers and linked views. viewer: a 3D model.", { name: {
|
|
4159
|
+
type: "string",
|
|
4160
|
+
enum: [
|
|
4161
|
+
"dashboard",
|
|
4162
|
+
"compare",
|
|
4163
|
+
"simulation",
|
|
4164
|
+
"explorable",
|
|
4165
|
+
"viewer"
|
|
4166
|
+
]
|
|
4167
|
+
} }, ["name"]);
|
|
4168
|
+
WRITE_ARTIFACT = fn("write_artifact", "Write an artifact: a whole HTML file on the kit. The title, description and uses given here are written into it. It is checked, and run where it can be; the result says what to fix. Nothing is written while a rule is broken.", {
|
|
4169
|
+
path: {
|
|
4170
|
+
...str,
|
|
4171
|
+
description: "Where: folder/name.html, beside the notes it belongs to."
|
|
4172
|
+
},
|
|
4173
|
+
title: str,
|
|
4174
|
+
description: {
|
|
4175
|
+
...str,
|
|
4176
|
+
description: "One sentence: what it shows, and what can be done with it."
|
|
4177
|
+
},
|
|
4178
|
+
uses: {
|
|
4179
|
+
type: "array",
|
|
4180
|
+
items: str,
|
|
4181
|
+
description: "The data or model files it reads, relative to it (runs.csv). Those its markup and script name are added."
|
|
4182
|
+
},
|
|
4183
|
+
html: {
|
|
4184
|
+
...str,
|
|
4185
|
+
description: "The whole file."
|
|
4186
|
+
}
|
|
4187
|
+
}, [
|
|
4188
|
+
"path",
|
|
4189
|
+
"title",
|
|
4190
|
+
"description",
|
|
4191
|
+
"uses",
|
|
4192
|
+
"html"
|
|
4193
|
+
]);
|
|
4194
|
+
READ_ARTIFACT = fn("read_artifact", "An artifact's whole file, to change it and write it again.", { path: {
|
|
4195
|
+
...str,
|
|
4196
|
+
description: "folder/name.html"
|
|
4197
|
+
} }, ["path"]);
|
|
3667
4198
|
OFFER_TEXT = fn("offer_text", "Offer them their own answers, lightly joined, to insert at the end of a note as their writing. Only their words.", {
|
|
3668
4199
|
id: {
|
|
3669
4200
|
...str,
|
|
@@ -3678,7 +4209,8 @@ was not said.`;
|
|
|
3678
4209
|
ask: [
|
|
3679
4210
|
POINT_TO_NOTE,
|
|
3680
4211
|
ASK_QUESTION,
|
|
3681
|
-
OFFER_TEXT
|
|
4212
|
+
OFFER_TEXT,
|
|
4213
|
+
READ_DATA
|
|
3682
4214
|
],
|
|
3683
4215
|
write: [
|
|
3684
4216
|
EDIT_NOTE$1,
|
|
@@ -3687,7 +4219,11 @@ was not said.`;
|
|
|
3687
4219
|
DELETE_NOTE,
|
|
3688
4220
|
POINT_TO_NOTE,
|
|
3689
4221
|
ASK_QUESTION,
|
|
3690
|
-
OFFER_TEXT
|
|
4222
|
+
OFFER_TEXT,
|
|
4223
|
+
READ_DATA,
|
|
4224
|
+
ARTIFACT_RECIPE,
|
|
4225
|
+
WRITE_ARTIFACT,
|
|
4226
|
+
READ_ARTIFACT
|
|
3691
4227
|
]
|
|
3692
4228
|
};
|
|
3693
4229
|
}));
|
|
@@ -3892,6 +4428,11 @@ function checkNoteId(id) {
|
|
|
3892
4428
|
if (!id || id.length > 400 || parts.some((p) => !p || BAD_PART.test(p))) throw new EngineError(`not a note's id: ${JSON.stringify(id)}`);
|
|
3893
4429
|
return id;
|
|
3894
4430
|
}
|
|
4431
|
+
/** A file's path in the base as Iso gives one: no leading slash, no "..", nothing hidden. */
|
|
4432
|
+
function filePathOf(raw) {
|
|
4433
|
+
const p = String(raw ?? "").trim().replace(/^\/+/, "");
|
|
4434
|
+
return p && !p.split("/").some((x) => !x || x === "." || x === ".." || x.startsWith(".")) ? p : null;
|
|
4435
|
+
}
|
|
3895
4436
|
async function walk(files, root) {
|
|
3896
4437
|
const out = [];
|
|
3897
4438
|
const at = (path) => root && path ? `${root}/${path}` : root || path;
|
|
@@ -3929,14 +4470,13 @@ function keepDone(before, next) {
|
|
|
3929
4470
|
return next;
|
|
3930
4471
|
}
|
|
3931
4472
|
}
|
|
3932
|
-
var SIGN_IN_MS, fromBase, REPLY_TOKENS, NOTE_TOKENS, DRAFT_PREFIX, DRAFT_DIR, isDraftId, bare, inBase, BAD_PART, TURN_ID, escapeRe, MAX_SENT, BRIEF, NOTE_ARG, MARKED, EDIT_NOTE, WRITE_NOTE, Engine;
|
|
4473
|
+
var SIGN_IN_MS, fromBase, REPLY_TOKENS, NOTE_TOKENS, ARTIFACT_TOKENS, DRAFT_PREFIX, DRAFT_DIR, isDraftId, bare, inBase, BAD_PART, TURN_ID, escapeRe, MAX_SENT, BRIEF, NOTE_ARG, MARKED, EDIT_NOTE, WRITE_NOTE, Engine;
|
|
3933
4474
|
var init_engine = __esmMin((() => {
|
|
3934
4475
|
init_src$1();
|
|
3935
4476
|
init_api();
|
|
3936
4477
|
init_iso();
|
|
3937
4478
|
init_loop();
|
|
3938
4479
|
init_lookup();
|
|
3939
|
-
init_understand();
|
|
3940
4480
|
init_hand();
|
|
3941
4481
|
init_turn();
|
|
3942
4482
|
init_meaning();
|
|
@@ -3945,10 +4485,11 @@ var init_engine = __esmMin((() => {
|
|
|
3945
4485
|
fromBase = (base, text) => `(From the base ${base}: name its notes ${base}:<id>, or give base "${base}".)\n${text}`;
|
|
3946
4486
|
REPLY_TOKENS = 2500;
|
|
3947
4487
|
NOTE_TOKENS = 6e3;
|
|
4488
|
+
ARTIFACT_TOKENS = 32e3;
|
|
3948
4489
|
DRAFT_PREFIX = "@draft/";
|
|
3949
4490
|
DRAFT_DIR = "drafts";
|
|
3950
4491
|
isDraftId = (id) => id.startsWith(DRAFT_PREFIX);
|
|
3951
|
-
bare = (id) => isDraftId(id) ? id.slice(7) :
|
|
4492
|
+
bare = (id) => isDraftId(id) ? id.slice(7) : id;
|
|
3952
4493
|
inBase = (id) => !id.startsWith("@");
|
|
3953
4494
|
BAD_PART = /^\.|[\0\\:*?"<>|]|^\s|\s$/;
|
|
3954
4495
|
TURN_ID = /^[0-9A-HJKMNP-TV-Z]{26}$/;
|
|
@@ -4149,9 +4690,54 @@ var init_engine = __esmMin((() => {
|
|
|
4149
4690
|
...pictures.length ? { pictures } : {}
|
|
4150
4691
|
};
|
|
4151
4692
|
}
|
|
4693
|
+
const model = mapModel(this.bundle), files = {};
|
|
4694
|
+
const items = [];
|
|
4695
|
+
const walk = (d) => {
|
|
4696
|
+
for (const c of d.children) if (c.kind === "dir") walk(c);
|
|
4697
|
+
else if (c.id.startsWith("f:")) items.push(c.ref);
|
|
4698
|
+
};
|
|
4699
|
+
walk(model.root);
|
|
4700
|
+
const all = new Set(items);
|
|
4701
|
+
for (const ref of items) {
|
|
4702
|
+
if (ref.endsWith("/")) {
|
|
4703
|
+
const count = [...this.bundle.files].filter((f) => f.startsWith(ref) && !f.slice(ref.length).includes("/")).length;
|
|
4704
|
+
files[ref] = {
|
|
4705
|
+
title: `${ref.slice(0, -1).split("/").at(-1)} · ${count} ${count === 1 ? "picture" : "pictures"}`,
|
|
4706
|
+
kind: "image",
|
|
4707
|
+
count
|
|
4708
|
+
};
|
|
4709
|
+
continue;
|
|
4710
|
+
}
|
|
4711
|
+
const kind = citeKind(ref), name = ref.split("/").at(-1);
|
|
4712
|
+
if (kind !== "artifact") {
|
|
4713
|
+
files[ref] = {
|
|
4714
|
+
title: name,
|
|
4715
|
+
kind
|
|
4716
|
+
};
|
|
4717
|
+
continue;
|
|
4718
|
+
}
|
|
4719
|
+
const html = await this.opts.notes.read(ref).catch(() => null), m = html ? manifestOf(html) : null;
|
|
4720
|
+
files[ref] = {
|
|
4721
|
+
title: m?.title || name,
|
|
4722
|
+
kind
|
|
4723
|
+
};
|
|
4724
|
+
const dir = ref.split("/").slice(0, -1);
|
|
4725
|
+
for (const u of m?.uses ?? []) {
|
|
4726
|
+
const parts = [...dir];
|
|
4727
|
+
for (const p of u.split("/")) if (p === "..") parts.pop();
|
|
4728
|
+
else if (p && p !== ".") parts.push(p);
|
|
4729
|
+
const to = parts.join("/");
|
|
4730
|
+
if (all.has(to) && !model.edges.some(([a, b]) => a === ref && b === to)) model.edges.push([
|
|
4731
|
+
ref,
|
|
4732
|
+
to,
|
|
4733
|
+
STRENGTH.uses
|
|
4734
|
+
]);
|
|
4735
|
+
}
|
|
4736
|
+
}
|
|
4152
4737
|
return {
|
|
4153
|
-
model
|
|
4154
|
-
notes
|
|
4738
|
+
model,
|
|
4739
|
+
notes,
|
|
4740
|
+
...items.length ? { files } : {}
|
|
4155
4741
|
};
|
|
4156
4742
|
}
|
|
4157
4743
|
/** The base's settings, from `.isorite/settings.toml`. */
|
|
@@ -4505,7 +5091,7 @@ var init_engine = __esmMin((() => {
|
|
|
4505
5091
|
}
|
|
4506
5092
|
async deleteNote(id, base, who = "you") {
|
|
4507
5093
|
if (!inBase(id)) throw new EngineError("only a note of the base is deleted here");
|
|
4508
|
-
const
|
|
5094
|
+
const { files: notes, path } = this.where(id);
|
|
4509
5095
|
await this.one(async () => {
|
|
4510
5096
|
const now = await notes.read(path);
|
|
4511
5097
|
if (now === null) throw new Conflict(`${id} is gone`, null);
|
|
@@ -4543,10 +5129,10 @@ var init_engine = __esmMin((() => {
|
|
|
4543
5129
|
this.queue = run.catch(() => void 0);
|
|
4544
5130
|
return run;
|
|
4545
5131
|
}
|
|
4546
|
-
/** A
|
|
4547
|
-
* and (in the notes) never in a hidden folder such as `.isorite
|
|
5132
|
+
/** A file in the notes that isn't a note (M23: any kind; M22 gave pictures only), or a picture in the base's
|
|
5133
|
+
* repository. Within its folder, and (in the notes) never in a hidden folder such as `.isorite/`. */
|
|
4548
5134
|
async file(path, from = "notes") {
|
|
4549
|
-
const type = pictureType(path);
|
|
5135
|
+
const type = from === "code" ? pictureType(path) : fileType(path);
|
|
4550
5136
|
if (!type) return null;
|
|
4551
5137
|
let files, at;
|
|
4552
5138
|
if (from === "code") {
|
|
@@ -4644,12 +5230,19 @@ var init_engine = __esmMin((() => {
|
|
|
4644
5230
|
};
|
|
4645
5231
|
});
|
|
4646
5232
|
}
|
|
4647
|
-
/** Where a note's file is: drafts
|
|
5233
|
+
/** Where a note's file is: drafts are among isorite's own files. */
|
|
4648
5234
|
where(id) {
|
|
4649
|
-
if (
|
|
4650
|
-
files: this.opts.
|
|
4651
|
-
path:
|
|
5235
|
+
if (inBase(id) && citeKind(id) === "artifact") return {
|
|
5236
|
+
files: this.opts.notes,
|
|
5237
|
+
path: checkNoteId(id)
|
|
4652
5238
|
};
|
|
5239
|
+
if (!inBase(id)) {
|
|
5240
|
+
if (!isDraftId(id)) throw new EngineError(`there is no note ${id}`);
|
|
5241
|
+
return {
|
|
5242
|
+
files: this.opts.own,
|
|
5243
|
+
path: `${DRAFT_DIR}/${checkNoteId(bare(id))}.md`
|
|
5244
|
+
};
|
|
5245
|
+
}
|
|
4653
5246
|
return {
|
|
4654
5247
|
files: this.opts.notes,
|
|
4655
5248
|
path: `${checkNoteId(id)}.md`
|
|
@@ -5398,6 +5991,32 @@ var init_engine = __esmMin((() => {
|
|
|
5398
5991
|
} : t)
|
|
5399
5992
|
};
|
|
5400
5993
|
}
|
|
5994
|
+
async conversations() {
|
|
5995
|
+
const study = this.opts.own;
|
|
5996
|
+
const names = await study.list(CONVERSATIONS_DIR).catch(() => []);
|
|
5997
|
+
const all = [];
|
|
5998
|
+
for (const f of names) {
|
|
5999
|
+
const id = f.name.slice(0, -6);
|
|
6000
|
+
if (f.dir || !f.name.endsWith(".jsonl") || !TURN_ID.test(id)) continue;
|
|
6001
|
+
const turns = readTurns(await study.read(conversationPath(id)) ?? "");
|
|
6002
|
+
if (!turns.length) continue;
|
|
6003
|
+
const notes = /* @__PURE__ */ new Set();
|
|
6004
|
+
for (const t of turns) {
|
|
6005
|
+
for (const g of t.given) if (g.kind !== "folder") notes.add(g.id);
|
|
6006
|
+
for (const i of t.items) if (i.kind === "look" && i.step.opened && !i.step.away) notes.add(i.step.opened);
|
|
6007
|
+
else if (i.kind === "point" || i.kind === "change" && i.why !== "delete") notes.add(i.kind === "change" && i.to ? i.to : i.note);
|
|
6008
|
+
}
|
|
6009
|
+
all.push({
|
|
6010
|
+
id,
|
|
6011
|
+
at: turns[0].at,
|
|
6012
|
+
last: turns.at(-1).at,
|
|
6013
|
+
asked: turns[0].asked,
|
|
6014
|
+
turns: turns.length,
|
|
6015
|
+
notes: notes.size
|
|
6016
|
+
});
|
|
6017
|
+
}
|
|
6018
|
+
return all.sort((a, b) => a.last < b.last ? 1 : a.last > b.last ? -1 : 0);
|
|
6019
|
+
}
|
|
5401
6020
|
/** What was given, checked: notes and passages of the base, and its folders. */
|
|
5402
6021
|
given(raw) {
|
|
5403
6022
|
if (!Array.isArray(raw) || raw.length > 40) throw new EngineError("give Iso between none and forty notes, folders and passages");
|
|
@@ -5415,10 +6034,6 @@ var init_engine = __esmMin((() => {
|
|
|
5415
6034
|
kind: "note",
|
|
5416
6035
|
id
|
|
5417
6036
|
};
|
|
5418
|
-
if (g?.kind === "understanding") return {
|
|
5419
|
-
kind: "understanding",
|
|
5420
|
-
id
|
|
5421
|
-
};
|
|
5422
6037
|
if (g?.kind === "passage" && typeof g.text === "string" && g.text.trim() && g.text.length <= 2e4) return {
|
|
5423
6038
|
kind: "passage",
|
|
5424
6039
|
id,
|
|
@@ -5454,16 +6069,6 @@ var init_engine = __esmMin((() => {
|
|
|
5454
6069
|
}
|
|
5455
6070
|
const c = this.bundle.concepts.get(g.id);
|
|
5456
6071
|
if (!c) continue;
|
|
5457
|
-
if (g.kind === "understanding") {
|
|
5458
|
-
const u = await this.understanding(g.id);
|
|
5459
|
-
out.push({
|
|
5460
|
-
kind: "understanding",
|
|
5461
|
-
id: g.id,
|
|
5462
|
-
title: c.title,
|
|
5463
|
-
reading: u.reading
|
|
5464
|
-
});
|
|
5465
|
-
continue;
|
|
5466
|
-
}
|
|
5467
6072
|
if (g.kind === "passage") out.push({
|
|
5468
6073
|
kind: "passage",
|
|
5469
6074
|
id: g.id,
|
|
@@ -5594,7 +6199,7 @@ var init_engine = __esmMin((() => {
|
|
|
5594
6199
|
group: "notes",
|
|
5595
6200
|
kind: g.kind,
|
|
5596
6201
|
title: g.kind === "folder" ? g.id || "the top of the base" : title(g.id),
|
|
5597
|
-
detail: g.kind === "passage" ? "a passage" : g.kind === "folder" ? `${s.kind === "folder" ? s.notes.length : 0} notes listed` :
|
|
6202
|
+
detail: g.kind === "passage" ? "a passage" : g.kind === "folder" ? `${s.kind === "folder" ? s.notes.length : 0} notes listed` : "",
|
|
5598
6203
|
tokens: scaled(estimate(sentText(s))),
|
|
5599
6204
|
on: !w.off.has(k),
|
|
5600
6205
|
by: "you",
|
|
@@ -5884,7 +6489,7 @@ var init_engine = __esmMin((() => {
|
|
|
5884
6489
|
const big = [...w.built.parts].sort((a, b) => b.tokens - a.tokens)[0], k = (n) => `${Math.round(n / 1e3)}k`;
|
|
5885
6490
|
throw new EngineError(`This turn would be sent about ${k(size())} tokens, more than the ${k(limit)} the model takes. The largest part is ${big.name} (${k(w.scaled(big.tokens))}): switch parts off in the Context view.`, 413);
|
|
5886
6491
|
}
|
|
5887
|
-
const sent = [...w.sent, ...w.reads], own = turnTools(mode
|
|
6492
|
+
const sent = [...w.sent, ...w.reads], own = turnTools(mode);
|
|
5888
6493
|
const inHand = new Map(w.reads.flatMap((r) => r.kind === "read" ? [[r.id, r.sections]] : []));
|
|
5889
6494
|
for (const g of w.givenOn) if (g.kind === "note") inHand.set(g.id, "whole");
|
|
5890
6495
|
const { messages } = turnMessages({
|
|
@@ -6008,9 +6613,9 @@ var init_engine = __esmMin((() => {
|
|
|
6008
6613
|
tier: req.tier,
|
|
6009
6614
|
messages,
|
|
6010
6615
|
lookup: tools,
|
|
6011
|
-
maxTokens: REPLY_TOKENS * 2,
|
|
6012
6616
|
signal: abort.signal,
|
|
6013
|
-
|
|
6617
|
+
maxTokens: mode === "write" ? ARTIFACT_TOKENS : REPLY_TOKENS * 2,
|
|
6618
|
+
most: mode === "write" ? Math.max(ROUNDS[req.tier], 6) : ROUNDS[req.tier],
|
|
6014
6619
|
onText: (delta, again) => this.publish({
|
|
6015
6620
|
type: "iso-text",
|
|
6016
6621
|
request: turn,
|
|
@@ -6172,7 +6777,7 @@ var init_engine = __esmMin((() => {
|
|
|
6172
6777
|
if (name === "edit_note") {
|
|
6173
6778
|
const id = noteId(this.bundle, args.id);
|
|
6174
6779
|
if (!id) return `There is no note ${String(args.id ?? "")}. Search for it by its words.`;
|
|
6175
|
-
const old = String(args.old ?? ""), next = String(args.new ?? "");
|
|
6780
|
+
const old = String(args.old ?? ""), next = unwrap(String(args.new ?? ""));
|
|
6176
6781
|
if (!old) return "Give the passage as it is now, in old.";
|
|
6177
6782
|
const now = await this.readNote(id);
|
|
6178
6783
|
const at = now.text.indexOf(old), again = at < 0 ? -1 : now.text.indexOf(old, at + 1);
|
|
@@ -6193,7 +6798,7 @@ var init_engine = __esmMin((() => {
|
|
|
6193
6798
|
if (name === "write_note") {
|
|
6194
6799
|
const id = this.newNoteId(args.id);
|
|
6195
6800
|
if (!id) return `${String(args.id ?? "")} is not a note's id in their base: give folder/name, lower case with hyphens.`;
|
|
6196
|
-
let text = String(args.text ?? "");
|
|
6801
|
+
let text = unwrap(String(args.text ?? ""));
|
|
6197
6802
|
if (!text.trim()) return "Give the note's whole text.";
|
|
6198
6803
|
if (!/^---\n/.test(text)) text = `---\ntype: Concept\ntitle: ${JSON.stringify(/^#\s+(.+)$/m.exec(text)?.[1]?.trim() ?? id.split("/").at(-1).replace(/-/g, " "))}\n---\n\n${text.replace(/^#\s+.+\n+/, "")}`;
|
|
6199
6804
|
const was = this.bundle.concepts.has(id) ? await this.readNote(id) : null;
|
|
@@ -6299,52 +6904,100 @@ var init_engine = __esmMin((() => {
|
|
|
6299
6904
|
});
|
|
6300
6905
|
return `It is offered to them for ${agents ? "their AGENTS.md" : title(id)}.`;
|
|
6301
6906
|
}
|
|
6302
|
-
if (name === "
|
|
6303
|
-
const
|
|
6304
|
-
if (!
|
|
6305
|
-
const
|
|
6306
|
-
if (!
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
}
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
why: was ? "edit" : "create",
|
|
6318
|
-
title: `Understanding · ${title(id)}`,
|
|
6319
|
-
added: 0,
|
|
6320
|
-
removed: 0
|
|
6321
|
-
});
|
|
6322
|
-
return `Your reading of ${title(id)} is written.`;
|
|
6907
|
+
if (name === "read_data") {
|
|
6908
|
+
const path = filePathOf(args.path);
|
|
6909
|
+
if (!path) return `${String(args.path ?? "")} is not a file's path in their base.`;
|
|
6910
|
+
const got = await this.file(path).catch(() => null);
|
|
6911
|
+
if (!got) return `There is no file ${path}.${this.nearFiles(path)}`;
|
|
6912
|
+
try {
|
|
6913
|
+
return describeShape(shapeOf(path, parseData(path, new TextDecoder().decode(got.bytes))));
|
|
6914
|
+
} catch (e) {
|
|
6915
|
+
return `${path} could not be read as data: ${e.message}`;
|
|
6916
|
+
}
|
|
6917
|
+
}
|
|
6918
|
+
if (name === "artifact_recipe") {
|
|
6919
|
+
const r = RECIPES[String(args.name ?? "")];
|
|
6920
|
+
if (!r) return `There's no recipe ${String(args.name ?? "")}: there are ${Object.keys(RECIPES).join(", ")}.`;
|
|
6921
|
+
return `${REFERENCE}\n\n# The recipe: ${String(args.name)}\n\n${r.about}. A complete working artifact; change what it marks, keep the rest.\n\n\`\`\`html\n${r.html}\`\`\``;
|
|
6323
6922
|
}
|
|
6923
|
+
if (name === "read_artifact") {
|
|
6924
|
+
const path = filePathOf(args.path);
|
|
6925
|
+
if (!path || citeKind(path) !== "artifact") return `${String(args.path ?? "")} is not an artifact's path (folder/name.html).`;
|
|
6926
|
+
const got = await this.opts.notes.read(path);
|
|
6927
|
+
return got === null ? `There is no artifact ${path}.` : got;
|
|
6928
|
+
}
|
|
6929
|
+
if (name === "write_artifact") return this.writeArtifact(args, t);
|
|
6324
6930
|
return `${name} cannot be used yet.`;
|
|
6325
6931
|
}
|
|
6326
|
-
/**
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
|
|
6331
|
-
|
|
6332
|
-
|
|
6333
|
-
|
|
6334
|
-
|
|
6932
|
+
/** Files in the same folder as a path, to say which there are when one isn't. */
|
|
6933
|
+
nearFiles(path) {
|
|
6934
|
+
const dir = path.split("/").slice(0, -1).join("/"), there = [...this.files.keys()].filter((f) => !f.endsWith(".md") && f.split("/").slice(0, -1).join("/") === dir);
|
|
6935
|
+
return there.length ? ` In ${dir || "the top folder"} there are: ${there.map((f) => f.split("/").at(-1)).join(", ")}.` : "";
|
|
6936
|
+
}
|
|
6937
|
+
/** write_artifact (M23, plan-m23 §3.5, §3.7): the manifest written as given, the rules checked before anything is
|
|
6938
|
+
* written, then written and journalled as a note is, and run where it can be, with all it reports said back. */
|
|
6939
|
+
async writeArtifact(args, t) {
|
|
6940
|
+
const path = filePathOf(args.path);
|
|
6941
|
+
if (!path || citeKind(path) !== "artifact" || !inBase(path)) return `${String(args.path ?? "")} is not an artifact's path: give folder/name.html.`;
|
|
6942
|
+
const raw = String(args.html ?? "");
|
|
6943
|
+
if (!raw.trim()) return "Give the whole file, in html.";
|
|
6944
|
+
const given = (Array.isArray(args.uses) ? args.uses : typeof args.uses === "string" ? args.uses.split(/[\s,]+/) : []).map((u) => String(u));
|
|
6945
|
+
const uses = [...new Set([
|
|
6946
|
+
...given,
|
|
6947
|
+
...manifestOf(raw).uses,
|
|
6948
|
+
...readsOf(raw)
|
|
6949
|
+
].map((u) => u.trim().replace(/^\.\//, "")).filter(Boolean))];
|
|
6950
|
+
const title = String(args.title ?? "").trim() || manifestOf(raw).title || path.split("/").at(-1).replace(/\.html?$/, "").replace(/-/g, " ");
|
|
6951
|
+
const html = withManifest(raw, {
|
|
6952
|
+
title,
|
|
6953
|
+
description: String(args.description ?? "").trim() || manifestOf(raw).description,
|
|
6954
|
+
author: "Iso",
|
|
6955
|
+
date: new Date(this.opts.clock.now()).toISOString().slice(0, 10),
|
|
6956
|
+
kit: "1",
|
|
6957
|
+
uses
|
|
6958
|
+
});
|
|
6959
|
+
const dir = path.split("/").slice(0, -1);
|
|
6960
|
+
const at = (rel) => {
|
|
6961
|
+
const parts = [...dir];
|
|
6962
|
+
for (const p of rel.split("/")) if (p === "..") parts.pop();
|
|
6963
|
+
else if (p && p !== ".") parts.push(p);
|
|
6964
|
+
return parts.join("/");
|
|
6335
6965
|
};
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
|
|
6339
|
-
|
|
6340
|
-
|
|
6341
|
-
|
|
6342
|
-
|
|
6343
|
-
|
|
6344
|
-
|
|
6345
|
-
|
|
6346
|
-
|
|
6347
|
-
|
|
6966
|
+
const files = [], columns = {};
|
|
6967
|
+
for (const u of uses) {
|
|
6968
|
+
const got = await this.file(at(u)).catch(() => null);
|
|
6969
|
+
if (!got) continue;
|
|
6970
|
+
files.push(u);
|
|
6971
|
+
if (citeKind(u) === "data") try {
|
|
6972
|
+
columns[u] = columnsOf(parseData(u, new TextDecoder().decode(got.bytes)));
|
|
6973
|
+
} catch {}
|
|
6974
|
+
}
|
|
6975
|
+
const broken = checkArtifact(html, {
|
|
6976
|
+
files,
|
|
6977
|
+
columns
|
|
6978
|
+
}), errors = broken.filter((b) => b.level === "error");
|
|
6979
|
+
const list = (bs) => bs.map((b) => `- ${b.message}`).join("\n");
|
|
6980
|
+
if (errors.length) return `Not written: ${errors.length === 1 ? "a rule is" : "rules are"} broken.\n${list(errors)}\nFix ${errors.length === 1 ? "it" : "them"} and call write_artifact again with the whole file.`;
|
|
6981
|
+
const was = await this.opts.notes.read(path);
|
|
6982
|
+
const saved = await this.one(() => this.put(path, html, was === null ? null : hashText(was), "edit", { asked: t.asked }, t.who));
|
|
6983
|
+
if (saved.entry) await t.item({
|
|
6984
|
+
kind: "change",
|
|
6985
|
+
note: path,
|
|
6986
|
+
entry: saved.entry,
|
|
6987
|
+
why: was === null ? "create" : "edit",
|
|
6988
|
+
title,
|
|
6989
|
+
added: html.split("\n").length,
|
|
6990
|
+
removed: was?.split("\n").length ?? 0
|
|
6991
|
+
});
|
|
6992
|
+
const warnings = broken.filter((b) => b.level === "warning");
|
|
6993
|
+
const run = await this.opts.renderArtifact?.(path, html).catch(() => null) ?? null;
|
|
6994
|
+
const problems = [
|
|
6995
|
+
...run && !run.rendered ? ["it didn't start: the kit never said it was ready"] : [],
|
|
6996
|
+
...(run?.errors ?? []).map((e) => `it reported: ${e}`),
|
|
6997
|
+
...run && run.rendered && run.drawn === 0 ? ["it drew nothing: no chart, canvas or 3D view has anything in it"] : [],
|
|
6998
|
+
...warnings.map((w) => w.message)
|
|
6999
|
+
];
|
|
7000
|
+
return `${was === null ? "Written" : "Written anew"}: ${path}. ${run ? "It was run, with every control touched" : "It couldn't be run here (no browser), so only its rules were checked"}${problems.length ? `, and:\n${problems.map((p) => `- ${p}`).join("\n")}\nFix these and call write_artifact again with the whole file.` : run ? ", with no error." : "."}`;
|
|
6348
7001
|
}
|
|
6349
7002
|
/** A question asked in a turn, made a question note: in the folder of the first note it is about, named by its
|
|
6350
7003
|
* title. */
|
|
@@ -6781,7 +7434,6 @@ var init_src = __esmMin((() => {
|
|
|
6781
7434
|
init_iso();
|
|
6782
7435
|
init_meaning();
|
|
6783
7436
|
init_agent();
|
|
6784
|
-
init_understand();
|
|
6785
7437
|
init_turn();
|
|
6786
7438
|
}));
|
|
6787
7439
|
//#endregion
|
|
@@ -7964,7 +8616,7 @@ function toTurns(messages, { live = /* @__PURE__ */ new Map(), ours = /* @__PURE
|
|
|
7964
8616
|
/** What is asked from a page, with what was given in the Iso pane's context: notes and folders by name, for the
|
|
7965
8617
|
* agent to read with isorite's tools, and passages as they are. */
|
|
7966
8618
|
function given(asked, parts, title) {
|
|
7967
|
-
const notes = parts.filter((p) => p.kind === "note"
|
|
8619
|
+
const notes = parts.filter((p) => p.kind === "note").map((p) => `- ${title(p.id)} (${p.id})`);
|
|
7968
8620
|
const folders = parts.filter((p) => p.kind === "folder").map((p) => `- the folder ${p.id}`);
|
|
7969
8621
|
const passages = parts.flatMap((p) => p.kind === "passage" ? [`From ${title(p.id)} (${p.id}):\n${p.text.split("\n").map((l) => "> " + l).join("\n")}`] : []);
|
|
7970
8622
|
const out = [asked.trim()];
|
|
@@ -8288,10 +8940,6 @@ const Given = z.discriminatedUnion("kind", [
|
|
|
8288
8940
|
kind: z.literal("passage"),
|
|
8289
8941
|
id: z.string().max(400),
|
|
8290
8942
|
text: z.string().max(2e4)
|
|
8291
|
-
}).strict(),
|
|
8292
|
-
z.object({
|
|
8293
|
-
kind: z.literal("understanding"),
|
|
8294
|
-
id: z.string().max(400)
|
|
8295
8943
|
}).strict()
|
|
8296
8944
|
]);
|
|
8297
8945
|
const Mode = z.enum([
|
|
@@ -8476,7 +9124,7 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, terminal
|
|
|
8476
9124
|
if (e instanceof EngineError) return refuse(c, e.status, e.message);
|
|
8477
9125
|
throw e;
|
|
8478
9126
|
}
|
|
8479
|
-
if (!found) return refuse(c, 404, `no
|
|
9127
|
+
if (!found) return refuse(c, 404, `no file at ${path}`);
|
|
8480
9128
|
const etag = `"${found.version}"`;
|
|
8481
9129
|
const headers = {
|
|
8482
9130
|
"Content-Type": found.type,
|
|
@@ -8509,11 +9157,6 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, terminal
|
|
|
8509
9157
|
});
|
|
8510
9158
|
post("/api/iso/ask", Ask, (_c, b) => api.ask(b));
|
|
8511
9159
|
post("/api/iso/converse", TurnAsk, (_c, b) => api.converse(b));
|
|
8512
|
-
app.get("/api/understanding", (c) => answer(c, () => api.understanding(id(c))));
|
|
8513
|
-
post("/api/understanding/scratchpad", z.object({
|
|
8514
|
-
scratchpad: z.string().max(2e5),
|
|
8515
|
-
base: z.string().max(64).nullable()
|
|
8516
|
-
}).strict(), (c, b) => api.saveScratchpad(id(c), b.scratchpad, b.base));
|
|
8517
9160
|
post("/api/iso/take", z.object({
|
|
8518
9161
|
conversation: z.string().max(64),
|
|
8519
9162
|
turn: z.string().max(64),
|
|
@@ -8544,6 +9187,7 @@ function createApp({ api, token, loopback, web, allowHosts = [], notes, terminal
|
|
|
8544
9187
|
"reject"
|
|
8545
9188
|
])
|
|
8546
9189
|
}).strict(), (_c, b) => api.answer(b.turn, b.permission, b.decision));
|
|
9190
|
+
app.get("/api/conversations", (c) => answer(c, () => api.conversations()));
|
|
8547
9191
|
app.get("/api/conversation", (c) => answer(c, async () => await api.conversation(c.req.query("id") || void 0) ?? {
|
|
8548
9192
|
id: "",
|
|
8549
9193
|
turns: []
|
|
@@ -9134,7 +9778,7 @@ var Setup = class {
|
|
|
9134
9778
|
};
|
|
9135
9779
|
//#endregion
|
|
9136
9780
|
//#region packages/server/src/version.ts
|
|
9137
|
-
const VERSION = "0.
|
|
9781
|
+
const VERSION = "0.12.0";
|
|
9138
9782
|
//#endregion
|
|
9139
9783
|
//#region packages/server/src/git.ts
|
|
9140
9784
|
/** The top of the repository `folder` is in, or null: the nearest folder holding `.git`, a directory or, in a
|
|
@@ -9188,9 +9832,155 @@ function openStudyBase(path) {
|
|
|
9188
9832
|
};
|
|
9189
9833
|
}
|
|
9190
9834
|
//#endregion
|
|
9835
|
+
//#region packages/server/src/render.ts
|
|
9836
|
+
/** A Chromium on this machine: ISORITE_BROWSER, Playwright's own, or a browser installed in the usual places. */
|
|
9837
|
+
function findBrowser(env = process.env) {
|
|
9838
|
+
if (env.ISORITE_BROWSER && existsSync(env.ISORITE_BROWSER)) return env.ISORITE_BROWSER;
|
|
9839
|
+
const cache = env.PLAYWRIGHT_BROWSERS_PATH ?? (process.platform === "darwin" ? join(homedir(), "Library", "Caches", "ms-playwright") : process.platform === "win32" ? join(env.LOCALAPPDATA ?? "", "ms-playwright") : join(homedir(), ".cache", "ms-playwright"));
|
|
9840
|
+
const found = [];
|
|
9841
|
+
try {
|
|
9842
|
+
for (const d of readdirSync(cache).filter((d) => /^chromium(_headless_shell)?-\d+$/.test(d)).sort().reverse()) for (const p of [
|
|
9843
|
+
"chrome-linux/headless_shell",
|
|
9844
|
+
"chrome-headless-shell-linux64/chrome-headless-shell",
|
|
9845
|
+
"chrome-linux/chrome",
|
|
9846
|
+
"chrome-linux64/chrome",
|
|
9847
|
+
"chrome-mac/headless_shell",
|
|
9848
|
+
"chrome-headless-shell-mac-arm64/chrome-headless-shell",
|
|
9849
|
+
"chrome-mac/Chromium.app/Contents/MacOS/Chromium",
|
|
9850
|
+
"chrome-win/headless_shell.exe",
|
|
9851
|
+
"chrome-headless-shell-win64/chrome-headless-shell.exe",
|
|
9852
|
+
"chrome-win/chrome.exe"
|
|
9853
|
+
]) found.push(join(cache, d, p));
|
|
9854
|
+
} catch {}
|
|
9855
|
+
found.push("/usr/bin/chromium", "/usr/bin/chromium-browser", "/usr/bin/google-chrome", "/usr/bin/google-chrome-stable", "/snap/bin/chromium", "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", "/Applications/Chromium.app/Contents/MacOS/Chromium", join(env["PROGRAMFILES"] ?? "C:\\Program Files", "Google", "Chrome", "Application", "chrome.exe"), join(env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)", "Microsoft", "Edge", "Application", "msedge.exe"));
|
|
9856
|
+
return found.find((p) => existsSync(p)) ?? null;
|
|
9857
|
+
}
|
|
9858
|
+
/** The host: a sandboxed frame given the document, and the bridge, answering files through window.isoRead. */
|
|
9859
|
+
const HOST = `(doc, ground) => {
|
|
9860
|
+
const f = document.getElementById("f");
|
|
9861
|
+
window.said = []; window.ready = false;
|
|
9862
|
+
const tell = (m, t = []) => f.contentWindow.postMessage({ isorite: 0, ...m }, "*", t);
|
|
9863
|
+
addEventListener("message", async (e) => {
|
|
9864
|
+
const m = e.data;
|
|
9865
|
+
if (e.source !== f.contentWindow || !m || m.isorite !== 0) return;
|
|
9866
|
+
if (m.type === "ready") { window.ready = true; tell({ type: "ground", ground }); tell({ type: "look", look: "isorite" }); }
|
|
9867
|
+
else if (m.type === "file" || m.type === "bytes") {
|
|
9868
|
+
const got = await window.isoRead(m.path);
|
|
9869
|
+
if (got.error) tell({ type: "reply", id: m.id, error: got.error });
|
|
9870
|
+
else if (m.type === "bytes") { const buf = got.b64 !== undefined ? Uint8Array.from(atob(got.b64), (c) => c.charCodeAt(0)).buffer : new TextEncoder().encode(got.text).buffer; tell({ type: "reply", id: m.id, bytes: buf }, [buf]); }
|
|
9871
|
+
else tell({ type: "reply", id: m.id, text: got.text ?? atob(got.b64) });
|
|
9872
|
+
}
|
|
9873
|
+
else if (m.type === "height") f.style.height = Math.min(8000, Math.ceil(m.height) + 4) + "px";
|
|
9874
|
+
else if (m.type === "error") window.said.push(String(m.message));
|
|
9875
|
+
});
|
|
9876
|
+
f.srcdoc = doc;
|
|
9877
|
+
}`;
|
|
9878
|
+
/** What it drew: SVG marks, a table's cells, and 100 for each canvas with anything on it (a WebGL one, if it has a size). */
|
|
9879
|
+
const DRAWN = `(() => {
|
|
9880
|
+
let n = document.querySelectorAll("svg path, svg line, svg circle, svg rect, svg polyline, svg polygon, svg text, iso-table td").length;
|
|
9881
|
+
for (const c of document.querySelectorAll("canvas")) {
|
|
9882
|
+
try {
|
|
9883
|
+
const g = c.getContext("2d");
|
|
9884
|
+
if (g) { const d = g.getImageData(0, 0, c.width, c.height).data; if (d.some((v, i) => v !== d[i % 4])) n += 100; }
|
|
9885
|
+
else n += 100;
|
|
9886
|
+
} catch { n += 100; }
|
|
9887
|
+
}
|
|
9888
|
+
return n;
|
|
9889
|
+
})()`;
|
|
9890
|
+
/** Every control touched: sliders to three quarters, choices to their second, buttons pressed, a drag on a canvas. */
|
|
9891
|
+
const TOUCH = `(async () => {
|
|
9892
|
+
for (const r of document.querySelectorAll("input[type=range]")) { r.value = String(+r.min + (+r.max - +r.min) * 0.75); r.dispatchEvent(new Event("input", { bubbles: true })); r.dispatchEvent(new Event("change", { bubbles: true })); }
|
|
9893
|
+
for (const s of document.querySelectorAll("select")) if (s.options.length > 1) { s.selectedIndex = 1; s.dispatchEvent(new Event("change", { bubbles: true })); }
|
|
9894
|
+
for (const b of document.querySelectorAll("button, input[type=checkbox]")) if (!b.closest("iso-note")) b.click();
|
|
9895
|
+
await new Promise((r) => setTimeout(r, 400));
|
|
9896
|
+
const c = document.querySelector("canvas");
|
|
9897
|
+
if (c) {
|
|
9898
|
+
const r = c.getBoundingClientRect(), at = (t, x) => c.dispatchEvent(new PointerEvent(t, { clientX: r.left + x, clientY: r.top + r.height / 2, bubbles: true, pointerId: 1 }));
|
|
9899
|
+
at("pointerdown", r.width / 2); at("pointermove", r.width / 2 + 60); at("pointerup", r.width / 2 + 60);
|
|
9900
|
+
c.dispatchEvent(new MouseEvent("click", { clientX: r.left + r.width / 3, clientY: r.top + r.height / 3, bubbles: true }));
|
|
9901
|
+
}
|
|
9902
|
+
})()`;
|
|
9903
|
+
let launched = null;
|
|
9904
|
+
async function browser(path) {
|
|
9905
|
+
launched ??= (async () => {
|
|
9906
|
+
const { chromium } = await import("playwright-core");
|
|
9907
|
+
return chromium.launch({
|
|
9908
|
+
executablePath: path,
|
|
9909
|
+
args: [
|
|
9910
|
+
"--use-gl=angle",
|
|
9911
|
+
"--use-angle=swiftshader",
|
|
9912
|
+
"--enable-unsafe-swiftshader"
|
|
9913
|
+
]
|
|
9914
|
+
});
|
|
9915
|
+
})().catch((e) => {
|
|
9916
|
+
launched = null;
|
|
9917
|
+
throw e;
|
|
9918
|
+
});
|
|
9919
|
+
return launched;
|
|
9920
|
+
}
|
|
9921
|
+
/** Render an assembled artifact (packages/kit's assemble) unseen, answer its bridge from `read` for the files in
|
|
9922
|
+
* `uses`, touch its controls, and say what happened. */
|
|
9923
|
+
async function renderArtifact(doc, uses, read, opts) {
|
|
9924
|
+
const b = await browser(opts.executable), width = opts.width ?? 1100;
|
|
9925
|
+
const page = await b.newPage({ viewport: {
|
|
9926
|
+
width,
|
|
9927
|
+
height: 900
|
|
9928
|
+
} });
|
|
9929
|
+
const errors = [];
|
|
9930
|
+
page.on("console", (m) => {
|
|
9931
|
+
if (m.type?.() === "error") errors.push(m.text());
|
|
9932
|
+
});
|
|
9933
|
+
page.on("pageerror", (e) => errors.push(e.message ?? String(e)));
|
|
9934
|
+
const work = (async () => {
|
|
9935
|
+
await page.exposeFunction("isoRead", async (rel) => {
|
|
9936
|
+
if (!uses.includes(rel)) return { error: `${rel} is not in the artifact's manifest (isorite:uses)` };
|
|
9937
|
+
const got = await read(rel);
|
|
9938
|
+
if (got === null) return { error: `${rel}: no such file beside the artifact` };
|
|
9939
|
+
return typeof got === "string" ? { text: got } : { b64: Buffer.from(got).toString("base64") };
|
|
9940
|
+
});
|
|
9941
|
+
await page.setContent(`<!doctype html><html><body style="margin:0;background:${opts.ground === "quartz" ? "#F2F0EA" : "#07101C"}"><iframe id="f" sandbox="allow-scripts" style="display:block;width:${width}px;height:900px;border:0"></iframe></body></html>`);
|
|
9942
|
+
await page.evaluate(`(${HOST})(${JSON.stringify(doc)}, ${JSON.stringify(opts.ground ?? "lapis")})`);
|
|
9943
|
+
await page.waitForTimeout(2e3);
|
|
9944
|
+
const ready = await page.evaluate("window.ready");
|
|
9945
|
+
const frame = page.frames().find((fr) => fr !== page.mainFrame());
|
|
9946
|
+
const height = await page.evaluate("document.getElementById(\"f\").offsetHeight");
|
|
9947
|
+
const drawn = frame ? await frame.evaluate(DRAWN) : 0;
|
|
9948
|
+
let screenshot;
|
|
9949
|
+
if (opts.screenshot) {
|
|
9950
|
+
await page.setViewportSize({
|
|
9951
|
+
width,
|
|
9952
|
+
height: Math.max(400, Math.min(height, 4e3))
|
|
9953
|
+
});
|
|
9954
|
+
screenshot = await page.screenshot({ fullPage: true });
|
|
9955
|
+
}
|
|
9956
|
+
if (frame) await frame.evaluate(TOUCH);
|
|
9957
|
+
await page.waitForTimeout(600);
|
|
9958
|
+
const said = await page.evaluate("window.said");
|
|
9959
|
+
return {
|
|
9960
|
+
rendered: ready,
|
|
9961
|
+
errors: [.../* @__PURE__ */ new Set([...said, ...errors])].slice(0, 12),
|
|
9962
|
+
drawn,
|
|
9963
|
+
height,
|
|
9964
|
+
...screenshot ? { screenshot } : {}
|
|
9965
|
+
};
|
|
9966
|
+
})();
|
|
9967
|
+
const timeout = new Promise((resolve) => setTimeout(() => resolve({
|
|
9968
|
+
rendered: false,
|
|
9969
|
+
errors: ["it didn't finish starting within the time allowed: a loop that never ends?"],
|
|
9970
|
+
drawn: 0,
|
|
9971
|
+
height: 0
|
|
9972
|
+
}), opts.timeout ?? 2e4));
|
|
9973
|
+
try {
|
|
9974
|
+
return await Promise.race([work, timeout]);
|
|
9975
|
+
} finally {
|
|
9976
|
+
await page.close().catch(() => void 0);
|
|
9977
|
+
}
|
|
9978
|
+
}
|
|
9979
|
+
//#endregion
|
|
9191
9980
|
//#region packages/server/src/serve.ts
|
|
9192
9981
|
init_src$1();
|
|
9193
9982
|
init_src();
|
|
9983
|
+
init_src$2();
|
|
9194
9984
|
/** The built app: in a checkout, where npm run build puts it; installed from npm, in the package's web/. */
|
|
9195
9985
|
const WEB_DIR = [join(dirname(fileURLToPath(import.meta.url)), "..", "web"), join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "app", "build")].find((dir) => existsSync(join(dir, "index.html"))) ?? join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..", "app", "build");
|
|
9196
9986
|
const moveIt = (study) => `${study} is a study base, which this isorite no longer reads (format 1). Move it into its notes with: isorite migrate ${study}`;
|
|
@@ -9248,9 +10038,28 @@ async function openEngine({ notes }, device = deviceId(), config = configPath(),
|
|
|
9248
10038
|
name: `vectors-${createHash("sha256").update(notes).digest("hex").slice(0, 16)}.json`
|
|
9249
10039
|
}
|
|
9250
10040
|
} : {},
|
|
9251
|
-
...extras.elsewhere ? { elsewhere: extras.elsewhere } : {}
|
|
10041
|
+
...extras.elsewhere ? { elsewhere: extras.elsewhere } : {},
|
|
10042
|
+
renderArtifact: (path, html) => runArtifact(notes, path, html)
|
|
9252
10043
|
});
|
|
9253
10044
|
}
|
|
10045
|
+
/** Run an artifact unseen: null when there's no browser, or no built kit, to run it with. */
|
|
10046
|
+
async function runArtifact(notes, path, html) {
|
|
10047
|
+
const executable = findBrowser(), kitDir = join(WEB_DIR, "kit");
|
|
10048
|
+
if (!executable || !existsSync(join(kitDir, "kit.js"))) return null;
|
|
10049
|
+
const kit = Object.fromEntries(KIT_FILES.map((f) => [f, readFileSync(join(kitDir, f), "utf8")]));
|
|
10050
|
+
const dir = dirname(resolve(notes, path)), uses = manifestOf(html).uses;
|
|
10051
|
+
const read = async (rel) => {
|
|
10052
|
+
const at = resolve(dir, rel);
|
|
10053
|
+
if (!at.startsWith(resolve(notes) + sep) || !existsSync(at)) return null;
|
|
10054
|
+
return /\.(csv|tsv|json|jsonl|ndjson|txt|gltf|obj)$/i.test(at) ? readFileSync(at, "utf8") : new Uint8Array(readFileSync(at));
|
|
10055
|
+
};
|
|
10056
|
+
const got = await renderArtifact(assemble(html, kit), uses, read, { executable });
|
|
10057
|
+
return {
|
|
10058
|
+
rendered: got.rendered,
|
|
10059
|
+
errors: got.errors,
|
|
10060
|
+
drawn: got.drawn
|
|
10061
|
+
};
|
|
10062
|
+
}
|
|
9254
10063
|
/** The guide's finder, as a provider: no key, no sign-in, and a name that says what it is. */
|
|
9255
10064
|
const { auth: _auth, ...openrouter } = OPENROUTER;
|
|
9256
10065
|
const FINDER = {
|
|
@@ -9981,6 +10790,8 @@ async function doctor(folder, o = {}) {
|
|
|
9981
10790
|
say("browser pages", asking.length ? "fail" : "ok", asking.length ? `${asking.join(" and ")} asked for a password (401, Basic): that is not isorite's server; stop it, or give isorite another port (--port <n>)` : `isorite's pages at ${url}/ ask for no password. ${hint}`);
|
|
9982
10791
|
}
|
|
9983
10792
|
if (env.SSH_CONNECTION) say("browser", "ok", `over SSH: on your own machine, ssh -L ${port}:localhost:${port} <this host>, then http://localhost:${port}/`);
|
|
10793
|
+
const chrome = findBrowser(env);
|
|
10794
|
+
say("artifacts", chrome ? "ok" : "warn", chrome ? `run unseen when Iso writes one, in ${chrome}` : "no Chromium found to run them unseen, so only their rules are checked when Iso writes one; ISORITE_BROWSER names one (Chrome, Chromium, Edge)");
|
|
9984
10795
|
let model = null;
|
|
9985
10796
|
if (env.ISORITE_EMBED === "off") say("search by meaning", "skip", "switched off (ISORITE_EMBED=off)");
|
|
9986
10797
|
else {
|
|
@@ -11057,7 +11868,7 @@ const ISO_QUESTIONS = {
|
|
|
11057
11868
|
about: [TRIANGLE]
|
|
11058
11869
|
}
|
|
11059
11870
|
};
|
|
11060
|
-
/** Iso's
|
|
11871
|
+
/** Iso's answer to where you stand on Gauss–Bonnet, each sentence resting on what you can open. */
|
|
11061
11872
|
const READING = `You can state it and use it: the [examples](/${BONNET}.md) are yours, and you [solved the torus](/${TORUS}.md). [The proof sketch](/${BONNET}.md) is still mine, untouched: you have not yet said it in your own words. [Where compactness is used](/${COMPACT}.md) is open, and so is [the triangle](/${TRIANGLE}.md), whose solution leans on a step not yet shown.`;
|
|
11062
11873
|
const anchor = (text) => ({
|
|
11063
11874
|
text,
|
|
@@ -11306,7 +12117,6 @@ async function buildGeometry(sources, code, now = Date.now()) {
|
|
|
11306
12117
|
await write(CYLINDER, final(CYLINDER));
|
|
11307
12118
|
await engine.setDone(CYLINDER, true, (await engine.readNote(CYLINDER)).version);
|
|
11308
12119
|
});
|
|
11309
|
-
on(1, 55, async () => engine.saveScratchpad(BONNET, "Compact: I think it is only that the triangulation is finite. Check against the Möbius band, which is compact and still fails, so orientation must be a separate matter.", null));
|
|
11310
12120
|
on(0, -40, async () => {
|
|
11311
12121
|
replies.push({
|
|
11312
12122
|
calls: [{
|
|
@@ -11322,34 +12132,20 @@ async function buildGeometry(sources, code, now = Date.now()) {
|
|
|
11322
12132
|
usd: .0071
|
|
11323
12133
|
}
|
|
11324
12134
|
}, {
|
|
11325
|
-
|
|
11326
|
-
name: "write_understanding",
|
|
11327
|
-
arguments: {
|
|
11328
|
-
id: BONNET,
|
|
11329
|
-
reading: READING
|
|
11330
|
-
}
|
|
11331
|
-
}],
|
|
12135
|
+
text: READING,
|
|
11332
12136
|
usage: {
|
|
11333
12137
|
in: 7200,
|
|
11334
12138
|
out: 150,
|
|
11335
12139
|
cached: 4200,
|
|
11336
12140
|
usd: .0103
|
|
11337
12141
|
}
|
|
11338
|
-
}, {
|
|
11339
|
-
text: "I have written my reading of you on Gauss–Bonnet.",
|
|
11340
|
-
usage: {
|
|
11341
|
-
in: 7500,
|
|
11342
|
-
out: 20,
|
|
11343
|
-
cached: 7e3,
|
|
11344
|
-
usd: .0037
|
|
11345
|
-
}
|
|
11346
12142
|
});
|
|
11347
12143
|
const { turn: id } = await engine.converse({
|
|
11348
12144
|
mode: "ask",
|
|
11349
12145
|
asked: "where do I stand on Gauss–Bonnet?",
|
|
11350
12146
|
tier: "mid",
|
|
11351
12147
|
given: [{
|
|
11352
|
-
kind: "
|
|
12148
|
+
kind: "note",
|
|
11353
12149
|
id: BONNET
|
|
11354
12150
|
}]
|
|
11355
12151
|
});
|
|
@@ -11377,14 +12173,14 @@ const finderHttp = (pace = 0) => scriptedHttp(finderAnswer, pace);
|
|
|
11377
12173
|
//#region packages/guide/src/node.ts
|
|
11378
12174
|
/** In a checkout, the package's notes/; in the npm package, where this is bundled into dist/, the guide/ beside it. */
|
|
11379
12175
|
const GUIDE_DIR = [join(import.meta.dirname, "..", "guide"), join(import.meta.dirname, "..", "notes")].find((d) => existsSync(join(d, "start-here.md"))) ?? join(import.meta.dirname, "..", "notes");
|
|
11380
|
-
/** The guide's
|
|
12176
|
+
/** The guide's files that aren't notes, by path, as bytes: its pictures (M22), and its artifacts and their data (M23). */
|
|
11381
12177
|
function loadGuidePictures(dir = GUIDE_DIR) {
|
|
11382
12178
|
const out = {};
|
|
11383
12179
|
const visit = (at) => {
|
|
11384
12180
|
for (const name of readdirSync(at)) {
|
|
11385
12181
|
const path = join(at, name);
|
|
11386
12182
|
if (statSync(path).isDirectory()) visit(path);
|
|
11387
|
-
else if (/\.(png|jpe?g|gif|svg|webp|avif)$/i.test(name)) out[relative(dir, path).split(sep).join("/")] = new Uint8Array(readFileSync(path));
|
|
12183
|
+
else if (/\.(png|jpe?g|gif|svg|webp|avif|html|csv|tsv|json|jsonl|stl|glb|gltf|obj|pdf)$/i.test(name)) out[relative(dir, path).split(sep).join("/")] = new Uint8Array(readFileSync(path));
|
|
11388
12184
|
}
|
|
11389
12185
|
};
|
|
11390
12186
|
visit(dir);
|
|
@@ -11606,6 +12402,7 @@ function bases(args, { config = configPath(), out = console.log } = {}) {
|
|
|
11606
12402
|
}
|
|
11607
12403
|
//#endregion
|
|
11608
12404
|
//#region packages/server/src/main.ts
|
|
12405
|
+
init_src$2();
|
|
11609
12406
|
init_src$1();
|
|
11610
12407
|
init_model();
|
|
11611
12408
|
const USAGE = `isorite: an agentic harness for understanding
|
|
@@ -11658,8 +12455,29 @@ function check(notes, out = console.log, unused = false) {
|
|
|
11658
12455
|
const bundle = loadBundle(root), issues = bundle.lint(), cycles = bundle.requiresCycles();
|
|
11659
12456
|
for (const i of issues) out(`${i.level.padEnd(7)} ${i.path}: ${i.message}`);
|
|
11660
12457
|
for (const c of cycles) out(`warning requires cycle: ${c.join(" -> ")}`);
|
|
11661
|
-
|
|
11662
|
-
|
|
12458
|
+
let artifactErrors = 0, artifactWarnings = 0;
|
|
12459
|
+
for (const f of [...bundle.files].filter((x) => citeKind(x) === "artifact").sort()) {
|
|
12460
|
+
const html = readFileSync(join(root, f), "utf8");
|
|
12461
|
+
if (manifestOf(html).kit === null) continue;
|
|
12462
|
+
const dir = dirname(join(root, f)), files = manifestOf(html).uses.filter((u) => existsSync(join(dir, u)));
|
|
12463
|
+
const columns = Object.fromEntries(files.filter((u) => citeKind(u) === "data").flatMap((u) => {
|
|
12464
|
+
try {
|
|
12465
|
+
return [[u, columnsOf(parseData(u, readFileSync(join(dir, u), "utf8")))]];
|
|
12466
|
+
} catch {
|
|
12467
|
+
return [];
|
|
12468
|
+
}
|
|
12469
|
+
}));
|
|
12470
|
+
for (const b of checkArtifact(html, {
|
|
12471
|
+
files,
|
|
12472
|
+
columns
|
|
12473
|
+
})) {
|
|
12474
|
+
out(`${b.level.padEnd(7)} ${f}: ${b.message}`);
|
|
12475
|
+
if (b.level === "error") artifactErrors++;
|
|
12476
|
+
else artifactWarnings++;
|
|
12477
|
+
}
|
|
12478
|
+
}
|
|
12479
|
+
const errors = issues.filter((i) => i.level === "error").length + artifactErrors;
|
|
12480
|
+
out(`${bundle.concepts.size} notes in ${bundle.directories.size} folders; ${errors} errors, ${issues.length - issues.filter((i) => i.level === "error").length + artifactWarnings} warnings, ${cycles.length} requires cycles`);
|
|
11663
12481
|
if (unused) {
|
|
11664
12482
|
const cited = new Set([...bundle.concepts.values()].flatMap((c) => c.cites.map((x) => x.target)));
|
|
11665
12483
|
const idle = [...bundle.files].filter((f) => citeKind(f) === "image" && !cited.has(f)).sort();
|