@wix/zero-config-implementation 1.7.0 → 1.9.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 (26) hide show
  1. package/dist/converters/data-item-builder.d.ts +4 -1
  2. package/dist/index.d.ts +5 -3
  3. package/dist/index.js +22432 -19408
  4. package/dist/information-extractors/react/extractors/core/index.d.ts +1 -1
  5. package/dist/information-extractors/react/extractors/core/runner.d.ts +5 -1
  6. package/dist/information-extractors/react/extractors/index.d.ts +1 -1
  7. package/dist/information-extractors/react/extractors/prop-tracker.d.ts +3 -6
  8. package/dist/information-extractors/react/index.d.ts +2 -3
  9. package/dist/information-extractors/react/types.d.ts +4 -17
  10. package/dist/information-extractors/react/utils/mock-generator.d.ts +13 -2
  11. package/dist/manifest-pipeline.d.ts +2 -2
  12. package/package.json +2 -2
  13. package/src/converters/data-item-builder.ts +120 -35
  14. package/src/converters/to-editor-component.ts +11 -6
  15. package/src/index.ts +5 -3
  16. package/src/information-extractors/react/extractors/core/index.ts +1 -1
  17. package/src/information-extractors/react/extractors/core/runner.ts +12 -7
  18. package/src/information-extractors/react/extractors/core/tree-builder.ts +3 -1
  19. package/src/information-extractors/react/extractors/index.ts +1 -0
  20. package/src/information-extractors/react/extractors/prop-tracker.ts +49 -28
  21. package/src/information-extractors/react/index.ts +1 -7
  22. package/src/information-extractors/react/types.ts +4 -20
  23. package/src/information-extractors/react/utils/mock-generator.ts +99 -31
  24. package/src/manifest-pipeline.ts +18 -12
  25. package/dist/information-extractors/react/utils/prop-spy.d.ts +0 -10
  26. package/src/information-extractors/react/utils/prop-spy.ts +0 -168
@@ -8,17 +8,13 @@
8
8
  import type { HTMLAttributes } from 'react'
9
9
  import { TRACE_ATTR } from '../../../component-renderer'
10
10
  import type { PropSpyMeta, TrackingStores } from '../types'
11
- import { type PropSpyContext, createPropSpyContext } from '../utils/prop-spy'
11
+ import { type PropSpyRegistrar, generateMockProps, resetMockCounter } from '../utils/mock-generator'
12
12
  import type { CreateElementEvent, ReactExtractor, RenderContext } from './core/types'
13
13
 
14
14
  // ─────────────────────────────────────────────────────────────────────────────
15
15
  // Types
16
16
  // ─────────────────────────────────────────────────────────────────────────────
17
17
 
