@fugood/bricks-ctor 2.25.0-beta.5 → 2.25.0-beta.50

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 (185) hide show
  1. package/compile/__tests__/config-diff.test.js +100 -0
  2. package/compile/__tests__/index.test.js +365 -0
  3. package/compile/__tests__/util.test.js +317 -0
  4. package/compile/action-name-map.ts +64 -0
  5. package/compile/config-diff.ts +155 -0
  6. package/compile/index.ts +273 -32
  7. package/compile/util.ts +26 -7
  8. package/package.json +7 -3
  9. package/skills/bricks-ctor/SKILL.md +23 -17
  10. package/skills/bricks-ctor/{rules → references}/animation.md +3 -2
  11. package/skills/bricks-ctor/{rules → references}/architecture-patterns.md +18 -0
  12. package/skills/bricks-ctor/{rules → references}/automations.md +11 -0
  13. package/skills/bricks-ctor/references/buttress.md +245 -0
  14. package/skills/bricks-ctor/references/data-calculation.md +239 -0
  15. package/skills/bricks-ctor/references/simulator.md +132 -0
  16. package/skills/bricks-ctor/references/source-editing-tools.md +81 -0
  17. package/skills/bricks-ctor/references/verification-toolchain.md +200 -0
  18. package/skills/bricks-design/SKILL.md +150 -45
  19. package/skills/bricks-design/references/architecture-truths.md +132 -0
  20. package/skills/bricks-design/references/avoiding-complexity.md +91 -0
  21. package/skills/bricks-design/references/design-critique.md +195 -0
  22. package/skills/bricks-design/references/design-languages.md +265 -0
  23. package/skills/bricks-design/references/performance.md +116 -0
  24. package/skills/bricks-design/references/presentation-and-slideshow.md +137 -0
  25. package/skills/bricks-design/references/translating-inputs.md +152 -0
  26. package/skills/bricks-design/references/variations-and-tweaks.md +124 -0
  27. package/skills/bricks-design/references/when-the-brief-is-branded.md +284 -0
  28. package/skills/bricks-design/references/when-the-brief-is-vague.md +85 -0
  29. package/skills/bricks-design/references/workflow.md +134 -0
  30. package/skills/bricks-ux/SKILL.md +114 -0
  31. package/skills/bricks-ux/references/accessibility.md +162 -0
  32. package/skills/bricks-ux/references/flow-states.md +175 -0
  33. package/skills/bricks-ux/references/interaction-archetypes.md +189 -0
  34. package/skills/bricks-ux/references/monitoring-screens.md +153 -0
  35. package/skills/bricks-ux/references/pressable-composition.md +126 -0
  36. package/skills/bricks-ux/references/user-journey.md +168 -0
  37. package/skills/bricks-ux/references/ux-critique.md +256 -0
  38. package/tools/__tests__/_cli-error.test.ts +35 -0
  39. package/tools/_cli-error.ts +17 -0
  40. package/tools/_edits-log.ts +41 -0
  41. package/tools/_git-author.ts +10 -2
  42. package/tools/_last-pushed-commit.ts +28 -0
  43. package/tools/_shell.ts +8 -1
  44. package/tools/deploy.ts +17 -6
  45. package/tools/mcp-env.ts +13 -0
  46. package/tools/mcp-server.ts +8 -0
  47. package/tools/mcp-tools/__tests__/data-calc-editing.test.js +516 -0
  48. package/tools/mcp-tools/__tests__/entry-editing.test.js +866 -0
  49. package/tools/mcp-tools/__tests__/huggingface.test.ts +49 -0
  50. package/tools/mcp-tools/__tests__/icons.test.ts +21 -0
  51. package/tools/mcp-tools/__tests__/mcp-env.test.js +19 -0
  52. package/tools/mcp-tools/_editing-helpers.ts +58 -0
  53. package/tools/mcp-tools/_verify.ts +50 -0
  54. package/tools/mcp-tools/compile.ts +21 -9
  55. package/tools/mcp-tools/data-calc-editing.ts +1349 -0
  56. package/tools/mcp-tools/entry-editing.ts +2336 -0
  57. package/tools/mcp-tools/huggingface.ts +23 -13
  58. package/tools/mcp-tools/icons.ts +23 -7
  59. package/tools/mcp-tools/media.ts +4 -1
  60. package/tools/postinstall.ts +80 -3
  61. package/tools/pull.ts +93 -22
  62. package/tools/push-config.ts +114 -0
  63. package/tools/{preview-main.mjs → simulator-main.mjs} +207 -12
  64. package/tools/simulator-preload.cjs +16 -0
  65. package/tools/{preview.ts → simulator.ts} +4 -4
  66. package/types/{animation.ts → animation.d.ts} +24 -8
  67. package/types/{automation.ts → automation.d.ts} +16 -20
  68. package/types/{brick-base.ts → brick-base.d.ts} +1 -1
  69. package/types/bricks/{Camera.ts → Camera.d.ts} +8 -8
  70. package/types/bricks/{Chart.ts → Chart.d.ts} +4 -4
  71. package/types/bricks/{GenerativeMedia.ts → GenerativeMedia.d.ts} +15 -15
  72. package/types/bricks/{Icon.ts → Icon.d.ts} +7 -7
  73. package/types/bricks/{Image.ts → Image.d.ts} +21 -9
  74. package/types/bricks/{Items.ts → Items.d.ts} +7 -7
  75. package/types/bricks/{Lottie.ts → Lottie.d.ts} +10 -10
  76. package/types/bricks/{Maps.ts → Maps.d.ts} +11 -11
  77. package/types/bricks/{QrCode.ts → QrCode.d.ts} +7 -7
  78. package/types/bricks/{Rect.ts → Rect.d.ts} +7 -7
  79. package/types/bricks/{RichText.ts → RichText.d.ts} +12 -9
  80. package/types/bricks/{Rive.ts → Rive.d.ts} +9 -9
  81. package/types/bricks/Scene3D.d.ts +676 -0
  82. package/types/bricks/{Sketch.ts → Sketch.d.ts} +6 -6
  83. package/types/bricks/{Slideshow.ts → Slideshow.d.ts} +7 -7
  84. package/types/bricks/{Svg.ts → Svg.d.ts} +7 -7
  85. package/types/bricks/{Text.ts → Text.d.ts} +9 -9
  86. package/types/bricks/{TextInput.ts → TextInput.d.ts} +10 -10
  87. package/types/bricks/{Video.ts → Video.d.ts} +12 -12
  88. package/types/bricks/{VideoStreaming.ts → VideoStreaming.d.ts} +10 -10
  89. package/types/bricks/{WebRtcStream.ts → WebRtcStream.d.ts} +1 -1
  90. package/types/bricks/{WebView.ts → WebView.d.ts} +4 -4
  91. package/types/bricks/{index.ts → index.d.ts} +1 -0
  92. package/types/{common.ts → common.d.ts} +3 -6
  93. package/types/data-calc-command/base.d.ts +57 -0
  94. package/types/data-calc-command/collection.d.ts +418 -0
  95. package/types/data-calc-command/color.d.ts +432 -0
  96. package/types/data-calc-command/constant.d.ts +50 -0
  97. package/types/data-calc-command/datetime.d.ts +147 -0
  98. package/types/data-calc-command/file.d.ts +129 -0
  99. package/types/data-calc-command/index.d.ts +13 -0
  100. package/types/data-calc-command/iteratee.d.ts +23 -0
  101. package/types/data-calc-command/logictype.d.ts +190 -0
  102. package/types/data-calc-command/math.d.ts +275 -0
  103. package/types/data-calc-command/object.d.ts +119 -0
  104. package/types/data-calc-command/sandbox.d.ts +66 -0
  105. package/types/data-calc-command/string.d.ts +407 -0
  106. package/types/{data-calc.ts → data-calc.d.ts} +1 -0
  107. package/types/{data.ts → data.d.ts} +4 -2
  108. package/types/generators/{Assistant.ts → Assistant.d.ts} +19 -0
  109. package/types/generators/{LlmGgml.ts → LlmGgml.d.ts} +43 -1
  110. package/types/generators/{LlmMlx.ts → LlmMlx.d.ts} +1 -0
  111. package/types/generators/{RerankerGgml.ts → RerankerGgml.d.ts} +5 -1
  112. package/types/generators/{SoundRecorder.ts → SoundRecorder.d.ts} +10 -1
  113. package/types/generators/{SpeechToTextGgml.ts → SpeechToTextGgml.d.ts} +6 -1
  114. package/types/generators/{SttAppleBuiltin.ts → SttAppleBuiltin.d.ts} +27 -4
  115. package/types/generators/{ThermalPrinter.ts → ThermalPrinter.d.ts} +9 -7
  116. package/types/generators/{VadGgml.ts → VadGgml.d.ts} +12 -2
  117. package/types/{subspace.ts → subspace.d.ts} +1 -1
  118. package/utils/__tests__/calc.test.js +25 -0
  119. package/utils/__tests__/id.test.js +154 -0
  120. package/utils/calc.ts +5 -1
  121. package/utils/data.ts +5 -7
  122. package/utils/event-props.ts +17 -0
  123. package/utils/id.ts +109 -56
  124. package/skills/bricks-ctor/rules/buttress.md +0 -156
  125. package/skills/bricks-ctor/rules/data-calculation.md +0 -209
  126. package/skills/bricks-design/LICENSE.txt +0 -180
  127. package/types/data-calc-command.ts +0 -7005
  128. /package/skills/bricks-ctor/{rules → references}/local-sync.md +0 -0
  129. /package/skills/bricks-ctor/{rules → references}/media-flow.md +0 -0
  130. /package/skills/bricks-ctor/{rules → references}/remote-data-bank.md +0 -0
  131. /package/skills/bricks-ctor/{rules → references}/standby-transition.md +0 -0
  132. /package/types/{canvas.ts → canvas.d.ts} +0 -0
  133. /package/types/{data-calc-script.ts → data-calc-script.d.ts} +0 -0
  134. /package/types/generators/{AlarmClock.ts → AlarmClock.d.ts} +0 -0
  135. /package/types/generators/{BleCentral.ts → BleCentral.d.ts} +0 -0
  136. /package/types/generators/{BlePeripheral.ts → BlePeripheral.d.ts} +0 -0
  137. /package/types/generators/{CanvasMap.ts → CanvasMap.d.ts} +0 -0
  138. /package/types/generators/{CastlesPay.ts → CastlesPay.d.ts} +0 -0
  139. /package/types/generators/{DataBank.ts → DataBank.d.ts} +0 -0
  140. /package/types/generators/{File.ts → File.d.ts} +0 -0
  141. /package/types/generators/{GraphQl.ts → GraphQl.d.ts} +0 -0
  142. /package/types/generators/{Http.ts → Http.d.ts} +0 -0
  143. /package/types/generators/{HttpServer.ts → HttpServer.d.ts} +0 -0
  144. /package/types/generators/{Information.ts → Information.d.ts} +0 -0
  145. /package/types/generators/{Intent.ts → Intent.d.ts} +0 -0
  146. /package/types/generators/{Iterator.ts → Iterator.d.ts} +0 -0
  147. /package/types/generators/{Keyboard.ts → Keyboard.d.ts} +0 -0
  148. /package/types/generators/{LlmAnthropicCompat.ts → LlmAnthropicCompat.d.ts} +0 -0
  149. /package/types/generators/{LlmAppleBuiltin.ts → LlmAppleBuiltin.d.ts} +0 -0
  150. /package/types/generators/{LlmMediaTekNeuroPilot.ts → LlmMediaTekNeuroPilot.d.ts} +0 -0
  151. /package/types/generators/{LlmOnnx.ts → LlmOnnx.d.ts} +0 -0
  152. /package/types/generators/{LlmOpenAiCompat.ts → LlmOpenAiCompat.d.ts} +0 -0
  153. /package/types/generators/{LlmQualcommAiEngine.ts → LlmQualcommAiEngine.d.ts} +0 -0
  154. /package/types/generators/{Mcp.ts → Mcp.d.ts} +0 -0
  155. /package/types/generators/{McpServer.ts → McpServer.d.ts} +0 -0
  156. /package/types/generators/{MediaFlow.ts → MediaFlow.d.ts} +0 -0
  157. /package/types/generators/{MqttBroker.ts → MqttBroker.d.ts} +0 -0
  158. /package/types/generators/{MqttClient.ts → MqttClient.d.ts} +0 -0
  159. /package/types/generators/{Question.ts → Question.d.ts} +0 -0
  160. /package/types/generators/{RealtimeTranscription.ts → RealtimeTranscription.d.ts} +0 -0
  161. /package/types/generators/{SerialPort.ts → SerialPort.d.ts} +0 -0
  162. /package/types/generators/{SoundPlayer.ts → SoundPlayer.d.ts} +0 -0
  163. /package/types/generators/{SpeechToTextOnnx.ts → SpeechToTextOnnx.d.ts} +0 -0
  164. /package/types/generators/{SpeechToTextPlatform.ts → SpeechToTextPlatform.d.ts} +0 -0
  165. /package/types/generators/{SqLite.ts → SqLite.d.ts} +0 -0
  166. /package/types/generators/{Step.ts → Step.d.ts} +0 -0
  167. /package/types/generators/{Tcp.ts → Tcp.d.ts} +0 -0
  168. /package/types/generators/{TcpServer.ts → TcpServer.d.ts} +0 -0
  169. /package/types/generators/{TextToSpeechAppleBuiltin.ts → TextToSpeechAppleBuiltin.d.ts} +0 -0
  170. /package/types/generators/{TextToSpeechGgml.ts → TextToSpeechGgml.d.ts} +0 -0
  171. /package/types/generators/{TextToSpeechOnnx.ts → TextToSpeechOnnx.d.ts} +0 -0
  172. /package/types/generators/{TextToSpeechOpenAiLike.ts → TextToSpeechOpenAiLike.d.ts} +0 -0
  173. /package/types/generators/{Tick.ts → Tick.d.ts} +0 -0
  174. /package/types/generators/{Udp.ts → Udp.d.ts} +0 -0
  175. /package/types/generators/{VadOnnx.ts → VadOnnx.d.ts} +0 -0
  176. /package/types/generators/{VadTraditional.ts → VadTraditional.d.ts} +0 -0
  177. /package/types/generators/{VectorStore.ts → VectorStore.d.ts} +0 -0
  178. /package/types/generators/{Watchdog.ts → Watchdog.d.ts} +0 -0
  179. /package/types/generators/{WebCrawler.ts → WebCrawler.d.ts} +0 -0
  180. /package/types/generators/{WebRtc.ts → WebRtc.d.ts} +0 -0
  181. /package/types/generators/{WebSocket.ts → WebSocket.d.ts} +0 -0
  182. /package/types/generators/{index.ts → index.d.ts} +0 -0
  183. /package/types/{index.ts → index.d.ts} +0 -0
  184. /package/types/{switch.ts → switch.d.ts} +0 -0
  185. /package/types/{system.ts → system.d.ts} +0 -0
