nodality 1.2.11 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mcp-server.mjs +45 -0
- package/dist/animator.cjs.js +1 -1
- package/dist/animator.cjs.js.LICENSE.txt +1 -1
- package/dist/animator.esm.js +1 -1
- package/dist/animator.esm.js.LICENSE.txt +1 -1
- package/dist/audionew.cjs.js +1 -1
- package/dist/audionew.cjs.js.LICENSE.txt +1 -1
- package/dist/audionew.esm.js +1 -1
- package/dist/audionew.esm.js.LICENSE.txt +1 -1
- package/dist/base.cjs.js +1 -1
- package/dist/base.cjs.js.LICENSE.txt +1 -1
- package/dist/base.esm.js +1 -1
- package/dist/base.esm.js.LICENSE.txt +1 -1
- package/dist/beta-desktop-bar.cjs.js +1 -1
- package/dist/beta-desktop-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/beta-desktop-bar.esm.js +1 -1
- package/dist/beta-desktop-bar.esm.js.LICENSE.txt +1 -1
- package/dist/beta-mobile-bar.cjs.js +1 -1
- package/dist/beta-mobile-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/beta-mobile-bar.esm.js +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 -1
- package/dist/button.cjs.js +1 -1
- package/dist/button.cjs.js.LICENSE.txt +1 -1
- package/dist/button.esm.js +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 +1 -1
- package/dist/center.cjs.js.LICENSE.txt +1 -1
- package/dist/center.esm.js +1 -1
- package/dist/center.esm.js.LICENSE.txt +1 -1
- package/dist/checkbox.cjs.js +1 -1
- package/dist/checkbox.cjs.js.LICENSE.txt +1 -1
- package/dist/checkbox.esm.js +1 -1
- package/dist/checkbox.esm.js.LICENSE.txt +1 -1
- package/dist/code.cjs.js +1 -1
- package/dist/code.cjs.js.LICENSE.txt +1 -1
- package/dist/code.esm.js +1 -1
- package/dist/code.esm.js.LICENSE.txt +1 -1
- package/dist/container.cjs.js +1 -1
- package/dist/container.cjs.js.LICENSE.txt +1 -1
- package/dist/container.esm.js +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 -1
- package/dist/designer.esm.js +1 -1
- package/dist/designer.esm.js.LICENSE.txt +1 -1
- 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 -1
- package/dist/flex-card.cjs.js +1 -1
- package/dist/flex-card.cjs.js.LICENSE.txt +1 -1
- package/dist/flex-card.esm.js +1 -1
- package/dist/flex-card.esm.js.LICENSE.txt +1 -1
- package/dist/flex-grid.cjs.js +1 -1
- package/dist/flex-grid.cjs.js.LICENSE.txt +1 -1
- package/dist/flex-grid.esm.js +1 -1
- package/dist/flex-grid.esm.js.LICENSE.txt +1 -1
- package/dist/flex-row.cjs.js +1 -1
- package/dist/flex-row.cjs.js.LICENSE.txt +1 -1
- package/dist/flex-row.esm.js +1 -1
- package/dist/flex-row.esm.js.LICENSE.txt +1 -1
- package/dist/floating-input.cjs.js +1 -1
- package/dist/floating-input.cjs.js.LICENSE.txt +1 -1
- package/dist/floating-input.esm.js +1 -1
- package/dist/floating-input.esm.js.LICENSE.txt +1 -1
- package/dist/free.cjs.js +1 -1
- package/dist/free.cjs.js.LICENSE.txt +1 -1
- package/dist/free.esm.js +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 +1 -1
- package/dist/image-picker.cjs.js.LICENSE.txt +1 -1
- package/dist/image-picker.esm.js +1 -1
- package/dist/image-picker.esm.js.LICENSE.txt +1 -1
- package/dist/image.cjs.js +1 -1
- package/dist/image.cjs.js.LICENSE.txt +1 -1
- package/dist/image.esm.js +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 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.esm.js +1 -1
- package/dist/index.esm.js.LICENSE.txt +1 -1
- 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 +1 -1
- package/dist/link-getter.cjs.js.LICENSE.txt +1 -1
- package/dist/link-getter.esm.js +1 -1
- package/dist/link-getter.esm.js.LICENSE.txt +1 -1
- package/dist/link.cjs.js +1 -1
- package/dist/link.cjs.js.LICENSE.txt +1 -1
- package/dist/link.esm.js +1 -1
- package/dist/link.esm.js.LICENSE.txt +1 -1
- package/dist/meta-adder.cjs.js +1 -1
- package/dist/meta-adder.cjs.js.LICENSE.txt +1 -1
- package/dist/meta-adder.esm.js +1 -1
- package/dist/meta-adder.esm.js.LICENSE.txt +1 -1
- package/dist/modal-2025.cjs.js +1 -1
- package/dist/modal-2025.cjs.js.LICENSE.txt +1 -1
- package/dist/modal-2025.esm.js +1 -1
- package/dist/modal-2025.esm.js.LICENSE.txt +1 -1
- package/dist/multiswitcher.cjs.js +1 -1
- package/dist/multiswitcher.cjs.js.LICENSE.txt +1 -1
- package/dist/multiswitcher.esm.js +1 -1
- package/dist/multiswitcher.esm.js.LICENSE.txt +1 -1
- package/dist/new-nav-bar.cjs.js +1 -1
- package/dist/new-nav-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/new-nav-bar.esm.js +1 -1
- package/dist/new-nav-bar.esm.js.LICENSE.txt +1 -1
- package/dist/picker.cjs.js +1 -1
- package/dist/picker.cjs.js.LICENSE.txt +1 -1
- package/dist/picker.esm.js +1 -1
- package/dist/picker.esm.js.LICENSE.txt +1 -1
- package/dist/progress.cjs.js +1 -1
- package/dist/progress.cjs.js.LICENSE.txt +1 -1
- package/dist/progress.esm.js +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 +1 -1
- package/dist/side-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/side-bar.esm.js +1 -1
- package/dist/side-bar.esm.js.LICENSE.txt +1 -1
- package/dist/side-nav-bar.cjs.js +1 -1
- package/dist/side-nav-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/side-nav-bar.esm.js +1 -1
- package/dist/side-nav-bar.esm.js.LICENSE.txt +1 -1
- package/dist/simple-bar.cjs.js +1 -1
- package/dist/simple-bar.cjs.js.LICENSE.txt +1 -1
- package/dist/simple-bar.esm.js +1 -1
- package/dist/simple-bar.esm.js.LICENSE.txt +1 -1
- package/dist/slider-2025.cjs.js +1 -1
- package/dist/slider-2025.cjs.js.LICENSE.txt +1 -1
- package/dist/slider-2025.esm.js +1 -1
- package/dist/slider-2025.esm.js.LICENSE.txt +1 -1
- package/dist/spacer.cjs.js +1 -1
- package/dist/spacer.cjs.js.LICENSE.txt +1 -1
- package/dist/spacer.esm.js +1 -1
- package/dist/spacer.esm.js.LICENSE.txt +1 -1
- package/dist/stack.cjs.js +1 -1
- package/dist/stack.cjs.js.LICENSE.txt +1 -1
- package/dist/stack.esm.js +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 +1 -1
- package/dist/table.cjs.js.LICENSE.txt +1 -1
- package/dist/table.esm.js +1 -1
- package/dist/table.esm.js.LICENSE.txt +1 -1
- package/dist/text-field.cjs.js +1 -1
- package/dist/text-field.cjs.js.LICENSE.txt +1 -1
- package/dist/text-field.esm.js +1 -1
- package/dist/text-field.esm.js.LICENSE.txt +1 -1
- package/dist/text.cjs.js +1 -1
- package/dist/text.cjs.js.LICENSE.txt +1 -1
- package/dist/text.esm.js +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 +1 -1
- package/dist/ulist.cjs.js.LICENSE.txt +1 -1
- package/dist/ulist.esm.js +1 -1
- package/dist/ulist.esm.js.LICENSE.txt +1 -1
- package/dist/video.cjs.js +1 -1
- package/dist/video.cjs.js.LICENSE.txt +1 -1
- package/dist/video.esm.js +1 -1
- package/dist/video.esm.js.LICENSE.txt +1 -1
- package/dist/wrap.cjs.js +1 -1
- package/dist/wrap.cjs.js.LICENSE.txt +1 -1
- package/dist/wrap.esm.js +1 -1
- package/dist/wrap.esm.js.LICENSE.txt +1 -1
- package/dist/zoom-card.cjs.js +1 -1
- package/dist/zoom-card.cjs.js.LICENSE.txt +1 -1
- package/dist/zoom-card.esm.js +1 -1
- package/dist/zoom-card.esm.js.LICENSE.txt +1 -1
- package/layout/animator.js +15 -2
- 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 +10 -3
- 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 +14 -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 +5 -3
- 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 +15 -2
- 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 +11 -3
- package/layout/side-nav-bar.js +16 -3
- 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 +16 -1
- package/layout/text.js +28 -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/check-page.js +239 -0
- package/lib/codegen.js +1 -1
- package/lib/data.js +1 -1
- package/lib/designer.js +64 -2
- package/lib/element-mapper.js +113 -23
- package/lib/element-params.generated.js +2 -2
- package/lib/keyframe-animation.js +1 -1
- package/lib/link-getter.js +1 -1
- package/lib/morph-node.js +8 -1
- package/lib/normalize-spec.js +134 -0
- package/lib/parse-html.js +1 -1
- package/lib/raster-inspect.js +1 -1
- package/lib/raster-ops.js +1 -1
- package/lib/raster-presets.js +1 -1
- package/lib/resolve-refs.js +121 -0
- 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 +60 -3
- package/lib/webmcp-adapter.js +1 -1
- package/package.json +3 -1
- package/schema.json +211 -12
- package/skills/nodality/SKILL.md +50 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* nodality v1.3.0
|
|
3
|
+
* (c) 2026 Filip Vabrousek
|
|
4
|
+
* License: MIT
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Tier 3 of AGENTIC-FIRST-PLAN.md §10 — declare shared structure once.
|
|
9
|
+
*
|
|
10
|
+
* MEASURED FIRST, as the plan asked. Chrome taken from a real site: a nav and
|
|
11
|
+
* a footer are 184 tokens, and **61% of every token in that site is the same
|
|
12
|
+
* nav and footer**. Referencing rather than repeating them is 1.98x at ten
|
|
13
|
+
* pages and 2.10x at twenty — and a NET LOSS at one page, which is why this is
|
|
14
|
+
* opt-in and why nothing changes for a caller who never uses it.
|
|
15
|
+
*
|
|
16
|
+
* The reason it is needed is not that the data format is verbose. The site
|
|
17
|
+
* used for that measurement does not repeat itself: it imports `renderNav`
|
|
18
|
+
* from a shared module and calls it. That is the imperative layer, where reuse
|
|
19
|
+
* is just a function. An element array cannot call a function, so an agent
|
|
20
|
+
* authoring as data pays for the chrome once per page, and the only way out is
|
|
21
|
+
* to drop into code — which is what property 1, "total", exists to prevent.
|
|
22
|
+
* A human never feels this and an agent always does.
|
|
23
|
+
*
|
|
24
|
+
* new Des()
|
|
25
|
+
* .defs({ nav: { type: "nav", items: [...] } })
|
|
26
|
+
* .add([{ $ref: "nav" }, { type: "h1", text: "Page" }])
|
|
27
|
+
*
|
|
28
|
+
* The definitions are ordinary data, so they serialise, ship in one file, and
|
|
29
|
+
* are imported by every page's entry — the sharing is a plain import of a
|
|
30
|
+
* plain object, not a new authoring mode.
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/** The key that marks a reference. `$`-prefixed so it cannot collide with a
|
|
34
|
+
* parameter name: the schema's vocabulary is all bare identifiers. */
|
|
35
|
+
export const REF_KEY = "$ref";
|
|
36
|
+
|
|
37
|
+
/** True for `{ $ref: "name" }` and nothing else. */
|
|
38
|
+
export const isRef = (v) =>
|
|
39
|
+
!!v && typeof v === "object" && !Array.isArray(v) && typeof v[REF_KEY] === "string";
|
|
40
|
+
|
|
41
|
+
/** Every name referenced anywhere in a spec, with the path that reached it. */
|
|
42
|
+
export function collectRefs(elements, at = "elements") {
|
|
43
|
+
const found = [];
|
|
44
|
+
const walk = (node, path) => {
|
|
45
|
+
if (Array.isArray(node)) return node.forEach((n, i) => walk(n, `${path}[${i}]`));
|
|
46
|
+
if (!node || typeof node !== "object") return;
|
|
47
|
+
if (isRef(node)) { found.push({ name: node[REF_KEY], path }); return; }
|
|
48
|
+
for (const key of ["children", "items"]) {
|
|
49
|
+
if (node[key] !== undefined) walk(node[key], `${path}.${key}`);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
walk(elements, at);
|
|
53
|
+
return found;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Replace every `{ $ref: name }` with a deep copy of `defs[name]`.
|
|
58
|
+
*
|
|
59
|
+
* A copy, not the definition itself: two pages referencing one nav must not
|
|
60
|
+
* share an object the mapper then mutates — rendering an `a` already adds
|
|
61
|
+
* `font` and `pad` to the descriptor it is given, so a shared definition would
|
|
62
|
+
* accumulate the leavings of every page that used it.
|
|
63
|
+
*
|
|
64
|
+
* An unresolvable reference is left in place rather than dropped or guessed.
|
|
65
|
+
* The validator reports it as DANGLING_REF with a did-you-mean; silently
|
|
66
|
+
* removing it would make a missing nav look like a page that never had one.
|
|
67
|
+
*/
|
|
68
|
+
export function resolveRefs(elements, defs, seen = new Set()) {
|
|
69
|
+
if (!defs || typeof defs !== "object") return elements;
|
|
70
|
+
|
|
71
|
+
const expand = (node) => {
|
|
72
|
+
if (Array.isArray(node)) return node.map(expand);
|
|
73
|
+
if (!node || typeof node !== "object") return node;
|
|
74
|
+
|
|
75
|
+
if (isRef(node)) {
|
|
76
|
+
const name = node[REF_KEY];
|
|
77
|
+
if (!Object.prototype.hasOwnProperty.call(defs, name)) return node;
|
|
78
|
+
if (seen.has(name)) {
|
|
79
|
+
// A definition that reaches itself. Returning the ref rather
|
|
80
|
+
// than recursing means a cycle is a reported error instead of
|
|
81
|
+
// a stack overflow at render time.
|
|
82
|
+
return node;
|
|
83
|
+
}
|
|
84
|
+
const next = new Set(seen).add(name);
|
|
85
|
+
const base = resolveRefs([defs[name]], defs, next)[0];
|
|
86
|
+
|
|
87
|
+
// Keys written BESIDE the reference override the definition.
|
|
88
|
+
//
|
|
89
|
+
// Without this they were silently dropped — `{ $ref: "nav", id:
|
|
90
|
+
// "topnav" }` expanded to the bare definition and the id went
|
|
91
|
+
// nowhere, which is precisely the declared-but-ignored failure
|
|
92
|
+
// this project spent six stages removing. Having built a new one,
|
|
93
|
+
// the choice was to reject the shape or to honour it; honouring it
|
|
94
|
+
// is what makes a shared fragment reusable at all, since the whole
|
|
95
|
+
// point of sharing a nav is that each page marks a different entry.
|
|
96
|
+
//
|
|
97
|
+
// Shallow, deliberately: a deep merge would make it unclear
|
|
98
|
+
// whether `items` replaces the definition's or extends it. It
|
|
99
|
+
// replaces.
|
|
100
|
+
const { [REF_KEY]: _drop, ...overrides } = node;
|
|
101
|
+
return Object.keys(overrides).length
|
|
102
|
+
? { ...(base && typeof base === "object" ? base : {}), ...overrides }
|
|
103
|
+
: base;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const out = { ...node };
|
|
107
|
+
for (const key of ["children", "items"]) {
|
|
108
|
+
if (out[key] !== undefined) out[key] = expand(out[key]);
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
return Array.isArray(elements) ? elements.map(expand) : elements;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Names defined but never referenced — dead weight in a shared file. */
|
|
117
|
+
export function unusedDefs(elements, defs) {
|
|
118
|
+
if (!defs) return [];
|
|
119
|
+
const used = new Set(collectRefs(elements).map((r) => r.name));
|
|
120
|
+
return Object.keys(defs).filter((n) => !used.has(n));
|
|
121
|
+
}
|
package/lib/scroll-video.js
CHANGED
package/lib/seo.js
CHANGED
package/lib/stacker.js
CHANGED
package/lib/suggest.js
CHANGED
package/lib/theme.js
CHANGED
package/lib/transform-anim.js
CHANGED
package/lib/transition.js
CHANGED
package/lib/validate-nodes.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* nodality v1.
|
|
2
|
+
* nodality v1.3.0
|
|
3
3
|
* (c) 2026 Filip Vabrousek
|
|
4
4
|
* License: MIT
|
|
5
5
|
*/
|
|
@@ -30,6 +30,8 @@ import {
|
|
|
30
30
|
FRAMEWORK_DOC, EASING_NAMES,
|
|
31
31
|
} from "./raster-ops.js";
|
|
32
32
|
import { presetNames, presetInfo } from "./raster-presets.js";
|
|
33
|
+
import { collectRefs, isRef, resolveRefs } from "./resolve-refs.js";
|
|
34
|
+
import { normalizeSpec, aliasConflicts, ALIASES } from "./normalize-spec.js";
|
|
33
35
|
import { ELEMENT_TYPES } from "./element-mapper.js";
|
|
34
36
|
import { ELEMENT_PARAM_NAMES } from "./element-params.generated.js";
|
|
35
37
|
import { didYouMean, suggest, levenshtein } from "./suggest.js";
|
|
@@ -161,7 +163,7 @@ export const CONTENT_SLOT = {
|
|
|
161
163
|
row: "children", form: "children", wrap: "children",
|
|
162
164
|
};
|
|
163
165
|
|
|
164
|
-
export function validateNodes(nodes, elements) {
|
|
166
|
+
export function validateNodes(nodes, elements, defs) {
|
|
165
167
|
const errors = [];
|
|
166
168
|
const push = (code, path, got, suggestions, valid) => {
|
|
167
169
|
errors.push({
|
|
@@ -219,6 +221,42 @@ export function validateNodes(nodes, elements) {
|
|
|
219
221
|
// element whose `type` names nothing is not a silent no-op but a THROW
|
|
220
222
|
// from the mapper at render time, which reaches a generator as a stack
|
|
221
223
|
// trace rather than as a report it can repair from.
|
|
224
|
+
// Tier 3: a reference with no definition. Reported here as well as
|
|
225
|
+
// refused at render, because a report is repairable in one turn and a
|
|
226
|
+
// throw is not — the Stage 3 contract applied to the newest way of being
|
|
227
|
+
// wrong. `defs` is optional: without it references are left alone rather
|
|
228
|
+
// than all reported missing, since a caller may validate a page before
|
|
229
|
+
// deciding what to share.
|
|
230
|
+
if (defs && typeof defs === "object" && Array.isArray(elements)) {
|
|
231
|
+
const defined = Object.keys(defs);
|
|
232
|
+
for (const { name, path } of collectRefs(elements)) {
|
|
233
|
+
if (!Object.prototype.hasOwnProperty.call(defs, name)) {
|
|
234
|
+
push("DANGLING_REF", path, name, suggest(name, defined), defined);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Walk the RESOLVED tree when definitions are available. A reference
|
|
240
|
+
// with keys beside it is a real element once merged, and only the merged
|
|
241
|
+
// form can be checked: `{ $ref: "nav", itms: [] }` is a typo in an
|
|
242
|
+
// override, and validating the unexpanded reference would miss it —
|
|
243
|
+
// a silent no-op inside the mechanism built to remove silent no-ops.
|
|
244
|
+
let toWalk = (defs && typeof defs === "object" && Array.isArray(elements))
|
|
245
|
+
? resolveRefs(elements, defs)
|
|
246
|
+
: elements;
|
|
247
|
+
|
|
248
|
+
// An alias and its canonical name both present, disagreeing: one of them
|
|
249
|
+
// is going to be ignored. The author hears it here rather than finding it
|
|
250
|
+
// in the page.
|
|
251
|
+
for (const c of aliasConflicts(Array.isArray(elements) ? elements : [])) {
|
|
252
|
+
push("CONFLICTING_ALIAS", c.path, c.alias, [c.canonical],
|
|
253
|
+
[`"${c.alias}" is accepted as "${c.canonical}"; give one of them, not both`]);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Walk the normalised tree, so `{type:"img", src:"..."}` is checked as the
|
|
257
|
+
// element it actually becomes rather than reported as an unknown parameter.
|
|
258
|
+
if (Array.isArray(toWalk)) toWalk = normalizeSpec(toWalk);
|
|
259
|
+
|
|
222
260
|
if (idsKnown) {
|
|
223
261
|
const walkEls = (list, at) => {
|
|
224
262
|
if (!Array.isArray(list)) return;
|
|
@@ -239,6 +277,12 @@ export function validateNodes(nodes, elements) {
|
|
|
239
277
|
push("BAD_ELEMENT", p, el, [], ["{ type: \"<element type>\", ... }"]);
|
|
240
278
|
return;
|
|
241
279
|
}
|
|
280
|
+
// A reference is not an element and has no `type` by design.
|
|
281
|
+
// Its validity is whether the name resolves, which the
|
|
282
|
+
// DANGLING_REF pass above decides; reporting MISSING_FIELD
|
|
283
|
+
// here rejected every correct `$ref` there is.
|
|
284
|
+
if (isRef(el)) return;
|
|
285
|
+
|
|
242
286
|
if (typeof el.type !== "string") {
|
|
243
287
|
push("MISSING_FIELD", `${p}.type`, el.type, [],
|
|
244
288
|
["an element type, e.g. \"h1\", \"wrap\", \"nav\""]);
|
|
@@ -252,6 +296,19 @@ export function validateNodes(nodes, elements) {
|
|
|
252
296
|
// removed for `cards`, surviving one slot over. It is not a
|
|
253
297
|
// typo, so near-miss detection cannot reach it.
|
|
254
298
|
const wants = CONTENT_SLOT[el.type];
|
|
299
|
+
if (wants) {
|
|
300
|
+
// A content slot that is present but not a list. The
|
|
301
|
+
// mapper tests `Array.isArray(...) && length`, so
|
|
302
|
+
// `items: {…}` and `items: "x"` fall through to the
|
|
303
|
+
// placeholders — content declared, content ignored, no
|
|
304
|
+
// error. Same family as WRONG_CONTENT_SLOT: the key is
|
|
305
|
+
// right and the value cannot carry anything.
|
|
306
|
+
const given = el[wants];
|
|
307
|
+
if (given !== undefined && given !== null && !Array.isArray(given)) {
|
|
308
|
+
push("BAD_CONTENT_SHAPE", `${p}.${wants}`, given, [],
|
|
309
|
+
[`an array — "${el.type}" reads \`${wants}\` as a list`]);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
255
312
|
if (wants) {
|
|
256
313
|
const other = wants === "items" ? "children" : "items";
|
|
257
314
|
const filled = (v) => Array.isArray(v) ? v.length > 0 : v != null;
|
|
@@ -327,7 +384,7 @@ export function validateNodes(nodes, elements) {
|
|
|
327
384
|
}
|
|
328
385
|
});
|
|
329
386
|
};
|
|
330
|
-
walkEls(
|
|
387
|
+
walkEls(toWalk, "elements");
|
|
331
388
|
}
|
|
332
389
|
|
|
333
390
|
nodes.forEach((node, i) => {
|
package/lib/webmcp-adapter.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nodality",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "A lightweight library for declarative UI elements.",
|
|
5
5
|
"main": "dist/index.cjs.js",
|
|
6
6
|
"module": "dist/index.esm.js",
|
|
@@ -33,6 +33,8 @@
|
|
|
33
33
|
"./raster": "./lib/raster-ops.js",
|
|
34
34
|
"./validate": "./lib/validate-nodes.js",
|
|
35
35
|
"./parse": "./lib/parse-html.js",
|
|
36
|
+
"./refs": "./lib/resolve-refs.js",
|
|
37
|
+
"./normalize": "./lib/normalize-spec.js",
|
|
36
38
|
"./inspect": "./lib/raster-inspect.js",
|
|
37
39
|
"./examples/*": "./examples/*.js",
|
|
38
40
|
"./transition": "./lib/transition.js",
|