@codeyam/codeyam-cli 0.1.0-staging.e090cb3 → 0.1.0-staging.e2d4438

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 (125) hide show
  1. package/analyzer-template/.build-info.json +7 -7
  2. package/analyzer-template/log.txt +3 -3
  3. package/analyzer-template/package.json +1 -1
  4. package/analyzer-template/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.ts +29 -18
  5. package/analyzer-template/packages/ai/src/lib/dataStructureChunking.ts +15 -6
  6. package/analyzer-template/packages/ai/src/lib/generateExecutionFlows.ts +96 -0
  7. package/analyzer-template/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.ts +50 -25
  8. package/analyzer-template/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.ts +153 -76
  9. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.d.ts.map +1 -1
  10. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js +93 -2
  11. package/analyzer-template/packages/utils/dist/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  12. package/analyzer-template/packages/utils/src/lib/fs/rsyncCopy.ts +108 -2
  13. package/analyzer-template/project/constructMockCode.ts +2 -2
  14. package/analyzer-template/project/writeScenarioComponents.ts +14 -0
  15. package/background/src/lib/virtualized/project/constructMockCode.js +2 -2
  16. package/background/src/lib/virtualized/project/constructMockCode.js.map +1 -1
  17. package/background/src/lib/virtualized/project/writeScenarioComponents.js +10 -0
  18. package/background/src/lib/virtualized/project/writeScenarioComponents.js.map +1 -1
  19. package/codeyam-cli/src/commands/analyze.js +2 -2
  20. package/codeyam-cli/src/commands/analyze.js.map +1 -1
  21. package/codeyam-cli/src/commands/default.js +1 -1
  22. package/codeyam-cli/src/commands/default.js.map +1 -1
  23. package/codeyam-cli/src/commands/memory.js +63 -20
  24. package/codeyam-cli/src/commands/memory.js.map +1 -1
  25. package/codeyam-cli/src/commands/verify.js +2 -2
  26. package/codeyam-cli/src/commands/verify.js.map +1 -1
  27. package/codeyam-cli/src/utils/__tests__/npmVersionCheck.test.js +179 -0
  28. package/codeyam-cli/src/utils/__tests__/npmVersionCheck.test.js.map +1 -0
  29. package/codeyam-cli/src/utils/backgroundServer.js +90 -23
  30. package/codeyam-cli/src/utils/backgroundServer.js.map +1 -1
  31. package/codeyam-cli/src/utils/npmVersionCheck.js +76 -0
  32. package/codeyam-cli/src/utils/npmVersionCheck.js.map +1 -0
  33. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/assertRules.js +1 -1
  34. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/assertRules.js.map +1 -1
  35. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/setupTempProject.js +0 -1
  36. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/helpers/setupTempProject.js.map +1 -1
  37. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js +2 -4
  38. package/codeyam-cli/src/utils/ruleReflection/__tests__/integration/ruleReflectionE2E.test.js.map +1 -1
  39. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js +2 -1
  40. package/codeyam-cli/src/utils/ruleReflection/__tests__/promptBuilder.test.js.map +1 -1
  41. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js +1 -1
  42. package/codeyam-cli/src/utils/ruleReflection/contextBuilder.js.map +1 -1
  43. package/codeyam-cli/src/utils/versionInfo.js +25 -0
  44. package/codeyam-cli/src/utils/versionInfo.js.map +1 -1
  45. package/codeyam-cli/src/webserver/__tests__/dependency-smoke.test.js +66 -0
  46. package/codeyam-cli/src/webserver/__tests__/dependency-smoke.test.js.map +1 -0
  47. package/codeyam-cli/src/webserver/app/lib/dbNotifier.js.map +1 -1
  48. package/codeyam-cli/src/webserver/backgroundServer.js +26 -0
  49. package/codeyam-cli/src/webserver/backgroundServer.js.map +1 -1
  50. package/codeyam-cli/src/webserver/bootstrap.js +11 -0
  51. package/codeyam-cli/src/webserver/bootstrap.js.map +1 -1
  52. package/codeyam-cli/src/webserver/build/client/assets/{CopyButton-CA3JxPb7.js → CopyButton-jNYXRRNI.js} +1 -1
  53. package/codeyam-cli/src/webserver/build/client/assets/{EntityItem-B86KKU7e.js → EntityItem-bwuHPyTa.js} +1 -1
  54. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeBadge-B5ctlSYt.js → EntityTypeBadge-CvzqMxcu.js} +1 -1
  55. package/codeyam-cli/src/webserver/build/client/assets/{EntityTypeIcon-BqY8gDAW.js → EntityTypeIcon-BH0XDim7.js} +1 -1
  56. package/codeyam-cli/src/webserver/build/client/assets/{InlineSpinner-ClaLpuOo.js → InlineSpinner-EhOseatT.js} +1 -1
  57. package/codeyam-cli/src/webserver/build/client/assets/{InteractivePreview-BDhPilK7.js → InteractivePreview-yjIHlOGa.js} +2 -2
  58. package/codeyam-cli/src/webserver/build/client/assets/{LibraryFunctionPreview-VeqEBv9v.js → LibraryFunctionPreview-Cq5o8jL4.js} +1 -1
  59. package/codeyam-cli/src/webserver/build/client/assets/{LoadingDots-Bs7Nn1Jr.js → LoadingDots-BvMu2i-g.js} +1 -1
  60. package/codeyam-cli/src/webserver/build/client/assets/{LogViewer-Bm3PmcCz.js → LogViewer-kgBTLoJD.js} +1 -1
  61. package/codeyam-cli/src/webserver/build/client/assets/{ReportIssueModal-CgMEzchJ.js → ReportIssueModal-BzPgx-xO.js} +1 -1
  62. package/codeyam-cli/src/webserver/build/client/assets/{SafeScreenshot-Gq3Ocjo6.js → SafeScreenshot-CwZrv-Ok.js} +1 -1
  63. package/codeyam-cli/src/webserver/build/client/assets/{ScenarioViewer-CBui0id_.js → ScenarioViewer-BX2Ny2Qj.js} +1 -1
  64. package/codeyam-cli/src/webserver/build/client/assets/{TruncatedFilePath-CiwXDxLh.js → TruncatedFilePath-CDpEprKa.js} +1 -1
  65. package/codeyam-cli/src/webserver/build/client/assets/{_index-B3TDXxnk.js → _index-BRx8ZGZo.js} +1 -1
  66. package/codeyam-cli/src/webserver/build/client/assets/{activity.(_tab)-BtBFH820.js → activity.(_tab)-4S4yPfFw.js} +1 -1
  67. package/codeyam-cli/src/webserver/build/client/assets/agent-transcripts-DHKuQSmR.js +17 -0
  68. package/codeyam-cli/src/webserver/build/client/assets/{book-open-PttOB2SF.js → book-open-D4IPYH_y.js} +1 -1
  69. package/codeyam-cli/src/webserver/build/client/assets/{chevron-down-TJp6ofnp.js → chevron-down-CG65viiV.js} +1 -1
  70. package/codeyam-cli/src/webserver/build/client/assets/{chunk-JZWAC4HX-JE9ZIoBl.js → chunk-JZWAC4HX-DB3aFuEO.js} +9 -9
  71. package/codeyam-cli/src/webserver/build/client/assets/{circle-check-CXhHQYrI.js → circle-check-igfMr5DY.js} +1 -1
  72. package/codeyam-cli/src/webserver/build/client/assets/{copy-6y9ALfGT.js → copy-Coc4o_8c.js} +1 -1
  73. package/codeyam-cli/src/webserver/build/client/assets/{createLucideIcon-Ca9fAY46.js → createLucideIcon-D1zB-pYc.js} +1 -1
  74. package/codeyam-cli/src/webserver/build/client/assets/{dev.empty-C0epRiVn.js → dev.empty-JTAjQ54M.js} +1 -1
  75. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha._-BVnB8a9L.js → entity._sha._-B0h9AqE6.js} +2 -2
  76. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha.scenarios._scenarioId.fullscreen-CBoafmVs.js → entity._sha.scenarios._scenarioId.fullscreen-DjLxr2JB.js} +1 -1
  77. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.create-scenario-DGgZjdFg.js → entity._sha_.create-scenario-CtYowLOt.js} +1 -1
  78. package/codeyam-cli/src/webserver/build/client/assets/{entity._sha_.edit._scenarioId-38yPijoD.js → entity._sha_.edit._scenarioId-PePWg17F.js} +1 -1
  79. package/codeyam-cli/src/webserver/build/client/assets/{entry.client-BSHEfydn.js → entry.client-I-Wo99C_.js} +1 -1
  80. package/codeyam-cli/src/webserver/build/client/assets/{fileTableUtils-DCPhhSMo.js → fileTableUtils-9sMMAiWJ.js} +1 -1
  81. package/codeyam-cli/src/webserver/build/client/assets/{files-0N0YJQv7.js → files-Co65J0s3.js} +1 -1
  82. package/codeyam-cli/src/webserver/build/client/assets/{git-DXnyr8uP.js → git-BdHOxVfg.js} +1 -1
  83. package/codeyam-cli/src/webserver/build/client/assets/globals-BSZfYCkU.css +1 -0
  84. package/codeyam-cli/src/webserver/build/client/assets/{index-ChN9-fAY.js → index-CUM5iXwc.js} +1 -1
  85. package/codeyam-cli/src/webserver/build/client/assets/{index-CcsFv748.js → index-_417gcQW.js} +1 -1
  86. package/codeyam-cli/src/webserver/build/client/assets/{labs-BLJ7HxOC.js → labs-BK0C1H1T.js} +1 -1
  87. package/codeyam-cli/src/webserver/build/client/assets/{loader-circle-CTqLEAGU.js → loader-circle-TzRHMVog.js} +1 -1
  88. package/codeyam-cli/src/webserver/build/client/assets/{manifest-b171b9d3.js → manifest-040dab1c.js} +1 -1
  89. package/codeyam-cli/src/webserver/build/client/assets/memory-UIDVz141.js +92 -0
  90. package/codeyam-cli/src/webserver/build/client/assets/{pause-D6vreykR.js → pause-hjzB7t2z.js} +1 -1
  91. package/codeyam-cli/src/webserver/build/client/assets/root-D1WadSdf.js +62 -0
  92. package/codeyam-cli/src/webserver/build/client/assets/{search-B8VUL8nl.js → search-DcAwD_Ln.js} +1 -1
  93. package/codeyam-cli/src/webserver/build/client/assets/{settings-BejnUJ6R.js → settings-CclxrcPK.js} +1 -1
  94. package/codeyam-cli/src/webserver/build/client/assets/{simulations-CPoAg7Zo.js → simulations-DVNJVQgD.js} +1 -1
  95. package/codeyam-cli/src/webserver/build/client/assets/{terminal-BrCP7uQo.js → terminal-DbEAHMbA.js} +1 -1
  96. package/codeyam-cli/src/webserver/build/client/assets/{triangle-alert-BZz2NjYa.js → triangle-alert-CAD5b1o_.js} +1 -1
  97. package/codeyam-cli/src/webserver/build/client/assets/{useCustomSizes-DNwUduNu.js → useCustomSizes-BqgrAzs3.js} +1 -1
  98. package/codeyam-cli/src/webserver/build/client/assets/{useLastLogLine-COky1GVF.js → useLastLogLine-DAFqfEDH.js} +1 -1
  99. package/codeyam-cli/src/webserver/build/client/assets/{useReportContext-CpZgwliL.js → useReportContext-DZlYx2c4.js} +1 -1
  100. package/codeyam-cli/src/webserver/build/client/assets/{useToast-Bv9JFvUO.js → useToast-ihdMtlf6.js} +1 -1
  101. package/codeyam-cli/src/webserver/build/server/assets/{index-8Fv-lH1-.js → index-B3dE0r28.js} +1 -1
  102. package/codeyam-cli/src/webserver/build/server/assets/server-build-DYbfdxa3.js +273 -0
  103. package/codeyam-cli/src/webserver/build/server/index.js +1 -1
  104. package/codeyam-cli/src/webserver/build-info.json +5 -5
  105. package/codeyam-cli/templates/codeyam-memory.md +5 -1
  106. package/codeyam-cli/templates/rule-reflection-hook.py +1 -1
  107. package/codeyam-cli/templates/rules-instructions.md +1 -1
  108. package/package.json +8 -8
  109. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js +24 -16
  110. package/packages/ai/src/lib/dataStructure/helpers/convertDotNotation.js.map +1 -1
  111. package/packages/ai/src/lib/dataStructureChunking.js +9 -5
  112. package/packages/ai/src/lib/dataStructureChunking.js.map +1 -1
  113. package/packages/ai/src/lib/generateExecutionFlows.js +81 -0
  114. package/packages/ai/src/lib/generateExecutionFlows.js.map +1 -1
  115. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js +42 -13
  116. package/packages/analyze/src/lib/files/scenarios/gatherDataForMocks.js.map +1 -1
  117. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js +123 -67
  118. package/packages/analyze/src/lib/files/scenarios/mergeInDependentDataStructure.js.map +1 -1
  119. package/packages/utils/src/lib/fs/rsyncCopy.js +93 -2
  120. package/packages/utils/src/lib/fs/rsyncCopy.js.map +1 -1
  121. package/codeyam-cli/src/webserver/build/client/assets/agent-transcripts-CN61MOMa.js +0 -11
  122. package/codeyam-cli/src/webserver/build/client/assets/globals-CKT08Djd.css +0 -1
  123. package/codeyam-cli/src/webserver/build/client/assets/memory-CCQd4aZA.js +0 -78
  124. package/codeyam-cli/src/webserver/build/client/assets/root-CHhiHoo_.js +0 -62
  125. package/codeyam-cli/src/webserver/build/server/assets/server-build-Akn3iYFP.js +0 -257