package/compile/index.ts CHANGED
@@ -6,10 +6,14 @@ import omit from 'lodash/omit'
6
6
  import { parse as parseAST } from 'acorn'
7
7
  import type { ExportNamedDeclaration, FunctionDeclaration } from 'acorn'
8
8
  import escodegen from 'escodegen'
9
- import { makeId } from '../utils/id'
9
+ import { makeSeededId } from '../utils/id'
10
10
  import { generateCalulationMap } from './util'
11
11
  import { templateActionNameMap } from './action-name-map'
12
12
  import { templateEventPropsMap } from '../utils/event-props'
13
+ import { sh } from '../tools/_shell'
14
+ import { computeConfigChange, readBuildConfig } from './config-diff'
15
+ import { appendEditRecord, editProvenance } from '../tools/_edits-log'
16
+ import { isTruthyEnv } from '../tools/mcp-env'
13
17
  import type {
14
18
  Application,
15
19
  Data,
@@ -154,11 +158,13 @@ const basicAnimationEvents = ['show', 'standby', 'breatheStart']
154
158
 
155
159
  const compileAnimations = (
156
160
  templateKey: string,
157
- animations: { [key: string]: Animation },
161
+ animations: { [key: string]: Animation | (() => Animation) },
158
162
  errorReference: string,
159
163
  ) =>
160
164
  Object.entries(animations).reduce((acc, [key, animation]) => {
161
- const animationId = assertEntryId(animation?.id, 'ANIMATION', errorReference)
165
+ // Animation events accept either a direct Animation or a getter; unwrap.
166
+ const resolved = typeof animation === 'function' ? animation() : animation
167
+ const animationId = assertEntryId(resolved?.id, 'ANIMATION', errorReference)
162
168
  acc[convertEventKey(basicAnimationEvents.includes(key) ? 'BRICK' : templateKey, key)] =
163
169
  `ANIMATION#${animationId}`
164
170
  return acc
@@ -179,10 +185,17 @@ const compileEvents = (
179
185
 
180
186
  let handlerKey
181
187
  let handlerTemplateKey
182
- if (handler === 'system' || typeof handler === 'string') {
183
- if (handler.startsWith('SUBSPACE_')) handlerKey = handler
184
- else handlerKey = handler.toUpperCase()
185
- if (handlerKey === 'SYSTEM') handlerTemplateKey = 'SYSTEM'
188
+ if (typeof handler === 'string') {
189
+ // Only the literal 'system' handler is normalized to the SYSTEM template key.
190
+ // SubspaceID (SUBSPACE_*) and ItemBrickID handlers are kept verbatim: the runtime
191
+ // resolves them case-sensitively (see mapEventMapHandlersWithNewId), so uppercasing
192
+ // a mixed-case ItemBrickID would break handler-to-item event wiring.
193
+ if (handler === 'system') {
194
+ handlerKey = 'SYSTEM'
195
+ handlerTemplateKey = 'SYSTEM'
196
+ } else {
197
+ handlerKey = handler
198
+ }
186
199
  } else if (typeof handler === 'function') {
187
200
  let instance = handler()
188
201
  if (instance?.id) {
@@ -301,6 +314,162 @@ const animationTypeMap = {
301
314
  AnimationTimingConfig: 'timing',
302
315
  AnimationSpringConfig: 'spring',
303
316
  AnimationDecayConfig: 'decay',
317
+ } as const
318
+
319
+ type CompiledAnimationType = (typeof animationTypeMap)[keyof typeof animationTypeMap]
320
+ type WarningMetadata = Record<string, unknown>
321
+
322
+ const animationProperties = new Set([
323
+ 'transform.translateX',
324
+ 'transform.translateY',
325
+ 'transform.scale',
326
+ 'transform.scaleX',
327
+ 'transform.scaleY',
328
+ 'transform.rotate',
329
+ 'transform.rotateX',
330
+ 'transform.rotateY',
331
+ 'opacity',
332
+ ])
333
+
334
+ const animationComposeTypes = new Set(['parallel', 'sequence'])
335
+ const springConfigFamilies = [
336
+ ['stiffness', 'damping', 'mass'],
337
+ ['tension', 'friction'],
338
+ ['bounciness', 'speed'],
339
+ ]
340
+ const springConfigFamilyKeys = new Set(springConfigFamilies.flat())
341
+
342
+ const isRecord = (value: unknown): value is Record<string, unknown> =>
343
+ Boolean(value) && typeof value === 'object' && !Array.isArray(value)
344
+
345
+ const hasDefinedConfigValue = (config: Record<string, unknown>, key: string) =>
346
+ config[key] !== undefined
347
+
348
+ const assertConfigValue = (
349
+ config: Record<string, unknown>,
350
+ key: string,
351
+ errorReference: string,
352
+ ) => {
353
+ if (!hasDefinedConfigValue(config, key)) {
354
+ throw new Error(`Invalid animation config ${errorReference}: missing "${key}"`)
355
+ }
356
+ }
357
+
358
+ const assertAnimationProperty = (property: unknown, errorReference: string) => {
359
+ if (typeof property !== 'string' || !animationProperties.has(property)) {
360
+ throw new Error(
361
+ `Invalid animation property${errorReference ? ` ${errorReference}` : ''}: ${String(
362
+ property,
363
+ )}`,
364
+ )
365
+ }
366
+ return property
367
+ }
368
+
369
+ const getAnimationType = (config: unknown, errorReference: string): CompiledAnimationType => {
370
+ if (!isRecord(config)) {
371
+ throw new Error(`Invalid animation config ${errorReference}: config must be an object`)
372
+ }
373
+
374
+ const animationType = animationTypeMap[config.__type as keyof typeof animationTypeMap]
375
+ if (!animationType) {
376
+ throw new Error(`Invalid animation config type ${errorReference}: ${String(config.__type)}`)
377
+ }
378
+ return animationType
379
+ }
380
+
381
+ const assertAnimationComposeType = (composeType: unknown, errorReference: string) => {
382
+ if (typeof composeType !== 'string' || !animationComposeTypes.has(composeType)) {
383
+ throw new Error(`Invalid animation compose type ${errorReference}: ${String(composeType)}`)
384
+ }
385
+ return composeType
386
+ }
387
+
388
+ const pickDefinedConfigValues = (config: Record<string, unknown>, keys: string[]) =>
389
+ keys.reduce((acc, key) => {
390
+ if (hasDefinedConfigValue(config, key)) acc[key] = config[key]
391
+ return acc
392
+ }, {})
393
+
394
+ const getDefinedConfigKeys = (config: Record<string, unknown>, keys: string[]) =>
395
+ keys.filter((key) => hasDefinedConfigValue(config, key))
396
+
397
+ const formatWarningMetadata = (metadata: WarningMetadata = {}) =>
398
+ Object.entries(metadata)
399
+ .filter(([, value]) => value !== undefined && value !== null && value !== '')
400
+ .map(([key, value]) => `${key}: ${String(value)}`)
401
+ .join(', ')
402
+
403
+ const formatWarningReference = (metadata?: WarningMetadata) => {
404
+ const metadataText = formatWarningMetadata(metadata)
405
+ return metadataText ? ` [${metadataText}]` : ''
406
+ }
407
+
408
+ const normalizeSpringConfig = (
409
+ config: Record<string, unknown>,
410
+ errorReference: string,
411
+ warningMetadata?: WarningMetadata,
412
+ ): Record<string, unknown> => {
413
+ assertConfigValue(config, 'toValue', errorReference)
414
+
415
+ const usedFamilies = springConfigFamilies.filter((keys) =>
416
+ keys.some((key) => hasDefinedConfigValue(config, key)),
417
+ )
418
+ if (usedFamilies.length <= 1) return config
419
+
420
+ const configWithoutSpringFamily = Object.entries(config).reduce((acc, [key, value]) => {
421
+ if (!springConfigFamilyKeys.has(key)) acc[key] = value
422
+ return acc
423
+ }, {})
424
+
425
+ // Match runtime normalization: physical spring values are most explicit,
426
+ // otherwise preserve BRICKS' historical tension/friction controls.
427
+ const resolvedFamily =
428
+ usedFamilies.find((keys) => keys.includes('stiffness')) ||
429
+ usedFamilies.find((keys) => keys.includes('tension')) ||
430
+ usedFamilies[0]
431
+ const resolvedFamilyKeys = getDefinedConfigKeys(config, resolvedFamily)
432
+ const droppedFamilyKeys = usedFamilies
433
+ .filter((keys) => keys !== resolvedFamily)
434
+ .flatMap((keys) => getDefinedConfigKeys(config, keys))
435
+
436
+ console.warn(
437
+ `[Warning] Resolved animation spring config${formatWarningReference(
438
+ warningMetadata,
439
+ )}: using ${resolvedFamilyKeys.join('/')}, dropping ${droppedFamilyKeys.join('/')}`,
440
+ )
441
+
442
+ return {
443
+ ...configWithoutSpringFamily,
444
+ ...pickDefinedConfigValues(config, resolvedFamily),
445
+ }
446
+ }
447
+
448
+ const compileAnimationConfig = (
449
+ animationType: CompiledAnimationType,
450
+ config: unknown,
451
+ errorReference: string,
452
+ warningMetadata?: WarningMetadata,
453
+ ) => {
454
+ if (!isRecord(config)) {
455
+ throw new Error(`Invalid animation config ${errorReference}: config must be an object`)
456
+ }
457
+
458
+ const compiledConfig = compileProperty(omit(config, '__type'), errorReference)
459
+
460
+ if (!isRecord(compiledConfig)) {
461
+ throw new Error(`Invalid animation config ${errorReference}: config must compile to an object`)
462
+ }
463
+
464
+ if (animationType === 'timing') {
465
+ assertConfigValue(compiledConfig, 'toValue', errorReference)
466
+ } else if (animationType === 'spring') {
467
+ return normalizeSpringConfig(compiledConfig, errorReference, warningMetadata)
468
+ } else if (animationType === 'decay') {
469
+ assertConfigValue(compiledConfig, 'velocity', errorReference)
470
+ }
471
+
472
+ return compiledConfig
304
473
  }
305
474
 
306
475
  const compileFrame = (frame: Canvas['items'][number]['frame']) => ({
@@ -324,9 +493,12 @@ const preloadTypes = [
324
493
  'media-resource-audio',
325
494
  'media-resource-file',
326
495
  'lottie-file-uri',
496
+ 'rive-file-uri',
327
497
  'ggml-model-asset',
328
498
  'gguf-model-asset',
329
499
  'binary-asset',
500
+ 'mlx-model-asset',
501
+ 'scene3d-objects',
330
502
  ]
331
503
 
332
504
  const compileKind = (kind: Data['kind']) => {
@@ -353,9 +525,10 @@ const compileKind = (kind: Data['kind']) => {
353
525
  }
354
526
 
355
527
  const compileRemoteUpdate = (remoteUpdate: Data['remoteUpdate']) => {
356
- if (!remoteUpdate) return {}
357
- if (remoteUpdate.type === 'auto') return { enable_remote_update: true }
528
+ if (!remoteUpdate) return { bank_type: 'none' }
529
+ if (remoteUpdate.type === 'auto') return { bank_type: 'create', enable_remote_update: true }
358
530
  return {
531
+ bank_type: remoteUpdate.type === 'device-specific' ? 'create-device-specific' : 'global',
359
532
  enable_remote_update: true,
360
533
  ...(remoteUpdate.type === 'device-specific' ? { use_remote_id_prefix: true } : {}),
361
534
  ...(remoteUpdate.type === 'global-data' ? { global_remote_update_prop: remoteUpdate.id } : {}),
@@ -485,6 +658,7 @@ const compileAutomationTest = (
485
658
 
486
659
  return {
487
660
  id: testId,
661
+ alias: test.alias,
488
662
  title: test.title,
489
663
  hide_short_ref: test.hideShortRef,
490
664
  timeout: test.timeout,
@@ -538,8 +712,38 @@ const compileAutomation = (automationMap: AutomationMap) =>
538
712
  }),
539
713
  )
540
714
 
715
+ // Record the minimal compiled-config delta this compile produced to the shared audit
716
+ // log (`.bricks/edits.jsonl`), so editing files directly and running `bun compile`
717
+ // leaves the same trail as the MCP source-editing tools. Maintained only in the
718
+ // editing-tools context (`BRICKS_CTOR_ENABLE_EDITING_TOOLS`); the source-editing tools
719
+ // turn it off for their verify compiles (see _verify.ts) so a tool edit records one
720
+ // richer entry instead of an extra generic compile entry. Also silent when there is no
721
+ // prior build to diff against (fresh projects, package tests, tooling outside a project).
722
+ const recordConfigChange = async (previousConfig: unknown, config: unknown) => {
723
+ if (previousConfig == null) return
724
+ if (!isTruthyEnv(process.env.BRICKS_CTOR_ENABLE_EDITING_TOOLS)) return
725
+ // The baseline was parsed from JSON; `computeConfigChange` applies the same
726
+ // JSON-omitted-field rules lazily so compile avoids cloning the full config.
727
+ const change = computeConfigChange(previousConfig, config)
728
+ if (change.status !== 'ok') return
729
+ await appendEditRecord(process.cwd(), {
730
+ ts: new Date().toISOString(),
731
+ tool: 'compile',
732
+ provenance: editProvenance(),
733
+ outcome: 'ok',
734
+ summary:
735
+ change.opCount === 0
736
+ ? 'compile: no config change'
737
+ : `compile: ${change.opCount} config op(s)`,
738
+ configChange: change,
739
+ }).catch(() => undefined)
740
+ }
741
+
541
742
  export const compile = async (app: Application) => {
542
743
  await new Promise((resolve) => setImmediate(resolve, 0))
744
+ // Snapshot the prior build artifact before the caller's compile.ts overwrites it, so
745
+ // the config change introduced by this compile can be recorded on return.
746
+ const previousConfig = await readBuildConfig(process.cwd())
543
747
  const timestamp = Date.now()
544
748
  // Pre-index subspace ids so the canvas-item validation below stays O(1).
545
749
  const subspaceIdSet = new Set(app.subspaces.map((s) => s.id))
@@ -559,7 +763,14 @@ export const compile = async (app: Application) => {
559
763
  // validation (root_canvas_id is required before the conditional
560
764
  // schema fix is published).
561
765
  if (subspace.module?.link) {
562
- const placeholderCanvasId = makeId('canvas')
766
+ // Seed the placeholder id from the (stable) subspace id. `makeId('canvas')` would take
767
+ // the count-fallback branch (a process-global counter that is never reset), so the
768
+ // placeholder id depended on how many prior count-fallback ids had been minted — making
769
+ // it differ between recompiles and breaking compile's byte-stable-output contract
770
+ // (phantom config-change ops). `makeSeededId` keeps no global state, so identical source
771
+ // recompiles to an identical id. (`makeId('canvas', alias)` would instead throw
772
+ // "Duplicate makeId alias" on the second compile in a long-lived process.)
773
+ const placeholderCanvasId = makeSeededId('canvas', `${subspaceId}:module-placeholder`)
563
774
  subspaceMap[subspaceId] = {
564
775
  title: subspace.title,
565
776
  description: subspace.description,
@@ -629,39 +840,63 @@ export const compile = async (app: Application) => {
629
840
  `(animation index: ${animationIndex}, subspace: ${subspaceId})`,
630
841
  )
631
842
 
632
- if (animation.__typename === 'Animation') {
843
+ const animationTypename = animation.__typename
844
+ if (animationTypename === 'Animation') {
633
845
  const animationDef = animation as AnimationDef
846
+ const animationErrorReference = `(animation: ${animationId}, subspace ${subspaceId})`
847
+ const animationWarningMetadata = {
848
+ animationIndex,
849
+ animationTitle: animationDef.title,
850
+ animationAlias: animationDef.alias,
851
+ animationProperty: animationDef.property,
852
+ subspaceIndex,
853
+ subspaceTitle: subspace.title,
854
+ }
855
+ const animationType = getAnimationType(animationDef.config, animationErrorReference)
634
856
  map[animationId] = {
635
857
  alias: animationDef.alias,
636
858
  title: animationDef.title,
637
859
  description: animationDef.description,
638
860
  hide_short_ref: animationDef.hideShortRef,
639
861
  animationRunType: animationDef.runType,
640
- property: animationDef.property,
641
- type: animationTypeMap[animationDef.config.__type],
642
- config: compileProperty(
643
- omit(animationDef.config, '__type'),
644
- `(animation: ${animationId}, subspace ${subspaceId})`,
862
+ property: assertAnimationProperty(animationDef.property, animationErrorReference),
863
+ type: animationType,
864
+ config: compileAnimationConfig(
865
+ animationType,
866
+ animationDef.config,
867
+ animationErrorReference,
868
+ animationWarningMetadata,
645
869
  ),
646
870
  }
647
- } else if (animation.__typename === 'AnimationCompose') {
871
+ } else if (animationTypename === 'AnimationCompose') {
648
872
  const animationDef = animation as AnimationComposeDef
873
+ const animationErrorReference = `(animation: ${animationId}, subspace ${subspaceId})`
649
874
  map[animationId] = {
650
875
  alias: animationDef.alias,
651
876
  title: animationDef.title,
652
877
  description: animationDef.description,
653
878
  hide_short_ref: animationDef.hideShortRef,
654
879
  animationRunType: animationDef.runType,
655
- compose_type: animationDef.composeType,
880
+ compose_type: assertAnimationComposeType(
881
+ animationDef.composeType,
882
+ animationErrorReference,
883
+ ),
656
884
  item_list: animationDef.items.map((item, index) => {
657
885
  const innerAnimation = item()
658
- if (!innerAnimation?.id)
659
- throw new Error(
660
- `Invalid animation index: ${index} (animation: ${innerAnimation.id}, subspace ${subspaceId})`,
661
- )
662
- return { animation_id: innerAnimation.id }
886
+ const innerAnimationId = assertEntryId(
887
+ innerAnimation?.id,
888
+ 'ANIMATION',
889
+ `(animation item index: ${index}, animation: ${animationId}, subspace ${subspaceId})`,
890
+ )
891
+ return { animation_id: innerAnimationId }
663
892
  }),
664
893
  }
894
+ } else {
895
+ throw new Error(
896
+ `Invalid animation typename (animation: ${animationId}, subspace ${subspaceId}): ${String(
897
+ animationTypename,
898
+ )}`,
899
+ )
665
900
  }
666
901
  return map
667
902
  }, {}),
@@ -1009,7 +1244,7 @@ export const compile = async (app: Application) => {
1009
1244
  ...compileRemoteUpdate(data.remoteUpdate),
1010
1245
  routing: data.routing,
1011
1246
  schema: data.schema,
1012
- type: data.type,
1247
+ type: data.type === 'boolean' ? 'bool' : data.type,
1013
1248
  ...compileKind(data.kind),
1014
1249
  value: compileProperty(data.value, `(data: ${dataId}, subspace ${subspaceId})`),
1015
1250
  event_map: compileEvents('PROPERTY_BANK', data.events || {}, {
@@ -1029,6 +1264,7 @@ export const compile = async (app: Application) => {
1029
1264
  )
1030
1265
 
1031
1266
  const calc: any = {
1267
+ alias: dataCalc.alias,
1032
1268
  title: dataCalc.title,
1033
1269
  description: dataCalc.description,
1034
1270
  hide_short_ref: dataCalc.hideShortRef,
@@ -1227,12 +1463,7 @@ export const compile = async (app: Application) => {
1227
1463
  : null,
1228
1464
  }
1229
1465
 
1230
- Object.assign(
1231
- calc,
1232
- generateCalulationMap(calc.script_config, {
1233
- snapshotMode: process.env.BRICKS_SNAPSHOT_MODE === '1',
1234
- }),
1235
- )
1466
+ Object.assign(calc, generateCalulationMap(calc.script_config, dataCalcId))
1236
1467
  }
1237
1468
  map[dataCalcId] = calc
1238
1469
  return map
@@ -1269,10 +1500,20 @@ export const compile = async (app: Application) => {
1269
1500
  automation_map: compiledAutomationMap || app.metadata?.TEMP_automation_map || {},
1270
1501
  update_timestamp: timestamp,
1271
1502
  }
1503
+ await recordConfigChange(previousConfig, config)
1272
1504
  return config
1273
1505
  }
1274
1506
 
1275
1507
  export const checkConfig = async (configPath: string) => {
1276
- const { sh } = await import('../tools/_shell')
1277
- await sh`bricks app check-config ${configPath}`
1508
+ // --validate-automation surfaces broken automation_map / test_map refs early,
1509
+ // which catches agent-authored automations that reference deleted bricks.
1510
+ await sh`bricks app check-config --validate-automation ${configPath}`
1511
+ // Doctor adds semantic lint checks after structural validation. Warnings are
1512
+ // surfaced in the compile log, but only errors fail by default. Older published
1513
+ // bricks-cli builds lack `app doctor` — skip rather than fail the compile.
1514
+ const doctor = await sh`bricks app doctor --validate-automation ${configPath}`.nothrow()
1515
+ if (doctor.exitCode !== 0) {
1516
+ if (/unknown command/i.test(doctor.stderr?.toString() ?? '')) return
1517
+ throw new Error(`bricks app doctor failed with exit ${doctor.exitCode}`)
1518
+ }
1278
1519
  }
package/compile/util.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { makeId } from '../utils/id'
1
+ import { makeSeededId } from '../utils/id'
2
2
 
3
3
  type ScriptConfig = {
4
4
  title?: string
@@ -27,21 +27,40 @@ export const validateConfig = (config: ScriptConfig) => {
27
27
  if (Object.values(config.outputs).some((value) => value.some((id) => config.inputs[id]))) {
28
28
  throw new Error(`${errorMsg}. key: outputs`)
29
29
  }
30
+ // The same data-node id reused across the output-side targets (output / error / outputs)
31
+ // also collides: generateCalulationMap spreads their node objects last-wins, so the later
32
+ // one silently overwrites the earlier wiring (e.g. error == output drops the error change
33
+ // link). The checks above only compare against `inputs`, so guard the output-side pairs
34
+ // too. (The same id appearing in multiple `outputs` entries is a supported last-wins case
35
+ // and stays allowed.)
36
+ if (config.error && config.output && config.error === config.output) {
37
+ throw new Error(`${errorMsg}. key: error/output`)
38
+ }
39
+ const outputsIds = Object.values(config.outputs).flat()
40
+ if (config.output && outputsIds.includes(config.output)) {
41
+ throw new Error(`${errorMsg}. key: output/outputs`)
42
+ }
43
+ if (config.error && outputsIds.includes(config.error)) {
44
+ throw new Error(`${errorMsg}. key: error/outputs`)
45
+ }
30
46
  }
31
47
 
32
48
  const padding = 15
33
49
  const layerXInterval = 300
34
50
  const layerYInterval = 150
35
51
 
36
- export const generateCalulationMap = (config: ScriptConfig, opts?: { snapshotMode?: boolean }) => {
52
+ // `calcId` (the owning script calc's stable id) seeds every derived command-node id, so an
53
+ // unchanged calc recompiles to identical ids and editing one calc never shifts another's —
54
+ // keeping the compiled config byte-stable for change detection. See makeSeededId in utils/id.
55
+ export const generateCalulationMap = (config: ScriptConfig, calcId: string) => {
37
56
  validateConfig(config)
38
- const sandboxId = makeId('property_bank_command', opts)
39
- const sandboxErrorId = makeId('property_bank_command', opts)
40
- const sandboxResultId = makeId('property_bank_command', opts)
57
+ const sandboxId = makeSeededId('property_bank_command', `${calcId}:sandbox-run`)
58
+ const sandboxErrorId = makeSeededId('property_bank_command', `${calcId}:sandbox-error`)
59
+ const sandboxResultId = makeSeededId('property_bank_command', `${calcId}:sandbox-result`)
41
60
 
42
61
  const inputs = Object.entries(config.inputs).reduce(
43
62
  (acc, [key, value], index) => {
44
- const commandId = makeId('property_bank_command', opts)
63
+ const commandId = makeSeededId('property_bank_command', `${calcId}:input:${key}`)
45
64
  acc.map[key] = {
46
65
  type: 'data-node',
47
66
  properties: {},
@@ -123,7 +142,7 @@ export const generateCalulationMap = (config: ScriptConfig, opts?: { snapshotMod
123
142
  let y = 0
124
143
  const outputs = Object.entries(config.outputs).reduce(
125
144
  (acc, [key, pbList], index) => {
126
- const commandId = makeId('property_bank_command', opts)
145
+ const commandId = makeSeededId('property_bank_command', `${calcId}:output:${key}`)
127
146
  acc.commandIdList.push(commandId)
128
147
  acc.map[commandId] = {
129
148
  type: 'command-node-object',
package/package.json CHANGED
@@ -1,13 +1,17 @@
1
1
  {
2
2
  "name": "@fugood/bricks-ctor",
3
- "version": "2.25.0-beta.5",
3
+ "version": "2.25.0-beta.50",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "typecheck": "tsc --noEmit",
7
7
  "build": "bun scripts/build.js"
8
8
  },
9
9
  "dependencies": {
10
- "@fugood/bricks-cli": "^2.25.0-beta.5",
10
+ "@babel/generator": "7.28.5",
11
+ "@babel/parser": "7.28.5",
12
+ "@babel/traverse": "7.28.5",
13
+ "@babel/types": "7.28.5",
14
+ "@fugood/bricks-cli": "^2.25.0-beta.50",
11
15
  "@huggingface/gguf": "^0.3.2",
12
16
  "@iarna/toml": "^3.0.0",
13
17
  "@modelcontextprotocol/sdk": "^1.15.0",
@@ -25,5 +29,5 @@
25
29
  "peerDependencies": {
26
30
  "oxfmt": "^0.36.0"
27
31
  },
28
- "gitHead": "ff0abf1f06694c6d71a1a14ac52c37f9a72d1ac3"
32
+ "gitHead": "3614445cd0166e40634f69c6238299bb63f4f597"
29
33
  }
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: bricks-ctor
3
- description: Advanced BRICKS configuration knowledge. Covers Standby Transition, Automations (E2E testing), Data Calculation (JS sandbox libraries), Local Sync, Remote Data Bank, Media Flow, and Buttress (remote inference). Triggers on multi-device sync, cloud data, media assets, AI offloading, E2E testing, or canvas transitions.
3
+ description: Advanced BRICKS configuration knowledge. Covers Standby Transition, Automations (E2E testing), Data Calculation (JS sandbox libraries), Local Sync, Remote Data Bank, Media Flow, Buttress (remote inference), and the verification toolchain (compile, harness-specific preview tool, on-device DevTools, definition-of-done gate). Triggers on multi-device sync, cloud data, media assets, AI offloading, E2E testing, canvas transitions, or verifying project work before declaring done.
4
4
  ---
5
5
 
6
6
  # BRICKS Ctor - Advanced Features
@@ -11,22 +11,28 @@ This skill covers advanced BRICKS features not in the main project instructions.
11
11
 
12
12
  | Rule | Description |
13
13
  |------|-------------|
14
- | [Architecture Patterns](rules/architecture-patterns.md) | **Read first** — decompose flows and select patterns |
15
- | [Animation](rules/animation.md) | Animation system for brick transforms and opacity |
16
- | [Standby Transition](rules/standby-transition.md) | Canvas enter/exit animations |
17
- | [Automations](rules/automations.md) | E2E testing and scheduled tasks |
18
- | [Data Calculation](rules/data-calculation.md) | JS sandbox libraries (25+ available) |
19
- | [Local Sync](rules/local-sync.md) | LAN device synchronization |
20
- | [Remote Data Bank](rules/remote-data-bank.md) | Cloud data sync and API access |
21
- | [Media Flow](rules/media-flow.md) | Media asset management |
22
- | [Buttress](rules/buttress.md) | Remote inference for AI generators |
14
+ | [Architecture Patterns](references/architecture-patterns.md) | **Read first** — decompose flows and select patterns |
15
+ | [Source-Editing Tools](references/source-editing-tools.md) | MCP tools for editing entries and data-calcs by AST (new/edit/remove, value grammar, verify) |
16
+ | [Animation](references/animation.md) | Animation system for brick transforms and opacity |
17
+ | [Standby Transition](references/standby-transition.md) | Canvas enter/exit animations |
18
+ | [Automations](references/automations.md) | E2E testing and scheduled tasks |
19
+ | [Data Calculation](references/data-calculation.md) | Wiring contract, trigger semantics, JS sandbox (25+ libraries) |
20
+ | [Local Sync](references/local-sync.md) | LAN device synchronization |
21
+ | [Remote Data Bank](references/remote-data-bank.md) | Cloud data sync and API access |
22
+ | [Media Flow](references/media-flow.md) | Media asset management |
23
+ | [Buttress](references/buttress.md) | Remote inference for AI generators |
24
+ | [Verification Toolchain](references/verification-toolchain.md) | Definition of done, compile, preview tool selection, on-device DevTools, Path 1/2/3 decision rule |
25
+ | [Simulator](references/simulator.md) | Path 1 fidelity — Simulator feature support, fallbacks (Camera/LLM/STT/Vector Store, Buttress disabled), when a green run is enough vs. Path 2 |
23
26
 
24
27
  ## Quick Reference
25
28
 
26
- - **Complex flows**: See [Architecture Patterns](rules/architecture-patterns.md) for decomposing multi-step workflows
27
- - **Multi-device**: See [Local Sync](rules/local-sync.md) for LAN coordination
28
- - **Cloud data**: See [Remote Data Bank](rules/remote-data-bank.md) for sync and API access
29
- - **Media assets**: See [Media Flow](rules/media-flow.md) for centralized asset management
30
- - **AI offloading**: See [Buttress](rules/buttress.md) for GPU server delegation
31
- - **E2E testing**: See [Automations](rules/automations.md) for test automation
32
- - **Enter animations**: See [Standby Transition](rules/standby-transition.md) for canvas transitions
29
+ - **Complex flows**: See [Architecture Patterns](references/architecture-patterns.md) for decomposing multi-step workflows
30
+ - **Editing entries/data-calcs**: See [Source-Editing Tools](references/source-editing-tools.md) prefer the MCP editing tools over hand-editing `subspaces/**`
31
+ - **Multi-device**: See [Local Sync](references/local-sync.md) for LAN coordination
32
+ - **Cloud data**: See [Remote Data Bank](references/remote-data-bank.md) for sync and API access
33
+ - **Media assets**: See [Media Flow](references/media-flow.md) for centralized asset management
34
+ - **AI offloading**: See [Buttress](references/buttress.md) for GPU server delegation
35
+ - **E2E testing**: See [Automations](references/automations.md) for test automation
36
+ - **Enter animations**: See [Standby Transition](references/standby-transition.md) for canvas transitions
37
+ - **Verification before done**: See [Verification Toolchain](references/verification-toolchain.md) for the definition-of-done gate and Path 1/2/3 decision rule
38
+ - **Simulator fidelity**: See [Simulator](references/simulator.md) for what the Simulator fakes (Camera, AI generators, Buttress) and when to escalate to a real device
@@ -41,12 +41,13 @@ const bounce: AnimationDef = {
41
41
  toValue: 1,
42
42
  friction: 7,
43
43
  tension: 40,
44
- speed: 12,
45
- bounciness: 8,
46
44
  },
47
45
  }
48
46
  ```
49
47
 
48
+ Use one spring parameter family per config: `tension/friction`, `speed/bounciness`, or
49
+ `stiffness/damping/mass`.
50
+
50
51
  ### Decay Animation
51
52
  Velocity-based deceleration animation.
52
53
 
@@ -26,10 +26,18 @@ The primary way to orchestrate multi-step flows. A single event can contain an a
26
26
  - Use `dataParams` + `mapping` to pass event data downstream
27
27
  - This is the "glue" that wires generators, state, and UI together
28
28
 
29
+ Sequential `PROPERTY_BANK` / `PROPERTY_BANK_EXPRESSION` actions in one chain read the data values that existed when the chain started. If a later action needs to read what an earlier action wrote, set `waitAsync: true` on the earlier action.
30
+
29
31
  ### System Actions (Priority 3)
30
32
  Built-in commands for direct state and UI changes.
31
33
  - **PROPERTY_BANK**: set data value
32
34
  - **PROPERTY_BANK_EXPRESSION**: inline JS expression for simple compute
35
+ - The expression engine folds statements into a single expression: only expression
36
+ statements, simple `const`/`let` declarations, and a final return/expression are
37
+ supported — **no `if`/`for`/`while`/`switch`** (use ternaries). The same limit
38
+ applies inside a zero-arg IIFE body. Unsupported statements fail at runtime with
39
+ the error visible only in a DevTools session, so prefer ternary chains or move the
40
+ logic to a DataCalculationScript.
33
41
  - **CHANGE_CANVAS**: navigate to another canvas
34
42
  - **DYNAMIC_ANIMATION**: trigger animation
35
43
  - **ALERT / MESSAGE**: system feedback
@@ -67,3 +75,13 @@ Only actual data derivation maps to Data Calculation:
67
75
 
68
76
  ### Step 4: Wire with Event Action Chains
69
77
  Connect the pieces through events on generators and bricks.
78
+
79
+ ## Recipe: user-driven state machine (calculator, form wizard, picker)
80
+
81
+ A brief like "state vars X, Y, Z; button A updates X from X+Y; button B resets" is a state machine. The shape:
82
+
83
+ - Each state variable is its own `Data` entity. Displays read it via `linkData(() => data.dFoo)`.
84
+ - Each button's `onPress` is an Event Action Chain. For every state var that changes, append one `PROPERTY_BANK_EXPRESSION` whose `expression` reads the current state and returns the new value.
85
+ - Use `Data Calculation` only for reusable pure derivations referenced as inputs to those expressions.
86
+
87
+ A 10-button calculator with 4 state vars is ~10 small chains of 1–4 inline expressions. If you find yourself writing a single auto-mode `DataCalculationScript` that consumes all state vars and emits all-new state through mirror `dFooResult` data — or pinging a `dLastInput` field to force an auto calc to re-run — the chain shape was the right answer.
@@ -118,6 +118,16 @@ const testLoginFlow: AutomationTest = {
118
118
  | `match_screenshot` | `[name, threshold?, maxRetry?]` | Screenshot compare |
119
119
  | `delay` | `[subspace?, property?, defaultValue?]` | Delay execution |
120
120
 
121
+ In project TypeScript source, pass entity getters for BRICKS entities:
122
+
123
+ ```typescript
124
+ run: ['brick_press', () => mainSubspace, () => bricks.bSubmitButton]
125
+ run: ['wait_until_canvas_change', () => mainSubspace, () => canvases.cDone, 5000]
126
+ run: ['assert_property', () => mainSubspace, () => data.dStep, 'done']
127
+ ```
128
+
129
+ The compiler resolves these getters to the current generated IDs.
130
+
121
131
  ### execute_action Params
122
132
 
123
133
  The `params` object in `execute_action` uses **runtime event property keys** from `event-props.ts`, NOT the action config `input` names from type definitions.
@@ -208,6 +218,7 @@ Automations work with Modules. Use Manual Run in Preview mode for module testing
208
218
 
209
219
  - **Automation map key**: Always use `'AUTOMATION_MAP_DEFAULT'` as the automation map ID (not `makeId()`). The preview test runner reads from `automationMap['AUTOMATION_MAP_DEFAULT']?.map`.
210
220
  - **Valid makeId types**: Use `'test'` for AutomationTest, `'test_case'` for TestCase, `'test_var'` for TestVariable. Do NOT use `'automation_test'` or `'automation_test_map'`.
221
+ - **Entity references in run arrays**: Use getter references (`() => subspace`, `() => bricks.bButton`, `() => data.dValue`) in TypeScript source so compile resolves fresh IDs.
211
222
  - **handler in execute_action**: Pass the entity's `.id` string (e.g., `bricks.bInput.id`), not a getter function.
212
223
 
213
224
  ## Best Practices