@tinkerlandai/indiana-iam 0.2.0 → 0.4.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.
Files changed (55) hide show
  1. package/README.md +127 -5
  2. package/assets/document-controller.js +18 -1
  3. package/assets/document-runtime.js +189 -1
  4. package/assets/viewer.js +74 -7
  5. package/package.json +6 -13
  6. package/src/analyze.mjs +897 -123
  7. package/src/anchors.mjs +5 -0
  8. package/src/build-options.d.ts +6 -1
  9. package/src/build-runtime.mjs +4 -1
  10. package/src/build-styles.mjs +49 -6
  11. package/src/build.mjs +13 -7
  12. package/src/cli.mjs +10 -4
  13. package/src/component-registry.mjs +229 -0
  14. package/src/dependencies.mjs +2 -2
  15. package/src/dom-payload-browser.mjs +28 -0
  16. package/src/dom-payload-json.mjs +12 -0
  17. package/src/dom-payload.mjs +50 -0
  18. package/src/framework-routes.mjs +44 -89
  19. package/src/generate.mjs +20 -7
  20. package/src/graph-sources.mjs +54 -0
  21. package/src/index.d.ts +13 -1
  22. package/src/index.mjs +1 -1
  23. package/src/integrity.mjs +4 -2
  24. package/src/library-entry.mjs +5 -0
  25. package/src/lucide-source.mjs +18 -0
  26. package/src/next-loader.cjs +7 -1
  27. package/src/next.cjs +5 -0
  28. package/src/next.mjs +4 -3
  29. package/src/node-hashes.mjs +13 -9
  30. package/src/render-index.mjs +6 -4
  31. package/src/route-lazy.mjs +80 -0
  32. package/src/route-tree.mjs +15 -3
  33. package/src/routing.mjs +1 -1
  34. package/src/schema-definition.mjs +8 -1
  35. package/src/source-attributes.mjs +24 -0
  36. package/src/source-charts.mjs +38 -0
  37. package/src/source-collections.mjs +241 -0
  38. package/src/source-entry.mjs +50 -0
  39. package/src/source-environment.mjs +26 -0
  40. package/src/source-media.mjs +21 -0
  41. package/src/source-query-evidence.mjs +64 -0
  42. package/src/source-redirects.mjs +388 -0
  43. package/src/source-resolution.mjs +268 -0
  44. package/src/static-primitives.mjs +48 -0
  45. package/src/static-query.mjs +39 -11
  46. package/src/tab-scenes.mjs +214 -0
  47. package/src/tanstack-routes.mjs +143 -0
  48. package/src/transform.mjs +2 -2
  49. package/src/ui-preview.mjs +225 -0
  50. package/src/ui-state-machine.mjs +76 -0
  51. package/src/ui-structure.mjs +645 -0
  52. package/src/upload.mjs +28 -6
  53. package/src/validate.mjs +19 -4
  54. package/src/vite.d.ts +4 -1
  55. package/src/vite.mjs +25 -10
