You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit ffc498d
Browse filesBrowse the repository at this point in the historyBrowse files
Copy file name to clipboardExpand all lines: .agents/docs/README.md
+2-1Lines changed: 2 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -15,4 +15,5 @@
15
15
-[@taffyjs/wasm package design](taffyjs-wasm-package.md) — public contract, artifact layout, initialization model, ownership boundary, and verification requirements for the explicit Wasm package.
16
16
-[@taffyjs/yoga decisions](taffyjs-yoga-decisions.md) — compatibility decisions for replacing yoga-layout through the TaffyJS Yoga layer.
17
17
-[@taffyjs/yoga implementation reference](taffyjs-yoga-reference.md) — the audited Yoga 3.2.1 public API inventory, implemented compatibility boundary, evidence, and ongoing maintenance constraints.
18
-
-[Tooling decisions](tooling-decisions.md) — rulings for npm scope and test workspace package naming, the repository-wide TypeScript default, JavaScript package builds, repository command orchestration, tool ownership, and demand-driven API code generation.
18
+
-[@taffyjs/yoga-wasm package design](taffyjs-yoga-wasm-package.md) — the shared-source Wasm transport, package boundary, loading behavior, and verification contract for the Yoga facade.
19
+
-[Tooling decisions](tooling-decisions.md) — rulings for third-party license filenames, npm scope and test workspace package naming, the repository-wide TypeScript default, JavaScript package builds, repository command orchestration, tool ownership, and demand-driven API code generation.
Copy file name to clipboardExpand all lines: .agents/docs/architecture.md
+7-1Lines changed: 7 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
# Architecture
2
2
3
-
This repository is a small Rust and JavaScript monorepo with one shared napi-rs binding and three public packages.
3
+
This repository is a small Rust and JavaScript monorepo with one shared napi-rs binding and four public packages.
4
4
5
5
## Ownership
6
6
@@ -9,20 +9,26 @@ This repository is a small Rust and JavaScript monorepo with one shared napi-rs
9
9
-`packages/taffyjs-node` owns the public ESM wrapper and declarations, the private napi-rs loader and declarations, and npm metadata. The wrapper owns JavaScript-only NodeId validity data and context; Taffy owns topology, Style, Layout, cache, and computation state.
10
10
-`packages/taffyjs-wasm` compiles the same authored source from `packages/taffyjs-node/src` against generated private Node and browser adapters that share one inline payload. It owns package metadata, generated artifacts, and build-time binding redirection; its package-specific typed generator lives under `tools/taffy-wasm`. It has no authored public wrapper source or separate initialization API.
11
11
-`packages/taffyjs-yoga` owns the Node-only ESM Yoga 3.2.1 compatibility facade, Yoga declarations and visible state, input translation, and Yoga-shaped output projection. It depends only on the public `@taffyjs/node` package boundary and does not modify Taffy or the native binding.
12
+
-`packages/taffyjs-yoga-wasm` builds the exact facade source owned by `packages/taffyjs-yoga/src` against the public `@taffyjs/wasm` backend. It owns only its package metadata and build-time backend redirection; it has no copied facade source or second compatibility policy.
12
13
-`tests/taffyjs-node` is a private consumer package that tests `@taffyjs/node` through its package boundary.
13
14
-`tests/taffyjs-wasm` is a private consumer package that checks the Wasm package through Node and browser package resolution, reuses the existing public type tests, mechanically inspects the published file set, and installs the packed result in fresh npm and pnpm consumers.
14
15
-`tests/taffyjs-yoga` is the corresponding private consumer package for compatibility, differential, and end-to-end tests through the `@taffyjs/yoga` package boundary; it does not contain a redundant nested `tests/` directory.
16
+
-`tests/taffyjs-yoga-wasm` reruns the same Yoga behavior and declaration suites against the Wasm transport, inspects the package boundary, and installs the packed packages in fresh npm and pnpm consumers for Node and Chromium checks.
15
17
16
18
The `@taffyjs/node` public entry bundles the private napi-rs root loader, which selects the matching optional platform package. Its generated loader and declaration remain repository build inputs rather than published package files. The `@taffyjs/wasm` default entry reaches a no-TLA ESM bridge that imports the shared payload and immediately invokes a synchronous CommonJS factory mechanically derived from napi-rs's eager Node loader; its browser entry reaches a TLA adapter and the unmodified deferred loader. Neither direct runtime package exposes an intermediate binding package, JavaScript shadow tree, separate core crate, public binding, or public initialization API.
17
19
18
20
`@taffyjs/yoga` is a Node-only TypeScript compatibility layer over `@taffyjs/node`; browser and WASM backends never live in that package. It performs bounded input normalization and output processing, but it does not modify or fork Taffy core, add a Yoga-specific native algorithm, or reimplement Flex layout in JavaScript; behavior that cannot cross that boundary is documented as Different or Unsupported as recorded in [@taffyjs/yoga decisions](taffyjs-yoga-decisions.md).
19
21
22
+
`@taffyjs/yoga-wasm` is a separate transport over `@taffyjs/wasm`, selected at build time by replacing only the exact bare `@taffyjs/node` dependency. The eager root entry and lazy `/load` entry keep Yoga's public shapes; `/load` dynamically imports the facade so a browser compiles no WebAssembly until `loadYoga()` is called. Compatibility semantics stay owned by the shared Yoga source and documentation.
23
+
20
24
## Testing
21
25
22
26
JavaScript integration tests are the primary proof of observable behavior. Package-local JavaScript and Rust unit tests are limited to critical behavior that is clearer in isolation. Maintained test names follow product behavior rather than temporary milestone or acceptance labels.
23
27
24
28
The Wasm package runs the complete Node public API behavior suite without copied test files: a Wasm-only Vite+ configuration replaces the exact `@taffyjs/node` import with `@taffyjs/wasm`. Subprocess fixtures select the same test entry through an inherited test-only file URL. The Wasm package additionally has a real Chromium runtime test through Vite+ and Playwright. A separate Vite consumer build proves that the `browser` export is selected, the payload occurs once in JavaScript, and no external `.wasm` asset is emitted; the browser runtime check exercises synchronous measurement and reuse after a controlled error without cross-origin isolation or `SharedArrayBuffer`.
25
29
30
+
The Yoga Wasm package likewise reuses the complete maintained Yoga suite and public declaration test. Its packed-consumer fixture proves unchanged `yoga-layout` root and `/load` imports in Node and bundled Chromium, including the lazy compilation boundary, rather than maintaining a second copy of compatibility tests.
31
+
26
32
A test earns its place only when it is the clearest durable check for a distinct observable behavior or safety property. Before adding one, identify the exact failure it would catch and explain why tests beside the code that owns the behavior would not already catch it. Distinct coverage is not enough: the protection must justify the code to maintain, time to run, ways it can fail for unrelated reasons, and platform-specific behavior it introduces. Prefer the smallest direct check. When consolidating duplicate files, move any genuinely unique assertion into the owning test before deleting the duplicate.
27
33
28
34
Review test changes in this order: whether the protection is worth its cost, whether the check sits beside the right code, then whether its implementation is correct. A passing test or a working harness proves only that it runs. Do not use runtime tests that read README, source, or test files as a substitute for testing product behavior or enforcing architecture. If an explicit repository rule needs automatic enforcement, such as which module may import a private loader, use a focused lint or CI check against the source files. Generated-artifact freshness follows [API code generation](api-codegen.md#verification), not generator unit tests.
Copy file name to clipboardExpand all lines: .agents/docs/binding-cases.md
+24-2Lines changed: 24 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -112,7 +112,7 @@ The `calc` feature is enabled by Taffy's default features, which this repository
112
112
113
113
### Selected JavaScript container semantics
114
114
115
-
The direct analogue of Rust's update syntax is a plain `StyleInput` object whose fields are optional. A missing field or a field whose value is `undefined` takes the corresponding `Style::DEFAULT` value. Node creation stores the resulting complete Style. `setStyle(node, input)` constructs another complete Style from defaults and the supplied fields and replaces the old Style; it does not merge omitted fields with the node's current Style. A separate merge or patch API would be additive convenience with different semantics.
115
+
The direct analogue of default-based Rust Style construction is a plain `StyleInput` object whose fields are optional. A missing field or a field whose value is `undefined` takes the corresponding `Style::DEFAULT` value. Node creation stores the resulting complete Style. `setStyle(node, input)` constructs another complete Style from defaults and the supplied fields and replaces the old Style; it does not merge omitted fields with the node's current Style. The additive `updateStyle(node, update)` operation instead preserves omitted values.
116
116
117
117
Fixed-shape geometry records used directly as Style fields are partial on input for the same default-based construction. Each missing or explicit-`undefined``Point`, `Size`, `Rect`, or `Line` component comes from that component of the enclosing field's `Style::DEFAULT` value. Thus `size: { width: value }` gets the default `auto` height, `padding: { left: value }` gets zero for the other three sides, and `inset: { left: value }` gets `auto` for the other three sides. This never reads the stored Style. Unknown enumerable string components are rejected because every component is optional and a misspelling would otherwise silently select the default. This rule does not make tagged semantic values, grid payloads, or arbitrary nested records recursively partial.
118
118
@@ -155,12 +155,34 @@ export interface Style {
155
155
156
156
The reusable geometry declarations referenced here are selected below.
157
157
158
-
The explicit `| undefined` members keep the declarations truthful for consumers that enable TypeScript's `exactOptionalPropertyTypes`: the runtime accepts both a missing property and an explicitly undefined property. Those forms use the field's default, while `null` explicitly requests `None` for a publicly nullable field. Conversion stores only the resulting Rust value: when a default is `Some(value)`, omission or `undefined` and `null` remain observably different; when the default is already `None`, they converge. Every property where `null` and `undefined` differ must explain both meanings in property-level JSDoc rather than relying only on an interface-level note. A complete `Style` keeps every field present and readonly, emits the concrete value for `Some(value)`, emits `null` for `None`, and never uses a missing or `undefined` field for nullable output.
158
+
The explicit `| undefined` members keep the declarations truthful for consumers that enable TypeScript's `exactOptionalPropertyTypes`: the runtime accepts both a missing property and an explicitly undefined property. Those forms use the field's default for construction and replacement and preserve the field for update, while `null` explicitly requests `None` for a publicly nullable field. Conversion stores only the resulting Rust value: when a default is `Some(value)`, omission or `undefined` and `null` remain observably different during replacement; when the default is already `None`, they converge. Every property where `null` and `undefined` differ must explain the operation-specific omission and explicit-null meanings in property-level JSDoc rather than relying only on an interface-level note. A complete `Style` keeps every field present and readonly, emits the concrete value for `Some(value)`, emits `null` for `None`, and never uses a missing or `undefined` field for nullable output.
159
159
160
160
`StyleInput` properties remain mutable in TypeScript. Conversion uses ordinary property access and does not inspect, reject, copy, freeze, or repeat validation solely because an input may contain accessors or be a Proxy; the caller owns those behaviors and side effects. Every binding-produced `Style` record uses readonly TypeScript properties because it is a detached snapshot whose mutation cannot change native state. The direct runtime representation is a complete eagerly materialized ordinary plain object without runtime freezing, sealing, or a Proxy. No lazy or selective output facility belongs to the initial API.
161
161
162
162
The first checkpoint is demonstrated by ordinary integration cases: `{}` and explicit `undefined` produce Taffy defaults; `setStyle(node, {})` resets a previously nondefault Style instead of merging; a partial fixed-shape nested record fills each omitted component from the corresponding enclosing Style default rather than stored state; an unknown top-level or partial-record component and `null` for a required field throw without changing the stored Style; `null` succeeds only for a publicly nullable field; a complete output uses `null` rather than an omitted or undefined field for Rust `None`; and declarations accept explicit `undefined` under `exactOptionalPropertyTypes` and retain the required property-level JSDoc. Getter- or Proxy-driven tree mutation is deliberately not a baseline fixture.
163
163
164
+
### Additive partial updates
165
+
166
+
`StyleUpdate` reuses the structural field types of `StyleInput`, including readonly collection inputs, without applying a recursive `Partial`. `updateStyle` interprets a missing or explicit-`undefined` outer field as “preserve,” and interprets missing components of the four public partial geometry families the same way. A supplied shorthand geometry value still supplies every component. Arrays, tagged-union branches, and other complete input records remain complete whole replacements; `[]` clears a collection and accepted `null` maps to `None`.
167
+
168
+
Rust converts the JavaScript object and all supplied collections into an owned patch before tree access. It compares only supplied fields and components with the stored Style, including bitwise comparison for direct floating-point fields so repeated `NaN` and distinct signed zero behave predictably. Empty and unchanged patches do not call Taffy's dirtying setter. For a real change, the complete candidate is validated before one `set_style` call, so a failure preserves both the old Style and dirty state.
169
+
170
+
Taffy 0.13 exposes the stored value through `style(&self) -> &Style` and replaces it through `set_style(Style)`, with no public mutable or take operation. `std::mem::take` would move a `Vec` allocation rather than clone it if the binding had `&mut Style`, but that reference cannot be obtained safely from the high-level tree. The implemented fallback returns an empty patch before cloning; every nonempty patch clones the current Style once, then compares and applies supplied fields in one traversal. An unchanged candidate is discarded without dirtying, while a changed candidate is validated and written once. Replacing a collection can therefore clone the old collection before overwriting it, but there is no workload benchmark justifying the larger selective-reconstruction implementation in the initial API. This preserves Taffy's sole ownership and avoids a JavaScript shadow Style; a future upstream closure-style update operation could safely expose in-place mutation and automatic dirty propagation.
171
+
172
+
A focused end-to-end native-binding microbenchmark on implementation commit `6ed48b4` retained the JavaScript-to-Rust boundary, input conversion, mutation, and alternating real value changes. It ran on Node.js 24.19.0 on Linux with an Intel Core i5-13500H, pinned to one core, after warmup, with 13 samples in each of two runs. Representative per-operation timings were:
173
+
174
+
| Preserved state and changed value |`setStyle` reconstruction |`updateStyle`| Observed relation |
These measurements support recommending `updateStyle` for incremental changes: it avoids reconverting retained JavaScript fields, and the advantage grows when retained input is larger. They do not establish per-call dominance. When the changed field itself is the large collection, both operations must parse its replacement and can converge; with string-heavy replacements, the current full-Style clone fallback can make `updateStyle` slower. This focused boundary microbenchmark also does not justify adding selective reconstruction without representative workload evidence. Public documentation should therefore say that `updateStyle` is generally faster for incremental changes, not that every call must be faster.
185
+
164
186
The pointer-backed calc variant is a stop rather than a routine field mapping. The public high-level Style vocabulary excludes calc because `TaffyTree` resolves every calc pointer to zero and exposes no resolver. Whether the Cargo feature is also disabled is an implementation choice to verify separately; keeping an internal feature enabled does not make calc a supported JavaScript value.
0 commit comments