@tinkerlandai/indiana-iam 0.3.0 → 0.5.0

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