@@ -0,0 +1,268 @@
1
+ /** Bounded AST references only. No merchant module, getter, factory or callback is invoked. */
2
+ export function sourceResolution({ ts, resolveModule, resolveBinding }) {
3
+ const unwrap = (node) => {
4
+ while (
5
+ node &&
6
+ (ts.isParenthesizedExpression(node) ||
7
+ ts.isAsExpression(node) ||
8
+ ts.isTypeAssertionExpression(node) ||
9
+ ts.isSatisfiesExpression(node) ||
10
+ ts.isNonNullExpression(node) ||
11
+ ts.isAwaitExpression(node))
12
+ )
13
+ node = node.expression;
14
+ return node;
15
+ };
16
+ function reference(node, module, seen = new Set()) {
17
+ node = unwrap(node);
18
+ if (!node || seen.size >= 32) return null;
19
+ const key = module.file + ":" + node.pos + ":" + node.end;
20
+ if (seen.has(key)) return null;
21
+ seen = new Set([...seen, key]);
22
+ if (ts.isIdentifier(node)) {
23
+ const ref = resolveBinding(module, node.text);
24
+ return ref && reference(ref.node, ref.module, seen);
25
+ }
26
+ if (
27
+ ts.isPropertyAccessExpression(node) ||
28
+ ts.isElementAccessExpression(node)
29
+ ) {
30
+ if (node.questionDotToken) return null;
31
+ const name = ts.isPropertyAccessExpression(node)
32
+ ? node.name.text
33
+ : string(node.argumentExpression, module, seen);
34
+ if (typeof name !== "string") return null;
35
+ if (ts.isIdentifier(node.expression)) {
36
+ const imported = module.imports.get(node.expression.text);
37
+ if (imported?.name === "*") {
38
+ const target = resolveModule(module.file, imported.specifier);
39
+ const ref =
40
+ target && resolveBinding(target, target.exports.get(name) ?? name);
41
+ return ref && reference(ref.node, ref.module, seen);
42
+ }
43
+ }
44
+ const record = reference(node.expression, module, seen);
45
+ if (!record) return null;
46
+ if (ts.isEnumDeclaration(record.node)) {
47
+ const member = record.node.members.find(
48
+ (member) => member.name.text === name,
49
+ );
50
+ return member && reference(member.initializer, record.module, seen);
51
+ }
52
+ if (
53
+ !ts.isObjectLiteralExpression(record.node) ||
54
+ record.node.properties.some(
55
+ (property) =>
56
+ !ts.isPropertyAssignment(property) ||
57
+ (property.name && ts.isComputedPropertyName(property.name)),
58
+ )
59
+ )
60
+ return null;
61
+ const fields = record.node.properties.filter(
62
+ (property) => property.name.text === name,
63
+ );
64
+ return fields.length === 1
65
+ ? reference(fields[0].initializer, record.module, seen)
66
+ : null;
67
+ }
68
+ return { node, module };
69
+ }
70
+ function string(node, module, seen = new Set()) {
71
+ const ref = reference(node, module, seen);
72
+ if (!ref) return undefined;
73
+ node = ref.node;
74
+ module = ref.module;
75
+ if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node))
76
+ return node.text;
77
+ const key = module.file + ":string:" + node.pos;
78
+ if (seen.has(key) || seen.size >= 32) return undefined;
79
+ seen = new Set([...seen, key]);
80
+ if (ts.isTemplateExpression(node)) {
81
+ let result = node.head.text;
82
+ for (const span of node.templateSpans) {
83
+ const value = string(span.expression, module, seen);
84
+ if (value === undefined) return undefined;
85
+ result += value + span.literal.text;
86
+ if (result.length > 2048) return undefined;
87
+ }
88
+ return result;
89
+ }
90
+ if (
91
+ ts.isBinaryExpression(node) &&
92
+ node.operatorToken.kind === ts.SyntaxKind.PlusToken
93
+ ) {
94
+ const left = string(node.left, module, seen),
95
+ right = string(node.right, module, seen);
96
+ return left !== undefined &&
97
+ right !== undefined &&
98
+ left.length + right.length <= 2048
99
+ ? left + right
100
+ : undefined;
101
+ }
102
+ return undefined;
103
+ }
104
+ function external(module, node) {
105
+ node = unwrap(node);
106
+ if (node && ts.isIdentifier(node)) return module.imports.get(node.text);
107
+ if (
108
+ node &&
109
+ ts.isPropertyAccessExpression(node) &&
110
+ ts.isIdentifier(node.expression)
111
+ ) {
112
+ const owner = module.imports.get(node.expression.text);
113
+ if (owner && ["*", "default"].includes(owner.name))
114
+ return { specifier: owner.specifier, name: node.name.text };
115
+ }
116
+ return null;
117
+ }
118
+ function functionNode(node) {
119
+ node = unwrap(node);
120
+ return node &&
121
+ (ts.isFunctionDeclaration(node) ||
122
+ ts.isFunctionExpression(node) ||
123
+ ts.isArrowFunction(node))
124
+ ? node
125
+ : null;
126
+ }
127
+ function returned(fn) {
128
+ if (!fn?.body) return null;
129
+ if (!ts.isBlock(fn.body)) return fn.body;
130
+ return fn.body.statements.length === 1 &&
131
+ ts.isReturnStatement(fn.body.statements[0])
132
+ ? fn.body.statements[0].expression
133
+ : null;
134
+ }
135
+ // Prove only the successful module return of a forwarding function. Catch
136
+ // side effects are opaque and must rethrow; finally/alternate module returns refuse.
137
+ function forwards(node, module, parameters, depth = 0) {
138
+ node = unwrap(node);
139
+ if (!node || depth > 8 || !ts.isCallExpression(node)) return false;
140
+ if (
141
+ ts.isIdentifier(node.expression) &&
142
+ parameters.has(node.expression.text)
143
+ )
144
+ return node.arguments.length === 0;
145
+ if (!ts.isIdentifier(node.expression)) return false;
146
+ const ref = resolveBinding(module, node.expression.text),
147
+ fn = ref && functionNode(ref.node);
148
+ if (
149
+ !fn ||
150
+ fn.asteriskToken ||
151
+ fn.parameters.length !== 1 ||
152
+ fn.parameters[0].dotDotDotToken ||
153
+ !ts.isIdentifier(fn.parameters[0].name) ||
154
+ node.arguments.length !== 1 ||
155
+ !ts.isIdentifier(node.arguments[0]) ||
156
+ !parameters.has(node.arguments[0].text)
157
+ )
158
+ return false;
159
+ const nested = new Set([fn.parameters[0].name.text]);
160
+ let result = returned(fn);
161
+ if (
162
+ !result &&
163
+ fn.body &&
164
+ ts.isBlock(fn.body) &&
165
+ fn.body.statements.length === 1
166
+ ) {
167
+ const statement = fn.body.statements[0];
168
+ if (
169
+ !ts.isTryStatement(statement) ||
170
+ statement.finallyBlock ||
171
+ !statement.catchClause ||
172
+ !ts.isThrowStatement(statement.catchClause.block.statements.at(-1))
173
+ )
174
+ return false;
175
+ let overrides = false;
176
+ const visit = (item) => {
177
+ if (ts.isReturnStatement(item)) overrides = true;
178
+ ts.forEachChild(item, visit);
179
+ };
180
+ visit(statement.catchClause);
181
+ if (
182
+ overrides ||
183
+ statement.tryBlock.statements.length !== 1 ||
184
+ !ts.isReturnStatement(statement.tryBlock.statements[0])
185
+ )
186
+ return false;
187
+ result = statement.tryBlock.statements[0].expression;
188
+ }
189
+ return forwards(result, ref.module, nested, depth + 1);
190
+ }
191
+ function lazyFactory(call, module) {
192
+ if (call.arguments.length !== 1) return null;
193
+ const imported = external(module, call.expression);
194
+ if (imported?.specifier === "react" && imported.name === "lazy")
195
+ return { node: call.arguments[0], module, wrapped: false };
196
+ if (!ts.isIdentifier(call.expression)) return null;
197
+ const ref = resolveBinding(module, call.expression.text),
198
+ fn = ref && functionNode(ref.node);
199
+ if (
200
+ !fn ||
201
+ fn.asteriskToken ||
202
+ fn.parameters.length !== 1 ||
203
+ fn.parameters[0].dotDotDotToken ||
204
+ !ts.isIdentifier(fn.parameters[0].name)
205
+ )
206
+ return null;
207
+ if (fn.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.AsyncKeyword)) return null;
208
+ const value = unwrap(returned(fn));
209
+ if (!value || !ts.isCallExpression(value) || value.arguments.length !== 1)
210
+ return null;
211
+ const wrapper = external(ref.module, value.expression),
212
+ callback = functionNode(value.arguments[0]);
213
+ if (
214
+ wrapper?.specifier !== "react" ||
215
+ wrapper.name !== "lazy" ||
216
+ !callback ||
217
+ callback.asteriskToken ||
218
+ callback.parameters.length ||
219
+ !forwards(
220
+ returned(callback),
221
+ ref.module,
222
+ new Set([fn.parameters[0].name.text]),
223
+ )
224
+ )
225
+ return null;
226
+ return { node: call.arguments[0], module, wrapped: true };
227
+ }
228
+ function component(module, name) {
229
+ if (typeof name !== "string" || !name) return null;
230
+ const parts = name.split(".");
231
+ let ref;
232
+ if (parts.length === 2) {
233
+ const imported = module.imports.get(parts[0]),
234
+ target =
235
+ imported &&
236
+ ["*", "default"].includes(imported.name) &&
237
+ resolveModule(module.file, imported.specifier);
238
+ ref =
239
+ target &&
240
+ resolveBinding(target, target.exports.get(parts[1]) ?? parts[1]);
241
+ } else if (parts.length === 1) ref = resolveBinding(module, name);
242
+ if (!ref) return null;
243
+ const node = unwrap(ref.node);
244
+ if (node && ts.isCallExpression(node)) {
245
+ const factory = lazyFactory(node, ref.module),
246
+ callback = factory && functionNode(factory.node),
247
+ body = callback && unwrap(returned(callback));
248
+ if (
249
+ body &&
250
+ !callback.asteriskToken &&
251
+ !callback.parameters.length &&
252
+ ts.isCallExpression(body) &&
253
+ body.expression.kind === ts.SyntaxKind.ImportKeyword &&
254
+ body.arguments.length === 1
255
+ ) {
256
+ const specifier = string(body.arguments[0], factory.module),
257
+ target =
258
+ typeof specifier === "string" &&
259
+ resolveModule(factory.module.file, specifier);
260
+ const resolved = target && resolveBinding(target, target.defaultName);
261
+ if (resolved)
262
+ return { ...resolved, lazy: true, wrapped: factory.wrapped };
263
+ }
264
+ }
265
+ return ref;
266
+ }
267
+ return { reference, string, component };
268
+ }
@@ -0,0 +1,48 @@
1
+ /** A finite source-only React Router matcher. No merchant modules or regular expressions run. */
2
+ export function sourceMatchPath(input, pathname) {
3
+ const options = typeof input === "string" ? { path: input } : input;
4
+ if (
5
+ !options ||
6
+ typeof options.path !== "string" ||
7
+ typeof pathname !== "string" ||
8
+ options.path.length > 1024 ||
9
+ pathname.length > 4000
10
+ )
11
+ throw Error("Unsupported source path");
12
+ const parts = options.path.split("/").filter(Boolean),
13
+ names = [];
14
+ let pattern = "^";
15
+ for (const [index, part] of parts.entries()) {
16
+ if (part === "*" && index === parts.length - 1) {
17
+ names.push("*");
18
+ pattern += "(?:/(.*))?";
19
+ } else if (/^:[A-Za-z_][\w-]*\??$/.test(part)) {
20
+ names.push(part.slice(1).replace(/\?$/, ""));
21
+ pattern += part.endsWith("?") ? "(?:/([^/]+))?" : "/([^/]+)";
22
+ } else if (/^[\w.~-]+$/.test(part))
23
+ pattern += "/" + part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24
+ else throw Error("Unsupported source pattern");
25
+ }
26
+ pattern += options.end === false ? "(?=/|$)" : "/*$";
27
+ const match = new RegExp(pattern, options.caseSensitive ? "" : "i").exec(
28
+ pathname,
29
+ );
30
+ if (!match) return null;
31
+ return {
32
+ params: Object.fromEntries(
33
+ names.map((name, index) => [
34
+ name,
35
+ match[index + 1] === undefined
36
+ ? undefined
37
+ : decodeURIComponent(match[index + 1]),
38
+ ]),
39
+ ),
40
+ pathname: match[0] || "/",
41
+ pathnameBase: (match[0] || "/").replace(/\/+$/, "") || "/",
42
+ pattern: {
43
+ path: options.path,
44
+ caseSensitive: options.caseSensitive ?? false,
45
+ end: options.end ?? true,
46
+ },
47
+ };
48
+ }
@@ -1,6 +1,7 @@
1
+ import {sourceMatchPath} from './static-primitives.mjs';
1
2
  // Abstract interpretation of bounded, synchronous, read-only source helpers.
