@tinkerlandai/indiana-iam 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/analyze.mjs CHANGED
@@ -1,7 +1,14 @@
1
1
  import fs from "node:fs/promises";
2
2
  import {referencedRasterSources} from "./source-media.mjs";
3
+ import {sourceAliases} from './source-aliases.mjs';
4
+ import {sourceCssModules} from './source-css-modules.mjs';
5
+ import {mantinePrimitive} from './source-mantine.mjs';
6
+ import {sourceMdx} from './source-mdx.mjs';
7
+ import {attachSourceStyles} from './source-styles.mjs';
8
+ import {sourceI18next} from './source-i18next.mjs';
3
9
  import {uiEnvironmentPrimitive} from "./source-environment.mjs";
4
10
  import {sourceChart} from "./source-charts.mjs";
11
+ import {flowbiteShape,rootFlowbiteTheme} from "./source-flowbite.mjs";
5
12
  import path from "node:path";
6
13
  import { automaticSourceAnchors, anchorAttributes, sourceScriptKind } from "./anchors.mjs";
7
14
  import { sourceFiles } from "./source-files.mjs";
@@ -41,6 +48,7 @@ import { sensitiveName, redactLiteral, privateUrl } from "./privacy.mjs";
41
48
  const isUnknown = (value) => value === UNKNOWN;
42
49
  const COMPONENT=Symbol('source-component');
43
50
  const TRANSLATION=Symbol('source-translation-catalog');
51
+ const TRANSLATION_FORMAT=Symbol('source-translation-format');
44
52
  const UI_SETTER=Symbol('source-ui-setter');
45
53
  const UI_PRIMITIVE=Symbol('source-ui-primitive');
46
54
 
@@ -83,11 +91,11 @@ function staticStyle(value, unsupported) {
83
91
  }).join(";");
84
92
  }
