@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.
Files changed (50) hide show
  1. package/README.md +104 -10
  2. package/assets/viewer.js +60 -32
  3. package/package.json +13 -3
  4. package/src/analyze.mjs +298 -87
  5. package/src/anchors.mjs +2 -0
  6. package/src/build-styles.mjs +14 -9
  7. package/src/build.mjs +6 -4
  8. package/src/cli.mjs +8 -1
  9. package/src/component-registry.mjs +1 -1
  10. package/src/document-material.mjs +180 -0
  11. package/src/dom-payload-json.mjs +2 -2
  12. package/src/dom-payload.mjs +3 -3
  13. package/src/framework-routes.mjs +6 -8
  14. package/src/generate.mjs +114 -24
  15. package/src/index.d.ts +16 -4
  16. package/src/index.mjs +1 -1
  17. package/src/json.mjs +2 -3
  18. package/src/library-entry.mjs +7 -3
  19. package/src/node-hashes.mjs +1 -1
  20. package/src/react-native-projection.mjs +300 -0
  21. package/src/react-native-routes.mjs +182 -0
  22. package/src/react-native.d.ts +47 -0
  23. package/src/react-native.mjs +45 -0
  24. package/src/render-index.mjs +8 -8
  25. package/src/route-composition.mjs +5 -5
  26. package/src/route-tree.mjs +9 -9
  27. package/src/routing.mjs +8 -7
  28. package/src/schema-definition.mjs +14 -16
  29. package/src/selected-manifest.mjs +12 -0
  30. package/src/source-aliases.mjs +34 -0
  31. package/src/source-charts.mjs +15 -0
  32. package/src/source-collections.mjs +3 -0
  33. package/src/source-css-modules.mjs +79 -0
  34. package/src/source-files.mjs +8 -7
  35. package/src/source-flowbite.mjs +60 -0
  36. package/src/source-i18next.mjs +22 -0
  37. package/src/source-mantine.mjs +61 -0
  38. package/src/source-mdx.mjs +92 -0
  39. package/src/source-media.mjs +12 -3
  40. package/src/source-next-redirects.mjs +98 -0
  41. package/src/source-preview-css.mjs +117 -0
  42. package/src/source-redirects.mjs +2 -0
  43. package/src/source-styles.mjs +9 -0
  44. package/src/static-query.mjs +67 -7
  45. package/src/svg-preview.mjs +23 -0
  46. package/src/tanstack-routes.mjs +1 -1
  47. package/src/ui-preview.mjs +14 -0
  48. package/src/ui-structure.mjs +9 -19
  49. package/src/validate.mjs +7 -1
  50. 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 = 5000,
191
- maxBytes = 100_000_000,
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 (stat.size > 1_000_000 || (byteCount += stat.size) > maxBytes)
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 text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
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
- file.endsWith(".module.css")
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
- if (!/\.[cm]?[jt]sx?$/.test(file)) {
249
- if (/^(?:src\/)?app\/(?:.*\/)?(?:page|layout)\.mdx$/.test(file) || /^(?:src\/)?pages\/.+\.mdx$/.test(file))
250
- gap("ROUTE_MDX_UNSUPPORTED", file, "MDX route/layout source requires a dedicated adapter; its page and layout coverage is incomplete");
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
- let alias = { "@/*": ["./*"] };
337
- try {
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 loc = module.source.getLineAndCharacterOfPosition(
536
- node.getStart(module.source),
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,context){
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'&&/^\.\.?\/[^?#]+\.(?:png|jpe?g|webp|gif)$/i.test(image.specifier)){
580
- const file=path.posix.normalize(path.posix.join(path.posix.dirname(module.file),image.specifier));
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){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);return typeof value==='string'&&!/\{\{/.test(value)?redactLiteral(value):UNKNOWN;}
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
- const compositions = frameworkRoutes({ modules, ...selection, resolveModule, ts, sourceLocation, navigationPaths, gap, resolution, inlineComponent });
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=/[\[\]]/.test(route)?UNKNOWN:route;
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, depth) {
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 > 45) { gap("RENDER_DEPTH_LIMIT", owner.module.file, owner.symbol + ": JSX traversal budget exceeded"); return ""; }
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:auto;max-height:'+chart.height+'px'},children:chart.children},'root');
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
- const controller=uiHandlers.find(h=>h.key==='onValueChange');
1848
- 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};
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
- 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||'';
1855
- const children = localTarget||registry ? '' : renderChildren();
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(tabPart){
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(['Root','SubmenuRoot','Portal','Provider'].includes(overlay.part)||props.asChild===true)props.style={display:'contents'};
1911
- if(overlay.part==='Popup')props.role=overlay.family==='Menu'?'menu':overlay.family==='Tooltip'?'tooltip':'dialog';
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 === "onClick" ? "click" : htmlTag === "select" || handler.payload.checked ? "change" : "input";
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 === "input" && (["password", "hidden"].includes(String(props.type).toLowerCase()) || sensitiveName(String(props.name || props.id || "")));
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
- return (
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').slice(0, 30)) {
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.24.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: ["web"] },
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)));