@@ -1,10 +1,10 @@
1
1
  {
2
- "buildTimestamp": "2026-02-12T23:50:24.548Z",
3
- "buildTime": 1770940224548,
4
- "gitCommit": "e090cb37d89776c7e2c35ef0b8abba954004f457",
2
+ "buildTimestamp": "2026-02-14T14:54:13.184Z",
3
+ "buildTime": 1771080853184,
4
+ "gitCommit": "e2d44382e8f321688a2d6a2409034c6213bab27e",
5
5
  "nodeVersion": "v20.20.0",
6
- "contentHash": "137e331d30c083a78ae7f8a1b9366f0cbefd882b8f0f7858cb3fb8501bf391ad",
7
- "buildNumber": 618,
8
- "semanticVersion": "0.1.618",
9
- "version": "0.1.618 (2026-02-12T23:50+137e331)"
6
+ "contentHash": "7f2a19190511996b687bdd764ab6297993590ece837b6bbdec8bda87b904ab50",
7
+ "buildNumber": 629,
8
+ "semanticVersion": "0.1.629",
9
+ "version": "0.1.629 (2026-02-14T14:54+7f2a191)"
10
10
  }
@@ -1,7 +1,7 @@
1
1
 
2
- [2/12/2026, 11:50:24 PM] > codeyam-combo@1.0.0 mergeDependencies
3
- [2/12/2026, 11:50:24 PM] > node ./scripts/mergePackageJsonFiles.cjs
2
+ [2/14/2026, 2:54:13 PM] > codeyam-combo@1.0.0 mergeDependencies
3
+ [2/14/2026, 2:54:13 PM] > node ./scripts/mergePackageJsonFiles.cjs
4
4
 
