Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,20 @@ jobs:
cache: 'npm'
- run: npm ci
- run: npm run verify
# dist/ is committed and is what a consumer runs (see .gitignore), so it must be
# the build of src/. verify's own build overwrites dist/ here and compares it
# with nothing, so this step does the comparison. Keep it after verify: a source
# that does not compile, or a failing test, is then reported by verify under its
# own name, and a red here means only "dist/ was not rebuilt and committed". It
# deletes dist/ first because tsc never deletes an output: built over the
# committed copy, a file whose source is gone would stay and pass. The status
# line prints every file that differs; it prints nothing when dist/ matches.
# To fix a red: rm -rf dist && npm run build, then commit dist/.
# test/ciDistGate.test.ts runs this script and keeps it in step with release.yml.
- name: dist/ is the build of src/
run: |
rm -rf dist
npm run build
git status --porcelain --untracked-files=all -- dist
git diff --exit-code --stat -- dist
test -z "$(git status --porcelain --untracked-files=all -- dist)"
12 changes: 12 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,18 @@ jobs:
- name: Verify
run: npm run verify

# A consumer pins the tag and runs its committed dist/, so the tag's dist/ must
# be the build of its src/. The same check as ci.yml's step of the same name (see
# the comment there). It runs before the Release is created, so a tag whose dist/
# is stale never becomes a Release.
- name: dist/ is the build of src/
run: |
rm -rf dist
npm run build
git status --porcelain --untracked-files=all -- dist
git diff --exit-code --stat -- dist
test -z "$(git status --porcelain --untracked-files=all -- dist)"

- name: Create GitHub Release
# A third-party action that holds the write token, pinned to the commit
# its v3.0.3 tag points at, so a moved tag cannot change what runs here.
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ coverage/
# as a git dependency, and enterprise npm setups often set `ignore-scripts=true`,
# which skips the `prepare` build on install. Shipping a prebuilt dist/ makes the
# git dependency resolve unconditionally. Rebuild with `npm run build` before
# committing source changes (CI `verify` enforces it).
# committing source changes. CI's "dist/ is the build of src/" step enforces it: it
# rebuilds dist/ from clean after verify and fails if the result differs.

# Internal working notes — never publish to the public tree.
docs/
Expand Down
25 changes: 23 additions & 2 deletions dist/datamodel/node/DataNode.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,9 @@ export default class DataNode extends BaseNode {
* to its envelope for exactly that reason: an empty string MATLAB had never written,
* standing in for an empty 0x0 double it could not see.
*
* So under an envelope an EMPTY override is dropped: it is a default rather than an
* edit, and the envelope is already the authority on that property.
* So under an envelope an EMPTY override is dropped when the stored bag holds no sibling
* for that key, or an empty one: it is a default rather than an edit, and the envelope is
* already the authority on that property.
*
* A non-empty override goes to BOTH places, and the reason it is both is defect 46.
* The sibling used to be the only place it went, on the reasoning that silently
Expand All @@ -194,6 +195,26 @@ export default class DataNode extends BaseNode {
* package's own reader can see — decoding the envelope back into node properties is
* the other half of defect 40 and is not done here. Writing both keeps every consumer
* correct and is strictly additive over the old behaviour.
*
* That sibling is also what tells a clear from a default (C-199). MATLAB never writes a
* property beside its envelope, so a sibling that IS there was written by this package,
* and the node read its value from it. An empty override is then the user clearing that
* value, and dropping it kept the old value in both copies: MATLAB (R2027a) reopened the
* cleared variable with it, and so did this reader. So a clear is written the way MATLAB
* writes a cleared property. The sibling is deleted, because MATLAB writes none and this
* reader then substitutes the same empty default the node now holds. The envelope's field
* becomes `[]`, the 0x0 double MATLAB itself writes for a default VariantVariable's
* Specification and for one set to '' (measured 2026-10-16), whichever empty the override
* was. A field the envelope does not declare leaves the envelope untouched, as an edit does.
* Own keys only, so a stored bag can never claim a sibling through its prototype.
*
* Only a NON-EMPTY sibling makes an empty override a clear. When the sibling is itself
* empty ('', null or undefined), as in `<P Name="Specification" Class="char"/>`, the node
* read '' from it, so an empty override repeats what it read: no change, and the file is
* written as it was read. Read as a clear, an edit to another property alone overwrote a
* real envelope value: Specification 'B' beside an empty sibling was saved as the 0x0
* double. No writer leaves that shape today, since MATLAB writes no sibling and this
* package (from v1.0.0) writes no empty one beside an envelope.
*/
_mergeProps(propOverrides: Record<string, unknown>): Record<string, unknown>;
serializeValue(): unknown;
Expand Down
2 changes: 1 addition & 1 deletion dist/datamodel/node/DataNode.d.ts.map

Large diffs are not rendered by default.

32 changes: 29 additions & 3 deletions dist/datamodel/node/DataNode.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/datamodel/node/DataNode.js.map

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions dist/datamodel/node/SimulinkObjectNode.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ import DataNode from './DataNode.js';
* saveobj envelope — it drops an EMPTY override rather than writing back a default MATLAB
* never wrote, and writes a non-empty one INTO the envelope as well as beside it, because the
* envelope is what MATLAB's loadobj actually reads (defects 40 and 46; `_mergeProps` carries
* the full account). That used to be the TEXT path only, with `VariantVariableNode` overriding
* `_getSerializedProperties` to opt its binary path in, and the asymmetry was recorded here as
* a separate question. Reading the emitted binary bytes answered it: `Simulink.VariantBank`,
* the full account). An empty override that meets a sibling this package wrote earlier is a
* clear, and is written as MATLAB writes one (C-199). That used to be the TEXT path only,
* with `VariantVariableNode` overriding `_getSerializedProperties` to opt its binary path in,
* and the asymmetry was recorded here as a separate question. Reading the emitted binary
* bytes answered it: `Simulink.VariantBank`,
* `Simulink.VariantBankCoderInfo` and `Simulink.VariantConfigurations` each came out carrying
* `<P Name="Value" Class="char"/>` NEXT TO their envelope — a property none of those MATLAB
* classes has, standing in for a default this node cannot see inside the envelope, written
Expand Down
2 changes: 1 addition & 1 deletion dist/datamodel/node/SimulinkObjectNode.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions dist/datamodel/node/SimulinkObjectNode.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/datamodel/node/SimulinkObjectNode.js.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading