@fugood/bricks-project 2.25.0-beta.45 → 2.25.0-beta.46

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.
@@ -27,17 +27,60 @@ export type ConfigChange =
27
27
  const isRecord = (value: unknown): value is Record<string, unknown> =>
28
28
  typeof value === 'object' && value !== null && !Array.isArray(value)
29
29
 
30
+ const isJsonDroppedValue = (value: unknown) =>
31
+ value === undefined || typeof value === 'function' || typeof value === 'symbol'
32
+
33
+ const toJsonComparableScalar = (value: unknown) => {
34
+ if (typeof value === 'number' && !Number.isFinite(value)) return null
35
+ return value
36
+ }
37
+
38
+ const toJsonCompatibleValue = (value: unknown): unknown => {
39
+ if (isJsonDroppedValue(value)) return undefined
40
+ if (Array.isArray(value)) {
41
+ return value.map((item) => (isJsonDroppedValue(item) ? null : toJsonCompatibleValue(item)))
42
+ }
43
+ if (isRecord(value)) {
44
+ return Object.entries(value).reduce((acc, [key, item]) => {
45
+ if (!isJsonDroppedValue(item)) acc[key] = toJsonCompatibleValue(item)
46
+ return acc
47
+ }, {})
48
+ }
49
+ return toJsonComparableScalar(value)
50
+ }
51
+
52
+ const hasJsonObjectKey = (value: Record<string, unknown>, key: string) =>
53
+ Object.prototype.hasOwnProperty.call(value, key) && !isJsonDroppedValue(value[key])
54
+
55
+ const getJsonArrayItem = (value: unknown[], index: number) => {
56
+ const item = value[index]
57
+ return isJsonDroppedValue(item) ? null : item
58
+ }
59
+
30
60
  const deepEqual = (a: unknown, b: unknown): boolean => {
61
+ a = toJsonComparableScalar(a)
62
+ b = toJsonComparableScalar(b)
31
63
  if (a === b) return true
32
64
  if (Array.isArray(a) && Array.isArray(b)) {
33
- return a.length === b.length && a.every((item, index) => deepEqual(item, b[index]))
65
+ if (a.length !== b.length) return false
66
+ for (let index = 0; index < a.length; index += 1) {
67
+ if (!deepEqual(getJsonArrayItem(a, index), getJsonArrayItem(b, index))) return false
68
+ }
69
+ return true
34
70
  }
35
71
  if (isRecord(a) && isRecord(b)) {
36
- const aKeys = Object.keys(a)
37
- return (
38
- aKeys.length === Object.keys(b).length &&
39
- aKeys.every((key) => key in b && deepEqual(a[key], b[key]))
40
- )
72
+ let comparableAKeys = 0
73
+ for (const key of Object.keys(a)) {
74
+ if (isJsonDroppedValue(a[key])) continue
75
+ comparableAKeys += 1
76
+ if (!hasJsonObjectKey(b, key) || !deepEqual(a[key], b[key])) return false
77
+ }
78
+
79
+ let comparableBKeys = 0
80
+ for (const key of Object.keys(b)) {
81
+ if (!isJsonDroppedValue(b[key])) comparableBKeys += 1
82
+ }
83
+ return comparableAKeys === comparableBKeys
41
84
  }
42
85
  return false
43
86
  }