18
- const SPY_REGEX = /__spy_\d+__/g
19
-
20
- export type GetSpyMetadataFn = (id: string) => PropSpyMeta | null
21
-
22
18
  export interface PropTrackerData {
23
19
  tag: string
24
20
  role?: string
@@ -28,7 +24,6 @@ export interface PropTrackerData {
28
24
 
29
25
  export interface PropTrackerExtractorState {
30
26
  stores: TrackingStores
31
- spyContext: PropSpyContext
32
27
  }
33
28
 
34
29
  // ─────────────────────────────────────────────────────────────────────────────
@@ -37,28 +32,54 @@ export interface PropTrackerExtractorState {
37
32
 
38
33
  /**
39
34
  * Creates a prop tracker extractor that:
40
- * 1. Wraps props with spy proxies during beforeRender
35
+ * 1. Generates spy-instrumented mock props during beforeRender
41
36
  * 2. Detects spy markers in element props during onCreateElement
42
- * 3. Writes tracking data to the store namespaced by 'prop-tracker'
37
+ * 3. propUsages tracking data to the store namespaced by 'prop-tracker'
43
38
  */
44
39
  export function createPropTrackerExtractor(): {
45
40
  extractor: ReactExtractor
46
41
  state: PropTrackerExtractorState
47
42
  } {
48
- const spyContext = createPropSpyContext()
43
+ const stringMeta = new Map<string, PropSpyMeta>()
44
+ const numberMeta = new Map<number, PropSpyMeta>()
45
+ const fnMeta = new WeakMap<(...args: unknown[]) => unknown, PropSpyMeta>()
49
46
  const stores: TrackingStores = {
50
- reads: new Map(),
51
- writes: new Map(),
47
+ propUsages: new Map(),
48
+ }
49
+
50
+ const registrar: PropSpyRegistrar = {
51
+ registerString(path, propName, value) {
52
+ stringMeta.set(value, { path, propName, originalValue: value })
53
+ },
54
+ registerNumber(path, propName, value) {
55
+ numberMeta.set(value, { path, propName, originalValue: value })
56
+ },
57
+ registerFunction(path, propName, value) {
58
+ fnMeta.set(value, { path, propName, originalValue: value })
59
+ },
52
60
  }
53
61
 
54
- const extractSpies = (value: unknown, seen = new WeakSet<object>()): { propName: string; path: string }[] => {
62
+ const extractSpies = (
63
+ value: unknown,
64
+ seen = new WeakSet<object>(),
65
+ ): { propName: string; path: string; embedded: boolean }[] => {
55
66
  if (typeof value === 'string') {
56
- return (value.match(SPY_REGEX) ?? [])
57
- .map((id) => {
58
- const meta = spyContext.getSpyMetadataByUniqueId(id)
59
- return meta ? { propName: meta.propName, path: meta.path } : null
60
- })
61
- .filter(Boolean) as { propName: string; path: string }[]
67
+ const results: { propName: string; path: string; embedded: boolean }[] = []
68
+ for (const [key, meta] of stringMeta) {
69
+ if (value.includes(key)) {
70
+ // embedded = spy marker was part of a longer string (e.g. `mailto:${email}`)
71
+ results.push({ propName: meta.propName, path: meta.path, embedded: value !== key })
72
+ }
73
+ }
74
+ return results
75
+ }
76
+ if (typeof value === 'number') {
77
+ const meta = numberMeta.get(value)
78
+ return meta ? [{ propName: meta.propName, path: meta.path, embedded: false }] : []
79
+ }
80
+ if (typeof value === 'function') {
81
+ const meta = fnMeta.get(value as (...args: unknown[]) => unknown)
82
+ return meta ? [{ propName: meta.propName, path: meta.path, embedded: false }] : []
62
83
  }
63
84
  if (value && typeof value === 'object') {
64
85
  // Prevent infinite recursion from circular references
@@ -79,8 +100,8 @@ export function createPropTrackerExtractor(): {
79
100
  name: 'prop-tracker',
80
101
 
81
102
  onBeforeRender(context: RenderContext): void {
82
- // Wrap props with spy proxies to track access
83
- context.props = spyContext.createAuditedProps(context.props, stores, () => context.componentInfo.componentName)
103
+ resetMockCounter()
104
+ context.props = generateMockProps(context.componentInfo, registrar)
84
105
  },
85
106
 
86
107
  onCreateElement(event: CreateElementEvent): void {
@@ -97,18 +118,18 @@ export function createPropTrackerExtractor(): {
97
118
 
98
119
  spies.forEach((spy) => {
99
120
  boundProps.add(spy.propName)
100
- const path = spy.path
101
- const entry = stores.writes.get(path) ?? { elements: new Map(), attributes: new Map() }
102
-
103
- if (!stores.writes.has(path)) {
104
- stores.writes.set(path, entry)
121
+ const path = spy.path.replace(/\[\d+\]/g, '')
122
+ if (!stores.propUsages.has(path)) {
123
+ stores.propUsages.set(path, { elements: new Map(), attributes: new Map() })
105
124
  }
125
+ const entry = stores.propUsages.get(path)!
106
126
 
107
127
  entry.elements.set(traceId, { tag, elementId: traceId })
108
- entry.attributes.set(`${traceId}:${key}`, { attr: key, concatenated: spies.length > 1 })
128
+ const isConcat = spies.length > 1 || spy.embedded
129
+ entry.attributes.set(`${traceId}:${key}`, { attr: key, concatenated: isConcat })
109
130
 
110
131
  // Track concatenated attributes
111
- if (spies.length > 1) {
132
+ if (isConcat) {
112
133
  concatenatedAttrs.set(key, spy.propName)
113
134
  }
114
135
  })
@@ -127,6 +148,6 @@ export function createPropTrackerExtractor(): {
127
148
 
128
149
  return {
129
150
  extractor,
130
- state: { stores, spyContext },
151
+ state: { stores },
131
152
  }
132
153
  }
@@ -21,6 +21,7 @@ export {
21
21
  export type {
22
22
  // Core types
23
23
  ExtractionResult,
24
+ RunExtractorsOptions,
24
25
  ExtractedElement,
25
26
  ReactExtractor,
26
27
  RenderContext,
@@ -41,13 +42,6 @@ export type {
41
42
  CoupledProp,
42
43
  TrackingStores,
43
44
  DOMBinding,
44
- PropReadInfo,
45
45
  PropWriteInfo,
46
46
  PropSpyMeta,
47
47
  } from './types'
48
-
49
- // ─────────────────────────────────────────────────────────────────────────────
50
- // Utility Exports
51
- // ─────────────────────────────────────────────────────────────────────────────
52
-
53
- export type { PropSpyContext } from './utils/prop-spy'
@@ -9,41 +9,23 @@ import type { ExtractedElement } from './extractors/core/tree-builder'
9
9
  // Prop Spy (for tracking prop access)
10
10
  // ─────────────────────────────────────────────────────────────────────────────
11
11
 
12
- export const PROP_SPY_SYMBOL = Symbol.for('__prop_spy__')
13
-
14
12
  export interface PropSpyMeta {
15
13
  path: string
16
14
  propName: string
17
- uniqueId: string
18
15
  originalValue: unknown
19
16
  }
20
17
 
21
- export interface PropSpy<T = unknown> {
22
- [PROP_SPY_SYMBOL]: true
23
- __meta: PropSpyMeta
24
- valueOf: () => T
25
- toString: () => string
26
- toJSON: () => T
27
- [Symbol.toPrimitive]?: (hint: string) => T | string | number
28
- }
29
-
30
18
  // ─────────────────────────────────────────────────────────────────────────────
31
19
  // Tracking Stores
32
20
  // ─────────────────────────────────────────────────────────────────────────────
33
21
 
34
- export interface PropReadInfo {
35
- components: Set<string>
36
- value: unknown
37
- }
38
-
39
22
  export interface PropWriteInfo {
40
23
  elements: Map<string, { tag: string; elementId: string }>
41
24
  attributes: Map<string, { attr: string; concatenated: boolean }>
42
25
  }
43
26
 
44
27
  export interface TrackingStores {
45
- reads: Map<string, PropReadInfo>
46
- writes: Map<string, PropWriteInfo>
28
+ propUsages: Map<string, PropWriteInfo>
47
29
  }
48
30
 
49
31
  // ─────────────────────────────────────────────────────────────────────────────
@@ -58,7 +40,8 @@ export interface DOMBinding {
58
40
  }
59
41
 
60
42
  export interface CoupledProp extends PropInfo {
61
- bindings: DOMBinding[]
43
+ /** Full stores.propUsages key for this prop, e.g. "props.linkUrl". */
44
+ propPath: string
62
45
  logicOnly: boolean
63
46
  }
64
47
 
@@ -67,4 +50,5 @@ export interface CoupledComponentInfo {
67
50
  props: Record<string, CoupledProp>
68
51
  elements: ExtractedElement[]
69
52
  innerElementProps?: Map<string, Record<string, CoupledProp>>
53
+ propUsages: TrackingStores['propUsages']
70
54
  }
@@ -6,21 +6,49 @@
6
6
  import { faker } from '@faker-js/faker'
7
7
  import type { ComponentInfo, DefaultValue, PropInfo, ResolvedType } from '../../ts/types'
8
8
 
9
+ // Unique primes used as traceable number values. Each rendered number prop gets
10
+ // one; the value is distinct enough to be identified in DOM attributes later.
11
+ const TRACEABLE_PRIMES = [11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
12
+ const FALLBACK_NUMBER_BASE = 101
13
+ let primeIndex = 0
14
+
15
+ function nextTraceableNumber(): number {
16
+ const withinPrimeList = primeIndex < TRACEABLE_PRIMES.length
17
+ if (withinPrimeList) {
18
+ return TRACEABLE_PRIMES[primeIndex++]
19
+ }
20
+ const overflowOffset = primeIndex++ - TRACEABLE_PRIMES.length
21
+ return FALLBACK_NUMBER_BASE + overflowOffset
22
+ }
23
+
9
24
  /**
10
25
  * Reset faker's seed and internal state for reproducible results
11
26
  */
12
27
  export function resetMockCounter(): void {
13
28
  faker.seed(42)
29
+ primeIndex = 0
30
+ }
31
+
32
+ /**
33
+ * Narrow interface for registering spy markers during mock generation.
34
+ * Satisfied structurally by PropSpyContext.
35
+ */
36
+ export interface PropSpyRegistrar {
37
+ registerString(path: string, propName: string, value: string): void
38
+ registerNumber(path: string, propName: string, value: number): void
39
+ registerFunction(path: string, propName: string, value: (...args: unknown[]) => unknown): void
14
40
  }
15
41
 
16
42
  /**
17
- * Generate mock props object from ComponentInfo
43
+ * Generate mock props object from ComponentInfo.
44
+ * When a registrar is provided, string and number values are spy-instrumented
45
+ * for DOM binding detection.
18
46
  */
19
- export function generateMockProps(componentInfo: ComponentInfo): Record<string, unknown> {
47
+ export function generateMockProps(componentInfo: ComponentInfo, registrar?: PropSpyRegistrar): Record<string, unknown> {
20
48
  const mockProps: Record<string, unknown> = {}
21
49
 
22
50
  for (const [propName, propInfo] of Object.entries(componentInfo.props)) {
23
- mockProps[propName] = generateMockValue(propInfo, propName)
51
+ mockProps[propName] = generateMockValue(propInfo, propName, `props.${propName}`, registrar)
24
52
  }
25
53
 
26
54
  return mockProps
@@ -29,13 +57,13 @@ export function generateMockProps(componentInfo: ComponentInfo): Record<string,
29
57
  /**
30
58
  * Generate a mock value based on PropInfo
31
59
  */
32
- function generateMockValue(propInfo: PropInfo, propName: string): unknown {
33
- // If there's a default value, use it
34
- if (propInfo.defaultValue !== undefined) {
60
+ function generateMockValue(propInfo: PropInfo, propName: string, path: string, registrar?: PropSpyRegistrar): unknown {
61
+ // In plain mode (no registrar), honour default values for realistic rendering
62
+ if (!registrar && propInfo.defaultValue !== undefined) {
35
63
  return extractDefaultValueValue(propInfo.defaultValue)
36
64
  }
37
65
 
38
- return generateValueFromResolvedType(propInfo.resolvedType, propName)
66
+ return generateValueFromResolvedType(propInfo.resolvedType, propName, path, registrar)
39
67
  }
40
68
 
41
69
  /**
@@ -57,10 +85,15 @@ function extractDefaultValueValue(defaultValue: DefaultValue): unknown {
57
85
  /**
58
86
  * Generate a mock value from a ResolvedType
59
87
  */
60
- function generateValueFromResolvedType(resolvedType: ResolvedType, propName: string): unknown {
88
+ function generateValueFromResolvedType(
89
+ resolvedType: ResolvedType,
90
+ propName: string,
91
+ path: string,
92
+ registrar?: PropSpyRegistrar,
93
+ ): unknown {
61
94
  const kind = resolvedType.kind
62
95
 
63
- // Handle semantic types (from React or Wix packages)
96
+ // Handle semantic types (from React or Wix packages) — returned as plain objects
64
97
  if (kind === 'semantic') {
65
98
  return generateSemanticValue(resolvedType.value as string, propName)
66
99
  }
@@ -68,28 +101,28 @@ function generateValueFromResolvedType(resolvedType: ResolvedType, propName: str
68
101
  // Handle structural types
69
102
  switch (kind) {
70
103
  case 'primitive':
71
- return generatePrimitiveValue(resolvedType.value as string, propName)
104
+ return generatePrimitiveValue(resolvedType.value as string, propName, path, registrar)
72
105
 
73
106
  case 'literal':
74
107
  return resolvedType.value
75
108
 
76
109
  case 'union':
77
- return generateUnionValue(resolvedType, propName)
110
+ return generateUnionValue(resolvedType, propName, path, registrar)
78
111
 
79
112
  case 'intersection':
80
- return generateIntersectionValue(resolvedType, propName)
113
+ return generateIntersectionValue(resolvedType, propName, path, registrar)
81
114
 
82
115
  case 'array':
83
- return generateArrayValue(resolvedType, propName)
116
+ return generateArrayValue(resolvedType, propName, path, registrar)
84
117
 
85
118
  case 'object':
86
- return generateObjectValue(resolvedType, propName)
119
+ return generateObjectValue(resolvedType, propName, path, registrar)
87
120
 
88
121
  case 'enum':
89
122
  return generateEnumValue(resolvedType)
90
123
 
91
124
  case 'function':
92
- return generateMockFunction(propName)
125
+ return generateMockFunction(propName, path, registrar)
93
126
 
94
127
  default:
95
128
  // Default to a string for unknown types
@@ -155,12 +188,23 @@ function generateSemanticValue(semanticType: string, propName: string): unknown
155
188
  /**
156
189
  * Generate a primitive value
157
190
  */
158
- function generatePrimitiveValue(primitiveType: string, propName: string): unknown {
191
+ function generatePrimitiveValue(
192
+ primitiveType: string,
193
+ propName: string,
194
+ path: string,
195
+ registrar?: PropSpyRegistrar,
196
+ ): unknown {
159
197
  switch (primitiveType) {
160
- case 'string':
161
- return `mock_${propName}_${faker.string.alphanumeric(6)}`
162
- case 'number':
163
- return faker.number.int({ min: 1, max: 100 })
198
+ case 'string': {
199
+ const raw = `mock_${propName}_${faker.string.alphanumeric(6)}`
200
+ if (registrar) registrar.registerString(path, propName, raw)
201
+ return raw
202
+ }
203
+ case 'number': {
204
+ const val = nextTraceableNumber()
205
+ if (registrar) registrar.registerNumber(path, propName, val)
206
+ return val
207
+ }
164
208
  case 'boolean':
165
209
  return faker.datatype.boolean()
166
210
  case 'null':
@@ -175,7 +219,12 @@ function generatePrimitiveValue(primitiveType: string, propName: string): unknow
175
219
  /**
176
220
  * Generate a value from a union type (pick the first non-null option)
177
221
  */
178
- function generateUnionValue(resolvedType: ResolvedType, propName: string): unknown {
222
+ function generateUnionValue(
223
+ resolvedType: ResolvedType,
224
+ propName: string,
225
+ path: string,
226
+ registrar?: PropSpyRegistrar,
227
+ ): unknown {
179
228
  const types = resolvedType.types ?? []
180
229
 
181
230
  // Prefer string literals for textEnum-like unions
@@ -186,7 +235,7 @@ function generateUnionValue(resolvedType: ResolvedType, propName: string): unkno
186
235
 
187
236
  // Otherwise, use the first type
188
237
  if (types.length > 0) {
189
- return generateValueFromResolvedType(types[0], propName)
238
+ return generateValueFromResolvedType(types[0], propName, path, registrar)
190
239
  }
191
240
 
192
241
  return `mock_${propName}_${faker.string.alphanumeric(6)}`
@@ -195,14 +244,19 @@ function generateUnionValue(resolvedType: ResolvedType, propName: string): unkno
195
244
  /**
196
245
  * Generate a value from an intersection type (merge object properties)
197
246
  */
198
- function generateIntersectionValue(resolvedType: ResolvedType, propName: string): unknown {
247
+ function generateIntersectionValue(
248
+ resolvedType: ResolvedType,
249
+ propName: string,
250
+ path: string,
251
+ registrar?: PropSpyRegistrar,
252
+ ): unknown {
199
253
  const types = resolvedType.types ?? []
200
254
  const merged: Record<string, unknown> = {}
201
255
 
202
256
  for (const type of types) {
203
257
  if (type.kind === 'object' && type.properties) {
204
258
  for (const [key, propInfo] of Object.entries(type.properties)) {
205
- merged[key] = generateMockValue(propInfo, `${propName}.${key}`)
259
+ merged[key] = generateMockValue(propInfo, `${propName}.${key}`, `${path}.${key}`, registrar)
206
260
  }
207
261
  }
208
262
  }
@@ -213,29 +267,38 @@ function generateIntersectionValue(resolvedType: ResolvedType, propName: string)
213
267
  /**
214
268
  * Generate an array value
215
269
  */
216
- function generateArrayValue(resolvedType: ResolvedType, propName: string): unknown[] {
270
+ function generateArrayValue(
271
+ resolvedType: ResolvedType,
272
+ propName: string,
273
+ path: string,
274
+ registrar?: PropSpyRegistrar,
275
+ ): unknown[] {
217
276
  const elementType = resolvedType.elementType
218
277
 
219
278
  if (!elementType) {
220
279
  return [`mock_${propName}[0]_${faker.string.alphanumeric(6)}`]
221
280
  }
222
281
 
223
- // Generate 2-3 items for the array
224
282
  return [
225
- generateValueFromResolvedType(elementType, `${propName}[0]`),
226
- generateValueFromResolvedType(elementType, `${propName}[1]`),
283
+ generateValueFromResolvedType(elementType, `${propName}[0]`, `${path}[0]`, registrar),
284
+ generateValueFromResolvedType(elementType, `${propName}[1]`, `${path}[1]`, registrar),
227
285
  ]
228
286
  }
229
287
 
230
288
  /**
231
289
  * Generate an object value from properties
232
290
  */
233
- function generateObjectValue(resolvedType: ResolvedType, propName: string): Record<string, unknown> {
291
+ function generateObjectValue(
292
+ resolvedType: ResolvedType,
293
+ propName: string,
294
+ path: string,
295
+ registrar?: PropSpyRegistrar,
296
+ ): Record<string, unknown> {
234
297
  const properties = resolvedType.properties ?? {}
235
298
  const obj: Record<string, unknown> = {}
236
299
 
237
300
  for (const [key, propInfo] of Object.entries(properties)) {
238
- obj[key] = generateMockValue(propInfo, `${propName}.${key}`)
301
+ obj[key] = generateMockValue(propInfo, key, `${path}.${key}`, registrar)
239
302
  }
240
303
 
241
304
  return obj
@@ -258,11 +321,16 @@ function generateEnumValue(resolvedType: ResolvedType): unknown {
258
321
  /**
259
322
  * Generate a mock function
260
323
  */
261
- function generateMockFunction(propName: string): (...args: unknown[]) => void {
324
+ function generateMockFunction(
325
+ propName: string,
326
+ path: string,
327
+ registrar?: PropSpyRegistrar,
328
+ ): (...args: unknown[]) => void {
262
329
  const fn = function mockFn(): void {
263
330
  // No-op mock function
264
331
  }
265
332
  Object.defineProperty(fn, 'name', { value: `mock_${propName}` })
333
+ if (registrar) registrar.registerFunction(path, propName, fn)
266
334
  return fn
267
335
  }
268
336
 
@@ -6,6 +6,7 @@ import type { PropInfo } from './information-extractors/ts/types'
6
6
  import {
7
7
  type ExtractedElement,
8
8
  type PropTrackerData,
9
+ type RunExtractorsOptions,
9
10
  createCssPropertiesExtractor,
10
11
  createPropTrackerExtractor,
11
12
  runExtractors,
@@ -62,6 +63,7 @@ export function processComponent(
62
63
  loadComponent: (componentName: string) => ComponentType<unknown> | null,
63
64
  cssImportPaths: string[],
64
65
  loaderHasError?: boolean,
66
+ options?: RunExtractorsOptions,
65
67
  ): ProcessComponentResult {
66
68
  const warnings: ExtractionWarning[] = []
67
69
 
@@ -97,7 +99,7 @@ export function processComponent(
97
99
  const { extractor: propTracker, state } = createPropTrackerExtractor()
98
100
  const cssExtractor = createCssPropertiesExtractor()
99
101
 
100
- const result = runExtractors(componentInfo, Component, [propTracker, cssExtractor])
102
+ const result = runExtractors(componentInfo, Component, [propTracker, cssExtractor], options)
101
103
  html = result.html
102
104
  extractedElements = result.elements
103
105
 
@@ -107,6 +109,7 @@ export function processComponent(
107
109
  props: coupledProps,
108
110
  elements: convertElements(extractedElements),
109
111
  innerElementProps: innerElementProps.size > 0 ? innerElementProps : undefined,
112
+ propUsages: state.stores.propUsages,
110
113
  }
111
114
  } catch (error) {
112
115
  warnings.push({
@@ -126,9 +129,13 @@ export function processComponent(
126
129
  enhancedInfo = {
127
130
  componentName: componentInfo.componentName,
128
131
  props: Object.fromEntries(
129
- Object.entries(componentInfo.props).map(([name, info]) => [name, { ...info, bindings: [], logicOnly: false }]),
132
+ Object.entries(componentInfo.props).map(([name, info]) => [
133
+ name,
134
+ { ...info, logicOnly: false, propPath: `props.${name}` },
135
+ ]),
130
136
  ),
131
137
  elements: [],
138
+ propUsages: new Map(),
132
139
  }
133
140
  }
134
141
 
@@ -173,14 +180,12 @@ function buildCoupledProps(
173
180
  const result: Record<string, CoupledProp> = {}
174
181
 
175
182
  for (const [name, info] of Object.entries(componentInfo.props)) {
176
- const path = `props.${name}`
177
- const wasRead = stores.reads.has(path)
178
- const writeInfo = stores.writes.get(path)
183
+ const topLevelWriteInfo = stores.propUsages.get(`props.${name}`)
179
184
 
180
185
  result[name] = {
181
186
  ...info,
182
- logicOnly: wasRead && !writeInfo,
183
- bindings: writeInfo ? extractBindings(writeInfo) : [],
187
+ logicOnly: !topLevelWriteInfo,
188
+ propPath: `props.${name}`,
184
189
  }
185
190
  }
186
191
 
@@ -192,7 +197,7 @@ function buildCoupledProps(
192
197
  const ELEMENT_PROPS_PREFIX = 'props.elementProps.'
193
198
 
194
199
  /**
195
- * Processes stores.writes entries with paths starting with "props.elementProps."
200
+ * Processes stores.propUsages entries with paths starting with "props.elementProps."
196
201
  * to extract inner element prop bindings grouped by elementId (traceId).
197
202
  */
198
203
  function processElementPropsWrites(
@@ -204,7 +209,7 @@ function processElementPropsWrites(
204
209
  const elementPropsInfo = componentInfo.props.elementProps
205
210
  if (!elementPropsInfo) return result
206
211
 
207
- for (const [path, writeInfo] of stores.writes) {
212
+ for (const [path, writeInfo] of stores.propUsages) {
208
213
  if (!path.startsWith(ELEMENT_PROPS_PREFIX)) continue
209
214
 
210
215
  // Resolve the PropInfo for this leaf prop by walking the type tree
@@ -228,10 +233,9 @@ function processElementPropsWrites(
228
233
  propsForElement[leafName] = {
229
234
  ...propInfo,
230
235
  logicOnly: false,
231
- bindings: [],
236
+ propPath: path,
232
237
  }
233
238
  }
234
- propsForElement[leafName].bindings.push(binding)
235
239
  }
236
240
  }
237
241
 
@@ -266,7 +270,9 @@ function resolveInnerPropInfo(elementPropsPropInfo: PropInfo, relativePath: stri
266
270
  return null
267
271
  }
268
272
 
269
- function extractBindings(writeInfo: TrackingStores['writes'] extends Map<string, infer V> ? V : never): DOMBinding[] {
273
+ function extractBindings(
274
+ writeInfo: TrackingStores['propUsages'] extends Map<string, infer V> ? V : never,
275
+ ): DOMBinding[] {
270
276
  const bindings: DOMBinding[] = []
271
277
 
272
278
  for (const [key, attrInfo] of writeInfo.attributes) {
@@ -1,10 +0,0 @@
1
- import { PropSpyMeta, TrackingStores } from '../types';
2
- export interface PropSpyContext {
3
- createAuditedProps: <T extends object | null>(target: T, stores: TrackingStores, getComponent: () => string, basePath?: string) => T;
4
- getSpyMetadataByUniqueId: (id: string) => PropSpyMeta | null;
5
- }
6
- /**
7
- * Creates an encapsulated prop spy context.
8
- * Each context has its own ID counter and metadata map, avoiding global state.
9
- */
10
- export declare function createPropSpyContext(): PropSpyContext;