circuitjson-toolkit 1.1.0 → 1.1.2

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/README.md CHANGED
@@ -37,6 +37,18 @@ See the [migration guide](docs/migration.md) and its generated
37
37
  [1.1.0 release notes](docs/release-notes-v1.1.0.md) provide the concise change
38
38
  summary.
39
39
 
40
+ Version 1.1.1 makes queued worker-request ownership synchronous. Parser and
41
+ project requests waiting behind active work can no longer observe later
42
+ caller-buffer mutation; explicit transfers detach exact buffers immediately
43
+ after admission. See the
44
+ [1.1.1 release notes](docs/release-notes-v1.1.1.md).
45
+
46
+ Version 1.1.2 makes schematic SVG paint deterministic. Open primitives always
47
+ emit `fill="none"`, filled primitives retain authored paint or use the shared
48
+ theme fallback, and generic component and symbol bodies explicitly use the
49
+ ECAD Forge schematic palette. See the
50
+ [1.1.2 release notes](docs/release-notes-v1.1.2.md).
51
+
40
52
  Before 1.1.0:
41
53
 
42
54
  ```js
@@ -66,6 +78,8 @@ const model = document.model
66
78
  - Copy-on-write normalization of supported legacy CircuitJSON aliases through
67
79
  `CircuitJsonDocument.normalizeModel()`
68
80
  - Deterministic PCB, schematic, and BOM renderers
81
+ - Explicit schematic SVG stroke/fill paint that does not depend on browser
82
+ defaults
69
83
  - Asset-backed `schematic_image` rows and hierarchical
70
84
  `schematic_sheet_symbol` rows with shared bounds and SVG behavior
71
85
  - Reusable exact PCB interaction and spatial indexes
@@ -294,6 +308,8 @@ copy while keeping sync, direct async, and worker results mutation-isolated.
294
308
  - [Model and envelope format](docs/model-format.md)
295
309
  - [Testing and downstream conformance](docs/testing.md)
296
310
  - [1.1.0 release notes](docs/release-notes-v1.1.0.md)
311
+ - [1.1.1 release notes](docs/release-notes-v1.1.1.md)
312
+ - [1.1.2 release notes](docs/release-notes-v1.1.2.md)
297
313
  - [Library scope](spec/library-scope.md)
298
314
 
299
315
  ## Package scope
package/docs/api.md CHANGED
@@ -80,6 +80,12 @@ Asynchronous parser and project calls accept:
80
80
  unavailable. Protocol, post-message, parser, and runtime errors stay visible.
81
81
  Worker and direct results have the same serialized shape.
82
82
 
83
+ Requests waiting behind active worker work take ownership immediately after
84
+ admission. The default mode clones the exact binary graph into a private queue
85
+ snapshot and leaves caller buffers intact. `transferInput: true` detaches exact
86
+ transferable buffers at admission; partial views, resizable buffers, and
87
+ shared buffers are isolated without detaching unrelated caller bytes.
88
+
83
89
  `retainSource` is exactly `'none' | 'reference'` and defaults to `'none'`.
84
90
  `'reference'` is an in-process parser option: the returned document has a
85
91
  non-enumerable `sourceReference` property whose value is the exact caller input
@@ -324,7 +330,12 @@ the same option names across toolkits. Canonical `schematic_image` rows resolve
324
330
  their exact `asset_id` from `document.assets`; full asset mode renders the
325
331
  payload, while metadata-only or unresolved assets are omitted without a
326
332
  placeholder. `schematic_sheet_symbol` renders a hierarchical child box and is
327
- never treated as a selectable `schematic_sheet` page.
333
+ never treated as a selectable `schematic_sheet` page. Since 1.1.2, every
334
+ primitive emits explicit SVG fill paint: open arcs, polylines, and shapes use
335
+ `fill="none"`; filled primitives keep valid authored paint or fall back to
336
+ `var(--schematic-fill-color, #f1d8bd)`. Generic component and symbol bodies use
337
+ the same fill variable plus
338
+ `var(--schematic-default-ink-color, #008aa3)` for their stroke.
328
339
 
329
340
  ### `BomTableRenderer.render(document, options?)`
330
341
 
package/docs/migration.md CHANGED
@@ -92,6 +92,19 @@ returns either `{ ok: true, value }` or
92
92
  uses ordered `detect`, `decode`, `project`, `validate`, and `complete`
93
93
  stages. Cancellation is request-scoped.
94
94
 
95
+ Since 1.1.1, a request waiting behind active worker work is owned immediately
96
+ after admission. With the default `transferInput: false`, later caller
97
+ mutations cannot alter a queued parser input, project entry, or asset. With
98
+ `transferInput: true`, exact transferable buffers detach at admission;
99
+ partial, resizable, and shared ranges continue to use isolated copies.
100
+
101
+ Since 1.1.2, schematic SVG renderers no longer inherit the browser's default
102
+ black fill. Open primitives always render with `fill="none"`. A primitive with
103
+ `is_filled: true` uses its valid authored `fill_color`/`fillColor`, or the
104
+ shared `--schematic-fill-color` theme variable when no paint is authored.
105
+ Generic component and symbol bodies explicitly use the shared schematic fill
106
+ and ink variables. Public names, parameters, and return shapes are unchanged.
107
+
95
108
  ## Package subpaths
96
109
 
97
110
  - `circuitjson-toolkit/parser`
@@ -0,0 +1,24 @@
1
+ # circuitjson-toolkit 1.1.1
2
+
3
+ ## Synchronous queued-request ownership
4
+
5
+ This patch makes the common worker client own every accepted parser and
6
+ project request when it enters the queue behind active work.
7
+
8
+ - Default `transferInput: false` requests clone their exact binary graph into
9
+ a private queue snapshot immediately. Later caller mutation cannot change a
10
+ queued parser input, project entry, or attached asset.
11
+ - `transferInput: true` detaches exact transferable caller buffers immediately
12
+ after queue admission. Partial views, resizable buffers, and shared buffers
13
+ keep their isolated-copy behavior without detaching unrelated caller bytes.
14
+ - Shared backing-buffer aliases remain shared in the owned request, and queued
15
+ snapshots transfer to the worker without another binary copy when posted.
16
+ - Disposed, pre-cancelled, over-limit, and initial worker-construction failures
17
+ reject before ownership. Automatic direct fallback therefore retains valid
18
+ binary input.
19
+ - Parser and project queues use the same bounded, accessor-safe traversal and
20
+ retain existing cancellation, error, and response behavior.
21
+
22
+ Public names, parameters, package subpaths, document envelopes, and project
23
+ envelopes are unchanged from 1.1.0. Gerber, Altium, KiCad, viewers, and ECAD
24
+ Forge receive the corrected behavior through the shared `ParserWorkerClient`.
@@ -0,0 +1,20 @@
1
+ # circuitjson-toolkit 1.1.2
2
+
3
+ ## Deterministic schematic paint
4
+
5
+ This patch removes browser-dependent SVG fill behavior from the canonical
6
+ schematic renderer.
7
+
8
+ - Open arcs, polylines, and unfilled shapes now emit `fill="none"` explicitly,
9
+ so they cannot become black-filled in a browser or host stylesheet.
10
+ - Filled schematic primitives retain a valid authored `fill_color` or
11
+ `fillColor`. When `is_filled: true` has no authored paint, rendering uses
12
+ `var(--schematic-fill-color, #f1d8bd)`.
13
+ - Generic `schematic_component` and `schematic_symbol` bodies now emit the
14
+ shared schematic fill and default-ink theme variables explicitly.
15
+ - Filled paths render as polygons; open paths remain polylines. This preserves
16
+ geometry while making fill intent unambiguous.
17
+ - Unsafe authored paints remain rejected by the existing SVG paint sanitizer.
18
+
19
+ Public exports, method names, parameters, CircuitJSON document envelopes, and
20
+ renderer return shapes are unchanged from 1.1.1.
package/docs/testing.md CHANGED
@@ -80,6 +80,11 @@ Worker checks install an in-memory `ecad-toolkit.worker.v1` loopback with real
80
80
  structured-clone and transfer boundaries, then require `worker: true`. A direct
81
81
  fallback cannot satisfy these checks.
82
82
 
83
+ Queue regressions hold one worker operation active while parser and project
84
+ requests wait behind it. They mutate default-mode caller buffers and require
85
+ the original request snapshot, then verify explicit-transfer buffers detach
86
+ only after admission and preserve shared backing-buffer aliases.
87
+
83
88
  Extension ownership tests use a realistic native graph above the former
84
89
  compact-metadata item ceiling. They require one bounded immutable capture,
85
90
  mutation isolation, a direct capture under two seconds, exact worker-result
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circuitjson-toolkit",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Canonical CircuitJSON parsing, project, rendering, query, manufacturing, simulation, and scene contracts",
5
5
  "keywords": [
6
6
  "circuitjson",
@@ -51,6 +51,8 @@
51
51
  "docs/model-format.md",
52
52
  "docs/provenance.md",
53
53
  "docs/release-notes-v1.1.0.md",
54
+ "docs/release-notes-v1.1.1.md",
55
+ "docs/release-notes-v1.1.2.md",
54
56
  "docs/testing.md",
55
57
  "spec",
56
58
  "LICENSE",
@@ -232,6 +232,8 @@ export class ParserWorkerClient {
232
232
  if (this.#pending.size >= MAX_PENDING_REQUESTS) {
233
233
  throw ParserWorkerClient.#queueError()
234
234
  }
235
+ const wasQueued = Boolean(this.#activeRequestId)
236
+ if (wasQueued) prepared = WorkerRequestData.ownForQueue(prepared)
235
237
  const requestId = `worker-${this.#nextRequestId}`
236
238
  this.#nextRequestId += 1
237
239
 
@@ -245,7 +247,7 @@ export class ParserWorkerClient {
245
247
  onAbort: null,
246
248
  onProgress: prepared.onProgress,
247
249
  previousProgress: null,
248
- attemptToken
250
+ attemptToken: wasQueued ? null : attemptToken
249
251
  }
250
252
  if (prepared.signal) {
251
253
  pending.onAbort = () => this.cancel(requestId)
@@ -50,6 +50,7 @@ const DATA_VIEW_BYTE_OFFSET_GETTER = Object.getOwnPropertyDescriptor(
50
50
  const DATA_VIEW_CONSTRUCTOR = DataView
51
51
  const UINT8_ARRAY_CONSTRUCTOR = Uint8Array
52
52
  const UINT8_ARRAY_SET = Uint8Array.prototype.set
53
+ const STRUCTURED_CLONE = globalThis.structuredClone
53
54
  const MAX_REQUEST_BYTES = 100_000_000
54
55
  const MAX_REQUEST_DEPTH = 64
55
56
  const MAX_REQUEST_VALUES = 100_000
@@ -95,6 +96,32 @@ export class WorkerRequestData {
95
96
  })
96
97
  }
97
98
 
99
+ /**
100
+ * Owns one already-prepared queued request and rebuilds its post transfer list.
101
+ * @param {{ payload: unknown, transfer: Transferable[] }} prepared Prepared request.
102
+ * @returns {Record<string, any>} Owned queued request.
103
+ */
104
+ static ownForQueue(prepared) {
105
+ const owned = Reflect.apply(STRUCTURED_CLONE, undefined, [
106
+ prepared.payload,
107
+ { transfer: prepared.transfer }
108
+ ])
109
+ const posted = WorkerRequestData.#prepare(owned, {
110
+ bytes: MAX_REQUEST_BYTES,
111
+ copyBinary: false,
112
+ output: false,
113
+ strictDescriptors: false,
114
+ transferInput: true,
115
+ trustProof: false,
116
+ values: MAX_REQUEST_VALUES
117
+ })
118
+ return {
119
+ ...prepared,
120
+ payload: posted.value,
121
+ transfer: posted.transfer
122
+ }
123
+ }
124
+
98
125
  /**
99
126
  * Prepares a bounded worker-owned result and transfers all safe buffers.
100
127
  * @param {unknown} value Worker result.
@@ -1,5 +1,7 @@
1
1
  import { SafeXmlText } from './SafeXmlText.mjs'
2
2
 
3
+ const DEFAULT_FILLED_PAINT = 'var(--schematic-fill-color, #f1d8bd)'
4
+
3
5
  /**
4
6
  * Builds SVG drawing attributes for schematic primitive rows.
5
7
  */
@@ -46,11 +48,16 @@ export class CircuitJsonSchematicSvgPrimitiveAttributes {
46
48
  '"'
47
49
  )
48
50
  }
49
- if (options.fill !== false && safeFill) {
51
+ const resolvedFill = CircuitJsonSchematicSvgPrimitiveAttributes.#fill(
52
+ element,
53
+ options,
54
+ safeFill
55
+ )
56
+ if (resolvedFill) {
50
57
  attributes.push(
51
58
  'fill="' +
52
59
  CircuitJsonSchematicSvgPrimitiveAttributes.#escapeHtml(
53
- safeFill
60
+ resolvedFill
54
61
  ) +
55
62
  '"'
56
63
  )
@@ -71,6 +78,21 @@ export class CircuitJsonSchematicSvgPrimitiveAttributes {
71
78
  return attributes.length ? ' ' + attributes.join(' ') : ''
72
79
  }
73
80
 
81
+ /**
82
+ * Resolves deterministic fill semantics for one SVG primitive.
83
+ * @param {object} element CircuitJSON element row.
84
+ * @param {{ fill?: boolean }} options Attribute options.
85
+ * @param {string} safeFill Valid authored fill paint.
86
+ * @returns {string} Explicit SVG fill paint.
87
+ */
88
+ static #fill(element, options, safeFill) {
89
+ if (options.fill === false || element?.is_filled === false) {
90
+ return 'none'
91
+ }
92
+ if (safeFill) return safeFill
93
+ return element?.is_filled === true ? DEFAULT_FILLED_PAINT : 'none'
94
+ }
95
+
74
96
  /**
75
97
  * Resolves a finite number.
76
98
  * @param {unknown} value Number candidate.
@@ -151,7 +151,7 @@ export class CircuitJsonSchematicSvgRenderer {
151
151
  component.schematic_component_id || ''
152
152
  ) +
153
153
  '">' +
154
- '<rect class="schematic-component__body" x="' +
154
+ '<rect class="schematic-component__body" fill="var(--schematic-fill-color, #f1d8bd)" stroke="var(--schematic-default-ink-color, #008aa3)" x="' +
155
155
  CircuitJsonSchematicSvgRenderer.#formatNumber(x) +
156
156
  '" y="' +
157
157
  CircuitJsonSchematicSvgRenderer.#formatNumber(y) +
@@ -218,7 +218,7 @@ export class CircuitJsonSchematicSvgRenderer {
218
218
  CircuitJsonSchematicSvgRenderer.#escapeHtml(
219
219
  symbol.schematic_symbol_id || ''
220
220
  ) +
221
- '"><rect class="schematic-symbol__body" x="' +
221
+ '"><rect class="schematic-symbol__body" fill="var(--schematic-fill-color, #f1d8bd)" stroke="var(--schematic-default-ink-color, #008aa3)" x="' +
222
222
  CircuitJsonSchematicSvgRenderer.#formatNumber(x) +
223
223
  '" y="' +
224
224
  CircuitJsonSchematicSvgRenderer.#formatNumber(y) +
@@ -517,7 +517,9 @@ export class CircuitJsonSchematicSvgRenderer {
517
517
  CircuitJsonSchematicSvgRenderer.#pointsAttribute(points)
518
518
  ) +
519
519
  '"' +
520
- CircuitJsonSchematicSvgPrimitiveAttributes.attributes(element) +
520
+ CircuitJsonSchematicSvgPrimitiveAttributes.attributes(element, {
521
+ fill: tag === 'polygon'
522
+ }) +
521
523
  '></' +
522
524
  tag +
523
525
  '>'