@@ -64,22 +107,32 @@ const diffInto = (
64
107
  if (isRecord(before) && isRecord(after)) {
65
108
  const keys = new Set([...Object.keys(before), ...Object.keys(after)])
66
109
  for (const key of keys) {
110
+ const beforeHasKey = hasJsonObjectKey(before, key)
111
+ const afterHasKey = hasJsonObjectKey(after, key)
112
+ if (!beforeHasKey && !afterHasKey) continue
113
+
67
114
  const nextPath = [...currentPath, key]
68
- if (!(key in after)) ops.push({ op: 'unset', path: nextPath })
69
- else if (!(key in before)) ops.push({ op: 'set', path: nextPath, value: after[key] })
70
- else diffInto(before[key], after[key], nextPath, ops)
115
+ if (!afterHasKey) ops.push({ op: 'unset', path: nextPath })
116
+ else if (!beforeHasKey) {
117
+ ops.push({ op: 'set', path: nextPath, value: toJsonCompatibleValue(after[key]) })
118
+ } else diffInto(before[key], after[key], nextPath, ops)
71
119
  }
72
120
  return
73
121
  }
74
122
 
75
123
  if (Array.isArray(before) && Array.isArray(after) && before.length === after.length) {
76
124
  for (let index = 0; index < after.length; index += 1) {
77
- diffInto(before[index], after[index], [...currentPath, index], ops)
125
+ diffInto(
126
+ getJsonArrayItem(before, index),
127
+ getJsonArrayItem(after, index),
128
+ [...currentPath, index],
129
+ ops,
130
+ )
78
131
  }
79
132
  return
80
133
  }
81
134
 
82
- ops.push({ op: 'set', path: currentPath, value: after })
135
+ ops.push({ op: 'set', path: currentPath, value: toJsonCompatibleValue(after) })
83
136
  }
84
137
 
85
138
  // Diff two compiled configs. `before == null` means there was no prior build to compare
