@fugood/bricks-ctor 2.24.9 → 2.24.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/compile/index.ts CHANGED
@@ -4,7 +4,7 @@ import upperFirst from 'lodash/upperFirst'
4
4
  import snakeCase from 'lodash/snakeCase'
5
5
  import omit from 'lodash/omit'
6
6
  import { parse as parseAST } from 'acorn'
7
- import type { ExportNamedDeclaration, FunctionDeclaration } from 'acorn'
7
+ import type { BlockStatement, ExportNamedDeclaration, FunctionDeclaration } from 'acorn'
8
8
  import escodegen from 'escodegen'
9
9
  import { makeSeededId } from '../utils/id'
10
10
  import { generateCalulationMap } from './util'
@@ -97,17 +97,28 @@ const compileProperty = (property, errorReference: string, result = {}) => {
97
97
  const compileScriptCalculationCode = (code = '') => {
98
98
  try {
99
99
  const program = parseAST(code, { sourceType: 'module', ecmaVersion: 2020 })
100
- // export function main() { ... }
101
- const declarationBody = (
102
- (program.body[0] as ExportNamedDeclaration).declaration as FunctionDeclaration
103
- )?.body
104
- return escodegen.generate(declarationBody, {
105
- format: {
106
- indent: { style: ' ' },
107
- semicolons: false,
100
+ // The stored config holds the bare function body, which codegen re-wraps as
101
+ // `export function main() { <code> }`. Unwrap it back to that body here.
102
+ let block = ((program.body[0] as ExportNamedDeclaration).declaration as FunctionDeclaration)
103
+ ?.body as BlockStatement | undefined
104
+ if (!block) return code || ''
105
+ // Earlier versions emitted the whole BlockStatement (braces included), so every
106
+ // compile -> codegen round-trip nested the body in one more `{ }`. Emit the inner
107
+ // statements instead, collapsing any wrapper blocks previous round-trips added so
108
+ // existing over-wrapped sandboxes heal on the next compile.
109
+ while (block.body.length === 1 && block.body[0].type === 'BlockStatement') {
110
+ block = block.body[0] as BlockStatement
111
+ }
112
+ return escodegen.generate(
113
+ { type: 'Program', body: block.body },
114
+ {
115
+ format: {
116
+ indent: { style: ' ' },
117
+ semicolons: false,
118
+ },
119
+ comment: true,
108
120
  },
109
- comment: true,
110
- })
121
+ )
111
122
  } catch {
112
123
  return code || ''
113
124
  }
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "@fugood/bricks-ctor",
3
- "version": "2.24.9",
3
+ "version": "2.24.10",
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.24.9",
10
+ "@fugood/bricks-cli": "^2.24.10",
11
11
  "@huggingface/gguf": "^0.3.2",
12
12
  "@iarna/toml": "^3.0.0",
13
13
  "@modelcontextprotocol/sdk": "^1.15.0",
@@ -25,6 +25,7 @@ The primary way to orchestrate multi-step flows. A single event can contain an a
25
25
  - Use `waitAsync: true` to await async actions before the next step
26
26
  - Use `dataParams` + `mapping` to pass event data downstream
27
27
  - This is the "glue" that wires generators, state, and UI together
28
+ - For Generator result UI: await Generator, write done/version Data; route/current Data stays identity.
28
29
 
29
30
  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
31
 
@@ -87,6 +87,17 @@ const triggerCalc: EventAction = {
87
87
  - When a **later action reads the calc's outputs**, set `waitAsync: true` on the `PROPERTY_BANK_COMMAND` action itself — it awaits the full calc chain including output writes.
88
88
  - A dataParam's `value` acts as an execution gate: `{ input: () => d, value: false }` skips that trigger (combine with `mapping` for conditional runs).
89
89
 
90
+ ## One trigger source per result panel
91
+
92
+ A panel that shows the result of an async action (Generator HTTP/LLM response, request telemetry) reads from a calc whose **trigger should come from a single source**: either the live async outlet, or a completion marker the action chain writes — not both.
93
+
94
+ - **Outlet-triggered** (`{ data: () => dResponse, trigger: true }`): the calc reruns as outlets land. Use when each outlet is only written once its value is final.
95
+ - **Marker-triggered**: run the generator with `waitAsync: true`, then write a `done` Data the calc triggers on. Use when several outlets settle separately and the panel must wait for all of them.
96
+
97
+ Wiring both at once — triggering on the live outlet _and_ gating on a separately-written marker — lets the calc run before the outlets have settled (it renders `undefined`/stale) while the marker path masks it intermittently. A panel that updates "sometimes" usually has two competing triggers: pick one, and order it with `waitAsync` so the trigger fires after the values it reads are written.
98
+
99
+ Scope the panel's calc to the **single value it derives** (the formatted display string), and write sibling status labels — running/done, progress, selected route — imperatively in the event chain with `PROPERTY_BANK`. A calc rewrites _every_ one of its outputs on each run, so a return that omits a key writes `undefined` to that output Data (see [Field Rules](#field-rules-defaults-and-constraints)): folding status labels into a multi-output result calc resets them to `undefined` on any run that returns only the derived value. If a calc genuinely must drive several outputs, return all of their keys every run.
100
+
90
101
  ## Script Sandbox
91
102
 
92
103
  Scripts run in `use strict` mode as a function body — top-level `return` returns the calc result. No `fetch`, `XMLHttpRequest`, or `require` in any mode: I/O belongs to Generators.
@@ -220,6 +231,8 @@ const appendHistory: DataCalculationScript = {
220
231
  | `PROPERTY_BANK_COMMAND` does nothing | Auto calc + `trigger: false` input, or `input` doesn't reference an input Data of the calc | Command a `trigger: true` input (auto) or any input (manual) |
221
232
  | Compile error `Not allow duplicate set property id...` | Auto mode with same Data as input and output | Use `triggerMode: 'manual'`, or split into separate Data |
222
233
  | Calc reads stale value written earlier in the same chain | Missing `waitAsync: true` on the preceding write | Set `waitAsync: true` on the write action |
234
+ | Result/telemetry panel shows `undefined` or stale data intermittently | Calc triggered by two sources at once (live outlet + a separately-written marker) | Trigger from one source — see [One trigger source per result panel](#one-trigger-source-per-result-panel) |
235
+ | Sibling status Data (selected/progress) resets to `undefined` after a calc runs | Multi-output result calc whose return omitted those keys on that run | Scope the calc to one output and write status labels imperatively, or return every output key each run — see [One trigger source per result panel](#one-trigger-source-per-result-panel) |
223
236
  | Works in Simulator, fails on device | V8 vs Hermes/JSC engine difference | Verify on device (Path 2); avoid engine-sensitive parsing |
224
237
  | `console.log` shows nothing | Console only emits during DevTools debug sessions | Attach DevTools, or write debug values to an output Data |
225
238
  | `Promise`/`setTimeout` undefined, or `Async mode is required` error | `enableAsync: false` | Set `enableAsync: true` |
package/tools/pull.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { readdir, readFile, unlink, writeFile } from 'node:fs/promises'
1
+ import { mkdir, readdir, readFile, unlink, writeFile } from 'node:fs/promises'
2
2
  import { existsSync } from 'node:fs'
3
- import { join, relative } from 'node:path'
3
+ import { dirname, join, relative } from 'node:path'
4
4
  import { format } from 'oxfmt'
5
5
  import { sh } from './_shell'
6
6
  import { extractCliErrorMessage } from './_cli-error'
@@ -86,7 +86,11 @@ const branchName = isModule
86
86
  ? 'BRICKS_PROJECT_try-pull-module'
87
87
  : 'BRICKS_PROJECT_try-pull-application'
88
88
 
89
+ let landingBranch = ''
89
90
  if (isGitRepo && !force) {
91
+ landingBranch = (await sh`cd ${cwd} && git branch --show-current`.text()).trim()
92
+ if (!landingBranch) throw new Error('Cannot pull from a detached HEAD')
93
+
90
94
  console.log(`Checking commit ${baseCommitId}...`)
91
95
  const found = (await sh`cd ${cwd} && git rev-list -1 ${baseCommitId}`.nothrow().text())
92
96
  .trim()
@@ -100,8 +104,8 @@ if (isGitRepo && !force) {
100
104
 
101
105
  // When the base commit isn't reachable in this clone (server stored a
102
106
  // nanoid, or the commit was pruned), fall back to forking from current
103
- // HEAD. The downstream merge into main collapses both paths into the
104
- // same result, just with different merge bases.
107
+ // HEAD. The downstream merge into the starting branch collapses both paths
108
+ // into the same result, just with different merge bases.
105
109
  if (found) {
106
110
  await sh`cd ${cwd} && git checkout -b ${branchName} ${baseCommitId}`.nothrow()
107
111
  } else {
@@ -143,7 +147,9 @@ await Promise.all(
143
147
  const result = await format(file.name, file.input, oxfmtConfig)
144
148
  content = result.code
145
149
  }
146
- return writeFile(`${cwd}/${file.name}`, content)
150
+ const target = join(cwd, file.name)
151
+ await mkdir(dirname(target), { recursive: true })
152
+ return writeFile(target, content)
147
153
  }),
148
154
  )
149
155
 
@@ -160,11 +166,11 @@ if (isGitRepo) {
160
166
  await sh`cd ${cwd} && git ${commitArgs}`
161
167
  }
162
168
  if (!force) {
163
- // Land the pulled commits on main with a single 3-way merge using
169
+ // Land the pulled commits on the starting branch with a single 3-way merge using
164
170
  // baseCommit as the merge base. The user doesn't have to manage a side
165
- // branch, and conflicts (if any) land in the working tree on main where
171
+ // branch, and conflicts (if any) land in the working tree on the starting branch where
166
172
  // auto-compile surfaces them as typecheck errors to resolve in-place.
167
- await sh`cd ${cwd} && git checkout main`
173
+ await sh`cd ${cwd} && git checkout ${landingBranch}`
168
174
  const mergeResult = await sh`cd ${cwd} && git merge ${branchName} --no-edit`.nothrow()
169
175
  if (mergeResult.exitCode !== 0) {
170
176
  // Conflict markers are in the working tree — commit them so the tree
@@ -174,7 +180,7 @@ if (isGitRepo) {
174
180
  await sh`cd ${cwd} && git add .`
175
181
  const conflictArgs = await buildCommitArgs(
176
182
  cwd,
177
- ['chore(project): merge with conflicts (resolve in main)'],
183
+ [`chore(project): merge with conflicts (resolve in ${landingBranch})`],
178
184
  ['--no-verify'],
179
185
  )
180
186
  await sh`cd ${cwd} && git ${conflictArgs}`
@@ -191,9 +191,11 @@ Default property:
191
191
  /* A stroke was just committed (drawn or added programmatically) */
192
192
  onStrokeEnd?: Array<EventAction<string & keyof TemplateEventPropsMap['Sketch']['onStrokeEnd']>>
193
193
  /* The canvas was cleared */
194
- onClear?: Array<EventAction>
194
+ onClear?: Array<EventAction<string & keyof TemplateEventPropsMap['Sketch']['onClear']>>
195
195
  /* Sketch state changed (any commit, undo, redo, clear, or import) */
196
- onStateChange?: Array<EventAction>
196
+ onStateChange?: Array<
197
+ EventAction<string & keyof TemplateEventPropsMap['Sketch']['onStateChange']>
198
+ >
197
199
  /* Active tool changed */
198
200
  onToolChange?: Array<
199
201
  EventAction<string & keyof TemplateEventPropsMap['Sketch']['onToolChange']>
@@ -33,7 +33,7 @@ Default property:
33
33
  property?: {
34
34
  /* Start tick on generator initialized */
35
35
  init?: boolean | DataLink
36
- /* Interval of second for countdown */
36
+ /* Tick interval in milliseconds for countdown */
37
37
  interval?: number | DataLink
38
38
  /* Initial value of countdown */
39
39
  countdownStartValue?: number | DataLink
package/utils/data.ts CHANGED
@@ -85,7 +85,7 @@ type SystemDataName =
85
85
  type SystemDataInfo = {
86
86
  name: SystemDataName
87
87
  id: string
88
- type: 'string' | 'number' | 'bool' | 'boolean' | 'array' | 'object' | 'any'
88
+ type: Data['type']
89
89
  title?: string
90
90
  description?: string
91
91
  schema?: object
@@ -142,7 +142,9 @@ export const templateEventPropsMap = {
142
142
  },
143
143
  },
144
144
  Sketch: {
145
- onStrokeEnd: { BRICK_SKETCH_STROKE_COUNT: 'number' },
145
+ onStrokeEnd: { BRICK_SKETCH_STROKE_COUNT: 'number', BRICK_SKETCH_STATE: '{}' },
146
+ onClear: { BRICK_SKETCH_STATE: '{}' },
147
+ onStateChange: { BRICK_SKETCH_STATE: '{}' },
146
148
  onToolChange: { BRICK_SKETCH_TOOL: 'string' },
147
149
  onExportImage: { BRICK_SKETCH_IMAGE_URI: 'string' },
148
150
  },