2
3
  // No import(), eval, native callback invocation, await or object mutation.
3
- export function staticQuery({ ts, node, module, context, binding, unknown, redact, sensitive }) {
4
+ export function staticQuery({ ts, node, module, context, binding, unknown, redact, sensitive, reference, isReference, imported, project }) {
4
5
  let steps = 0;
5
6
  const closure = Symbol("source-function"), writable = Symbol("function-locals"), fail = () => { throw unknown; };
6
7
  const tick = () => { if (++steps > 6000) fail(); };
@@ -9,7 +10,9 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
9
10
  const scalar = value => value === null || ["string", "boolean", "number", "undefined"].includes(typeof value);
10
11
  function clean(value, depth = 0) {
11
12
  tick();
12
- if (depth > 20 || value === unknown || value?.[closure]) fail();
13
+ if (depth > 20) fail();
14
+ if(isReference?.(value))return value;
15
+ if(value===unknown||value?.[closure]){if(depth>0)return unknown;fail();}
13
16
  if (typeof value === "number" && !Number.isFinite(value)) fail();
14
17
  if (typeof value === "string") return redact(value);
15
18
  if (Array.isArray(value)) { if (value.length > 256) fail(); return value.map(v => clean(v, depth + 1)); }
@@ -20,6 +23,7 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
20
23
  function read(n, m, env, seen = new Set(), depth = 0) {
21
24
  tick(); n = unwrap(n); if (!n || depth > 24) fail();
22
25
  const ev = child => read(child, m, env, seen, depth + 1);
26
+ const opaque=!(ts.isIdentifier(n)&&Object.hasOwn(env,n.text))&&reference?.(n,m);if(opaque)return opaque;
23
27
  if (ts.isStringLiteralLike(n)) return redact(n.text);
24
28
  if (ts.isNumericLiteral(n)) return Number(n.text);
25
29
  if (n.kind === ts.SyntaxKind.TrueKeyword) return true;
@@ -38,11 +42,11 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
38
42
  if (ts.isObjectLiteralExpression(n)) {
39
43
  const value = Object.create(null);
40
44
  for (const p of n.properties) {
41
- if (ts.isShorthandPropertyAssignment(p)) { value[p.name.text] = sensitive(p.name.text) ? unknown : ev(p.name); continue; }
45
+ if (ts.isShorthandPropertyAssignment(p)) { try{value[p.name.text] = sensitive(p.name.text) ? unknown : ev(p.name);}catch{value[p.name.text]=unknown;}continue; }
42
46
  if (!ts.isPropertyAssignment(p) || !p.name || ts.isComputedPropertyName(p.name)) fail();
43
47
  const key = p.name.text; if (!key || ["__proto__", "constructor", "prototype"].includes(key)) fail();
44
48
  // Keep unrelated async/mutating helpers opaque, never evaluate their bodies.
45
- value[key] = sensitive(key) ? unknown : ev(p.initializer);
49
+ try{value[key] = sensitive(key) ? unknown : ev(p.initializer);}catch{value[key]=unknown;}
46
50
  }
47
51
  return value;
48
52
  }
@@ -50,8 +54,11 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
50
54
  const object = ev(n.expression), key = ts.isPropertyAccessExpression(n) ? n.name.text : ev(n.argumentExpression);
51
55
  if (object == null && n.questionDotToken) return undefined;
52
56
  if (object == null || ["__proto__", "constructor", "prototype"].includes(key) || sensitive(String(key))) fail();
53
- return Object.hasOwn(Object(object), key) || key === "length" ? object[key] : undefined;
57
+ const result=Object.hasOwn(Object(object), key) || key === "length" ? object[key] : undefined;
58
+ if(result===unknown)fail();return result;
54
59
  }
60
+ if(ts.isTemplateExpression(n)){let text=n.head.text;for(const span of n.templateSpans){const v=ev(span.expression);if(!scalar(v)||v===unknown)fail();text+=String(v)+span.literal.text;if(text.length>4000)fail();}return redact(text);}
61
+ if(ts.isRegularExpressionLiteral(n)){const raw=n.getText(m.source);if(raw==='/[?#]/')return {sourceRegex:'query-separator'};if(raw==='/\\/+$/')return {sourceRegex:'trailing-slashes'};if(raw==='/^./')return {sourceRegex:'first-character'};fail();}
55
62
  if (ts.isTypeOfExpression(n)) return typeof ev(n.expression);
56
63
  if (ts.isPrefixUnaryExpression(n)) { const value = ev(n.operand); if (n.operator === ts.SyntaxKind.ExclamationToken) return !value; if (n.operator === ts.SyntaxKind.MinusToken && typeof value === "number") return -value; fail(); }
57
64
  if (ts.isBinaryExpression(n)) {
@@ -67,15 +74,34 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
67
74
  }
68
75
  if (ts.isConditionalExpression(n)) return ev(ev(n.condition) ? n.whenTrue : n.whenFalse);
69
76
  if (ts.isCallExpression(n)) {
77
+ const projected=project?.(n,m,env);if(Array.isArray(projected))return projected[0];
70
78
  if (n.arguments.length > 8) fail();
79
+ const primitive=imported?.(m,n.expression.getText(m.source));
80
+ if(primitive?.specifier==='react'&&primitive.name==='useMemo'&&n.arguments.length===2){const fn=ev(n.arguments[0]);return call(fn,[],depth+1);}
71
81
  const args = n.arguments.map(ev);
82
+ if(ts.isPropertyAccessExpression(n.expression)&&ts.isIdentifier(n.expression.expression)&&n.expression.expression.text==='Object'&&!Object.hasOwn(env,'Object')&&!binding(m,'Object')){
83
+ const method=n.expression.name.text,target=args[0];
84
+ if(!plain(target)||isReference?.(target)||Object.keys(target).length>256)fail();
85
+ if(method==='keys'&&args.length===1)return Object.keys(target);
86
+ if(method==='hasOwn'&&args.length===2&&['string','number'].includes(typeof args[1]))return Object.hasOwn(target,args[1]);
87
+ fail();
88
+ }
89
+ if(['react-router','react-router-dom'].includes(primitive?.specifier)&&primitive.name==='matchPath'&&args.length===2)return sourceMatchPath(args[0],args[1]);
90
+ if(ts.isIdentifier(n.expression)&&n.expression.text==='String'&&args.length===1&&scalar(args[0]))return String(args[0]);
72
91
  if (ts.isPropertyAccessExpression(n.expression)) {
73
92
  const target = ev(n.expression.expression), method = n.expression.name.text;
74
- if (Array.isArray(target) && ["find", "filter", "map"].includes(method) && args.length === 1 && args[0]?.[closure]) {
93
+ if (Array.isArray(target) && ["find", "filter", "map", "some", "every", "flatMap"].includes(method) && args.length === 1 && args[0]?.[closure]) {
75
94
  if (target.length > 256) fail();
76
95
  const result = [];
77
- for (let i = 0; i < target.length; i++) { const value = call(args[0], [target[i], i], depth + 1); if (method === "find" && value) return target[i]; if (method === "filter" && value) result.push(target[i]); if (method === "map") result.push(value); }
78
- return method === "find" ? undefined : result;
96
+ for (let i = 0; i < target.length; i++) { const value = call(args[0], [target[i], i], depth + 1); if (method === "find" && value) return target[i]; if (method === "filter" && value) result.push(target[i]); if (method === "map") result.push(value); if(method==="flatMap"){if(!Array.isArray(value))fail();result.push(...value);} if(method==="some"&&value)return true;if(method==="every"&&!value)return false; }
97
+ return method === "find" ? undefined : method==="some"?false:method==="every"?true:result;
98
+ }
99
+ if(typeof target==='string'&&target.length<=4000){
100
+ if(['startsWith','endsWith','includes','toLowerCase','toUpperCase','trim','slice'].includes(method)&&args.every(v=>['string','number'].includes(typeof v)))return String.prototype[method].apply(target,args);
101
+ if(method==='split'&&args[0]?.sourceRegex==='query-separator'&&args[1]===1)return target.split(/[?#]/,1);
102
+ if(method==='replace'&&args[0]?.sourceRegex==='trailing-slashes'&&args[1]==='')return target.replace(/\/+$/,'');
103
+ if(method==='replaceAll'&&args.length===2&&typeof args[0]==='string'&&args[0].length&&typeof args[1]==='string'){const result=target.split(args[0]).join(args[1]);if(result.length>4000)fail();return result;}
104
+ if(method==='replace'&&args[0]?.sourceRegex==='first-character'&&args[1]?.[closure]){const first=call(args[1],[target.charAt(0)],depth+1);if(typeof first!=='string')fail();return first+target.slice(1);}
79
105
  }
80
106
  }
81
107
  return call(ev(n.expression), args, depth + 1);
@@ -86,9 +112,11 @@ export function staticQuery({ ts, node, module, context, binding, unknown, redac
86
112
  tick(); if (!fn?.[closure] || depth > 24 || fn.node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword)) fail();
87
113
  const env = { ...fn.env, [writable]: new Set() };
88
114
  for (let i = 0; i < fn.node.parameters.length; i++) {
89
- const p = fn.node.parameters[i]; if (!ts.isIdentifier(p.name) || p.dotDotDotToken) fail();
90
- env[p.name.text] = args[i] === undefined && p.initializer ? read(p.initializer, fn.module, env, new Set(), depth + 1) : args[i];
91
- env[writable].add(p.name.text);
115
+ const p = fn.node.parameters[i]; if (p.dotDotDotToken) fail();
116
+ const value=args[i] === undefined && p.initializer ? read(p.initializer, fn.module, env, new Set(), depth + 1) : args[i];
117
+ if(ts.isIdentifier(p.name)){env[p.name.text]=value;env[writable].add(p.name.text);}
118
+ else if(ts.isArrayBindingPattern(p.name)&&Array.isArray(value)&&p.name.elements.length<=8){for(const [index,field] of p.name.elements.entries()){if(ts.isOmittedExpression(field))continue;if(!ts.isIdentifier(field.name)||field.dotDotDotToken)fail();env[field.name.text]=value[index]===undefined&&field.initializer?read(field.initializer,fn.module,env,new Set(),depth+1):value[index];env[writable].add(field.name.text);}}
119
+ else fail();
92
120
  }
93
121
  if (!ts.isBlock(fn.node.body)) return read(fn.node.body, fn.module, env, new Set(), depth + 1);
94
122
  const result = statements(fn.node.body.statements, fn.module, env, depth + 1); return result?.value;
@@ -0,0 +1,214 @@
1
+ import { JSDOM, VirtualConsole } from "jsdom";
2
+ import { documentDom } from "./dom-payload.mjs";
3
+ import { createStateMachine } from "./ui-state-machine.mjs";
4
+ import { previewUiDocument } from "./ui-preview.mjs";
5
+ import {graphSource} from './graph-sources.mjs';
6
+
7
+ /** Build thumbnail states and edges by dispatching the IAM's actual scalar transitions. */
8
+ export function tabUiScenes(manifest, tabKey) {
9
+ const graph = manifest.routeTree,
10
+ tab = graph.ui?.tabs.find((t) => t.tabKey === tabKey);
11
+ if (!tab || !tab.variableKey) throw Error("Unknown controlled source Tab");
12
+ const render = graph.renders.find((r) => r.renderKey === tab.renderKey),
13
+ document = manifest.documents.find(
14
+ (d) => d.documentKey === render.documentKey,
15
+ );
16
+ const dom = new JSDOM(documentDom(document).html, {
17
+ virtualConsole: new VirtualConsole(),
18
+ });
19
+ try {
20
+ const doc = dom.window.document,
21
+ nodes = new Map(
22
+ [...doc.querySelectorAll("[data-iam-node]")].map((n) => [
23
+ n.getAttribute("data-iam-node"),
24
+ n,
25
+ ]),
26
+ );
27
+ const ancestors = [];
28
+ let current = nodes.get(tab.nodeId);
29
+ while (current && ancestors.length < 128) {
30
+ ancestors.push(current);
31
+ current = current.parentElement;
32
+ }
33
+ const conditions = {},
34
+ regions = {},
35
+ values = {},
36
+ ids = new Set(ancestors.map((n) => n.getAttribute("data-iam-node")));
37
+ for (const node of ancestors) {
38
+ if (node.hasAttribute("data-iam-condition")) {
39
+ const truthy = node.getAttribute("data-iam-alternative") === "true",
40
+ root = truthy ? node : node.previousElementSibling;
41
+ if (
42
+ !root ||
43
+ root.getAttribute("data-iam-condition") !==
44
+ node.getAttribute("data-iam-condition")
45
+ )
46
+ throw Error("Invalid source condition pair");
47
+ conditions[root.getAttribute("data-iam-node")] = truthy;
48
+ }
49
+ if (node.hasAttribute("data-iam-region-value")) {
50
+ const region = (graph.ui.regions ?? []).find((r) =>
51
+ r.options.some(
52
+ (o) => o.nodeId === node.getAttribute("data-iam-node"),
53
+ ),
54
+ );
55
+ if (region)
56
+ regions[region.regionKey] = node.getAttribute(
57
+ "data-iam-region-value",
58
+ );
59
+ }
60
+ }
61
+ for (const binding of graph.bindings.filter(
62
+ (b) =>
63
+ b.renderKey === tab.renderKey &&
64
+ b.property === "visible" &&
65
+ ids.has(b.nodeId),
66
+ )) {
67
+ if (binding.value.op === "var") values[binding.value.key] = true;
68
+ if (binding.value.op === "not" && binding.value.arg.op === "var")
69
+ values[binding.value.arg.key] = false;
70
+ if (
71
+ binding.value.op === "eq" &&
72
+ binding.value.left.op === "var" &&
73
+ binding.value.right.op === "const"
74
+ )
75
+ values[binding.value.left.key] = binding.value.right.value;
76
+ }
77
+ const scope = nodes.get(tab.scopeNodeId ?? tab.nodeId),
78
+ overlay = scope?.getAttribute("role") === "dialog",
79
+ width = Number(
80
+ /\bw-\[(\d+)px\]/.exec(scope?.getAttribute("class") ?? "")?.[1] || 0,
81
+ );
82
+ const viewport = overlay
83
+ ? {
84
+ width: width ? Math.max(320, width + 48) : 640,
85
+ height: width ? 480 : 400,
86
+ }
87
+ : { width: 480, height: 760 };
88
+ const base = {
89
+ strategy: "content",
90
+ conditions,
91
+ regions,
92
+ values,
93
+ focusNodeId: tab.scopeNodeId ?? tab.nodeId,
94
+ viewport,
95
+ };
96
+ const initial = createStateMachine(
97
+ graph,
98
+ tab.routeKey,
99
+ tab.renderKey,
100
+ ).snapshot(),
101
+ scenes = [];
102
+ for (const item of tab.items) {
103
+ const machine = createStateMachine(graph, tab.routeKey, tab.renderKey);
104
+ machine.preview(values);
105
+ const transition = graph.transitions.find(
106
+ (t) =>
107
+ t.renderKey === tab.renderKey &&
108
+ t.trigger.nodeId === item.nodeId &&
109
+ t.trigger.event === "click",
110
+ );
111
+ const result = transition
112
+ ? machine.dispatch(item.nodeId, "click", {})
113
+ : null;
114
+ if (!result || machine.snapshot()[tab.variableKey] !== item.value)
115
+ continue;
116
+ const changed = Object.fromEntries(
117
+ Object.entries(machine.snapshot()).filter(
118
+ ([key, value]) => initial[key] !== value || key === tab.variableKey,
119
+ ),
120
+ );
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
+ : [];
147
+ const allowedIds = new Set([
148
+ ...ancestors.map((n) => n.getAttribute("data-iam-node")),
149
+ ...(scope
150
+ ? [scope, ...scope.querySelectorAll("[data-iam-node]")].map((n) =>
151
+ n.getAttribute("data-iam-node"),
152
+ )
153
+ : []),
154
+ ]);
155
+ scenes.push({
156
+ assumptions: preview.assumptions.filter((a) =>
157
+ allowedIds.has(a.groupKey),
158
+ ),
159
+ value: item.value,
160
+ label:
161
+ nodes.get(item.nodeId)?.textContent.replace(/\s+/g, " ").trim() ||
162
+ item.value,
163
+ options,
164
+ headings: [...new Set(headings)].slice(0, 8),
165
+ content: content.slice(0, 1000),
166
+ fields,
167
+ collections: preview.collectionChoices.filter((c) => {
168
+ const row = (graph.ui.collections ?? []).find(
169
+ (r) => r.collectionKey === c.collectionKey,
170
+ ),
171
+ node = row && nodes.get(row.nodeId);
172
+ return node && (scope === node || scope?.contains(node));
173
+ }),
174
+ unobserved: preview.unobserved.filter((u) =>
175
+ allowedIds.has(u.groupKey),
176
+ ),
177
+ transitionKey: result.transitionKey,
178
+ changedNodeIds: tab.contentNodeIds ?? [],
179
+ });
180
+ sceneDom.window.close();
181
+ }
182
+ const edges = [];
183
+ for (const from of scenes)
184
+ for (const to of scenes) {
185
+ if (from.value === to.value) continue;
186
+ const item = tab.items.find((i) => i.value === to.value),
187
+ machine = createStateMachine(graph, tab.routeKey, tab.renderKey);
188
+ machine.preview(from.options.values);
189
+ const result = machine.dispatch(item.nodeId, "click", {});
190
+ if (!result || machine.snapshot()[tab.variableKey] !== to.value)
191
+ continue;
192
+ const transition = graph.transitions.find(
193
+ (t) => t.transitionKey === result.transitionKey,
194
+ );
195
+ edges.push({
196
+ from: from.value,
197
+ to: to.value,
198
+ transitionKey: result.transitionKey,
199
+ event: transition.trigger.event,
200
+ variableKey: tab.variableKey,
201
+ writes: transition.stateWrites.map((w) => ({
202
+ variableKey: w.variableKey,
203
+ label: graph.variables.find((v) => v.variableKey === w.variableKey)
204
+ .label,
205
+ value: machine.snapshot()[w.variableKey],
206
+ })),
207
+ sourceRefs: [graphSource(graph,transition), tab.scopeSource],
208
+ });
209
+ }
210
+ return { tabKey, scenes, edges };
211
+ } finally {
212
+ dom.window.close();
213
+ }
214
+ }