5
5
 
6
- [2/12/2026, 11:50:24 PM] Merged dependencies into root package.json
6
+ [2/14/2026, 2:54:13 PM] Merged dependencies into root package.json
7
7
 
@@ -8,7 +8,7 @@
8
8
  },
9
9
  "dependencies": {
10
10
  "@aws-sdk/client-cloudwatch-logs": "^3.980.0",
11
- "@aws-sdk/client-cloudfront": "^3.966.0",
11
+ "@aws-sdk/client-cloudfront": "^3.985.0",
12
12
  "@aws-sdk/client-codebuild": "^3.948.0",
13
13
  "@aws-sdk/client-dynamodb": "^3.956.0",
14
14
  "@aws-sdk/client-ec2": "^3.899.0",
@@ -14,35 +14,46 @@ const isStandaloneIndex = (s?: string) => !!s && STANDALONE_INDEX_RE.test(s);
14
14
  // The regex matches any path ending with .length that has [] somewhere before it
15
15
  const DYNAMIC_LENGTH_RE = /\[\].*\.length$/;
16
16
 
17
- // Treat these as structural placeholders (don't commit them as concrete leaves)
18
- function isSkippableLeafType(t: string) {
19
- // 'unknown' by itself is a placeholder (but 'boolean | unknown' is not)
20
- // Also handles optional variants like 'object | undefined', 'object | null',
21
- // 'array | undefined', etc. these are still structural placeholders that
22
- // should not overwrite already-populated structures.
17
+ // Cache for type string analysis to avoid repeated split/filter operations.
18
+ // These functions are called multiple times per path segment across thousands of paths.
19
+ const typeAnalysisCache = new Map<
20
+ string,
21
+ { isSkippable: boolean; baseType: string; isNullable: boolean }
22
+ >();
23
+
24
+ function getTypeAnalysis(t: string) {
25
+ const cached = typeAnalysisCache.get(t);
26
+ if (cached) return cached;
23
27
  const parts = t.split('|').map((s) => s.trim());
24
28
  const base = parts.filter((s) => s !== 'undefined' && s !== 'null');
25
- return (
26
- base.length === 1 &&
27
- (base[0] === 'object' ||
28
- base[0] === 'array' ||
29
- base[0] === 'function' ||
30
- base[0] === 'unknown')
31
- );
29
+ const result = {
30
+ isSkippable:
31
+ base.length === 1 &&
32
+ (base[0] === 'object' ||
33
+ base[0] === 'array' ||
34
+ base[0] === 'function' ||
35
+ base[0] === 'unknown'),
36
+ baseType: base[0],
37
+ isNullable: parts.includes('undefined') || parts.includes('null'),
38
+ };
39
+ typeAnalysisCache.set(t, result);
40
+ return result;
41
+ }
42
+
43
+ // Treat these as structural placeholders (don't commit them as concrete leaves)
44
+ function isSkippableLeafType(t: string) {
45
+ return getTypeAnalysis(t).isSkippable;
32
46
  }
33
47
 
34
48
  // Extract the base structural type from a potentially nullable type string.
35
49
  // e.g., 'object | undefined' → 'object', 'array | null' → 'array'
36
50
  function getBaseSkippableType(t: string): string {
37
- const parts = t.split('|').map((s) => s.trim());
38
- const base = parts.filter((s) => s !== 'undefined' && s !== 'null');
39
- return base[0];
51
+ return getTypeAnalysis(t).baseType;
40
52
  }
41
53
 
42
54
  // Check if a type string has nullable annotations (| undefined or | null)
43
55
  function isNullableType(t: string): boolean {
44
- const parts = t.split('|').map((s) => s.trim());
45
- return parts.includes('undefined') || parts.includes('null');
56
+ return getTypeAnalysis(t).isNullable;
46
57
  }
47
58
 
48
59
  // Matches paths containing [][] — e.g., "items[][]" or "items[][].text"
@@ -4,10 +4,10 @@ import type { ExecutionFlow, ScenariosDataStructure } from '~codeyam/types';
4
4
  type RequiredValue = NonNullable<ExecutionFlow['requiredValues']>[number];
5
5
 
6
6
  const DEFAULT_MAX_CHUNK_SIZE = 10_000; // ~10K chars per chunk
7
+ const DEFAULT_MAX_KEYS_PER_CHUNK = 20;
7
8
 
8
9
  export interface ChunkOptions {
9
10
  maxChunkSize?: number;
10
- /** @deprecated Use maxChunkSize instead. Kept for backward compatibility in tests. */
11
11
  maxKeysPerChunk?: number;
12
12
  }
13
13
 
@@ -33,16 +33,19 @@ export function chunkDataStructure(
33
33
  return [dataForMocks];
34
34
  }
35
35
 
36
- const { maxChunkSize = DEFAULT_MAX_CHUNK_SIZE } = options;
36
+ const {
37
+ maxChunkSize = DEFAULT_MAX_CHUNK_SIZE,
38
+ maxKeysPerChunk = DEFAULT_MAX_KEYS_PER_CHUNK,
39
+ } = options;
37
40
  const keys = Object.keys(dataForMocks);
38
41
 
39
42
  // Calculate total size to see if chunking is needed
40
43
  const totalSize = JSON.stringify(dataForMocks).length;
41
- if (totalSize <= maxChunkSize) {
44
+ if (totalSize <= maxChunkSize && keys.length <= maxKeysPerChunk) {
42
45
  return [dataForMocks];
43
46
  }
44
47
 
45
- // Greedily pack keys into chunks by cumulative size
48
+ // Greedily pack keys into chunks by cumulative size and key count
46
49
  const chunks: Array<ScenariosDataStructure['dataForMocks']> = [];
47
50
  let currentChunk: Record<string, unknown> = {};
48
51
  let currentSize = 0;
@@ -52,8 +55,14 @@ export function chunkDataStructure(
52
55
  (dataForMocks as Record<string, unknown>)[key],
53
56
  ).length;
54
57
 
55
- // If adding this key would exceed the limit AND chunk isn't empty, start a new chunk
56
- if (currentSize > 0 && currentSize + keySize > maxChunkSize) {
58
+ const currentKeyCount = Object.keys(currentChunk).length;
59
+
60
+ // If adding this key would exceed size or key count limit AND chunk isn't empty, start a new chunk
61
+ if (
62
+ currentSize > 0 &&
63
+ (currentSize + keySize > maxChunkSize ||
64
+ currentKeyCount >= maxKeysPerChunk)
65
+ ) {
57
66
  chunks.push(currentChunk as ScenariosDataStructure['dataForMocks']);
58
67
  currentChunk = {};
59
68
  currentSize = 0;
@@ -32,6 +32,30 @@ import resolvePathToControllable from './resolvePathToControllable';
32
32
  import { AI } from '~codeyam/ai';
33
33
  import type { JsxRenderingUsage, ConditionalUsage } from './astScopes/types';
34
34
 
35
+ /**
36
+ * Patterns that indicate a child component is a modal/overlay/blocking element.
37
+ * Same patterns used in generateExecutionFlowsFromConditionalEffects.ts for
38
+ * state variable names — here applied to child component names.
39
+ */
40
+ const BLOCKING_CHILD_PATTERNS = [
41
+ /modal/i,
42
+ /overlay/i,
43
+ /dialog/i,
44
+ /popup/i,
45
+ /drawer/i,
46
+ /sheet/i,
47
+ /lightbox/i,
48
+ /backdrop/i,
49
+ ];
50
+
51
+ /**
52
+ * Check if a child component name suggests it's a blocking/modal element.
53
+ * Exported for testing.
54
+ */
55
+ export function isBlockingChildComponent(componentName: string): boolean {
56
+ return BLOCKING_CHILD_PATTERNS.some((pattern) => pattern.test(componentName));
57
+ }
58
+
35
59
  interface GenerateExecutionFlowsArgs {
36
60
  entity: Pick<Entity, 'sha' | 'name' | 'filePath' | 'code' | 'metadata'>;
37
61
  mergedDataStructure: Omit<DataStructure, 'equivalentSignatureVariables'>;
@@ -273,6 +297,11 @@ export default function generateExecutionFlows({
273
297
  calculateFlowPriority(flow, gatingPathToChildren);
274
298
  }
275
299
 
300
+ // Assign exclusiveGroup for truthy/falsy flow pairs that control child components.
301
+ // When the same gating path produces both a truthy flow (show component) and a
302
+ // falsy flow (hide component), they are mutually exclusive and should be grouped.
303
+ assignExclusiveGroupsForGatingPairs(allFlows);
304
+
276
305
  // Sort flows by priority (highest first)
277
306
  allFlows.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
278
307
 
@@ -361,6 +390,60 @@ function buildGatingPathToChildrenMap(
361
390
  return gatingPathToChildren;
362
391
  }
363
392
 
393
+ /**
394
+ * Assign exclusiveGroup for truthy/falsy flow pairs that control child components.
395
+ *
396
+ * When the same attribute path produces both a truthy and falsy flow (e.g.,
397
+ * {diffView && <DiffModal/>}), they are mutually exclusive — only one can be
398
+ * active at a time. Grouping them ensures the scenario generator treats them
399
+ * as alternative states rather than combining them.
400
+ */
401
+ function assignExclusiveGroupsForGatingPairs(flows: ExecutionFlow[]): void {
402
+ // Build a map: attributePath -> flows with truthy or falsy comparison for that path
403
+ const pathToFlows = new Map<
404
+ string,
405
+ { truthy: ExecutionFlow[]; falsy: ExecutionFlow[] }
406
+ >();
407
+
408
+ for (const flow of flows) {
409
+ // Only consider flows that control child components
410
+ if (!flow.childComponentsControlled?.length) continue;
411
+
412
+ for (const rv of flow.requiredValues) {
413
+ if (rv.comparison === 'truthy' || rv.comparison === 'falsy') {
414
+ if (!pathToFlows.has(rv.attributePath)) {
415
+ pathToFlows.set(rv.attributePath, { truthy: [], falsy: [] });
416
+ }
417
+ pathToFlows.get(rv.attributePath)![rv.comparison].push(flow);
418
+ }
419
+ }
420
+ }
421
+
422
+ // Also check falsy flows that DON'T have childComponentsControlled —
423
+ // they still pair with truthy flows that do
424
+ for (const flow of flows) {
425
+ if (flow.childComponentsControlled?.length) continue; // Already processed
426
+
427
+ for (const rv of flow.requiredValues) {
428
+ if (rv.comparison === 'falsy' && pathToFlows.has(rv.attributePath)) {
429
+ pathToFlows.get(rv.attributePath)!.falsy.push(flow);
430
+ }
431
+ }
432
+ }
433
+
434
+ // Assign exclusiveGroup where both truthy and falsy flows exist
435
+ for (const [attributePath, { truthy, falsy }] of pathToFlows) {
436
+ if (truthy.length > 0 && falsy.length > 0) {
437
+ const groupName = `gating:${attributePath}`;
438
+ for (const flow of [...truthy, ...falsy]) {
439
+ if (!flow.exclusiveGroup) {
440
+ flow.exclusiveGroup = groupName;
441
+ }
442
+ }
443
+ }
444
+ }
445
+ }
446
+
364
447
  /**
365
448
  * Calculate priority and childComponentsControlled for a flow.
366
449
  *
@@ -415,6 +498,19 @@ function calculateFlowPriority(
415
498
  if (flow.impact !== 'high') {
416
499
  flow.impact = 'high';
417
500
  }
501
+
502
+ // Check if any controlled child component is a modal/dialog/overlay.
503
+ // Only truthy flows (which SHOW the component) should be marked as blocking.
504
+ // Falsy flows (which HIDE the component) should not be blocking.
505
+ const isTruthyFlow = flow.requiredValues.some(
506
+ (rv) => rv.comparison === 'truthy' || rv.comparison === 'equals',
507
+ );
508
+ if (
509
+ isTruthyFlow &&
510
+ Array.from(childComponentsControlled).some(isBlockingChildComponent)
511
+ ) {
512
+ flow.blocksOtherFlows = true;
513
+ }
418
514
  }
419
515
 
420
516
  // Calculate priority score
@@ -353,8 +353,6 @@ function processCall(
353
353
  // Preprocess to filter signatures and extract functionCallReturnValue
354
354
  let preprocessedSchema = preprocessSchemaForMocks(mergedSchema);
355
355
 
356
- const functionNames: string[] = [];
357
-
358
356
  const isFunctionNameWithoutFunction = (
359
357
  path: string,
360
358
  functionName: string,
@@ -439,31 +437,58 @@ function processCall(
439
437
  }
440
438
  }
441
439
 
442
- const relevantMergedDependencySchema = Object.keys(preprocessedSchema).reduce(
443
- (acc, path) => {
444
- if (path.endsWith(')')) {
445
- const pathParts = splitOutsideParenthesesAndArrays(path);
446
- const functionName = joinParenthesesAndArrays([
447
- ...pathParts.slice(0, -1),
448
- pathParts[pathParts.length - 1].split('(')[0],
449
- ]);
450
- functionNames.push(functionName);
451
- for (const existingPath in acc) {
452
- if (isFunctionNameWithoutFunction(existingPath, functionName)) {
453
- delete acc[existingPath];
454
- }
455
- }
456
- }
440
+ // Two-pass function name filtering to avoid O() retroactive deletion.
441
+ // Pass 1: Discover all unique function names from paths ending with ')'
442
+ const functionNameSet = new Set<string>();
443
+ const schemaKeys = Object.keys(preprocessedSchema);
444
+ for (const path of schemaKeys) {
445
+ if (path.endsWith(')')) {
446
+ const pathParts = splitOutsideParenthesesAndArrays(path);
447
+ const functionName = joinParenthesesAndArrays([
448
+ ...pathParts.slice(0, -1),
449
+ pathParts[pathParts.length - 1].split('(')[0],
450
+ ]);
451
+ functionNameSet.add(functionName);
452
+ }
453
+ }
457
454
 
458
- if (functionNames.some((fn) => isFunctionNameWithoutFunction(path, fn))) {
459
- return acc;
460
- }
455
+ // Index function names by first segment for O(1) bucket lookup instead of
456
+ // checking all function names for every path. Most paths only need to check
457
+ // 1-2 function names instead of all F.
458
+ const fnByFirstSegment = new Map<string, string[]>();
459
+ for (const fn of functionNameSet) {
460
+ const dotIdx = fn.indexOf('.');
461
+ const firstSeg = dotIdx >= 0 ? fn.slice(0, dotIdx) : fn;
462
+ let bucket = fnByFirstSegment.get(firstSeg);
463
+ if (!bucket) {
464
+ bucket = [];
465
+ fnByFirstSegment.set(firstSeg, bucket);
466
+ }
467
+ bucket.push(fn);
468
+ }
461
469
 
462
- acc[path] = preprocessedSchema[path];
463
- return acc;
464
- },
465
- {} as Record<string, string>,
466
- );
470
+ // Pass 2: Filter paths that match any discovered function name prefix.
471
+ // Use indexed lookup: extract path's first segment, check only matching function names.
472
+ const relevantMergedDependencySchema: Record<string, string> = {};
473
+ for (const path of schemaKeys) {
474
+ // Extract first segment of path (before first '.', '(', or '<')
475
+ const dotIdx = path.indexOf('.');
476
+ const parenIdx = path.indexOf('(');
477
+ const angleIdx = path.indexOf('<');
478
+ let minIdx = path.length;
479
+ if (dotIdx >= 0 && dotIdx < minIdx) minIdx = dotIdx;
480
+ if (parenIdx >= 0 && parenIdx < minIdx) minIdx = parenIdx;
481
+ if (angleIdx >= 0 && angleIdx < minIdx) minIdx = angleIdx;
482
+ const firstSeg = path.slice(0, minIdx);
483
+
484
+ const candidates = fnByFirstSegment.get(firstSeg);
485
+ if (
486
+ !candidates ||
487
+ !candidates.some((fn) => isFunctionNameWithoutFunction(path, fn))
488
+ ) {
489
+ relevantMergedDependencySchema[path] = preprocessedSchema[path];
490
+ }
491
+ }
467
492
 
468
493
  const filledSchema = fillInDirectSchemaGapsAndUnknowns({
469
494
  scopeName: importedExport.name,