Skip to content

prepareExport() mutates live blockList state for nop*Block types during serialization, causing inconsistent export behavior across repeated saves #7394

@sahu-virendra-1908

Description

@sahu-virendra-1908

Description

While reviewing the export pipeline in Music Blocks, I noticed that prepareExport() directly mutates live block objects while serializing nop*Block types.

Location:

js/activity.js

Current implementation:

case "nopValueBlock":
case "nopZeroArgBlock":
case "nopOneArgBlock":
case "nopTwoArgBlock":
case "nopThreeArgBlock":
    myBlock.name = myBlock.privateData;
    break;

prepareExport() is expected to behave as a read-only serialization step, but this code overwrites the live runtime value of:

myBlock.name

inside the active blockList.

Because the mutation is applied directly to the canvas/runtime block object and is never restored afterward, later export or runtime operations may observe a different block name than the original nop*Block value.

This becomes especially problematic because the serialization logic itself depends on matching:

nopValueBlock
nopZeroArgBlock
nopOneArgBlock
...

during future export calls.

After the first mutation, subsequent exports may no longer enter these switch branches because the block name has already been replaced with privateData.

Expected Behavior

prepareExport() should serialize block data without mutating live runtime block objects.

The active canvas state should remain unchanged after export, auto-save, or save operations.

Current Behavior

During export, nop*Block types overwrite their live .name value with:

myBlock.privateData

This mutation persists after serialization completes.

Subsequent export operations may then:

  • serialize different block names
  • skip nop*Block handling logic
  • produce inconsistent save/load behavior for plugin fallback blocks

Why This Matters

prepareExport() is triggered from several common workflows, including:

  • auto-save
  • manual save
  • save-as
  • play/save flows

Mutating runtime block state during serialization can therefore affect later editor behavior and repeated exports within the same session.

Root Cause

The serializer directly mutates:

myBlock.name

instead of using a temporary serialization-only value.

This causes runtime state leakage from the export pipeline back into the active canvas model.

Suggested Fix

Avoid mutating myBlock.name during serialization.

Instead, use a local serialization variable:

const serializedName =
    myBlock.privateData || myBlock.name;

and serialize that value without modifying the live block object.

Suggested Approach

case "nopValueBlock":
case "nopZeroArgBlock":
case "nopOneArgBlock":
case "nopTwoArgBlock":
case "nopThreeArgBlock": {
    const serializedName =
        myBlock.privateData || myBlock.name;

    data.push([
        blockIndexById.get(blk),
        serializedName,
        myBlock.container.x,
        myBlock.container.y,
        connections
    ]);

    continue;
}

Checklist

  • I have read and followed the project's code of conduct.
  • I have searched for similar issues before creating this one.
  • I have provided all the necessary information to understand and reproduce the issue.
  • I am willing to contribute to the resolution of this issue.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status

    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions