Skip to content

Include final retain in tree deltas - #27809

Open
CraigMacomber wants to merge 2 commits into
microsoft:mainfrom
CraigMacomber:quillLastLine
Open

Include final retain in tree deltas#27809
CraigMacomber wants to merge 2 commits into
microsoft:mainfrom
CraigMacomber:quillLastLine

Conversation

@CraigMacomber

Copy link
Copy Markdown
Contributor

Description

Previously our array node deltas were not required to include the final retain. This is now alwayse included.

Addationally handling of the extra new line required to make quill a happe is now more robust, fixing a buig where remote copllaborators should end up with extra new lines.

Reviewer Guidance

The review process is outlined on this wiki page.

Copilot AI review requested due to automatic review settings July 31, 2026 18:43
@CraigMacomber
CraigMacomber requested review from a team as code owners July 31, 2026 18:43
@github-actions github-actions Bot added base: main PRs targeted against main branch area: framework Framework is a tag for issues involving the developer framework. Eg Aqueduct area: tools area: dds Issues related to distributed data structures area: repo Repo related work area: website area: dds: tree changeset-present labels Jul 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Hi! Thank you for opening this PR. Want me to review it?

Based on the diff (460 lines, 12 files), I've queued these reviewers:

  • Correctness — logic errors, race conditions, lifecycle issues
  • Security — vulnerabilities, secret exposure, injection
  • API Compatibility — breaking changes, release tags, type design
  • Performance — algorithmic regressions, memory leaks
  • Testing — coverage gaps, hollow tests

How this works

  • Adjust the reviewer set by ticking/unticking boxes above. Reviewer toggles alone don't trigger anything.

  • Tick Start review below to dispatch the review fleet.

  • After review finishes, tick Start review again to request another run — it auto-resets after each dispatch.

  • This comment updates as new commits land; your reviewer selections are preserved.

  • Start review

Copilot AI left a comment

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.

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

@github-actions

Copy link
Copy Markdown
Contributor

Bundle size comparison

Base commit: f3785dfc769ab502e10e1123733c60835e7ad73c
Head commit: 0883865dfb3569fc44ee93bb48746cfbb072553c

Notable changes

No bundles changed by ≥ 500 bytes parsed.

Per-bundle deltas

@fluid-example/bundle-size-tests

  • azureClient.js: parsed 620351 → 620407 (+56), gzip 165480 → 165526 (+46)
  • odspClient.js: parsed 592639 → 592695 (+56), gzip 158606 → 158652 (+46)
  • aqueduct.js: parsed 526970 → 527005 (+35), gzip 141035 → 141063 (+28)
  • fluidFramework.js: parsed 398036 → 398078 (+42), gzip 112955 → 113034 (+79)
  • sharedTree.js: parsed 387440 → 387475 (+35), gzip 110380 → 110457 (+77)
  • containerRuntime.js: parsed 305091 → 305105 (+14), gzip 83553 → 83558 (+5)
  • sharedString.js: parsed 176457 → 176464 (+7), gzip 49807 → 49815 (+8)
  • experimentalSharedTree.js: parsed 160677 → 160677 (0), gzip 46276 → 46276 (0)
  • matrix.js: parsed 160302 → 160309 (+7), gzip 45800 → 45807 (+7)
  • loader.js: parsed 145459 → 145473 (+14), gzip 39221 → 39235 (+14)
  • odspDriver.js: parsed 103906 → 103927 (+21), gzip 32404 → 32411 (+7)
  • directory.js: parsed 67110 → 67117 (+7), gzip 18859 → 18866 (+7)
  • 578.js: parsed 58686 → 58686 (0), gzip 17657 → 17657 (0)
  • map.js: parsed 47205 → 47212 (+7), gzip 14455 → 14462 (+7)
  • odspPrefetchSnapshot.js: parsed 45635 → 45649 (+14), gzip 15242 → 15250 (+8)
  • 252.js: parsed 44371 → 44371 (0), gzip 13732 → 13732 (0)
  • summarizerDelayLoadedModule.js: parsed 30717 → 30717 (0), gzip 7716 → 7716 (0)
  • socketModule.js: parsed 26469 → 26476 (+7), gzip 7895 → 7903 (+8)
  • createNewModule.js: parsed 12454 → 12454 (0), gzip 4797 → 4797 (0)
  • summaryModule.js: parsed 3789 → 3789 (0), gzip 1857 → 1857 (0)
  • connectionState.js: parsed 909 → 909 (0), gzip 500 → 500 (0)
  • sharedTreeAttributes.js: parsed 847 → 854 (+7), gzip 499 → 507 (+8)
  • debugAssert.js: parsed 429 → 429 (0), gzip 299 → 299 (0)
  • FluidFramework-HashFallback.js: parsed 419 → 419 (0), gzip 313 → 313 (0)