85
93
  const allowedTags = new Set(
86
- "main section article aside header footer nav div span p h1 h2 h3 h4 h5 h6 a button form label input select option textarea ul ol li dl dt dd table thead tbody tr th td caption fieldset legend details summary pre code blockquote strong em b i small s u mark time br hr img picture source svg path circle rect line polyline polygon ellipse g title".split(
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(
87
95
  " ",
88
96
  ),
89
97
  );
90
- const voidTags = new Set(["input", "br", "hr", "img", "source"]);
98
+ const voidTags = new Set(["input", "br", "hr", "wbr", "img", "source"]);
91
99
  const escape = (text) =>
92
100
  redactLiteral(text).replace(
93
101
  /[&<>"']/g,
@@ -187,8 +195,8 @@ export async function analyze({
187
195
  name,
188
196
  repository = "",
189
197
  commit = "",
190
- maxFiles = 5000,
191
- maxBytes = 100_000_000,
198
+ maxFiles = Infinity,
199
+ maxBytes = Infinity,
192
200
  navigationPaths = [],
193
201
  entry,
194
202
  automaticAnchors = false,
@@ -201,7 +209,7 @@ export async function analyze({
201
209
  elements = new Map(),
202
210
  components = new Map(),
203
211
  assets = new Map();
204
- const cssSources = [];
212
+ const cssSources = [], cssModuleFiles = new Map();
205
213
  const jsonResources=new Map(),translationResources=new Set();
206
214
  const presentation={conditions:[],tabs:[],navigation:[],regions:[],collections:[],fragments:[],attributes:[],attributeSets:[]},collectionTemplates=new Map();
207
215
  const ui = { variables: [], bindings: [], transitions: [], uncovered: [] };
@@ -210,7 +218,6 @@ export async function analyze({
210
218
  function gap(code, file, detail) {
211
219
  detail = redactLiteral(detail);
212
220
  if (
213
- gaps.length < 1000 &&
214
221
  !gaps.some(
215
222
  (x) => x.code === code && x.file === file && x.detail === detail,
216
223
  )
@@ -224,10 +231,11 @@ export async function analyze({
224
231
  const real = await fs.realpath(absolute);
225
232
  if (!real.startsWith(root + path.sep))
226
233
  throw new Error("Source path escapes root");
227
- if (stat.size > 1_000_000 || (byteCount += stat.size) > maxBytes)
234
+ if ((byteCount += stat.size) > maxBytes)
228
235
  throw new Error("Source byte budget exceeded");
229
236
  const bytes = await fs.readFile(absolute);
230
- 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;
231
239
  inventory.push({
232
240
  file,
233
241
  sha256: sha256(bytes),
@@ -235,33 +243,32 @@ export async function analyze({
235
243
  });
236
244
  if(file.endsWith('.json')){try{jsonResources.set(file,JSON.parse(text));}catch{/* Non-resource JSON stays opaque. */}continue;}
237
245
  if (file.endsWith(".css")) {
246
+ if (file.endsWith('.module.css')) { cssModuleFiles.set(file, text); continue; }
238
247
  gap(
239
- file.endsWith(".module.css")
240
- ? "CSS_MODULES_UNSUPPORTED"
241
- : "STYLE_SOURCE_PARTIAL",
248
+ "STYLE_SOURCE_PARTIAL",
242
249
  file,
243
250
  "Only root/dark tokens and pinned utility rules are reconstructed; scoped/custom selectors, keyframes and original font rules are not compiled",
244
251
  );
245
252
  if (!file.endsWith(".module.css")) cssSources.push({ file, text });
246
253
  continue;
247
254
  }
248
- if (!/\.[cm]?[jt]sx?$/.test(file)) {
249
- if (/^(?:src\/)?app\/(?:.*\/)?(?:page|layout)\.mdx$/.test(file) || /^(?:src\/)?pages\/.+\.mdx$/.test(file))
250
- gap("ROUTE_MDX_UNSUPPORTED", file, "MDX route/layout source requires a dedicated adapter; its page and layout coverage is incomplete");
251
- continue;
252
- }
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;
253
258
  const source = ts.createSourceFile(
254
259
  file,
255
260
  text,
256
261
  ts.ScriptTarget.Latest,
257
262
  true,
258
- sourceScriptKind(file, ts),
263
+ mdx ? ts.ScriptKind.TSX : sourceScriptKind(file, ts),
259
264
  );
260
265
  const module = {
261
266
  file,
262
267
  text,
263
268
  source,
264
- 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,
265
272
  bindings: new Map(),
266
273
  imports: new Map(),
267
274
  exports: new Map(),
@@ -333,14 +340,8 @@ export async function analyze({
333
340
  }
334
341
  modules.set(file, module);
335
342
  }
336
- let alias = { "@/*": ["./*"] };
337
- try {
338
- const config = ts.parseConfigFileTextToJson(
339
- "tsconfig.json",
340
- await fs.readFile(path.join(root, "tsconfig.json"), "utf8"),
341
- ).config;
342
- alias = config.compilerOptions?.paths || alias;
343
- } catch { /* Source projects may omit tsconfig.json. */ }
343
+ const alias = await sourceAliases(root,ts);
344
+ const cssModules = sourceCssModules({files: cssModuleFiles, aliases: alias, gap, redact: redactLiteral});
344
345
  function resolveModule(from, specifier) {
345
346
  let relative;
346
347
  if (specifier.startsWith("."))
@@ -367,6 +368,8 @@ export async function analyze({
367
368
  ".jsx",
368
369
  ".js",
369
370
  ".mjs",
371
+ ".mdx",
372
+ ".md",
370
373
  "/index.tsx",
371
374
  "/index.ts",
372
375
  "/index.jsx",
@@ -388,6 +391,7 @@ export async function analyze({
388
391
  }
389
392
  function sourceContextCall(node,module,ctx){
390
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)};
391
395
  if(imported?.specifier==='react'&&imported.name==='useContext'&&ts.isIdentifier(node.arguments[0]))return ctx.__iamContexts?.get(contextKey(module,node.arguments[0].text))??UNKNOWN;
392
396
  const ref=ts.isIdentifier(node.expression)&&binding(module,node.expression.text),fn=ref&&functionFrom(ref.node);
393
397
  if(!fn?.body||!ts.isBlock(fn.body))return UNKNOWN;
@@ -399,14 +403,39 @@ export async function analyze({
399
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)){
400
404
  const catalogs=[...ref.module.imports.values()].filter(i=>i.specifier.endsWith('.json')).map(i=>jsonResource(ref.module.file,i.specifier)).filter(Boolean);
401
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);
402
408
  }
403
409
  }
404
410
  return UNKNOWN;
405
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
+ }
406
425
  function basePrimitive(module,tag,seen=new Set()){
407
426
  if(seen.has(module.file+'#'+tag)||seen.size>8)return null;seen.add(module.file+'#'+tag);
408
427
  const [name,part,extra]=tag.split('.'),ref=module.imports.get(name);
409
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};
410
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'};
411
440
  const radix=imported?.specifier?.match(/^@radix-ui\/react-([a-z-]+)$/)?.[1];
412
441
  if(radixFamilies[radix]) {
@@ -532,8 +561,9 @@ export async function analyze({
532
561
  return target;
533
562
  }
534
563
  function sourceLocation(module, node, symbol) {
535
- const loc = module.source.getLineAndCharacterOfPosition(
536
- 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,
537
567
  );
538
568
  return {
539
569
  file: module.file,
@@ -544,16 +574,21 @@ export async function analyze({
544
574
  };
545
575
  }
546
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
+ }
547
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;}
548
583
  const ref=ts.isIdentifier(node)?binding(module,node.text):null;const def=componentsByFunction.get(node)??(ref&&componentDefs.get(ref.module.file+'#'+ref.name));
549
584
  return def?{[COMPONENT]:def}:null;
550
585
  }
551
- function environment(node,module,context){
586
+ function environment(node,module,_context){
552
587
  const value=uiEnvironmentPrimitive({ts,node,module,binding,imported:externalImport,evaluate:evalExpression});
553
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];}
554
589
  return value;
555
590
  }
556
- function query(node,module,context){return staticQuery({ts,node,module,context,binding,unknown:UNKNOWN,redact:redactLiteral,sensitive:sensitiveName,reference:sourceComponent,isReference:v=>Boolean(v?.[COMPONENT]||v?.[LUCIDE_SOURCE]||v?.[UI_PRIMITIVE]),imported:externalImport,project:(n,m,c)=>{const v=environment(n,m,c);return Array.isArray(v)?v:null;}});}
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;}});}
557
592
  function evalExpression(
558
593
  node,
559
594
  module,
@@ -564,6 +599,7 @@ export async function analyze({
564
599
  node = unwrap(node);
565
600
  if (!node || depth > 30) return UNKNOWN;
566
601
  const ev = (n) => evalExpression(n, module, context, stack, depth + 1);
602
+ if (ts.isAwaitExpression(node)) return query(node, module, context, true);
567
603
  const component=!(ts.isIdentifier(node)&&Object.hasOwn(context,node.text))&&sourceComponent(node,module);if(component)return component;
568
604
  if (ts.isStringLiteralLike(node) || ts.isNumericLiteral(node))
569
605
  return ts.isNumericLiteral(node) ? Number(node.text) : redactLiteral(node.text);
@@ -572,12 +608,16 @@ export async function analyze({
572
608
  if (node.kind === ts.SyntaxKind.NullKeyword) return null;
573
609
  if (ts.isIdentifier(node)) {
574
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;
575
613
  const variableKey=context.__iamVariableKeys?.[context.__iamSetters?.[node.text]];
576
614
  if(variableKey)return {[UI_SETTER]:{variableKey,value:{op:'event',key:'value'},payload:{value:ui.variables.find(v=>v.variableKey===variableKey)?.valueType}}};
577
615
  if (node.text === "undefined") return undefined;
578
616
  const image=module.imports.get(node.text);
579
- if(image?.name==='default'&&/^\.\.?\/[^?#]+\.(?:png|jpe?g|webp|gif)$/i.test(image.specifier)){
580
- const file=path.posix.normalize(path.posix.join(path.posix.dirname(module.file),image.specifier));
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;
581
621
  if(!file.startsWith('../')&&!file.split('/').some(p=>p.startsWith('.'))&&!sensitiveName(file))return '/__iam_source__/'+file;
582
622
  }
583
623
  const token = module.file + "#" + node.text;
@@ -690,7 +730,13 @@ export async function analyze({
690
730
  const primitive=externalImport(module,callee);
691
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};
692
732
  const translation=ts.isIdentifier(node.expression)&&context[node.expression.text]?.[TRANSLATION];
693
- if(translation){const key=ev(node.arguments[0]);if(typeof key!=='string')return UNKNOWN;const value=key.split('.').reduce((value,key)=>value&&Object.hasOwn(Object(value),key)?value[key]:undefined,translation);return typeof value==='string'&&!/\{\{/.test(value)?redactLiteral(value):UNKNOWN;}
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
+ }
694
740
  if(primitive?.specifier==='react'&&primitive.name==='useMemo'&&node.arguments.length===2){
695
741
  const fn=node.arguments[0];if(ts.isArrowFunction(fn)&&!fn.modifiers?.length&&!fn.parameters.length&&!ts.isBlock(fn.body))return ev(fn.body);
696
742
  }
@@ -806,7 +852,7 @@ export async function analyze({
806
852
  }
807
853
  const forwarded = new WeakMap(), objectBindings = new WeakMap();
808
854
  function componentContext(def, props) {
809
- 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: [] };
810
856
  const param = def.fn?.parameters?.[0];
811
857
  if (param) {
812
858
  if (ts.isIdentifier(param.name)) ctx[param.name.text] = props;
@@ -830,6 +876,7 @@ export async function analyze({
830
876
  if (handler) ctx.__iamPropHandlers.set(el.name.getText(), handler);
831
877
  }
832
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);
833
880
  if (def.fn?.body&&ts.isBlock(def.fn.body))
834
881
  for (const s of def.fn.body.statements)
835
882
  if (ts.isVariableStatement(s))
@@ -846,6 +893,7 @@ export async function analyze({
846
893
  for(const field of d.name.elements)if(ts.isIdentifier(field.name)&&!field.dotDotDotToken){
847
894
  const key=field.propertyName?.text||field.name.text;
848
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);
849
897
  const inherited=value&&typeof value==='object'&&objectBindings.get(value)?.get(key);
850
898
  if(inherited)ctx.__iamPropBindings.set(field.name.text,inherited);
851
899
  const setter=value?.[key]?.[UI_SETTER];
@@ -853,6 +901,18 @@ export async function analyze({
853
901
  }
854
902
  } else if (ts.isArrayBindingPattern(d.name)) {
855
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
+ }
856
916
  if (hook?.specifier === "react" && hook.name === "useActionState") {
857
917
  const key = d.name.elements[0]?.name?.text, pending = d.name.elements[2]?.name?.text, initial = evalExpression(call.arguments[1], def.module, ctx);
858
918
  if (key && !sensitiveName(key) && initial && !isUnknown(initial) && typeof initial === "object" && !Array.isArray(initial)) {
@@ -941,6 +1001,7 @@ export async function analyze({
941
1001
  )
942
1002
  throw new Error("Invalid source basePath");
943
1003
  const selection = await sourceEntry({ root, modules, resolveModule, ts, entry });
1004
+ const flowbiteTheme=rootFlowbiteTheme({ts,modules:selection.discoveryModules,imported:externalImport,evaluate:evalExpression});
944
1005
  if (selection.htmlSource) inventory.push(selection.htmlSource);
945
1006
  inventory.sort((a, b) => a.file < b.file ? -1 : a.file > b.file ? 1 : 0);
946
1007
  const compositions = frameworkRoutes({ modules, ...selection, resolveModule, ts, sourceLocation, navigationPaths, gap, resolution, inlineComponent });
@@ -999,7 +1060,6 @@ export async function analyze({
999
1060
  const record = new Map(),
1000
1061
  used = new Set(),
1001
1062
  branches = [],attributeRequests=[];
1002
- let renderedCount = 0;
1003
1063
  function renderComponent(def, props, instance, depth) {
1004
1064
  if (depth === 0 && composition.entryMode === "soft") instance = composition.compositionKey + ":" + instance;
1005
1065
  // Component recursion and JSX nesting are separate budgets. Counting DOM
@@ -1011,7 +1071,7 @@ export async function analyze({
1011
1071
  used.add(def.key + ":" + instance);
1012
1072
  if (!page.componentKeys.includes(def.key))
1013
1073
  page.componentKeys.push(def.key);
1014
- props.__iamRoutePath=/[\[\]]/.test(route)?UNKNOWN:route;
1074
+ props.__iamRoutePath=/[[\]]/.test(route)?UNKNOWN:route;
1015
1075
  const ctx = componentContext(def, props);
1016
1076
  ctx.__iamOutlet = props.__iamOutlet;
1017
1077
  ctx.__iamScope = identity("state-scope", {
@@ -1039,6 +1099,10 @@ export async function analyze({
1039
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) });
1040
1100
  }
1041
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
+ }
1042
1106
  // Rebind pure local records after scalar state identities exist. This preserves
1043
1107
  // useMemo/context field relationships without invoking a hook or callback.
1044
1108
  for(const [name,expression]of ctx.__iamLocals){
@@ -1126,6 +1190,7 @@ export async function analyze({
1126
1190
  }
1127
1191
  function uiHandler(initializer, owner, ctx, property = '') {
1128
1192
  let fn = initializer && ts.isJsxExpression(initializer) ? unwrap(initializer.expression) : null;
1193
+ if(fn&&ts.isPropertyAccessExpression(fn)&&ctx.__iamPropHandlers.has(fn.getText(owner.module.source)))return ctx.__iamPropHandlers.get(fn.getText(owner.module.source));
1129
1194
  const eventValue = (variableKey) => ({ variableKey, value: {op:'event',key:'value'}, payload:{value:ui.variables.find(v=>v.variableKey===variableKey)?.valueType} });
1130
1195
  if(fn&&ts.isIdentifier(fn)&&ctx.__iamPropHandlers.has(fn.text))return ctx.__iamPropHandlers.get(fn.text);
1131
1196
  if(fn&&ts.isIdentifier(fn)&&ctx.__iamSetters[fn.text]&&['onValueChange','onOpenChange'].includes(property)) {
@@ -1136,7 +1201,7 @@ export async function analyze({
1136
1201
  const props=ctx[fn.expression.text],handler=props&&typeof props==='object'&&forwarded.get(props)?.handlers.find(h=>h.key===fn.name.text);
1137
1202
  if(handler)return handler;
1138
1203
  }
1139
- if(!fn||!ts.isArrowFunction(fn)||fn.modifiers?.some(m=>m.kind===ts.SyntaxKind.AsyncKeyword))return null;
1204
+ if(!fn||![ts.isArrowFunction,ts.isFunctionExpression,ts.isFunctionDeclaration].some(is=>is(fn))||fn.modifiers?.some(m=>m.kind===ts.SyntaxKind.AsyncKeyword))return null;
1140
1205
  let statements=ts.isBlock(fn.body)?[...fn.body.statements]:[fn.body];
1141
1206
  if(statements.length>16)return null;
1142
1207
  const writes=[],payload={};
@@ -1181,13 +1246,14 @@ export async function analyze({
1181
1246
  ts,
1182
1247
  node,
1183
1248
  module: owner.module,
1184
- locals: ctx.__iamLocals,
1249
+ locals: new Map([...ctx.__iamLocals,...ctx.__iamQueryLocals]),
1185
1250
  binding,
1186
1251
  functionFrom,
1187
1252
  imported: externalImport,
1188
1253
  });
1189
1254
  }
1190
1255
  function remoteResultCondition(node, owner, ctx) {
1256
+ if (queryBacked(node, owner, ctx)) return 'remote';
1191
1257
  let shape = false;
1192
1258
  const root =
1193
1259
  ts.isIdentifier(node) && ctx.__iamLocals.has(node.text)
@@ -1224,7 +1290,7 @@ export async function analyze({
1224
1290
  next[key] = evalExpression(init, owner.module, next);
1225
1291
  return next;
1226
1292
  }
1227
- function finiteCollection(node, owner, ctx, instance, depth) {
1293
+ function finiteCollection(node, owner, ctx, instance, _depth) {
1228
1294
  if (ctx.__iamCollectionTemplate || !ts.isConditionalExpression(node))
1229
1295
  return null;
1230
1296
  const maps = [];
@@ -1484,6 +1550,12 @@ export async function analyze({
1484
1550
  if (!node) return "";
1485
1551
  if (depth > 45) { gap("RENDER_DEPTH_LIMIT", owner.module.file, owner.symbol + ": JSX traversal budget exceeded"); return ""; }
1486
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
+ }
1487
1559
  const collection=finiteCollection(node,owner,ctx,instance,depth);if(collection!==null)return collection;
1488
1560
  if(ts.isIdentifier(node)&&ctx.__iamLocals.has(node.text)&&hasSourceJsx(ctx.__iamLocals.get(node.text))){
1489
1561
  if(ctx.__iamActiveJsx?.has(node.text))return '';
@@ -1602,7 +1674,7 @@ export async function analyze({
1602
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);
1603
1675
  record.set(id,{elementKey,source,synthetic:true});
1604
1676
  elements.set(elementKey,{elementKey,componentKey:owner.key,tag:'div',source,identityStrategy:'provisional_ast_ordinal',editableProperties:[],binding:'preview_only'});
1605
- return '<div data-iam-node="'+id+'" data-iam-unobserved="true" hidden="" data-iam-condition="'+conditionRow.conditionKey+'" data-iam-alternative="'+(index===0)+'">'+renderExpr(branch,owner,ctx,instance+'/unobserved-'+index,depth+1)+'</div>';
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>';
1606
1678
  }).join('');
1607
1679
  }
1608
1680
  if (
@@ -1617,15 +1689,7 @@ export async function analyze({
1617
1689
  fn &&
1618
1690
  (ts.isArrowFunction(fn) || ts.isFunctionExpression(fn))
1619
1691
  ) {
1620
- const limit=ctx.__iamCollectionTemplate?128:50;
1621
- if (values.length > limit)
1622
- gap(
1623
- "COLLECTION_TRUNCATED",
1624
- m.file,
1625
- "Static collection limited to 50 preview items",
1626
- );
1627
1692
  return values
1628
- .slice(0, limit)
1629
1693
  .map((value, i) => {
1630
1694
  const next = { ...ctx };
1631
1695
  if (fn.parameters[0] && ts.isIdentifier(fn.parameters[0].name))
@@ -1681,8 +1745,6 @@ export async function analyze({
1681
1745
  }
1682
1746
  return "";
1683
1747
  }
1684
- if (++renderedCount > 12000)
1685
- throw new Error("Rendered node budget exceeded");
1686
1748
  const opening = ts.isJsxElement(node) ? node.openingElement : node,
1687
1749
  tag = opening.tagName.getText(m.source),
1688
1750
  props = m.anchors.get(opening) ? { "data-indiana-id": m.anchors.get(opening) } : {}, uiHandlers = [], uiBindings = [], forwardedHandlers=[],uiAttributes=[];
@@ -1714,7 +1776,7 @@ export async function analyze({
1714
1776
  }
1715
1777
  for (let i = uiBindings.length - 1; i >= 0; i--) if (uiBindings[i].property === key) uiBindings.splice(i, 1);
1716
1778
  for (let i = uiHandlers.length - 1; i >= 0; i--) if (uiHandlers[i].key === key) uiHandlers.splice(i, 1);
1717
- if (["onClick", "onChange", "onInput", "onValueChange", "onOpenChange"].includes(key)) {
1779
+ if (["onClick", "onChange", "onInput", "onValueChange", "onOpenChange", "onClose"].includes(key)) {
1718
1780
  props[key] = undefined;
1719
1781
  const supported = ctx.__iamCollectionTemplate?null:uiHandler(attr.initializer, owner, ctx, key);
1720
1782
  if (supported) { props[key] = undefined; uiHandlers.push({ ...supported, key, source: sourceLocation(m, attr, owner.symbol) }); }
@@ -1804,7 +1866,7 @@ export async function analyze({
1804
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});
1805
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+'>';
1806
1868
  };
1807
- return draw({tag:'svg',attrs:{xmlns:'http://www.w3.org/2000/svg',viewBox:'0 0 '+chart.width+' '+chart.height,width:chart.width,height:chart.height,role:'img','aria-label':chart.unobserved?'Chart: data unobserved':'Source chart projection','data-iam-chart':chart.unobserved?'unobserved':'source',style:'display:block;width:100%;height:auto;max-height:'+chart.height+'px'},children:chart.children},'root');
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');
1808
1870
  }
1809
1871
  const sourceIcon=evalExpression(opening.tagName,m,ctx);
1810
1872
  if(sourceIcon?.[LUCIDE_SOURCE])return renderLucide(sourceIcon,node,owner,props,instance,!ctx.__iamCollectionTemplate);
@@ -1813,8 +1875,8 @@ export async function analyze({
1813
1875
  let childCtx=ctx,tabGroup=ctx.__iamTabs;
1814
1876
  const provider=tag.endsWith('.Provider')?contextKey(m,tag.slice(0,-9)):contextKey(m,tag);
1815
1877
  if(provider){const contexts=new Map(ctx.__iamContexts);contexts.set(provider,props.value);childCtx={...childCtx,__iamContexts:contexts};}
1816
- if(overlay&&['Root','SubmenuRoot'].includes(overlay.part)){
1817
- const openProperty=overlay.family==='Transition'?'in':'open',open=props[openProperty];
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];
1818
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')};
1819
1881
  if(overlay.family!=='Transition'&&open===undefined&&!props.__iamOpaqueProps&&(props.defaultOpen===undefined||typeof props.defaultOpen==='boolean')){
1820
1882
  const scopeKey=identity('primitive-state',{page:pageKey,instance,family:overlay.family}),variableKey=identity('ui-variable',{scope:scopeKey,label:'open'});
@@ -1844,15 +1906,50 @@ export async function analyze({
1844
1906
  }
1845
1907
  if(tabPart==='Root') {
1846
1908
  const valueBinding=uiBindings.find(b=>b.property==='value')?.value;
1847
- const controller=uiHandlers.find(h=>h.key==='onValueChange');
1909
+ const controller=uiHandlers.find(h=>h.key==='onValueChange'||basePart?.mantine&&h.key==='onChange');
1848
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};
1849
1911
  childCtx={...childCtx,__iamTabs:tabGroup};
1850
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(' ');}
1851
1921
  const localRef=componentBinding(m,tag),localTarget=ctx[tag]?.[COMPONENT]??(localRef&&componentDefs.get(localRef.module.file+'#'+localRef.name));
1852
1922
  const dynamicNode=ctx.__iamLocals.get(tag);
1853
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;
1854
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||'';
1855
- const children = localTarget||registry ? '' : renderChildren();
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
+ }
1856
1953
 
1857
1954
  // Project the documented Controller render callback from source, with no
1858
1955
  // form initialization, live field values or business callback execution.
@@ -1897,7 +1994,8 @@ export async function analyze({
1897
1994
  props["data-iam-adapter"] = tag;
1898
1995
  }
1899
1996
  if (/^[A-Z]/.test(tag) && !intrinsicAlias) {
1900
- if(tabPart){
1997
+ if (mantine) htmlTag = mantine.tag;
1998
+ else if(tabPart){
1901
1999
  htmlTag=tabPart==='Tab'?'button':'div';
1902
2000
  if(tabPart==='List')props.role='tablist';
1903
2001
  if(tabPart==='Tab'){props.role='tab';props.type='button';props['aria-selected']=tabGroup?.initialValue===props.value;}
@@ -1907,14 +2005,24 @@ export async function analyze({
1907
2005
  htmlTag=['Trigger','Close','Item','CheckboxItem','RadioItem','SubmenuTrigger'].includes(overlay.part)&&props.asChild!==true?'button':overlay.part==='Title'?'h2':overlay.part==='Description'?'p':'div';
1908
2006
  if(htmlTag==='button')props.type='button';
1909
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)});}
1910
- if(['Root','SubmenuRoot','Portal','Provider'].includes(overlay.part)||props.asChild===true)props.style={display:'contents'};
1911
- if(overlay.part==='Popup')props.role=overlay.family==='Menu'?'menu':overlay.family==='Tooltip'?'tooltip':'dialog';
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;
1912
2012
  }
1913
2013
  else if (tag === "Link" || ["react-router", "react-router-dom", "@tanstack/react-router"].includes(routerPrimitive?.specifier) && ["Link", "NavLink"].includes(routerPrimitive.name)) {
1914
2014
  htmlTag = "a";
1915
2015
  if (typeof props.to === "string") props.href = props.to;
1916
2016
  }
1917
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
+ }
1918
2026
  else if(m.imports.get(tag)?.specifier==='react-basics'&&m.imports.get(tag)?.name==='Text'){
1919
2027
  htmlTag=typeof props.as==='string'&&allowedTags.has(props.as)?props.as:'span';
1920
2028
  gap('COMPONENT_STYLE_ADAPTER',m.file,'react-basics Text DOM semantics only; actual styles need runtime evidence');
@@ -2020,7 +2128,7 @@ export async function analyze({
2020
2128
  record.set(nodeId, {
2021
2129
  elementKey,
2022
2130
  source,
2023
- synthetic: htmlTag !== tag && tag !== "Link" && tag !== "Image"&&!intrinsicAlias&&!knownExternalPrimitive,
2131
+ synthetic: Boolean(m.mdx) || htmlTag !== tag && tag !== "Link" && tag !== "Image"&&!intrinsicAlias&&!knownExternalPrimitive,
2024
2132
  });
2025
2133
  if(tabPart==='Root'){
2026
2134
  // Tab initialization below shares the same concrete source container.
@@ -2042,7 +2150,7 @@ export async function analyze({
2042
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});}
2043
2151
  }
2044
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});
2045
- for (const handler of uiHandlers.filter(h=>!['onValueChange','onOpenChange'].includes(h.key))) {
2153
+ for (const handler of uiHandlers.filter(h=>!['onValueChange','onOpenChange','onClose'].includes(h.key)&&!(tabPart==='Root'&&h.key==='onChange'))) {
2046
2154
  const event = handler.key === "onClick" ? "click" : htmlTag === "select" || handler.payload.checked ? "change" : "input";
2047
2155
  const writes=handler.writes||[{variableKey:handler.variableKey,value:handler.value}];
2048
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 });
@@ -2056,14 +2164,22 @@ export async function analyze({
2056
2164
  }
2057
2165
  ui.bindings.push({ bindingKey: identity("ui-binding", { node: nodeId, property: binding.property }), routeKey: pageKey, nodeId, elementKey, ...binding });
2058
2166
  }
2059
- 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 || "")));
2060
2168
  if (sensitiveInput) {
2061
2169
  delete props.value; delete props.defaultValue;
2170
+ if (htmlTag === 'textarea') children = '';
2062
2171
  ui.bindings = ui.bindings.filter(b => b.nodeId !== nodeId);
2063
2172
  ui.transitions = ui.transitions.filter(t => t.trigger.nodeId !== nodeId);
2064
2173
  gap("SENSITIVE_INPUT_REDACTED", m.file, "Input value and state bridge omitted");
2065
2174
  }
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);
2066
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
+ }
2067
2183
  for (const [key, value] of Object.entries(props)) {
2068
2184
  if (key === "style") {
2069
2185
  const css = staticStyle(value, () => gap("INLINE_STYLE_UNSUPPORTED", m.file,
@@ -2077,12 +2193,17 @@ export async function analyze({
2077
2193
  gap("PRIVATE_URL_REDACTED", m.file, "URL credentials or sensitive query omitted");
2078
2194
  continue;
2079
2195
  }
2080
- if (["disabled", "hidden", "checked"].includes(key) && value === false) continue;
2196
+ if (["disabled", "hidden", "checked", "required", "readOnly", "multiple", "selected", "open"].includes(key) && value === false) continue;
2081
2197
  const attr =
2082
2198
  {
2083
2199
  className: "class",
2084
2200
  htmlFor: "for",
2085
2201
  tabIndex: "tabindex",
2202
+ readOnly: "readonly",
2203
+ minLength: "minlength",
2204
+ maxLength: "maxlength",
2205
+ autoComplete: "autocomplete",
2206
+ inputMode: "inputmode",
2086
2207
  viewBox: "viewBox",
2087
2208
  strokeWidth: "stroke-width",
2088
2209
  strokeLinecap: "stroke-linecap",
@@ -2091,7 +2212,7 @@ export async function analyze({
2091
2212
  clipRule: "clip-rule",
2092
2213
  }[key] || key;
2093
2214
  if (
2094
- !/^(?:class|id|name|type|placeholder|value|title|alt|href|src|role|for|disabled|hidden|checked|width|height|viewBox|d|fill|stroke|stroke-width|stroke-linecap|stroke-linejoin|fill-rule|clip-rule|cx|cy|r|x|y|x1|x2|y1|y2|points|aria-[a-z-]+|data-indiana-id|data-indiana-map-(?:area|flow|kind|auth|edge)|data-(?:slot|state|side|variant|collapsible|orientation|active)|data-testid|data-test|data-qa|data-iam-(?:adapter|toggle|tab-value|tab-key|condition|alternative|condition-default|unobserved))$/.test(
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(
2095
2216
  attr,
2096
2217
  )
2097
2218
  )
@@ -2107,6 +2228,7 @@ export async function analyze({
2107
2228
  }
2108
2229
  attrs["data-iam-external"] = "true";
2109
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;}
2110
2232
  if (String(value).startsWith("/") && !String(value).startsWith("//"))
2111
2233
  assets.set(String(value), null);
2112
2234
  else {
@@ -2120,7 +2242,7 @@ export async function analyze({
2120
2242
  .forEach((c) => allClasses.add(c));
2121
2243
  attrs[attr] = String(value);
2122
2244
  }
2123
- return (
2245
+ const rendered = (
2124
2246
  "<" +
2125
2247
  htmlTag +
2126
2248
  Object.entries(attrs)
@@ -2131,6 +2253,12 @@ export async function analyze({
2131
2253
  ? (tag.startsWith("Icons.") ? "◇" : children) + "</" + htmlTag + ">"
2132
2254
  : "")
2133
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;
2134
2262
  }
2135
2263
  function renderEntry(file, props, instance, symbol) {
2136
2264
  const entryModule = modules.get(file), entryRef = entryModule && componentBinding(entryModule, symbol ?? entryModule.defaultName);
@@ -2296,7 +2424,7 @@ export async function analyze({
2296
2424
  });
2297
2425
  // Unknown source alternatives live in ui.conditions. Representable local
2298
2426
  // state candidates remain separate from concrete source documents.
2299
- for (const branch of branches.filter(b=>b.evidence==='source_inferred').slice(0, 30)) {
2427
+ for (const branch of branches.filter(b=>b.evidence==='source_inferred')) {
2300
2428
  const key = identity("state", {page:pageKey,source:branch.source,label:branch.label});
2301
2429
  if(!states.some(state=>state.stateKey===key)){
2302
2430
  states.push({stateKey:key,pageKey,label:branch.label,evidence:branch.evidence,source:branch.source,documentKey:null});
@@ -2430,6 +2558,9 @@ export async function analyze({
2430
2558
  );
2431
2559
  css +=
2432
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}';
2433
2564
  if (!pages.length)
2434
2565
  throw new Error(
2435
2566
  "No supported source routes found. Use Next App Router, Next Pages Router or a statically declared React Router route tree.",
@@ -2458,7 +2589,7 @@ export async function analyze({
2458
2589
  schemaVersion: 2,
2459
2590
  producer: {
2460
2591
  name: "indiana-app-manifest-local",
2461
- version: "0.24.0",
2592
+ version: "0.27.0",
2462
2593
  method: "typescript-ast-and-bounded-static-projection;lucide-static-"+LUCIDE_SOURCE_VERSION,
2463
2594
  },
2464
2595
  app: { name, platforms: ["web"] },
@@ -2499,6 +2630,7 @@ export async function analyze({
2499
2630
  const sourceGaps=new Map();
2500
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});}
2501
2632
  ui.uncovered=[...sourceGaps.values()];
2633
+ attachSourceStyles(manifest, projectionCss);
2502
2634
  manifest.routeTree = assembleRouteTree(manifest, ui, renderDefinitions, compositions.navigation);
2503
2635
  presentation.conditions=[...new Map(presentation.conditions.map(c=>[c.conditionKey,c])).values()];
2504
2636
  presentation.regions=presentation.regions.filter(r=>concrete.has(r.nodeId)&&r.options.every(o=>concrete.has(o.nodeId)));