@tinkerlandai/indiana-iam 0.4.0 → 0.6.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 +104 -10
- package/assets/viewer.js +60 -32
- package/package.json +13 -3
- package/src/analyze.mjs +298 -87
- package/src/anchors.mjs +2 -0
- package/src/build-styles.mjs +14 -9
- package/src/build.mjs +6 -4
- package/src/cli.mjs +8 -1
- package/src/component-registry.mjs +1 -1
- 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 +6 -8
- package/src/generate.mjs +114 -24
- package/src/index.d.ts +16 -4
- package/src/index.mjs +1 -1
- package/src/json.mjs +2 -3
- package/src/library-entry.mjs +7 -3
- package/src/node-hashes.mjs +1 -1
- package/src/react-native-projection.mjs +300 -0
- package/src/react-native-routes.mjs +182 -0
- package/src/react-native.d.ts +47 -0
- package/src/react-native.mjs +45 -0
- package/src/render-index.mjs +8 -8
- package/src/route-composition.mjs +5 -5
- package/src/route-tree.mjs +9 -9
- package/src/routing.mjs +8 -7
- package/src/schema-definition.mjs +14 -16
- package/src/selected-manifest.mjs +12 -0
- package/src/source-aliases.mjs +34 -0
- package/src/source-charts.mjs +15 -0
- package/src/source-collections.mjs +3 -0
- package/src/source-css-modules.mjs +79 -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 +12 -3
- package/src/source-next-redirects.mjs +98 -0
- package/src/source-preview-css.mjs +117 -0
- package/src/source-redirects.mjs +2 -0
- package/src/source-styles.mjs +9 -0
- package/src/static-query.mjs +67 -7
- package/src/svg-preview.mjs +23 -0
- package/src/tanstack-routes.mjs +1 -1
- package/src/ui-preview.mjs +14 -0
- package/src/ui-structure.mjs +9 -19
- package/src/validate.mjs +7 -1
- package/src/viewer-index.mjs +49 -0
package/src/analyze.mjs
CHANGED
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
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';
|
|
3
9
|
import {uiEnvironmentPrimitive} from "./source-environment.mjs";
|
|
4
10
|
import {sourceChart} from "./source-charts.mjs";
|
|
11
|
+
import {flowbiteShape,rootFlowbiteTheme} from "./source-flowbite.mjs";
|
|
5
12
|
import path from "node:path";
|
|
6
13
|
import { automaticSourceAnchors, anchorAttributes, sourceScriptKind } from "./anchors.mjs";
|
|
7
14
|
import { sourceFiles } from "./source-files.mjs";
|
|
@@ -9,6 +16,8 @@ import { dependency } from "./dependencies.mjs";
|
|
|
9
16
|
import { assembleRouteTree, readVariables } from "./route-tree.mjs";
|
|
10
17
|
import { sourceEntry } from "./source-entry.mjs";
|
|
11
18
|
import { frameworkRoutes, externalImport } from "./framework-routes.mjs";
|
|
19
|
+
import { reactNativeRoutes, nativeModuleCandidates } from "./react-native-routes.mjs";
|
|
20
|
+
import { nativeExpression, nativePrimitive } from "./react-native-projection.mjs";
|
|
12
21
|
import { sourceResolution } from "./source-resolution.mjs";
|
|
13
22
|
import { routeIndexDigest, refreshRenderHashes, flattenComposition } from "./render-index.mjs";
|
|
14
23
|
import { storeDocumentNodes } from "./node-hashes.mjs";
|
|
@@ -41,6 +50,7 @@ import { sensitiveName, redactLiteral, privateUrl } from "./privacy.mjs";
|
|
|
41
50
|
const isUnknown = (value) => value === UNKNOWN;
|
|
42
51
|
const COMPONENT=Symbol('source-component');
|
|
43
52
|
const TRANSLATION=Symbol('source-translation-catalog');
|
|
53
|
+
const TRANSLATION_FORMAT=Symbol('source-translation-format');
|
|
44
54
|
const UI_SETTER=Symbol('source-ui-setter');
|
|
45
55
|
const UI_PRIMITIVE=Symbol('source-ui-primitive');
|
|
46
56
|
|
|
@@ -83,11 +93,11 @@ function staticStyle(value, unsupported) {
|
|
|
83
93
|
}).join(";");
|
|
84
94
|
}
|
|
85
95
|
const allowedTags = new Set(
|
|
86
|
-
"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(
|
|
96
|
+
"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(
|
|
87
97
|
" ",
|
|
88
98
|
),
|
|
89
99
|
);
|
|
90
|
-
const voidTags = new Set(["input", "br", "hr", "img", "source"]);
|
|
100
|
+
const voidTags = new Set(["input", "br", "hr", "wbr", "img", "source"]);
|
|
91
101
|
const escape = (text) =>
|
|
92
102
|
redactLiteral(text).replace(
|
|
93
103
|
/[&<>"']/g,
|
|
@@ -187,12 +197,17 @@ export async function analyze({
|
|
|
187
197
|
name,
|
|
188
198
|
repository = "",
|
|
189
199
|
commit = "",
|
|
190
|
-
maxFiles =
|
|
191
|
-
maxBytes =
|
|
200
|
+
maxFiles = Infinity,
|
|
201
|
+
maxBytes = Infinity,
|
|
192
202
|
navigationPaths = [],
|
|
193
203
|
entry,
|
|
194
204
|
automaticAnchors = false,
|
|
205
|
+
platform = "web",
|
|
206
|
+
routerRoot,
|
|
195
207
|
}) {
|
|
208
|
+
if (!["web", "ios", "android"].includes(platform)) throw Error("Unsupported IAM platform");
|
|
209
|
+
const nativePlatform = platform !== "web";
|
|
210
|
+
if (!nativePlatform && routerRoot !== undefined) throw Error("routerRoot is only supported for native Expo projects");
|
|
196
211
|
root = await fs.realpath(root);
|
|
197
212
|
const files = await sourceFiles(root, { maxFiles });
|
|
198
213
|
const modules = new Map(),
|
|
@@ -201,7 +216,7 @@ export async function analyze({
|
|
|
201
216
|
elements = new Map(),
|
|
202
217
|
components = new Map(),
|
|
203
218
|
assets = new Map();
|
|
204
|
-
const cssSources = [];
|
|
219
|
+
const cssSources = [], cssModuleFiles = new Map();
|
|
205
220
|
const jsonResources=new Map(),translationResources=new Set();
|
|
206
221
|
const presentation={conditions:[],tabs:[],navigation:[],regions:[],collections:[],fragments:[],attributes:[],attributeSets:[]},collectionTemplates=new Map();
|
|
207
222
|
const ui = { variables: [], bindings: [], transitions: [], uncovered: [] };
|
|
@@ -210,7 +225,6 @@ export async function analyze({
|
|
|
210
225
|
function gap(code, file, detail) {
|
|
211
226
|
detail = redactLiteral(detail);
|
|
212
227
|
if (
|
|
213
|
-
gaps.length < 1000 &&
|
|
214
228
|
!gaps.some(
|
|
215
229
|
(x) => x.code === code && x.file === file && x.detail === detail,
|
|
216
230
|
)
|
|
@@ -224,10 +238,11 @@ export async function analyze({
|
|
|
224
238
|
const real = await fs.realpath(absolute);
|
|
225
239
|
if (!real.startsWith(root + path.sep))
|
|
226
240
|
throw new Error("Source path escapes root");
|
|
227
|
-
if (
|
|
241
|
+
if ((byteCount += stat.size) > maxBytes)
|
|
228
242
|
throw new Error("Source byte budget exceeded");
|
|
229
243
|
const bytes = await fs.readFile(absolute);
|
|
230
|
-
const
|
|
244
|
+
const originalText = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
245
|
+
let text = originalText;
|
|
231
246
|
inventory.push({
|
|
232
247
|
file,
|
|
233
248
|
sha256: sha256(bytes),
|
|
@@ -235,33 +250,32 @@ export async function analyze({
|
|
|
235
250
|
});
|
|
236
251
|
if(file.endsWith('.json')){try{jsonResources.set(file,JSON.parse(text));}catch{/* Non-resource JSON stays opaque. */}continue;}
|
|
237
252
|
if (file.endsWith(".css")) {
|
|
253
|
+
if (file.endsWith('.module.css')) { cssModuleFiles.set(file, text); continue; }
|
|
238
254
|
gap(
|
|
239
|
-
|
|
240
|
-
? "CSS_MODULES_UNSUPPORTED"
|
|
241
|
-
: "STYLE_SOURCE_PARTIAL",
|
|
255
|
+
"STYLE_SOURCE_PARTIAL",
|
|
242
256
|
file,
|
|
243
257
|
"Only root/dark tokens and pinned utility rules are reconstructed; scoped/custom selectors, keyframes and original font rules are not compiled",
|
|
244
258
|
);
|
|
245
259
|
if (!file.endsWith(".module.css")) cssSources.push({ file, text });
|
|
246
260
|
continue;
|
|
247
261
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
continue;
|
|
252
|
-
}
|
|
262
|
+
const mdx = /\.mdx?$/.test(file) ? sourceMdx(file, originalText, gap) : null;
|
|
263
|
+
if (mdx) text = mdx.text;
|
|
264
|
+
if (!mdx && !/\.[cm]?[jt]sx?$/.test(file)) continue;
|
|
253
265
|
const source = ts.createSourceFile(
|
|
254
266
|
file,
|
|
255
267
|
text,
|
|
256
268
|
ts.ScriptTarget.Latest,
|
|
257
269
|
true,
|
|
258
|
-
sourceScriptKind(file, ts),
|
|
270
|
+
mdx ? ts.ScriptKind.TSX : sourceScriptKind(file, ts),
|
|
259
271
|
);
|
|
260
272
|
const module = {
|
|
261
273
|
file,
|
|
262
274
|
text,
|
|
263
275
|
source,
|
|
264
|
-
anchors: automaticAnchors ? automaticSourceAnchors(file, source) : new Map(),
|
|
276
|
+
anchors: automaticAnchors && !nativePlatform && !mdx ? automaticSourceAnchors(file, source) : new Map(),
|
|
277
|
+
mdx,
|
|
278
|
+
originalSource: mdx ? ts.createSourceFile(file, originalText, ts.ScriptTarget.Latest, false) : source,
|
|
265
279
|
bindings: new Map(),
|
|
266
280
|
imports: new Map(),
|
|
267
281
|
exports: new Map(),
|
|
@@ -331,16 +345,19 @@ export async function analyze({
|
|
|
331
345
|
module.exports.set(e.name.text,local);if(e.name.text==='default')module.defaultName=local;
|
|
332
346
|
}
|
|
333
347
|
}
|
|
348
|
+
if (/^(?:src\/)?pages\//.test(file)) {
|
|
349
|
+
for (const statement of source.statements) {
|
|
350
|
+
const assignment = ts.isExpressionStatement(statement) && unwrap(statement.expression);
|
|
351
|
+
if (assignment && ts.isBinaryExpression(assignment) && assignment.operatorToken.kind === ts.SyntaxKind.EqualsToken && ts.isPropertyAccessExpression(assignment.left) && assignment.left.name.text === 'getLayout' && ts.isIdentifier(assignment.left.expression) && assignment.left.expression.text === module.defaultName) {
|
|
352
|
+
const fn = functionFrom(assignment.right);
|
|
353
|
+
if (fn && fn.parameters.length === 1 && ts.isIdentifier(fn.parameters[0].name) && !fn.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword)) module.bindings.set('$pageLayout', fn);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
334
357
|
modules.set(file, module);
|
|
335
358
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const config = ts.parseConfigFileTextToJson(
|
|
339
|
-
"tsconfig.json",
|
|
340
|
-
await fs.readFile(path.join(root, "tsconfig.json"), "utf8"),
|
|
341
|
-
).config;
|
|
342
|
-
alias = config.compilerOptions?.paths || alias;
|
|
343
|
-
} catch { /* Source projects may omit tsconfig.json. */ }
|
|
359
|
+
const alias = await sourceAliases(root,ts);
|
|
360
|
+
const cssModules = sourceCssModules({files: cssModuleFiles, aliases: alias, gap, redact: redactLiteral});
|
|
344
361
|
function resolveModule(from, specifier) {
|
|
345
362
|
let relative;
|
|
346
363
|
if (specifier.startsWith("."))
|
|
@@ -359,7 +376,7 @@ export async function analyze({
|
|
|
359
376
|
}
|
|
360
377
|
if (!relative || relative.startsWith("../")) return null;
|
|
361
378
|
return (
|
|
362
|
-
[
|
|
379
|
+
(nativePlatform ? nativeModuleCandidates(relative, platform) : [
|
|
363
380
|
relative,
|
|
364
381
|
...[
|
|
365
382
|
".tsx",
|
|
@@ -367,11 +384,13 @@ export async function analyze({
|
|
|
367
384
|
".jsx",
|
|
368
385
|
".js",
|
|
369
386
|
".mjs",
|
|
387
|
+
".mdx",
|
|
388
|
+
".md",
|
|
370
389
|
"/index.tsx",
|
|
371
390
|
"/index.ts",
|
|
372
391
|
"/index.jsx",
|
|
373
392
|
].map((ext) => relative + ext),
|
|
374
|
-
]
|
|
393
|
+
])
|
|
375
394
|
.map((p) => modules.get(p))
|
|
376
395
|
.find(Boolean) || null
|
|
377
396
|
);
|
|
@@ -388,6 +407,7 @@ export async function analyze({
|
|
|
388
407
|
}
|
|
389
408
|
function sourceContextCall(node,module,ctx){
|
|
390
409
|
const imported=externalImport(module,node.expression.getText(module.source));
|
|
410
|
+
if(['react-i18next','next-i18next'].includes(imported?.specifier)&&imported.name==='useTranslation')return {t:translationHook(node,module,ctx)};
|
|
391
411
|
if(imported?.specifier==='react'&&imported.name==='useContext'&&ts.isIdentifier(node.arguments[0]))return ctx.__iamContexts?.get(contextKey(module,node.arguments[0].text))??UNKNOWN;
|
|
392
412
|
const ref=ts.isIdentifier(node.expression)&&binding(module,node.expression.text),fn=ref&&functionFrom(ref.node);
|
|
393
413
|
if(!fn?.body||!ts.isBlock(fn.body))return UNKNOWN;
|
|
@@ -399,14 +419,52 @@ export async function analyze({
|
|
|
399
419
|
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)){
|
|
400
420
|
const catalogs=[...ref.module.imports.values()].filter(i=>i.specifier.endsWith('.json')).map(i=>jsonResource(ref.module.file,i.specifier)).filter(Boolean);
|
|
401
421
|
if(catalogs.length===1){translationResources.add(catalogs[0]);return {[TRANSLATION]:catalogs[0]};}
|
|
422
|
+
const locals={...ctx};fn.parameters.forEach((p,i)=>{if(ts.isIdentifier(p.name))locals[p.name.text]=evalExpression(node.arguments[i],module,ctx);});
|
|
423
|
+
return translationHook(call,ref.module,locals);
|
|
402
424
|
}
|
|
403
425
|
}
|
|
404
426
|
return UNKNOWN;
|
|
405
427
|
}
|
|
428
|
+
let i18nextPreview;
|
|
429
|
+
function translationHook(node,module,ctx){
|
|
430
|
+
if(externalImport(module,node.expression.getText(module.source))?.specifier==='next-i18next') {
|
|
431
|
+
const config=[...modules.values()].find(m=>/^next-i18next\.config\.[cm]?[jt]s$/.test(m.file));
|
|
432
|
+
const statement=config?.source.statements.find(s=>ts.isExpressionStatement(s)&&ts.isBinaryExpression(s.expression)&&s.expression.left.getText(config.source)==='module.exports');
|
|
433
|
+
const options=statement&&evalExpression(statement.expression.right,config,{}),language=options?.i18n?.defaultLocale;
|
|
434
|
+
const namespace=node.arguments[0]?evalExpression(node.arguments[0],module,ctx):options?.defaultNS??'common';
|
|
435
|
+
const catalog=typeof language==='string'&&typeof namespace==='string'?jsonResources.get('public/locales/'+language+'/'+namespace+'.json'):null;
|
|
436
|
+
if(catalog&&typeof catalog==='object') {translationResources.add(catalog);gap('UI_LOCALE_FALLBACK_PREVIEW',config.file,'Read-only preview uses source defaultLocale '+language+'; request locale and remote translations are unobserved.');return {[TRANSLATION]:catalog};}
|
|
437
|
+
return UNKNOWN;
|
|
438
|
+
}
|
|
439
|
+
i18nextPreview??=sourceI18next({ts,modules,binding,imported:externalImport,jsonResource})??false;
|
|
440
|
+
if(!i18nextPreview)return UNKNOWN;
|
|
441
|
+
const namespace=node.arguments[0]?evalExpression(node.arguments[0],module,ctx):i18nextPreview.namespace;
|
|
442
|
+
const options=node.arguments[1]?evalExpression(node.arguments[1],module,ctx):{};
|
|
443
|
+
if(typeof namespace!=='string'||isUnknown(options)||options?.keyPrefix!==undefined&&typeof options.keyPrefix!=='string')return UNKNOWN;
|
|
444
|
+
let catalog=i18nextPreview.resources[i18nextPreview.language]?.[namespace];
|
|
445
|
+
if(typeof options?.keyPrefix==='string')catalog=options.keyPrefix.split('.').reduce((v,k)=>v&&Object.hasOwn(v,k)?v[k]:undefined,catalog);
|
|
446
|
+
if(!catalog||typeof catalog!=='object')return UNKNOWN;
|
|
447
|
+
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.');
|
|
448
|
+
translationResources.add(catalog);return {[TRANSLATION]:catalog,[TRANSLATION_FORMAT]:{prefix:i18nextPreview.prefix,suffix:i18nextPreview.suffix}};
|
|
449
|
+
}
|
|
406
450
|
function basePrimitive(module,tag,seen=new Set()){
|
|
407
451
|
if(seen.has(module.file+'#'+tag)||seen.size>8)return null;seen.add(module.file+'#'+tag);
|
|
408
452
|
const [name,part,extra]=tag.split('.'),ref=module.imports.get(name);
|
|
409
453
|
const imported=externalImport(module,tag);
|
|
454
|
+
if (ref?.specifier === 'vaul' && ref.name === 'Drawer' && !extra) {
|
|
455
|
+
const mapped = {Root:'Root',Portal:'Portal',Overlay:'Backdrop',Content:'Popup',Title:'Title',Description:'Description',Trigger:'Trigger',Close:'Close'}[part];
|
|
456
|
+
if (mapped) return {family:'Dialog',part:mapped};
|
|
457
|
+
}
|
|
458
|
+
if (ref?.specifier === '@mantine/core' && ref.name === 'Tabs' && !extra) return {family:'Tabs',part:part ?? 'Root',mantine:true};
|
|
459
|
+
if(imported?.specifier==='@headlessui/react'){
|
|
460
|
+
const part={Dialog:'Root',DialogPanel:'Popup',DialogBackdrop:'Backdrop',DialogTitle:'Title',Description:'Description'}[imported.name];
|
|
461
|
+
if(part)return {family:'Dialog',part,rootVisibility:part==='Root'};
|
|
462
|
+
}
|
|
463
|
+
if(imported?.specifier==='react-aria-components'){
|
|
464
|
+
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];
|
|
465
|
+
if(part)return {family:'AriaDialog',part,...(imported.name==='Menu'?{role:'menu'}:['Modal','Popover'].includes(imported.name)?{role:null}:{})};
|
|
466
|
+
}
|
|
467
|
+
if(ref?.specifier==='cmdk'&&ref.name==='Command'&&part==='Dialog'&&!extra)return {family:'Dialog',part:'Root',rootVisibility:true};
|
|
410
468
|
const radixFamilies={'dialog':'Dialog','alert-dialog':'AlertDialog','popover':'Popover','dropdown-menu':'Menu','context-menu':'Menu','tooltip':'Tooltip','hover-card':'Popover','tabs':'Tabs','collapsible':'Collapsible'};
|
|
411
469
|
const radix=imported?.specifier?.match(/^@radix-ui\/react-([a-z-]+)$/)?.[1];
|
|
412
470
|
if(radixFamilies[radix]) {
|
|
@@ -532,8 +590,9 @@ export async function analyze({
|
|
|
532
590
|
return target;
|
|
533
591
|
}
|
|
534
592
|
function sourceLocation(module, node, symbol) {
|
|
535
|
-
const
|
|
536
|
-
|
|
593
|
+
const generated = node.getStart(module.source);
|
|
594
|
+
const loc = module.originalSource.getLineAndCharacterOfPosition(
|
|
595
|
+
module.mdx ? module.mdx.position(generated) : generated,
|
|
537
596
|
);
|
|
538
597
|
return {
|
|
539
598
|
file: module.file,
|
|
@@ -544,16 +603,21 @@ export async function analyze({
|
|
|
544
603
|
};
|
|
545
604
|
}
|
|
546
605
|
function sourceComponent(node,module){
|
|
606
|
+
const tabler=ts.isIdentifier(node)&&externalImport(module,node.text);
|
|
607
|
+
if(tabler?.specifier==='@tabler/icons-react'&&/^Icon[A-Z]/.test(tabler.name)){
|
|
608
|
+
const icon=lucideSource(tabler.name.slice(4));
|
|
609
|
+
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;}
|
|
610
|
+
}
|
|
547
611
|
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;}
|
|
548
612
|
const ref=ts.isIdentifier(node)?binding(module,node.text):null;const def=componentsByFunction.get(node)??(ref&&componentDefs.get(ref.module.file+'#'+ref.name));
|
|
549
613
|
return def?{[COMPONENT]:def}:null;
|
|
550
614
|
}
|
|
551
|
-
function environment(node,module,
|
|
615
|
+
function environment(node,module,_context){
|
|
552
616
|
const value=uiEnvironmentPrimitive({ts,node,module,binding,imported:externalImport,evaluate:evalExpression});
|
|
553
617
|
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];}
|
|
554
618
|
return value;
|
|
555
619
|
}
|
|
556
|
-
function query(node,module,context){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,project:(n,m,c)=>{const v=environment(n,m,c);return Array.isArray(v)?v:null;}});}
|
|
620
|
+
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;}});}
|
|
557
621
|
function evalExpression(
|
|
558
622
|
node,
|
|
559
623
|
module,
|
|
@@ -564,6 +628,14 @@ export async function analyze({
|
|
|
564
628
|
node = unwrap(node);
|
|
565
629
|
if (!node || depth > 30) return UNKNOWN;
|
|
566
630
|
const ev = (n) => evalExpression(n, module, context, stack, depth + 1);
|
|
631
|
+
if (nativePlatform) {
|
|
632
|
+
const result = nativeExpression(node, module, { ts, platform, evaluate: ev, unknown: UNKNOWN,
|
|
633
|
+
shadowed: name => Object.hasOwn(context, name),
|
|
634
|
+
gap: detail => gap("RN_CAPABILITY_UNOBSERVED", module.file, detail),
|
|
635
|
+
});
|
|
636
|
+
if (result) return result.value;
|
|
637
|
+
}
|
|
638
|
+
if (ts.isAwaitExpression(node)) return query(node, module, context, true);
|
|
567
639
|
const component=!(ts.isIdentifier(node)&&Object.hasOwn(context,node.text))&&sourceComponent(node,module);if(component)return component;
|
|
568
640
|
if (ts.isStringLiteralLike(node) || ts.isNumericLiteral(node))
|
|
569
641
|
return ts.isNumericLiteral(node) ? Number(node.text) : redactLiteral(node.text);
|
|
@@ -572,12 +644,16 @@ export async function analyze({
|
|
|
572
644
|
if (node.kind === ts.SyntaxKind.NullKeyword) return null;
|
|
573
645
|
if (ts.isIdentifier(node)) {
|
|
574
646
|
if (Object.hasOwn(context, node.text)) return context[node.text];
|
|
647
|
+
const cssModule = cssModules.resolve(module, module.imports.get(node.text));
|
|
648
|
+
if (cssModule) return cssModule;
|
|
575
649
|
const variableKey=context.__iamVariableKeys?.[context.__iamSetters?.[node.text]];
|
|
576
650
|
if(variableKey)return {[UI_SETTER]:{variableKey,value:{op:'event',key:'value'},payload:{value:ui.variables.find(v=>v.variableKey===variableKey)?.valueType}}};
|
|
577
651
|
if (node.text === "undefined") return undefined;
|
|
578
652
|
const image=module.imports.get(node.text);
|
|
579
|
-
if(image?.name==='default'
|
|
580
|
-
|
|
653
|
+
if(image?.name==='default'&&/^[^?#]+\.(?:png|jpe?g|webp|gif|svg)$/i.test(image.specifier)){
|
|
654
|
+
let file=image.specifier.startsWith('.')?path.posix.normalize(path.posix.join(path.posix.dirname(module.file),image.specifier)):null;
|
|
655
|
+
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;}}
|
|
656
|
+
if(!file)return UNKNOWN;
|
|
581
657
|
if(!file.startsWith('../')&&!file.split('/').some(p=>p.startsWith('.'))&&!sensitiveName(file))return '/__iam_source__/'+file;
|
|
582
658
|
}
|
|
583
659
|
const token = module.file + "#" + node.text;
|
|
@@ -690,7 +766,13 @@ export async function analyze({
|
|
|
690
766
|
const primitive=externalImport(module,callee);
|
|
691
767
|
if(['react-router','react-router-dom'].includes(primitive?.specifier)&&primitive.name==='useLocation'&&typeof context.__iamRoutePath==='string')return {pathname:context.__iamRoutePath,search:UNKNOWN,hash:UNKNOWN};
|
|
692
768
|
const translation=ts.isIdentifier(node.expression)&&context[node.expression.text]?.[TRANSLATION];
|
|
693
|
-
if(translation){
|
|
769
|
+
if(translation){
|
|
770
|
+
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;
|
|
771
|
+
const format=context[node.expression.text][TRANSLATION_FORMAT]??{prefix:'{{',suffix:'}}'},options=node.arguments[1]?ev(node.arguments[1]):{};
|
|
772
|
+
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;
|
|
773
|
+
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 '';});
|
|
774
|
+
return !missing&&!/[{}]/.test(text)&&text.length<=4000?redactLiteral(text):UNKNOWN;
|
|
775
|
+
}
|
|
694
776
|
if(primitive?.specifier==='react'&&primitive.name==='useMemo'&&node.arguments.length===2){
|
|
695
777
|
const fn=node.arguments[0];if(ts.isArrowFunction(fn)&&!fn.modifiers?.length&&!fn.parameters.length&&!ts.isBlock(fn.body))return ev(fn.body);
|
|
696
778
|
}
|
|
@@ -745,6 +827,7 @@ export async function analyze({
|
|
|
745
827
|
args = node.arguments.map(ev),
|
|
746
828
|
method = node.expression.name.text;
|
|
747
829
|
if (isUnknown(target) || args.some(isUnknown)) return query(node,module,context);
|
|
830
|
+
if (typeof target === 'string' && method === 'toString' && !args.length) return target;
|
|
748
831
|
if (
|
|
749
832
|
typeof target === "string" &&
|
|
750
833
|
[
|
|
@@ -806,10 +889,10 @@ export async function analyze({
|
|
|
806
889
|
}
|
|
807
890
|
const forwarded = new WeakMap(), objectBindings = new WeakMap();
|
|
808
891
|
function componentContext(def, props) {
|
|
809
|
-
const ctx = { ...props, __iamSetters: {}, __iamStates: new Set(), __iamStateNodes: {}, __iamLocals: new Map(), __iamPropBindings: new Map(), __iamPropHandlers: new Map() };
|
|
892
|
+
const ctx = { ...props, __iamSetters: {}, __iamStates: new Set(), __iamStateNodes: {}, __iamLocals: new Map(), __iamQueryLocals: new Map(), __iamPropBindings: new Map(), __iamPropHandlers: new Map(), __iamDisclosures: [] };
|
|
810
893
|
const param = def.fn?.parameters?.[0];
|
|
811
894
|
if (param) {
|
|
812
|
-
if (ts.isIdentifier(param.name)) ctx[param.name.text] = props;
|
|
895
|
+
if (ts.isIdentifier(param.name)) ctx[param.name.text] = def.symbol === '$pageLayout' ? props.children : props;
|
|
813
896
|
else if (ts.isObjectBindingPattern(param.name))
|
|
814
897
|
for (const el of param.name.elements) {
|
|
815
898
|
if (el.dotDotDotToken && ts.isIdentifier(el.name)) {
|
|
@@ -830,6 +913,7 @@ export async function analyze({
|
|
|
830
913
|
if (handler) ctx.__iamPropHandlers.set(el.name.getText(), handler);
|
|
831
914
|
}
|
|
832
915
|
}
|
|
916
|
+
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);
|
|
833
917
|
if (def.fn?.body&&ts.isBlock(def.fn.body))
|
|
834
918
|
for (const s of def.fn.body.statements)
|
|
835
919
|
if (ts.isVariableStatement(s))
|
|
@@ -846,6 +930,7 @@ export async function analyze({
|
|
|
846
930
|
for(const field of d.name.elements)if(ts.isIdentifier(field.name)&&!field.dotDotDotToken){
|
|
847
931
|
const key=field.propertyName?.text||field.name.text;
|
|
848
932
|
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;
|
|
933
|
+
ctx.__iamQueryLocals.set(field.name.text,d.initializer);
|
|
849
934
|
const inherited=value&&typeof value==='object'&&objectBindings.get(value)?.get(key);
|
|
850
935
|
if(inherited)ctx.__iamPropBindings.set(field.name.text,inherited);
|
|
851
936
|
const setter=value?.[key]?.[UI_SETTER];
|
|
@@ -853,6 +938,18 @@ export async function analyze({
|
|
|
853
938
|
}
|
|
854
939
|
} else if (ts.isArrayBindingPattern(d.name)) {
|
|
855
940
|
const call = unwrap(d.initializer), hook = call && ts.isCallExpression(call) && ts.isIdentifier(call.expression) && def.module.imports.get(call.expression.text);
|
|
941
|
+
if (hook?.specifier === '@mantine/hooks' && hook.name === 'useDisclosure') {
|
|
942
|
+
const state = d.name.elements[0], handlers = d.name.elements[1]?.name;
|
|
943
|
+
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;}
|
|
944
|
+
const label = state.name.text;
|
|
945
|
+
ctx[label] = call.arguments.length ? evalExpression(call.arguments[0], def.module, ctx) : false;
|
|
946
|
+
ctx.__iamStates.add(label); ctx.__iamStateNodes[label] = d;
|
|
947
|
+
if (handlers && ts.isObjectBindingPattern(handlers)) {
|
|
948
|
+
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});
|
|
949
|
+
} else if (handlers && ts.isIdentifier(handlers)) for (const op of ['open','close','toggle']) ctx.__iamDisclosures.push({label,name:handlers.text+'.'+op,op});
|
|
950
|
+
gap('DISCLOSURE_SOURCE_PREVIEW',def.module.file,'Local boolean disclosure state only; hook callbacks and browser effects are not executed.');
|
|
951
|
+
continue;
|
|
952
|
+
}
|
|
856
953
|
if (hook?.specifier === "react" && hook.name === "useActionState") {
|
|
857
954
|
const key = d.name.elements[0]?.name?.text, pending = d.name.elements[2]?.name?.text, initial = evalExpression(call.arguments[1], def.module, ctx);
|
|
858
955
|
if (key && !sensitiveName(key) && initial && !isUnknown(initial) && typeof initial === "object" && !Array.isArray(initial)) {
|
|
@@ -902,7 +999,7 @@ export async function analyze({
|
|
|
902
999
|
module.file,
|
|
903
1000
|
"Middleware/proxy may change public routes; not executed by source analysis",
|
|
904
1001
|
);
|
|
905
|
-
if (!/^next\.config\.[cm]?[jt]s$/.test(module.file)) continue;
|
|
1002
|
+
if (nativePlatform || !/^next\.config\.[cm]?[jt]s$/.test(module.file)) continue;
|
|
906
1003
|
const found = new Set();
|
|
907
1004
|
function readConfig(node) {
|
|
908
1005
|
const key = node.name?.text;
|
|
@@ -940,10 +1037,18 @@ export async function analyze({
|
|
|
940
1037
|
!/^\/(?:[^/?#[\]\\\s]+\/)*[^/?#[\]\\\s]+$/.test(routing.basePath)
|
|
941
1038
|
)
|
|
942
1039
|
throw new Error("Invalid source basePath");
|
|
943
|
-
const selection = await sourceEntry({ root, modules, resolveModule, ts, entry });
|
|
1040
|
+
const selection = nativePlatform ? { discoveryModules: modules } : await sourceEntry({ root, modules, resolveModule, ts, entry });
|
|
1041
|
+
if (nativePlatform) {
|
|
1042
|
+
gap("RN_SOURCE_PREVIEW", "", "Inert HTML projection of React Native source. Native Yoga layout, device density, accessibility, gestures and navigation stacks are not observed; preview DOM locators never authorize native visual changes.");
|
|
1043
|
+
gap("RN_METRO_CONFIG_UNOBSERVED", "", "Uses default Metro source extensions; custom resolvers, Babel plugins and Metro configuration are not executed.");
|
|
1044
|
+
}
|
|
1045
|
+
const flowbiteTheme=rootFlowbiteTheme({ts,modules:selection.discoveryModules,imported:externalImport,evaluate:evalExpression});
|
|
944
1046
|
if (selection.htmlSource) inventory.push(selection.htmlSource);
|
|
945
1047
|
inventory.sort((a, b) => a.file < b.file ? -1 : a.file > b.file ? 1 : 0);
|
|
946
|
-
|
|
1048
|
+
if (nativePlatform && navigationPaths.length) throw Error("Retained Web navigation paths do not describe a native back stack");
|
|
1049
|
+
const compositions = nativePlatform
|
|
1050
|
+
? reactNativeRoutes({ modules, platform, entry, routerRoot, binding, sourceLocation, gap })
|
|
1051
|
+
: frameworkRoutes({ modules, ...selection, resolveModule, ts, sourceLocation, navigationPaths, gap, resolution, inlineComponent });
|
|
947
1052
|
const pageFiles = compositions.pageFiles;
|
|
948
1053
|
routing.framework = compositions.framework;
|
|
949
1054
|
if (compositions.routeSources) routing.routeSources = compositions.routeSources;
|
|
@@ -999,7 +1104,6 @@ export async function analyze({
|
|
|
999
1104
|
const record = new Map(),
|
|
1000
1105
|
used = new Set(),
|
|
1001
1106
|
branches = [],attributeRequests=[];
|
|
1002
|
-
let renderedCount = 0;
|
|
1003
1107
|
function renderComponent(def, props, instance, depth) {
|
|
1004
1108
|
if (depth === 0 && composition.entryMode === "soft") instance = composition.compositionKey + ":" + instance;
|
|
1005
1109
|
// Component recursion and JSX nesting are separate budgets. Counting DOM
|
|
@@ -1011,7 +1115,7 @@ export async function analyze({
|
|
|
1011
1115
|
used.add(def.key + ":" + instance);
|
|
1012
1116
|
if (!page.componentKeys.includes(def.key))
|
|
1013
1117
|
page.componentKeys.push(def.key);
|
|
1014
|
-
props.__iamRoutePath=/[
|
|
1118
|
+
props.__iamRoutePath=/[[\]]/.test(route)?UNKNOWN:route;
|
|
1015
1119
|
const ctx = componentContext(def, props);
|
|
1016
1120
|
ctx.__iamOutlet = props.__iamOutlet;
|
|
1017
1121
|
ctx.__iamScope = identity("state-scope", {
|
|
@@ -1039,6 +1143,10 @@ export async function analyze({
|
|
|
1039
1143
|
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) });
|
|
1040
1144
|
}
|
|
1041
1145
|
}
|
|
1146
|
+
for (const disclosure of ctx.__iamDisclosures) {
|
|
1147
|
+
const variableKey=ctx.__iamVariableKeys[disclosure.label];if(!variableKey)continue;
|
|
1148
|
+
ctx.__iamPropHandlers.set(disclosure.name,{variableKey,value:disclosure.op==='toggle'?{op:'not',arg:{op:'var',key:variableKey}}:{op:'const',value:disclosure.op==='open'},payload:{}});
|
|
1149
|
+
}
|
|
1042
1150
|
// Rebind pure local records after scalar state identities exist. This preserves
|
|
1043
1151
|
// useMemo/context field relationships without invoking a hook or callback.
|
|
1044
1152
|
for(const [name,expression]of ctx.__iamLocals){
|
|
@@ -1126,9 +1234,10 @@ export async function analyze({
|
|
|
1126
1234
|
}
|
|
1127
1235
|
function uiHandler(initializer, owner, ctx, property = '') {
|
|
1128
1236
|
let fn = initializer && ts.isJsxExpression(initializer) ? unwrap(initializer.expression) : null;
|
|
1237
|
+
if(fn&&ts.isPropertyAccessExpression(fn)&&ctx.__iamPropHandlers.has(fn.getText(owner.module.source)))return ctx.__iamPropHandlers.get(fn.getText(owner.module.source));
|
|
1129
1238
|
const eventValue = (variableKey) => ({ variableKey, value: {op:'event',key:'value'}, payload:{value:ui.variables.find(v=>v.variableKey===variableKey)?.valueType} });
|
|
1130
1239
|
if(fn&&ts.isIdentifier(fn)&&ctx.__iamPropHandlers.has(fn.text))return ctx.__iamPropHandlers.get(fn.text);
|
|
1131
|
-
if(fn&&ts.isIdentifier(fn)&&ctx.__iamSetters[fn.text]&&['onValueChange','onOpenChange'].includes(property)) {
|
|
1240
|
+
if(fn&&ts.isIdentifier(fn)&&ctx.__iamSetters[fn.text]&&['onValueChange','onOpenChange',...(nativePlatform?['onChangeText']:[])].includes(property)) {
|
|
1132
1241
|
const key=ctx.__iamVariableKeys[ctx.__iamSetters[fn.text]];return key?eventValue(key):null;
|
|
1133
1242
|
}
|
|
1134
1243
|
if(fn&&ts.isIdentifier(fn)&&ctx.__iamLocals.has(fn.text))fn=unwrap(ctx.__iamLocals.get(fn.text));
|
|
@@ -1136,7 +1245,7 @@ export async function analyze({
|
|
|
1136
1245
|
const props=ctx[fn.expression.text],handler=props&&typeof props==='object'&&forwarded.get(props)?.handlers.find(h=>h.key===fn.name.text);
|
|
1137
1246
|
if(handler)return handler;
|
|
1138
1247
|
}
|
|
1139
|
-
if(!fn||!ts.isArrowFunction(fn)||fn.modifiers?.some(m=>m.kind===ts.SyntaxKind.AsyncKeyword))return null;
|
|
1248
|
+
if(!fn||![ts.isArrowFunction,ts.isFunctionExpression,ts.isFunctionDeclaration].some(is=>is(fn))||fn.modifiers?.some(m=>m.kind===ts.SyntaxKind.AsyncKeyword))return null;
|
|
1140
1249
|
let statements=ts.isBlock(fn.body)?[...fn.body.statements]:[fn.body];
|
|
1141
1250
|
if(statements.length>16)return null;
|
|
1142
1251
|
const writes=[],payload={};
|
|
@@ -1158,7 +1267,7 @@ export async function analyze({
|
|
|
1158
1267
|
if(!arg)return null;
|
|
1159
1268
|
const forwardedWrites=(inherited.writes||[{variableKey:inherited.variableKey,value:inherited.value}]).map(write=>({...write,value:replaceEvent(write.value,arg)}));
|
|
1160
1269
|
for(const write of forwardedWrites)inputs(write.value);
|
|
1161
|
-
if(['onValueChange','onOpenChange'].includes(property))for(const [key,type]of Object.entries(inherited.payload))if(Object.hasOwn(payload,key))payload[key]=type;
|
|
1270
|
+
if(['onValueChange','onOpenChange',...(nativePlatform?['onChangeText']:[])].includes(property))for(const [key,type]of Object.entries(inherited.payload))if(Object.hasOwn(payload,key))payload[key]=type;
|
|
1162
1271
|
if(forwardedWrites.some(write=>uiExpressionType(write.value,payload)!==ui.variables.find(v=>v.variableKey===write.variableKey)?.valueType))return null;
|
|
1163
1272
|
writes.push(...forwardedWrites);continue;
|
|
1164
1273
|
}
|
|
@@ -1169,7 +1278,7 @@ export async function analyze({
|
|
|
1169
1278
|
const value=uiExpression(functional?functional.body:input,owner,ctx,fn.parameters[0]?.name?.text||null,aliases);
|
|
1170
1279
|
if(!value)return null;
|
|
1171
1280
|
inputs(value);const expected=ui.variables.find(v=>v.variableKey===variableKey)?.valueType;
|
|
1172
|
-
if(value.op==='event'&&value.key==='value'&&['onValueChange','onOpenChange'].includes(property))payload.value=expected;
|
|
1281
|
+
if(value.op==='event'&&value.key==='value'&&['onValueChange','onOpenChange',...(nativePlatform?['onChangeText']:[])].includes(property))payload.value=expected;
|
|
1173
1282
|
if(uiExpressionType(value,payload)!==expected)return null;
|
|
1174
1283
|
writes.push({variableKey,value});
|
|
1175
1284
|
}
|
|
@@ -1181,13 +1290,14 @@ export async function analyze({
|
|
|
1181
1290
|
ts,
|
|
1182
1291
|
node,
|
|
1183
1292
|
module: owner.module,
|
|
1184
|
-
locals: ctx.__iamLocals,
|
|
1293
|
+
locals: new Map([...ctx.__iamLocals,...ctx.__iamQueryLocals]),
|
|
1185
1294
|
binding,
|
|
1186
1295
|
functionFrom,
|
|
1187
1296
|
imported: externalImport,
|
|
1188
1297
|
});
|
|
1189
1298
|
}
|
|
1190
1299
|
function remoteResultCondition(node, owner, ctx) {
|
|
1300
|
+
if (queryBacked(node, owner, ctx)) return 'remote';
|
|
1191
1301
|
let shape = false;
|
|
1192
1302
|
const root =
|
|
1193
1303
|
ts.isIdentifier(node) && ctx.__iamLocals.has(node.text)
|
|
@@ -1205,7 +1315,7 @@ export async function analyze({
|
|
|
1205
1315
|
"data",
|
|
1206
1316
|
"length",
|
|
1207
1317
|
"size",
|
|
1208
|
-
].includes(n.name.text)
|
|
1318
|
+
].includes(n.name.text) || ts.isPropertyAccessExpression(n) && isUnknown(evalExpression(n.expression,owner.module,ctx))
|
|
1209
1319
|
)
|
|
1210
1320
|
shape = true;
|
|
1211
1321
|
ts.forEachChild(n, inspect);
|
|
@@ -1224,7 +1334,7 @@ export async function analyze({
|
|
|
1224
1334
|
next[key] = evalExpression(init, owner.module, next);
|
|
1225
1335
|
return next;
|
|
1226
1336
|
}
|
|
1227
|
-
function finiteCollection(node, owner, ctx, instance,
|
|
1337
|
+
function finiteCollection(node, owner, ctx, instance, _depth) {
|
|
1228
1338
|
if (ctx.__iamCollectionTemplate || !ts.isConditionalExpression(node))
|
|
1229
1339
|
return null;
|
|
1230
1340
|
const maps = [];
|
|
@@ -1482,8 +1592,14 @@ export async function analyze({
|
|
|
1482
1592
|
function renderExpr(node, owner, ctx, instance, depth) {
|
|
1483
1593
|
node = unwrap(node);
|
|
1484
1594
|
if (!node) return "";
|
|
1485
|
-
if (depth >
|
|
1595
|
+
if (depth > 128) { gap("RENDER_DEPTH_LIMIT", owner.module.file, owner.symbol + ": JSX nesting budget exceeded"); return ""; }
|
|
1486
1596
|
const m = owner.module;
|
|
1597
|
+
if(ts.isCallExpression(node)&&ts.isIdentifier(node.expression)&&ctx.__iamLocals.has(node.expression.text)){
|
|
1598
|
+
const fn=unwrap(ctx.__iamLocals.get(node.expression.text));
|
|
1599
|
+
if(fn&&(ts.isArrowFunction(fn)||ts.isFunctionDeclaration(fn)||ts.isFunctionExpression(fn))&&!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)))&&hasSourceJsx(uiReturnExpression(fn))&&fn.parameters.length<=8&&fn.parameters.every(p=>ts.isIdentifier(p.name)&&!p.dotDotDotToken)&&!node.arguments.some(ts.isSpreadElement)){
|
|
1600
|
+
return renderInvocation({callback:{fn,owner,ctx,instance:instance+'/source-helper'},args:node.arguments.map(arg=>evalExpression(arg,m,ctx)),caller:ctx},instance,depth+1);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1487
1603
|
const collection=finiteCollection(node,owner,ctx,instance,depth);if(collection!==null)return collection;
|
|
1488
1604
|
if(ts.isIdentifier(node)&&ctx.__iamLocals.has(node.text)&&hasSourceJsx(ctx.__iamLocals.get(node.text))){
|
|
1489
1605
|
if(ctx.__iamActiveJsx?.has(node.text))return '';
|
|
@@ -1602,7 +1718,7 @@ export async function analyze({
|
|
|
1602
1718
|
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);
|
|
1603
1719
|
record.set(id,{elementKey,source,synthetic:true});
|
|
1604
1720
|
elements.set(elementKey,{elementKey,componentKey:owner.key,tag:'div',source,identityStrategy:'provisional_ast_ordinal',editableProperties:[],binding:'preview_only'});
|
|
1605
|
-
return '<div data-iam-node="'+id+'" data-iam-unobserved="true" hidden="" data-iam-condition="'+conditionRow.conditionKey+'" data-iam-alternative="'+(index===0)+'">'+renderExpr(branch,owner,ctx,instance+'/unobserved-'+index,depth+1)+'</div>';
|
|
1721
|
+
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>';
|
|
1606
1722
|
}).join('');
|
|
1607
1723
|
}
|
|
1608
1724
|
if (
|
|
@@ -1617,15 +1733,7 @@ export async function analyze({
|
|
|
1617
1733
|
fn &&
|
|
1618
1734
|
(ts.isArrowFunction(fn) || ts.isFunctionExpression(fn))
|
|
1619
1735
|
) {
|
|
1620
|
-
const limit=ctx.__iamCollectionTemplate?128:50;
|
|
1621
|
-
if (values.length > limit)
|
|
1622
|
-
gap(
|
|
1623
|
-
"COLLECTION_TRUNCATED",
|
|
1624
|
-
m.file,
|
|
1625
|
-
"Static collection limited to 50 preview items",
|
|
1626
|
-
);
|
|
1627
1736
|
return values
|
|
1628
|
-
.slice(0, limit)
|
|
1629
1737
|
.map((value, i) => {
|
|
1630
1738
|
const next = { ...ctx };
|
|
1631
1739
|
if (fn.parameters[0] && ts.isIdentifier(fn.parameters[0].name))
|
|
@@ -1648,6 +1756,18 @@ export async function analyze({
|
|
|
1648
1756
|
.join("");
|
|
1649
1757
|
}
|
|
1650
1758
|
gap("DYNAMIC_COLLECTION", m.file, node.expression.getText(m.source));
|
|
1759
|
+
// Inspect one source template, never a fabricated data item. It stays
|
|
1760
|
+
// hidden in the page preview; a nested UI scene can inspect its structure.
|
|
1761
|
+
if (fn && (ts.isArrowFunction(fn) || ts.isFunctionExpression(fn)) && !fn.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) && hasSourceJsx(uiReturnExpression(fn)) && !ctx.__iamUnknownTemplate) {
|
|
1762
|
+
const next = {...ctx,__iamUnknownTemplate:true};
|
|
1763
|
+
for (const parameter of fn.parameters) {
|
|
1764
|
+
if (ts.isIdentifier(parameter.name)) next[parameter.name.text] = UNKNOWN;
|
|
1765
|
+
else if (ts.isObjectBindingPattern(parameter.name)) for (const field of parameter.name.elements) if (ts.isIdentifier(field.name)) next[field.name.text] = UNKNOWN;
|
|
1766
|
+
}
|
|
1767
|
+
const source = sourceLocation(m,node,owner.symbol),elementKey=identity('element',{component:owner.key,template:node.getStart(m.source)}),id=identity('node',{page:pageKey,element:elementKey,instance});
|
|
1768
|
+
elements.set(elementKey,{elementKey,componentKey:owner.key,tag:'div',source,identityStrategy:'provisional_ast_ordinal',editableProperties:[],binding:'preview_only'});record.set(id,{elementKey,source,synthetic:true});
|
|
1769
|
+
return '<div data-iam-node="'+id+'" data-iam-source-template="unobserved" hidden="">'+renderExpr(uiReturnExpression(fn),owner,next,instance+'/source-template',depth+1)+'</div>';
|
|
1770
|
+
}
|
|
1651
1771
|
return unobservedValue(node, owner, instance,queryBacked(node.expression.expression,owner,ctx));
|
|
1652
1772
|
}
|
|
1653
1773
|
if (!ts.isJsxElement(node) && !ts.isJsxSelfClosingElement(node)) {
|
|
@@ -1681,8 +1801,6 @@ export async function analyze({
|
|
|
1681
1801
|
}
|
|
1682
1802
|
return "";
|
|
1683
1803
|
}
|
|
1684
|
-
if (++renderedCount > 12000)
|
|
1685
|
-
throw new Error("Rendered node budget exceeded");
|
|
1686
1804
|
const opening = ts.isJsxElement(node) ? node.openingElement : node,
|
|
1687
1805
|
tag = opening.tagName.getText(m.source),
|
|
1688
1806
|
props = m.anchors.get(opening) ? { "data-indiana-id": m.anchors.get(opening) } : {}, uiHandlers = [], uiBindings = [], forwardedHandlers=[],uiAttributes=[];
|
|
@@ -1714,7 +1832,7 @@ export async function analyze({
|
|
|
1714
1832
|
}
|
|
1715
1833
|
for (let i = uiBindings.length - 1; i >= 0; i--) if (uiBindings[i].property === key) uiBindings.splice(i, 1);
|
|
1716
1834
|
for (let i = uiHandlers.length - 1; i >= 0; i--) if (uiHandlers[i].key === key) uiHandlers.splice(i, 1);
|
|
1717
|
-
if (["onClick", "onChange", "onInput", "onValueChange", "onOpenChange"].includes(key)) {
|
|
1835
|
+
if (["onClick", "onChange", "onInput", "onValueChange", "onOpenChange", "onClose", ...(nativePlatform ? ["onPress", "onChangeText"] : [])].includes(key)) {
|
|
1718
1836
|
props[key] = undefined;
|
|
1719
1837
|
const supported = ctx.__iamCollectionTemplate?null:uiHandler(attr.initializer, owner, ctx, key);
|
|
1720
1838
|
if (supported) { props[key] = undefined; uiHandlers.push({ ...supported, key, source: sourceLocation(m, attr, owner.symbol) }); }
|
|
@@ -1804,7 +1922,7 @@ export async function analyze({
|
|
|
1804
1922
|
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});
|
|
1805
1923
|
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+'>';
|
|
1806
1924
|
};
|
|
1807
|
-
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:
|
|
1925
|
+
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');
|
|
1808
1926
|
}
|
|
1809
1927
|
const sourceIcon=evalExpression(opening.tagName,m,ctx);
|
|
1810
1928
|
if(sourceIcon?.[LUCIDE_SOURCE])return renderLucide(sourceIcon,node,owner,props,instance,!ctx.__iamCollectionTemplate);
|
|
@@ -1813,8 +1931,8 @@ export async function analyze({
|
|
|
1813
1931
|
let childCtx=ctx,tabGroup=ctx.__iamTabs;
|
|
1814
1932
|
const provider=tag.endsWith('.Provider')?contextKey(m,tag.slice(0,-9)):contextKey(m,tag);
|
|
1815
1933
|
if(provider){const contexts=new Map(ctx.__iamContexts);contexts.set(provider,props.value);childCtx={...childCtx,__iamContexts:contexts};}
|
|
1816
|
-
if(overlay&&['Root','SubmenuRoot'].includes(overlay.part)){
|
|
1817
|
-
const openProperty=overlay.family==='Transition'?'in':'open',open=props[openProperty];
|
|
1934
|
+
if(overlay&&(['Root','SubmenuRoot'].includes(overlay.part)||overlay.family==='AriaDialog'&&['Backdrop','Popup'].includes(overlay.part)&&(Object.hasOwn(props,'isOpen')||Object.hasOwn(props,'defaultOpen')))){
|
|
1935
|
+
const openProperty=overlay.family==='Transition'?'in':overlay.family==='AriaDialog'?'isOpen':'open',open=props[openProperty];
|
|
1818
1936
|
overlayGroup={open:open===true||open===undefined&&props.defaultOpen===true,variable:uiBindings.find(b=>b.property===openProperty)?.value,controller:uiHandlers.find(h=>h.key==='onOpenChange')};
|
|
1819
1937
|
if(overlay.family!=='Transition'&&open===undefined&&!props.__iamOpaqueProps&&(props.defaultOpen===undefined||typeof props.defaultOpen==='boolean')){
|
|
1820
1938
|
const scopeKey=identity('primitive-state',{page:pageKey,instance,family:overlay.family}),variableKey=identity('ui-variable',{scope:scopeKey,label:'open'});
|
|
@@ -1844,15 +1962,64 @@ export async function analyze({
|
|
|
1844
1962
|
}
|
|
1845
1963
|
if(tabPart==='Root') {
|
|
1846
1964
|
const valueBinding=uiBindings.find(b=>b.property==='value')?.value;
|
|
1847
|
-
|
|
1848
|
-
|
|
1965
|
+
let controller=uiHandlers.find(h=>h.key==='onValueChange'||basePart?.mantine&&h.key==='onChange');
|
|
1966
|
+
const attribute = name => {const a=opening.attributes.properties.find(a=>ts.isJsxAttribute(a)&&a.name.text===name);return a?.initializer&&ts.isJsxExpression(a.initializer)?unwrap(a.initializer.expression):null;};
|
|
1967
|
+
const input=attribute('value'),handler=attribute('onValueChange');
|
|
1968
|
+
if (!controller && handler && (ts.isArrowFunction(handler)||ts.isFunctionExpression(handler)) && !handler.modifiers?.length && handler.parameters.length===1 && ts.isIdentifier(handler.parameters[0].name)) {
|
|
1969
|
+
let call=ts.isBlock(handler.body)&&handler.body.statements.length===1?handler.body.statements[0]:handler.body;
|
|
1970
|
+
if(ts.isExpressionStatement(call))call=call.expression;call=unwrap(call);
|
|
1971
|
+
const label=call&&ts.isCallExpression(call)&&ts.isIdentifier(call.expression)&&call.arguments.length===1?ctx.__iamSetters[call.expression.text]:null,key=label&&ctx.__iamVariableKeys[label];
|
|
1972
|
+
if(key&&input&&collectionStateReads(ts,input,ctx).includes(key)) controller={variableKey:key,forValue:value=>{
|
|
1973
|
+
const written=evalExpression(call.arguments[0],m,{...ctx,[handler.parameters[0].name.text]:value});
|
|
1974
|
+
if(typeof written!=='string'||evalExpression(input,m,{...ctx,[label]:written})!==value)return null;
|
|
1975
|
+
return {variableKey:key,value:{op:'const',value:written}};
|
|
1976
|
+
}};
|
|
1977
|
+
}
|
|
1978
|
+
const variableKey=valueBinding?.op==='var'?valueBinding.key:controller?.forValue?controller.variableKey:null;
|
|
1979
|
+
tabGroup={tabKey:identity('ui-tab',{page:pageKey,scope:ctx.__iamScope,instance}),routeKey:pageKey,renderKey:'pending',nodeId:'pending',variableKey,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===variableKey)?.source??sourceLocation(m,node,owner.symbol),controller};
|
|
1849
1980
|
childCtx={...childCtx,__iamTabs:tabGroup};
|
|
1850
1981
|
}
|
|
1982
|
+
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;
|
|
1983
|
+
let accordion=ctx.__iamContexts?.get('$iam-mantine-accordion');
|
|
1984
|
+
if(mantineName==='Accordion'){
|
|
1985
|
+
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};
|
|
1986
|
+
gap('ACCORDION_SOURCE_PREVIEW',m.file,'Native local details/summary preview; source defaults are retained and library callbacks are not executed.');
|
|
1987
|
+
}
|
|
1988
|
+
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;}
|
|
1989
|
+
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(' ');}
|
|
1851
1990
|
const localRef=componentBinding(m,tag),localTarget=ctx[tag]?.[COMPONENT]??(localRef&&componentDefs.get(localRef.module.file+'#'+localRef.name));
|
|
1852
1991
|
const dynamicNode=ctx.__iamLocals.get(tag);
|
|
1853
1992
|
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;
|
|
1854
|
-
|
|
1855
|
-
const children
|
|
1993
|
+
let childrenRenders=0;
|
|
1994
|
+
const renderChildren=(inherited=childCtx)=>{if(childrenRenders)gap('REPEATED_SOURCE_INSTANCE',m.file,'Forwarded children have distinct preview instances; repeated runtime selectors remain ambiguous');const childInstance=instance+(childrenRenders++?'/forward-'+(childrenRenders-1):'');return 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},childInstance+'/child-'+i,depth+1)).join(''):props.children?.iamRenderChildren?props.children.iamRenderChildren(inherited):props.children?.iamChildren||'';};
|
|
1995
|
+
let children = localTarget||registry ? '' : renderChildren();
|
|
1996
|
+
const mantine = mantinePrimitive(m.imports.get(tag.split('.')[0]), props, tag.split('.').slice(1).join('.'));
|
|
1997
|
+
if (mantine) {
|
|
1998
|
+
gap('MANTINE_SOURCE_PROFILE', m.file, 'Inert semantic Mantine projection; theme resolution, portals, focus management and library callbacks are not executed.');
|
|
1999
|
+
if (mantine.transparent) return children;
|
|
2000
|
+
Object.assign(props, mantine.props);
|
|
2001
|
+
if (mantine.overlay) {
|
|
2002
|
+
const visible = uiBindings.find(b => b.property === 'opened');
|
|
2003
|
+
if (visible) uiBindings.push({...visible, property: 'visible'});
|
|
2004
|
+
if (typeof props.opened !== 'boolean') gap('MANTINE_OPEN_STATE_UNOBSERVED', m.file, 'Unknown overlay state is initially hidden; no runtime open state is inferred.');
|
|
2005
|
+
}
|
|
2006
|
+
}
|
|
2007
|
+
const flowbite=flowbiteShape(externalImport(m,tag),props,children,['Table','Navbar','Sidebar'].includes(m.imports.get(tag.split('.')[0])?.name)?tag.split('.')[1]:undefined,flowbiteTheme);
|
|
2008
|
+
if(flowbite){
|
|
2009
|
+
gap('UI_LIBRARY_STATIC_PROFILE',m.file,'Flowbite 0.3 inert presentation profile; custom theme and library state remain unobserved.');
|
|
2010
|
+
const source=sourceLocation(m,node,owner.symbol);
|
|
2011
|
+
const draw=(item,part)=>{
|
|
2012
|
+
if(typeof item==='string')return escape(item);
|
|
2013
|
+
if(Object.hasOwn(item,'html'))return item.html;
|
|
2014
|
+
const elementKey=identity('element',{component:owner.key,library:'flowbite',offset:node.pos,part}),nodeId=identity('node',{page:pageKey,elementKey,instance});
|
|
2015
|
+
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});
|
|
2016
|
+
if(item.attrs.class)item.attrs.class.split(/\s+/).filter(Boolean).forEach(c=>allClasses.add(c));
|
|
2017
|
+
if(item.attrs.src)assets.set(item.attrs.src,null);
|
|
2018
|
+
const attrs=Object.entries(item.attrs).filter(([,v])=>v!==false).map(([k,v])=>' '+k+'="'+escape(v===true?'':v)+'"').join('');
|
|
2019
|
+
return '<'+item.tag+' data-iam-node="'+nodeId+'"'+attrs+'>'+(voidTags.has(item.tag)?'':item.children.map((child,i)=>draw(child,part+'/'+i)).join('')+'</'+item.tag+'>');
|
|
2020
|
+
};
|
|
2021
|
+
return draw(flowbite,'root');
|
|
2022
|
+
}
|
|
1856
2023
|
|
|
1857
2024
|
// Project the documented Controller render callback from source, with no
|
|
1858
2025
|
// form initialization, live field values or business callback execution.
|
|
@@ -1879,6 +2046,14 @@ export async function analyze({
|
|
|
1879
2046
|
|
|
1880
2047
|
if (ctx[tag]?.iamChildren !== undefined) return ctx[tag].iamChildren;
|
|
1881
2048
|
if (ctx[tag]?.iamRenderChildren) return ctx[tag].iamRenderChildren(ctx);
|
|
2049
|
+
const native = nativePlatform ? nativePrimitive(m, tag, props, { platform, gap: detail => gap("RN_CAPABILITY_UNOBSERVED", m.file, detail) }) : null;
|
|
2050
|
+
if (native?.ignore) return "";
|
|
2051
|
+
if (native?.outlet) {
|
|
2052
|
+
gap("RN_NAVIGATOR_SOURCE_ONLY", m.file, "Projects the current route through the source layout; native headers, tabs, focus and stack history are unobserved");
|
|
2053
|
+
return ctx.__iamOutlet?.iamRenderChildren?.(ctx) ?? "";
|
|
2054
|
+
}
|
|
2055
|
+
if (native?.props) Object.assign(props, native.props);
|
|
2056
|
+
if (native?.text !== undefined) children = escape(native.text);
|
|
1882
2057
|
const routerPrimitive = externalImport(m, tag);
|
|
1883
2058
|
if (['react-router','react-router-dom'].includes(routerPrimitive?.specifier)&&routerPrimitive.name==='Navigate')return '';
|
|
1884
2059
|
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 ?? "";
|
|
@@ -1897,7 +2072,9 @@ export async function analyze({
|
|
|
1897
2072
|
props["data-iam-adapter"] = tag;
|
|
1898
2073
|
}
|
|
1899
2074
|
if (/^[A-Z]/.test(tag) && !intrinsicAlias) {
|
|
1900
|
-
if(
|
|
2075
|
+
if (native?.tag) htmlTag = native.tag;
|
|
2076
|
+
else if (mantine) htmlTag = mantine.tag;
|
|
2077
|
+
else if(tabPart){
|
|
1901
2078
|
htmlTag=tabPart==='Tab'?'button':'div';
|
|
1902
2079
|
if(tabPart==='List')props.role='tablist';
|
|
1903
2080
|
if(tabPart==='Tab'){props.role='tab';props.type='button';props['aria-selected']=tabGroup?.initialValue===props.value;}
|
|
@@ -1907,14 +2084,24 @@ export async function analyze({
|
|
|
1907
2084
|
htmlTag=['Trigger','Close','Item','CheckboxItem','RadioItem','SubmenuTrigger'].includes(overlay.part)&&props.asChild!==true?'button':overlay.part==='Title'?'h2':overlay.part==='Description'?'p':'div';
|
|
1908
2085
|
if(htmlTag==='button')props.type='button';
|
|
1909
2086
|
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)});}
|
|
1910
|
-
if(
|
|
1911
|
-
if(
|
|
2087
|
+
if(overlay.rootVisibility){props.hidden=!overlayGroup?.open;if(overlayGroup?.variable)uiBindings.push({property:'visible',value:overlayGroup.variable,source:sourceLocation(m,node,owner.symbol)});}
|
|
2088
|
+
else if(['Root','SubmenuRoot','Portal','Provider'].includes(overlay.part)||props.asChild===true)props.style={display:'contents'};
|
|
2089
|
+
if(overlay.part==='Popup'&&overlay.role!==null)props.role=overlay.role??(overlay.family==='Menu'?'menu':overlay.family==='Tooltip'?'tooltip':'dialog');
|
|
2090
|
+
if(overlay.part==='Content'&&overlay.role)props.role=overlay.role;
|
|
1912
2091
|
}
|
|
1913
2092
|
else if (tag === "Link" || ["react-router", "react-router-dom", "@tanstack/react-router"].includes(routerPrimitive?.specifier) && ["Link", "NavLink"].includes(routerPrimitive.name)) {
|
|
1914
2093
|
htmlTag = "a";
|
|
1915
2094
|
if (typeof props.to === "string") props.href = props.to;
|
|
1916
2095
|
}
|
|
1917
2096
|
else if (tag === "Image") htmlTag = "img";
|
|
2097
|
+
else if(externalImport(m,tag)?.specifier==='recharts'&&externalImport(m,tag)?.name==='ResponsiveContainer'){
|
|
2098
|
+
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.');
|
|
2099
|
+
}
|
|
2100
|
+
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))){
|
|
2101
|
+
const part=tag.split('.')[1]??externalImport(m,tag)?.name;
|
|
2102
|
+
htmlTag=['Image','AvatarImage'].includes(part)?'img':'span';
|
|
2103
|
+
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.');}
|
|
2104
|
+
}
|
|
1918
2105
|
else if(m.imports.get(tag)?.specifier==='react-basics'&&m.imports.get(tag)?.name==='Text'){
|
|
1919
2106
|
htmlTag=typeof props.as==='string'&&allowedTags.has(props.as)?props.as:'span';
|
|
1920
2107
|
gap('COMPONENT_STYLE_ADAPTER',m.file,'react-basics Text DOM semantics only; actual styles need runtime evidence');
|
|
@@ -2020,7 +2207,7 @@ export async function analyze({
|
|
|
2020
2207
|
record.set(nodeId, {
|
|
2021
2208
|
elementKey,
|
|
2022
2209
|
source,
|
|
2023
|
-
synthetic: htmlTag !== tag && tag !== "Link" && tag !== "Image"&&!intrinsicAlias&&!knownExternalPrimitive,
|
|
2210
|
+
synthetic: nativePlatform || Boolean(m.mdx) || htmlTag !== tag && tag !== "Link" && tag !== "Image"&&!intrinsicAlias&&!knownExternalPrimitive,
|
|
2024
2211
|
});
|
|
2025
2212
|
if(tabPart==='Root'){
|
|
2026
2213
|
// Tab initialization below shares the same concrete source container.
|
|
@@ -2039,11 +2226,11 @@ export async function analyze({
|
|
|
2039
2226
|
if(!tabGroup.items.some(item=>item.value===props.value))tabGroup.items.push({value:props.value,nodeId});
|
|
2040
2227
|
props['data-iam-tab-value']=props.value;props['data-iam-tab-key']=tabGroup.tabKey;
|
|
2041
2228
|
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]));
|
|
2042
|
-
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});}
|
|
2229
|
+
if(tabGroup.controller){const specialized=tabGroup.controller.forValue?.(props.value),writes=(tabGroup.controller.forValue?(specialized?[specialized]:[]):tabGroup.controller.writes||[{variableKey:tabGroup.controller.variableKey,value:tabGroup.controller.value}]).map(w=>({...w,value:replace(w.value)}));if(writes.length)uiHandlers.push({...writes[0],writes,key:'onClick',payload:{},source});}
|
|
2043
2230
|
}
|
|
2044
2231
|
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});
|
|
2045
|
-
for (const handler of uiHandlers.filter(h=>!['onValueChange','onOpenChange'].includes(h.key))) {
|
|
2046
|
-
const event = handler.key
|
|
2232
|
+
for (const handler of uiHandlers.filter(h=>!['onValueChange','onOpenChange','onClose'].includes(h.key)&&!(tabPart==='Root'&&h.key==='onChange'))) {
|
|
2233
|
+
const event = ["onClick", "onPress"].includes(handler.key) ? "click" : htmlTag === "select" || handler.payload.checked ? "change" : "input";
|
|
2047
2234
|
const writes=handler.writes||[{variableKey:handler.variableKey,value:handler.value}];
|
|
2048
2235
|
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 });
|
|
2049
2236
|
}
|
|
@@ -2056,14 +2243,22 @@ export async function analyze({
|
|
|
2056
2243
|
}
|
|
2057
2244
|
ui.bindings.push({ bindingKey: identity("ui-binding", { node: nodeId, property: binding.property }), routeKey: pageKey, nodeId, elementKey, ...binding });
|
|
2058
2245
|
}
|
|
2059
|
-
const sensitiveInput = htmlTag ===
|
|
2246
|
+
const sensitiveInput = native?.sensitive || ['input', 'textarea', 'select'].includes(htmlTag) && (htmlTag === 'input' && ["password", "hidden"].includes(String(props.type).toLowerCase()) || sensitiveName(String(props.name || props.id || "")));
|
|
2060
2247
|
if (sensitiveInput) {
|
|
2061
2248
|
delete props.value; delete props.defaultValue;
|
|
2249
|
+
if (htmlTag === 'textarea') children = '';
|
|
2062
2250
|
ui.bindings = ui.bindings.filter(b => b.nodeId !== nodeId);
|
|
2063
2251
|
ui.transitions = ui.transitions.filter(t => t.trigger.nodeId !== nodeId);
|
|
2064
2252
|
gap("SENSITIVE_INPUT_REDACTED", m.file, "Input value and state bridge omitted");
|
|
2065
2253
|
}
|
|
2254
|
+
if (['input', 'textarea', 'select'].includes(htmlTag) && !Object.hasOwn(props, 'value') && Object.hasOwn(props, 'defaultValue')) props.value = props.defaultValue;
|
|
2255
|
+
if (htmlTag === 'input' && !Object.hasOwn(props, 'checked') && typeof props.defaultChecked === 'boolean') props.checked = props.defaultChecked;
|
|
2256
|
+
if (htmlTag === 'textarea' && ['string', 'number'].includes(typeof props.value)) children = escape(props.value);
|
|
2066
2257
|
const attrs = { "data-iam-node": nodeId,...(['input','textarea','select'].includes(htmlTag)&&Object.hasOwn(props,'value')&&isUnknown(props.value)?{'data-iam-input-unobserved':'true'}:{}) };
|
|
2258
|
+
if(mantine?.overlay){
|
|
2259
|
+
const close=uiHandlers.find(h=>h.key==='onClose');
|
|
2260
|
+
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;}
|
|
2261
|
+
}
|
|
2067
2262
|
for (const [key, value] of Object.entries(props)) {
|
|
2068
2263
|
if (key === "style") {
|
|
2069
2264
|
const css = staticStyle(value, () => gap("INLINE_STYLE_UNSUPPORTED", m.file,
|
|
@@ -2077,12 +2272,17 @@ export async function analyze({
|
|
|
2077
2272
|
gap("PRIVATE_URL_REDACTED", m.file, "URL credentials or sensitive query omitted");
|
|
2078
2273
|
continue;
|
|
2079
2274
|
}
|
|
2080
|
-
if (["disabled", "hidden", "checked"].includes(key) && value === false) continue;
|
|
2275
|
+
if (["disabled", "hidden", "checked", "required", "readOnly", "multiple", "selected", "open"].includes(key) && value === false) continue;
|
|
2081
2276
|
const attr =
|
|
2082
2277
|
{
|
|
2083
2278
|
className: "class",
|
|
2084
2279
|
htmlFor: "for",
|
|
2085
2280
|
tabIndex: "tabindex",
|
|
2281
|
+
readOnly: "readonly",
|
|
2282
|
+
minLength: "minlength",
|
|
2283
|
+
maxLength: "maxlength",
|
|
2284
|
+
autoComplete: "autocomplete",
|
|
2285
|
+
inputMode: "inputmode",
|
|
2086
2286
|
viewBox: "viewBox",
|
|
2087
2287
|
strokeWidth: "stroke-width",
|
|
2088
2288
|
strokeLinecap: "stroke-linecap",
|
|
@@ -2091,7 +2291,7 @@ export async function analyze({
|
|
|
2091
2291
|
clipRule: "clip-rule",
|
|
2092
2292
|
}[key] || key;
|
|
2093
2293
|
if (
|
|
2094
|
-
!/^(?: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-(?: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(
|
|
2294
|
+
!/^(?: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-(?:native-component|native-platform|native-unobserved|adapter|toggle|tab-value|tab-key|condition|alternative|condition-default|unobserved))$/.test(
|
|
2095
2295
|
attr,
|
|
2096
2296
|
)
|
|
2097
2297
|
)
|
|
@@ -2107,6 +2307,7 @@ export async function analyze({
|
|
|
2107
2307
|
}
|
|
2108
2308
|
attrs["data-iam-external"] = "true";
|
|
2109
2309
|
} else if (attr === "src") {
|
|
2310
|
+
if(!['img','source'].includes(htmlTag)){gap('RESOURCE_HOST_UNSUPPORTED',m.file,tag+': resource attribute omitted from a non-image source adapter');continue;}
|
|
2110
2311
|
if (String(value).startsWith("/") && !String(value).startsWith("//"))
|
|
2111
2312
|
assets.set(String(value), null);
|
|
2112
2313
|
else {
|
|
@@ -2120,7 +2321,7 @@ export async function analyze({
|
|
|
2120
2321
|
.forEach((c) => allClasses.add(c));
|
|
2121
2322
|
attrs[attr] = String(value);
|
|
2122
2323
|
}
|
|
2123
|
-
|
|
2324
|
+
const rendered = (
|
|
2124
2325
|
"<" +
|
|
2125
2326
|
htmlTag +
|
|
2126
2327
|
Object.entries(attrs)
|
|
@@ -2131,6 +2332,12 @@ export async function analyze({
|
|
|
2131
2332
|
? (tag.startsWith("Icons.") ? "◇" : children) + "</" + htmlTag + ">"
|
|
2132
2333
|
: "")
|
|
2133
2334
|
);
|
|
2335
|
+
if (mantine?.label) {
|
|
2336
|
+
const labelId = identity('node', {page: pageKey, element: elementKey, instance, mantineLabel: true});
|
|
2337
|
+
record.set(labelId, {elementKey, source, synthetic: true});
|
|
2338
|
+
return '<label data-iam-node="' + labelId + '" style="display:flex;flex-direction:column;gap:0.35rem">' + '<span>' + escape(mantine.label) + '</span>' + rendered + '</label>';
|
|
2339
|
+
}
|
|
2340
|
+
return rendered;
|
|
2134
2341
|
}
|
|
2135
2342
|
function renderEntry(file, props, instance, symbol) {
|
|
2136
2343
|
const entryModule = modules.get(file), entryRef = entryModule && componentBinding(entryModule, symbol ?? entryModule.defaultName);
|
|
@@ -2156,7 +2363,7 @@ export async function analyze({
|
|
|
2156
2363
|
return '<div data-iam-node="'+contentId+'" data-iam-page-content="true">'+content+'</div>';
|
|
2157
2364
|
}
|
|
2158
2365
|
const layer=branch.layers[index],child={iamRenderChildren:next=>renderLayer(index-1,next)};
|
|
2159
|
-
const props = ["react-router", "tanstack-router"].includes(routing.framework)
|
|
2366
|
+
const props = ["react-router", "tanstack-router", "expo-router"].includes(routing.framework)
|
|
2160
2367
|
? { ...contextProps(ctx), __iamOutlet: child }
|
|
2161
2368
|
: { ...contextProps(ctx), children: child };
|
|
2162
2369
|
if (/^(?:src\/)?pages\/_app\.[jt]sx?$/.test(layer.file)) { props.Component = child; props.pageProps = {}; }
|
|
@@ -2296,7 +2503,7 @@ export async function analyze({
|
|
|
2296
2503
|
});
|
|
2297
2504
|
// Unknown source alternatives live in ui.conditions. Representable local
|
|
2298
2505
|
// state candidates remain separate from concrete source documents.
|
|
2299
|
-
for (const branch of branches.filter(b=>b.evidence==='source_inferred')
|
|
2506
|
+
for (const branch of branches.filter(b=>b.evidence==='source_inferred')) {
|
|
2300
2507
|
const key = identity("state", {page:pageKey,source:branch.source,label:branch.label});
|
|
2301
2508
|
if(!states.some(state=>state.stateKey===key)){
|
|
2302
2509
|
states.push({stateKey:key,pageKey,label:branch.label,evidence:branch.evidence,source:branch.source,documentKey:null});
|
|
@@ -2309,7 +2516,7 @@ export async function analyze({
|
|
|
2309
2516
|
stateKey,
|
|
2310
2517
|
kind: "source_projection",
|
|
2311
2518
|
evidence: unprojected?'uncovered':"source_inferred",
|
|
2312
|
-
viewport: { width: 1280, height: 800 },
|
|
2519
|
+
viewport: nativePlatform ? { width: 390, height: 844 } : { width: 1280, height: 800 },
|
|
2313
2520
|
html: concreteRoot.outerHTML,
|
|
2314
2521
|
css: "",
|
|
2315
2522
|
js: "",
|
|
@@ -2430,6 +2637,9 @@ export async function analyze({
|
|
|
2430
2637
|
);
|
|
2431
2638
|
css +=
|
|
2432
2639
|
"\n.iam-state{display:contents}.iam-state[hidden]{display:none!important}";
|
|
2640
|
+
let projectionCss = cssModules.css();
|
|
2641
|
+
if (documents.some(d=>d.html.includes('class="iam-state'))) projectionCss += "\n.iam-state{display:contents}.iam-state[hidden]{display:none!important}";
|
|
2642
|
+
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}';
|
|
2433
2643
|
if (!pages.length)
|
|
2434
2644
|
throw new Error(
|
|
2435
2645
|
"No supported source routes found. Use Next App Router, Next Pages Router or a statically declared React Router route tree.",
|
|
@@ -2458,10 +2668,10 @@ export async function analyze({
|
|
|
2458
2668
|
schemaVersion: 2,
|
|
2459
2669
|
producer: {
|
|
2460
2670
|
name: "indiana-app-manifest-local",
|
|
2461
|
-
version: "0.
|
|
2462
|
-
method: "typescript-ast-and-bounded-static-projection;lucide-static-"+LUCIDE_SOURCE_VERSION,
|
|
2671
|
+
version: "0.27.0",
|
|
2672
|
+
method: "typescript-ast-and-bounded-static-projection;lucide-static-"+LUCIDE_SOURCE_VERSION+(nativePlatform ? ";react-native-source-preview-v1" : ""),
|
|
2463
2673
|
},
|
|
2464
|
-
app: { name, platforms: [
|
|
2674
|
+
app: { name, platforms: [platform] },
|
|
2465
2675
|
routing,
|
|
2466
2676
|
release: { repository, commit, sourceGraphDigest },
|
|
2467
2677
|
pages,
|
|
@@ -2476,7 +2686,7 @@ export async function analyze({
|
|
|
2476
2686
|
runtimeObserved: false,
|
|
2477
2687
|
generatedPageCount: pages.length,
|
|
2478
2688
|
sourcePageFileCount: pageFiles.length,
|
|
2479
|
-
unsupportedFrameworks: ["vue", "svelte", "android", "ios"],
|
|
2689
|
+
unsupportedFrameworks: nativePlatform ? ["vue", "svelte", "swiftui", "compose", "flutter", "react-navigation"] : ["vue", "svelte", "android", "ios"],
|
|
2480
2690
|
gaps,
|
|
2481
2691
|
},
|
|
2482
2692
|
integrity: {
|
|
@@ -2499,6 +2709,7 @@ export async function analyze({
|
|
|
2499
2709
|
const sourceGaps=new Map();
|
|
2500
2710
|
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});}
|
|
2501
2711
|
ui.uncovered=[...sourceGaps.values()];
|
|
2712
|
+
attachSourceStyles(manifest, projectionCss);
|
|
2502
2713
|
manifest.routeTree = assembleRouteTree(manifest, ui, renderDefinitions, compositions.navigation);
|
|
2503
2714
|
presentation.conditions=[...new Map(presentation.conditions.map(c=>[c.conditionKey,c])).values()];
|
|
2504
2715
|
presentation.regions=presentation.regions.filter(r=>concrete.has(r.nodeId)&&r.options.every(o=>concrete.has(o.nodeId)));
|