@Josmithr
Josmithr requested a review from daesunp July 31, 2026 19:33
};
}

function lineAtomToQuillAttributes(

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.

Nit: docs?

Comment on lines +242 to +251
if (remainingQuillContent.length > 0) {
assert(
remainingQuillContent === "\n",
"Expected only Quill's mandatory terminal newline to remain",
);
quillOps.push({ delete: 1 });
}
if (!root.fullString().endsWith("\n")) {
quillOps.push({ insert: "\n" });
}

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.

Nit: some docs here would probably be useful

Comment on lines +426 to +429
function getArrayLength(arrayNodeKernel: TreeNodeKernel): number {
return arrayNodeKernel.getInnerNode().getBoxed(EmptyKey).length;
}

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'm assuming there's no stricter typing we can use here? If not, some docs would probably be good.

Comment on lines +94 to +125
it("keeps remote editor DOM in sync after a user edit", () => {
const text = TextAsTree.Tree.fromString("");
const root = toPropTreeNode(text);
const rendered = render(
<>
<ViewComponent root={root} />
<ViewComponent root={root} />
</>,
{ reactStrictMode },
);
const sourceContainer =
rendered.container.querySelector<HTMLElement>(".ql-container");
const editors = rendered.container.querySelectorAll<HTMLElement>(".ql-editor");
const sourceEditor = editors[0];
const remoteEditor = editors[1];
assert.ok(
sourceContainer !== null &&
sourceEditor !== undefined &&
remoteEditor !== undefined,
);
const source = Quill.find(sourceContainer) as Quill;

source.setText("Hello", "user");

assert.equal(sourceEditor.innerHTML, "<p>Hello</p>");
assert.equal(remoteEditor.innerHTML, sourceEditor.innerHTML);

source.setText("Hello\nWorld", "user");

assert.equal(sourceEditor.innerHTML, "<p>Hello</p><p>World</p>");
assert.equal(remoteEditor.innerHTML, sourceEditor.innerHTML);
});

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.

Nit: some docs in here would be useful

Comment on lines +297 to +328
it("keeps remote editor DOM in sync after a user edit", () => {
const { tree } = createFormattedTreeView();
const root = toPropTreeNode(tree);
const rendered = render(
<>
<FormattedMainView root={root} />
<FormattedMainView root={root} />
</>,
{ reactStrictMode },
);
const sourceContainer =
rendered.container.querySelector<HTMLElement>(".ql-container");
const editors = rendered.container.querySelectorAll<HTMLElement>(".ql-editor");
const sourceEditor = editors[0];
const remoteEditor = editors[1];
assert.ok(
sourceContainer !== null &&
sourceEditor !== undefined &&
remoteEditor !== undefined,
);
const source = Quill.find(sourceContainer) as Quill;

source.setText("Hello", "user");

assert.equal(sourceEditor.innerHTML, "<p>Hello</p>");
assert.equal(remoteEditor.innerHTML, sourceEditor.innerHTML);

source.formatLine(0, source.getLength(), "list", "bullet", "user");

assert.match(sourceEditor.innerHTML, /^<ol>/);
assert.equal(remoteEditor.innerHTML, sourceEditor.innerHTML);
});

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.

Nit: some docs in here would be useful

CraigMacomber added a commit that referenced this pull request Jul 31, 2026
## Description

While authoring #27809 I
had to make some assumptions about deltas, which were undocumented.

This fills in the missing documentation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: dds: tree area: dds Issues related to distributed data structures area: framework Framework is a tag for issues involving the developer framework. Eg Aqueduct area: repo Repo related work area: tools area: website base: main PRs targeted against main branch changeset-present

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants