@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinkerlandai/indiana-iam",
3
- "version": "0.7.0",
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 { documentNodes } from "./node-hashes.mjs";
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 = documentNodes(document);
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 =
@@ -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';
@@ -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
- function domNodes(document){
24
- const decoded = {...document, ...documentDom(document)};
25
- const {JSDOM}=dependency('jsdom').value,dom=new JSDOM('<!doctype html><html><body>'+decoded.html+'</body></html>');
26
- try{return expandDomNodes(decoded,dom.window.document.body,(page,parentId,index)=>identity('node',{page,parentId,index,kind:3}));}finally{dom.window.close();}
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=domNodes(document);
48
+ const values=documentStructure(document);
32
49
  sealNodes(values, document.rootNodeId);
33
50
  return values;
34
51
  }
@@ -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 { previewUiDocument } from "./ui-preview.mjs";
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 dom = new JSDOM(documentDom(document).html, {
17
- virtualConsole: new VirtualConsole(),
18
- });
15
+ const doc = inertUiDocument(documentDom(document).html);
19
16
  try {
20
- const doc = dom.window.document,
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
- preview = previewUiDocument(manifest, document, options),
123
- sceneDom = new JSDOM(preview.html, {
124
- virtualConsole: new VirtualConsole(),
125
- });
126
- const focused = [
127
- ...sceneDom.window.document.querySelectorAll("[data-iam-node]"),
128
- ]
129
- .find((n) => n.getAttribute("data-iam-node") === base.focusNodeId)
130
- ?.cloneNode(true);
131
- focused
132
- ?.querySelectorAll("[hidden],[role=tablist]")
133
- .forEach((n) => n.remove());
134
- const headings = focused
135
- ? [...focused.querySelectorAll("h1,h2,h3,h4,label,[role=columnheader]")]
136
- .map((n) => n.textContent.replace(/\s+/g, " ").trim())
137
- .filter(Boolean)
138
- : [];
139
- const content = focused?.textContent.replace(/\s+/g, " ").trim() ?? "";
140
- const fields = focused
141
- ? [...focused.querySelectorAll("input,textarea,select")].map((n) => ({
142
- tag: n.tagName.toLowerCase(),
143
- label: n.getAttribute("aria-label") ?? "",
144
- placeholder: n.getAttribute("placeholder") ?? "",
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
- dom.window.close();
206
+ doc.body.replaceChildren();
213
207
  }
214
208
  }
@@ -1,6 +1,6 @@
1
- import { JSDOM, VirtualConsole } from "jsdom";
1
+ import { JSDOM } from "jsdom";
2
2
  import { documentDom } from "./dom-payload.mjs";
3
- import {documentNodes} from './node-hashes.mjs';
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 dom = new JSDOM(documentDom(document).html, {
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=mapped.get(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});
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=documentNodes(document).find(n=>n.nodeId===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});}
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
- dom.window.close();
275
+ doc.body.replaceChildren();
260
276
  }
261
277
  }