Skip to content

array node treeChanged delta payload#26927

Open
daesunp wants to merge 8 commits intomicrosoft:mainfrom
daesunp:additional-retain-op-info
Open

array node treeChanged delta payload#26927
daesunp wants to merge 8 commits intomicrosoft:mainfrom
daesunp:additional-retain-op-info

Conversation

@daesunp
Copy link
Copy Markdown
Contributor

@daesunp daesunp commented Apr 2, 2026

Description

Adds support for delta payload for array node events in treeChanged.

@daesunp daesunp force-pushed the additional-retain-op-info branch from 4718fbf to 32f933e Compare April 2, 2026 20:44
@daesunp daesunp marked this pull request as ready for review April 2, 2026 21:17
@daesunp daesunp requested a review from a team as a code owner April 2, 2026 21:17
Copilot AI review requested due to automatic review settings April 2, 2026 21:17
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR expands the nodeChanged delta payload for array nodes by adding a per-element nested change description (childDelta) on "retain" ops, enabling consumers (e.g., text editors) to perform more granular updates without re-diffing entire subtrees.

Changes:

  • Add ArrayNodeRetainOp.childDelta?: NodeDelta and introduce the recursive NodeDelta type to describe nested changes within retained elements.
  • Extend the delta → array-ops conversion to populate childDelta from nested fields marks, and update AnchorSet batching so array nodeChanged can fire for “pure nested changes” (retain-with-fields) in hydrated mode.
  • Update/expand test coverage around hydration behavior and add new tests validating childDelta shape/content.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
packages/framework/fluid-framework/api-report/fluid-framework.alpha.api.md API report update for newly exported NodeDelta and childDelta on array retain ops.
packages/dds/tree/src/test/tableSchema.spec.ts Adjust hydration-aware expectations for nodeChanged firing behavior.
packages/dds/tree/src/test/simple-tree/api/treeNodeApi.spec.ts Adds/updates tests for hydration behavior and validates childDelta payloads.
packages/dds/tree/src/simple-tree/index.ts Re-export NodeDelta from the simple-tree surface.
packages/dds/tree/src/simple-tree/api/treeNodeApi.ts Implements NodeDelta, childDelta generation, and updated delta conversion logic.
packages/dds/tree/src/simple-tree/api/treeAlpha.ts Re-export NodeDelta from TreeAlpha API.
packages/dds/tree/src/simple-tree/api/index.ts Re-export NodeDelta from the simple-tree API index.
packages/dds/tree/src/index.ts Re-export NodeDelta from the package root.
packages/dds/tree/src/entrypoints/legacy.ts Include NodeDelta in generated legacy entrypoint exports.
packages/dds/tree/src/entrypoints/alpha.ts Include NodeDelta in generated alpha entrypoint exports.
packages/dds/tree/src/core/tree/anchorSet.ts Emit childrenChangedAfterBatch for array fields on retain-with-fields-only marks (hydrated mode).
packages/dds/tree/api-report/tree.alpha.api.md API report update for NodeDelta and childDelta on array retain ops.

// A retain mark with nested `fields` implies a tree node was retained, not a leaf.
// Reaching here indicates an unexpected internal state.
fail("Expected a tree node for a retain mark with nested fields.");
}
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

buildPropertyDelta can emit { kind: "leaf", newValue: undefined } when an optional field is cleared/removed (object nodes represent empty optional fields as undefined). TreeLeafValue explicitly excludes undefined, so this both violates the exported NodeDelta type and gives consumers an invalid value. Consider explicitly handling detach-only marks (field cleared) with a dedicated delta variant (e.g. kind: "deleted") or widen the leaf/newValue encoding to include undefined, and update NodeDelta + API reports accordingly.

Suggested change
}
}
if (propertyValue === undefined) {
// Optional fields that are cleared are represented as `undefined` in the post-change object view.
// `TreeLeafValue` excludes `undefined`, so avoid emitting an invalid leaf delta payload.
// Use the existing empty object delta encoding to signal that the property changed without
// reporting a nested retained subtree.
return { kind: "object", changedProperties: new Map() };
}

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to have kind: "deleted"

Comment on lines +459 to +463
} else {
// Map or record node: stored field keys are the property keys.
// Map entries are accessed via .get() — direct property access does not work on map node proxies.
assert(isMapNodeSchema(nodeSchema), "expected map or record schema in map branch");
const mapNode = node as unknown as TreeMapNode;
Copy link

Copilot AI Apr 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The map/record branch in buildNodeDeltaFromFields asserts isMapNodeSchema(nodeSchema), but the comment and NodeDelta docs say this should handle both map and record nodes. For NodeKind.Record schemas this assertion will fire, breaking childDelta generation for record nodes. Please add record support (e.g. check isMapNodeSchema || isRecordNodeSchema and read values using the appropriate access pattern).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added section for recode nodes

Comment on lines +39 to +42
* - Array nodes define a change as when an element is added, removed, moved, or replaced,
* or when a property of an element changes (i.e., a nested change within an element).
* In the latter case, the {@link ArrayNodeRetainOp.childDelta} field on the corresponding
* retain op describes what changed within the element.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we want to report nested changes as a part of the nodeChanged event. Presumably we only want to do this for the treeChanged event to be consistent with other node types. nodeChanged is a shallow check, and treeChanged is a deep check.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated to be in treeChanged and now just provides a boolean instead of childDelta

}

// @alpha @sealed
export type NodeDelta = {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does treeChanged represent deep tree changes? Seems like we would want to leverage the same tree structure (presumably just adding support for arrays).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved to treechanged :)

@daesunp daesunp changed the title Add childDelta to array nodeChanged retain ops array node treeChanged delta payload Apr 7, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 7, 2026

🔗 No broken links found! ✅

Your attention to detail is admirable.

linkcheck output


> fluid-framework-docs-site@0.0.0 ci:check-links /home/runner/work/FluidFramework/FluidFramework/docs
> start-server-and-test "npm run serve -- --no-open" 3000 check-links

1: starting server using command "npm run serve -- --no-open"
and when url "[ 'http://127.0.0.1:3000' ]" is responding with HTTP status code 200
running tests using command "npm run check-links"


> fluid-framework-docs-site@0.0.0 serve
> docusaurus serve --no-open

[SUCCESS] Serving "build" directory at: http://localhost:3000/

> fluid-framework-docs-site@0.0.0 check-links
> linkcheck http://localhost:3000 --skip-file skipped-urls.txt

Crawling...

Stats:
  278943 links
    1879 destination URLs
    2124 URLs ignored
       0 warnings
       0 errors


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants