@tinkerlandai/indiana-iam 0.3.0 → 0.5.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/README.md +81 -5
- package/assets/document-controller.js +18 -1
- package/assets/document-runtime.js +189 -1
- package/assets/viewer.js +78 -36
- package/package.json +12 -14
- package/src/analyze.mjs +968 -146
- package/src/anchors.mjs +2 -0
- package/src/build-options.d.ts +1 -1
- package/src/build-runtime.mjs +4 -1
- package/src/build-styles.mjs +56 -8
- package/src/build.mjs +3 -3
- package/src/component-registry.mjs +229 -0
- package/src/dependencies.mjs +2 -2
- package/src/document-material.mjs +180 -0
- package/src/dom-payload-json.mjs +2 -2
- package/src/dom-payload.mjs +3 -3
- package/src/framework-routes.mjs +4 -3
- package/src/generate.mjs +107 -25
- package/src/graph-sources.mjs +54 -0
- package/src/index.d.ts +2 -1
- package/src/index.mjs +1 -1
- package/src/json.mjs +2 -3
- package/src/library-entry.mjs +9 -2
- package/src/lucide-source.mjs +18 -0
- package/src/next-loader.cjs +7 -1
- package/src/next.cjs +5 -0
- package/src/node-hashes.mjs +10 -9
- package/src/render-index.mjs +7 -7
- package/src/route-composition.mjs +5 -5
- package/src/route-lazy.mjs +80 -0
- package/src/route-tree.mjs +19 -7
- package/src/routing.mjs +7 -6
- package/src/schema-definition.mjs +13 -15
- package/src/selected-manifest.mjs +12 -0
- package/src/source-aliases.mjs +34 -0
- package/src/source-attributes.mjs +24 -0
- package/src/source-charts.mjs +53 -0
- package/src/source-collections.mjs +241 -0
- package/src/source-css-modules.mjs +79 -0
- package/src/source-environment.mjs +26 -0
- package/src/source-files.mjs +8 -7
- package/src/source-flowbite.mjs +60 -0
- package/src/source-i18next.mjs +22 -0
- package/src/source-mantine.mjs +61 -0
- package/src/source-mdx.mjs +92 -0
- package/src/source-media.mjs +30 -0
- package/src/source-query-evidence.mjs +64 -0
- package/src/source-redirects.mjs +388 -0
- package/src/source-styles.mjs +9 -0
- package/src/static-primitives.mjs +48 -0
- package/src/static-query.mjs +102 -14
- package/src/svg-preview.mjs +23 -0
- package/src/tab-scenes.mjs +214 -0
- package/src/tanstack-routes.mjs +1 -1
- package/src/ui-preview.mjs +225 -0
- package/src/ui-state-machine.mjs +76 -0
- package/src/ui-structure.mjs +634 -0
- package/src/validate.mjs +5 -0
- package/src/viewer-index.mjs +49 -0
package/src/analyze.mjs
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
|
+
import {referencedRasterSources} from "./source-media.mjs";
|
|
3
|
+
import {sourceAliases} from './source-aliases.mjs';
|
|
4
|
+
import {sourceCssModules} from './source-css-modules.mjs';
|
|
5
|
+
import {mantinePrimitive} from './source-mantine.mjs';
|
|
6
|
+
import {sourceMdx} from './source-mdx.mjs';
|
|
7
|
+
import {attachSourceStyles} from './source-styles.mjs';
|
|
8
|
+
import {sourceI18next} from './source-i18next.mjs';
|
|
9
|
+
import {uiEnvironmentPrimitive} from "./source-environment.mjs";
|
|
10
|
+
import {sourceChart} from "./source-charts.mjs";
|
|
11
|
+
import {flowbiteShape,rootFlowbiteTheme} from "./source-flowbite.mjs";
|
|
2
12
|
import path from "node:path";
|
|
3
13
|
import { automaticSourceAnchors, anchorAttributes, sourceScriptKind } from "./anchors.mjs";
|
|
4
14
|
import { sourceFiles } from "./source-files.mjs";
|
|
@@ -7,9 +17,16 @@ import { assembleRouteTree, readVariables } from "./route-tree.mjs";
|
|
|
7
17
|
import { sourceEntry } from "./source-entry.mjs";
|
|
8
18
|
import { frameworkRoutes, externalImport } from "./framework-routes.mjs";
|
|
9
19
|
import { sourceResolution } from "./source-resolution.mjs";
|
|
10
|
-
import { routeIndexDigest, refreshRenderHashes } from "./render-index.mjs";
|
|
20
|
+
import { routeIndexDigest, refreshRenderHashes, flattenComposition } from "./render-index.mjs";
|
|
11
21
|
import { storeDocumentNodes } from "./node-hashes.mjs";
|
|
12
22
|
import { staticQuery } from "./static-query.mjs";
|
|
23
|
+
import { attachUiStructure } from './ui-structure.mjs';
|
|
24
|
+
import { componentRegistry } from './component-registry.mjs';
|
|
25
|
+
import { sourceRedirects } from './source-redirects.mjs';
|
|
26
|
+
import {internGraphSources} from './graph-sources.mjs';
|
|
27
|
+
import {collectionStateReads,sourceCollectionFragment} from './source-collections.mjs';
|
|
28
|
+
import {sourceQueryEvidence} from './source-query-evidence.mjs';
|
|
29
|
+
import {lucideSource,lucideGeometry,LUCIDE_SOURCE,LUCIDE_SOURCE_VERSION} from './lucide-source.mjs';
|
|
13
30
|
import {
|
|
14
31
|
publicRoute,
|
|
15
32
|
assertRoutes,
|
|
@@ -29,6 +46,28 @@ const { JSDOM } = dependency("jsdom").value;
|
|
|
29
46
|
const UNKNOWN = Symbol("unknown");
|
|
30
47
|
import { sensitiveName, redactLiteral, privateUrl } from "./privacy.mjs";
|
|
31
48
|
const isUnknown = (value) => value === UNKNOWN;
|
|
49
|
+
const COMPONENT=Symbol('source-component');
|
|
50
|
+
const TRANSLATION=Symbol('source-translation-catalog');
|
|
51
|
+
const TRANSLATION_FORMAT=Symbol('source-translation-format');
|
|
52
|
+
const UI_SETTER=Symbol('source-ui-setter');
|
|
53
|
+
const UI_PRIMITIVE=Symbol('source-ui-primitive');
|
|
54
|
+
|
|
55
|
+
function uiReturnExpression(fn) {
|
|
56
|
+
if(!fn?.body||!ts.isBlock(fn.body))return returnExpression(fn);
|
|
57
|
+
const returned=statement=>ts.isReturnStatement(statement)?statement.expression:ts.isBlock(statement)&&statement.statements.every(s=>ts.isVariableStatement(s)||ts.isReturnStatement(s))?returned(statement.statements.at(-1)):null;
|
|
58
|
+
function sequence(statements) {
|
|
59
|
+
for(let i=0;i<statements.length;i++) {
|
|
60
|
+
const statement=statements[i];
|
|
61
|
+
if(ts.isReturnStatement(statement))return statement.expression;
|
|
62
|
+
if(ts.isIfStatement(statement)) {
|
|
63
|
+
const yes=returned(statement.thenStatement),no=statement.elseStatement?returned(statement.elseStatement):sequence(statements.slice(i+1));
|
|
64
|
+
if(yes&&no)return {kind:ts.SyntaxKind.ConditionalExpression,condition:statement.expression,whenTrue:yes,whenFalse:no};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return returnExpression(fn);
|
|
68
|
+
}
|
|
69
|
+
return sequence([...fn.body.statements]);
|
|
70
|
+
}
|
|
32
71
|
// React numeric styles use px except for unitless properties and CSS variables.
|
|
33
72
|
const unitlessStyles = new Set("animationIterationCount aspectRatio borderImageOutset borderImageSlice borderImageWidth boxFlex boxFlexGroup boxOrdinalGroup columnCount columns flex flexGrow flexPositive flexShrink flexNegative flexOrder gridArea gridRow gridRowEnd gridRowSpan gridRowStart gridColumn gridColumnEnd gridColumnSpan gridColumnStart fontWeight lineClamp lineHeight opacity order orphans scale tabSize widows zIndex zoom fillOpacity floodOpacity stopOpacity strokeDasharray strokeDashoffset strokeMiterlimit strokeOpacity strokeWidth".split(" "));
|
|
34
73
|
function staticStyle(value, unsupported) {
|
|
@@ -52,11 +91,11 @@ function staticStyle(value, unsupported) {
|
|
|
52
91
|
}).join(";");
|
|
53
92
|
}
|
|
54
93
|
const allowedTags = new Set(
|
|
55
|
-
"main section article aside header footer nav div span p h1 h2 h3 h4 h5 h6 a button form label input select option textarea ul ol li dl dt dd table thead tbody tr th td caption fieldset legend details summary pre code blockquote strong em b i small s u mark time br hr img picture source svg path circle rect line polyline polygon ellipse g title".split(
|
|
94
|
+
"main section article aside header footer nav div span p h1 h2 h3 h4 h5 h6 a button form label input select option textarea ul ol li dl dt dd table thead tbody tfoot tr th td caption fieldset legend details summary pre code blockquote strong em b i small s del u mark time br hr wbr img picture source svg path circle rect line polyline polygon ellipse g title".split(
|
|
56
95
|
" ",
|
|
57
96
|
),
|
|
58
97
|
);
|
|
59
|
-
const voidTags = new Set(["input", "br", "hr", "img", "source"]);
|
|
98
|
+
const voidTags = new Set(["input", "br", "hr", "wbr", "img", "source"]);
|
|
60
99
|
const escape = (text) =>
|
|
61
100
|
redactLiteral(text).replace(
|
|
62
101
|
/[&<>"']/g,
|
|
@@ -156,8 +195,8 @@ export async function analyze({
|
|
|
156
195
|
name,
|
|
157
196
|
repository = "",
|
|
158
197
|
commit = "",
|
|
159
|
-
maxFiles =
|
|
160
|
-
maxBytes =
|
|
198
|
+
maxFiles = Infinity,
|
|
199
|
+
maxBytes = Infinity,
|
|
161
200
|
navigationPaths = [],
|
|
162
201
|
entry,
|
|
163
202
|
automaticAnchors = false,
|
|
@@ -170,14 +209,15 @@ export async function analyze({
|
|
|
170
209
|
elements = new Map(),
|
|
171
210
|
components = new Map(),
|
|
172
211
|
assets = new Map();
|
|
173
|
-
const cssSources = [];
|
|
212
|
+
const cssSources = [], cssModuleFiles = new Map();
|
|
213
|
+
const jsonResources=new Map(),translationResources=new Set();
|
|
214
|
+
const presentation={conditions:[],tabs:[],navigation:[],regions:[],collections:[],fragments:[],attributes:[],attributeSets:[]},collectionTemplates=new Map();
|
|
174
215
|
const ui = { variables: [], bindings: [], transitions: [], uncovered: [] };
|
|
175
216
|
const uiVariableKeys = new Set();
|
|
176
217
|
let byteCount = 0;
|
|
177
218
|
function gap(code, file, detail) {
|
|
178
219
|
detail = redactLiteral(detail);
|
|
179
220
|
if (
|
|
180
|
-
gaps.length < 1000 &&
|
|
181
221
|
!gaps.some(
|
|
182
222
|
(x) => x.code === code && x.file === file && x.detail === detail,
|
|
183
223
|
)
|
|
@@ -191,39 +231,44 @@ export async function analyze({
|
|
|
191
231
|
const real = await fs.realpath(absolute);
|
|
192
232
|
if (!real.startsWith(root + path.sep))
|
|
193
233
|
throw new Error("Source path escapes root");
|
|
194
|
-
if (
|
|
234
|
+
if ((byteCount += stat.size) > maxBytes)
|
|
195
235
|
throw new Error("Source byte budget exceeded");
|
|
196
236
|
const bytes = await fs.readFile(absolute);
|
|
197
|
-
const
|
|
237
|
+
const originalText = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
238
|
+
let text = originalText;
|
|
198
239
|
inventory.push({
|
|
199
240
|
file,
|
|
200
241
|
sha256: sha256(bytes),
|
|
201
242
|
bytes: bytes.length,
|
|
202
243
|
});
|
|
244
|
+
if(file.endsWith('.json')){try{jsonResources.set(file,JSON.parse(text));}catch{/* Non-resource JSON stays opaque. */}continue;}
|
|
203
245
|
if (file.endsWith(".css")) {
|
|
246
|
+
if (file.endsWith('.module.css')) { cssModuleFiles.set(file, text); continue; }
|
|
204
247
|
gap(
|
|
205
|
-
|
|
206
|
-
? "CSS_MODULES_UNSUPPORTED"
|
|
207
|
-
: "STYLE_SOURCE_PARTIAL",
|
|
248
|
+
"STYLE_SOURCE_PARTIAL",
|
|
208
249
|
file,
|
|
209
250
|
"Only root/dark tokens and pinned utility rules are reconstructed; scoped/custom selectors, keyframes and original font rules are not compiled",
|
|
210
251
|
);
|
|
211
252
|
if (!file.endsWith(".module.css")) cssSources.push({ file, text });
|
|
212
253
|
continue;
|
|
213
254
|
}
|
|
214
|
-
|
|
255
|
+
const mdx = /\.mdx?$/.test(file) ? sourceMdx(file, originalText, gap) : null;
|
|
256
|
+
if (mdx) text = mdx.text;
|
|
257
|
+
if (!mdx && !/\.[cm]?[jt]sx?$/.test(file)) continue;
|
|
215
258
|
const source = ts.createSourceFile(
|
|
216
259
|
file,
|
|
217
260
|
text,
|
|
218
261
|
ts.ScriptTarget.Latest,
|
|
219
262
|
true,
|
|
220
|
-
sourceScriptKind(file, ts),
|
|
263
|
+
mdx ? ts.ScriptKind.TSX : sourceScriptKind(file, ts),
|
|
221
264
|
);
|
|
222
265
|
const module = {
|
|
223
266
|
file,
|
|
224
267
|
text,
|
|
225
268
|
source,
|
|
226
|
-
anchors: automaticAnchors ? automaticSourceAnchors(file, source) : new Map(),
|
|
269
|
+
anchors: automaticAnchors && !mdx ? automaticSourceAnchors(file, source) : new Map(),
|
|
270
|
+
mdx,
|
|
271
|
+
originalSource: mdx ? ts.createSourceFile(file, originalText, ts.ScriptTarget.Latest, false) : source,
|
|
227
272
|
bindings: new Map(),
|
|
228
273
|
imports: new Map(),
|
|
229
274
|
exports: new Map(),
|
|
@@ -295,14 +340,8 @@ export async function analyze({
|
|
|
295
340
|
}
|
|
296
341
|
modules.set(file, module);
|
|
297
342
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const config = ts.parseConfigFileTextToJson(
|
|
301
|
-
"tsconfig.json",
|
|
302
|
-
await fs.readFile(path.join(root, "tsconfig.json"), "utf8"),
|
|
303
|
-
).config;
|
|
304
|
-
alias = config.compilerOptions?.paths || alias;
|
|
305
|
-
} catch { /* Source projects may omit tsconfig.json. */ }
|
|
343
|
+
const alias = await sourceAliases(root,ts);
|
|
344
|
+
const cssModules = sourceCssModules({files: cssModuleFiles, aliases: alias, gap, redact: redactLiteral});
|
|
306
345
|
function resolveModule(from, specifier) {
|
|
307
346
|
let relative;
|
|
308
347
|
if (specifier.startsWith("."))
|
|
@@ -329,6 +368,8 @@ export async function analyze({
|
|
|
329
368
|
".jsx",
|
|
330
369
|
".js",
|
|
331
370
|
".mjs",
|
|
371
|
+
".mdx",
|
|
372
|
+
".md",
|
|
332
373
|
"/index.tsx",
|
|
333
374
|
"/index.ts",
|
|
334
375
|
"/index.jsx",
|
|
@@ -338,6 +379,78 @@ export async function analyze({
|
|
|
338
379
|
.find(Boolean) || null
|
|
339
380
|
);
|
|
340
381
|
}
|
|
382
|
+
function jsonResource(from,specifier){
|
|
383
|
+
let file=specifier.startsWith('.')?path.posix.normalize(path.posix.join(path.posix.dirname(from),specifier)):null;
|
|
384
|
+
if(!file)for(const [pattern,values] of Object.entries(alias)){const prefix=pattern.split('*')[0];if(pattern.includes('*')&&specifier.startsWith(prefix)){file=values[0].replace('*',specifier.slice(prefix.length)).replace(/^\.\//,'');break;}}
|
|
385
|
+
return file&&!file.startsWith('../')?jsonResources.get(file):undefined;
|
|
386
|
+
}
|
|
387
|
+
function contextKey(module,name){
|
|
388
|
+
const ref=binding(module,name),call=ref&&unwrap(ref.node);
|
|
389
|
+
const primitive=call&&ts.isCallExpression(call)&&externalImport(ref.module,call.expression.getText(ref.module.source));
|
|
390
|
+
return primitive?.specifier==='react'&&primitive.name==='createContext'?ref.module.file+'#'+ref.name:null;
|
|
391
|
+
}
|
|
392
|
+
function sourceContextCall(node,module,ctx){
|
|
393
|
+
const imported=externalImport(module,node.expression.getText(module.source));
|
|
394
|
+
if(imported?.specifier==='react-i18next'&&imported.name==='useTranslation')return {t:translationHook(node,module,ctx)};
|
|
395
|
+
if(imported?.specifier==='react'&&imported.name==='useContext'&&ts.isIdentifier(node.arguments[0]))return ctx.__iamContexts?.get(contextKey(module,node.arguments[0].text))??UNKNOWN;
|
|
396
|
+
const ref=ts.isIdentifier(node.expression)&&binding(module,node.expression.text),fn=ref&&functionFrom(ref.node);
|
|
397
|
+
if(!fn?.body||!ts.isBlock(fn.body))return UNKNOWN;
|
|
398
|
+
const result=unwrap(returnExpression(fn));
|
|
399
|
+
if(!result||!ts.isIdentifier(result))return UNKNOWN;
|
|
400
|
+
for(const statement of fn.body.statements)if(ts.isVariableStatement(statement))for(const decl of statement.declarationList.declarations){
|
|
401
|
+
const call=unwrap(decl.initializer),primitive=call&&ts.isCallExpression(call)&&externalImport(ref.module,call.expression.getText(ref.module.source));
|
|
402
|
+
if(primitive?.specifier==='react'&&primitive.name==='useContext'&&ts.isIdentifier(decl.name)&&decl.name.text===result.text&&ts.isIdentifier(call.arguments[0]))return ctx.__iamContexts?.get(contextKey(ref.module,call.arguments[0].text))??UNKNOWN;
|
|
403
|
+
if(primitive?.specifier==='react-i18next'&&primitive.name==='useTranslation'&&ts.isObjectBindingPattern(decl.name)&&decl.name.elements.some(e=>(e.propertyName?.text||e.name.text)==='t'&&e.name.text===result.text)){
|
|
404
|
+
const catalogs=[...ref.module.imports.values()].filter(i=>i.specifier.endsWith('.json')).map(i=>jsonResource(ref.module.file,i.specifier)).filter(Boolean);
|
|
405
|
+
if(catalogs.length===1){translationResources.add(catalogs[0]);return {[TRANSLATION]:catalogs[0]};}
|
|
406
|
+
const locals={...ctx};fn.parameters.forEach((p,i)=>{if(ts.isIdentifier(p.name))locals[p.name.text]=evalExpression(node.arguments[i],module,ctx);});
|
|
407
|
+
return translationHook(call,ref.module,locals);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return UNKNOWN;
|
|
411
|
+
}
|
|
412
|
+
let i18nextPreview;
|
|
413
|
+
function translationHook(node,module,ctx){
|
|
414
|
+
i18nextPreview??=sourceI18next({ts,modules,binding,imported:externalImport,jsonResource})??false;
|
|
415
|
+
if(!i18nextPreview)return UNKNOWN;
|
|
416
|
+
const namespace=node.arguments[0]?evalExpression(node.arguments[0],module,ctx):i18nextPreview.namespace;
|
|
417
|
+
const options=node.arguments[1]?evalExpression(node.arguments[1],module,ctx):{};
|
|
418
|
+
if(typeof namespace!=='string'||isUnknown(options)||options?.keyPrefix!==undefined&&typeof options.keyPrefix!=='string')return UNKNOWN;
|
|
419
|
+
let catalog=i18nextPreview.resources[i18nextPreview.language]?.[namespace];
|
|
420
|
+
if(typeof options?.keyPrefix==='string')catalog=options.keyPrefix.split('.').reduce((v,k)=>v&&Object.hasOwn(v,k)?v[k]:undefined,catalog);
|
|
421
|
+
if(!catalog||typeof catalog!=='object')return UNKNOWN;
|
|
422
|
+
gap('UI_LOCALE_FALLBACK_PREVIEW',i18nextPreview.file,'Read-only preview uses the source fallback locale '+i18nextPreview.language+'; browser language, storage and runtime locale selection are not observed.');
|
|
423
|
+
translationResources.add(catalog);return {[TRANSLATION]:catalog,[TRANSLATION_FORMAT]:{prefix:i18nextPreview.prefix,suffix:i18nextPreview.suffix}};
|
|
424
|
+
}
|
|
425
|
+
function basePrimitive(module,tag,seen=new Set()){
|
|
426
|
+
if(seen.has(module.file+'#'+tag)||seen.size>8)return null;seen.add(module.file+'#'+tag);
|
|
427
|
+
const [name,part,extra]=tag.split('.'),ref=module.imports.get(name);
|
|
428
|
+
const imported=externalImport(module,tag);
|
|
429
|
+
if (ref?.specifier === '@mantine/core' && ref.name === 'Tabs' && !extra) return {family:'Tabs',part:part ?? 'Root',mantine:true};
|
|
430
|
+
if(imported?.specifier==='@headlessui/react'){
|
|
431
|
+
const part={Dialog:'Root',DialogPanel:'Popup',DialogBackdrop:'Backdrop',DialogTitle:'Title',Description:'Description'}[imported.name];
|
|
432
|
+
if(part)return {family:'Dialog',part,rootVisibility:part==='Root'};
|
|
433
|
+
}
|
|
434
|
+
if(imported?.specifier==='react-aria-components'){
|
|
435
|
+
const part={DialogTrigger:'Root',MenuTrigger:'Root',TooltipTrigger:'Root',ModalOverlay:'Backdrop',Modal:'Popup',Popover:'Popup',Dialog:'Popup',Menu:'Content',MenuItem:'Item',Button:'Trigger',Heading:'Title'}[imported.name];
|
|
436
|
+
if(part)return {family:'AriaDialog',part,...(imported.name==='Menu'?{role:'menu'}:['Modal','Popover'].includes(imported.name)?{role:null}:{})};
|
|
437
|
+
}
|
|
438
|
+
if(ref?.specifier==='cmdk'&&ref.name==='Command'&&part==='Dialog'&&!extra)return {family:'Dialog',part:'Root',rootVisibility:true};
|
|
439
|
+
const radixFamilies={'dialog':'Dialog','alert-dialog':'AlertDialog','popover':'Popover','dropdown-menu':'Menu','context-menu':'Menu','tooltip':'Tooltip','hover-card':'Popover','tabs':'Tabs','collapsible':'Collapsible'};
|
|
440
|
+
const radix=imported?.specifier?.match(/^@radix-ui\/react-([a-z-]+)$/)?.[1];
|
|
441
|
+
if(radixFamilies[radix]) {
|
|
442
|
+
const family=radixFamilies[radix],prefix=radix.split('-').map(v=>v[0].toUpperCase()+v.slice(1)).join('');
|
|
443
|
+
const exported=imported.name===prefix?'Root':imported.name.startsWith(prefix)?imported.name.slice(prefix.length):imported.name;
|
|
444
|
+
const mapped=family==='Tabs'?({Trigger:'Tab',Content:'Panel'}[exported]??exported):({Content:'Popup',Overlay:'Backdrop',Sub:'SubmenuRoot',SubTrigger:'SubmenuTrigger',SubContent:'Popup'}[exported]??exported);
|
|
445
|
+
return {family,part:mapped};
|
|
446
|
+
}
|
|
447
|
+
if(imported?.specifier==='react-transition-group'&&['Transition','CSSTransition'].includes(imported.name))return {family:'Transition',part:'Root'};
|
|
448
|
+
const families={'@base-ui/react/tabs':'Tabs','@base-ui/react/popover':'Popover','@base-ui/react/dialog':'Dialog','@base-ui/react/alert-dialog':'AlertDialog','@base-ui/react/menu':'Menu','@base-ui/react/tooltip':'Tooltip'};
|
|
449
|
+
if(!extra&&part&&ref&&families[ref.specifier]===ref.name)return {family:ref.name,part};
|
|
450
|
+
if(!part){const target=binding(module,tag),node=target&&unwrap(target.node);if(node&&ts.isPropertyAccessExpression(node))return basePrimitive(target.module,node.getText(target.module.source),seen);}
|
|
451
|
+
return null;
|
|
452
|
+
}
|
|
453
|
+
function tabsPrimitive(module,tag){const primitive=basePrimitive(module,tag);return primitive?.family==='Tabs'&&['Root','List','Tab','Panel','Indicator'].includes(primitive.part)?primitive.part:null;}
|
|
341
454
|
function binding(module, name, seen=new Set()) {
|
|
342
455
|
if(!name||seen.size>=16||seen.has(module.file+'#'+name))return null;seen.add(module.file+'#'+name);
|
|
343
456
|
if (module.bindings.has(name))
|
|
@@ -352,7 +465,7 @@ export async function analyze({
|
|
|
352
465
|
: target.exports.get(imported.name) || imported.name;
|
|
353
466
|
return binding(target,key,seen);
|
|
354
467
|
}
|
|
355
|
-
const componentDefs = new Map();
|
|
468
|
+
const componentDefs = new Map(),componentsByFunction=new WeakMap();
|
|
356
469
|
function registerComponent(module, symbol, node) {
|
|
357
470
|
const fn = functionFrom(node);
|
|
358
471
|
if (!fn) return null;
|
|
@@ -377,7 +490,7 @@ export async function analyze({
|
|
|
377
490
|
ts.forEachChild(n, number);
|
|
378
491
|
}
|
|
379
492
|
number(fn);
|
|
380
|
-
const def = { module, symbol, fn, key, ordinals };
|
|
493
|
+
const def = { module, symbol, fn, key, ordinals };componentsByFunction.set(fn,def);
|
|
381
494
|
let returns = 0;
|
|
382
495
|
function countReturns(item) {
|
|
383
496
|
if (
|
|
@@ -448,8 +561,9 @@ export async function analyze({
|
|
|
448
561
|
return target;
|
|
449
562
|
}
|
|
450
563
|
function sourceLocation(module, node, symbol) {
|
|
451
|
-
const
|
|
452
|
-
|
|
564
|
+
const generated = node.getStart(module.source);
|
|
565
|
+
const loc = module.originalSource.getLineAndCharacterOfPosition(
|
|
566
|
+
module.mdx ? module.mdx.position(generated) : generated,
|
|
453
567
|
);
|
|
454
568
|
return {
|
|
455
569
|
file: module.file,
|
|
@@ -459,6 +573,22 @@ export async function analyze({
|
|
|
459
573
|
fileHash: module.hash,
|
|
460
574
|
};
|
|
461
575
|
}
|
|
576
|
+
function sourceComponent(node,module){
|
|
577
|
+
const tabler=ts.isIdentifier(node)&&externalImport(module,node.text);
|
|
578
|
+
if(tabler?.specifier==='@tabler/icons-react'&&/^Icon[A-Z]/.test(tabler.name)){
|
|
579
|
+
const icon=lucideSource(tabler.name.slice(4));
|
|
580
|
+
if(icon){gap('ICON_SOURCE_APPROXIMATION',module.file,tabler.name+': inert Lucide counterpart used for source preview; original Tabler geometry is not claimed.');return icon;}
|
|
581
|
+
}
|
|
582
|
+
const imported=ts.isIdentifier(node)&&externalImport(module,node.text);if(imported?.specifier==='@radix-ui/react-slot'&&['Slot','Root'].includes(imported.name))return {[UI_PRIMITIVE]:imported};if(imported?.specifier==='lucide-react'){const icon=lucideSource(imported.name);if(icon)return icon;}
|
|
583
|
+
const ref=ts.isIdentifier(node)?binding(module,node.text):null;const def=componentsByFunction.get(node)??(ref&&componentDefs.get(ref.module.file+'#'+ref.name));
|
|
584
|
+
return def?{[COMPONENT]:def}:null;
|
|
585
|
+
}
|
|
586
|
+
function environment(node,module,_context){
|
|
587
|
+
const value=uiEnvironmentPrimitive({ts,node,module,binding,imported:externalImport,evaluate:evalExpression});
|
|
588
|
+
if(value?.kind==='empty-preference'){gap('UI_PREFERENCE_EMPTY_PREVIEW',module.file,'Source-declared SSR fallback for a UI preference; preview starts without cookies, never reads merchant browser state.');return [undefined];}
|
|
589
|
+
return value;
|
|
590
|
+
}
|
|
591
|
+
function query(node,module,context,allowAsyncSource=false){return staticQuery({ts,node,module,context,binding,unknown:UNKNOWN,redact:redactLiteral,sensitive:sensitiveName,reference:sourceComponent,isReference:v=>Boolean(v?.[COMPONENT]||v?.[LUCIDE_SOURCE]||v?.[UI_PRIMITIVE]),imported:externalImport,allowAsyncSource,onAsyncSource:({timerSkipped})=>gap('ASYNC_SOURCE_RETURN_PREVIEW',module.file,'Only source-declared literal data is projected; network, actions, loading/error outcomes and query callbacks are not executed.'+(timerSkipped?' A source-proven timer-only delay is skipped without invoking timers.':'')),project:(n,m,c)=>{if(ts.isIdentifier(n.expression)&&c[n.expression.text]?.[TRANSLATION]){const value=evalExpression(n,m,c);return isUnknown(value)?null:[value];}const v=environment(n,m,c);return Array.isArray(v)?v:null;}});}
|
|
462
592
|
function evalExpression(
|
|
463
593
|
node,
|
|
464
594
|
module,
|
|
@@ -469,6 +599,8 @@ export async function analyze({
|
|
|
469
599
|
node = unwrap(node);
|
|
470
600
|
if (!node || depth > 30) return UNKNOWN;
|
|
471
601
|
const ev = (n) => evalExpression(n, module, context, stack, depth + 1);
|
|
602
|
+
if (ts.isAwaitExpression(node)) return query(node, module, context, true);
|
|
603
|
+
const component=!(ts.isIdentifier(node)&&Object.hasOwn(context,node.text))&&sourceComponent(node,module);if(component)return component;
|
|
472
604
|
if (ts.isStringLiteralLike(node) || ts.isNumericLiteral(node))
|
|
473
605
|
return ts.isNumericLiteral(node) ? Number(node.text) : redactLiteral(node.text);
|
|
474
606
|
if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
|
|
@@ -476,7 +608,18 @@ export async function analyze({
|
|
|
476
608
|
if (node.kind === ts.SyntaxKind.NullKeyword) return null;
|
|
477
609
|
if (ts.isIdentifier(node)) {
|
|
478
610
|
if (Object.hasOwn(context, node.text)) return context[node.text];
|
|
611
|
+
const cssModule = cssModules.resolve(module, module.imports.get(node.text));
|
|
612
|
+
if (cssModule) return cssModule;
|
|
613
|
+
const variableKey=context.__iamVariableKeys?.[context.__iamSetters?.[node.text]];
|
|
614
|
+
if(variableKey)return {[UI_SETTER]:{variableKey,value:{op:'event',key:'value'},payload:{value:ui.variables.find(v=>v.variableKey===variableKey)?.valueType}}};
|
|
479
615
|
if (node.text === "undefined") return undefined;
|
|
616
|
+
const image=module.imports.get(node.text);
|
|
617
|
+
if(image?.name==='default'&&/^[^?#]+\.(?:png|jpe?g|webp|gif|svg)$/i.test(image.specifier)){
|
|
618
|
+
let file=image.specifier.startsWith('.')?path.posix.normalize(path.posix.join(path.posix.dirname(module.file),image.specifier)):null;
|
|
619
|
+
if(!file)for(const[pattern,targets]of Object.entries(alias)){const parts=pattern.split('*');if(parts.length===2&&image.specifier.startsWith(parts[0])&&image.specifier.endsWith(parts[1])&&Array.isArray(targets)&&targets.length===1&&typeof targets[0]==='string'){const value=image.specifier.slice(parts[0].length,image.specifier.length-parts[1].length||undefined);file=path.posix.normalize(targets[0].replace('*',value));break;}}
|
|
620
|
+
if(!file)return UNKNOWN;
|
|
621
|
+
if(!file.startsWith('../')&&!file.split('/').some(p=>p.startsWith('.'))&&!sensitiveName(file))return '/__iam_source__/'+file;
|
|
622
|
+
}
|
|
480
623
|
const token = module.file + "#" + node.text;
|
|
481
624
|
if (stack.has(token)) return UNKNOWN;
|
|
482
625
|
const ref = binding(module, node.text);
|
|
@@ -487,12 +630,16 @@ export async function analyze({
|
|
|
487
630
|
if (ts.isArrayLiteralExpression(node)) return node.elements.map(ev);
|
|
488
631
|
if (ts.isObjectLiteralExpression(node)) {
|
|
489
632
|
const obj = {};
|
|
633
|
+
const fields=new Map();
|
|
490
634
|
for (const prop of node.properties) {
|
|
491
635
|
if (ts.isPropertyAssignment(prop))
|
|
492
636
|
obj[prop.name.getText(module.source).replace(/^['"]|['"]$/g, "")] =
|
|
493
637
|
ev(prop.initializer);
|
|
494
|
-
else if (ts.isShorthandPropertyAssignment(prop))
|
|
638
|
+
else if (ts.isShorthandPropertyAssignment(prop)){
|
|
495
639
|
obj[prop.name.text] = ev(prop.name);
|
|
640
|
+
const variable=context.__iamVariableKeys?.[prop.name.text],inherited=context.__iamPropBindings?.get(prop.name.text);
|
|
641
|
+
if(variable||inherited)fields.set(prop.name.text,inherited||{op:'var',key:variable});
|
|
642
|
+
}
|
|
496
643
|
else if (ts.isSpreadAssignment(prop)) {
|
|
497
644
|
const v = ev(prop.expression);
|
|
498
645
|
if (isUnknown(v)) return UNKNOWN;
|
|
@@ -500,6 +647,7 @@ export async function analyze({
|
|
|
500
647
|
Object.assign(obj, v);
|
|
501
648
|
}
|
|
502
649
|
}
|
|
650
|
+
if(fields.size)objectBindings.set(obj,fields);
|
|
503
651
|
return obj;
|
|
504
652
|
}
|
|
505
653
|
if (
|
|
@@ -553,6 +701,12 @@ export async function analyze({
|
|
|
553
701
|
return isUnknown(a) ? UNKNOWN : (a ?? b);
|
|
554
702
|
if (isUnknown(a) || isUnknown(b)) return UNKNOWN;
|
|
555
703
|
if (op === ts.SyntaxKind.PlusToken) return a + b;
|
|
704
|
+
if(op === ts.SyntaxKind.MinusToken && typeof a==='number' && typeof b==='number')return a-b;
|
|
705
|
+
if(['number','string'].includes(typeof a)&&typeof a===typeof b){
|
|
706
|
+
if(op===ts.SyntaxKind.GreaterThanToken)return a>b;if(op===ts.SyntaxKind.GreaterThanEqualsToken)return a>=b;
|
|
707
|
+
if(op===ts.SyntaxKind.LessThanToken)return a<b;if(op===ts.SyntaxKind.LessThanEqualsToken)return a<=b;
|
|
708
|
+
}
|
|
709
|
+
if(op===ts.SyntaxKind.InKeyword&&typeof a==='string'&&b&&typeof b==='object'&&!['__proto__','constructor','prototype'].includes(a))return Object.hasOwn(b,a);
|
|
556
710
|
if (
|
|
557
711
|
[
|
|
558
712
|
ts.SyntaxKind.EqualsEqualsEqualsToken,
|
|
@@ -570,9 +724,27 @@ export async function analyze({
|
|
|
570
724
|
}
|
|
571
725
|
if (ts.isCallExpression(node)) {
|
|
572
726
|
const callee = node.expression.getText(module.source);
|
|
727
|
+
const projected=environment(node,module,context);if(Array.isArray(projected))return projected[0];
|
|
728
|
+
const renderCallback=ev(node.expression)?.iamRenderProp;
|
|
729
|
+
if(renderCallback)return {iamRenderInvocation:{callback:renderCallback,args:node.arguments.map(ev),caller:context}};
|
|
730
|
+
const primitive=externalImport(module,callee);
|
|
731
|
+
if(['react-router','react-router-dom'].includes(primitive?.specifier)&&primitive.name==='useLocation'&&typeof context.__iamRoutePath==='string')return {pathname:context.__iamRoutePath,search:UNKNOWN,hash:UNKNOWN};
|
|
732
|
+
const translation=ts.isIdentifier(node.expression)&&context[node.expression.text]?.[TRANSLATION];
|
|
733
|
+
if(translation){
|
|
734
|
+
const key=ev(node.arguments[0]);if(typeof key!=='string')return UNKNOWN;const value=key.split('.').reduce((value,key)=>value&&Object.hasOwn(Object(value),key)?value[key]:undefined,translation);if(typeof value!=='string')return UNKNOWN;
|
|
735
|
+
const format=context[node.expression.text][TRANSLATION_FORMAT]??{prefix:'{{',suffix:'}}'},options=node.arguments[1]?ev(node.arguments[1]):{};
|
|
736
|
+
const pattern=format.prefix==='{{'&&format.suffix==='}}'?/\{\{([A-Za-z_][A-Za-z0-9_]*)\}\}/g:format.prefix==='{'&&format.suffix==='}'?/\{([A-Za-z_][A-Za-z0-9_]*)\}/g:null;if(!pattern)return UNKNOWN;
|
|
737
|
+
let missing=false;const text=value.replace(pattern,(_,k)=>{const v=options&&Object.hasOwn(Object(options),k)?options[k]:UNKNOWN;if(typeof v==='string'||typeof v==='number'&&Number.isFinite(v))return String(v);missing=true;return '';});
|
|
738
|
+
return !missing&&!/[{}]/.test(text)&&text.length<=4000?redactLiteral(text):UNKNOWN;
|
|
739
|
+
}
|
|
740
|
+
if(primitive?.specifier==='react'&&primitive.name==='useMemo'&&node.arguments.length===2){
|
|
741
|
+
const fn=node.arguments[0];if(ts.isArrowFunction(fn)&&!fn.modifiers?.length&&!fn.parameters.length&&!ts.isBlock(fn.body))return ev(fn.body);
|
|
742
|
+
}
|
|
743
|
+
const contextValue=sourceContextCall(node,module,context);
|
|
744
|
+
if(!isUnknown(contextValue))return contextValue;
|
|
573
745
|
if (/(?:^|\.)useState$/.test(callee))
|
|
574
746
|
return {
|
|
575
|
-
stateInitial: node.arguments.length ? ev(node.arguments[0]) : null,
|
|
747
|
+
stateInitial: node.arguments.length ? (ts.isArrowFunction(node.arguments[0])&&node.arguments[0].parameters.length===0 ? query({kind:ts.SyntaxKind.CallExpression,expression:node.arguments[0],arguments:[]},module,context) : ev(node.arguments[0])) : null,
|
|
576
748
|
};
|
|
577
749
|
if (callee === "cn" || callee === "clsx") {
|
|
578
750
|
let valid=true;
|
|
@@ -607,6 +779,7 @@ export async function analyze({
|
|
|
607
779
|
([key, values]) =>
|
|
608
780
|
values[isUnknown((props || {})[key]) ? options.defaultVariants?.[key] : (props || {})[key] ?? options.defaultVariants?.[key]],
|
|
609
781
|
),
|
|
782
|
+
...(options?.compoundVariants??[]).flatMap(row=>Object.entries(row).filter(([key])=>!['class','className'].includes(key)).every(([key,value])=>{const actual=Object.hasOwn(props??{},key)&&props[key]!==undefined?props[key]:options.defaultVariants?.[key];return Array.isArray(value)?value.includes(actual):value===actual;})?[row.class,row.className]:[]),
|
|
610
783
|
props?.class,
|
|
611
784
|
props?.className,
|
|
612
785
|
]
|
|
@@ -617,7 +790,7 @@ export async function analyze({
|
|
|
617
790
|
const target = ev(node.expression.expression),
|
|
618
791
|
args = node.arguments.map(ev),
|
|
619
792
|
method = node.expression.name.text;
|
|
620
|
-
if (isUnknown(target) || args.some(isUnknown)) return
|
|
793
|
+
if (isUnknown(target) || args.some(isUnknown)) return query(node,module,context);
|
|
621
794
|
if (
|
|
622
795
|
typeof target === "string" &&
|
|
623
796
|
[
|
|
@@ -633,7 +806,7 @@ export async function analyze({
|
|
|
633
806
|
return target.filter((v) => !isUnknown(v)).join(args[0]);
|
|
634
807
|
}
|
|
635
808
|
}
|
|
636
|
-
if (ts.isCallExpression(node)) return
|
|
809
|
+
if (ts.isCallExpression(node)) return query(node,module,context);
|
|
637
810
|
return UNKNOWN;
|
|
638
811
|
}
|
|
639
812
|
// Preserve only definitely present class tokens when a template contains an
|
|
@@ -679,7 +852,7 @@ export async function analyze({
|
|
|
679
852
|
}
|
|
680
853
|
const forwarded = new WeakMap(), objectBindings = new WeakMap();
|
|
681
854
|
function componentContext(def, props) {
|
|
682
|
-
const ctx = { ...props, __iamSetters: {}, __iamStates: new Set(), __iamStateNodes: {}, __iamLocals: new Map(), __iamPropBindings: new Map(), __iamPropHandlers: new Map() };
|
|
855
|
+
const ctx = { ...props, __iamSetters: {}, __iamStates: new Set(), __iamStateNodes: {}, __iamLocals: new Map(), __iamQueryLocals: new Map(), __iamPropBindings: new Map(), __iamPropHandlers: new Map(), __iamDisclosures: [] };
|
|
683
856
|
const param = def.fn?.parameters?.[0];
|
|
684
857
|
if (param) {
|
|
685
858
|
if (ts.isIdentifier(param.name)) ctx[param.name.text] = props;
|
|
@@ -689,30 +862,57 @@ export async function analyze({
|
|
|
689
862
|
const omitted = new Set(param.name.elements.filter(item => !item.dotDotDotToken).map(item => item.propertyName?.getText() || item.name.getText()));
|
|
690
863
|
const rest = Object.fromEntries(Object.entries(props).filter(([key]) => !omitted.has(key)));
|
|
691
864
|
const parent = forwarded.get(props);
|
|
692
|
-
if (parent) forwarded.set(rest, { bindings: parent.bindings.filter(b => !omitted.has(b.property)), handlers: parent.handlers.filter(h => !omitted.has(h.key)) });
|
|
865
|
+
if (parent) forwarded.set(rest, { bindings: parent.bindings.filter(b => !omitted.has(b.property)), handlers: parent.handlers.filter(h => !omitted.has(h.key)), attributes:(parent.attributes??[]).filter(a=>!omitted.has(a.key)) });
|
|
693
866
|
ctx[el.name.text] = rest; continue;
|
|
694
867
|
}
|
|
695
868
|
const key = el.propertyName?.getText() || el.name.getText();
|
|
696
869
|
ctx[el.name.getText()] =
|
|
697
|
-
props[key]
|
|
698
|
-
(el.initializer
|
|
699
|
-
?
|
|
700
|
-
: UNKNOWN);
|
|
870
|
+
Object.hasOwn(props, key) && props[key] !== undefined ? props[key] :
|
|
871
|
+
(el.initializer ? evalExpression(el.initializer, def.module, ctx) :
|
|
872
|
+
props.__iamOpaqueProps ? UNKNOWN : undefined);
|
|
701
873
|
const inherited = forwarded.get(props);
|
|
702
874
|
const binding = inherited?.bindings.find(b => b.property === key), handler = inherited?.handlers.find(h => h.key === key);
|
|
703
875
|
if (binding) ctx.__iamPropBindings.set(el.name.getText(), binding.value);
|
|
704
876
|
if (handler) ctx.__iamPropHandlers.set(el.name.getText(), handler);
|
|
705
877
|
}
|
|
706
878
|
}
|
|
879
|
+
if (def.fn?.body&&ts.isBlock(def.fn.body)) for (const statement of def.fn.body.statements) if (ts.isFunctionDeclaration(statement) && statement.name) ctx.__iamLocals.set(statement.name.text, statement);
|
|
707
880
|
if (def.fn?.body&&ts.isBlock(def.fn.body))
|
|
708
881
|
for (const s of def.fn.body.statements)
|
|
709
882
|
if (ts.isVariableStatement(s))
|
|
710
883
|
for (const d of s.declarationList.declarations) {
|
|
711
884
|
if (ts.isIdentifier(d.name)) {
|
|
712
|
-
|
|
885
|
+
const projected=environment(d.initializer,def.module,ctx);
|
|
886
|
+
if(projected?.kind==='viewport'){
|
|
887
|
+
const width=Number(projected.query.match(/\d+/)[0]);ctx[d.name.text]=projected.query.includes('max-width')?1280<=width:1280>=width;ctx.__iamStates.add(d.name.text);ctx.__iamStateNodes[d.name.text]=d;ctx.__iamViewportQueries??={};ctx.__iamViewportQueries[d.name.text]=projected.query;
|
|
888
|
+
gap('VIEWPORT_SOURCE_PROJECTION',def.module.file,'Source matchMedia query projected against the preview viewport; subscription effects are not executed.');
|
|
889
|
+
}else ctx[d.name.text] = sensitiveName(d.name.text) ? UNKNOWN : evalExpression(d.initializer, def.module, ctx);
|
|
713
890
|
ctx.__iamLocals.set(d.name.text, d.initializer);
|
|
891
|
+
} else if(ts.isObjectBindingPattern(d.name)){
|
|
892
|
+
const value=evalExpression(d.initializer,def.module,ctx);
|
|
893
|
+
for(const field of d.name.elements)if(ts.isIdentifier(field.name)&&!field.dotDotDotToken){
|
|
894
|
+
const key=field.propertyName?.text||field.name.text;
|
|
895
|
+
ctx[field.name.text]=!isUnknown(value)&&value&&typeof value==='object'&&Object.hasOwn(value,key)?value[key]:field.initializer?evalExpression(field.initializer,def.module,ctx):UNKNOWN;
|
|
896
|
+
ctx.__iamQueryLocals.set(field.name.text,d.initializer);
|
|
897
|
+
const inherited=value&&typeof value==='object'&&objectBindings.get(value)?.get(key);
|
|
898
|
+
if(inherited)ctx.__iamPropBindings.set(field.name.text,inherited);
|
|
899
|
+
const setter=value?.[key]?.[UI_SETTER];
|
|
900
|
+
if(setter)ctx.__iamPropHandlers.set(field.name.text,setter);
|
|
901
|
+
}
|
|
714
902
|
} else if (ts.isArrayBindingPattern(d.name)) {
|
|
715
903
|
const call = unwrap(d.initializer), hook = call && ts.isCallExpression(call) && ts.isIdentifier(call.expression) && def.module.imports.get(call.expression.text);
|
|
904
|
+
if (hook?.specifier === '@mantine/hooks' && hook.name === 'useDisclosure') {
|
|
905
|
+
const state = d.name.elements[0], handlers = d.name.elements[1]?.name;
|
|
906
|
+
if (call.arguments.length > 1 || !state || !ts.isIdentifier(state.name) || state.dotDotDotToken) {gap('DISCLOSURE_HOOK_UNOBSERVED',def.module.file,'Only a source boolean useDisclosure without lifecycle callbacks is simulated.');continue;}
|
|
907
|
+
const label = state.name.text;
|
|
908
|
+
ctx[label] = call.arguments.length ? evalExpression(call.arguments[0], def.module, ctx) : false;
|
|
909
|
+
ctx.__iamStates.add(label); ctx.__iamStateNodes[label] = d;
|
|
910
|
+
if (handlers && ts.isObjectBindingPattern(handlers)) {
|
|
911
|
+
for (const h of handlers.elements) if (!h.dotDotDotToken && ts.isIdentifier(h.name) && ['open','close','toggle'].includes(h.propertyName?.text ?? h.name.text)) ctx.__iamDisclosures.push({label,name:h.name.text,op:h.propertyName?.text ?? h.name.text});
|
|
912
|
+
} else if (handlers && ts.isIdentifier(handlers)) for (const op of ['open','close','toggle']) ctx.__iamDisclosures.push({label,name:handlers.text+'.'+op,op});
|
|
913
|
+
gap('DISCLOSURE_SOURCE_PREVIEW',def.module.file,'Local boolean disclosure state only; hook callbacks and browser effects are not executed.');
|
|
914
|
+
continue;
|
|
915
|
+
}
|
|
716
916
|
if (hook?.specifier === "react" && hook.name === "useActionState") {
|
|
717
917
|
const key = d.name.elements[0]?.name?.text, pending = d.name.elements[2]?.name?.text, initial = evalExpression(call.arguments[1], def.module, ctx);
|
|
718
918
|
if (key && !sensitiveName(key) && initial && !isUnknown(initial) && typeof initial === "object" && !Array.isArray(initial)) {
|
|
@@ -740,7 +940,7 @@ export async function analyze({
|
|
|
740
940
|
continue;
|
|
741
941
|
}
|
|
742
942
|
const key = stateBinding.name.text;
|
|
743
|
-
ctx[key] = sensitiveName(key) ? UNKNOWN : value.stateInitial;
|
|
943
|
+
ctx[key] = sensitiveName(key) && typeof value.stateInitial !== 'boolean' ? UNKNOWN : value.stateInitial;
|
|
744
944
|
ctx.__iamStates.add(key);
|
|
745
945
|
ctx.__iamStateNodes[key] = d;
|
|
746
946
|
if (d.name.elements[1]?.name && !d.name.elements[1].dotDotDotToken && ts.isIdentifier(d.name.elements[1].name))
|
|
@@ -801,6 +1001,7 @@ export async function analyze({
|
|
|
801
1001
|
)
|
|
802
1002
|
throw new Error("Invalid source basePath");
|
|
803
1003
|
const selection = await sourceEntry({ root, modules, resolveModule, ts, entry });
|
|
1004
|
+
const flowbiteTheme=rootFlowbiteTheme({ts,modules:selection.discoveryModules,imported:externalImport,evaluate:evalExpression});
|
|
804
1005
|
if (selection.htmlSource) inventory.push(selection.htmlSource);
|
|
805
1006
|
inventory.sort((a, b) => a.file < b.file ? -1 : a.file > b.file ? 1 : 0);
|
|
806
1007
|
const compositions = frameworkRoutes({ modules, ...selection, resolveModule, ts, sourceLocation, navigationPaths, gap, resolution, inlineComponent });
|
|
@@ -858,19 +1059,19 @@ export async function analyze({
|
|
|
858
1059
|
};
|
|
859
1060
|
const record = new Map(),
|
|
860
1061
|
used = new Set(),
|
|
861
|
-
branches = [];
|
|
862
|
-
let renderedCount = 0;
|
|
1062
|
+
branches = [],attributeRequests=[];
|
|
863
1063
|
function renderComponent(def, props, instance, depth) {
|
|
864
1064
|
if (depth === 0 && composition.entryMode === "soft") instance = composition.compositionKey + ":" + instance;
|
|
865
1065
|
// Component recursion and JSX nesting are separate budgets. Counting DOM
|
|
866
1066
|
// wrappers as component calls silently dropped ordinary nested navigation.
|
|
867
|
-
if (used.size >=
|
|
1067
|
+
if (used.size >= 24 || used.has(def.key + ":" + instance)) {
|
|
868
1068
|
gap("COMPONENT_DEPTH_LIMIT", def.module.file, def.symbol + ": active component call budget exceeded");
|
|
869
1069
|
return "";
|
|
870
1070
|
}
|
|
871
1071
|
used.add(def.key + ":" + instance);
|
|
872
1072
|
if (!page.componentKeys.includes(def.key))
|
|
873
1073
|
page.componentKeys.push(def.key);
|
|
1074
|
+
props.__iamRoutePath=/[[\]]/.test(route)?UNKNOWN:route;
|
|
874
1075
|
const ctx = componentContext(def, props);
|
|
875
1076
|
ctx.__iamOutlet = props.__iamOutlet;
|
|
876
1077
|
ctx.__iamScope = identity("state-scope", {
|
|
@@ -895,11 +1096,21 @@ export async function analyze({
|
|
|
895
1096
|
}
|
|
896
1097
|
if (!uiVariableKeys.has(variableKey)) {
|
|
897
1098
|
uiVariableKeys.add(variableKey);
|
|
898
|
-
ui.variables.push({ variableKey, routeKey: pageKey, scopeKey: ctx.__iamScope, label, valueType: typeof initialValue, initialValue, source: sourceLocation(def.module, ctx.__iamStateNodes[label], def.symbol) });
|
|
1099
|
+
ui.variables.push({ variableKey, routeKey: pageKey, scopeKey: ctx.__iamScope, label:ctx.__iamViewportQueries?.[label]?'@media '+ctx.__iamViewportQueries[label]:label, valueType: typeof initialValue, initialValue, source: sourceLocation(def.module, ctx.__iamStateNodes[label], def.symbol) });
|
|
899
1100
|
}
|
|
900
1101
|
}
|
|
1102
|
+
for (const disclosure of ctx.__iamDisclosures) {
|
|
1103
|
+
const variableKey=ctx.__iamVariableKeys[disclosure.label];if(!variableKey)continue;
|
|
1104
|
+
ctx.__iamPropHandlers.set(disclosure.name,{variableKey,value:disclosure.op==='toggle'?{op:'not',arg:{op:'var',key:variableKey}}:{op:'const',value:disclosure.op==='open'},payload:{}});
|
|
1105
|
+
}
|
|
1106
|
+
// Rebind pure local records after scalar state identities exist. This preserves
|
|
1107
|
+
// useMemo/context field relationships without invoking a hook or callback.
|
|
1108
|
+
for(const [name,expression]of ctx.__iamLocals){
|
|
1109
|
+
if(ctx.__iamStates.has(name)||sensitiveName(name))continue;
|
|
1110
|
+
const value=evalExpression(expression,def.module,ctx);if(!isUnknown(value))ctx[name]=value;
|
|
1111
|
+
}
|
|
901
1112
|
const result = renderExpr(
|
|
902
|
-
|
|
1113
|
+
uiReturnExpression(def.fn),
|
|
903
1114
|
def,
|
|
904
1115
|
ctx,
|
|
905
1116
|
instance,
|
|
@@ -934,6 +1145,7 @@ export async function analyze({
|
|
|
934
1145
|
}
|
|
935
1146
|
if (ts.isIdentifier(node)) {
|
|
936
1147
|
if (Object.hasOwn(aliases, node.text)) return aliases[node.text];
|
|
1148
|
+
if(eventParam&&node.text===eventParam)return {op:'event',key:'value'};
|
|
937
1149
|
if (ctx.__iamVariableKeys[node.text]) return { op: "var", key: ctx.__iamVariableKeys[node.text] };
|
|
938
1150
|
if (ctx.__iamPropBindings.has(node.text)) return ctx.__iamPropBindings.get(node.text);
|
|
939
1151
|
if (ctx.__iamLocals.has(node.text)) return compile(ctx.__iamLocals.get(node.text));
|
|
@@ -976,44 +1188,379 @@ export async function analyze({
|
|
|
976
1188
|
if (["add", "subtract"].includes(expression.op)) return left === "number" && right === "number" ? "number" : undefined;
|
|
977
1189
|
return undefined;
|
|
978
1190
|
}
|
|
979
|
-
function uiHandler(initializer, owner, ctx) {
|
|
1191
|
+
function uiHandler(initializer, owner, ctx, property = '') {
|
|
980
1192
|
let fn = initializer && ts.isJsxExpression(initializer) ? unwrap(initializer.expression) : null;
|
|
981
|
-
if
|
|
982
|
-
|
|
983
|
-
if
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
}
|
|
987
|
-
if
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
const
|
|
996
|
-
if
|
|
997
|
-
const
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1193
|
+
if(fn&&ts.isPropertyAccessExpression(fn)&&ctx.__iamPropHandlers.has(fn.getText(owner.module.source)))return ctx.__iamPropHandlers.get(fn.getText(owner.module.source));
|
|
1194
|
+
const eventValue = (variableKey) => ({ variableKey, value: {op:'event',key:'value'}, payload:{value:ui.variables.find(v=>v.variableKey===variableKey)?.valueType} });
|
|
1195
|
+
if(fn&&ts.isIdentifier(fn)&&ctx.__iamPropHandlers.has(fn.text))return ctx.__iamPropHandlers.get(fn.text);
|
|
1196
|
+
if(fn&&ts.isIdentifier(fn)&&ctx.__iamSetters[fn.text]&&['onValueChange','onOpenChange'].includes(property)) {
|
|
1197
|
+
const key=ctx.__iamVariableKeys[ctx.__iamSetters[fn.text]];return key?eventValue(key):null;
|
|
1198
|
+
}
|
|
1199
|
+
if(fn&&ts.isIdentifier(fn)&&ctx.__iamLocals.has(fn.text))fn=unwrap(ctx.__iamLocals.get(fn.text));
|
|
1200
|
+
if(fn&&ts.isPropertyAccessExpression(fn)&&ts.isIdentifier(fn.expression)) {
|
|
1201
|
+
const props=ctx[fn.expression.text],handler=props&&typeof props==='object'&&forwarded.get(props)?.handlers.find(h=>h.key===fn.name.text);
|
|
1202
|
+
if(handler)return handler;
|
|
1203
|
+
}
|
|
1204
|
+
if(!fn||![ts.isArrowFunction,ts.isFunctionExpression,ts.isFunctionDeclaration].some(is=>is(fn))||fn.modifiers?.some(m=>m.kind===ts.SyntaxKind.AsyncKeyword))return null;
|
|
1205
|
+
let statements=ts.isBlock(fn.body)?[...fn.body.statements]:[fn.body];
|
|
1206
|
+
if(statements.length>16)return null;
|
|
1207
|
+
const writes=[],payload={};
|
|
1208
|
+
const inputs=e=>{if(e.op==='event')payload[e.key]=e.key==='checked'?'boolean':'string';for(const c of [e.arg,e.left,e.right,...(e.args||[]),...(e.parts||[]).map(p=>p.when)])if(c)inputs(c);};
|
|
1209
|
+
const replaceEvent=(value,arg)=>value.op==='event'&&value.key==='value'?arg:Object.fromEntries(Object.entries(value).map(([k,v])=>[k,Array.isArray(v)?v.map(item=>item&&typeof item==='object'?replaceEvent(item,arg):item):v&&typeof v==='object'?replaceEvent(v,arg):v]));
|
|
1210
|
+
for(let body of statements) {
|
|
1211
|
+
if(ts.isExpressionStatement(body)||ts.isReturnStatement(body))body=body.expression;
|
|
1212
|
+
body=unwrap(body);
|
|
1213
|
+
if(body&&ts.isCallExpression(body)&&!body.arguments.length&&ts.isPropertyAccessExpression(body.expression)&&ts.isIdentifier(body.expression.expression)&&body.expression.expression.text===fn.parameters[0]?.name?.text&&['preventDefault','stopPropagation'].includes(body.expression.name.text))continue;
|
|
1214
|
+
if(body&&ts.isBinaryExpression(body)&&body.operatorToken.kind===ts.SyntaxKind.AmpersandAmpersandToken) {
|
|
1215
|
+
const guard=unwrap(body.left);
|
|
1216
|
+
if(!ts.isBinaryExpression(guard)||guard.operatorToken.kind!==ts.SyntaxKind.EqualsEqualsEqualsToken||!ts.isTypeOfExpression(guard.left)||!ts.isIdentifier(guard.left.expression)||guard.left.expression.text!==fn.parameters[0]?.name?.text||!ts.isStringLiteral(guard.right)||!['string','number','boolean'].includes(guard.right.text))return null;
|
|
1217
|
+
body=unwrap(body.right);
|
|
1218
|
+
}
|
|
1219
|
+
if(!body||!ts.isCallExpression(body)||!ts.isIdentifier(body.expression)||body.arguments.length!==1)return null;
|
|
1220
|
+
const inherited=ctx.__iamPropHandlers.get(body.expression.text);
|
|
1221
|
+
if(inherited) {
|
|
1222
|
+
const arg=uiExpression(body.arguments[0],owner,ctx,fn.parameters[0]?.name?.text||null);
|
|
1223
|
+
if(!arg)return null;
|
|
1224
|
+
const forwardedWrites=(inherited.writes||[{variableKey:inherited.variableKey,value:inherited.value}]).map(write=>({...write,value:replaceEvent(write.value,arg)}));
|
|
1225
|
+
for(const write of forwardedWrites)inputs(write.value);
|
|
1226
|
+
if(['onValueChange','onOpenChange'].includes(property))for(const [key,type]of Object.entries(inherited.payload))if(Object.hasOwn(payload,key))payload[key]=type;
|
|
1227
|
+
if(forwardedWrites.some(write=>uiExpressionType(write.value,payload)!==ui.variables.find(v=>v.variableKey===write.variableKey)?.valueType))return null;
|
|
1228
|
+
writes.push(...forwardedWrites);continue;
|
|
1229
|
+
}
|
|
1230
|
+
const label=ctx.__iamSetters[body.expression.text],variableKey=ctx.__iamVariableKeys[label];
|
|
1231
|
+
if(!variableKey||writes.some(w=>w.variableKey===variableKey))return null;
|
|
1232
|
+
const input=unwrap(body.arguments[0]),functional=input&&ts.isArrowFunction(input)&&!ts.isBlock(input.body)&&input.parameters.length===1&&ts.isIdentifier(input.parameters[0].name)?input:null;
|
|
1233
|
+
const aliases=functional?{[functional.parameters[0].name.text]:{op:'var',key:variableKey}}:{};
|
|
1234
|
+
const value=uiExpression(functional?functional.body:input,owner,ctx,fn.parameters[0]?.name?.text||null,aliases);
|
|
1235
|
+
if(!value)return null;
|
|
1236
|
+
inputs(value);const expected=ui.variables.find(v=>v.variableKey===variableKey)?.valueType;
|
|
1237
|
+
if(value.op==='event'&&value.key==='value'&&['onValueChange','onOpenChange'].includes(property))payload.value=expected;
|
|
1238
|
+
if(uiExpressionType(value,payload)!==expected)return null;
|
|
1239
|
+
writes.push({variableKey,value});
|
|
1240
|
+
}
|
|
1241
|
+
if(!writes.length||new Set(writes.map(w=>w.variableKey)).size!==writes.length)return null;
|
|
1242
|
+
return {...writes[0],writes,payload};
|
|
1243
|
+
}
|
|
1244
|
+
function queryBacked(node, owner, ctx) {
|
|
1245
|
+
return sourceQueryEvidence({
|
|
1246
|
+
ts,
|
|
1247
|
+
node,
|
|
1248
|
+
module: owner.module,
|
|
1249
|
+
locals: new Map([...ctx.__iamLocals,...ctx.__iamQueryLocals]),
|
|
1250
|
+
binding,
|
|
1251
|
+
functionFrom,
|
|
1252
|
+
imported: externalImport,
|
|
1253
|
+
});
|
|
1002
1254
|
}
|
|
1003
|
-
function
|
|
1255
|
+
function remoteResultCondition(node, owner, ctx) {
|
|
1256
|
+
if (queryBacked(node, owner, ctx)) return 'remote';
|
|
1257
|
+
let shape = false;
|
|
1258
|
+
const root =
|
|
1259
|
+
ts.isIdentifier(node) && ctx.__iamLocals.has(node.text)
|
|
1260
|
+
? ctx.__iamLocals.get(node.text)
|
|
1261
|
+
: node;
|
|
1262
|
+
const inspect = (n) => {
|
|
1263
|
+
if (!n || shape) return;
|
|
1264
|
+
if (
|
|
1265
|
+
ts.isPropertyAccessExpression(n) &&
|
|
1266
|
+
[
|
|
1267
|
+
"isLoading",
|
|
1268
|
+
"isPending",
|
|
1269
|
+
"isError",
|
|
1270
|
+
"error",
|
|
1271
|
+
"data",
|
|
1272
|
+
"length",
|
|
1273
|
+
"size",
|
|
1274
|
+
].includes(n.name.text)
|
|
1275
|
+
)
|
|
1276
|
+
shape = true;
|
|
1277
|
+
ts.forEachChild(n, inspect);
|
|
1278
|
+
};
|
|
1279
|
+
inspect(root);
|
|
1280
|
+
return shape
|
|
1281
|
+
? queryBacked(node, owner, ctx)
|
|
1282
|
+
? "remote"
|
|
1283
|
+
: "unobserved-data"
|
|
1284
|
+
: null;
|
|
1285
|
+
}
|
|
1286
|
+
function updatedCollectionContext(ctx, label, value, owner) {
|
|
1287
|
+
const next = { ...ctx, [label]: value, __iamCollectionTemplate: true };
|
|
1288
|
+
for (const [key, init] of ctx.__iamLocals)
|
|
1289
|
+
if (!ctx.__iamStates.has(key))
|
|
1290
|
+
next[key] = evalExpression(init, owner.module, next);
|
|
1291
|
+
return next;
|
|
1292
|
+
}
|
|
1293
|
+
function finiteCollection(node, owner, ctx, instance, _depth) {
|
|
1294
|
+
if (ctx.__iamCollectionTemplate || !ts.isConditionalExpression(node))
|
|
1295
|
+
return null;
|
|
1296
|
+
const maps = [];
|
|
1297
|
+
const scan = (n) => {
|
|
1298
|
+
if (
|
|
1299
|
+
ts.isCallExpression(n) &&
|
|
1300
|
+
ts.isPropertyAccessExpression(n.expression) &&
|
|
1301
|
+
n.expression.name.text === "map"
|
|
1302
|
+
)
|
|
1303
|
+
maps.push(n);
|
|
1304
|
+
if (!ts.isFunctionLike(n)) ts.forEachChild(n, scan);
|
|
1305
|
+
};
|
|
1306
|
+
scan(node);
|
|
1307
|
+
if (maps.length !== 1) return null;
|
|
1308
|
+
const receiver = maps[0].expression.expression,
|
|
1309
|
+
reads = collectionStateReads(ts, receiver, ctx),
|
|
1310
|
+
tabs = presentation.tabs.filter(
|
|
1311
|
+
(t) =>
|
|
1312
|
+
reads.includes(t.variableKey) &&
|
|
1313
|
+
Object.values(ctx.__iamVariableKeys).includes(t.variableKey),
|
|
1314
|
+
);
|
|
1315
|
+
if (tabs.length !== 1 || tabs[0].items.length < 2 || tabs[0].items.length > 8)
|
|
1316
|
+
return null;
|
|
1317
|
+
const tab = tabs[0],
|
|
1318
|
+
label = Object.keys(ctx.__iamVariableKeys).find(
|
|
1319
|
+
(k) => ctx.__iamVariableKeys[k] === tab.variableKey,
|
|
1320
|
+
);
|
|
1321
|
+
const candidates = tab.items.map((item) => {
|
|
1322
|
+
const next = updatedCollectionContext(ctx, label, item.value, owner);
|
|
1323
|
+
return {
|
|
1324
|
+
value: item.value,
|
|
1325
|
+
ctx: next,
|
|
1326
|
+
items: evalExpression(receiver, owner.module, next),
|
|
1327
|
+
};
|
|
1328
|
+
});
|
|
1329
|
+
if (
|
|
1330
|
+
candidates.some((c) => !Array.isArray(c.items) || c.items.length > 128) ||
|
|
1331
|
+
new Set(candidates.map((c) => JSON.stringify(c.items))).size < 2
|
|
1332
|
+
)
|
|
1333
|
+
return null;
|
|
1334
|
+
const source = sourceLocation(owner.module, node, owner.symbol),
|
|
1335
|
+
captured = {};
|
|
1336
|
+
const capture = (n) => {
|
|
1337
|
+
if (
|
|
1338
|
+
ts.isIdentifier(n) &&
|
|
1339
|
+
Object.hasOwn(ctx, n.text) &&
|
|
1340
|
+
!n.text.startsWith("__iam")
|
|
1341
|
+
) {
|
|
1342
|
+
const value = ctx[n.text];
|
|
1343
|
+
captured[n.text] = isUnknown(value)
|
|
1344
|
+
? { kind: "unknown" }
|
|
1345
|
+
: value === undefined
|
|
1346
|
+
? { kind: "undefined" }
|
|
1347
|
+
: value?.[COMPONENT]
|
|
1348
|
+
? { kind: "component", key: value[COMPONENT].key }
|
|
1349
|
+
: value?.[LUCIDE_SOURCE]
|
|
1350
|
+
? { kind: "lucide", name: value[LUCIDE_SOURCE] }
|
|
1351
|
+
: { kind: "literal", value };
|
|
1352
|
+
}
|
|
1353
|
+
ts.forEachChild(n, capture);
|
|
1354
|
+
};
|
|
1355
|
+
capture(node);
|
|
1356
|
+
let cacheKey;
|
|
1357
|
+
try {
|
|
1358
|
+
cacheKey = identity("collection-template", {
|
|
1359
|
+
source,
|
|
1360
|
+
captured,
|
|
1361
|
+
cases: candidates.map((c) => ({ value: c.value, items: c.items })),
|
|
1362
|
+
});
|
|
1363
|
+
} catch {
|
|
1364
|
+
return null;
|
|
1365
|
+
}
|
|
1366
|
+
let cases = collectionTemplates.get(cacheKey);
|
|
1367
|
+
if (!cases) {
|
|
1368
|
+
cases = [];
|
|
1369
|
+
for (const candidate of candidates) {
|
|
1370
|
+
const sizes = {
|
|
1371
|
+
bindings: ui.bindings.length,
|
|
1372
|
+
transitions: ui.transitions.length,
|
|
1373
|
+
variables: ui.variables.length,
|
|
1374
|
+
conditions: presentation.conditions.length,
|
|
1375
|
+
tabs: presentation.tabs.length,
|
|
1376
|
+
regions: presentation.regions.length,
|
|
1377
|
+
};
|
|
1378
|
+
const html = renderExpr(
|
|
1379
|
+
node,
|
|
1380
|
+
owner,
|
|
1381
|
+
candidate.ctx,
|
|
1382
|
+
cacheKey + "/" + candidate.value,
|
|
1383
|
+
// A shared source fragment owns its bounded subtree budget. App wrapper
|
|
1384
|
+
// depth must not erase leaf text from an otherwise identical collection.
|
|
1385
|
+
0,
|
|
1386
|
+
),
|
|
1387
|
+
fragment = sourceCollectionFragment(
|
|
1388
|
+
html,
|
|
1389
|
+
source,
|
|
1390
|
+
candidate.items.length,
|
|
1391
|
+
record,
|
|
1392
|
+
);
|
|
1393
|
+
for (const variable of ui.variables.slice(sizes.variables))
|
|
1394
|
+
uiVariableKeys.delete(variable.variableKey);
|
|
1395
|
+
ui.bindings.length = sizes.bindings;
|
|
1396
|
+
ui.transitions.length = sizes.transitions;
|
|
1397
|
+
ui.variables.length = sizes.variables;
|
|
1398
|
+
for (const key of ["conditions", "tabs", "regions"])
|
|
1399
|
+
presentation[key].length = sizes[key];
|
|
1400
|
+
if (
|
|
1401
|
+
!presentation.fragments.some(
|
|
1402
|
+
(f) => f.fragmentKey === fragment.fragmentKey,
|
|
1403
|
+
)
|
|
1404
|
+
)
|
|
1405
|
+
presentation.fragments.push(fragment);
|
|
1406
|
+
cases.push({ value: candidate.value, fragmentKey: fragment.fragmentKey });
|
|
1407
|
+
}
|
|
1408
|
+
collectionTemplates.set(cacheKey, cases);
|
|
1409
|
+
}
|
|
1410
|
+
const collectionKey = identity("ui-collection", {
|
|
1411
|
+
page: pageKey,
|
|
1412
|
+
instance,
|
|
1413
|
+
source,
|
|
1414
|
+
}),
|
|
1415
|
+
nodeId = identity("node", { page: pageKey, collectionKey }),
|
|
1416
|
+
elementKey = identity("element", {
|
|
1417
|
+
component: owner.key,
|
|
1418
|
+
sourceCollection: node.pos,
|
|
1419
|
+
});
|
|
1420
|
+
elements.set(elementKey, {
|
|
1421
|
+
elementKey,
|
|
1422
|
+
componentKey: owner.key,
|
|
1423
|
+
tag: "div",
|
|
1424
|
+
source,
|
|
1425
|
+
identityStrategy: "provisional_ast_ordinal",
|
|
1426
|
+
editableProperties: [],
|
|
1427
|
+
binding: "read_only",
|
|
1428
|
+
});
|
|
1429
|
+
record.set(nodeId, { elementKey, source, synthetic: true });
|
|
1430
|
+
const requires = reads
|
|
1431
|
+
.filter((key) => key !== tab.variableKey)
|
|
1432
|
+
.map((variableKey) => ({
|
|
1433
|
+
variableKey,
|
|
1434
|
+
value: ui.variables.find((v) => v.variableKey === variableKey)
|
|
1435
|
+
?.initialValue,
|
|
1436
|
+
}));
|
|
1437
|
+
presentation.collections.push({
|
|
1438
|
+
collectionKey,
|
|
1439
|
+
routeKey: pageKey,
|
|
1440
|
+
renderKey: "pending",
|
|
1441
|
+
nodeId,
|
|
1442
|
+
variableKey: tab.variableKey,
|
|
1443
|
+
requires,
|
|
1444
|
+
cases,
|
|
1445
|
+
source,
|
|
1446
|
+
});
|
|
1447
|
+
return (
|
|
1448
|
+
'<div data-iam-node="' +
|
|
1449
|
+
nodeId +
|
|
1450
|
+
'" data-iam-collection="' +
|
|
1451
|
+
collectionKey +
|
|
1452
|
+
'" data-iam-unobserved="true" hidden=""></div>'
|
|
1453
|
+
);
|
|
1454
|
+
}
|
|
1455
|
+
const iconGlyphs=new Set();
|
|
1456
|
+
function renderLucide(icon, node, owner, props, instance, shared=true) {
|
|
1457
|
+
const geometry = lucideGeometry(icon),
|
|
1458
|
+
source = sourceLocation(owner.module, node, owner.symbol);
|
|
1459
|
+
if (!geometry) return "";
|
|
1460
|
+
const element = (tag, attributes, children, part) => {
|
|
1461
|
+
const elementKey = identity("element", {
|
|
1462
|
+
component: owner.key,
|
|
1463
|
+
lucide: node.pos,
|
|
1464
|
+
part,
|
|
1465
|
+
tag,
|
|
1466
|
+
}),
|
|
1467
|
+
nodeId = identity("node", {
|
|
1468
|
+
page: pageKey,
|
|
1469
|
+
elementKey,
|
|
1470
|
+
instance,
|
|
1471
|
+
icon: icon[LUCIDE_SOURCE],
|
|
1472
|
+
});
|
|
1473
|
+
elements.set(elementKey, {
|
|
1474
|
+
elementKey,
|
|
1475
|
+
componentKey: owner.key,
|
|
1476
|
+
tag,
|
|
1477
|
+
source,
|
|
1478
|
+
identityStrategy: "provisional_ast_ordinal",
|
|
1479
|
+
editableProperties: [],
|
|
1480
|
+
binding: "read_only",
|
|
1481
|
+
});
|
|
1482
|
+
record.set(nodeId, { elementKey, source, synthetic: true });
|
|
1483
|
+
return (
|
|
1484
|
+
"<" +
|
|
1485
|
+
tag +
|
|
1486
|
+
' data-iam-node="' +
|
|
1487
|
+
nodeId +
|
|
1488
|
+
'" ' +
|
|
1489
|
+
Object.entries(attributes)
|
|
1490
|
+
.map(([key, value]) => key + '="' + escape(value) + '"')
|
|
1491
|
+
.join(" ") +
|
|
1492
|
+
">" +
|
|
1493
|
+
children +
|
|
1494
|
+
"</" +
|
|
1495
|
+
tag +
|
|
1496
|
+
">"
|
|
1497
|
+
);
|
|
1498
|
+
};
|
|
1499
|
+
const className = typeof props.className === "string" ? props.className : "";
|
|
1500
|
+
className
|
|
1501
|
+
.split(/\s+/)
|
|
1502
|
+
.filter(Boolean)
|
|
1503
|
+
.forEach((c) => allClasses.add(c));
|
|
1504
|
+
const glyphId='iam-glyph-'+sha256(String(icon[LUCIDE_SOURCE])).slice(0,24);
|
|
1505
|
+
let glyph='';if(shared&&!iconGlyphs.has(glyphId)){
|
|
1506
|
+
iconGlyphs.add(glyphId);glyph=element('defs',{},element('g',{id:glyphId},geometry.map(([tag,attrs],index)=>element(tag,attrs,'','glyph-'+glyphId+'-'+index)).join(''),'glyph-'+glyphId),'defs-'+glyphId);
|
|
1507
|
+
}
|
|
1508
|
+
return element(
|
|
1509
|
+
"svg",
|
|
1510
|
+
{
|
|
1511
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
1512
|
+
width: 24,
|
|
1513
|
+
height: 24,
|
|
1514
|
+
viewBox: "0 0 24 24",
|
|
1515
|
+
fill: "none",
|
|
1516
|
+
stroke: "currentColor",
|
|
1517
|
+
"stroke-width":
|
|
1518
|
+
typeof props.strokeWidth === "number" ? props.strokeWidth : 2,
|
|
1519
|
+
"stroke-linecap": "round",
|
|
1520
|
+
"stroke-linejoin": "round",
|
|
1521
|
+
"aria-hidden": "true",
|
|
1522
|
+
class: className,
|
|
1523
|
+
},
|
|
1524
|
+
shared?glyph+element('use',{href:'#'+glyphId},'','use'):geometry.map(([tag,attrs],index)=>element(tag,attrs,'',index)).join(''),
|
|
1525
|
+
"root",
|
|
1526
|
+
);
|
|
1527
|
+
}
|
|
1528
|
+
function unobservedValue(node, owner, instance,remote=false) {
|
|
1004
1529
|
const source = sourceLocation(owner.module, node, owner.symbol);
|
|
1005
1530
|
const elementKey = identity("element", { component: owner.key, unobservedValue: node.getStart(owner.module.source) - owner.fn.getStart(owner.module.source) });
|
|
1006
1531
|
const nodeId = identity("node", { page: pageKey, element: elementKey, instance });
|
|
1007
1532
|
elements.set(elementKey, { elementKey, componentKey: owner.key, tag: "span", source, identityStrategy: "provisional_ast_ordinal", editableProperties: [], binding: "preview_only" });
|
|
1008
1533
|
record.set(nodeId, { elementKey, source, synthetic: true });
|
|
1009
1534
|
// Preserve an unknown child boundary. An empty projection must never prove that replacing text is safe.
|
|
1010
|
-
return '<span data-iam-node="' + nodeId + '" hidden=""></span>';
|
|
1535
|
+
return '<span data-iam-node="' + nodeId + '" data-iam-observation="'+(remote?'remote':'unobserved')+'" hidden=""></span>';
|
|
1536
|
+
}
|
|
1537
|
+
function renderInvocation(invocation,instance,depth){
|
|
1538
|
+
const {callback,args,caller}=invocation,next={...callback.ctx,__iamContexts:caller.__iamContexts??callback.ctx.__iamContexts,__iamOverlays:caller.__iamOverlays??callback.ctx.__iamOverlays};
|
|
1539
|
+
for(const [index,parameter]of callback.fn.parameters.entries()){
|
|
1540
|
+
const value=args[index];
|
|
1541
|
+
if(ts.isIdentifier(parameter.name))next[parameter.name.text]=value;
|
|
1542
|
+
else if(ts.isObjectBindingPattern(parameter.name))for(const field of parameter.name.elements)if(ts.isIdentifier(field.name))next[field.name.text]=!isUnknown(value)&&value&&typeof value==='object'?value[field.propertyName?.text||field.name.text]??UNKNOWN:UNKNOWN;
|
|
1543
|
+
}
|
|
1544
|
+
gap('RENDER_PROP_SOURCE_ONLY',callback.owner.module.file,callback.owner.symbol+': invoked JSX render-prop reconstructed from source; effects and business callbacks are not executed');
|
|
1545
|
+
return renderExpr(uiReturnExpression(callback.fn),callback.owner,next,callback.instance+'/call-'+instance,depth+1);
|
|
1011
1546
|
}
|
|
1547
|
+
function hasSourceJsx(node){let found=false;const visit=n=>{if(ts.isJsxElement(n)||ts.isJsxSelfClosingElement(n)||ts.isJsxFragment(n)){found=true;return;}if(!ts.isFunctionLike(n))ts.forEachChild(n,visit);};if(node)visit(node);return found;}
|
|
1012
1548
|
function renderExpr(node, owner, ctx, instance, depth) {
|
|
1013
1549
|
node = unwrap(node);
|
|
1014
1550
|
if (!node) return "";
|
|
1015
1551
|
if (depth > 45) { gap("RENDER_DEPTH_LIMIT", owner.module.file, owner.symbol + ": JSX traversal budget exceeded"); return ""; }
|
|
1016
1552
|
const m = owner.module;
|
|
1553
|
+
if(ts.isCallExpression(node)&&ts.isIdentifier(node.expression)&&ctx.__iamLocals.has(node.expression.text)){
|
|
1554
|
+
const fn=unwrap(ctx.__iamLocals.get(node.expression.text));
|
|
1555
|
+
if(fn&&ts.isArrowFunction(fn)&&!fn.modifiers?.some(v=>v.kind===ts.SyntaxKind.AsyncKeyword)&&!ts.isBlock(fn.body)&&hasSourceJsx(fn.body)&&fn.parameters.length<=8&&fn.parameters.every(p=>ts.isIdentifier(p.name)&&!p.dotDotDotToken)&&!node.arguments.some(ts.isSpreadElement)){
|
|
1556
|
+
return renderInvocation({callback:{fn,owner,ctx,instance:instance+'/source-helper'},args:node.arguments.map(arg=>evalExpression(arg,m,ctx)),caller:ctx},instance,depth+1);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
const collection=finiteCollection(node,owner,ctx,instance,depth);if(collection!==null)return collection;
|
|
1560
|
+
if(ts.isIdentifier(node)&&ctx.__iamLocals.has(node.text)&&hasSourceJsx(ctx.__iamLocals.get(node.text))){
|
|
1561
|
+
if(ctx.__iamActiveJsx?.has(node.text))return '';
|
|
1562
|
+
return renderExpr(ctx.__iamLocals.get(node.text),owner,{...ctx,__iamActiveJsx:new Set([...(ctx.__iamActiveJsx??[]),node.text])},instance+'/local-'+node.text,depth+1);
|
|
1563
|
+
}
|
|
1017
1564
|
if (ts.isJsxFragment(node))
|
|
1018
1565
|
return node.children
|
|
1019
1566
|
.map((n, i) =>
|
|
@@ -1023,6 +1570,13 @@ export async function analyze({
|
|
|
1023
1570
|
if (ts.isJsxText(node)) return escape(jsxText(node.text));
|
|
1024
1571
|
if (ts.isJsxExpression(node))
|
|
1025
1572
|
return renderExpr(node.expression, owner, ctx, instance, depth + 1);
|
|
1573
|
+
if (ts.isBinaryExpression(node) && [ts.SyntaxKind.QuestionQuestionToken, ts.SyntaxKind.BarBarToken].includes(node.operatorToken.kind)) {
|
|
1574
|
+
const left = evalExpression(node.left, m, ctx);
|
|
1575
|
+
if (!isUnknown(left)) {
|
|
1576
|
+
const fallback = node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken ? left == null : !left;
|
|
1577
|
+
return renderExpr(fallback ? node.right : node.left, owner, ctx, instance + '/fallback', depth + 1);
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1026
1580
|
if(ts.isArrowFunction(node)){
|
|
1027
1581
|
const parent=node.parent?.parent,tag=parent&&ts.isJsxElement(parent)?parent.openingElement.tagName.getText(m.source):'',imported=m.imports.get(tag),body=unwrap(node.body);
|
|
1028
1582
|
if(imported?.specifier==='react-basics'&&imported.name==='Modal'&&!node.modifiers?.some(v=>v.kind===ts.SyntaxKind.AsyncKeyword)&&node.parameters.length<=3&&node.parameters.every(p=>ts.isIdentifier(p.name))&&(ts.isJsxElement(body)||ts.isJsxFragment(body)||ts.isJsxSelfClosingElement(body))){
|
|
@@ -1113,12 +1667,14 @@ export async function analyze({
|
|
|
1113
1667
|
gap("UNOBSERVED_CONDITION", m.file, label);
|
|
1114
1668
|
// Preserve both source alternatives for exact runtime binding. Neither
|
|
1115
1669
|
// is asserted to be authorized/visible, nor executed in source preview.
|
|
1670
|
+
const dataBranches=[yes,no].map(branch=>branch?evalExpression(branch,m,ctx):null),observation=remoteResultCondition(condition,owner,ctx)??(dataBranches.every(v=>v===null||['string','number'].includes(typeof v))?'unobserved-data':null),conditionRow={conditionKey:identity('ui-condition',{file:m.file,offset:condition.getStart(m.source)}),expression:label,source:sourceLocation(m,condition,owner.symbol),...(observation?{observation}:{})};
|
|
1671
|
+
presentation.conditions.push(conditionRow);
|
|
1116
1672
|
return [yes, no].map((branch,index)=>{
|
|
1117
1673
|
if(!branch)return '';
|
|
1118
1674
|
const elementKey=identity('element',{component:owner.key,state:label,unobserved:index,sourceOffset:condition.getStart(m.source)-owner.fn.getStart(m.source)}),id=identity('node',{page:pageKey,element:elementKey,instance:instance+'/unobserved-'+index}),source=sourceLocation(m,condition,owner.symbol);
|
|
1119
1675
|
record.set(id,{elementKey,source,synthetic:true});
|
|
1120
1676
|
elements.set(elementKey,{elementKey,componentKey:owner.key,tag:'div',source,identityStrategy:'provisional_ast_ordinal',editableProperties:[],binding:'preview_only'});
|
|
1121
|
-
return '<div data-iam-node="'+id+'" data-iam-unobserved="true" hidden="">'+renderExpr(branch,owner,ctx,instance+'/unobserved-'+index,depth+1)+'</div>';
|
|
1677
|
+
return '<div data-iam-node="'+id+'" data-iam-unobserved="true" hidden="" data-iam-condition="'+conditionRow.conditionKey+'" data-iam-alternative="'+(index===0)+'" class="iam-state">'+renderExpr(branch,owner,ctx,instance+'/unobserved-'+index,depth+1)+'</div>';
|
|
1122
1678
|
}).join('');
|
|
1123
1679
|
}
|
|
1124
1680
|
if (
|
|
@@ -1133,22 +1689,20 @@ export async function analyze({
|
|
|
1133
1689
|
fn &&
|
|
1134
1690
|
(ts.isArrowFunction(fn) || ts.isFunctionExpression(fn))
|
|
1135
1691
|
) {
|
|
1136
|
-
if (values.length > 50)
|
|
1137
|
-
gap(
|
|
1138
|
-
"COLLECTION_TRUNCATED",
|
|
1139
|
-
m.file,
|
|
1140
|
-
"Static collection limited to 50 preview items",
|
|
1141
|
-
);
|
|
1142
1692
|
return values
|
|
1143
|
-
.slice(0, 50)
|
|
1144
1693
|
.map((value, i) => {
|
|
1145
1694
|
const next = { ...ctx };
|
|
1146
1695
|
if (fn.parameters[0] && ts.isIdentifier(fn.parameters[0].name))
|
|
1147
1696
|
next[fn.parameters[0].name.text] = value;
|
|
1148
1697
|
if (fn.parameters[1] && ts.isIdentifier(fn.parameters[1].name))
|
|
1149
1698
|
next[fn.parameters[1].name.text] = i;
|
|
1699
|
+
if(fn.parameters[0]&&ts.isObjectBindingPattern(fn.parameters[0].name))for(const field of fn.parameters[0].name.elements){
|
|
1700
|
+
if(!ts.isIdentifier(field.name)||field.dotDotDotToken)continue;
|
|
1701
|
+
const key=field.propertyName?.text||field.name.text;next[field.name.text]=value&&typeof value==='object'&&!isUnknown(value)&&Object.hasOwn(value,key)?value[key]:field.initializer?evalExpression(field.initializer,m,next):undefined;
|
|
1702
|
+
}
|
|
1703
|
+
if(ts.isBlock(fn.body))for(const statement of fn.body.statements)if(ts.isVariableStatement(statement))for(const decl of statement.declarationList.declarations)if(ts.isIdentifier(decl.name))next[decl.name.text]=evalExpression(decl.initializer,m,next);
|
|
1150
1704
|
return renderExpr(
|
|
1151
|
-
|
|
1705
|
+
uiReturnExpression(fn),
|
|
1152
1706
|
owner,
|
|
1153
1707
|
next,
|
|
1154
1708
|
instance + "/item-" + i,
|
|
@@ -1158,7 +1712,7 @@ export async function analyze({
|
|
|
1158
1712
|
.join("");
|
|
1159
1713
|
}
|
|
1160
1714
|
gap("DYNAMIC_COLLECTION", m.file, node.expression.getText(m.source));
|
|
1161
|
-
return unobservedValue(node, owner, instance);
|
|
1715
|
+
return unobservedValue(node, owner, instance,queryBacked(node.expression.expression,owner,ctx));
|
|
1162
1716
|
}
|
|
1163
1717
|
if (!ts.isJsxElement(node) && !ts.isJsxSelfClosingElement(node)) {
|
|
1164
1718
|
if (
|
|
@@ -1168,6 +1722,9 @@ export async function analyze({
|
|
|
1168
1722
|
)
|
|
1169
1723
|
return ctx[node.text].iamChildren;
|
|
1170
1724
|
const value = evalExpression(node, m, ctx), expr = uiExpression(node, owner, ctx);
|
|
1725
|
+
if(value?.iamRenderInvocation)return renderInvocation(value.iamRenderInvocation,instance,depth);
|
|
1726
|
+
if(value?.iamRenderChildren)return value.iamRenderChildren(ctx);
|
|
1727
|
+
if(value?.iamChildren!==undefined)return value.iamChildren;
|
|
1171
1728
|
if (expr && readVariables(expr).length && ["string", "number"].includes(typeof value)) {
|
|
1172
1729
|
const source = sourceLocation(m, node, owner.symbol), elementKey = identity("element", { component: owner.key, stateTextOffset: node.getStart(m.source) - owner.fn.getStart(m.source) }), id = identity("node", { page: pageKey, element: elementKey, instance });
|
|
1173
1730
|
record.set(id, { elementKey, source, synthetic: true });
|
|
@@ -1184,20 +1741,19 @@ export async function analyze({
|
|
|
1184
1741
|
return escape(value);
|
|
1185
1742
|
if (isUnknown(value)) {
|
|
1186
1743
|
gap("DYNAMIC_VALUE", m.file, node.getText(m.source).slice(0, 100));
|
|
1187
|
-
return unobservedValue(node, owner, instance);
|
|
1744
|
+
return unobservedValue(node, owner, instance,queryBacked(node,owner,ctx));
|
|
1188
1745
|
}
|
|
1189
1746
|
return "";
|
|
1190
1747
|
}
|
|
1191
|
-
if (++renderedCount > 12000)
|
|
1192
|
-
throw new Error("Rendered node budget exceeded");
|
|
1193
1748
|
const opening = ts.isJsxElement(node) ? node.openingElement : node,
|
|
1194
1749
|
tag = opening.tagName.getText(m.source),
|
|
1195
|
-
props = m.anchors.get(opening) ? { "data-indiana-id": m.anchors.get(opening) } : {}, uiHandlers = [], uiBindings = [], forwardedHandlers=[];
|
|
1750
|
+
props = m.anchors.get(opening) ? { "data-indiana-id": m.anchors.get(opening) } : {}, uiHandlers = [], uiBindings = [], forwardedHandlers=[],uiAttributes=[];
|
|
1196
1751
|
for (const attr of opening.attributes.properties) {
|
|
1197
1752
|
if (ts.isJsxSpreadAttribute(attr)) {
|
|
1198
1753
|
const v = evalExpression(attr.expression, m, ctx);
|
|
1199
1754
|
if (isUnknown(v)) {
|
|
1200
1755
|
for (const attribute of anchorAttributes) delete props[attribute];
|
|
1756
|
+
props.__iamOpaqueProps = true;
|
|
1201
1757
|
gap("UNKNOWN_ANCHOR_SPREAD", m.file, "Opaque spread may override element identity");
|
|
1202
1758
|
continue;
|
|
1203
1759
|
}
|
|
@@ -1207,17 +1763,22 @@ export async function analyze({
|
|
|
1207
1763
|
for (const key of Object.keys(v)) {
|
|
1208
1764
|
for (let i = uiBindings.length - 1; i >= 0; i--) if (uiBindings[i].property === key) uiBindings.splice(i, 1);
|
|
1209
1765
|
for (let i = uiHandlers.length - 1; i >= 0; i--) if (uiHandlers[i].key === key) uiHandlers.splice(i, 1);
|
|
1766
|
+
for (let i = uiAttributes.length - 1; i >= 0; i--) if (uiAttributes[i].key === key) uiAttributes.splice(i, 1);
|
|
1210
1767
|
}
|
|
1211
|
-
if (inherited) { uiBindings.push(...inherited.bindings); uiHandlers.push(...inherited.handlers); }
|
|
1768
|
+
if (inherited) { uiBindings.push(...inherited.bindings); uiHandlers.push(...inherited.handlers); uiAttributes.push(...(inherited.attributes??[])); }
|
|
1212
1769
|
}
|
|
1213
1770
|
continue;
|
|
1214
1771
|
}
|
|
1215
1772
|
const key = attr.name.getText(m.source);
|
|
1773
|
+
for(let i=uiAttributes.length-1;i>=0;i--)if(uiAttributes[i].key===key)uiAttributes.splice(i,1);
|
|
1774
|
+
if(!ctx.__iamCollectionTemplate&&['className','placeholder','aria-label'].includes(key)&&attr.initializer&&ts.isJsxExpression(attr.initializer)){
|
|
1775
|
+
const reads=collectionStateReads(ts,attr.initializer.expression,ctx);if(reads.length)uiAttributes.push({key,owner,ctx,expression:attr.initializer.expression,property:key==='className'?'class':key,reads,source:sourceLocation(m,attr,owner.symbol)});
|
|
1776
|
+
}
|
|
1216
1777
|
for (let i = uiBindings.length - 1; i >= 0; i--) if (uiBindings[i].property === key) uiBindings.splice(i, 1);
|
|
1217
1778
|
for (let i = uiHandlers.length - 1; i >= 0; i--) if (uiHandlers[i].key === key) uiHandlers.splice(i, 1);
|
|
1218
|
-
if (["onClick", "onChange", "onInput"].includes(key)) {
|
|
1779
|
+
if (["onClick", "onChange", "onInput", "onValueChange", "onOpenChange", "onClose"].includes(key)) {
|
|
1219
1780
|
props[key] = undefined;
|
|
1220
|
-
const supported = uiHandler(attr.initializer, owner, ctx);
|
|
1781
|
+
const supported = ctx.__iamCollectionTemplate?null:uiHandler(attr.initializer, owner, ctx, key);
|
|
1221
1782
|
if (supported) { props[key] = undefined; uiHandlers.push({ ...supported, key, source: sourceLocation(m, attr, owner.symbol) }); }
|
|
1222
1783
|
const fn =
|
|
1223
1784
|
attr.initializer && ts.isJsxExpression(attr.initializer)
|
|
@@ -1249,7 +1810,7 @@ export async function analyze({
|
|
|
1249
1810
|
key +
|
|
1250
1811
|
" at line " +
|
|
1251
1812
|
sourceLocation(m, attr, owner.symbol).line +
|
|
1252
|
-
":
|
|
1813
|
+
": handler is not simulated; no business execution",
|
|
1253
1814
|
);
|
|
1254
1815
|
continue;
|
|
1255
1816
|
}
|
|
@@ -1275,6 +1836,16 @@ export async function analyze({
|
|
|
1275
1836
|
const expr = uiExpression(attr.initializer.expression, owner, ctx);
|
|
1276
1837
|
if (expr && readVariables(expr).length) uiBindings.push({ property: key, value: expr, source: sourceLocation(m, attr, owner.symbol) });
|
|
1277
1838
|
}
|
|
1839
|
+
if(attr.initializer&&ts.isJsxExpression(attr.initializer)){
|
|
1840
|
+
let fn=unwrap(attr.initializer.expression);const visited=new Set();
|
|
1841
|
+
while(fn&&ts.isIdentifier(fn)&&ctx.__iamLocals.has(fn.text)&&!visited.has(fn.text)){visited.add(fn.text);fn=unwrap(ctx.__iamLocals.get(fn.text));}
|
|
1842
|
+
if(fn&&(ts.isArrowFunction(fn)||ts.isFunctionExpression(fn))){
|
|
1843
|
+
let jsx=false;const inspect=n=>{if(ts.isJsxElement(n)||ts.isJsxSelfClosingElement(n)||ts.isJsxFragment(n))jsx=true;ts.forEachChild(n,inspect);};inspect(fn.body);
|
|
1844
|
+
const safe=!fn.modifiers?.some(v=>v.kind===ts.SyntaxKind.AsyncKeyword)&&(!ts.isBlock(fn.body)||fn.body.statements.every(s=>ts.isVariableStatement(s)||ts.isIfStatement(s)||ts.isReturnStatement(s)));
|
|
1845
|
+
if(jsx&&safe){props[key]={iamRenderProp:{fn,owner,ctx,instance:instance+'/prop-'+key}};continue;}
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
if(attr.initializer&&ts.isJsxExpression(attr.initializer)&&hasSourceJsx(attr.initializer.expression)){const value=attr.initializer.expression;props[key]={iamRenderChildren:inherited=>renderExpr(value,owner,{...ctx,__iamContexts:inherited.__iamContexts??ctx.__iamContexts,__iamTabs:inherited.__iamTabs??ctx.__iamTabs,__iamOverlays:inherited.__iamOverlays??ctx.__iamOverlays},instance+'/prop-'+key,depth+1)};continue;}
|
|
1278
1849
|
props[key] = !attr.initializer
|
|
1279
1850
|
? true
|
|
1280
1851
|
: ts.isStringLiteral(attr.initializer)
|
|
@@ -1285,24 +1856,131 @@ export async function analyze({
|
|
|
1285
1856
|
gap('DYNAMIC_CLASS_PARTIAL',m.file,'className at line '+sourceLocation(m,attr,owner.symbol).line+': only definitely present class tokens retained; unresolved styling needs runtime observation');
|
|
1286
1857
|
}
|
|
1287
1858
|
}
|
|
1288
|
-
const
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1859
|
+
const chart=sourceChart({ts,node:opening,tag,owner,context:ctx,evaluate:evalExpression,imported:externalImport});
|
|
1860
|
+
if(chart){
|
|
1861
|
+
gap(chart.unobserved?'CHART_DATA_UNOBSERVED':'CHART_SOURCE_PROJECTION',m.file,chart.unobserved?'Chart data is dynamic, random or outside bounds; no values are invented.':'Bounded source data rendered as inert SVG; library effects, animation and tooltips are not executed.');
|
|
1862
|
+
const source=sourceLocation(m,node,owner.symbol);
|
|
1863
|
+
const draw=(item,part)=>{
|
|
1864
|
+
if(typeof item==='string')return escape(item);
|
|
1865
|
+
const elementKey=identity('element',{component:owner.key,chart:node.pos,part}),nodeId=identity('node',{page:pageKey,elementKey,instance});
|
|
1866
|
+
elements.set(elementKey,{elementKey,componentKey:owner.key,tag:item.tag,source,identityStrategy:'provisional_ast_ordinal',editableProperties:[],binding:'preview_only'});record.set(nodeId,{elementKey,source,synthetic:true});
|
|
1867
|
+
return '<'+item.tag+' data-iam-node="'+nodeId+'" '+Object.entries(item.attrs).map(([k,v])=>k+'="'+escape(v)+'"').join(' ')+'>'+item.children.map((child,i)=>draw(child,part+'/'+i)).join('')+'</'+item.tag+'>';
|
|
1868
|
+
};
|
|
1869
|
+
return draw({tag:'svg',attrs:{xmlns:'http://www.w3.org/2000/svg',viewBox:'0 0 '+chart.width+' '+chart.height,width:chart.width,height:chart.height,role:'img','aria-label':chart.unobserved?'Chart: data unobserved':'Source chart projection','data-iam-chart':chart.unobserved?'unobserved':'source',style:'display:block;width:100%;height:100%;max-height:'+chart.height+'px'},children:chart.children},'root');
|
|
1870
|
+
}
|
|
1871
|
+
const sourceIcon=evalExpression(opening.tagName,m,ctx);
|
|
1872
|
+
if(sourceIcon?.[LUCIDE_SOURCE])return renderLucide(sourceIcon,node,owner,props,instance,!ctx.__iamCollectionTemplate);
|
|
1873
|
+
const tabPart=tabsPrimitive(m,tag),basePart=basePrimitive(m,tag),overlay=basePart&&basePart.family!=='Tabs'?basePart:null;
|
|
1874
|
+
let overlayGroup=overlay?ctx.__iamOverlays?.get(overlay.family):null;
|
|
1875
|
+
let childCtx=ctx,tabGroup=ctx.__iamTabs;
|
|
1876
|
+
const provider=tag.endsWith('.Provider')?contextKey(m,tag.slice(0,-9)):contextKey(m,tag);
|
|
1877
|
+
if(provider){const contexts=new Map(ctx.__iamContexts);contexts.set(provider,props.value);childCtx={...childCtx,__iamContexts:contexts};}
|
|
1878
|
+
if(overlay&&(['Root','SubmenuRoot'].includes(overlay.part)||overlay.family==='AriaDialog'&&['Backdrop','Popup'].includes(overlay.part)&&(Object.hasOwn(props,'isOpen')||Object.hasOwn(props,'defaultOpen')))){
|
|
1879
|
+
const openProperty=overlay.family==='Transition'?'in':overlay.family==='AriaDialog'?'isOpen':'open',open=props[openProperty];
|
|
1880
|
+
overlayGroup={open:open===true||open===undefined&&props.defaultOpen===true,variable:uiBindings.find(b=>b.property===openProperty)?.value,controller:uiHandlers.find(h=>h.key==='onOpenChange')};
|
|
1881
|
+
if(overlay.family!=='Transition'&&open===undefined&&!props.__iamOpaqueProps&&(props.defaultOpen===undefined||typeof props.defaultOpen==='boolean')){
|
|
1882
|
+
const scopeKey=identity('primitive-state',{page:pageKey,instance,family:overlay.family}),variableKey=identity('ui-variable',{scope:scopeKey,label:'open'});
|
|
1883
|
+
ui.variables.push({variableKey,routeKey:pageKey,scopeKey,label:overlay.family+'.open',valueType:'boolean',initialValue:overlayGroup.open,source:sourceLocation(m,node,owner.symbol)});
|
|
1884
|
+
overlayGroup.variable={op:'var',key:variableKey};
|
|
1885
|
+
overlayGroup.controller={variableKey,value:{op:'event',key:'value'},payload:{value:'boolean'}};
|
|
1886
|
+
}
|
|
1887
|
+
if(overlay.family==='Transition'){
|
|
1888
|
+
const attr=opening.attributes.properties.find(a=>ts.isJsxAttribute(a)&&a.name.text==='onEnter');
|
|
1889
|
+
const callback=attr?.initializer&&ts.isJsxExpression(attr.initializer)?unwrap(attr.initializer.expression):null;
|
|
1890
|
+
let statement=callback&&ts.isArrowFunction(callback)&&!callback.parameters.length&&ts.isBlock(callback.body)?callback.body.statements[0]:null;
|
|
1891
|
+
if(statement&&ts.isIfStatement(statement))statement=evalExpression(statement.expression,m,ctx)===true?statement.thenStatement:null;
|
|
1892
|
+
if(statement&&ts.isBlock(statement))statement=statement.statements[0];
|
|
1893
|
+
const assignment=statement&&ts.isExpressionStatement(statement)?unwrap(statement.expression):null;
|
|
1894
|
+
const left=assignment&&ts.isBinaryExpression(assignment)&&assignment.operatorToken.kind===ts.SyntaxKind.EqualsToken?assignment.left:null;
|
|
1895
|
+
if(left&&ts.isPropertyAccessExpression(left)&&left.name.text==='display'&&ts.isPropertyAccessExpression(left.expression)&&left.expression.name.text==='style'&&ts.isPropertyAccessExpression(left.expression.expression)&&left.expression.expression.name.text==='current'&&ts.isIdentifier(left.expression.expression.expression)&&(assignment.right.kind===ts.SyntaxKind.NullKeyword||ts.isStringLiteral(assignment.right)&&assignment.right.text==='')){
|
|
1896
|
+
overlayGroup.reveal={ref:left.expression.expression.expression.text,file:m.file,symbol:owner.symbol};
|
|
1897
|
+
gap('TRANSITION_VISIBILITY_SOURCE_ONLY',m.file,'Source onEnter clears the referenced child display style; only that UI assignment is projected, without running callbacks or effects.');
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
if(isUnknown(open)){
|
|
1901
|
+
const attribute=opening.attributes.properties.find(a=>ts.isJsxAttribute(a)&&a.name.text===openProperty),expression=attribute?.initializer&&ts.isJsxExpression(attribute.initializer)?attribute.initializer.expression:opening;
|
|
1902
|
+
overlayGroup.condition={conditionKey:identity('ui-condition',{file:m.file,offset:expression.getStart(m.source)}),expression:attribute?redactLiteral(expression.getText(m.source)).slice(0,160):'open (forwarded)',source:sourceLocation(m,expression,owner.symbol)};
|
|
1903
|
+
presentation.conditions.push(overlayGroup.condition);
|
|
1904
|
+
}
|
|
1905
|
+
const overlays=new Map(ctx.__iamOverlays);overlays.set(overlay.family,overlayGroup);childCtx={...childCtx,__iamOverlays:overlays};
|
|
1906
|
+
}
|
|
1907
|
+
if(tabPart==='Root') {
|
|
1908
|
+
const valueBinding=uiBindings.find(b=>b.property==='value')?.value;
|
|
1909
|
+
const controller=uiHandlers.find(h=>h.key==='onValueChange'||basePart?.mantine&&h.key==='onChange');
|
|
1910
|
+
tabGroup={tabKey:identity('ui-tab',{page:pageKey,scope:ctx.__iamScope,instance}),routeKey:pageKey,renderKey:'pending',nodeId:'pending',variableKey:valueBinding?.op==='var'?valueBinding.key:null,initialValue:typeof props.value==='string'?props.value:typeof props.defaultValue==='string'?props.defaultValue:null,items:[],source:sourceLocation(m,node,owner.symbol),scopeSource:ui.variables.find(v=>v.variableKey===valueBinding?.key)?.source??sourceLocation(m,node,owner.symbol),controller};
|
|
1911
|
+
childCtx={...childCtx,__iamTabs:tabGroup};
|
|
1912
|
+
}
|
|
1913
|
+
const mantineRef=m.imports.get(tag.split('.')[0]),mantineName=mantineRef?.specifier==='@mantine/core'?(mantineRef.name==='*'?tag.split('.').slice(1).join('.'):mantineRef.name+tag.split('.').slice(1).map(s=>'.'+s).join('')):null;
|
|
1914
|
+
let accordion=ctx.__iamContexts?.get('$iam-mantine-accordion');
|
|
1915
|
+
if(mantineName==='Accordion'){
|
|
1916
|
+
const contexts=new Map(childCtx.__iamContexts);accordion={value:props.value??props.defaultValue,multiple:props.multiple===true,name:identity('accordion',{page:pageKey,instance}),classes:props.classNames};contexts.set('$iam-mantine-accordion',accordion);childCtx={...childCtx,__iamContexts:contexts};
|
|
1917
|
+
gap('ACCORDION_SOURCE_PREVIEW',m.file,'Native local details/summary preview; source defaults are retained and library callbacks are not executed.');
|
|
1918
|
+
}
|
|
1919
|
+
if(mantineName==='Accordion.Item'&&accordion){props.open=Array.isArray(accordion.value)?accordion.value.includes(props.value):typeof props.value==='string'&&accordion.value===props.value;if(!accordion.multiple)props.name=accordion.name;}
|
|
1920
|
+
if(mantineName?.startsWith('Accordion.')&&accordion){const field={Item:'item',Control:'control',Panel:'panel'}[mantineName.split('.')[1]],name=accordion.classes?.[field];if(typeof name==='string')props.className=[props.className,name].filter(v=>typeof v==='string').join(' ');}
|
|
1921
|
+
const localRef=componentBinding(m,tag),localTarget=ctx[tag]?.[COMPONENT]??(localRef&&componentDefs.get(localRef.module.file+'#'+localRef.name));
|
|
1922
|
+
const dynamicNode=ctx.__iamLocals.get(tag);
|
|
1923
|
+
const registry=!localTarget&&dynamicNode?componentRegistry({ts,node:dynamicNode,module:m,locals:ctx.__iamLocals,binding,functionFrom,value:(n,module)=>evalExpression(n,module,ctx),component:(n,module)=>sourceComponent(n,module)?.[COMPONENT],variable:name=>ctx.__iamVariableKeys[name]??(ctx.__iamPropBindings.get(name)?.op==='var'?ctx.__iamPropBindings.get(name).key:null),location:sourceLocation,unknown:UNKNOWN,imported:externalImport}):null;
|
|
1924
|
+
const renderChildren=(inherited=childCtx)=>ts.isJsxElement(node)?node.children.map((child,i)=>renderExpr(child,owner,{...childCtx,__iamContexts:inherited.__iamContexts??childCtx.__iamContexts,__iamTabs:inherited.__iamTabs??childCtx.__iamTabs,__iamOverlays:inherited.__iamOverlays??childCtx.__iamOverlays},instance+'/child-'+i,depth+1)).join(''):props.children?.iamRenderChildren?props.children.iamRenderChildren(inherited):props.children?.iamChildren||'';
|
|
1925
|
+
let children = localTarget||registry ? '' : renderChildren();
|
|
1926
|
+
const mantine = mantinePrimitive(m.imports.get(tag.split('.')[0]), props, tag.split('.').slice(1).join('.'));
|
|
1927
|
+
if (mantine) {
|
|
1928
|
+
gap('MANTINE_SOURCE_PROFILE', m.file, 'Inert semantic Mantine projection; theme resolution, portals, focus management and library callbacks are not executed.');
|
|
1929
|
+
if (mantine.transparent) return children;
|
|
1930
|
+
Object.assign(props, mantine.props);
|
|
1931
|
+
if (mantine.overlay) {
|
|
1932
|
+
const visible = uiBindings.find(b => b.property === 'opened');
|
|
1933
|
+
if (visible) uiBindings.push({...visible, property: 'visible'});
|
|
1934
|
+
if (typeof props.opened !== 'boolean') gap('MANTINE_OPEN_STATE_UNOBSERVED', m.file, 'Unknown overlay state is initially hidden; no runtime open state is inferred.');
|
|
1935
|
+
}
|
|
1936
|
+
}
|
|
1937
|
+
const flowbite=flowbiteShape(externalImport(m,tag),props,children,['Table','Navbar','Sidebar'].includes(m.imports.get(tag.split('.')[0])?.name)?tag.split('.')[1]:undefined,flowbiteTheme);
|
|
1938
|
+
if(flowbite){
|
|
1939
|
+
gap('UI_LIBRARY_STATIC_PROFILE',m.file,'Flowbite 0.3 inert presentation profile; custom theme and library state remain unobserved.');
|
|
1940
|
+
const source=sourceLocation(m,node,owner.symbol);
|
|
1941
|
+
const draw=(item,part)=>{
|
|
1942
|
+
if(typeof item==='string')return escape(item);
|
|
1943
|
+
if(Object.hasOwn(item,'html'))return item.html;
|
|
1944
|
+
const elementKey=identity('element',{component:owner.key,library:'flowbite',offset:node.pos,part}),nodeId=identity('node',{page:pageKey,elementKey,instance});
|
|
1945
|
+
elements.set(elementKey,{elementKey,componentKey:owner.key,tag:item.tag,source,identityStrategy:'provisional_ast_ordinal',editableProperties:[],binding:'preview_only'});record.set(nodeId,{elementKey,source,synthetic:true});
|
|
1946
|
+
if(item.attrs.class)item.attrs.class.split(/\s+/).filter(Boolean).forEach(c=>allClasses.add(c));
|
|
1947
|
+
if(item.attrs.src)assets.set(item.attrs.src,null);
|
|
1948
|
+
const attrs=Object.entries(item.attrs).filter(([,v])=>v!==false).map(([k,v])=>' '+k+'="'+escape(v===true?'':v)+'"').join('');
|
|
1949
|
+
return '<'+item.tag+' data-iam-node="'+nodeId+'"'+attrs+'>'+(voidTags.has(item.tag)?'':item.children.map((child,i)=>draw(child,part+'/'+i)).join('')+'</'+item.tag+'>');
|
|
1950
|
+
};
|
|
1951
|
+
return draw(flowbite,'root');
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
// Project the documented Controller render callback from source, with no
|
|
1955
|
+
// form initialization, live field values or business callback execution.
|
|
1956
|
+
const formPrimitive = externalImport(m, tag);
|
|
1957
|
+
if (formPrimitive?.specifier === 'react-hook-form') {
|
|
1958
|
+
if (formPrimitive.name === 'FormProvider') return children;
|
|
1959
|
+
if (formPrimitive.name === 'Controller' && props.render?.iamRenderProp)
|
|
1960
|
+
return renderInvocation({ callback: props.render.iamRenderProp,
|
|
1961
|
+
args: [{ field: { name: typeof props.name === 'string' ? props.name : UNKNOWN }, fieldState: UNKNOWN, formState: UNKNOWN }],
|
|
1962
|
+
caller: childCtx }, instance + '/controller', depth + 1);
|
|
1963
|
+
}
|
|
1964
|
+
const slotPrimitive = ctx[tag]?.[UI_PRIMITIVE]??externalImport(m, tag);
|
|
1965
|
+
if (slotPrimitive?.specifier === '@radix-ui/react-slot' && ['Slot', 'Root'].includes(slotPrimitive.name)) {
|
|
1966
|
+
const fragment=JSDOM.fragment(children),only=[...fragment.childNodes].filter(n=>n.nodeType!==3||n.textContent.trim());
|
|
1967
|
+
if(only.length!==1||only[0].nodeType!==1){gap('SLOT_CHILD_UNSUPPORTED',m.file,'Slot requires one concrete element; source-only children retained without prop merging');return children;}
|
|
1968
|
+
const child=only[0];
|
|
1969
|
+
if(typeof props.className==='string'){
|
|
1970
|
+
const names=[props.className,child.getAttribute('class')].filter(Boolean).join(' ');child.setAttribute('class',names);names.split(/\s+/).filter(Boolean).forEach(c=>allClasses.add(c));
|
|
1971
|
+
}
|
|
1972
|
+
for(const [key,value]of Object.entries(props))if(/^(?:title|role|data-(?:slot|state|side|variant|collapsible|active)|aria-[a-z-]+)$/.test(key)&&['string','number','boolean'].includes(typeof value)&&!child.hasAttribute(key))child.setAttribute(key,String(value));
|
|
1973
|
+
if(props.style&&typeof props.style==='object'){const css=staticStyle(props.style,()=>gap('INLINE_STYLE_UNSUPPORTED',m.file,'Unresolved Slot style'));if(css)child.setAttribute('style',css+';'+(child.getAttribute('style')||''));}
|
|
1974
|
+
return [...fragment.childNodes].map(n=>n.nodeType===1?n.outerHTML:n.textContent).join('');
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1301
1977
|
if (ctx[tag]?.iamChildren !== undefined) return ctx[tag].iamChildren;
|
|
1978
|
+
if (ctx[tag]?.iamRenderChildren) return ctx[tag].iamRenderChildren(ctx);
|
|
1302
1979
|
const routerPrimitive = externalImport(m, tag);
|
|
1303
|
-
if ([
|
|
1980
|
+
if (['react-router','react-router-dom'].includes(routerPrimitive?.specifier)&&routerPrimitive.name==='Navigate')return '';
|
|
1981
|
+
if (["react-router", "react-router-dom", "@tanstack/react-router"].includes(routerPrimitive?.specifier) && routerPrimitive.name === "Outlet") return ctx.__iamOutlet?.iamRenderChildren ? ctx.__iamOutlet.iamRenderChildren(ctx) : ctx.__iamOutlet?.iamChildren ?? "";
|
|
1304
1982
|
if (
|
|
1305
|
-
/^(?:ThemeProvider|Providers?|Analytics|TailwindIndicator|Toaster)$/.test(
|
|
1983
|
+
!localTarget && /^(?:ThemeProvider|Providers?|Analytics|TailwindIndicator|Toaster)$/.test(
|
|
1306
1984
|
tag,
|
|
1307
1985
|
)
|
|
1308
1986
|
)
|
|
@@ -1310,16 +1988,41 @@ export async function analyze({
|
|
|
1310
1988
|
const intrinsicAlias = typeof ctx[tag] === "string" && allowedTags.has(ctx[tag]);
|
|
1311
1989
|
const importedPrimitive=m.imports.get(tag),knownExternalPrimitive=importedPrimitive?.specifier==='react-basics'&&['Text','Button','SubmitButton'].includes(importedPrimitive.name);
|
|
1312
1990
|
let htmlTag = intrinsicAlias ? ctx[tag] : tag;
|
|
1991
|
+
if(provider)return children;
|
|
1313
1992
|
if (tag === "html" || tag === "body") {
|
|
1314
1993
|
htmlTag = "div";
|
|
1315
1994
|
props["data-iam-adapter"] = tag;
|
|
1316
1995
|
}
|
|
1317
1996
|
if (/^[A-Z]/.test(tag) && !intrinsicAlias) {
|
|
1318
|
-
if (
|
|
1997
|
+
if (mantine) htmlTag = mantine.tag;
|
|
1998
|
+
else if(tabPart){
|
|
1999
|
+
htmlTag=tabPart==='Tab'?'button':'div';
|
|
2000
|
+
if(tabPart==='List')props.role='tablist';
|
|
2001
|
+
if(tabPart==='Tab'){props.role='tab';props.type='button';props['aria-selected']=tabGroup?.initialValue===props.value;}
|
|
2002
|
+
if(tabPart==='Panel'){props.role='tabpanel';props.hidden=tabGroup?.initialValue!==props.value;}
|
|
2003
|
+
}
|
|
2004
|
+
else if(overlay){
|
|
2005
|
+
htmlTag=['Trigger','Close','Item','CheckboxItem','RadioItem','SubmenuTrigger'].includes(overlay.part)&&props.asChild!==true?'button':overlay.part==='Title'?'h2':overlay.part==='Description'?'p':'div';
|
|
2006
|
+
if(htmlTag==='button')props.type='button';
|
|
2007
|
+
if(['Portal','Popup','Backdrop'].includes(overlay.part)||overlay.family==='Transition'){props.hidden=!overlayGroup?.open;if(overlayGroup?.condition){props['data-iam-condition']=overlayGroup.condition.conditionKey;props['data-iam-alternative']='true';props['data-iam-condition-default']='false';props['data-iam-unobserved']='true';}if(overlayGroup?.variable)uiBindings.push({property:'visible',value:overlayGroup.variable,source:sourceLocation(m,node,owner.symbol)});}
|
|
2008
|
+
if(overlay.rootVisibility){props.hidden=!overlayGroup?.open;if(overlayGroup?.variable)uiBindings.push({property:'visible',value:overlayGroup.variable,source:sourceLocation(m,node,owner.symbol)});}
|
|
2009
|
+
else if(['Root','SubmenuRoot','Portal','Provider'].includes(overlay.part)||props.asChild===true)props.style={display:'contents'};
|
|
2010
|
+
if(overlay.part==='Popup'&&overlay.role!==null)props.role=overlay.role??(overlay.family==='Menu'?'menu':overlay.family==='Tooltip'?'tooltip':'dialog');
|
|
2011
|
+
if(overlay.part==='Content'&&overlay.role)props.role=overlay.role;
|
|
2012
|
+
}
|
|
2013
|
+
else if (tag === "Link" || ["react-router", "react-router-dom", "@tanstack/react-router"].includes(routerPrimitive?.specifier) && ["Link", "NavLink"].includes(routerPrimitive.name)) {
|
|
1319
2014
|
htmlTag = "a";
|
|
1320
2015
|
if (typeof props.to === "string") props.href = props.to;
|
|
1321
2016
|
}
|
|
1322
2017
|
else if (tag === "Image") htmlTag = "img";
|
|
2018
|
+
else if(externalImport(m,tag)?.specifier==='recharts'&&externalImport(m,tag)?.name==='ResponsiveContainer'){
|
|
2019
|
+
htmlTag='div';const dimension=v=>typeof v==='number'&&Number.isFinite(v)||typeof v==='string'&&/^[0-9.]+(?:%|px|rem|vh|vw)?$/.test(v)?v:'100%';props.style={...(props.style&&typeof props.style==='object'?props.style:{}),width:dimension(props.width),height:dimension(props.height),minWidth:0};gap('RESPONSIVE_CHART_SOURCE_PREVIEW',m.file,'Static chart viewport follows its declared container; library measurements, effects and animation are not executed.');
|
|
2020
|
+
}
|
|
2021
|
+
else if((['@base-ui/react/avatar','@base-ui/react'].includes(m.imports.get(tag.split('.')[0])?.specifier)&&m.imports.get(tag.split('.')[0]).name==='Avatar'&&['Root','Image','Fallback'].includes(tag.split('.')[1]))||(m.imports.get(tag.split('.')[0])?.specifier==='@radix-ui/react-avatar'&&['Image','AvatarImage','Root','Avatar','Fallback','AvatarFallback'].includes(externalImport(m,tag)?.name))){
|
|
2022
|
+
const part=tag.split('.')[1]??externalImport(m,tag)?.name;
|
|
2023
|
+
htmlTag=['Image','AvatarImage'].includes(part)?'img':'span';
|
|
2024
|
+
if(['Fallback','AvatarFallback'].includes(part)){props.hidden=true;gap('AVATAR_FALLBACK_UNOBSERVED',m.file,'Packaged image is the preview; merchant image loading/error fallback state is not simulated.');}
|
|
2025
|
+
}
|
|
1323
2026
|
else if(m.imports.get(tag)?.specifier==='react-basics'&&m.imports.get(tag)?.name==='Text'){
|
|
1324
2027
|
htmlTag=typeof props.as==='string'&&allowedTags.has(props.as)?props.as:'span';
|
|
1325
2028
|
gap('COMPONENT_STYLE_ADAPTER',m.file,'react-basics Text DOM semantics only; actual styles need runtime evidence');
|
|
@@ -1335,13 +2038,17 @@ export async function analyze({
|
|
|
1335
2038
|
.join(" ");
|
|
1336
2039
|
} else {
|
|
1337
2040
|
const ref = componentBinding(m, tag),
|
|
1338
|
-
target = ref && componentDefs.get(ref.module.file + "#" + ref.name);
|
|
2041
|
+
target = ctx[tag]?.[COMPONENT]??(ref && componentDefs.get(ref.module.file + "#" + ref.name));
|
|
1339
2042
|
if (target) {
|
|
1340
2043
|
const own = components.get(owner.key);
|
|
1341
2044
|
if (!own.dependencies.includes(target.key))
|
|
1342
2045
|
own.dependencies.push(target.key);
|
|
1343
|
-
const
|
|
1344
|
-
|
|
2046
|
+
const hasChildren=ts.isJsxElement(node)&&node.children.some(n=>!ts.isJsxText(n)||jsxText(n.text).trim());
|
|
2047
|
+
const meaningful=ts.isJsxElement(node)?node.children.filter(n=>!ts.isJsxText(n)||jsxText(n.text).trim()):[];
|
|
2048
|
+
const childCallback=meaningful.length===1&&ts.isJsxExpression(meaningful[0])?unwrap(meaningful[0].expression):null;
|
|
2049
|
+
const callbackBody=childCallback&&(ts.isArrowFunction(childCallback)||ts.isFunctionExpression(childCallback))&&!childCallback.modifiers?.some(v=>v.kind===ts.SyntaxKind.AsyncKeyword)&&(!ts.isBlock(childCallback.body)||childCallback.body.statements.every(s=>ts.isVariableStatement(s)||ts.isIfStatement(s)||ts.isReturnStatement(s)))&&hasSourceJsx(uiReturnExpression(childCallback));
|
|
2050
|
+
const childProps = { ...props, ...(hasChildren?{children:callbackBody?{iamRenderProp:{fn:childCallback,owner,ctx,instance:instance+'/render-child'}}:{iamRenderChildren:renderChildren}}:{}), __iamCollectionTemplate:ctx.__iamCollectionTemplate, __iamOutlet: ctx.__iamOutlet, __iamContexts:ctx.__iamContexts, __iamTabs:ctx.__iamTabs, __iamOverlays:ctx.__iamOverlays };
|
|
2051
|
+
forwarded.set(childProps, { bindings: uiBindings, handlers: [...uiHandlers,...forwardedHandlers], attributes:uiAttributes });
|
|
1345
2052
|
return renderComponent(
|
|
1346
2053
|
target,
|
|
1347
2054
|
childProps,
|
|
@@ -1349,6 +2056,23 @@ export async function analyze({
|
|
|
1349
2056
|
depth + 1,
|
|
1350
2057
|
);
|
|
1351
2058
|
}
|
|
2059
|
+
if(registry){
|
|
2060
|
+
const regionKey=identity('ui-region',{page:pageKey,instance,tag}),regionNode=identity('node',{page:pageKey,instance,regionKey});
|
|
2061
|
+
const source=sourceLocation(m,opening,owner.symbol),elementKey=identity('element',{component:owner.key,regionOffset:opening.getStart(m.source)-owner.fn.getStart(m.source)});
|
|
2062
|
+
elements.set(elementKey,{elementKey,componentKey:owner.key,tag:'div',source,identityStrategy:'provisional_ast_ordinal',editableProperties:[],binding:'preview_only'});record.set(regionNode,{elementKey,source,synthetic:true});
|
|
2063
|
+
const row={regionKey,routeKey:pageKey,renderKey:'pending',nodeId:regionNode,expression:redactLiteral(dynamicNode.getText(m.source)).slice(0,160),variableKey:registry.variableKey,initialValue:ui.variables.find(v=>v.variableKey===registry.variableKey)?.initialValue??null,options:[],source};
|
|
2064
|
+
if(typeof row.initialValue!=='string'||!registry.options.some(o=>o.value===row.initialValue))row.initialValue=null;
|
|
2065
|
+
let html='<div data-iam-node="'+regionNode+'" data-iam-region="'+regionKey+'">';
|
|
2066
|
+
for(const option of registry.options){
|
|
2067
|
+
const id=identity('node',{page:pageKey,regionKey,value:option.value}),optionElement=identity('element',{component:owner.key,regionKey,value:option.value});
|
|
2068
|
+
elements.set(optionElement,{elementKey:optionElement,componentKey:owner.key,tag:'div',source:option.source,identityStrategy:'provisional_ast_ordinal',editableProperties:[],binding:'preview_only'});record.set(id,{elementKey:optionElement,source:option.source,synthetic:true});
|
|
2069
|
+
const catalog=ctx.t?.[TRANSLATION]??(translationResources.size===1?[...translationResources][0]:null),title=catalog?option.label.split('.').reduce((v,key)=>v?.[key],catalog):undefined;
|
|
2070
|
+
row.options.push({value:option.value,label:typeof title==='string'?title:option.label,nodeId:id,componentKey:option.component.key,source:option.source,scope:option.scope});
|
|
2071
|
+
const own=components.get(owner.key);if(!own.dependencies.includes(option.component.key))own.dependencies.push(option.component.key);
|
|
2072
|
+
html+='<div data-iam-node="'+id+'" data-iam-region-value="'+escape(option.value)+'" data-iam-unobserved="true" hidden="">'+renderComponent(option.component,{...props,children:{iamRenderChildren:renderChildren},__iamOutlet:ctx.__iamOutlet,__iamContexts:ctx.__iamContexts,__iamOverlays:ctx.__iamOverlays},instance+'/registry-'+option.value,depth+1)+'</div>';
|
|
2073
|
+
}
|
|
2074
|
+
html+='</div>';presentation.regions.push(row);gap('DYNAMIC_COMPONENT_CANDIDATES',m.file,tag+': finite registry alternatives retained as source-only UI previews; selection and scope are unobserved');return html;
|
|
2075
|
+
}
|
|
1352
2076
|
htmlTag = /Button|Trigger/.test(tag)
|
|
1353
2077
|
? "button"
|
|
1354
2078
|
: /Input/.test(tag)
|
|
@@ -1361,6 +2085,10 @@ export async function analyze({
|
|
|
1361
2085
|
}
|
|
1362
2086
|
}
|
|
1363
2087
|
if (!allowedTags.has(htmlTag)) return children;
|
|
2088
|
+
const reveal=ctx.__iamOverlays?.get('Transition')?.reveal,ref=opening.attributes.properties.find(a=>ts.isJsxAttribute(a)&&a.name.text==='ref');
|
|
2089
|
+
if(reveal?.file===m.file&&reveal.symbol===owner.symbol&&ref?.initializer&&ts.isJsxExpression(ref.initializer)&&ts.isIdentifier(ref.initializer.expression)&&ref.initializer.expression.text===reveal.ref&&props.style?.display==='none'){
|
|
2090
|
+
const {display,...style}=props.style;props.style=style;
|
|
2091
|
+
}
|
|
1364
2092
|
const anchor = anchorAttributes
|
|
1365
2093
|
.map((key) =>
|
|
1366
2094
|
typeof props[key] === "string" ? key + "=" + props[key] : null,
|
|
@@ -1385,6 +2113,7 @@ export async function analyze({
|
|
|
1385
2113
|
element: elementKey,
|
|
1386
2114
|
instance,
|
|
1387
2115
|
});
|
|
2116
|
+
if(!ctx.__iamCollectionTemplate)for(const request of uiAttributes)attributeRequests.push({nodeId,...request});
|
|
1388
2117
|
elements.set(elementKey, {
|
|
1389
2118
|
elementKey,
|
|
1390
2119
|
componentKey: owner.key,
|
|
@@ -1399,29 +2128,58 @@ export async function analyze({
|
|
|
1399
2128
|
record.set(nodeId, {
|
|
1400
2129
|
elementKey,
|
|
1401
2130
|
source,
|
|
1402
|
-
synthetic: htmlTag !== tag && tag !== "Link" && tag !== "Image"&&!intrinsicAlias&&!knownExternalPrimitive,
|
|
2131
|
+
synthetic: Boolean(m.mdx) || htmlTag !== tag && tag !== "Link" && tag !== "Image"&&!intrinsicAlias&&!knownExternalPrimitive,
|
|
1403
2132
|
});
|
|
1404
|
-
|
|
2133
|
+
if(tabPart==='Root'){
|
|
2134
|
+
// Tab initialization below shares the same concrete source container.
|
|
2135
|
+
tabGroup.nodeId=nodeId;
|
|
2136
|
+
const {controller,...row}=tabGroup;
|
|
2137
|
+
if(row.items.length&&row.initialValue!==null&&!row.items.some(item=>item.value===row.initialValue))row.initialValue=null;
|
|
2138
|
+
presentation.tabs.push(row);
|
|
2139
|
+
}
|
|
2140
|
+
if(overlayGroup?.controller&&overlayGroup.variable&&['Dialog','AlertDialog','Menu','Popover','Collapsible'].includes(overlay?.family)&&['Trigger','Close'].includes(overlay.part)&&!uiHandlers.some(h=>h.key==='onClick')){
|
|
2141
|
+
const next=overlay.part==='Close'?{op:'const',value:false}:{op:'not',arg:overlayGroup.variable};
|
|
2142
|
+
const replace=expression=>expression.op==='event'&&expression.key==='value'?next:Object.fromEntries(Object.entries(expression).map(([key,value])=>[key,Array.isArray(value)?value.map(v=>v&&typeof v==='object'?replace(v):v):value&&typeof value==='object'?replace(value):value]));
|
|
2143
|
+
const controller=overlayGroup.controller,writes=(controller.writes||[{variableKey:controller.variableKey,value:controller.value}]).map(w=>({...w,value:replace(w.value)}));
|
|
2144
|
+
uiHandlers.push({...writes[0],writes,key:'onClick',payload:{},source});
|
|
2145
|
+
}
|
|
2146
|
+
if(tabPart==='Tab'&&tabGroup&&typeof props.value==='string'&&props.value.length<=2048){
|
|
2147
|
+
if(!tabGroup.items.some(item=>item.value===props.value))tabGroup.items.push({value:props.value,nodeId});
|
|
2148
|
+
props['data-iam-tab-value']=props.value;props['data-iam-tab-key']=tabGroup.tabKey;
|
|
2149
|
+
const replace=value=>value.op==='event'&&value.key==='value'?{op:'const',value:props.value}:Object.fromEntries(Object.entries(value).map(([k,v])=>[k,Array.isArray(v)?v.map(item=>item&&typeof item==='object'?replace(item):item):v&&typeof v==='object'?replace(v):v]));
|
|
2150
|
+
if(tabGroup.controller){const writes=(tabGroup.controller.writes||[{variableKey:tabGroup.controller.variableKey,value:tabGroup.controller.value}]).map(w=>({...w,value:replace(w.value)}));uiHandlers.push({...writes[0],writes,key:'onClick',payload:{},source});}
|
|
2151
|
+
}
|
|
2152
|
+
if(tabPart==='Panel'&&tabGroup?.variableKey&&typeof props.value==='string')uiBindings.push({property:'visible',value:{op:'eq',left:{op:'var',key:tabGroup.variableKey},right:{op:'const',value:props.value}},source});
|
|
2153
|
+
for (const handler of uiHandlers.filter(h=>!['onValueChange','onOpenChange','onClose'].includes(h.key)&&!(tabPart==='Root'&&h.key==='onChange'))) {
|
|
1405
2154
|
const event = handler.key === "onClick" ? "click" : htmlTag === "select" || handler.payload.checked ? "change" : "input";
|
|
1406
|
-
|
|
2155
|
+
const writes=handler.writes||[{variableKey:handler.variableKey,value:handler.value}];
|
|
2156
|
+
ui.transitions.push({ transitionKey: identity("ui-transition", { page: pageKey, node: nodeId, event }), routeKey: pageKey, fromStateKey: "pending", toStateKey: "pending", trigger: { nodeId, event, payload: handler.payload }, guard: { op: "const", value: true }, stateReads: [...new Set(writes.flatMap(w=>readVariables(w.value)))].sort(), stateWrites: writes, simulation: "implemented", evidence: "source_inferred", source: handler.source });
|
|
1407
2157
|
}
|
|
1408
|
-
for (const binding of uiBindings.filter(b => ["value", "checked", "disabled", "className"].includes(b.property))) {
|
|
2158
|
+
for (const binding of uiBindings.filter(b => !ctx.__iamCollectionTemplate&&["value", "checked", "disabled", "className", "visible"].includes(b.property)&&!(tabPart==='Root'&&b.property==='value'))) {
|
|
1409
2159
|
if(['value','checked'].includes(binding.property)&&!['input','select','textarea'].includes(htmlTag)){gap('UI_BINDING_TARGET_UNSUPPORTED',m.file,tag+': input property cannot be simulated on a non-input adapter');continue;}
|
|
1410
|
-
const expectedType = ["checked", "disabled"].includes(binding.property) ? "boolean" : "string";
|
|
2160
|
+
const expectedType = ["checked", "disabled", "visible"].includes(binding.property) ? "boolean" : "string";
|
|
1411
2161
|
if (uiExpressionType(binding.value) !== expectedType) {
|
|
1412
2162
|
gap("UI_BINDING_TYPE_UNSUPPORTED", m.file, tag + "." + binding.property + ": source value cannot be represented by the typed UI simulation");
|
|
1413
2163
|
continue;
|
|
1414
2164
|
}
|
|
1415
2165
|
ui.bindings.push({ bindingKey: identity("ui-binding", { node: nodeId, property: binding.property }), routeKey: pageKey, nodeId, elementKey, ...binding });
|
|
1416
2166
|
}
|
|
1417
|
-
const sensitiveInput = htmlTag ===
|
|
2167
|
+
const sensitiveInput = ['input', 'textarea', 'select'].includes(htmlTag) && (htmlTag === 'input' && ["password", "hidden"].includes(String(props.type).toLowerCase()) || sensitiveName(String(props.name || props.id || "")));
|
|
1418
2168
|
if (sensitiveInput) {
|
|
1419
2169
|
delete props.value; delete props.defaultValue;
|
|
2170
|
+
if (htmlTag === 'textarea') children = '';
|
|
1420
2171
|
ui.bindings = ui.bindings.filter(b => b.nodeId !== nodeId);
|
|
1421
2172
|
ui.transitions = ui.transitions.filter(t => t.trigger.nodeId !== nodeId);
|
|
1422
2173
|
gap("SENSITIVE_INPUT_REDACTED", m.file, "Input value and state bridge omitted");
|
|
1423
2174
|
}
|
|
1424
|
-
|
|
2175
|
+
if (['input', 'textarea', 'select'].includes(htmlTag) && !Object.hasOwn(props, 'value') && Object.hasOwn(props, 'defaultValue')) props.value = props.defaultValue;
|
|
2176
|
+
if (htmlTag === 'input' && !Object.hasOwn(props, 'checked') && typeof props.defaultChecked === 'boolean') props.checked = props.defaultChecked;
|
|
2177
|
+
if (htmlTag === 'textarea' && ['string', 'number'].includes(typeof props.value)) children = escape(props.value);
|
|
2178
|
+
const attrs = { "data-iam-node": nodeId,...(['input','textarea','select'].includes(htmlTag)&&Object.hasOwn(props,'value')&&isUnknown(props.value)?{'data-iam-input-unobserved':'true'}:{}) };
|
|
2179
|
+
if(mantine?.overlay){
|
|
2180
|
+
const close=uiHandlers.find(h=>h.key==='onClose');
|
|
2181
|
+
if(close){const closeId=identity('node',{page:pageKey,element:elementKey,instance,mantineClose:true});record.set(closeId,{elementKey,source,synthetic:true});const writes=close.writes||[{variableKey:close.variableKey,value:close.value}];ui.transitions.push({transitionKey:identity('ui-transition',{page:pageKey,node:closeId,event:'click'}),routeKey:pageKey,fromStateKey:'pending',toStateKey:'pending',trigger:{nodeId:closeId,event:'click',payload:{}},guard:{op:'const',value:true},stateReads:[...new Set(writes.flatMap(w=>readVariables(w.value)))].sort(),stateWrites:writes,simulation:'implemented',evidence:'source_inferred',source:close.source});children='<button type="button" aria-label="Close dialog" data-iam-node="'+closeId+'">×</button>'+children;}
|
|
2182
|
+
}
|
|
1425
2183
|
for (const [key, value] of Object.entries(props)) {
|
|
1426
2184
|
if (key === "style") {
|
|
1427
2185
|
const css = staticStyle(value, () => gap("INLINE_STYLE_UNSUPPORTED", m.file,
|
|
@@ -1435,12 +2193,17 @@ export async function analyze({
|
|
|
1435
2193
|
gap("PRIVATE_URL_REDACTED", m.file, "URL credentials or sensitive query omitted");
|
|
1436
2194
|
continue;
|
|
1437
2195
|
}
|
|
1438
|
-
if (["disabled", "hidden", "checked"].includes(key) && value === false) continue;
|
|
2196
|
+
if (["disabled", "hidden", "checked", "required", "readOnly", "multiple", "selected", "open"].includes(key) && value === false) continue;
|
|
1439
2197
|
const attr =
|
|
1440
2198
|
{
|
|
1441
2199
|
className: "class",
|
|
1442
2200
|
htmlFor: "for",
|
|
1443
2201
|
tabIndex: "tabindex",
|
|
2202
|
+
readOnly: "readonly",
|
|
2203
|
+
minLength: "minlength",
|
|
2204
|
+
maxLength: "maxlength",
|
|
2205
|
+
autoComplete: "autocomplete",
|
|
2206
|
+
inputMode: "inputmode",
|
|
1444
2207
|
viewBox: "viewBox",
|
|
1445
2208
|
strokeWidth: "stroke-width",
|
|
1446
2209
|
strokeLinecap: "stroke-linecap",
|
|
@@ -1449,7 +2212,7 @@ export async function analyze({
|
|
|
1449
2212
|
clipRule: "clip-rule",
|
|
1450
2213
|
}[key] || key;
|
|
1451
2214
|
if (
|
|
1452
|
-
!/^(?:class|id|name|type|placeholder|value|title|alt|href|src|role|for|disabled|hidden|checked|width|height|viewBox|d|fill|stroke|stroke-width|stroke-linecap|stroke-linejoin|fill-rule|clip-rule|cx|cy|r|x|y|x1|x2|y1|y2|points|aria-[a-z-]+|data-indiana-id|data-indiana-map-(?:area|flow|kind|auth|edge)|data-testid|data-test|data-qa|data-iam-(?:adapter|toggle))$/.test(
|
|
2215
|
+
!/^(?:class|id|name|type|placeholder|value|title|alt|href|src|role|for|disabled|hidden|checked|required|readonly|multiple|selected|open|start|min|max|step|minlength|maxlength|pattern|autocomplete|inputmode|rows|cols|width|height|viewBox|d|fill|stroke|stroke-width|stroke-linecap|stroke-linejoin|fill-rule|clip-rule|cx|cy|r|x|y|x1|x2|y1|y2|points|aria-[a-z-]+|data-indiana-id|data-indiana-map-(?:area|flow|kind|auth|edge)|data-(?:slot|state|side|variant|collapsible|orientation|active)|data-testid|data-test|data-qa|data-iam-(?:adapter|toggle|tab-value|tab-key|condition|alternative|condition-default|unobserved))$/.test(
|
|
1453
2216
|
attr,
|
|
1454
2217
|
)
|
|
1455
2218
|
)
|
|
@@ -1465,6 +2228,7 @@ export async function analyze({
|
|
|
1465
2228
|
}
|
|
1466
2229
|
attrs["data-iam-external"] = "true";
|
|
1467
2230
|
} else if (attr === "src") {
|
|
2231
|
+
if(!['img','source'].includes(htmlTag)){gap('RESOURCE_HOST_UNSUPPORTED',m.file,tag+': resource attribute omitted from a non-image source adapter');continue;}
|
|
1468
2232
|
if (String(value).startsWith("/") && !String(value).startsWith("//"))
|
|
1469
2233
|
assets.set(String(value), null);
|
|
1470
2234
|
else {
|
|
@@ -1478,7 +2242,7 @@ export async function analyze({
|
|
|
1478
2242
|
.forEach((c) => allClasses.add(c));
|
|
1479
2243
|
attrs[attr] = String(value);
|
|
1480
2244
|
}
|
|
1481
|
-
|
|
2245
|
+
const rendered = (
|
|
1482
2246
|
"<" +
|
|
1483
2247
|
htmlTag +
|
|
1484
2248
|
Object.entries(attrs)
|
|
@@ -1489,6 +2253,12 @@ export async function analyze({
|
|
|
1489
2253
|
? (tag.startsWith("Icons.") ? "◇" : children) + "</" + htmlTag + ">"
|
|
1490
2254
|
: "")
|
|
1491
2255
|
);
|
|
2256
|
+
if (mantine?.label) {
|
|
2257
|
+
const labelId = identity('node', {page: pageKey, element: elementKey, instance, mantineLabel: true});
|
|
2258
|
+
record.set(labelId, {elementKey, source, synthetic: true});
|
|
2259
|
+
return '<label data-iam-node="' + labelId + '" style="display:flex;flex-direction:column;gap:0.35rem">' + '<span>' + escape(mantine.label) + '</span>' + rendered + '</label>';
|
|
2260
|
+
}
|
|
2261
|
+
return rendered;
|
|
1492
2262
|
}
|
|
1493
2263
|
function renderEntry(file, props, instance, symbol) {
|
|
1494
2264
|
const entryModule = modules.get(file), entryRef = entryModule && componentBinding(entryModule, symbol ?? entryModule.defaultName);
|
|
@@ -1503,22 +2273,38 @@ export async function analyze({
|
|
|
1503
2273
|
}
|
|
1504
2274
|
return renderComponent(entryDef, props, instance, 0);
|
|
1505
2275
|
}
|
|
1506
|
-
function renderBranch(branch) {
|
|
1507
|
-
|
|
1508
|
-
|
|
2276
|
+
function renderBranch(branch, inherited = {}) {
|
|
2277
|
+
const contentId=identity('node',{page:pageKey,lane:branch.lane,pageContent:true,...(composition.entryMode==='soft'?{composition:composition.compositionKey}:{})}),contentElement=identity('element',{page:pageKey,lane:branch.lane,pageContent:true});
|
|
2278
|
+
const contentSource=sourceLocation(modules.get(branch.file),modules.get(branch.file).source,branch.symbol??'$page-content');
|
|
2279
|
+
elements.set(contentElement,{elementKey:contentElement,componentKey:def.key,tag:'div',source:contentSource,identityStrategy:'page_root',editableProperties:[],binding:'preview_only'});record.set(contentId,{elementKey:contentElement,source:contentSource,synthetic:true});
|
|
2280
|
+
const contextProps=ctx=>({__iamContexts:ctx.__iamContexts,__iamTabs:ctx.__iamTabs,__iamOverlays:ctx.__iamOverlays});
|
|
2281
|
+
function renderLayer(index,ctx){
|
|
2282
|
+
if(index<0){
|
|
2283
|
+
const content=renderEntry(branch.file,contextProps(ctx),branch===composition.composition?'page':'slot-page:'+branch.file,branch.symbol);
|
|
2284
|
+
return '<div data-iam-node="'+contentId+'" data-iam-page-content="true">'+content+'</div>';
|
|
2285
|
+
}
|
|
2286
|
+
const layer=branch.layers[index],child={iamRenderChildren:next=>renderLayer(index-1,next)};
|
|
1509
2287
|
const props = ["react-router", "tanstack-router"].includes(routing.framework)
|
|
1510
|
-
? { __iamOutlet:
|
|
1511
|
-
: { children:
|
|
1512
|
-
if (/^(?:src\/)?pages\/_app\.[jt]sx?$/.test(layer.file)) { props.Component =
|
|
2288
|
+
? { ...contextProps(ctx), __iamOutlet: child }
|
|
2289
|
+
: { ...contextProps(ctx), children: child };
|
|
2290
|
+
if (/^(?:src\/)?pages\/_app\.[jt]sx?$/.test(layer.file)) { props.Component = child; props.pageProps = {}; }
|
|
1513
2291
|
for (const slot of layer.slots) {
|
|
1514
|
-
props[slot.name] = {
|
|
2292
|
+
props[slot.name] = { iamRenderChildren:next=>renderBranch(slot.branch,next) };
|
|
1515
2293
|
if (composition.entryMode === "hard") gap("SOFT_NAVIGATION_STATE_UNOBSERVED", layer.file, "Slot " + slot.name + ": hard-load composition alone does not establish retained soft-navigation state");
|
|
1516
2294
|
}
|
|
1517
|
-
|
|
2295
|
+
return renderEntry(layer.file, props, "layout:" + path.posix.dirname(layer.file), layer.symbol);
|
|
1518
2296
|
}
|
|
1519
|
-
return
|
|
2297
|
+
return renderLayer(branch.layers.length-1,inherited);
|
|
1520
2298
|
}
|
|
1521
2299
|
let html = composition.gaps.length ? "" : renderBranch(composition.composition);
|
|
2300
|
+
for(const request of attributeRequests){
|
|
2301
|
+
const tabs=presentation.tabs.filter(t=>request.reads.includes(t.variableKey));if(tabs.length!==1||tabs[0].items.length>8)continue;
|
|
2302
|
+
const tab=tabs[0],cases=[];
|
|
2303
|
+
for(const item of tab.items){const next={...request.ctx};for(const [label,key]of Object.entries(next.__iamVariableKeys))if(key===tab.variableKey)next[label]=item.value;for(const [label,expr]of next.__iamPropBindings)if(expr.op==='var'&&expr.key===tab.variableKey)next[label]=item.value;for(const [label,init]of next.__iamLocals)if(!next.__iamStates.has(label))next[label]=evalExpression(init,request.owner.module,next);const value=evalExpression(request.expression,request.owner.module,next);if(typeof value!=='string'||value.length>4000){cases.length=0;break;}cases.push({value:item.value,text:value});if(request.property==='class')value.split(/\s+/).filter(Boolean).forEach(c=>allClasses.add(c));}
|
|
2304
|
+
if(cases.length<2||new Set(cases.map(c=>c.text)).size<2)continue;
|
|
2305
|
+
const set={property:request.property,cases,source:request.source},setKey=identity('ui-attribute-set',set);if(!presentation.attributeSets.some(s=>s.setKey===setKey))presentation.attributeSets.push({setKey,...set});
|
|
2306
|
+
const row={routeKey:pageKey,renderKey:'pending',nodeId:request.nodeId,variableKey:tab.variableKey,setKey};presentation.attributes.push({attributeKey:identity('ui-attribute',row),...row});
|
|
2307
|
+
}
|
|
1522
2308
|
const rootId = identity("node", { page: pageKey, root: true, ...(composition.entryMode === "soft" ? { composition: composition.compositionKey } : {}) }),
|
|
1523
2309
|
rootElement = identity("element", { page: pageKey, root: true });
|
|
1524
2310
|
record.set(rootId, {
|
|
@@ -1636,21 +2422,12 @@ export async function analyze({
|
|
|
1636
2422
|
source: page.source,
|
|
1637
2423
|
documentKey,
|
|
1638
2424
|
});
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
if (!states.some((s) => s.stateKey === key)) {
|
|
1646
|
-
states.push({
|
|
1647
|
-
stateKey: key,
|
|
1648
|
-
pageKey,
|
|
1649
|
-
label: branch.label,
|
|
1650
|
-
evidence: branch.evidence,
|
|
1651
|
-
source: branch.source,
|
|
1652
|
-
documentKey: null,
|
|
1653
|
-
});
|
|
2425
|
+
// Unknown source alternatives live in ui.conditions. Representable local
|
|
2426
|
+
// state candidates remain separate from concrete source documents.
|
|
2427
|
+
for (const branch of branches.filter(b=>b.evidence==='source_inferred')) {
|
|
2428
|
+
const key = identity("state", {page:pageKey,source:branch.source,label:branch.label});
|
|
2429
|
+
if(!states.some(state=>state.stateKey===key)){
|
|
2430
|
+
states.push({stateKey:key,pageKey,label:branch.label,evidence:branch.evidence,source:branch.source,documentKey:null});
|
|
1654
2431
|
page.stateKeys.push(key);
|
|
1655
2432
|
}
|
|
1656
2433
|
}
|
|
@@ -1679,7 +2456,7 @@ export async function analyze({
|
|
|
1679
2456
|
for (const node of doc.nodes) {
|
|
1680
2457
|
if (
|
|
1681
2458
|
node.kind !== "element" ||
|
|
1682
|
-
|
|
2459
|
+
node.tag !== "a"
|
|
1683
2460
|
)
|
|
1684
2461
|
continue;
|
|
1685
2462
|
const href = node.attributes.href || "",
|
|
@@ -1713,9 +2490,18 @@ export async function analyze({
|
|
|
1713
2490
|
});
|
|
1714
2491
|
}
|
|
1715
2492
|
const packagedAssets = [];
|
|
2493
|
+
inventory.push(...await referencedRasterSources(root,assets.keys()));
|
|
1716
2494
|
for (const [url] of assets) {
|
|
1717
2495
|
gap("PUBLIC_ASSET_NOT_PACKAGED", "public", url + ": requires an explicit public-asset build input");
|
|
1718
2496
|
}
|
|
2497
|
+
// Only statically imported styles belong to the source entry's default presentation.
|
|
2498
|
+
const styleFiles=new Map(cssSources.map(s=>[s.file,s])),activeStyles=new Map();
|
|
2499
|
+
const visitStyle=file=>{const style=styleFiles.get(file);if(!style||activeStyles.has(file))return;activeStyles.set(file,style);for(const match of style.text.matchAll(/@import\s+["']([^"']+)["']/g))if(match[1].startsWith('.'))visitStyle(path.posix.normalize(path.posix.join(path.posix.dirname(file),match[1])));};
|
|
2500
|
+
for(const module of selection.discoveryModules.values())for(const statement of module.source.statements)if(ts.isImportDeclaration(statement)&&statement.moduleSpecifier&&ts.isStringLiteral(statement.moduleSpecifier)){
|
|
2501
|
+
const spec=statement.moduleSpecifier.text;if(spec.endsWith('.css')&&spec.startsWith('.'))visitStyle(path.posix.normalize(path.posix.join(path.posix.dirname(module.file),spec)));
|
|
2502
|
+
}
|
|
2503
|
+
const sourceStyles=[...activeStyles.values()],sourceThemes=sourceStyles.flatMap(s=>[...s.text.matchAll(/@theme(?:\s+inline)?\s*\{([^{}]*)\}/g)].map(m=>m[0])).join('\n');
|
|
2504
|
+
const color=t=>sourceStyles.some(s=>new RegExp('--'+t+':\\s*(?:oklch|oklab|rgb|hsl|lab|lch|color|var)\\(|--'+t+':\\s*#').test(s.text))?'var(--'+t+')':'hsl(var(--'+t+'))';
|
|
1719
2505
|
let css = "";
|
|
1720
2506
|
try {
|
|
1721
2507
|
const tw = dependency("tailwindcss");
|
|
@@ -1746,16 +2532,16 @@ export async function analyze({
|
|
|
1746
2532
|
];
|
|
1747
2533
|
const custom =
|
|
1748
2534
|
"@theme inline {" +
|
|
1749
|
-
tokens.map((t) => "--color-" + t + ":
|
|
2535
|
+
tokens.map((t) => "--color-" + t + ":" + color(t) + ";").join("") +
|
|
1750
2536
|
"--font-heading: ui-sans-serif,system-ui;--radius-lg:var(--radius);--radius-md:calc(var(--radius) - 2px);}";
|
|
1751
2537
|
const compile = await tw.value.compile(
|
|
1752
|
-
theme + "\n" + custom + "\n@tailwind utilities;",
|
|
2538
|
+
theme + "\n" + custom + "\n" + sourceThemes + "\n@tailwind utilities;",
|
|
1753
2539
|
);
|
|
1754
2540
|
css = compile.build([...allClasses].sort());
|
|
1755
2541
|
} catch (error) {
|
|
1756
2542
|
gap("STYLE_COMPILER", "", error.message);
|
|
1757
2543
|
}
|
|
1758
|
-
const vars =
|
|
2544
|
+
const vars = sourceStyles
|
|
1759
2545
|
.flatMap((s) =>
|
|
1760
2546
|
[...s.text.matchAll(/(?::root|\.dark)\s*\{([^{}]*)\}/g)].map(
|
|
1761
2547
|
(m) => m[0],
|
|
@@ -1765,13 +2551,16 @@ export async function analyze({
|
|
|
1765
2551
|
css +=
|
|
1766
2552
|
"\n" +
|
|
1767
2553
|
vars +
|
|
1768
|
-
"\n*{box-sizing:border-box;border-color:
|
|
2554
|
+
"\n*{box-sizing:border-box;border-color:"+color("border")+";border-style:solid;border-width:0}[hidden]{display:none!important}body{margin:0;font-family:ui-sans-serif,system-ui,sans-serif;color:"+color("foreground")+";background:"+color("background")+"}h1,h2,h3,p,ul,ol{margin:0}a{color:inherit;text-decoration:none}button,input,textarea,select{font:inherit}button,a{cursor:pointer}button{background:transparent}svg{display:block}img{max-width:100%;height:auto}.container{width:100%;max-width:1400px;padding-inline:2rem;margin-inline:auto}.iam-icon{display:inline-flex;align-items:center;justify-content:center;width:1.1em;height:1.1em}.iam-page{min-height:100vh}.iam-placeholder{padding:1rem;border:1px dashed #d4d4d4}";
|
|
1769
2555
|
const runtime = await fs.readFile(
|
|
1770
2556
|
new URL("../assets/document-runtime.js", import.meta.url),
|
|
1771
2557
|
"utf8",
|
|
1772
2558
|
);
|
|
1773
2559
|
css +=
|
|
1774
2560
|
"\n.iam-state{display:contents}.iam-state[hidden]{display:none!important}";
|
|
2561
|
+
let projectionCss = cssModules.css();
|
|
2562
|
+
if (documents.some(d=>d.html.includes('class="iam-state'))) projectionCss += "\n.iam-state{display:contents}.iam-state[hidden]{display:none!important}";
|
|
2563
|
+
if ([...modules.values()].some(m=>m.mdx)) projectionCss += '\n.iam-markdown{line-height:1.7}.iam-markdown p,.iam-markdown ul,.iam-markdown ol,.iam-markdown pre,.iam-markdown blockquote,.iam-markdown table{margin-block:1em}.iam-markdown h1,.iam-markdown h2,.iam-markdown h3{margin-block:1.2em .5em;font-weight:700;line-height:1.25}.iam-markdown h1{font-size:2em}.iam-markdown h2{font-size:1.5em}.iam-markdown h3{font-size:1.25em}.iam-markdown ul,.iam-markdown ol{padding-left:1.5em}.iam-markdown ul{list-style:disc}.iam-markdown ol{list-style:decimal}.iam-markdown pre{padding:1em;border-radius:.4em;background:#f1f5f9;color:#0f172a;overflow-x:auto;white-space:pre}.iam-markdown code{font-family:ui-monospace,monospace}.iam-markdown blockquote{padding-left:1em;border-left:3px solid #cbd5e1}.iam-markdown table{border-collapse:collapse}.iam-markdown th,.iam-markdown td{border:1px solid #cbd5e1;padding:.4em .7em}.iam-markdown a{text-decoration:underline}';
|
|
1775
2564
|
if (!pages.length)
|
|
1776
2565
|
throw new Error(
|
|
1777
2566
|
"No supported source routes found. Use Next App Router, Next Pages Router or a statically declared React Router route tree.",
|
|
@@ -1781,6 +2570,16 @@ export async function analyze({
|
|
|
1781
2570
|
doc.js = runtime;
|
|
1782
2571
|
sealDocument(doc);
|
|
1783
2572
|
}
|
|
2573
|
+
// Inventory every source file, but serialize UI definitions reachable from
|
|
2574
|
+
// projected routes (including retained alternatives and shared fragments).
|
|
2575
|
+
const liveElements=new Set([...documents.flatMap(d=>d.nodes.map(n=>n.elementKey)),...presentation.fragments.flatMap(f=>f.sourceElements)]);
|
|
2576
|
+
for(const key of elements.keys())if(!liveElements.has(key))elements.delete(key);
|
|
2577
|
+
const liveComponents=new Set([...pages.flatMap(p=>p.componentKeys),...[...elements.values()].map(e=>e.componentKey)]);
|
|
2578
|
+
const compositionFiles=new Set(renderDefinitions.flatMap(d=>flattenComposition(d.composition).flatMap(entry=>[entry.file,...entry.layers.map(layer=>layer.file)])));
|
|
2579
|
+
for(const component of components.values())if(compositionFiles.has(component.file))liveComponents.add(component.componentKey);
|
|
2580
|
+
const retainComponent=key=>{const c=components.get(key);if(!c)return;for(const dep of c.dependencies)if(!liveComponents.has(dep)){liveComponents.add(dep);retainComponent(dep);}};
|
|
2581
|
+
for(const key of [...liveComponents])retainComponent(key);
|
|
2582
|
+
for(const key of components.keys())if(!liveComponents.has(key))components.delete(key);
|
|
1784
2583
|
const sourceGraphDigest = hashObject("iam/source-graph/v1", {
|
|
1785
2584
|
files: inventory,
|
|
1786
2585
|
components: [...components.values()],
|
|
@@ -1790,8 +2589,8 @@ export async function analyze({
|
|
|
1790
2589
|
schemaVersion: 2,
|
|
1791
2590
|
producer: {
|
|
1792
2591
|
name: "indiana-app-manifest-local",
|
|
1793
|
-
version: "0.
|
|
1794
|
-
method: "typescript-ast-and-bounded-static-projection",
|
|
2592
|
+
version: "0.27.0",
|
|
2593
|
+
method: "typescript-ast-and-bounded-static-projection;lucide-static-"+LUCIDE_SOURCE_VERSION,
|
|
1795
2594
|
},
|
|
1796
2595
|
app: { name, platforms: ["web"] },
|
|
1797
2596
|
routing,
|
|
@@ -1824,7 +2623,30 @@ export async function analyze({
|
|
|
1824
2623
|
ui.bindings = ui.bindings.filter(b => concrete.has(b.nodeId));
|
|
1825
2624
|
ui.transitions = ui.transitions.filter(t => concrete.has(t.trigger.nodeId));
|
|
1826
2625
|
for (const doc of documents) for (const node of doc.nodes) if (node.kind === "element" && ["button", "form", "input", "select", "textarea"].includes(node.tag) && !ui.transitions.some(t => t.trigger.nodeId === node.nodeId)) ui.uncovered.push({ routeKey: doc.pageKey, nodeId: node.nodeId, event: "source-event", reason: "No supported UI-only handler; business callbacks are not executed", source: node.source });
|
|
2626
|
+
// State with no representable UI read/write has no preview effect.
|
|
2627
|
+
const usedVariables=new Set([...ui.bindings.flatMap(b=>readVariables(b.value)),...ui.transitions.flatMap(t=>[...t.stateReads,...t.stateWrites.map(w=>w.variableKey)]),...[...presentation.tabs,...presentation.regions,...presentation.collections].map(t=>t.variableKey).filter(Boolean),...presentation.collections.flatMap(c=>c.requires.map(r=>r.variableKey))]);
|
|
2628
|
+
ui.variables=ui.variables.filter(v=>usedVariables.has(v.variableKey));
|
|
2629
|
+
// Repeated instances of the same unsupported source control share one source gap.
|
|
2630
|
+
const sourceGaps=new Map();
|
|
2631
|
+
for(const row of ui.uncovered){const key=JSON.stringify([row.routeKey,row.source,row.event,row.reason]);const prior=sourceGaps.get(key);if(prior)prior.nodeId=null;else sourceGaps.set(key,{...row});}
|
|
2632
|
+
ui.uncovered=[...sourceGaps.values()];
|
|
2633
|
+
attachSourceStyles(manifest, projectionCss);
|
|
1827
2634
|
manifest.routeTree = assembleRouteTree(manifest, ui, renderDefinitions, compositions.navigation);
|
|
2635
|
+
presentation.conditions=[...new Map(presentation.conditions.map(c=>[c.conditionKey,c])).values()];
|
|
2636
|
+
presentation.regions=presentation.regions.filter(r=>concrete.has(r.nodeId)&&r.options.every(o=>concrete.has(o.nodeId)));
|
|
2637
|
+
presentation.tabs=presentation.tabs.filter(t=>concrete.has(t.nodeId)&&t.items.length&&t.items.every(i=>concrete.has(i.nodeId)));
|
|
2638
|
+
presentation.attributes=presentation.attributes.filter(a=>concrete.has(a.nodeId));presentation.attributeSets=presentation.attributeSets.filter(s=>presentation.attributes.some(a=>a.setKey===s.setKey));
|
|
2639
|
+
presentation.navigation=sourceRedirects({ts,modules,binding,functionFrom,externalImport,sourceLocation,constant:(node,module)=>{const result=evalExpression(node,module,{});return isUnknown(result)?undefined:result;},pages,components:[...components.values()],documents});
|
|
2640
|
+
manifest.routeTree=attachUiStructure(manifest.routeTree,presentation,documents);
|
|
2641
|
+
// Discarded adapter children have no concrete UI. Keep every document and
|
|
2642
|
+
// shared-fragment mapping, but do not serialize unreachable element records.
|
|
2643
|
+
const referencedElements=new Set([...documents.flatMap(d=>d.nodes.map(n=>n.elementKey)),...presentation.fragments.flatMap(f=>f.sourceElements)]);
|
|
2644
|
+
manifest.elements=manifest.elements.filter(element=>referencedElements.has(element.elementKey));
|
|
2645
|
+
// Each navigation row references its exact existing interaction. The original
|
|
2646
|
+
// interaction ID remains in the authoritative array, instead of being repeated.
|
|
2647
|
+
const interactionIndices=new Map(manifest.interactions.map((row,index)=>[row.interactionKey,index]));
|
|
2648
|
+
for(const row of manifest.routeTree.navigation)row.interactionKey=interactionIndices.get(row.interactionKey);
|
|
2649
|
+
internGraphSources(manifest.routeTree);
|
|
1828
2650
|
manifest.integrity.routeTreeDigest = routeIndexDigest(manifest.routeTree);
|
|
1829
2651
|
const concreteNodes = new Map(documents.flatMap(doc => doc.nodes.map(node => [node.nodeId, node])));
|
|
1830
2652
|
for (const target of manifest.routeTree.targets) {
|