package/compile/index.ts CHANGED
@@ -714,9 +714,9 @@ const compileAutomation = (automationMap: AutomationMap) =>
714
714
  const recordConfigChange = async (previousConfig: unknown, config: unknown) => {
715
715
  if (previousConfig == null) return
716
716
  if (!isTruthyEnv(process.env.BRICKS_CTOR_ENABLE_EDITING_TOOLS)) return
717
- // The baseline was parsed from JSON; round-trip the fresh config the same way so keys
718
- // holding undefined (dropped by the artifact's JSON.stringify) don't diff as phantom sets.
719
- const change = computeConfigChange(previousConfig, JSON.parse(JSON.stringify(config)))
717
+ // The baseline was parsed from JSON; `computeConfigChange` applies the same
718
+ // JSON-omitted-field rules lazily so compile avoids cloning the full config.
719
+ const change = computeConfigChange(previousConfig, config)
720
720
  if (change.status !== 'ok') return
721
721
  await appendEditRecord(process.cwd(), {
722
722
  ts: new Date().toISOString(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fugood/bricks-project",
3
- "version": "2.25.0-beta.45",
3
+ "version": "2.25.0-beta.46",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "typecheck": "tsc --noEmit",
@@ -11,7 +11,7 @@
11
11
  "@babel/parser": "7.28.5",
12
12
  "@babel/traverse": "7.28.5",
13
13
  "@babel/types": "7.28.5",
14
- "@fugood/bricks-cli": "^2.25.0-beta.45",
14
+ "@fugood/bricks-cli": "^2.25.0-beta.46",
15
15
  "@huggingface/gguf": "^0.3.2",
16
16
  "@iarna/toml": "^3.0.0",
17
17
  "@modelcontextprotocol/sdk": "^1.15.0",
package/package.json.bak CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fugood/bricks-ctor",
3
- "version": "2.25.0-beta.45",
3
+ "version": "2.25.0-beta.46",
4
4
  "main": "index.ts",
5
5
  "scripts": {
6
6
  "typecheck": "tsc --noEmit",
@@ -11,7 +11,7 @@
11
11
  "@babel/parser": "7.28.5",
12
12
  "@babel/traverse": "7.28.5",
13
13
  "@babel/types": "7.28.5",
14
- "@fugood/bricks-cli": "^2.25.0-beta.45",
14
+ "@fugood/bricks-cli": "^2.25.0-beta.46",
15
15
  "@huggingface/gguf": "^0.3.2",
16
16
  "@iarna/toml": "^3.0.0",
17
17
  "@modelcontextprotocol/sdk": "^1.15.0",
@@ -46,7 +46,7 @@ These work, but with browser caveats:
46
46
  | WebView / WebCrawler | Subject to browser CORS — a load/fetch that works on device may be blocked |
47
47
  | On-device AI (LLM / STT / VAD / Vector Store / Reranker) | Runs **single-threaded** — far slower than the device, not representative of real latency. Also subject to the model fallbacks below |
48
48
  | On-device database (SQLite — `GENERATOR_SQLITE`) | Runs for real on the in-memory WASM `sqlite-vec` build — `execute` / `query` / `transaction` / batch all work. `storageType: file` is transparently treated as in-memory, so nothing persists across reloads (see above) |
49
- | Scene3D / Maps / Sketch / WebRTC | Supported |
49
+ | Scene3D / Sketch / WebRTC | Supported |
50
50
 
51
51
  Feature availability also varies across the device platforms themselves (iOS / tvOS / Android / the desktop OSes). When a deployment targets a specific platform's capability, confirm it on that platform.
52
52
 
@@ -89,6 +89,7 @@ So that camera and AI features are usable without device permissions, multi-giga
89
89
  | Brick / Generator | In the Simulator | Does NOT prove |
90
90
  |-------------------|------------------|----------------|
91
91
  | Camera (`BRICK_CAMERA`) | A 3D mock canvas, no camera permission prompt. `takePicture` snapshots the canvas; recording produces a placeholder clip | Real camera feed, focus, recording, permission flow |
92
+ | Maps (`BRICK_MAPS`) | A real interactive map on free OpenStreetMap-based tiles — no Google Maps API key needed. Markers, path polyline, the six themes / map types (approximated with free tile sets + CSS tints), and the zoom / pan / navigate / focus / reset / fit actions all work | Google / Apple Maps rendering, exact `customMapStyle` / theme styling (approximated), traffic / buildings / indoors layers, real device geolocation |
92
93
  | Thermal Printer (`GENERATOR_THERMAL_PRINTER`) | A simulated printer — `init` / `checkStatus` / `scan` fake per-driver status and discovered devices (ESC/POS, Star, TSC, Castles); `print` renders an approximate on-screen receipt. A bottom-left bubble shows live status with a fault toggle to exercise error wiring. Print results can be exported as PNG via `bricks-cli` (see below) | Real device connection, actual paper output, exact native driver status codes |
93
94
  | LLM (`GENERATOR_LLM`) | Swapped to a tiny local stand-in model | Output quality / latency of your real model |
94
95
  | Reranker — GGML (`GENERATOR_RERANKER`) | Swapped to a small local multilingual reranker model | Ranking quality / latency of your real model |
@@ -119,7 +120,7 @@ The PNG is the same approximate receipt the on-screen preview shows (rendered fr
119
120
 
120
121
  ### Running the real implementation instead
121
122
 
122
- Each substituted brick/generator can be switched back to its real implementation per item: open the **gear (Simulator settings)** in the editor's preview toolbar, uncheck the item, and **Apply**. Apply persists the choice and reloads the preview so it takes effect (a plain refresh won't). Use this to, e.g., point a Vector Store at a real API key in the preview. The browser limits above still apply, and **Buttress stays disabled regardless** — there's no backend for it here.
123
+ Each substituted brick/generator can be switched back to its real implementation per item: open the **gear (Simulator settings)** in the editor's preview toolbar, uncheck the item, and **Apply**. Apply persists the choice and reloads the preview so it takes effect (a plain refresh won't). Use this to, e.g., point a Vector Store at a real API key in the preview, or render the real Google/Apple Maps brick (which needs a Maps API key on web). The browser limits above still apply, and **Buttress stays disabled regardless** — there's no backend for it here.
123
124
 
124
125
  The Thermal Printer is the exception: it has no real web implementation to switch to (the native drivers can't run in a browser), so it is **always simulated** and is not in the gear list.
125
126