@tinkerlandai/indiana-iam 0.7.0 → 0.7.1
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/package.json +2 -1
- package/src/document-material.mjs +2 -2
- package/src/library-entry.mjs +1 -0
- package/src/node-hashes.mjs +23 -6
- package/src/tab-scenes.mjs +29 -35
- package/src/ui-preview.mjs +29 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinkerlandai/indiana-iam",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Build-time Indiana App Manifest (IAM) generator for Next.js, Vite and React Native / Expo source.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
"micromark-extension-gfm": "3.0.0",
|
|
60
60
|
"micromark-extension-mdxjs": "3.0.0",
|
|
61
61
|
"pako": "3.0.2",
|
|
62
|
+
"parse5": "7.3.0",
|
|
62
63
|
"sharp": "0.35.5",
|
|
63
64
|
"tailwind-merge": "3.7.0",
|
|
64
65
|
"tailwindcss": "4.3.3",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { documentStructure } from "./node-hashes.mjs";
|
|
2
2
|
import { documentDom } from "./dom-payload.mjs";
|
|
3
3
|
import { sha256, canonical } from "./integrity.mjs";
|
|
4
4
|
const resourceHashes = new WeakMap();
|
|
@@ -36,7 +36,7 @@ export function documentMaterial(
|
|
|
36
36
|
(document) => document.documentKey === render.documentKey,
|
|
37
37
|
);
|
|
38
38
|
if (!document) throw Error("Missing IAM preview document");
|
|
39
|
-
const concreteNodes =
|
|
39
|
+
const concreteNodes = documentStructure(document);
|
|
40
40
|
const elementKeys = new Set(concreteNodes.map((node) => node.elementKey));
|
|
41
41
|
const ui = manifest.routeTree.ui;
|
|
42
42
|
const collections =
|
package/src/library-entry.mjs
CHANGED
|
@@ -7,6 +7,7 @@ import {privateUrl}from'./privacy.mjs';
|
|
|
7
7
|
import{renderGuardFor}from'./render-index.mjs';
|
|
8
8
|
import { documentScript } from "./shared-scripts.mjs";
|
|
9
9
|
import { documentNodes } from './node-hashes.mjs';
|
|
10
|
+
export { documentStructure as iamDocumentStructure } from './node-hashes.mjs';
|
|
10
11
|
export { documentNodes as iamDocumentNodes } from './node-hashes.mjs';
|
|
11
12
|
export { documentDom as iamDocumentDom } from './dom-payload.mjs';
|
|
12
13
|
export { rebindIamSource } from './rebind.mjs';
|
package/src/node-hashes.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { documentDom } from "./dom-payload.mjs";
|
|
2
2
|
import { canonical, identity, sealNodes, sealDocument } from './integrity.mjs';
|
|
3
|
-
import { dependency } from './dependencies.mjs';
|
|
4
3
|
import { sourceMappingsHash } from './compact-dom.mjs';
|
|
4
|
+
import { parseFragment } from 'parse5';
|
|
5
5
|
|
|
6
6
|
/** HTML already owns the DOM values and ordering. Store only the source map
|
|
7
7
|
* in preorder, rather than repeating attributes, text and every tree edge. */
|
|
@@ -20,15 +20,32 @@ export function expandDomNodes(document, body, deriveTextId) {
|
|
|
20
20
|
if(body.childNodes.length!==1||visit(body.firstChild,null,0)!==document.rootNodeId||index!==document.nodes.length)throw Error('HTML-derived DOM coverage differs');
|
|
21
21
|
return result;
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
|
|
24
|
+
/** Adapt the same HTML parser's inert tree to the existing source-map expansion.
|
|
25
|
+
* Maps need tree values, not a window, CSSOM, scripts or derived node hashes. */
|
|
26
|
+
export function documentStructure(document) {
|
|
27
|
+
const decoded = { ...document, ...documentDom(document) };
|
|
28
|
+
const adapt = node => ({
|
|
29
|
+
nodeType: node.tagName ? 1 : node.nodeName === '#text' ? 3 : 8,
|
|
30
|
+
tagName: node.tagName,
|
|
31
|
+
data: node.value,
|
|
32
|
+
get attributes() {
|
|
33
|
+
return (node.attrs ?? []).map(attr => ({
|
|
34
|
+
name: attr.prefix ? attr.prefix + ':' + attr.name : attr.name,
|
|
35
|
+
value: attr.value,
|
|
36
|
+
}));
|
|
37
|
+
},
|
|
38
|
+
getAttribute(name) { return this.attributes.find(attr => attr.name === name)?.value ?? null; },
|
|
39
|
+
get childNodes() { return (node.childNodes ?? []).map(adapt); },
|
|
40
|
+
get firstChild() { return this.childNodes[0]; },
|
|
41
|
+
});
|
|
42
|
+
return expandDomNodes(decoded, adapt(parseFragment(decoded.html)),
|
|
43
|
+
(page, parentId, index) => identity('node', { page, parentId, index, kind: 3 }));
|
|
27
44
|
}
|
|
28
45
|
|
|
29
46
|
/** Node hashes are pure derived values. IDs, source mappings and algorithms never change. */
|
|
30
47
|
export function documentNodes(document) {
|
|
31
|
-
const values=
|
|
48
|
+
const values=documentStructure(document);
|
|
32
49
|
sealNodes(values, document.rootNodeId);
|
|
33
50
|
return values;
|
|
34
51
|
}
|
package/src/tab-scenes.mjs
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { JSDOM, VirtualConsole } from "jsdom";
|
|
2
1
|
import { documentDom } from "./dom-payload.mjs";
|
|
3
2
|
import { createStateMachine } from "./ui-state-machine.mjs";
|
|
4
|
-
import {
|
|
3
|
+
import { inspectUiDocument, inertUiDocument } from "./ui-preview.mjs";
|
|
5
4
|
import {graphSource} from './graph-sources.mjs';
|
|
6
5
|
|
|
7
6
|
/** Build thumbnail states and edges by dispatching the IAM's actual scalar transitions. */
|
|
@@ -13,12 +12,9 @@ export function tabUiScenes(manifest, tabKey) {
|
|
|
13
12
|
document = manifest.documents.find(
|
|
14
13
|
(d) => d.documentKey === render.documentKey,
|
|
15
14
|
);
|
|
16
|
-
const
|
|
17
|
-
virtualConsole: new VirtualConsole(),
|
|
18
|
-
});
|
|
15
|
+
const doc = inertUiDocument(documentDom(document).html);
|
|
19
16
|
try {
|
|
20
|
-
const
|
|
21
|
-
nodes = new Map(
|
|
17
|
+
const nodes = new Map(
|
|
22
18
|
[...doc.querySelectorAll("[data-iam-node]")].map((n) => [
|
|
23
19
|
n.getAttribute("data-iam-node"),
|
|
24
20
|
n,
|
|
@@ -118,32 +114,31 @@ export function tabUiScenes(manifest, tabKey) {
|
|
|
118
114
|
([key, value]) => initial[key] !== value || key === tab.variableKey,
|
|
119
115
|
),
|
|
120
116
|
);
|
|
121
|
-
const options = { ...base, values: { ...values, ...changed } }
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
: [];
|
|
117
|
+
const options = { ...base, values: { ...values, ...changed } };
|
|
118
|
+
const { preview, headings, content, fields } = inspectUiDocument(
|
|
119
|
+
manifest, document, options, (doc, preview) => {
|
|
120
|
+
const focused = [...doc.querySelectorAll("[data-iam-node]")]
|
|
121
|
+
.find((n) => n.getAttribute("data-iam-node") === base.focusNodeId)
|
|
122
|
+
?.cloneNode(true);
|
|
123
|
+
focused?.querySelectorAll("[hidden],[role=tablist]")
|
|
124
|
+
.forEach((n) => n.remove());
|
|
125
|
+
return {
|
|
126
|
+
preview,
|
|
127
|
+
headings: focused
|
|
128
|
+
? [...focused.querySelectorAll("h1,h2,h3,h4,label,[role=columnheader]")]
|
|
129
|
+
.map((n) => n.textContent.replace(/\s+/g, " ").trim()).filter(Boolean)
|
|
130
|
+
: [],
|
|
131
|
+
content: focused?.textContent.replace(/\s+/g, " ").trim() ?? "",
|
|
132
|
+
fields: focused
|
|
133
|
+
? [...focused.querySelectorAll("input,textarea,select")].map((n) => ({
|
|
134
|
+
tag: n.tagName.toLowerCase(),
|
|
135
|
+
label: n.getAttribute("aria-label") ?? "",
|
|
136
|
+
placeholder: n.getAttribute("placeholder") ?? "",
|
|
137
|
+
}))
|
|
138
|
+
: [],
|
|
139
|
+
};
|
|
140
|
+
},
|
|
141
|
+
);
|
|
147
142
|
const allowedIds = new Set([
|
|
148
143
|
...ancestors.map((n) => n.getAttribute("data-iam-node")),
|
|
149
144
|
...(scope
|
|
@@ -177,7 +172,6 @@ export function tabUiScenes(manifest, tabKey) {
|
|
|
177
172
|
transitionKey: result.transitionKey,
|
|
178
173
|
changedNodeIds: tab.contentNodeIds ?? [],
|
|
179
174
|
});
|
|
180
|
-
sceneDom.window.close();
|
|
181
175
|
}
|
|
182
176
|
const edges = [];
|
|
183
177
|
for (const from of scenes)
|
|
@@ -209,6 +203,6 @@ export function tabUiScenes(manifest, tabKey) {
|
|
|
209
203
|
}
|
|
210
204
|
return { tabKey, scenes, edges };
|
|
211
205
|
} finally {
|
|
212
|
-
|
|
206
|
+
doc.body.replaceChildren();
|
|
213
207
|
}
|
|
214
208
|
}
|
package/src/ui-preview.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { JSDOM
|
|
1
|
+
import { JSDOM } from "jsdom";
|
|
2
2
|
import { documentDom } from "./dom-payload.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import {documentStructure} from './node-hashes.mjs';
|
|
4
4
|
import { createStateMachine } from "./ui-state-machine.mjs";
|
|
5
5
|
import { applySourceAttributes } from "./source-attributes.mjs";
|
|
6
6
|
import {
|
|
@@ -10,14 +10,27 @@ import {
|
|
|
10
10
|
|
|
11
11
|
/** Inert source alternatives for UI inspection. This never changes the admitted render or SDK targets. */
|
|
12
12
|
export function previewUiDocument(manifest, document, options = {}) {
|
|
13
|
+
return inspectUiDocument(manifest, document, options, (doc, preview) => ({
|
|
14
|
+
html: doc.documentElement.outerHTML,
|
|
15
|
+
...preview,
|
|
16
|
+
}));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** An isolated inert document needs no browsing context, CSSOM or per-scene window. */
|
|
20
|
+
export function inertUiDocument(html) {
|
|
21
|
+
const fragment = JSDOM.fragment(html);
|
|
22
|
+
const doc = fragment.ownerDocument.implementation.createHTMLDocument();
|
|
23
|
+
doc.body.replaceChildren(fragment);
|
|
24
|
+
return doc;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Inspect an inert preview without serializing and reparsing it. */
|
|
28
|
+
export function inspectUiDocument(manifest, document, options, inspect) {
|
|
13
29
|
const graph = manifest.routeTree,
|
|
14
30
|
render = graph.renders.find((r) => r.documentKey === document.documentKey);
|
|
15
31
|
if (!render) throw Error("Unknown UI preview render");
|
|
16
|
-
const
|
|
17
|
-
virtualConsole: new VirtualConsole(),
|
|
18
|
-
});
|
|
32
|
+
const doc = inertUiDocument(documentDom(document).html);
|
|
19
33
|
try {
|
|
20
|
-
const doc = dom.window.document;
|
|
21
34
|
const nodes = new Map(
|
|
22
35
|
[...doc.querySelectorAll("[data-iam-node]")].map((n) => [
|
|
23
36
|
n.getAttribute("data-iam-node"),
|
|
@@ -194,17 +207,21 @@ export function previewUiDocument(manifest, document, options = {}) {
|
|
|
194
207
|
options.strategy === "content",
|
|
195
208
|
selections,
|
|
196
209
|
);
|
|
210
|
+
let mapped;
|
|
211
|
+
const sourceNode = id => {
|
|
212
|
+
mapped ??= new Map(documentStructure(document).map(n => [n.nodeId, n]));
|
|
213
|
+
return mapped.get(id);
|
|
214
|
+
};
|
|
197
215
|
if(options.strategy==='content') {
|
|
198
|
-
const mapped=new Map(documentNodes(document).map(n=>[n.nodeId,n]));
|
|
199
216
|
const sources=new Map(manifest.elements.map(e=>[e.elementKey,e.source]));
|
|
200
217
|
for(const field of doc.querySelectorAll('[data-iam-observation="remote"],[data-iam-observation="unobserved"],[data-iam-input-unobserved]')) {
|
|
201
218
|
const hidden=field.closest('[hidden]');if(hidden&&(!focus||focus.contains(hidden)))continue;
|
|
202
|
-
const id=field.getAttribute('data-iam-node'),node=
|
|
219
|
+
const id=field.getAttribute('data-iam-node'),node=sourceNode(id),source=node&&(node.source??sources.get(node.elementKey));if(source&&!unobserved.some(u=>u.groupKey===id))unobserved.push({groupKey:id,expression:'Data unobserved',source});
|
|
203
220
|
}
|
|
204
221
|
}
|
|
205
222
|
if (focus) {
|
|
206
223
|
const template=focus.closest('[data-iam-source-template="unobserved"]');
|
|
207
|
-
if(template){const id=template.getAttribute('data-iam-node'),node=
|
|
224
|
+
if(template){const id=template.getAttribute('data-iam-node'),node=sourceNode(id),source=node&&(node.source??manifest.elements.find(e=>e.elementKey===node.elementKey)?.source);if(source)unobserved.push({groupKey:id,expression:'Source template · collection data unobserved',source});}
|
|
208
225
|
}
|
|
209
226
|
const assumptions = [...groups].flatMap(([key, rows]) => {
|
|
210
227
|
if (!selections.has(key)) return [];
|
|
@@ -246,16 +263,15 @@ export function previewUiDocument(manifest, document, options = {}) {
|
|
|
246
263
|
requires,
|
|
247
264
|
};
|
|
248
265
|
});
|
|
249
|
-
return {
|
|
250
|
-
html: doc.documentElement.outerHTML,
|
|
266
|
+
return inspect(doc, {
|
|
251
267
|
assumptions,
|
|
252
268
|
conditionChoices,
|
|
253
269
|
values,
|
|
254
270
|
componentChoices,
|
|
255
271
|
collectionChoices,
|
|
256
272
|
unobserved,
|
|
257
|
-
};
|
|
273
|
+
});
|
|
258
274
|
} finally {
|
|
259
|
-
|
|
275
|
+
doc.body.replaceChildren();
|
|
260
276
|
}
|
|
261
277
|
}
|