@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 +22 -11
- package/package.json +2 -2
- package/skills/bricks-ctor/references/architecture-patterns.md +1 -0
- package/skills/bricks-ctor/references/data-calculation.md +13 -0
- package/tools/pull.ts +15 -9
- package/types/bricks/Sketch.d.ts +4 -2
- package/types/generators/Tick.d.ts +1 -1
- package/utils/data.ts +1 -1
- package/utils/event-props.ts +3 -1
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
|
-
//
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
[
|
|
183
|
+
[`chore(project): merge with conflicts (resolve in ${landingBranch})`],
|
|
178
184
|
['--no-verify'],
|
|
179
185
|
)
|
|
180
186
|
await sh`cd ${cwd} && git ${conflictArgs}`
|
package/types/bricks/Sketch.d.ts
CHANGED
|
@@ -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<
|
|
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
|
-
/*
|
|
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
package/utils/event-props.ts
CHANGED
|
@@ -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
|
},
|