nodality 1.2.9 → 1.2.11
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/bin/mcp-server.mjs +86 -0
- package/bin/nodality.js +12 -0
- package/bin/schema-cli.mjs +72 -0
- package/dist/animator.cjs.js.LICENSE.txt +1 -1
- package/dist/animator.esm.js.LICENSE.txt +1 -1
- package/dist/audionew.cjs.js.LICENSE.txt +1 -1
- package/dist/audionew.esm.js.LICENSE.txt +1 -1
- package/dist/base.cjs.js.LICENSE.txt +1 -1
- package/dist/base.esm.js.LICENSE.txt +1 -1
- package/dist/beta-desktop-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/beta-desktop-bar.esm.js.LICENSE.txt +1 -1
- package/dist/beta-mobile-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/beta-mobile-bar.esm.js.LICENSE.txt +1 -1
- package/dist/bundle.umd.js +1 -1
- package/dist/bundle.umd.js.LICENSE.txt +1 -28
- package/dist/button.cjs.js.LICENSE.txt +1 -1
- package/dist/button.esm.js.LICENSE.txt +1 -1
- package/dist/card-getter.cjs.js.LICENSE.txt +1 -1
- package/dist/card-getter.esm.js.LICENSE.txt +1 -1
- package/dist/center.cjs.js.LICENSE.txt +1 -1
- package/dist/center.esm.js.LICENSE.txt +1 -1
- package/dist/checkbox.cjs.js.LICENSE.txt +1 -1
- package/dist/checkbox.esm.js.LICENSE.txt +1 -1
- package/dist/code.cjs.js.LICENSE.txt +1 -1
- package/dist/code.esm.js.LICENSE.txt +1 -1
- package/dist/container.cjs.js.LICENSE.txt +1 -1
- package/dist/container.esm.js.LICENSE.txt +1 -1
- package/dist/data-list.cjs.js.LICENSE.txt +1 -1
- package/dist/data-list.esm.js.LICENSE.txt +1 -1
- package/dist/designer.cjs.js +1 -1
- package/dist/designer.cjs.js.LICENSE.txt +1 -28
- package/dist/designer.esm.js +1 -1
- package/dist/designer.esm.js.LICENSE.txt +1 -28
- package/dist/element-mapper.cjs.js +1 -1
- package/dist/element-mapper.cjs.js.LICENSE.txt +1 -1
- package/dist/element-mapper.esm.js +1 -1
- package/dist/element-mapper.esm.js.LICENSE.txt +1 -1
- package/dist/finalresult.esm.js +1 -1
- package/dist/finalresult.esm.js.LICENSE.txt +1 -28
- package/dist/flex-card.cjs.js.LICENSE.txt +1 -1
- package/dist/flex-card.esm.js.LICENSE.txt +1 -1
- package/dist/flex-grid.cjs.js.LICENSE.txt +1 -1
- package/dist/flex-grid.esm.js.LICENSE.txt +1 -1
- package/dist/flex-row.cjs.js.LICENSE.txt +1 -1
- package/dist/flex-row.esm.js.LICENSE.txt +1 -1
- package/dist/floating-input.cjs.js.LICENSE.txt +1 -1
- package/dist/floating-input.esm.js.LICENSE.txt +1 -1
- package/dist/free.cjs.js.LICENSE.txt +1 -1
- package/dist/free.esm.js.LICENSE.txt +1 -1
- package/dist/horizontal-scroller.cjs.js.LICENSE.txt +1 -1
- package/dist/horizontal-scroller.esm.js.LICENSE.txt +1 -1
- package/dist/image-picker.cjs.js.LICENSE.txt +1 -1
- package/dist/image-picker.esm.js.LICENSE.txt +1 -1
- package/dist/image.cjs.js.LICENSE.txt +1 -1
- package/dist/image.esm.js.LICENSE.txt +1 -1
- package/dist/index.cjs.js +1 -1
- package/dist/index.cjs.js.LICENSE.txt +1 -28
- package/dist/index.d.ts +1 -1
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.LICENSE.txt +1 -28
- package/dist/keyframe-animation.cjs.js.LICENSE.txt +1 -1
- package/dist/keyframe-animation.esm.js.LICENSE.txt +1 -1
- package/dist/link-getter.cjs.js.LICENSE.txt +1 -1
- package/dist/link-getter.esm.js.LICENSE.txt +1 -1
- package/dist/link.cjs.js.LICENSE.txt +1 -1
- package/dist/link.esm.js.LICENSE.txt +1 -1
- package/dist/meta-adder.cjs.js.LICENSE.txt +1 -1
- package/dist/meta-adder.esm.js.LICENSE.txt +1 -1
- package/dist/modal-2025.cjs.js.LICENSE.txt +1 -1
- package/dist/modal-2025.esm.js.LICENSE.txt +1 -1
- package/dist/multiswitcher.cjs.js.LICENSE.txt +1 -1
- package/dist/multiswitcher.esm.js.LICENSE.txt +1 -1
- package/dist/new-nav-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/new-nav-bar.esm.js.LICENSE.txt +1 -1
- package/dist/picker.cjs.js.LICENSE.txt +1 -1
- package/dist/picker.esm.js.LICENSE.txt +1 -1
- package/dist/progress.cjs.js.LICENSE.txt +1 -1
- package/dist/progress.esm.js.LICENSE.txt +1 -1
- package/dist/radio.cjs.js.LICENSE.txt +1 -1
- package/dist/radio.esm.js.LICENSE.txt +1 -1
- package/dist/range.cjs.js.LICENSE.txt +1 -1
- package/dist/range.esm.js.LICENSE.txt +1 -1
- package/dist/scroll-video.cjs.js.LICENSE.txt +1 -1
- package/dist/scroll-video.esm.js.LICENSE.txt +1 -1
- package/dist/side-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/side-bar.esm.js.LICENSE.txt +1 -1
- package/dist/side-nav-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/side-nav-bar.esm.js.LICENSE.txt +1 -1
- package/dist/simple-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/simple-bar.esm.js.LICENSE.txt +1 -1
- package/dist/slider-2025.cjs.js.LICENSE.txt +1 -1
- package/dist/slider-2025.esm.js.LICENSE.txt +1 -1
- package/dist/spacer.cjs.js.LICENSE.txt +1 -1
- package/dist/spacer.esm.js.LICENSE.txt +1 -1
- package/dist/stack.cjs.js.LICENSE.txt +1 -1
- package/dist/stack.esm.js.LICENSE.txt +1 -1
- package/dist/stacker.cjs.js.LICENSE.txt +1 -1
- package/dist/stacker.esm.js.LICENSE.txt +1 -1
- package/dist/table.cjs.js.LICENSE.txt +1 -1
- package/dist/table.esm.js.LICENSE.txt +1 -1
- package/dist/text-field.cjs.js.LICENSE.txt +1 -1
- package/dist/text-field.esm.js.LICENSE.txt +1 -1
- package/dist/text.cjs.js.LICENSE.txt +1 -1
- package/dist/text.esm.js.LICENSE.txt +1 -1
- package/dist/theme.cjs.js.LICENSE.txt +1 -1
- package/dist/theme.esm.js.LICENSE.txt +1 -1
- package/dist/transform-anim.cjs.js.LICENSE.txt +1 -1
- package/dist/transform-anim.esm.js.LICENSE.txt +1 -1
- package/dist/ulist.cjs.js.LICENSE.txt +1 -1
- package/dist/ulist.esm.js.LICENSE.txt +1 -1
- package/dist/video.cjs.js.LICENSE.txt +1 -1
- package/dist/video.esm.js.LICENSE.txt +1 -1
- package/dist/wrap.cjs.js.LICENSE.txt +1 -1
- package/dist/wrap.esm.js.LICENSE.txt +1 -1
- package/dist/zoom-card.cjs.js.LICENSE.txt +1 -1
- package/dist/zoom-card.esm.js.LICENSE.txt +1 -1
- package/layout/animator.js +1 -1
- package/layout/audio.js +1 -1
- package/layout/audionew.js +1 -1
- package/layout/base.js +1 -1
- package/layout/beta-desktop-bar.js +1 -1
- package/layout/beta-mobile-bar.js +1 -1
- package/layout/button.js +1 -1
- package/layout/center.js +1 -1
- package/layout/checkbox.js +1 -1
- package/layout/circle.js +1 -1
- package/layout/code.js +1 -1
- package/layout/container.js +1 -1
- package/layout/dropdown-2025.js +1 -1
- package/layout/flex-card.js +1 -1
- package/layout/flex-grid.js +1 -1
- package/layout/flex-row.js +1 -1
- package/layout/form-components/custom.js +1 -1
- package/layout/form-components/data-list.js +1 -1
- package/layout/form-components/floating-input.js +1 -1
- package/layout/form-components/form-all.js +1 -1
- package/layout/form-components/form.js +1 -1
- package/layout/form-components/image-picker.js +1 -1
- package/layout/form-components/picker.js +1 -1
- package/layout/form-components/radio.js +1 -1
- package/layout/form-components/radiogroup.js +1 -1
- package/layout/form-components/range.js +1 -1
- package/layout/free.js +1 -1
- package/layout/grid-switcher.js +1 -1
- package/layout/grid.js +1 -1
- package/layout/horizontal-scroller.js +1 -1
- package/layout/image.js +1 -1
- package/layout/index.js +1 -1
- package/layout/link.js +1 -1
- package/layout/list.js +1 -1
- package/layout/meta-adder.js +1 -1
- package/layout/modal-2025.js +1 -1
- package/layout/morph.js +1 -1
- package/layout/multiswitcher.js +1 -1
- package/layout/nav-bar.js +1 -1
- package/layout/nav-factor/custom-div.js +1 -1
- package/layout/new-nav-bar.js +1 -1
- package/layout/polygon.js +1 -1
- package/layout/prerender-site.js +1 -1
- package/layout/prerender.js +1 -1
- package/layout/progress.js +1 -1
- package/layout/row.js +1 -1
- package/layout/scroll-video.js +1 -1
- package/layout/side-bar.js +1 -1
- package/layout/side-nav-bar.js +1 -1
- package/layout/simple-bar.js +1 -1
- package/layout/slider-2025.js +1 -1
- package/layout/spacer.js +1 -1
- package/layout/stack.js +1 -1
- package/layout/svg.js +1 -1
- package/layout/switcher.js +1 -1
- package/layout/table.js +1 -1
- package/layout/text-field.js +1 -1
- package/layout/text.js +1 -1
- package/layout/ulist.js +1 -1
- package/layout/video.js +1 -1
- package/layout/wrap.js +1 -1
- package/layout/zoom-card.js +1 -1
- package/lib/agent-surface.js +1 -1
- package/lib/card-getter.js +1 -1
- package/lib/codegen.js +1 -1
- package/lib/data.js +1 -1
- package/lib/designer.js +21 -1
- package/lib/element-mapper.js +173 -16
- package/lib/element-params.generated.js +11 -0
- package/lib/keyframe-animation.js +1 -1
- package/lib/link-getter.js +1 -1
- package/lib/morph-node.js +1 -1
- package/lib/parse-html.js +151 -0
- package/lib/raster-inspect.js +1 -1
- package/lib/raster-ops.js +1 -1
- package/lib/raster-presets.js +1 -1
- package/lib/scroll-video.js +1 -1
- package/lib/seo.js +1 -1
- package/lib/stacker.js +1 -1
- package/lib/suggest.js +1 -1
- package/lib/theme.js +1 -1
- package/lib/transform-anim.js +1 -1
- package/lib/transition.js +1 -1
- package/lib/validate-nodes.js +112 -2
- package/lib/webmcp-adapter.js +1 -1
- package/package.json +5 -2
- package/schema.json +7953 -0
- package/scripts/generate-schema.mjs +251 -0
- package/skills/nodality/SKILL.md +71 -1
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Stage 2 of AGENTIC-FIRST-PLAN.md — the machine-readable schema, DERIVED.
|
|
4
|
+
*
|
|
5
|
+
* A schema maintained beside the code drifts from it; the op registry did
|
|
6
|
+
* exactly that until 1.2.8, when `copy` turned out to read a parameter it
|
|
7
|
+
* never declared. So nothing here is hand-written. Every entry is recovered
|
|
8
|
+
* from the source that will actually run:
|
|
9
|
+
*
|
|
10
|
+
* types ELEMENT_TYPES in lib/element-mapper.js — the same list the
|
|
11
|
+
* validator checks against, so the two cannot disagree.
|
|
12
|
+
* dispatch the `obj.el.type === "x"` chain in mapType(), giving the
|
|
13
|
+
* mapper method for each type.
|
|
14
|
+
* components the `new X(` calls inside that method, resolved to files
|
|
15
|
+
* through element-mapper's own import statements.
|
|
16
|
+
* parameters `obj.<name>` reads in those files, comments stripped —
|
|
17
|
+
* the technique already proven in the docs repo's
|
|
18
|
+
* audit-options.mjs, which exists because a fifth of the
|
|
19
|
+
* `obj.*` references in these files are commented out.
|
|
20
|
+
* descriptions `//@ name: text` annotations, and `//@deprecated name: why`.
|
|
21
|
+
*
|
|
22
|
+
* What it will NOT do is guess. A type whose component cannot be resolved is
|
|
23
|
+
* emitted with `"resolved": false` and an empty parameter list rather than a
|
|
24
|
+
* plausible-looking one, because a schema that is confidently wrong is worse
|
|
25
|
+
* for an agent than a schema that admits a gap: the agent trusts it either
|
|
26
|
+
* way.
|
|
27
|
+
*
|
|
28
|
+
* node scripts/generate-schema.mjs # write schema.json
|
|
29
|
+
* node scripts/generate-schema.mjs --check # exit 1 if it would change
|
|
30
|
+
* node scripts/generate-schema.mjs --type cards # print one type
|
|
31
|
+
*/
|
|
32
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
33
|
+
import { join, dirname } from "node:path";
|
|
34
|
+
import { fileURLToPath } from "node:url";
|
|
35
|
+
|
|
36
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
const ROOT = join(HERE, "..");
|
|
38
|
+
const MAPPER = join(ROOT, "lib", "element-mapper.js");
|
|
39
|
+
const OUT = join(ROOT, "schema.json");
|
|
40
|
+
|
|
41
|
+
const src = readFileSync(MAPPER, "utf8");
|
|
42
|
+
|
|
43
|
+
/** Strip comments line-wise, the way audit-options.mjs does. */
|
|
44
|
+
function liveLines(text) {
|
|
45
|
+
const out = [];
|
|
46
|
+
let inBlock = false;
|
|
47
|
+
for (let line of text.split("\n")) {
|
|
48
|
+
const t = line.trim();
|
|
49
|
+
if (inBlock) { if (t.includes("*/")) inBlock = false; continue; }
|
|
50
|
+
if (t.startsWith("/*")) { if (!t.includes("*/")) inBlock = true; continue; }
|
|
51
|
+
if (t.startsWith("//") || t.startsWith("*")) continue;
|
|
52
|
+
out.push(line.replace(/\/\/.*$/, ""));
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── 1. the authoritative type list ───────────────────────────────────
|
|
58
|
+
const typesBlock = src.slice(src.indexOf("const ELEMENT_TYPES = ["));
|
|
59
|
+
const TYPES = [...typesBlock.slice(0, typesBlock.indexOf("]")).matchAll(/"([^"]+)"/g)]
|
|
60
|
+
.map((m) => m[1]);
|
|
61
|
+
if (!TYPES.length) { console.error("could not read ELEMENT_TYPES"); process.exit(1); }
|
|
62
|
+
|
|
63
|
+
// ── 2. type -> mapper method, from the dispatch chain ────────────────
|
|
64
|
+
const mapperLive = liveLines(src).join("\n");
|
|
65
|
+
const DISPATCH = {};
|
|
66
|
+
{
|
|
67
|
+
// `obj.el.type === "x"` … `return this.mapY(obj)` — take the first return
|
|
68
|
+
// after each comparison, which is how the chain is written throughout.
|
|
69
|
+
const re = /obj\.el\.type\s*===\s*"([a-zA-Z0-9]+)"[\s\S]{0,200}?return\s+this\.([a-zA-Z0-9_]+)\(/g;
|
|
70
|
+
let m;
|
|
71
|
+
while ((m = re.exec(mapperLive))) if (!DISPATCH[m[1]]) DISPATCH[m[1]] = m[2];
|
|
72
|
+
|
|
73
|
+
// The text family is not dispatched by comparison but by membership:
|
|
74
|
+
// let headings = ["h1", … , "p"]; if (headings.includes(obj.el.type))
|
|
75
|
+
// Missing this left the seven most-used types unresolved, which would
|
|
76
|
+
// have been the schema's largest and least excusable hole.
|
|
77
|
+
const grp = /let\s+(\w+)\s*=\s*\[([^\]]+)\][\s\S]{0,120}?\1\.includes\(obj\.el\.type\)[\s\S]{0,120}?return\s+this\.([a-zA-Z0-9_]+)\(/g;
|
|
78
|
+
while ((m = grp.exec(mapperLive))) {
|
|
79
|
+
const method = m[3];
|
|
80
|
+
for (const t of [...m[2].matchAll(/"([^"]+)"/g)].map((x) => x[1])) {
|
|
81
|
+
if (!DISPATCH[t]) DISPATCH[t] = method;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── 3. mapper method -> component classes it constructs ──────────────
|
|
87
|
+
function methodBody(name) {
|
|
88
|
+
const at = mapperLive.indexOf(`static ${name}(`);
|
|
89
|
+
if (at < 0) return "";
|
|
90
|
+
// Balance braces from the method's opening brace.
|
|
91
|
+
let i = mapperLive.indexOf("{", at), depth = 0;
|
|
92
|
+
for (let j = i; j < mapperLive.length; j++) {
|
|
93
|
+
if (mapperLive[j] === "{") depth++;
|
|
94
|
+
else if (mapperLive[j] === "}") { depth--; if (!depth) return mapperLive.slice(i, j); }
|
|
95
|
+
}
|
|
96
|
+
return mapperLive.slice(i);
|
|
97
|
+
}
|
|
98
|
+
const classesIn = (body) =>
|
|
99
|
+
[...new Set([...body.matchAll(/new\s+([A-Z][A-Za-z0-9]*)\s*\(/g)].map((m) => m[1]))];
|
|
100
|
+
|
|
101
|
+
// ── 4. class -> file, from element-mapper's own imports ──────────────
|
|
102
|
+
const IMPORTS = {};
|
|
103
|
+
for (const m of src.matchAll(/import\s*\{([^}]+)\}\s*from\s*"([^"]+)"/g)) {
|
|
104
|
+
const file = m[2];
|
|
105
|
+
for (const raw of m[1].split(",")) {
|
|
106
|
+
const name = raw.trim().split(/\s+as\s+/).pop().trim();
|
|
107
|
+
if (name) IMPORTS[name] = file;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function fileFor(cls) {
|
|
111
|
+
const rel = IMPORTS[cls];
|
|
112
|
+
if (!rel) return null;
|
|
113
|
+
const p = join(ROOT, "lib", rel);
|
|
114
|
+
return existsSync(p) ? p : null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── 5. parameters a file actually reads ──────────────────────────────
|
|
118
|
+
const paramCache = new Map();
|
|
119
|
+
function paramsOf(file) {
|
|
120
|
+
if (paramCache.has(file)) return paramCache.get(file);
|
|
121
|
+
const text = readFileSync(file, "utf8");
|
|
122
|
+
const found = new Set();
|
|
123
|
+
for (const line of liveLines(text)) {
|
|
124
|
+
for (const m of line.matchAll(/\bobj\.([a-zA-Z][a-zA-Z0-9]*)/g)) found.add(m[1]);
|
|
125
|
+
for (const m of line.matchAll(/\bthis\.options\.([a-zA-Z][a-zA-Z0-9]*)/g)) found.add(m[1]);
|
|
126
|
+
}
|
|
127
|
+
for (const junk of ["options", "el", "customOptions", "storage", "i"]) found.delete(junk);
|
|
128
|
+
paramCache.set(file, found);
|
|
129
|
+
return found;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// ── 6. descriptions from //@ annotations, wherever they live ─────────
|
|
133
|
+
const DESCRIPTIONS = {}, DEPRECATED = {};
|
|
134
|
+
{
|
|
135
|
+
const files = new Set(Object.values(IMPORTS)
|
|
136
|
+
.map((rel) => join(ROOT, "lib", rel)).filter(existsSync));
|
|
137
|
+
files.add(MAPPER);
|
|
138
|
+
for (const f of files) {
|
|
139
|
+
for (const line of readFileSync(f, "utf8").split("\n")) {
|
|
140
|
+
let m = line.match(/\/\/@deprecated\s+([a-zA-Z][a-zA-Z0-9]*)\s*:\s*(.+)$/);
|
|
141
|
+
if (m) { DEPRECATED[m[1]] ??= m[2].trim(); continue; }
|
|
142
|
+
m = line.match(/\/\/@\s+([a-zA-Z][a-zA-Z0-9]*)\s*:\s*(.+)$/);
|
|
143
|
+
if (m) DESCRIPTIONS[m[1]] ??= m[2].trim();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── 7. assemble ──────────────────────────────────────────────────────
|
|
149
|
+
const schema = { generated: "scripts/generate-schema.mjs", types: {} };
|
|
150
|
+
let resolved = 0;
|
|
151
|
+
for (const type of TYPES) {
|
|
152
|
+
const method = DISPATCH[type] || null;
|
|
153
|
+
// A mapper's helpers are part of the mapper: `mapGrid` reads `items` only
|
|
154
|
+
// through `gridItemsSource`, so scanning the dispatched method alone
|
|
155
|
+
// reported that `cards` has no content slot. Follow `this.x(` one level.
|
|
156
|
+
const bodyOf = (name, seen = new Set()) => {
|
|
157
|
+
if (!name || seen.has(name)) return "";
|
|
158
|
+
seen.add(name);
|
|
159
|
+
const b = methodBody(name);
|
|
160
|
+
let out = b;
|
|
161
|
+
for (const m of b.matchAll(/\bthis\.([a-zA-Z][a-zA-Z0-9_]*)\s*\(/g)) {
|
|
162
|
+
if (seen.size < 12) out += "\n" + bodyOf(m[1], seen);
|
|
163
|
+
}
|
|
164
|
+
return out;
|
|
165
|
+
};
|
|
166
|
+
const body = method ? bodyOf(method) : "";
|
|
167
|
+
const classes = body ? classesIn(body) : [];
|
|
168
|
+
const files = classes.map(fileFor).filter(Boolean);
|
|
169
|
+
|
|
170
|
+
const params = new Set();
|
|
171
|
+
for (const f of files) for (const p of paramsOf(f)) params.add(p);
|
|
172
|
+
|
|
173
|
+
// The mapper's OWN `el.<name>` reads. Scanning components alone missed
|
|
174
|
+
// these, because a mapper often passes an element field as a constructor
|
|
175
|
+
// argument rather than an option — `new Text(el.text)`. That left `text`
|
|
176
|
+
// off every heading, which would have been the single worst error the
|
|
177
|
+
// schema could contain: the most-used parameter of the most-used type.
|
|
178
|
+
for (const m of body.matchAll(/\bel\.([a-zA-Z][a-zA-Z0-9]*)/g)) params.add(m[1]);
|
|
179
|
+
|
|
180
|
+
// Read off the element by every mapper regardless of component.
|
|
181
|
+
for (const p of ["type", "id"]) params.add(p);
|
|
182
|
+
|
|
183
|
+
const ok = files.length > 0;
|
|
184
|
+
if (ok) resolved++;
|
|
185
|
+
schema.types[type] = {
|
|
186
|
+
resolved: ok,
|
|
187
|
+
mapper: method,
|
|
188
|
+
components: classes,
|
|
189
|
+
params: [...params].sort().map((name) => ({
|
|
190
|
+
name,
|
|
191
|
+
...(DESCRIPTIONS[name] ? { description: DESCRIPTIONS[name] } : {}),
|
|
192
|
+
...(DEPRECATED[name] ? { deprecated: DEPRECATED[name] } : {}),
|
|
193
|
+
})),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
schema.summary = { types: TYPES.length, resolved, unresolved: TYPES.length - resolved };
|
|
197
|
+
|
|
198
|
+
// ── 8. modes ─────────────────────────────────────────────────────────
|
|
199
|
+
const args = process.argv.slice(2);
|
|
200
|
+
const json = JSON.stringify(schema, null, 2) + "\n";
|
|
201
|
+
|
|
202
|
+
if (args.includes("--type")) {
|
|
203
|
+
const t = args[args.indexOf("--type") + 1];
|
|
204
|
+
const entry = schema.types[t];
|
|
205
|
+
if (!entry) { console.error(`unknown type "${t}"`); process.exit(1); }
|
|
206
|
+
console.log(JSON.stringify({ type: t, ...entry }, null, 2));
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (args.includes("--check")) {
|
|
211
|
+
const current = existsSync(OUT) ? readFileSync(OUT, "utf8") : "";
|
|
212
|
+
if (current !== json) {
|
|
213
|
+
console.error("schema.json is out of date — run: node scripts/generate-schema.mjs");
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
console.log(`schema.json current (${resolved}/${TYPES.length} types resolved)`);
|
|
217
|
+
process.exit(0);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (args.includes("--stdout")) { process.stdout.write(json); process.exit(0); }
|
|
221
|
+
|
|
222
|
+
// The validator's vocabulary, emitted as a module so it ships in the bundle.
|
|
223
|
+
//
|
|
224
|
+
// The UNION of every type's parameters, not a per-type table. Per-type would
|
|
225
|
+
// be 20 kB in a zero-dependency bundle, and — worse — it would produce false
|
|
226
|
+
// positives: several mappers spread the whole element (`...el`) into their
|
|
227
|
+
// component, so they genuinely accept names no static scan can enumerate.
|
|
228
|
+
// Rejecting one of those would stop `preview` from rendering a page that
|
|
229
|
+
// works, which 1.2.7 established is the costlier direction to be wrong in.
|
|
230
|
+
//
|
|
231
|
+
// The union is used for NEAR-MISS detection only: `itms` is reported because
|
|
232
|
+
// it is one edit from `items`, while an unrecognised name with no close
|
|
233
|
+
// match is left alone. Per-type vocabularies are served on demand by
|
|
234
|
+
// `npx nodality schema <type>` — which is the point of Stage 2's property 2,
|
|
235
|
+
// schema on demand rather than schema in context.
|
|
236
|
+
{
|
|
237
|
+
const union = new Set();
|
|
238
|
+
for (const t of Object.values(schema.types)) for (const p of t.params) union.add(p.name);
|
|
239
|
+
const names = [...union].sort();
|
|
240
|
+
const mod = `// GENERATED by scripts/generate-schema.mjs — do not edit.
|
|
241
|
+
// Every parameter name any element type reads, recovered from source.
|
|
242
|
+
// Used for near-miss typo detection in validate-nodes.js. A drift test
|
|
243
|
+
// regenerates this and fails if it differs, so it cannot rot silently.
|
|
244
|
+
export const ELEMENT_PARAM_NAMES = ${JSON.stringify(names)};
|
|
245
|
+
`;
|
|
246
|
+
writeFileSync(join(ROOT, "lib", "element-params.generated.js"), mod);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
writeFileSync(OUT, json);
|
|
250
|
+
console.log(`schema.json written: ${TYPES.length} types, ${resolved} resolved, ` +
|
|
251
|
+
`${TYPES.length - resolved} unresolved`);
|
package/skills/nodality/SKILL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: nodality
|
|
3
|
-
description: Build or edit a page with the Nodality library – declarative static UI from two arrays (elements + nodes), GPU raster effects, morph navigation graphs, prerendering, and agent surfaces. Use when the user asks to create or change a Nodality page, mentions (E, N), Des(), morph/raster/agent-surface nodes, works with imperative Nodality code (new Text(), new Link(), .render()),
|
|
3
|
+
description: Build or edit a page with the Nodality library – declarative static UI from two arrays (elements + nodes), GPU raster effects, morph navigation graphs, prerendering, and agent surfaces. Use when the user asks to create or change a Nodality page, mentions (E, N), Des(), morph/raster/agent-surface nodes, works with imperative Nodality code (new Text(), new Link(), .render()), asks to draft a landing page with Nodality, or asks to EDIT an already-rendered Nodality page (parse it back to data, change it, re-render).
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Working with Nodality
|
|
@@ -45,6 +45,12 @@ drift. If the MCP is not configured, add it:
|
|
|
45
45
|
1. **`list_ops` first.** Never guess an op name, a parameter, an easing
|
|
46
46
|
name, a transition preset, or an element type. One call returns all
|
|
47
47
|
of them as data.
|
|
48
|
+
1b. **`get_schema` for the element you are about to write.** `list_ops`
|
|
49
|
+
gives the vocabulary of N; `get_schema` gives the vocabulary of E,
|
|
50
|
+
one type at a time – its parameters, recovered from the source the
|
|
51
|
+
components actually read. Ask for the type, not the whole schema:
|
|
52
|
+
that is the difference between paying for a schema once and paying
|
|
53
|
+
for it in every request.
|
|
48
54
|
2. **Author the pair.** E top-down (page structure), then N (what
|
|
49
55
|
happens to it). Small pages fit in one file.
|
|
50
56
|
3. **`validate_nodes` before showing the user anything.** Pass both
|
|
@@ -62,6 +68,36 @@ Skipping step 3 is the classic failure: a misspelled op renders
|
|
|
62
68
|
*nothing* rather than erroring, so the page looks plausible and is
|
|
63
69
|
silently missing its effects.
|
|
64
70
|
|
|
71
|
+
## Composites carry their content in a slot
|
|
72
|
+
|
|
73
|
+
A composite is not empty scaffolding – you give it content, and **which
|
|
74
|
+
key you use is part of the type**:
|
|
75
|
+
|
|
76
|
+
| slot | types |
|
|
77
|
+
|---|---|
|
|
78
|
+
| `items` | `cards`, `nav`, `sideNav`, `table`, `ulist` |
|
|
79
|
+
| `children` | `row`, `form`, `wrap` |
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
{ type: "cards", items: [ // shorthand
|
|
83
|
+
{ img: "a.jpg", title: "Alpha", link: "#a" },
|
|
84
|
+
[{ type: "h2", text: "Beta" }], // or nested specs
|
|
85
|
+
]}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`items` takes either shorthand entries or a nested list of element
|
|
89
|
+
specs, and one array may mix both. Use nested specs when the cards
|
|
90
|
+
differ from one another – that is the case where a data format wins by
|
|
91
|
+
the widest margin, and where writing it as code costs *more* than
|
|
92
|
+
hand-written JSX.
|
|
93
|
+
|
|
94
|
+
**Declaring content in the wrong slot renders the placeholders.** A
|
|
95
|
+
composite given nothing falls back to sample content, so
|
|
96
|
+
`{type:"table", children:[…]}` produces a plausible-looking table of
|
|
97
|
+
someone else's data rather than an error. `validate_nodes` reports this
|
|
98
|
+
as `WRONG_CONTENT_SLOT` and names the slot that carries content – which
|
|
99
|
+
is another reason step 3 is not optional. `get_schema` names it too.
|
|
100
|
+
|
|
65
101
|
## The four node families
|
|
66
102
|
|
|
67
103
|
Told apart by the shape of `op`:
|
|
@@ -159,6 +195,40 @@ One naming caution: `Text` and `Image` collide with DOM constructor
|
|
|
159
195
|
names. Always use the module import; on a page using the CDN globals,
|
|
160
196
|
never assume `window.Text` / `window.Image` are still the DOM's own.
|
|
161
197
|
|
|
198
|
+
## Editing a page that already exists
|
|
199
|
+
|
|
200
|
+
Editing is the common case in production, and it does not mean
|
|
201
|
+
regenerating. Read the page back to data, change the one thing, render
|
|
202
|
+
again:
|
|
203
|
+
|
|
204
|
+
```js
|
|
205
|
+
import { parseHTML } from "nodality/parse";
|
|
206
|
+
|
|
207
|
+
const spec = parseHTML(html); // or the parse_html MCP tool
|
|
208
|
+
spec[0].items[1].title = "Gamma"; // change one card
|
|
209
|
+
new Des().nodes([]).add(spec).set({ mount: "#mount", annotate: true });
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**Render with `{annotate: true}` if the page will ever be edited.** It
|
|
213
|
+
writes each descriptor onto the node it produced, and that is what makes
|
|
214
|
+
the read exact – every type, every option, nothing inferred. It is
|
|
215
|
+
opt-in and off by default because it puts attributes in the output.
|
|
216
|
+
|
|
217
|
+
Without annotation only what the tag settles comes back: headings,
|
|
218
|
+
paragraphs, links, images, lists. Thirteen composite types render as a
|
|
219
|
+
bare `<div>` with nothing to tell them apart, so those are reported
|
|
220
|
+
**unrecovered rather than guessed** – a wrong guess would silently
|
|
221
|
+
become a different page. Use `parseReport` (or the `parse_html` tool)
|
|
222
|
+
rather than `parseHTML` when you need to know which you got:
|
|
223
|
+
|
|
224
|
+
```js
|
|
225
|
+
const { ok, exact, spec, unrecovered, errors } = parseReport(html);
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`ok` is false if anything was unrecovered *or* the recovered spec fails
|
|
229
|
+
validation. Check it before re-rendering: a descriptor read out of a
|
|
230
|
+
document is untrusted input.
|
|
231
|
+
|
|
162
232
|
## Verifying your work
|
|
163
233
|
|
|
164
234
|
- **Verify a morph by progress, not by DOM presence.** The destination
|