authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-08 02:40:37-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-08 02:40:37-07:00
log02d45d734921e4a41dddbf7887dd3224bb4c7f4c
tree1cc8f6cdc96d1022e724af5d0ef93f3ab0c8c3d7
parente3fba00fa7baf2a33af4943802256e937ba0573d
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

feat: join ordinary paragraphs with native identity and tag semantics

Preserve inherited character styles, nested child placement, history and unrelated objects while publishing the joined graph and title metadata atomically. Empty left paragraphs adopt the right text identity; left tags survive as verified in independent OneNote controls. Validate twenty Rust joins through cold native captures. Add preservation, invalid-target, property-reference remapping and interrupted-publication checks, plus stateful split/join fuzzing. Public gate passes 186 Rust tests, 8 doctests and 130 Python tests with fmt, Clippy and builds. Assisted-by: gpt-6-astra

97 files changed, 2054 insertions(+), 178 deletions(-)

corpus/paragraph-edit/README.md+14-2
......@@ -34,8 +34,7 @@ directory comes from `ONESTORE_PARAGRAPH_OUTPUT`. Native character/style compari
3434use both the keyboard-generated controls and the Rust-written notebooks. Empty
3535typing checks extend the preexisting empty run in an independent expected model.
3636
37`tools/test_paragraph_edit.py` verifies these controls without a VM. The native
38join captures establish behavior for subsequent join implementation.
37`tools/test_paragraph_edit.py` verifies these controls without a VM.
3938Identical captured files link to one canonical copy within this corpus.
4039
4140`join-edges`, authored with `tools/native/paragraph-joins.ps1`, adds five native
......@@ -48,3 +47,16 @@ The untouched parent's text gains a timestamp and `0x880034dd`, as in the earlie
4847native controls; its content and other properties stay unchanged. The test fixes
4948these observed graph, identity and metadata effects alongside independent native
5049character-style comparisons.
50
51`join-tags`, authored with `tools/native/paragraph-tag-joins.ps1`, confirms the
52left-tag rule for an empty untagged left paragraph, an empty tagged left paragraph
53with a different right tag, and two nonempty paragraphs with different tags.
54Right-side tags disappear in all three cases; only the left tag survives.
55
56`rust-join` retains twenty Rust joins across the original splits, inheritance
57edges and additional tag controls. Each group has a cold OneNote capture and
58saved notebook. Public tests compare the Rust and native-saved images to native
59XML, preserve exact child/content identities across reopening, and compare
60the native-rendered result to the corresponding keyboard-generated control.
61`export_native_paragraph_joins` regenerates candidates in a new directory specified
62by `ONESTORE_PARAGRAPH_JOIN_OUTPUT`.
corpus/paragraph-edit/join-tags/before/notebook/Open Notebook.onetoc2 created
Binary files /dev/null and b/corpus/paragraph-edit/join-tags/before/notebook/Open Notebook.onetoc2 differ
corpus/paragraph-edit/join-tags/before/notebook/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/join-tags/before/notebook/synthetic.one differ
corpus/paragraph-edit/join-tags/before/read/environment.json created+7
......@@ -0,0 +1,7 @@
1{
2 "powershell": "5.1.14409.1005",
3 "schema": "xs2010",
4 "hostname": "ONE-M6-57E1FB2A",
5 "cold": false,
6 "onenote": "14.0.4763.1000"
7}
corpus/paragraph-edit/join-tags/before/read/hierarchy.xml created+2
......@@ -0,0 +1,2 @@
1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{3E150BBB-447F-4345-8BCC-7B6B8B50CB91}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-08T09:34:38.000Z" color="#B49EDE"><one:Section name="synthetic" ID="{9E605B7B-B21F-039A-0A67-51514F4CD2FA}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-08T09:34:38.000Z" color="#8AA8E4"><one:Page ID="{B229A8FD-3BD4-408B-8D00-F9C0B374CEE5}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T09:34:38.000Z" pageLevel="1"/><one:Page ID="{2A557903-2906-4962-90DB-1DBBD4B4F368}{1}{B0}" name="Join empty left right tag" dateTime="2026-09-08T09:33:01.000Z" lastModifiedTime="2026-09-08T09:34:38.000Z" pageLevel="1"/><one:Page ID="{766D2454-BC90-480A-8BA1-BF0C9E703454}{1}{B0}" name="Join empty left two tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:34:38.000Z" pageLevel="1"/><one:Page ID="{B93EF35F-621E-4F9A-90D4-B55A0CF7E4AA}{1}{B0}" name="Join different tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:34:38.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/paragraph-edit/join-tags/before/read/page-000.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2A557903-2906-4962-90DB-1DBBD4B4F368}{1}{B0}" name="Join empty left right tag" dateTime="2026-09-08T09:33:01.000Z" lastModifiedTime="2026-09-08T09:34:38.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Other task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:33:03.000Z" lastModifiedTime="2026-09-08T09:33:03.000Z" objectID="{DB20B227-1FF5-4341-9621-D7C3E0EEE79B}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Join empty left right tag]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T09:33:05.000Z" objectID="{DB20B227-1FF5-4341-9621-D7C3E0EEE79B}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="116.9218902587891" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T09:33:03.000Z" lastModifiedTime="2026-09-08T09:33:03.000Z" objectID="{DB20B227-1FF5-4341-9621-D7C3E0EEE79B}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE><one:OE creationTime="2026-09-08T09:33:01.000Z" lastModifiedTime="2026-09-08T09:33:03.000Z" objectID="{DB20B227-1FF5-4341-9621-D7C3E0EEE79B}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE creationTime="2026-09-08T09:33:03.000Z" lastModifiedTime="2026-09-08T09:33:03.000Z" objectID="{DB20B227-1FF5-4341-9621-D7C3E0EEE79B}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/join-tags/before/read/page-001.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{766D2454-BC90-480A-8BA1-BF0C9E703454}{1}{B0}" name="Join empty left two tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:34:38.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Fixture task"/><one:TagDef index="1" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Other task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:33:04.000Z" objectID="{5237862E-022E-4FC4-8E36-D9C8E010D9E1}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Join empty left two tags]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T09:33:05.000Z" objectID="{5237862E-022E-4FC4-8E36-D9C8E010D9E1}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="116.9218902587891" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:33:04.000Z" objectID="{5237862E-022E-4FC4-8E36-D9C8E010D9E1}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[]]></one:T></one:OE><one:OE creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:33:04.000Z" objectID="{5237862E-022E-4FC4-8E36-D9C8E010D9E1}{47}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="1" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:33:04.000Z" objectID="{5237862E-022E-4FC4-8E36-D9C8E010D9E1}{56}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/join-tags/before/read/page-002.xml created+20
......@@ -0,0 +1,20 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B229A8FD-3BD4-408B-8D00-F9C0B374CEE5}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T09:34:38.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Rich author" lastModifiedBy="Rich author" lastModifiedTime="2026-09-08T04:47:24.000Z" objectID="{D5E50041-70A1-433D-82F2-751B4CE82B84}{10}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="127.5136947631836" height="42.90299224853515"/><one:OEChildren><one:OE creationTime="2026-09-08T04:47:24.000Z" lastModifiedTime="2026-09-08T04:47:24.000Z" objectID="{D280A8B6-83DC-0837-32A6-B61187835972}{1}{B0}" alignment="left"><one:T><![CDATA[<span style='font-weight:bold;
3text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span
4style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family:
5Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri;
6font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through;
7font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family:
8Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family:
9Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size:
1011.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238;
11background:yellow;mso-highlight:yellow'>color</span><span style='font-family:
12Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family:
13Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size:
1411.0pt'><br>
15End</span>]]></one:T></one:OE><one:OE author="snow" authorInitials="S" lastModifiedBy="snow" lastModifiedByInitials="S" creationTime="2026-09-05T05:06:55.000Z" lastModifiedTime="2026-09-05T05:06:56.000Z" objectID="{D5E50041-70A1-433D-82F2-751B4CE82B84}{11}{B0}" alignment="left" quickStyleIndex="0" style="font-size:11.0pt;color:#1F4E79"><one:T><![CDATA[<span
16style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span
17style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span
18style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span
19style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed'
20lang=ar-SA>مرحبا</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/join-tags/before/read/page-003.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B93EF35F-621E-4F9A-90D4-B55A0CF7E4AA}{1}{B0}" name="Join different tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:34:38.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Fixture task"/><one:TagDef index="1" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Other task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:33:04.000Z" objectID="{998EEB43-1A80-48BB-B928-2AE6052587A2}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Join different tags]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T09:33:05.000Z" objectID="{998EEB43-1A80-48BB-B928-2AE6052587A2}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="116.9218902587891" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:33:04.000Z" objectID="{998EEB43-1A80-48BB-B928-2AE6052587A2}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[Left]]></one:T></one:OE><one:OE creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:33:04.000Z" objectID="{998EEB43-1A80-48BB-B928-2AE6052587A2}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="1" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:33:04.000Z" objectID="{998EEB43-1A80-48BB-B928-2AE6052587A2}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/join-tags/before/read/payloads.json created+1
......@@ -0,0 +1 @@
1../../../before/read/payloads.json
\ No newline at end of file
corpus/paragraph-edit/join-tags/cases.json created+17
......@@ -0,0 +1,17 @@
1[
2 {
3 "body": "\u003cone:OE\u003e\u003cone:T\u003e\u003c![CDATA[]]\u003e\u003c/one:T\u003e\u003c/one:OE\u003e\u003cone:OE\u003e\u003cone:Tag index=\"1\" completed=\"true\" disabled=\"false\" creationDate=\"2020-01-02T03:04:05.000Z\" completionDate=\"2020-01-02T04:05:06.000Z\"/\u003e\u003cone:T\u003e\u003c![CDATA[\u003cb\u003eBold\u003c/b\u003e \u003ci\u003eitalic\u003c/i\u003e 🦀 é \u003cspan style=\"color:#123456\"\u003etail\u003c/span\u003e]]\u003e\u003c/one:T\u003e\u003c/one:OE\u003e",
4 "name": "Join empty left right tag",
5 "page": "{110D64A5-F4AB-4699-B620-CF39E7595E3D}{1}{B0}"
6 },
7 {
8 "body": "\u003cone:OE\u003e\u003cone:Tag index=\"0\" completed=\"true\" disabled=\"false\" creationDate=\"2020-01-02T03:04:05.000Z\" completionDate=\"2020-01-02T04:05:06.000Z\"/\u003e\u003cone:T\u003e\u003c![CDATA[]]\u003e\u003c/one:T\u003e\u003c/one:OE\u003e\u003cone:OE\u003e\u003cone:Tag index=\"1\" completed=\"true\" disabled=\"false\" creationDate=\"2020-01-02T03:04:05.000Z\" completionDate=\"2020-01-02T04:05:06.000Z\"/\u003e\u003cone:T\u003e\u003c![CDATA[\u003cb\u003eBold\u003c/b\u003e \u003ci\u003eitalic\u003c/i\u003e 🦀 é \u003cspan style=\"color:#123456\"\u003etail\u003c/span\u003e]]\u003e\u003c/one:T\u003e\u003c/one:OE\u003e",
9 "name": "Join empty left two tags",
10 "page": "{4D3539F2-613D-47F1-AD5A-6D8EAD9D9901}{1}{B0}"
11 },
12 {
13 "body": "\u003cone:OE\u003e\u003cone:Tag index=\"0\" completed=\"true\" disabled=\"false\" creationDate=\"2020-01-02T03:04:05.000Z\" completionDate=\"2020-01-02T04:05:06.000Z\"/\u003e\u003cone:T\u003eLeft\u003c/one:T\u003e\u003c/one:OE\u003e\u003cone:OE\u003e\u003cone:Tag index=\"1\" completed=\"true\" disabled=\"false\" creationDate=\"2020-01-02T03:04:05.000Z\" completionDate=\"2020-01-02T04:05:06.000Z\"/\u003e\u003cone:T\u003e\u003c![CDATA[\u003cb\u003eBold\u003c/b\u003e \u003ci\u003eitalic\u003c/i\u003e 🦀 é \u003cspan style=\"color:#123456\"\u003etail\u003c/span\u003e]]\u003e\u003c/one:T\u003e\u003c/one:OE\u003e",
14 "name": "Join different tags",
15 "page": "{8266EEF9-BFB3-4061-B62F-67D83F1A49FF}{1}{B0}"
16 }
17]
corpus/paragraph-edit/join-tags/joined/notebook/Open Notebook.onetoc2 created+1
......@@ -0,0 +1 @@
1../../before/notebook/Open Notebook.onetoc2
\ No newline at end of file
corpus/paragraph-edit/join-tags/joined/notebook/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/join-tags/joined/notebook/synthetic.one differ
corpus/paragraph-edit/join-tags/joined/read/environment.json created+1
......@@ -0,0 +1 @@
1../../before/read/environment.json
\ No newline at end of file
corpus/paragraph-edit/join-tags/joined/read/hierarchy.xml created+2
......@@ -0,0 +1,2 @@
1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{3E150BBB-447F-4345-8BCC-7B6B8B50CB91}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-08T09:36:35.000Z" color="#B49EDE" isCurrentlyViewed="true"><one:Section name="synthetic" ID="{9E605B7B-B21F-039A-0A67-51514F4CD2FA}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-08T09:36:35.000Z" color="#8AA8E4" isCurrentlyViewed="true"><one:Page ID="{B229A8FD-3BD4-408B-8D00-F9C0B374CEE5}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T09:34:38.000Z" pageLevel="1"/><one:Page ID="{2A557903-2906-4962-90DB-1DBBD4B4F368}{1}{B0}" name="Join empty left right tag" dateTime="2026-09-08T09:33:01.000Z" lastModifiedTime="2026-09-08T09:36:31.000Z" pageLevel="1"/><one:Page ID="{766D2454-BC90-480A-8BA1-BF0C9E703454}{1}{B0}" name="Join empty left two tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:36:33.000Z" pageLevel="1"/><one:Page ID="{B93EF35F-621E-4F9A-90D4-B55A0CF7E4AA}{1}{B0}" name="Join different tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:36:35.000Z" pageLevel="1" isCurrentlyViewed="true"/></one:Section></one:Notebook>
corpus/paragraph-edit/join-tags/joined/read/page-000.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2A557903-2906-4962-90DB-1DBBD4B4F368}{1}{B0}" name="Join empty left right tag" dateTime="2026-09-08T09:33:01.000Z" lastModifiedTime="2026-09-08T09:36:31.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:33:03.000Z" lastModifiedTime="2026-09-08T09:33:03.000Z" objectID="{DB20B227-1FF5-4341-9621-D7C3E0EEE79B}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Join empty left right tag]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T09:36:31.000Z" objectID="{DB20B227-1FF5-4341-9621-D7C3E0EEE79B}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="116.921875" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T09:33:03.000Z" lastModifiedTime="2026-09-08T09:36:31.000Z" objectID="{DB20B227-1FF5-4341-9621-D7C3E0EEE79B}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE creationTime="2026-09-08T09:33:03.000Z" lastModifiedTime="2026-09-08T09:33:03.000Z" objectID="{DB20B227-1FF5-4341-9621-D7C3E0EEE79B}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/join-tags/joined/read/page-001.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{766D2454-BC90-480A-8BA1-BF0C9E703454}{1}{B0}" name="Join empty left two tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:36:33.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Fixture task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:36:32.000Z" objectID="{5237862E-022E-4FC4-8E36-D9C8E010D9E1}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Join empty left two tags]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T09:36:33.000Z" objectID="{5237862E-022E-4FC4-8E36-D9C8E010D9E1}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="116.9218902587891" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:36:33.000Z" objectID="{5237862E-022E-4FC4-8E36-D9C8E010D9E1}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:36:32.000Z" objectID="{5237862E-022E-4FC4-8E36-D9C8E010D9E1}{56}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/join-tags/joined/read/page-002.xml created+20
......@@ -0,0 +1,20 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B229A8FD-3BD4-408B-8D00-F9C0B374CEE5}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T09:36:30.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Rich author" lastModifiedBy="Rich author" lastModifiedTime="2026-09-08T04:47:24.000Z" objectID="{D5E50041-70A1-433D-82F2-751B4CE82B84}{10}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="523.125" height="42.90299224853515"/><one:OEChildren><one:OE creationTime="2026-09-08T04:47:24.000Z" lastModifiedTime="2026-09-08T04:47:24.000Z" objectID="{D280A8B6-83DC-0837-32A6-B61187835972}{1}{B0}" alignment="left"><one:T><![CDATA[<span style='font-weight:bold;
3text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span
4style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family:
5Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri;
6font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through;
7font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family:
8Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family:
9Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size:
1011.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238;
11background:yellow;mso-highlight:yellow'>color</span><span style='font-family:
12Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family:
13Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size:
1411.0pt'><br>
15End</span>]]></one:T></one:OE><one:OE author="snow" authorInitials="S" lastModifiedBy="snow" lastModifiedByInitials="S" creationTime="2026-09-05T05:06:55.000Z" lastModifiedTime="2026-09-05T05:06:56.000Z" objectID="{D5E50041-70A1-433D-82F2-751B4CE82B84}{11}{B0}" alignment="left" quickStyleIndex="0" style="font-size:11.0pt;color:#1F4E79"><one:T><![CDATA[<span
16style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span
17style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span
18style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span
19style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed'
20lang=ar-SA>مرحبا</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/join-tags/joined/read/page-003.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B93EF35F-621E-4F9A-90D4-B55A0CF7E4AA}{1}{B0}" name="Join different tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:36:35.000Z" pageLevel="1" isCurrentlyViewed="true" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Fixture task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:36:34.000Z" objectID="{998EEB43-1A80-48BB-B928-2AE6052587A2}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Join different tags]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T09:36:35.000Z" objectID="{998EEB43-1A80-48BB-B928-2AE6052587A2}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="134.0610046386719" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T09:36:35.000Z" lastModifiedTime="2026-09-08T09:36:35.000Z" objectID="{998EEB43-1A80-48BB-B928-2AE6052587A2}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3lang=en-US>Left</span><span style='font-weight:bold' lang=en-US>Bold</span><span
4lang=en-US> </span><span style='font-style:italic' lang=en-US>italic</span><span
5lang=en-US> &#129408; é </span><span style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:36:34.000Z" objectID="{998EEB43-1A80-48BB-B928-2AE6052587A2}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/join-tags/joined/read/payloads.json created+1
......@@ -0,0 +1 @@
1../../../before/read/payloads.json
\ No newline at end of file
corpus/paragraph-edit/join-tags/joined/ui/join-different-tags.ahk created+22
......@@ -0,0 +1,22 @@
1#Requires AutoHotkey v2.0
2OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1)))
3dm := Buffer(220, 0)
4NumPut("UShort", 220, dm, 68)
5if !DllCall("EnumDisplaySettingsW", "Ptr", 0, "UInt", 0xFFFFFFFF, "Ptr", dm)
6 throw Error("Cannot inspect display mode")
7NumPut("UInt", NumGet(dm, 72, "UInt") | 0x180000, dm, 72)
8NumPut("UInt", 1280, dm, 172)
9NumPut("UInt", 720, dm, 176)
10if DllCall("ChangeDisplaySettingsW", "Ptr", dm, "UInt", 0, "Int") != 0
11 throw Error("Display mode rejected")
12app := ComObject("OneNote.Application")
13app.NavigateTo("{B93EF35F-621E-4F9A-90D4-B55A0CF7E4AA}{1}{B0}", "{998EEB43-1A80-48BB-B928-2AE6052587A2}{46}{B0}", false)
14hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10)
15if !hwnd
16 throw Error("OneNote window did not appear")
17WinMaximize(hwnd)
18WinActivate(hwnd)
19if !WinWaitActive(hwnd,, 10)
20 throw Error("OneNote window did not become active")
21Sleep 300
22Send "{Left}{Backspace}"
corpus/paragraph-edit/join-tags/joined/ui/join-different-tags.json created+13
......@@ -0,0 +1,13 @@
1{
2 "exit": 0,
3 "stdout": "",
4 "stderr": "",
5 "w": 1280,
6 "h": 720,
7 "error": null,
8 "win": {
9 "title": "Join different tags - Microsoft OneNote",
10 "class": "Framework::CFrame",
11 "dialog": false
12 }
13}
\ No newline at end of file
corpus/paragraph-edit/join-tags/joined/ui/join-different-tags.png created
Binary files /dev/null and b/corpus/paragraph-edit/join-tags/joined/ui/join-different-tags.png differ
corpus/paragraph-edit/join-tags/joined/ui/join-empty-left-right-tag.ahk created+22
......@@ -0,0 +1,22 @@
1#Requires AutoHotkey v2.0
2OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1)))
3dm := Buffer(220, 0)
4NumPut("UShort", 220, dm, 68)
5if !DllCall("EnumDisplaySettingsW", "Ptr", 0, "UInt", 0xFFFFFFFF, "Ptr", dm)
6 throw Error("Cannot inspect display mode")
7NumPut("UInt", NumGet(dm, 72, "UInt") | 0x180000, dm, 72)
8NumPut("UInt", 1280, dm, 172)
9NumPut("UInt", 720, dm, 176)
10if DllCall("ChangeDisplaySettingsW", "Ptr", dm, "UInt", 0, "Int") != 0
11 throw Error("Display mode rejected")
12app := ComObject("OneNote.Application")
13app.NavigateTo("{2A557903-2906-4962-90DB-1DBBD4B4F368}{1}{B0}", "{DB20B227-1FF5-4341-9621-D7C3E0EEE79B}{46}{B0}", false)
14hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10)
15if !hwnd
16 throw Error("OneNote window did not appear")
17WinMaximize(hwnd)
18WinActivate(hwnd)
19if !WinWaitActive(hwnd,, 10)
20 throw Error("OneNote window did not become active")
21Sleep 300
22Send "{Left}{Backspace}"
corpus/paragraph-edit/join-tags/joined/ui/join-empty-left-right-tag.json created+13
......@@ -0,0 +1,13 @@
1{
2 "exit": 0,
3 "stdout": "",
4 "stderr": "",
5 "w": 1280,
6 "h": 720,
7 "error": null,
8 "win": {
9 "title": "Join empty left right tag - Microsoft OneNote",
10 "class": "Framework::CFrame",
11 "dialog": false
12 }
13}
\ No newline at end of file
corpus/paragraph-edit/join-tags/joined/ui/join-empty-left-right-tag.png created
Binary files /dev/null and b/corpus/paragraph-edit/join-tags/joined/ui/join-empty-left-right-tag.png differ
corpus/paragraph-edit/join-tags/joined/ui/join-empty-left-two-tags.ahk created+22
......@@ -0,0 +1,22 @@
1#Requires AutoHotkey v2.0
2OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1)))
3dm := Buffer(220, 0)
4NumPut("UShort", 220, dm, 68)
5if !DllCall("EnumDisplaySettingsW", "Ptr", 0, "UInt", 0xFFFFFFFF, "Ptr", dm)
6 throw Error("Cannot inspect display mode")
7NumPut("UInt", NumGet(dm, 72, "UInt") | 0x180000, dm, 72)
8NumPut("UInt", 1280, dm, 172)
9NumPut("UInt", 720, dm, 176)
10if DllCall("ChangeDisplaySettingsW", "Ptr", dm, "UInt", 0, "Int") != 0
11 throw Error("Display mode rejected")
12app := ComObject("OneNote.Application")
13app.NavigateTo("{766D2454-BC90-480A-8BA1-BF0C9E703454}{1}{B0}", "{5237862E-022E-4FC4-8E36-D9C8E010D9E1}{47}{B0}", false)
14hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10)
15if !hwnd
16 throw Error("OneNote window did not appear")
17WinMaximize(hwnd)
18WinActivate(hwnd)
19if !WinWaitActive(hwnd,, 10)
20 throw Error("OneNote window did not become active")
21Sleep 300
22Send "{Left}{Backspace}"
corpus/paragraph-edit/join-tags/joined/ui/join-empty-left-two-tags.json created+13
......@@ -0,0 +1,13 @@
1{
2 "exit": 0,
3 "stdout": "",
4 "stderr": "",
5 "w": 1280,
6 "h": 720,
7 "error": null,
8 "win": {
9 "title": "Join empty left two tags - Microsoft OneNote",
10 "class": "Framework::CFrame",
11 "dialog": false
12 }
13}
\ No newline at end of file
corpus/paragraph-edit/join-tags/joined/ui/join-empty-left-two-tags.png created
Binary files /dev/null and b/corpus/paragraph-edit/join-tags/joined/ui/join-empty-left-two-tags.png differ
corpus/paragraph-edit/join-tags/joined/ui/selections.json created+20
......@@ -0,0 +1,20 @@
1[
2 {
3 "case": "Join empty left right tag",
4 "page": "{2A557903-2906-4962-90DB-1DBBD4B4F368}{1}{B0}",
5 "object": "{DB20B227-1FF5-4341-9621-D7C3E0EEE79B}{46}{B0}",
6 "keys": "{Left}{Backspace}"
7 },
8 {
9 "case": "Join empty left two tags",
10 "page": "{766D2454-BC90-480A-8BA1-BF0C9E703454}{1}{B0}",
11 "object": "{5237862E-022E-4FC4-8E36-D9C8E010D9E1}{47}{B0}",
12 "keys": "{Left}{Backspace}"
13 },
14 {
15 "case": "Join different tags",
16 "page": "{B93EF35F-621E-4F9A-90D4-B55A0CF7E4AA}{1}{B0}",
17 "object": "{998EEB43-1A80-48BB-B928-2AE6052587A2}{46}{B0}",
18 "keys": "{Left}{Backspace}"
19 }
20]
\ No newline at end of file
corpus/paragraph-edit/join-tags/run.json created+19
......@@ -0,0 +1,19 @@
1{
2 "notebook": "/Users/clo/dev/one/corpus/formatted-insertion/native-paragraph/candidate",
3 "expected_pages": 4,
4 "author": "evidence/m10/native-paragraph-tag-joins.ps1",
5 "author_timeout_seconds": 600,
6 "inspect": true,
7 "collect_notebook": true,
8 "base": {
9 "file": "win7-office-base.qcow2",
10 "format": "qcow2",
11 "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346",
12 "virtual_size": 68719476736
13 },
14 "scripts": {
15 "author.ps1": "f975577d6abdb21e6cc8e2a6f605ab239047108e6da2e2f804ab8197991d86e7",
16 "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331",
17 "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41"
18 }
19}
corpus/paragraph-edit/join-tags/source.json created+1
......@@ -0,0 +1 @@
1../join-edges/source.json
\ No newline at end of file
corpus/paragraph-edit/join-tags/teardown.json created+1
......@@ -0,0 +1 @@
1../join-edges/teardown.json
\ No newline at end of file
corpus/paragraph-edit/rust-join/inheritance/candidate/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/rust-join/inheritance/candidate/synthetic.one differ
corpus/paragraph-edit/rust-join/inheritance/manifest.json created+47
......@@ -0,0 +1,47 @@
1[
2 {
3 "case": "Join inherited styles",
4 "intent": {
5 "author": "Rust join author",
6 "left": "{10952FC5-A849-0DE5-03A7-5D9304627DA7},44",
7 "right": "{10952FC5-A849-0DE5-03A7-5D9304627DA7},50"
8 },
9 "space": "{D6CEB12D-D1B7-033B-20A2-E9B6FF501602},1"
10 },
11 {
12 "case": "Join empty left tag",
13 "intent": {
14 "author": "Rust join author",
15 "left": "{8BAFC64A-8457-0253-3906-57D27F8546B7},45",
16 "right": "{8BAFC64A-8457-0253-3906-57D27F8546B7},51"
17 },
18 "space": "{F6E7A6F0-805F-0858-2B5A-4B1260940CF9},1"
19 },
20 {
21 "case": "Join right tag",
22 "intent": {
23 "author": "Rust join author",
24 "left": "{2E9A20A9-1E0E-0F04-0FCE-8497CF032068},44",
25 "right": "{2E9A20A9-1E0E-0F04-0FCE-8497CF032068},49"
26 },
27 "space": "{04EE80E5-584B-04E0-1F05-07D86902DFA1},1"
28 },
29 {
30 "case": "Join both tags",
31 "intent": {
32 "author": "Rust join author",
33 "left": "{7A5195E3-F792-0C49-1F3C-EBBFCE774FE6},44",
34 "right": "{7A5195E3-F792-0C49-1F3C-EBBFCE774FE6},50"
35 },
36 "space": "{631A0B54-DBB1-0988-22E1-96E8B1202B59},1"
37 },
38 {
39 "case": "Join both children",
40 "intent": {
41 "author": "Rust join author",
42 "left": "{0384F5F6-5F0B-0164-32C6-6E97A4F1B661},49",
43 "right": "{0384F5F6-5F0B-0164-32C6-6E97A4F1B661},54"
44 },
45 "space": "{D7D982D1-A3D3-0690-1181-A9703CF717E5},1"
46 }
47]
\ No newline at end of file
corpus/paragraph-edit/rust-join/inheritance/native/notebook/Open Notebook.onetoc2 created
Binary files /dev/null and b/corpus/paragraph-edit/rust-join/inheritance/native/notebook/Open Notebook.onetoc2 differ
corpus/paragraph-edit/rust-join/inheritance/native/notebook/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/rust-join/inheritance/native/notebook/synthetic.one differ
corpus/paragraph-edit/rust-join/inheritance/native/read/environment.json created+7
......@@ -0,0 +1,7 @@
1{
2 "powershell": "5.1.14409.1005",
3 "schema": "xs2010",
4 "hostname": "ONE-M6-36919857",
5 "cold": true,
6 "onenote": "14.0.4763.1000"
7}
corpus/paragraph-edit/rust-join/inheritance/native/read/hierarchy.xml created+2
......@@ -0,0 +1,2 @@
1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{B91F23B4-AB1B-420F-90A5-CE03531BD49B}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-08T09:41:46.000Z" color="#B49EDE"><one:Section name="synthetic" ID="{7D7D2CEA-26EA-0A71-114D-7381C43452E5}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-08T09:41:46.000Z" color="#8AA8E4"><one:Page ID="{5134DF6C-AF21-4960-962A-DB10380C4EFA}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T09:41:46.000Z" pageLevel="1"/><one:Page ID="{E3E250B5-308F-46E5-8BB3-BA59D089D3E4}{1}{B0}" name="Join inherited styles" dateTime="2026-09-08T09:07:14.000Z" lastModifiedTime="2026-09-08T09:41:46.000Z" pageLevel="1"/><one:Page ID="{C3CB4768-6167-4D86-804B-18FD4F4DC91F}{1}{B0}" name="Join empty left tag" dateTime="2026-09-08T09:07:15.000Z" lastModifiedTime="2026-09-08T09:41:46.000Z" pageLevel="1"/><one:Page ID="{31C2617D-B973-413E-B414-543746DB1A47}{1}{B0}" name="Join right tag" dateTime="2026-09-08T09:07:16.000Z" lastModifiedTime="2026-09-08T09:41:46.000Z" pageLevel="1"/><one:Page ID="{5636EACC-3A89-4C56-89F0-C5079EF9EEBF}{1}{B0}" name="Join both tags" dateTime="2026-09-08T09:07:16.000Z" lastModifiedTime="2026-09-08T09:41:46.000Z" pageLevel="1"/><one:Page ID="{E2F56349-42EB-434E-BA90-FA9F132ED203}{1}{B0}" name="Join both children" dateTime="2026-09-08T09:07:17.000Z" lastModifiedTime="2026-09-08T09:41:46.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/paragraph-edit/rust-join/inheritance/native/read/page-000.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{31C2617D-B973-413E-B414-543746DB1A47}{1}{B0}" name="Join right tag" dateTime="2026-09-08T09:07:16.000Z" lastModifiedTime="2026-09-08T09:41:46.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:07:16.000Z" lastModifiedTime="2026-09-08T09:07:16.000Z" objectID="{1BB6C131-FF36-4ADA-A4DF-D778E0DAE58E}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Join right tag]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{1BB6C131-FF36-4ADA-A4DF-D778E0DAE58E}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="134.0610046386719" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T09:07:16.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{1BB6C131-FF36-4ADA-A4DF-D778E0DAE58E}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3lang=en-US>Left</span><span style='font-weight:bold' lang=en-US>Bold</span><span
4lang=en-US> </span><span style='font-style:italic' lang=en-US>italic</span><span
5lang=en-US> &#129408; é </span><span style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:07:16.000Z" lastModifiedTime="2026-09-08T09:07:16.000Z" objectID="{1BB6C131-FF36-4ADA-A4DF-D778E0DAE58E}{54}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/inheritance/native/read/page-001.xml created+20
......@@ -0,0 +1,20 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5134DF6C-AF21-4960-962A-DB10380C4EFA}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T09:41:46.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Rich author" lastModifiedBy="Rich author" lastModifiedTime="2026-09-08T04:47:24.000Z" objectID="{36F877D0-E454-4AD6-99D8-57CBC790AB9B}{10}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="127.5136947631836" height="42.90299224853515"/><one:OEChildren><one:OE creationTime="2026-09-08T04:47:24.000Z" lastModifiedTime="2026-09-08T04:47:24.000Z" objectID="{319DDF27-1729-01DC-298C-94C10CFBD96D}{1}{B0}" alignment="left"><one:T><![CDATA[<span style='font-weight:bold;
3text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span
4style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family:
5Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri;
6font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through;
7font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family:
8Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family:
9Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size:
1011.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238;
11background:yellow;mso-highlight:yellow'>color</span><span style='font-family:
12Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family:
13Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size:
1411.0pt'><br>
15End</span>]]></one:T></one:OE><one:OE author="snow" authorInitials="S" lastModifiedBy="snow" lastModifiedByInitials="S" creationTime="2026-09-05T05:06:55.000Z" lastModifiedTime="2026-09-05T05:06:56.000Z" objectID="{36F877D0-E454-4AD6-99D8-57CBC790AB9B}{11}{B0}" alignment="left" quickStyleIndex="0" style="font-size:11.0pt;color:#1F4E79"><one:T><![CDATA[<span
16style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span
17style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span
18style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span
19style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed'
20lang=ar-SA>مرحبا</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/inheritance/native/read/page-002.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5636EACC-3A89-4C56-89F0-C5079EF9EEBF}{1}{B0}" name="Join both tags" dateTime="2026-09-08T09:07:16.000Z" lastModifiedTime="2026-09-08T09:41:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Fixture task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:07:17.000Z" lastModifiedTime="2026-09-08T09:07:17.000Z" objectID="{4F7D747B-16AA-4997-B42D-B850E1AE8A00}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Join both tags]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{4F7D747B-16AA-4997-B42D-B850E1AE8A00}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="134.0610046386719" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T09:07:17.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{4F7D747B-16AA-4997-B42D-B850E1AE8A00}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3lang=en-US>Left</span><span style='font-weight:bold' lang=en-US>Bold</span><span
4lang=en-US> </span><span style='font-style:italic' lang=en-US>italic</span><span
5lang=en-US> &#129408; é </span><span style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:07:17.000Z" lastModifiedTime="2026-09-08T09:07:17.000Z" objectID="{4F7D747B-16AA-4997-B42D-B850E1AE8A00}{54}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/inheritance/native/read/page-003.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C3CB4768-6167-4D86-804B-18FD4F4DC91F}{1}{B0}" name="Join empty left tag" dateTime="2026-09-08T09:07:15.000Z" lastModifiedTime="2026-09-08T09:41:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Fixture task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:07:16.000Z" lastModifiedTime="2026-09-08T09:07:16.000Z" objectID="{BE8327D2-656F-478D-9217-043D505C8351}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Join empty left tag]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{BE8327D2-656F-478D-9217-043D505C8351}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="116.9218902587891" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T09:07:16.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{BE8327D2-656F-478D-9217-043D505C8351}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:07:16.000Z" lastModifiedTime="2026-09-08T09:07:16.000Z" objectID="{BE8327D2-656F-478D-9217-043D505C8351}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/inheritance/native/read/page-004.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E2F56349-42EB-434E-BA90-FA9F132ED203}{1}{B0}" name="Join both children" dateTime="2026-09-08T09:07:17.000Z" lastModifiedTime="2026-09-08T09:41:46.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:07:17.000Z" lastModifiedTime="2026-09-08T09:07:17.000Z" objectID="{36A8146E-BE33-44BA-99D7-3D788B287387}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Join both children]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{36A8146E-BE33-44BA-99D7-3D788B287387}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="184.8066253662109" height="53.71086883544922"/><one:OEChildren><one:OE creationTime="2026-09-08T09:07:17.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{36A8146E-BE33-44BA-99D7-3D788B287387}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Left]]></one:T><one:OEChildren><one:OE lastModifiedBy="Rust join author" creationTime="2026-09-08T09:07:17.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{36A8146E-BE33-44BA-99D7-3D788B287387}{45}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3lang=en-US>Left child</span><span style='font-weight:bold' lang=en-US>Bold</span><span
4lang=en-US> </span><span style='font-style:italic' lang=en-US>italic</span><span
5lang=en-US> &#129408; é </span><span style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE creationTime="2026-09-08T09:07:17.000Z" lastModifiedTime="2026-09-08T09:07:17.000Z" objectID="{36A8146E-BE33-44BA-99D7-3D788B287387}{58}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Right child]]></one:T></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T09:07:17.000Z" lastModifiedTime="2026-09-08T09:07:17.000Z" objectID="{36A8146E-BE33-44BA-99D7-3D788B287387}{63}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/inheritance/native/read/page-005.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E3E250B5-308F-46E5-8BB3-BA59D089D3E4}{1}{B0}" name="Join inherited styles" dateTime="2026-09-08T09:07:14.000Z" lastModifiedTime="2026-09-08T09:41:46.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:07:15.000Z" lastModifiedTime="2026-09-08T09:07:15.000Z" objectID="{25B9CE5D-4971-483B-A8B6-0E7C2BBBB841}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Join inherited styles]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{25B9CE5D-4971-483B-A8B6-0E7C2BBBB841}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="160.521011352539" height="33.87984466552734"/><one:OEChildren><one:OE creationTime="2026-09-08T09:07:15.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{25B9CE5D-4971-483B-A8B6-0E7C2BBBB841}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span style='font-family:Calibri;
3font-size:11.0pt'>Left</span><span style='font-weight:bold;font-family:Georgia;
4font-size:18.0pt;color:#123456'>Right </span><span style='font-weight:bold;
5font-style:italic;font-family:Georgia;font-size:18.0pt;color:#123456'>italic</span>]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:07:15.000Z" lastModifiedTime="2026-09-08T09:07:15.000Z" objectID="{25B9CE5D-4971-483B-A8B6-0E7C2BBBB841}{54}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/inheritance/native/read/payloads.json created+1
......@@ -0,0 +1 @@
1../../../../before/read/payloads.json
\ No newline at end of file
corpus/paragraph-edit/rust-join/inheritance/native/run.json created+18
......@@ -0,0 +1,18 @@
1{
2 "notebook": "/Users/clo/dev/one/evidence/m10/paragraph-rust-join-01/inheritance/candidate",
3 "expected_pages": 6,
4 "author": null,
5 "author_timeout_seconds": 600,
6 "inspect": false,
7 "collect_notebook": true,
8 "base": {
9 "file": "win7-office-base.qcow2",
10 "format": "qcow2",
11 "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346",
12 "virtual_size": 68719476736
13 },
14 "scripts": {
15 "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331",
16 "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41"
17 }
18}
corpus/paragraph-edit/rust-join/inheritance/native/source.json created+8
......@@ -0,0 +1,8 @@
1[
2 {
3 "path": "synthetic.one",
4 "bytes": 56344,
5 "sha256": "50318b4b3d4f4850dd578518eea4cc7649d29cc0c84b0d6a9b41811f91e6e2e1",
6 "mtime_ns": 1788860404399203298
7 }
8]
corpus/paragraph-edit/rust-join/inheritance/native/teardown.json created+1
......@@ -0,0 +1 @@
1../../../join-edges/teardown.json
\ No newline at end of file
corpus/paragraph-edit/rust-join/split/candidate/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/rust-join/split/candidate/synthetic.one differ
corpus/paragraph-edit/rust-join/split/manifest.json created+110
......@@ -0,0 +1,110 @@
1[
2 {
3 "case": "Split middle",
4 "intent": {
5 "author": "Rust join author",
6 "left": "{DFE950EF-6BEB-0F5B-2403-7B74F1AE5002},44",
7 "right": "{0BA342F5-0690-087C-24FE-8EE6D81C2041},12"
8 },
9 "space": "{FE224563-B517-023E-0D75-9904B1952A26},1"
10 },
11 {
12 "case": "Split style boundary",
13 "intent": {
14 "author": "Rust join author",
15 "left": "{5FB484E2-1E3E-0A9D-1989-271A3EBBEC42},44",
16 "right": "{051BC1DE-5AE6-0D04-0CEF-9D05F53C8EAE},12"
17 },
18 "space": "{DED48A05-0DB1-0712-086D-09237C982341},1"
19 },
20 {
21 "case": "Split start",
22 "intent": {
23 "author": "Rust join author",
24 "left": "{012ED94B-43B3-079A-3050-B1CBE25C1C09},44",
25 "right": "{B230A510-9428-0CE7-1B89-7CA970F8F676},12"
26 },
27 "space": "{7919E933-0809-00D8-1754-A21684215860},1"
28 },
29 {
30 "case": "Split end",
31 "intent": {
32 "author": "Rust join author",
33 "left": "{DAF723A3-C2A3-0798-2EAB-34613166A794},44",
34 "right": "{601DAE0A-2A53-0C28-3077-1FE8FCB26FC2},12"
35 },
36 "space": "{927F2809-173C-036E-37B6-60A6568F7723},1"
37 },
38 {
39 "case": "Split empty",
40 "intent": {
41 "author": "Rust join author",
42 "left": "{0B467F31-D287-08CD-09F4-A6FBB2F5418F},45",
43 "right": "{EACB4A2C-2444-0A1F-03D6-68187F2FB345},12"
44 },
45 "space": "{E4D8C4CC-508B-04D2-36CE-C5B8C9D2FEC4},1"
46 },
47 {
48 "case": "Split parent",
49 "intent": {
50 "author": "Rust join author",
51 "left": "{3860EA13-D444-0598-35D6-CF96769C6A21},44",
52 "right": "{C84CA5DB-61A8-0F9B-3F91-D6E0C74536F7},12"
53 },
54 "space": "{7A363D4C-73C2-0ABE-0931-E6F62A90B95E},1"
55 },
56 {
57 "case": "Split child",
58 "intent": {
59 "author": "Rust join author",
60 "left": "{B587CBC6-0BCC-0A23-16F6-E1B0B3C5D061},49",
61 "right": "{DC240C63-FC8E-0267-0AFD-A53062C25702},12"
62 },
63 "space": "{3CC2C4DC-651A-07C9-2F7B-375C6663F3CA},1"
64 },
65 {
66 "case": "Split bullet",
67 "intent": {
68 "author": "Rust join author",
69 "left": "{8C65E427-477C-02ED-1807-2F10E277019D},44",
70 "right": "{28F3F5EC-E687-02CA-2CFD-E73A184B875F},12"
71 },
72 "space": "{942EF5E4-FFD1-0FE4-23D2-7429899577BD},1"
73 },
74 {
75 "case": "Split number",
76 "intent": {
77 "author": "Rust join author",
78 "left": "{D4ABBE4F-2165-0122-1F9F-ACE3FE92C703},44",
79 "right": "{DD3FA0E7-15A6-01C4-1B77-F85F779BCBC8},12"
80 },
81 "space": "{A4AE40FF-DD80-0003-2856-17D8295C1B91},1"
82 },
83 {
84 "case": "Split tag",
85 "intent": {
86 "author": "Rust join author",
87 "left": "{37AA5877-2980-0227-169F-8B1E8EE0795A},44",
88 "right": "{1480427A-E9ED-04EC-0409-0653AE11FF78},12"
89 },
90 "space": "{739DB38B-4239-0A7C-2652-4E525D219638},1"
91 },
92 {
93 "case": "Split cell",
94 "intent": {
95 "author": "Rust join author",
96 "left": "{F0832BC9-1578-0A96-08AF-99344644FFB8},50",
97 "right": "{1E353497-5C26-0459-3C0C-0E8D29ED8C5A},12"
98 },
99 "space": "{4432E3EC-5E5F-0A04-2108-E9A2C17BDBEC},1"
100 },
101 {
102 "case": "Split soft break",
103 "intent": {
104 "author": "Rust join author",
105 "left": "{88189BA1-2F19-0282-05ED-A0B83153F143},44",
106 "right": "{C1568E82-003F-016B-041A-CA304E1A5138},12"
107 },
108 "space": "{D8E15673-A45E-0914-291A-7767C3222F19},1"
109 }
110]
\ No newline at end of file
corpus/paragraph-edit/rust-join/split/native/notebook/Open Notebook.onetoc2 created
Binary files /dev/null and b/corpus/paragraph-edit/rust-join/split/native/notebook/Open Notebook.onetoc2 differ
corpus/paragraph-edit/rust-join/split/native/notebook/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/rust-join/split/native/notebook/synthetic.one differ
corpus/paragraph-edit/rust-join/split/native/read/environment.json created+7
......@@ -0,0 +1,7 @@
1{
2 "powershell": "5.1.14409.1005",
3 "schema": "xs2010",
4 "hostname": "ONE-M6-6A0771AD",
5 "cold": true,
6 "onenote": "14.0.4763.1000"
7}
corpus/paragraph-edit/rust-join/split/native/read/hierarchy.xml created+2
......@@ -0,0 +1,2 @@
1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{B7838A92-CCAA-444E-9E8A-6B8769364D70}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-08T09:41:44.000Z" color="#B49EDE"><one:Section name="synthetic" ID="{00520639-1FE0-0064-1EE3-45C1B4C10B45}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-08T09:41:44.000Z" color="#8AA8E4"><one:Page ID="{2C1BF5BF-962B-4375-9984-ED5048F9175A}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T09:41:44.000Z" pageLevel="1"/><one:Page ID="{B6218E28-6D25-4DF5-A9CA-FCABEEB9B660}{1}{B0}" name="Split middle" dateTime="2026-09-08T08:09:29.000Z" lastModifiedTime="2026-09-08T08:11:45.000Z" pageLevel="1"/><one:Page ID="{96D7414E-D583-48D9-ACD2-6C8C23B4BF07}{1}{B0}" name="Split style boundary" dateTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T08:11:47.000Z" pageLevel="1"/><one:Page ID="{311A2278-D03B-4F13-B3EB-C7B9DB0DC426}{1}{B0}" name="Split start" dateTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T08:11:49.000Z" pageLevel="1"/><one:Page ID="{DA7CE342-CF0E-4CA5-9309-050909A3EB65}{1}{B0}" name="Split end" dateTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T08:11:51.000Z" pageLevel="1"/><one:Page ID="{ACDB0F87-88B9-4B19-9271-A01796FE6282}{1}{B0}" name="Split empty" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:53.000Z" pageLevel="1"/><one:Page ID="{3235F607-ABF0-4575-AD8E-835975BC2518}{1}{B0}" name="Split parent" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:55.000Z" pageLevel="1"/><one:Page ID="{74C10F97-BD28-4802-8BC4-52F3394F6F8C}{1}{B0}" name="Split child" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:57.000Z" pageLevel="1"/><one:Page ID="{DC2D3EAF-27E3-402F-876D-1186D6B9EBFB}{1}{B0}" name="Split bullet" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:59.000Z" pageLevel="1"/><one:Page ID="{ECAD8BB4-05B2-4FC8-8CE9-7277767087D7}{1}{B0}" name="Split number" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:01.000Z" pageLevel="1"/><one:Page ID="{3B9E78C0-9A0B-45B7-82ED-2BFD020D0A7E}{1}{B0}" name="Split tag" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:03.000Z" pageLevel="1"/><one:Page ID="{0C3128A7-866D-45CF-85B7-8C0D9E5747AA}{1}{B0}" name="Split cell" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:05.000Z" pageLevel="1"/><one:Page ID="{90E29D38-7C6C-46DF-8DA5-12C89C0EB35F}{1}{B0}" name="Split soft break" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:07.000Z" pageLevel="1"/><one:Page ID="{3AC29ACF-40BB-4891-82DA-E37311CB08E1}{1}{B0}" name="Split before hyperlink" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:09.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/paragraph-edit/rust-join/split/native/read/page-000.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{0C3128A7-866D-45CF-85B7-8C0D9E5747AA}{1}{B0}" name="Split cell" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:05.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:04.000Z" objectID="{B880E082-CD4A-455D-AC10-FC9B196863FE}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Split cell]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{B880E082-CD4A-455D-AC10-FC9B196863FE}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="254.9588165283203" height="35.37543487548828"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{B880E082-CD4A-455D-AC10-FC9B196863FE}{40}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{B880E082-CD4A-455D-AC10-FC9B196863FE}{41}{B0}"><one:Columns><one:Column index="0" width="180.0" isLocked="true"/><one:Column index="1" width="66.82881927490234"/></one:Columns><one:Row objectID="{B880E082-CD4A-455D-AC10-FC9B196863FE}{42}{B0}" lastModifiedTime="2026-09-08T09:40:04.000Z"><one:Cell lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{B880E082-CD4A-455D-AC10-FC9B196863FE}{43}{B0}"><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{B880E082-CD4A-455D-AC10-FC9B196863FE}{46}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-08T08:12:04.000Z" objectID="{B880E082-CD4A-455D-AC10-FC9B196863FE}{54}{B0}" lastModifiedBy="virtual" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:04.000Z" objectID="{B880E082-CD4A-455D-AC10-FC9B196863FE}{57}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:04.000Z" objectID="{B880E082-CD4A-455D-AC10-FC9B196863FE}{62}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/split/native/read/page-001.xml created+20
......@@ -0,0 +1,20 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2C1BF5BF-962B-4375-9984-ED5048F9175A}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T09:41:44.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Rich author" lastModifiedBy="Rich author" lastModifiedTime="2026-09-08T04:47:24.000Z" objectID="{4BD75D03-DD5E-40C3-9676-618BB765F23B}{10}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="127.5136947631836" height="42.90299224853515"/><one:OEChildren><one:OE creationTime="2026-09-08T04:47:24.000Z" lastModifiedTime="2026-09-08T04:47:24.000Z" objectID="{4CB2F5F4-2E23-0BC9-2622-A2817C0E80CD}{1}{B0}" alignment="left"><one:T><![CDATA[<span style='font-weight:bold;
3text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span
4style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family:
5Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri;
6font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through;
7font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family:
8Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family:
9Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size:
1011.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238;
11background:yellow;mso-highlight:yellow'>color</span><span style='font-family:
12Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family:
13Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size:
1411.0pt'><br>
15End</span>]]></one:T></one:OE><one:OE author="snow" authorInitials="S" lastModifiedBy="snow" lastModifiedByInitials="S" creationTime="2026-09-05T05:06:55.000Z" lastModifiedTime="2026-09-05T05:06:56.000Z" objectID="{4BD75D03-DD5E-40C3-9676-618BB765F23B}{11}{B0}" alignment="left" quickStyleIndex="0" style="font-size:11.0pt;color:#1F4E79"><one:T><![CDATA[<span
16style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span
17style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span
18style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span
19style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed'
20lang=ar-SA>مرحبا</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/split/native/read/page-002.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{311A2278-D03B-4F13-B3EB-C7B9DB0DC426}{1}{B0}" name="Split start" dateTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T08:11:49.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T08:11:48.000Z" objectID="{492D1200-9B81-4851-94EF-D464BD70804F}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Split start]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{492D1200-9B81-4851-94EF-D464BD70804F}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="116.921875" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{492D1200-9B81-4851-94EF-D464BD70804F}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T08:11:48.000Z" objectID="{492D1200-9B81-4851-94EF-D464BD70804F}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/split/native/read/page-003.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{3235F607-ABF0-4575-AD8E-835975BC2518}{1}{B0}" name="Split parent" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:55.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:54.000Z" objectID="{70632158-0C76-4A53-9169-AA3929B0F667}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Split parent]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{70632158-0C76-4A53-9169-AA3929B0F667}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="135.3173980712891" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{70632158-0C76-4A53-9169-AA3929B0F667}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T><one:OEChildren><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:54.000Z" objectID="{70632158-0C76-4A53-9169-AA3929B0F667}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved child]]></one:T></one:OE></one:OEChildren></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:54.000Z" objectID="{70632158-0C76-4A53-9169-AA3929B0F667}{53}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/split/native/read/page-004.xml created+3
......@@ -0,0 +1,3 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{3AC29ACF-40BB-4891-82DA-E37311CB08E1}{1}{B0}" name="Split before hyperlink" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:09.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:08.000Z" objectID="{1C30D2AC-82F5-45BF-B455-5C48F6664CC0}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Split before hyperlink]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T08:12:09.000Z" objectID="{1C30D2AC-82F5-45BF-B455-5C48F6664CC0}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="119.1777801513672" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:09.000Z" objectID="{1C30D2AC-82F5-45BF-B455-5C48F6664CC0}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Be]]></one:T></one:OE><one:OE creationTime="2026-09-08T08:12:09.000Z" lastModifiedTime="2026-09-08T08:12:09.000Z" objectID="{66B1C2E6-94CC-43F9-80CF-15B172F28E66}{11}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[fore <a
3href="https://example.invalid/paragraph">Link label</a> tail]]></one:T></one:OE><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:08.000Z" objectID="{1C30D2AC-82F5-45BF-B455-5C48F6664CC0}{47}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/split/native/read/page-005.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{3B9E78C0-9A0B-45B7-82ED-2BFD020D0A7E}{1}{B0}" name="Split tag" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:03.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Fixture task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:02.000Z" objectID="{7FA9933C-F1B2-4DEC-B220-EEB1D1CCE51C}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Split tag]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{7FA9933C-F1B2-4DEC-B220-EEB1D1CCE51C}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="116.9218902587891" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{7FA9933C-F1B2-4DEC-B220-EEB1D1CCE51C}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:02.000Z" objectID="{7FA9933C-F1B2-4DEC-B220-EEB1D1CCE51C}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/split/native/read/page-006.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{74C10F97-BD28-4802-8BC4-52F3394F6F8C}{1}{B0}" name="Split child" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:57.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:56.000Z" objectID="{FD84008D-D3FE-45E8-B249-841FECE94C27}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Split child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{FD84008D-D3FE-45E8-B249-841FECE94C27}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="143.921875" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{FD84008D-D3FE-45E8-B249-841FECE94C27}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved parent]]></one:T><one:OEChildren><one:OE lastModifiedBy="Rust join author" creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{FD84008D-D3FE-45E8-B249-841FECE94C27}{45}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:56.000Z" objectID="{FD84008D-D3FE-45E8-B249-841FECE94C27}{53}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/split/native/read/page-007.xml created+3
......@@ -0,0 +1,3 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{90E29D38-7C6C-46DF-8DA5-12C89C0EB35F}{1}{B0}" name="Split soft break" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:07.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:06.000Z" objectID="{C01B50EA-F72B-4D49-A152-C5176E7F6D05}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Split soft break]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{C01B50EA-F72B-4D49-A152-C5176E7F6D05}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="487.125" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T08:12:07.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{C01B50EA-F72B-4D49-A152-C5176E7F6D05}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Bold<br>
3soft break]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:06.000Z" objectID="{C01B50EA-F72B-4D49-A152-C5176E7F6D05}{45}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/split/native/read/page-008.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{96D7414E-D583-48D9-ACD2-6C8C23B4BF07}{1}{B0}" name="Split style boundary" dateTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T08:11:47.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T08:11:46.000Z" objectID="{17B74FA9-C60C-4556-BD36-42B561977004}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Split style boundary]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{17B74FA9-C60C-4556-BD36-42B561977004}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="116.921875" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{17B74FA9-C60C-4556-BD36-42B561977004}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T08:11:46.000Z" objectID="{17B74FA9-C60C-4556-BD36-42B561977004}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/split/native/read/page-009.xml created+2
......@@ -0,0 +1,2 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{ACDB0F87-88B9-4B19-9271-A01796FE6282}{1}{B0}" name="Split empty" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:53.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:52.000Z" objectID="{4345B47A-0AB5-4706-AD4B-C354EDD9DDC9}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Split empty]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{4345B47A-0AB5-4706-AD4B-C354EDD9DDC9}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="115.6704635620117" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{4345B47A-0AB5-4706-AD4B-C354EDD9DDC9}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:52.000Z" objectID="{4345B47A-0AB5-4706-AD4B-C354EDD9DDC9}{46}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/split/native/read/page-010.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B6218E28-6D25-4DF5-A9CA-FCABEEB9B660}{1}{B0}" name="Split middle" dateTime="2026-09-08T08:09:29.000Z" lastModifiedTime="2026-09-08T08:11:45.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T08:09:32.000Z" objectID="{97EA9BA4-B3D9-4090-80BC-1EDBAE82CC44}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Split middle]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{97EA9BA4-B3D9-4090-80BC-1EDBAE82CC44}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="116.921875" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:30.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{97EA9BA4-B3D9-4090-80BC-1EDBAE82CC44}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T08:09:32.000Z" objectID="{97EA9BA4-B3D9-4090-80BC-1EDBAE82CC44}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/split/native/read/page-011.xml created+6
......@@ -0,0 +1,6 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{DA7CE342-CF0E-4CA5-9309-050909A3EB65}{1}{B0}" name="Split end" dateTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T08:11:51.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:50.000Z" objectID="{92F4E8E8-1A91-4853-8A14-51CE6E4A3BD2}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Split end]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{92F4E8E8-1A91-4853-8A14-51CE6E4A3BD2}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="116.921875" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{92F4E8E8-1A91-4853-8A14-51CE6E4A3BD2}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tai</span><span style='color:#123456'
6lang=en-US>l</span>]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:50.000Z" objectID="{92F4E8E8-1A91-4853-8A14-51CE6E4A3BD2}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/split/native/read/page-012.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{DC2D3EAF-27E3-402F-876D-1186D6B9EBFB}{1}{B0}" name="Split bullet" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:59.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:58.000Z" objectID="{C4662F6C-9F4E-4D26-BCB8-4ABFBD5B9DDB}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Split bullet]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{C4662F6C-9F4E-4D26-BCB8-4ABFBD5B9DDB}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="135.3173980712891" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{C4662F6C-9F4E-4D26-BCB8-4ABFBD5B9DDB}{40}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T><one:OEChildren><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:58.000Z" objectID="{C4662F6C-9F4E-4D26-BCB8-4ABFBD5B9DDB}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved child]]></one:T></one:OE></one:OEChildren></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:58.000Z" objectID="{C4662F6C-9F4E-4D26-BCB8-4ABFBD5B9DDB}{54}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/split/native/read/page-013.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{ECAD8BB4-05B2-4FC8-8CE9-7277767087D7}{1}{B0}" name="Split number" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:01.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:00.000Z" objectID="{9CA87504-F957-4EE9-BB20-C94CA1BE5B45}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Split number]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{9CA87504-F957-4EE9-BB20-C94CA1BE5B45}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="116.9218902587891" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{9CA87504-F957-4EE9-BB20-C94CA1BE5B45}{40}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." restartNumberingAt="3" fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:00.000Z" objectID="{9CA87504-F957-4EE9-BB20-C94CA1BE5B45}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/split/native/read/payloads.json created+1
......@@ -0,0 +1 @@
1../../../../before/read/payloads.json
\ No newline at end of file
corpus/paragraph-edit/rust-join/split/native/run.json created+18
......@@ -0,0 +1,18 @@
1{
2 "notebook": "/Users/clo/dev/one/evidence/m10/paragraph-rust-join-01/split/candidate",
3 "expected_pages": 14,
4 "author": null,
5 "author_timeout_seconds": 600,
6 "inspect": false,
7 "collect_notebook": true,
8 "base": {
9 "file": "win7-office-base.qcow2",
10 "format": "qcow2",
11 "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346",
12 "virtual_size": 68719476736
13 },
14 "scripts": {
15 "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331",
16 "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41"
17 }
18}
corpus/paragraph-edit/rust-join/split/native/source.json created+8
......@@ -0,0 +1,8 @@
1[
2 {
3 "path": "synthetic.one",
4 "bytes": 139752,
5 "sha256": "6677af274e17de1ac194b2b18bc96d3eb33c04968881c272383f7cc47b10c5de",
6 "mtime_ns": 1788860404345967341
7 }
8]
corpus/paragraph-edit/rust-join/split/native/teardown.json created+1
......@@ -0,0 +1 @@
1../../../join-edges/teardown.json
\ No newline at end of file
corpus/paragraph-edit/rust-join/tags/candidate/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/rust-join/tags/candidate/synthetic.one differ
corpus/paragraph-edit/rust-join/tags/manifest.json created+29
......@@ -0,0 +1,29 @@
1[
2 {
3 "case": "Join empty left right tag",
4 "intent": {
5 "author": "Rust join author",
6 "left": "{0D11242E-6A38-0F74-261A-A6FC444FA262},45",
7 "right": "{0D11242E-6A38-0F74-261A-A6FC444FA262},50"
8 },
9 "space": "{FC64EF0A-5CCB-0557-20E0-6C847015B691},1"
10 },
11 {
12 "case": "Join empty left two tags",
13 "intent": {
14 "author": "Rust join author",
15 "left": "{84061027-77E3-03F1-3E0D-A8F744B19C18},45",
16 "right": "{84061027-77E3-03F1-3E0D-A8F744B19C18},51"
17 },
18 "space": "{A05CB25D-C95D-043F-3B9A-CE333AD171AD},1"
19 },
20 {
21 "case": "Join different tags",
22 "intent": {
23 "author": "Rust join author",
24 "left": "{4FBF7D4A-6F4D-048E-0913-5BD9A184C25B},44",
25 "right": "{4FBF7D4A-6F4D-048E-0913-5BD9A184C25B},50"
26 },
27 "space": "{6F0F6556-17D3-03AF-20EF-C465A856A153},1"
28 }
29]
\ No newline at end of file
corpus/paragraph-edit/rust-join/tags/native/notebook/Open Notebook.onetoc2 created
Binary files /dev/null and b/corpus/paragraph-edit/rust-join/tags/native/notebook/Open Notebook.onetoc2 differ
corpus/paragraph-edit/rust-join/tags/native/notebook/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/rust-join/tags/native/notebook/synthetic.one differ
corpus/paragraph-edit/rust-join/tags/native/read/environment.json created+7
......@@ -0,0 +1,7 @@
1{
2 "powershell": "5.1.14409.1005",
3 "schema": "xs2010",
4 "hostname": "ONE-M6-5FECAC90",
5 "cold": true,
6 "onenote": "14.0.4763.1000"
7}
corpus/paragraph-edit/rust-join/tags/native/read/hierarchy.xml created+2
......@@ -0,0 +1,2 @@
1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{37CBDE5C-07A2-48D1-98C7-0E523CC21B7B}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-08T09:41:49.000Z" color="#B49EDE"><one:Section name="synthetic" ID="{7BFF2432-8C29-0E59-15D1-59F0215B7D6A}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-08T09:41:49.000Z" color="#8AA8E4"><one:Page ID="{57B6D7B4-05E2-4D48-92B6-F161DD636175}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T09:41:49.000Z" pageLevel="1"/><one:Page ID="{CFCA064A-1730-44A1-8F6D-151ABAA35CF8}{1}{B0}" name="Join empty left right tag" dateTime="2026-09-08T09:33:01.000Z" lastModifiedTime="2026-09-08T09:41:49.000Z" pageLevel="1"/><one:Page ID="{93F25B1D-82A6-45C9-9417-B7ADF0679BC4}{1}{B0}" name="Join empty left two tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:41:49.000Z" pageLevel="1"/><one:Page ID="{5CA18C16-5C28-4259-8F62-BDFB62E04B3A}{1}{B0}" name="Join different tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:41:49.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/paragraph-edit/rust-join/tags/native/read/page-000.xml created+20
......@@ -0,0 +1,20 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{57B6D7B4-05E2-4D48-92B6-F161DD636175}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T09:41:49.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Rich author" lastModifiedBy="Rich author" lastModifiedTime="2026-09-08T04:47:24.000Z" objectID="{307A7F08-4E97-4EFE-9D44-7DBA22FF8414}{10}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="127.5136947631836" height="42.90299224853515"/><one:OEChildren><one:OE creationTime="2026-09-08T04:47:24.000Z" lastModifiedTime="2026-09-08T04:47:24.000Z" objectID="{371FD7FF-BDEA-05F4-2D10-BEB0E994F6E2}{1}{B0}" alignment="left"><one:T><![CDATA[<span style='font-weight:bold;
3text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span
4style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family:
5Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri;
6font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through;
7font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family:
8Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family:
9Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size:
1011.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238;
11background:yellow;mso-highlight:yellow'>color</span><span style='font-family:
12Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family:
13Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size:
1411.0pt'><br>
15End</span>]]></one:T></one:OE><one:OE author="snow" authorInitials="S" lastModifiedBy="snow" lastModifiedByInitials="S" creationTime="2026-09-05T05:06:55.000Z" lastModifiedTime="2026-09-05T05:06:56.000Z" objectID="{307A7F08-4E97-4EFE-9D44-7DBA22FF8414}{11}{B0}" alignment="left" quickStyleIndex="0" style="font-size:11.0pt;color:#1F4E79"><one:T><![CDATA[<span
16style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span
17style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span
18style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span
19style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed'
20lang=ar-SA>مرحبا</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/tags/native/read/page-001.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5CA18C16-5C28-4259-8F62-BDFB62E04B3A}{1}{B0}" name="Join different tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:41:49.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Fixture task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:33:04.000Z" objectID="{7C11940A-24B6-4578-A69E-22476B322832}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Join different tags]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{7C11940A-24B6-4578-A69E-22476B322832}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="134.0610046386719" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{7C11940A-24B6-4578-A69E-22476B322832}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3lang=en-US>Left</span><span style='font-weight:bold' lang=en-US>Bold</span><span
4lang=en-US> </span><span style='font-style:italic' lang=en-US>italic</span><span
5lang=en-US> &#129408; é </span><span style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:33:04.000Z" objectID="{7C11940A-24B6-4578-A69E-22476B322832}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/tags/native/read/page-002.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{93F25B1D-82A6-45C9-9417-B7ADF0679BC4}{1}{B0}" name="Join empty left two tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:41:49.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Fixture task"/><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:33:04.000Z" objectID="{B7A8F967-3C18-4207-9180-D1698E077671}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Join empty left two tags]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{B7A8F967-3C18-4207-9180-D1698E077671}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="116.9218902587891" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{B7A8F967-3C18-4207-9180-D1698E077671}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T09:33:04.000Z" objectID="{B7A8F967-3C18-4207-9180-D1698E077671}{56}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/tags/native/read/page-003.xml created+5
......@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CFCA064A-1730-44A1-8F6D-151ABAA35CF8}{1}{B0}" name="Join empty left right tag" dateTime="2026-09-08T09:33:01.000Z" lastModifiedTime="2026-09-08T09:41:49.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:33:03.000Z" lastModifiedTime="2026-09-08T09:33:03.000Z" objectID="{3EBFCD6E-21C3-4E82-8997-DF628EF9480B}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Join empty left right tag]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="Rust join author" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{3EBFCD6E-21C3-4E82-8997-DF628EF9480B}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="116.921875" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T09:33:03.000Z" lastModifiedTime="2026-09-08T09:40:04.000Z" objectID="{3EBFCD6E-21C3-4E82-8997-DF628EF9480B}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span
4style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
5style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T09:33:03.000Z" lastModifiedTime="2026-09-08T09:33:03.000Z" objectID="{3EBFCD6E-21C3-4E82-8997-DF628EF9480B}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/rust-join/tags/native/read/payloads.json created+1
......@@ -0,0 +1 @@
1../../../../before/read/payloads.json
\ No newline at end of file
corpus/paragraph-edit/rust-join/tags/native/run.json created+18
......@@ -0,0 +1,18 @@
1{
2 "notebook": "/Users/clo/dev/one/evidence/m10/paragraph-rust-join-01/tags/candidate",
3 "expected_pages": 4,
4 "author": null,
5 "author_timeout_seconds": 600,
6 "inspect": false,
7 "collect_notebook": true,
8 "base": {
9 "file": "win7-office-base.qcow2",
10 "format": "qcow2",
11 "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346",
12 "virtual_size": 68719476736
13 },
14 "scripts": {
15 "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331",
16 "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41"
17 }
18}
corpus/paragraph-edit/rust-join/tags/native/source.json created+8
......@@ -0,0 +1,8 @@
1[
2 {
3 "path": "synthetic.one",
4 "bytes": 41808,
5 "sha256": "04f5dd05df411fdb228ff7cb80e02ca24744d40b6f0bd485b688aa8c0d6e8ac3",
6 "mtime_ns": 1788860404422342545
7 }
8]
corpus/paragraph-edit/rust-join/tags/native/teardown.json created+1
......@@ -0,0 +1 @@
1../../../join-edges/teardown.json
\ No newline at end of file
crates/onestore/README.md+9
......@@ -50,6 +50,7 @@ harness also accepts `--client-profile release`.
5050| `replace_text`, `commit_text`, `commit_file_text` | Replace a UTF-16 range across ordinary text runs; publish text, run boundaries and modification time together |
5151| `Insertion`, `PreparedEdit::insert` | Insert paragraphs into editable containers or positioned outlines into a page, retaining intent identities across rebases |
5252| `ParagraphSplit`, `PreparedEdit::split` | Split ordinary text at a UTF-16 scalar boundary, retaining the original left identities and moving children to the right |
53| `ParagraphJoin`, `PreparedEdit::join` | Join adjacent ordinary text while preserving inherited character styles and native text-identity rules |
5354| `TextAttribute`, `PreparedEdit::format` | Change character formatting over a UTF-16 range while sharing immutable styles; preserve unselected runs |
5455| `PreparedEdit::commit`, `PreparedEdit::commit_file` | Publish the exact prepared image under caller-held exclusion or the conservative filesystem adapter |
5556| `read_file` | Read a snapshot under whole-file exclusion |
......@@ -85,6 +86,14 @@ the complete child graph and title metadata; repeating an existing identity requ
8586reconciliation. Title containers, generated fields, recording-linked text and
8687associated run metadata are rejected before I/O. Native split controls and subsequent
8788typing checks reside in [the paragraph corpus](../../corpus/paragraph-edit/README.md).
89Joins retain the left paragraph. Nonempty left text keeps its identity; empty left
90text adopts the right text identity. **The left tags win: right-side tags are removed
91from active text even when the left text is empty.** History retains the original
92objects. Select the preceding leaf text; where that leaf is deeper than the right
93paragraph, right children move to its ancestor at the right paragraph's level.
94Ambiguous ancestry, unsupported indentation transitions and unknown implicit
95font/language inheritance reject before I/O. This is a logical join, so keyboard
96actions that only change list or indentation state remain separate operations.
8897Generated fields, protected targets and unsupported run-data boundary changes are
8998rejected before publication. Local caches expose text, insertion and formatting edits;
9099the [document-writer acceptance](../../evidence/MILESTONE9.md#document-writer-and-offline-acceptance)
crates/onestore/src/commit.rs+12
......@@ -183,6 +183,18 @@ pub struct PreparedEdit<'a> {
183183}
184184
185185impl<'a> PreparedEdit<'a> {
186 /// Joins adjacent ordinary paragraphs with native left-tag and text-identity semantics.
187 pub fn join(
188 source: &'a [u8],
189 space: ExGuid,
190 join: &crate::ParagraphJoin,
191 ) -> Result<Self, crate::Error> {
192 Ok(Self {
193 source,
194 written: join.apply(source, space)?,
195 })
196 }
197
186198 /// Splits a paragraph and updates its children, lists, tags and title metadata atomically.
187199 /// Fields and associated run metadata are rejected before I/O.
188200 pub fn split(
crates/onestore/src/lib.rs+1-1
......@@ -32,7 +32,7 @@ pub use files::FileDataReference;
3232pub use formatting::TextAttribute;
3333pub use insertion::Insertion;
3434pub use objects::{Object, ObjectData, ObjectReferences, ResolvedRevision};
35pub use paragraph::ParagraphSplit;
35pub use paragraph::{ParagraphJoin, ParagraphSplit};
3636pub use properties::{IdStream, Property, PropertySets, Value};
3737pub use revisions::{ExGuid, ObjectSpace, Revision, RevisionIndex};
3838pub use snapshot::{read_snapshot, read_storage_snapshot};
crates/onestore/src/paragraph.rs+475-76
......@@ -76,7 +76,7 @@ impl ParagraphSplit {
7676 .into_iter()
7777 .filter_map(|(sid, page)| (sid == space).then_some(page))
7878 .collect();
79 let [page] = pages.as_slice() else {
79 let [_] = pages.as_slice() else {
8080 return Err(invalid("Splitting requires a single active page"));
8181 };
8282 let semantic = document
......@@ -84,7 +84,7 @@ impl ParagraphSplit {
8484 .remove(&space)
8585 .ok_or_else(|| invalid("The active page is unavailable"))?;
8686 let rid = semantic.contexts[&ExGuid::default()];
87 let mut view = semantic
87 let view = semantic
8888 .revisions
8989 .into_iter()
9090 .find_map(|(id, view)| (id == rid).then_some(view))
......@@ -134,48 +134,9 @@ impl ParagraphSplit {
134134 }
135135 pending.extend(parents.get(&id).into_iter().flatten().copied());
136136 }
137 let node = &view.nodes[&self.text];
138 let Kind::RichText {
139 text,
140 runs,
141 boilerplate,
142 ..
143 } = &node.kind
144 else {
145 return Err(invalid("Select ordinary paragraph text"));
146 };
147 if *boilerplate || !node.media_ids.is_empty() || node.media_time_ms.is_some() {
148 return Err(invalid(
149 "Generated or recording-linked text cannot be split",
150 ));
151 }
152 for run in view.text_runs(self.text)? {
153 if [
154 run.format.hidden,
155 run.format.hyperlink,
156 run.format.math,
157 run.format.embedded_object,
158 ]
159 .contains(&Some(true))
160 || run.text.contains(['\u{fffc}', '\u{fddf}'])
161 {
162 return Err(invalid(
163 "This paragraph contains a field or embedded object that cannot be split",
164 ));
165 }
166 }
167137 let raw = index.resolve(space, rid)?;
168 let ObjectData::Properties(blob) = raw.objects[&self.text].data else {
169 unreachable!()
170 };
171 if PropertySets::parse(blob)?.sets[0]
172 .iter()
173 .any(|p| matches!(p.id, 0x40003499 | 0x24003458))
174 {
175 return Err(invalid(
176 "This paragraph contains run metadata that cannot be split",
177 ));
178 }
138 let (text, runs) = ordinary_text(&view, &raw, self.text)?;
139 let node = &view.nodes[&self.text];
179140 let length = u32::try_from(text.encode_utf16().count())
180141 .map_err(|_| invalid("Paragraph exceeds the UTF-16 offset range"))?;
181142 if self.offset > length || lists.len() > 251 {
......@@ -286,39 +247,7 @@ impl ParagraphSplit {
286247 };
287248 object.set(&[(0x14001d7a, &modified)])?;
288249 }
289 for (id, object) in &changed {
290 view.nodes.insert(
291 *id,
292 Element::parse(
293 &Object {
294 jcid: object.jcid,
295 reference_count: 0,
296 data: ObjectData::Properties(&object.bytes),
297 global_ids: Arc::clone(&object.global_ids),
298 },
299 &store,
300 )?,
301 );
302 }
303 let title = page_title(&view, &pages, None)?;
304 drop(view);
305 if let Some((_, automatic, title)) = title {
306 let metadata = raw
307 .roots
308 .get(&2)
309 .ok_or_else(|| invalid("Page title metadata is unavailable"))?;
310 if raw.objects[metadata].jcid != 0x20030 {
311 return Err(invalid("Page title metadata is unavailable"));
312 }
313 let title = string(&title);
314 let mut object = PropertyObject::from_object(&raw.objects[metadata])?;
315 object.set(&[(0x1c001cf3, &title)])?;
316 changed.insert(*metadata, object);
317 changed
318 .get_mut(page)
319 .unwrap()
320 .set(&[(0x1c001d3c, if automatic { &title } else { &[0, 0] })])?;
321 }
250 update_title(&store, &raw, view, &pages, &mut changed)?;
322251 write_revision(source, space, |_| Ok(changed))
323252 }
324253}
......@@ -381,3 +310,473 @@ fn fragment(
381310 ])?;
382311 Ok(object)
383312}
313
314fn ordinary_text<'a>(
315 view: &'a crate::document::Revision<'_>,
316 raw: &crate::ResolvedRevision<'_>,
317 id: ExGuid,
318) -> Result<(&'a str, &'a [crate::document::TextRun]), Error> {
319 let node = &view.nodes[&id];
320 let Kind::RichText {
321 text,
322 runs,
323 boilerplate,
324 ..
325 } = &node.kind
326 else {
327 return Err(invalid("Select ordinary paragraph text"));
328 };
329 if *boilerplate || !node.media_ids.is_empty() || node.media_time_ms.is_some() {
330 return Err(invalid(
331 "Generated or recording-linked text cannot be split or joined",
332 ));
333 }
334 for run in view.text_runs(id)? {
335 if [
336 run.format.hidden,
337 run.format.hyperlink,
338 run.format.math,
339 run.format.embedded_object,
340 ]
341 .contains(&Some(true))
342 || run.text.contains(['\u{fffc}', '\u{fddf}'])
343 {
344 return Err(invalid(
345 "This paragraph contains a field or embedded object that cannot be split or joined",
346 ));
347 }
348 }
349 let ObjectData::Properties(blob) = raw.objects[&id].data else {
350 unreachable!()
351 };
352 if PropertySets::parse(blob)?.sets[0]
353 .iter()
354 .any(|p| matches!(p.id, 0x40003499 | 0x24003458))
355 {
356 return Err(invalid(
357 "This paragraph contains run metadata that cannot be split or joined",
358 ));
359 }
360 Ok((text, runs))
361}
362
363fn update_title(
364 store: &Store<'_>,
365 raw: &crate::ResolvedRevision<'_>,
366 view: crate::document::Revision<'_>,
367 pages: &[ExGuid],
368 changed: &mut BTreeMap<ExGuid, PropertyObject>,
369) -> Result<(), Error> {
370 // Shorten the moved view's lifetime to the changed property buffers.
371 let mut view = view;
372 for (id, object) in changed.iter() {
373 view.nodes.insert(
374 *id,
375 Element::parse(
376 &Object {
377 jcid: object.jcid,
378 reference_count: 0,
379 data: ObjectData::Properties(&object.bytes),
380 global_ids: Arc::clone(&object.global_ids),
381 },
382 store,
383 )?,
384 );
385 }
386 let title = page_title(&view, pages, None)?;
387 drop(view);
388 if let Some((_, automatic, title)) = title {
389 let metadata = raw
390 .roots
391 .get(&2)
392 .ok_or_else(|| invalid("Page title metadata is unavailable"))?;
393 if raw.objects[metadata].jcid != 0x20030 {
394 return Err(invalid("Page title metadata is unavailable"));
395 }
396 let title = string(&title);
397 let mut object = PropertyObject::from_object(&raw.objects[metadata])?;
398 object.set(&[(0x1c001cf3, &title)])?;
399 changed.insert(*metadata, object);
400 changed
401 .get_mut(&pages[0])
402 .unwrap()
403 .set(&[(0x1c001d3c, if automatic { &title } else { &[0, 0] })])?;
404 }
405 Ok(())
406}
407
408/// Joins adjacent ordinary text in one outline or table cell.
409/// The left paragraph survives. Empty left text adopts the right text identity;
410/// otherwise the left text survives. The left paragraph's tags are retained.
411/// Right-side tags are removed from active text, including when the left text is empty.
412#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
413#[serde(deny_unknown_fields)]
414pub struct ParagraphJoin {
415 left: ExGuid,
416 right: ExGuid,
417 author: String,
418}
419
420impl ParagraphJoin {
421 /// Select the preceding leaf paragraph's text and the following paragraph's text.
422 pub fn new(left: ExGuid, right: ExGuid, author: &str) -> Result<Self, Error> {
423 if left == right || left.guid == [0; 16] || right.guid == [0; 16] || author.contains('\0') {
424 return Err(invalid(
425 "Choose two distinct text objects and an author name without NUL",
426 ));
427 }
428 Ok(Self {
429 left,
430 right,
431 author: author.to_owned(),
432 })
433 }
434
435 pub(crate) fn apply(&self, source: &[u8], space: ExGuid) -> Result<Vec<u8>, Error> {
436 if self.left == self.right
437 || self.left.guid == [0; 16]
438 || self.right.guid == [0; 16]
439 || self.author.contains('\0')
440 {
441 return Err(invalid(
442 "Choose two distinct text objects and an author name without NUL",
443 ));
444 }
445 let store = Store::parse(source)?;
446 let index = RevisionIndex::parse(&store)?;
447 index.validate_current()?;
448 let mut document = Document::parse(&index)?;
449 let pages: Vec<_> = document
450 .pages()?
451 .into_iter()
452 .filter_map(|(sid, page)| (sid == space).then_some(page))
453 .collect();
454 if pages.len() != 1 {
455 return Err(invalid("Joining requires a single active page"));
456 }
457 let semantic = document
458 .spaces
459 .remove(&space)
460 .ok_or_else(|| invalid("The active page is unavailable"))?;
461 let rid = semantic.contexts[&ExGuid::default()];
462 let view = semantic
463 .revisions
464 .into_iter()
465 .find_map(|(id, view)| (id == rid).then_some(view))
466 .unwrap();
467 let parents = editable_parents(&view, &pages, self.left)?;
468 editable_parents(&view, &pages, self.right)?;
469 let parent = |id| -> Result<ExGuid, Error> {
470 let [parent] = parents.get(&id).map(Vec::as_slice).unwrap_or_default() else {
471 return Err(invalid("Select text with a unique editable parent"));
472 };
473 Ok(*parent)
474 };
475 let left = parent(self.left)?;
476 let right = parent(self.right)?;
477 for (paragraph, text) in [(left, self.left), (right, self.right)] {
478 if !matches!(view.nodes[&paragraph].kind, Kind::Paragraph { .. })
479 || view.nodes[&paragraph].content != [text]
480 {
481 return Err(invalid("Select ordinary paragraph text"));
482 }
483 let mut at = paragraph;
484 while at != pages[0] {
485 if matches!(view.nodes[&at].kind, Kind::Title) {
486 return Err(invalid(
487 "Title containers cannot be joined with ordinary paragraphs",
488 ));
489 }
490 at = parent(at)?;
491 }
492 }
493 if !view.nodes[&left].children.is_empty() {
494 return Err(invalid("Select the preceding paragraph's last descendant"));
495 }
496 let right_parent = parent(right)?;
497 if !matches!(
498 view.nodes[&right_parent].kind,
499 Kind::Outline { .. } | Kind::OutlineGroup | Kind::Paragraph { .. } | Kind::Cell { .. }
500 ) {
501 return Err(invalid("Select paragraphs in one outline or table cell"));
502 }
503 let mut stem = left;
504 while parent(stem)? != right_parent {
505 let previous = stem;
506 stem = parent(stem)?;
507 if !matches!(
508 view.nodes[&stem].kind,
509 Kind::Paragraph { .. } | Kind::OutlineGroup
510 ) || view.nodes[&stem].children.last() != Some(&previous)
511 {
512 return Err(invalid(
513 "Select adjacent text within one outline or table cell",
514 ));
515 }
516 }
517 let siblings = &view.nodes[&right_parent].children;
518 if !siblings.windows(2).any(|pair| pair == [stem, right]) {
519 return Err(invalid("Select adjacent paragraph text in document order"));
520 }
521 let children = &view.nodes[&right].children;
522 if !children.is_empty()
523 && !view.nodes[&stem].children.is_empty()
524 && view.nodes[&stem].child_level != view.nodes[&right].child_level
525 {
526 return Err(invalid(
527 "Joining these child indentation levels requires a hierarchy edit",
528 ));
529 }
530 let raw = index.resolve(space, rid)?;
531 let (a, a_runs) = ordinary_text(&view, &raw, self.left)?;
532 let (b, b_runs) = ordinary_text(&view, &raw, self.right)?;
533 let length = u32::try_from(a.encode_utf16().count())
534 .map_err(|_| invalid("Paragraph exceeds the UTF-16 offset range"))?;
535 let right_length = u32::try_from(b.encode_utf16().count())
536 .map_err(|_| invalid("Paragraph exceeds the UTF-16 offset range"))?;
537 length
538 .checked_add(right_length)
539 .ok_or_else(|| invalid("Joined text exceeds the UTF-16 offset range"))?;
540 let modified = current_timestamps()?.0.to_le_bytes();
541 let mut changed = BTreeMap::new();
542 let (survivor, mut text) = if a.is_empty() {
543 let mut target = PropertyObject::from_object(&raw.objects[&self.right])?;
544 target.copy_property(
545 &PropertyObject::from_object(&raw.objects[&self.left])?,
546 0x40003489,
547 )?;
548 (self.right, target)
549 } else {
550 let mut target = PropertyObject::from_object(&raw.objects[&self.left])?;
551 let mut styles = BTreeMap::new();
552 let mut ends = Vec::new();
553 let mut references = Vec::new();
554 let mut empty_style = None;
555 for run in a_runs.iter().filter(|run| run.start < run.end) {
556 let id = if let Some(id) = run.format {
557 id
558 } else if let Some(id) = empty_style {
559 id
560 } else {
561 let id = ExGuid {
562 guid: fresh_guid()?,
563 n: 1,
564 };
565 changed.insert(
566 id,
567 PropertyObject {
568 jcid: 0x12004d,
569 bytes: properties(&[])?,
570 global_ids: Arc::new(BTreeMap::from([(0, id.guid)])),
571 },
572 );
573 empty_style = Some(id);
574 id
575 };
576 references.extend_from_slice(&target.reference(id)?);
577 ends.extend_from_slice(&run.end.to_le_bytes());
578 }
579 let left_base = character_properties(&view, &raw, self.left)?;
580 let right_base = character_properties(&view, &raw, self.right)?;
581 for (i, run) in b_runs.iter().enumerate() {
582 if run.start == run.end && i != b_runs.len() - 1 {
583 continue;
584 }
585 let id = if let Some(id) = styles.get(&run.format) {
586 *id
587 } else {
588 let mut style = if let Some(id) = run.format {
589 PropertyObject::from_object(&raw.objects[&id])?
590 } else {
591 PropertyObject {
592 jcid: 0x12004d,
593 bytes: properties(&[])?,
594 global_ids: Arc::new(BTreeMap::new()),
595 }
596 };
597 if left_base != right_base {
598 let mut values = right_base.clone();
599 for property in &PropertySets::parse(&style.bytes)?.sets[0] {
600 let key = property.id & 0x7fffffff;
601 if is_character_property(key) {
602 let value = match property.value {
603 crate::Value::NoData => &[][..],
604 crate::Value::Bytes(bytes) => bytes,
605 _ => unreachable!(),
606 };
607 values.insert(key, (property.id, value.to_vec()));
608 }
609 }
610 for key in left_base.keys() {
611 if values.contains_key(key) {
612 continue;
613 }
614 let value = match key >> 26 & 31 {
615 2 => Vec::new(),
616 _ if matches!(*key, 0x14001c0c | 0x14001c0d) => {
617 0xff000000_u32.to_le_bytes().to_vec()
618 }
619 _ => {
620 return Err(invalid(
621 "The right paragraph's implicit font or language cannot be preserved under the left style",
622 ));
623 }
624 };
625 values.insert(*key, (*key, value));
626 }
627 style.set(
628 &values
629 .values()
630 .map(|(id, value)| (*id, value.as_slice()))
631 .collect::<Vec<_>>(),
632 )?;
633 }
634 let id = if let Some(id) = run
635 .format
636 .filter(|id| raw.objects[id].data == ObjectData::Properties(&style.bytes))
637 {
638 id
639 } else {
640 let id = ExGuid {
641 guid: fresh_guid()?,
642 n: 1,
643 };
644 style.reference(id)?;
645 changed.insert(id, style);
646 id
647 };
648 styles.insert(run.format, id);
649 id
650 };
651 references.extend_from_slice(&target.reference(id)?);
652 ends.extend_from_slice(&(length + run.end).to_le_bytes());
653 }
654 ends.truncate(ends.len() - 4);
655 if ends
656 .chunks_exact(4)
657 .map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap()))
658 .collect::<Vec<_>>()
659 .windows(2)
660 .any(|pair| pair[0] >= pair[1])
661 {
662 return Err(invalid("Text-run boundaries must be strictly increasing"));
663 }
664 target.set(&[
665 (0x1c001c22, &string(&format!("{a}{b}"))),
666 (0x1c001e12, &ends),
667 (0x24001e13, &references),
668 ])?;
669 (self.left, target)
670 };
671 text.set(&[(0x14001d7a, &modified)])?;
672 changed.insert(survivor, text);
673 let author = ExGuid {
674 guid: fresh_guid()?,
675 n: 1,
676 };
677 changed.insert(
678 author,
679 PropertyObject {
680 jcid: 0x120001,
681 bytes: properties(&[(0x1c001d75, string(&self.author))])?,
682 global_ids: Arc::new(BTreeMap::from([(0, author.guid)])),
683 },
684 );
685 let mut left_object = PropertyObject::from_object(&raw.objects[&left])?;
686 let content = left_object.reference(survivor)?;
687 let author = left_object.reference(author)?;
688 left_object.set(&[(0x24001c1f, &content), (0x20001d79, &author)])?;
689 changed.insert(left, left_object);
690 if !children.is_empty() {
691 let target = match changed.entry(stem) {
692 std::collections::btree_map::Entry::Occupied(entry) => entry.into_mut(),
693 std::collections::btree_map::Entry::Vacant(entry) => {
694 entry.insert(PropertyObject::from_object(&raw.objects[&stem])?)
695 }
696 };
697 let mut references = Vec::new();
698 for id in view.nodes[&stem].children.iter().chain(children) {
699 references.extend_from_slice(&target.reference(*id)?);
700 }
701 target.set(&[(0x24001c20, &references)])?;
702 target.copy_property(
703 &PropertyObject::from_object(&raw.objects[&right])?,
704 0x0c001c03,
705 )?;
706 }
707 let mut container = PropertyObject::from_object(&raw.objects[&right_parent])?;
708 let mut references = Vec::new();
709 for id in siblings.iter().filter(|id| **id != right) {
710 references.extend_from_slice(&container.reference(*id)?);
711 }
712 container.set(&[(0x24001c20, &references)])?;
713 changed.insert(right_parent, container);
714 let mut pending = vec![left, right_parent];
715 let mut ancestors = BTreeSet::new();
716 while let Some(id) = pending.pop() {
717 if !ancestors.insert(id) {
718 continue;
719 }
720 let object = match changed.entry(id) {
721 std::collections::btree_map::Entry::Occupied(entry) => entry.into_mut(),
722 std::collections::btree_map::Entry::Vacant(entry) => {
723 entry.insert(PropertyObject::from_object(&raw.objects[&id])?)
724 }
725 };
726 object.set(&[(0x14001d7a, &modified)])?;
727 pending.extend(parents.get(&id).into_iter().flatten().copied());
728 }
729 update_title(&store, &raw, view, &pages, &mut changed)?;
730 write_revision(source, space, |_| Ok(changed))
731 }
732}
733
734fn is_character_property(id: u32) -> bool {
735 matches!(
736 id,
737 0x08001c04
738 ..=0x08001c09
739 | 0x08001e16
740 | 0x08001e14
741 | 0x08001e19
742 | 0x08003401
743 | 0x08001e22
744 | 0x08003476
745 | 0x1c001c0a
746 | 0x10001c0b
747 | 0x14001c0c
748 | 0x14001c0d
749 | 0x14001c3b
750 )
751}
752
753fn character_properties(
754 view: &crate::document::Revision<'_>,
755 raw: &crate::ResolvedRevision<'_>,
756 text: ExGuid,
757) -> Result<BTreeMap<u32, (u32, Vec<u8>)>, Error> {
758 let Kind::RichText {
759 paragraph_style, ..
760 } = view.nodes[&text].kind
761 else {
762 unreachable!()
763 };
764 let mut values = BTreeMap::new();
765 for id in paragraph_style.into_iter().chain([text]) {
766 let ObjectData::Properties(bytes) = raw.objects[&id].data else {
767 unreachable!()
768 };
769 for property in &PropertySets::parse(bytes)?.sets[0] {
770 let key = property.id & 0x7fffffff;
771 if is_character_property(key) {
772 let value = match property.value {
773 crate::Value::NoData => &[][..],
774 crate::Value::Bytes(bytes) => bytes,
775 _ => unreachable!(),
776 };
777 values.insert(key, (property.id, value.to_vec()));
778 }
779 }
780 }
781 Ok(values)
782}
crates/onestore/src/write.rs+108
......@@ -472,6 +472,114 @@ impl PropertyObject {
472472 }
473473 compact(id, &self.global_ids)
474474 }
475
476 pub fn copy_property(&mut self, source: &Self, id: u32) -> Result<()> {
477 let properties = PropertySets::parse(&source.bytes)?;
478 let mut target = Self {
479 jcid: self.jcid,
480 bytes: self.bytes.clone(),
481 global_ids: Arc::clone(&self.global_ids),
482 };
483 target.remove(&[id])?;
484 let lengths = property_set_lengths(&properties);
485 let mut offset = properties.root_ids.as_ptr().addr() - source.bytes.as_ptr().addr()
486 + properties.root_ids.len();
487 let mut selected = None;
488 for property in &properties.sets[0] {
489 let end = offset + field_length(property, &lengths);
490 if property.id & 0x7fffffff == id & 0x7fffffff {
491 selected = Some((property, &source.bytes[offset..end]));
492 break;
493 }
494 offset = end;
495 }
496 if let Some((property, field)) = selected {
497 let mut added: [Vec<u8>; 3] = std::array::from_fn(|_| Vec::new());
498 let mut pending = vec![property];
499 while let Some(property) = pending.pop() {
500 match &property.value {
501 Value::References {
502 stream,
503 compact_ids,
504 } => {
505 let index = match stream {
506 crate::IdStream::Objects => 0,
507 crate::IdStream::ObjectSpaces => 1,
508 crate::IdStream::Contexts => 2,
509 };
510 let mut cursor = crate::bytes::Cursor {
511 bytes: compact_ids,
512 offset: 0,
513 };
514 while cursor.offset < cursor.bytes.len() {
515 let id = cursor.compact(&source.global_ids)?;
516 added[index].extend_from_slice(&target.reference(id)?);
517 }
518 }
519 Value::Sets(children) => {
520 for child in children.clone().rev() {
521 pending.extend(properties.sets[child].iter().rev());
522 }
523 }
524 _ => {}
525 }
526 }
527 let old = PropertySets::parse(&target.bytes)?;
528 let count = u16::try_from(old.sets[0].len() + 1).map_err(|_| Error {
529 offset: 0,
530 message: "Root property count exceeds the format limit",
531 })?;
532 let streams = crate::properties::reference_streams(&mut crate::bytes::Cursor {
533 bytes: &target.bytes,
534 offset: 0,
535 })?;
536 let stream_count = (0..3)
537 .rev()
538 .find(|i| streams[*i].offset != 0 || !added[*i].is_empty())
539 .unwrap()
540 + 1;
541 let mut bytes = Vec::new();
542 for i in 0..stream_count {
543 let stream = &streams[i];
544 let count = u32::try_from((stream.bytes.len() + added[i].len()) / 4)
545 .ok()
546 .filter(|n| *n <= 0xffffff)
547 .ok_or(Error {
548 offset: 0,
549 message: "Reference stream exceeds the format limit",
550 })?;
551 let reserved = if stream.offset == 0 {
552 0
553 } else {
554 u32::from_le_bytes(
555 target.bytes[stream.offset - 4..stream.offset]
556 .try_into()
557 .unwrap(),
558 ) & 0x3f000000
559 };
560 let flags = match (i, stream_count) {
561 (0, 1) => 0x80000000,
562 (0 | 1, 3) => 0x40000000,
563 _ => 0,
564 };
565 bytes.extend_from_slice(&(count | reserved | flags).to_le_bytes());
566 bytes.extend_from_slice(stream.bytes);
567 bytes.extend_from_slice(&added[i]);
568 }
569 bytes.extend_from_slice(&count.to_le_bytes());
570 bytes.extend_from_slice(old.root_ids);
571 bytes.extend_from_slice(&property.id.to_le_bytes());
572 let start =
573 old.root_ids.as_ptr().addr() - target.bytes.as_ptr().addr() + old.root_ids.len();
574 bytes.extend_from_slice(&target.bytes[start..target.bytes.len() - old.padding.len()]);
575 bytes.extend_from_slice(field);
576 bytes.resize(bytes.len().next_multiple_of(8), 0);
577 PropertySets::parse(&bytes)?;
578 target.bytes = bytes;
579 }
580 *self = target;
581 Ok(())
582 }
475583}
476584
477585pub(crate) fn write_revision(
crates/onestore/src/write/tests.rs+75-1
......@@ -29,8 +29,11 @@ fn document_insertions_and_formatting_respect_readonly_ancestors() {
2929 ))
3030 })
3131 .unwrap();
32 let right = Insertion::paragraph(outline, None, "Right", "Author").unwrap();
33 let expanded = PreparedEdit::insert(&source, sid, &right).unwrap();
34 let source = expanded.as_bytes();
3235 for blocked in [page, outline, paragraph] {
33 let protected = write_revision(&source, sid, |raw| {
36 let protected = write_revision(source, sid, |raw| {
3437 let mut object = PropertyObject::from_object(&raw.objects[&blocked])?;
3538 object.set(&[(0x88001cde, &[])])?;
3639 Ok(BTreeMap::from([(blocked, object)]))
......@@ -48,6 +51,8 @@ fn document_insertions_and_formatting_respect_readonly_ancestors() {
4851 .unwrap();
4952 let split = crate::ParagraphSplit::new(text, 1, "Author").unwrap();
5053 assert!(PreparedEdit::split(&protected, sid, &split).is_err());
54 let join = crate::ParagraphJoin::new(text, right.text_object(), "Author").unwrap();
55 assert!(PreparedEdit::join(&protected, sid, &join).is_err());
5156 assert!(
5257 PreparedEdit::format(
5358 &protected,
......@@ -63,6 +68,18 @@ fn document_insertions_and_formatting_respect_readonly_ancestors() {
6368 assert!(PreparedEdit::insert(&protected, sid, &outline).is_err());
6469 }
6570 }
71 let protected = write_revision(source, sid, |raw| {
72 let mut object = PropertyObject::from_object(&raw.objects[&right.object()])?;
73 object.set(&[(0x88001cde, &[])])?;
74 Ok(BTreeMap::from([(right.object(), object)]))
75 })
76 .unwrap();
77 let document = crate::document::Document::parse(&index).unwrap();
78 let space = &document.spaces[&sid];
79 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
80 let text = view.nodes[&paragraph].content[0];
81 let join = crate::ParagraphJoin::new(text, right.text_object(), "Author").unwrap();
82 assert!(PreparedEdit::join(&protected, sid, &join).is_err());
6683}
6784
6885fn add_paragraph(source: &[u8], number: u32) -> Vec<u8> {
......@@ -572,6 +589,56 @@ fn nested_fields_and_other_reference_streams_remain_byte_exact() {
572589 object.remove(&ids).unwrap();
573590 assert_eq!(object.bytes, before);
574591 }
592 let source = super::PropertyObject {
593 jcid: 0x6000e,
594 bytes: original.clone(),
595 global_ids: std::sync::Arc::new(std::collections::BTreeMap::from([(0, [1; 16])])),
596 };
597 let mut target = super::PropertyObject {
598 jcid: 0x6000e,
599 bytes: properties(&[(0x20000001, vec![6, 0, 0, 0])]).unwrap(),
600 global_ids: std::sync::Arc::new(std::collections::BTreeMap::from([(0, [9; 16])])),
601 };
602 target.copy_property(&source, 0x40000002).unwrap();
603 let copied = PropertySets::parse(&target.bytes).unwrap();
604 assert_eq!(target.global_ids[&0], [9; 16]);
605 assert_eq!(target.global_ids[&1], [1; 16]);
606 assert_eq!(
607 copied.sets[0][0].value,
608 Value::References {
609 stream: crate::IdStream::Objects,
610 compact_ids: &[6, 0, 0, 0],
611 }
612 );
613 for (i, n) in [2, 4, 5].into_iter().enumerate() {
614 let Value::References { compact_ids, .. } = copied.sets[1][i].value else {
615 panic!()
616 };
617 assert_eq!(compact_ids, &[n, 1, 0, 0]);
618 }
619 assert_eq!(copied.sets[1][3], previous.sets[1][3]);
620 assert_eq!(
621 target
622 .bytes
623 .windows(nested.len())
624 .filter(|b| *b == nested)
625 .count(),
626 1
627 );
628 let before = target.bytes.clone();
629 target.copy_property(&source, 0x40000002).unwrap();
630 assert_eq!(target.bytes, before);
631 target.copy_property(&source, 0x20000001).unwrap();
632 assert_eq!(PropertySets::parse(&target.bytes).unwrap().sets[0].len(), 1);
633 let before = target.bytes.clone();
634 let ids = std::sync::Arc::clone(&target.global_ids);
635 let invalid = super::PropertyObject {
636 global_ids: Default::default(),
637 ..source
638 };
639 assert!(target.copy_property(&invalid, 0x40000002).is_err());
640 assert_eq!(target.bytes, before);
641 assert_eq!(target.global_ids, ids);
575642}
576643
577644#[test]
......@@ -599,6 +666,13 @@ fn deep_property_splices_do_not_use_the_call_stack() {
599666 let parsed = PropertySets::parse(&object.bytes).unwrap();
600667 assert_eq!(parsed.sets.len(), 100_001);
601668 assert_eq!(&object.bytes[..bytes.len()], &bytes);
669 let mut copied = super::PropertyObject {
670 jcid: object.jcid,
671 bytes: properties(&[]).unwrap(),
672 global_ids: Default::default(),
673 };
674 copied.copy_property(&object, 0x44000001).unwrap();
675 assert_eq!(copied.bytes, object.bytes);
602676 object.remove(&[0x44000001]).unwrap();
603677 let parsed = PropertySets::parse(&object.bytes).unwrap();
604678 assert_eq!(parsed.sets.len(), 1);
crates/onestore/tests/paragraph.rs+441-1
......@@ -1,5 +1,5 @@
11use onestore::{
2 ExGuid, ParagraphSplit, PreparedEdit, RevisionIndex, Store,
2 ExGuid, ParagraphJoin, ParagraphSplit, PreparedEdit, RevisionIndex, Store,
33 document::{Document, Kind, Revision},
44};
55use serde_json::Value;
......@@ -25,6 +25,270 @@ fn characters(view: &Revision<'_>, id: ExGuid) -> Vec<(char, Value)> {
2525 .collect()
2626}
2727
28const JOIN_FIXTURES: [(&[u8], &[u8], bool); 3] = [
29 (
30 include_bytes!("../../../corpus/paragraph-edit/split/notebook/synthetic.one").as_slice(),
31 include_bytes!("../../../corpus/paragraph-edit/joined/notebook/synthetic.one").as_slice(),
32 true,
33 ),
34 (
35 include_bytes!("../../../corpus/paragraph-edit/join-edges/before/notebook/synthetic.one")
36 .as_slice(),
37 include_bytes!("../../../corpus/paragraph-edit/join-edges/joined/notebook/synthetic.one")
38 .as_slice(),
39 false,
40 ),
41 (
42 include_bytes!("../../../corpus/paragraph-edit/join-tags/before/notebook/synthetic.one")
43 .as_slice(),
44 include_bytes!("../../../corpus/paragraph-edit/join-tags/joined/notebook/synthetic.one")
45 .as_slice(),
46 false,
47 ),
48];
49
50fn join_targets(document: &Document<'_>, split_cases: bool) -> Vec<(String, ExGuid, ExGuid)> {
51 let mut cases = Vec::new();
52 if split_cases {
53 let manifest: Value =
54 serde_json::from_str(include_str!("../../../corpus/paragraph-edit/manifest.json"))
55 .unwrap();
56 for case in manifest["cases"].as_array().unwrap() {
57 cases.push((
58 case["case"].as_str().unwrap().to_owned(),
59 serde_json::from_value::<ExGuid>(case["original_text"].clone()).unwrap(),
60 serde_json::from_value::<ExGuid>(case["new_text"].clone()).unwrap(),
61 ));
62 }
63 } else {
64 for (sid, page) in document.pages().unwrap() {
65 let s = &document.spaces[&sid];
66 let view = &s.revisions[&s.contexts[&ExGuid::default()]];
67 let Kind::Metadata {
68 title: Some(name), ..
69 } = &view.nodes[&view.roots[&2]].kind
70 else {
71 panic!()
72 };
73 if !name.starts_with("Join ") {
74 continue;
75 }
76 let outline = view.nodes[&page]
77 .children
78 .iter()
79 .find(|id| matches!(view.nodes[id].kind, Kind::Outline { .. }))
80 .unwrap();
81 let mut left = view.nodes[outline].children[0];
82 while let Some(child) = view.nodes[&left].children.last() {
83 left = *child;
84 }
85 let right = view.nodes[outline].children[1];
86 cases.push((
87 name.clone(),
88 view.nodes[&left].content[0],
89 view.nodes[&right].content[0],
90 ));
91 }
92 }
93 assert_eq!(cases.len(), document.pages().unwrap().len() - 1);
94 cases
95}
96
97#[test]
98fn joins_match_native_graphs_tags_and_inherited_character_styles() {
99 for (source, native, split_cases) in JOIN_FIXTURES {
100 let store = Store::parse(source).unwrap();
101 let index = RevisionIndex::parse(&store).unwrap();
102 let document = Document::parse(&index).unwrap();
103 let native_store = Store::parse(native).unwrap();
104 let native_index = RevisionIndex::parse(&native_store).unwrap();
105 let native_document = Document::parse(&native_index).unwrap();
106 let cases = join_targets(&document, split_cases);
107 for (name, left, right) in cases {
108 let (sid, space) = document
109 .spaces
110 .iter()
111 .find(|(_, space)| {
112 space.revisions[&space.contexts[&ExGuid::default()]]
113 .nodes
114 .contains_key(&left)
115 })
116 .unwrap();
117 let intent = ParagraphJoin::new(left, right, "Join author").unwrap();
118 let restored = serde_json::from_value(serde_json::to_value(&intent).unwrap()).unwrap();
119 assert_eq!(intent, restored);
120 let edited = PreparedEdit::join(source, *sid, &restored);
121 if name == "Split before hyperlink" {
122 assert!(edited.is_err());
123 continue;
124 }
125 let edited = edited.unwrap_or_else(|error| panic!("{name}: {error}"));
126 let current_store = Store::parse(edited.as_bytes()).unwrap();
127 assert_eq!(
128 current_store.header.transaction_count,
129 store.header.transaction_count + 1
130 );
131 let current_index = RevisionIndex::parse(&current_store).unwrap();
132 current_index.validate_current().unwrap();
133 let current_document = Document::parse(&current_index).unwrap();
134 let current = &current_document.spaces[sid];
135 let current = &current.revisions[&current.contexts[&ExGuid::default()]];
136 let expected = &native_document.spaces[sid];
137 let expected = &expected.revisions[&expected.contexts[&ExGuid::default()]];
138 let (_, page) = document
139 .pages()
140 .unwrap()
141 .into_iter()
142 .find(|(id, _)| id == sid)
143 .unwrap();
144 let before = &space.revisions[&space.contexts[&ExGuid::default()]];
145 let left_paragraph = *before
146 .nodes
147 .iter()
148 .find(|(_, node)| node.content == [left])
149 .unwrap()
150 .0;
151 let right_paragraph = *before
152 .nodes
153 .iter()
154 .find(|(_, node)| node.content == [right])
155 .unwrap()
156 .0;
157 let right_parent = *before
158 .nodes
159 .iter()
160 .find(|(_, node)| node.children.contains(&right_paragraph))
161 .unwrap()
162 .0;
163 let survivor = if characters(before, left).is_empty() {
164 right
165 } else {
166 left
167 };
168 let mut changed = std::collections::BTreeSet::from([survivor, before.roots[&2]]);
169 let mut ancestors = vec![left_paragraph, right_parent];
170 while let Some(id) = ancestors.pop() {
171 if !changed.insert(id) || id == page {
172 continue;
173 }
174 ancestors.extend(before.nodes.iter().filter_map(|(parent, node)| {
175 node.children
176 .iter()
177 .chain(&node.content)
178 .chain(&node.structure)
179 .any(|child| *child == id)
180 .then_some(*parent)
181 }));
182 }
183 let old_raw = index
184 .resolve(*sid, space.contexts[&ExGuid::default()])
185 .unwrap();
186 let current_rid = current_document.spaces[sid].contexts[&ExGuid::default()];
187 let new_raw = current_index.resolve(*sid, current_rid).unwrap();
188 for (id, object) in old_raw.objects {
189 if !changed.contains(&id) {
190 assert_eq!(
191 object.data, new_raw.objects[&id].data,
192 "{name} untouched {id}"
193 );
194 }
195 }
196 let mut pending: Vec<_> = expected.nodes[&page]
197 .children
198 .iter()
199 .filter(|id| matches!(expected.nodes[id].kind, Kind::Outline { .. }))
200 .copied()
201 .collect();
202 while let Some(id) = pending.pop() {
203 let node = &expected.nodes[&id];
204 assert_eq!(
205 current.nodes[&id].children, node.children,
206 "{name} children {id}"
207 );
208 assert_eq!(
209 current.nodes[&id].content, node.content,
210 "{name} content {id}"
211 );
212 assert_eq!(
213 current.nodes[&id].child_level, node.child_level,
214 "{name} indentation {id}"
215 );
216 pending.extend(
217 node.children
218 .iter()
219 .chain(&node.content)
220 .chain(&node.structure)
221 .copied(),
222 );
223 if matches!(node.kind, Kind::RichText { .. }) {
224 assert_eq!(
225 serde_json::to_value(&current.nodes[&id].tags).unwrap(),
226 serde_json::to_value(&node.tags).unwrap(),
227 "{name} tags {id}"
228 );
229 let normalize = |view, id| {
230 characters(view, id)
231 .into_iter()
232 .map(|(c, mut style)| {
233 let fields = style.as_object_mut().unwrap();
234 for key in
235 ["alignment", "space_before", "space_after", "line_spacing"]
236 {
237 fields.remove(key);
238 }
239 for key in [
240 "bold",
241 "italic",
242 "underline",
243 "strike",
244 "superscript",
245 "subscript",
246 "hidden",
247 "hyperlink",
248 "hyperlink_label",
249 "math",
250 "embedded_object",
251 "rtl",
252 ] {
253 if fields[key].is_null() {
254 fields.insert(key.into(), false.into());
255 }
256 }
257 for key in ["color", "highlight"] {
258 if fields[key].is_null() {
259 fields.insert(key.into(), 0xff000000_u32.into());
260 }
261 }
262 (c, style)
263 })
264 .collect::<Vec<_>>()
265 };
266 assert_eq!(
267 normalize(current, id),
268 normalize(expected, id),
269 "{name} styles {id}"
270 );
271 }
272 }
273 for (sid, old) in &index.spaces {
274 for revision in old.revisions.keys() {
275 let old = index.resolve(*sid, *revision).unwrap();
276 let retained = current_index.resolve(*sid, *revision).unwrap();
277 assert_eq!(old.roots, retained.roots);
278 for (id, object) in old.objects {
279 assert_eq!(object.data, retained.objects[&id].data);
280 }
281 }
282 }
283 assert!(PreparedEdit::join(edited.as_bytes(), *sid, &intent).is_err());
284 assert_eq!(
285 space.contexts.len(),
286 current_document.spaces[sid].contexts.len()
287 );
288 }
289 }
290}
291
28292#[test]
29293fn splits_partition_native_paragraphs_at_every_scalar_boundary() {
30294 let manifest: Value =
......@@ -341,3 +605,179 @@ fn interrupted_splits_publish_a_complete_graph_or_retain_the_original() {
341605 }
342606 }
343607}
608
609#[test]
610#[ignore = "exports paragraph joins for independent native validation"]
611fn export_native_paragraph_joins() {
612 use std::{fs, path::PathBuf};
613 let output = PathBuf::from(std::env::var_os("ONESTORE_PARAGRAPH_JOIN_OUTPUT").unwrap());
614 assert!(output.is_absolute());
615 fs::create_dir(&output).unwrap();
616 for (number, (source, _, split_cases)) in JOIN_FIXTURES.into_iter().enumerate() {
617 let store = Store::parse(source).unwrap();
618 let index = RevisionIndex::parse(&store).unwrap();
619 let document = Document::parse(&index).unwrap();
620 let cases = join_targets(&document, split_cases);
621 let mut source = source.to_vec();
622 let mut manifest = Vec::new();
623 for (name, left, right) in cases {
624 if name == "Split before hyperlink" {
625 continue;
626 }
627 let sid = *document
628 .spaces
629 .iter()
630 .find(|(_, space)| {
631 space.revisions[&space.contexts[&ExGuid::default()]]
632 .nodes
633 .contains_key(&left)
634 })
635 .unwrap()
636 .0;
637 let intent = ParagraphJoin::new(left, right, "Rust join author").unwrap();
638 let edit = PreparedEdit::join(&source, sid, &intent).unwrap();
639 source = edit.as_bytes().to_vec();
640 manifest.push(serde_json::json!({"case":name,"space":sid,"intent":intent}));
641 }
642 let folder = output.join(["split", "inheritance", "tags"][number]);
643 fs::create_dir_all(folder.join("candidate")).unwrap();
644 fs::write(folder.join("candidate/synthetic.one"), source).unwrap();
645 fs::write(
646 folder.join("manifest.json"),
647 serde_json::to_vec_pretty(&manifest).unwrap(),
648 )
649 .unwrap();
650 }
651}
652
653#[test]
654fn interrupted_joins_preserve_complete_graphs_and_empty_text_adoption() {
655 let source = onestore::create_section("join.one", "Left", "Author").unwrap();
656 let store = Store::parse(&source).unwrap();
657 let index = RevisionIndex::parse(&store).unwrap();
658 let document = Document::parse(&index).unwrap();
659 let (sid, page) = document.pages().unwrap()[0];
660 let space = &document.spaces[&sid];
661 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
662 let outline = *view.nodes[&page]
663 .children
664 .iter()
665 .find(|id| matches!(view.nodes[id].kind, Kind::Outline { .. }))
666 .unwrap();
667 let left = view.nodes[&view.nodes[&outline].children[0]].content[0];
668 let right = onestore::Insertion::paragraph(outline, None, "Right 🦀", "Author")
669 .unwrap()
670 .with_formatting(0..5, &[onestore::TextAttribute::Bold(true)])
671 .unwrap();
672 let inserted = PreparedEdit::insert(&source, sid, &right).unwrap();
673 let child =
674 onestore::Insertion::paragraph(right.object(), None, "Retained child", "Author").unwrap();
675 let original = PreparedEdit::insert(inserted.as_bytes(), sid, &child).unwrap();
676 let original = original.as_bytes();
677 let empty = onestore::replace_text(original, sid, left, 0..4, "").unwrap();
678 let checkpoint = checkpoint::pending(original, sid, left, 0x14001d7a);
679 for source in [original, &empty, &checkpoint] {
680 let intent = ParagraphJoin::new(left, right.text_object(), "Join author").unwrap();
681 let edit = PreparedEdit::join(source, sid, &intent).unwrap();
682 let before = current::current(source);
683 let after = current::current(edit.as_bytes());
684 for write_limit in [17, 4096] {
685 let disk = |fail_at| disk::Disk {
686 visible: source.to_vec(),
687 durable: source.to_vec(),
688 operation: 0,
689 fail_at,
690 write_limit,
691 random: 917,
692 };
693 let mut successful = disk(None);
694 edit.commit(&mut successful).unwrap();
695 assert_eq!(successful.durable, edit.as_bytes());
696 for at in std::iter::once(1).chain(source.len().div_ceil(193)..=successful.operation) {
697 let mut interrupted = disk(Some(at));
698 let error = edit.commit(&mut interrupted).unwrap_err();
699 let recovered = current::current(&interrupted.durable);
700 assert!(
701 recovered == before || recovered == after,
702 "interruption {at}"
703 );
704 match error.state {
705 onestore::CommitState::NotCommitted => assert_eq!(recovered, before),
706 onestore::CommitState::Committed => assert_eq!(recovered, after),
707 onestore::CommitState::Unknown => {}
708 }
709 }
710 }
711 }
712}
713
714#[test]
715fn joins_reject_invalid_identities_wrong_order_and_unrelated_pages() {
716 let (source, _, _) = JOIN_FIXTURES[0];
717 let store = Store::parse(source).unwrap();
718 let index = RevisionIndex::parse(&store).unwrap();
719 let document = Document::parse(&index).unwrap();
720 let cases = join_targets(&document, true);
721 let (_, left, right) = &cases[0];
722 let (sid, space) = document
723 .spaces
724 .iter()
725 .find(|(_, space)| {
726 space.revisions[&space.contexts[&ExGuid::default()]]
727 .nodes
728 .contains_key(left)
729 })
730 .unwrap();
731 assert!(ParagraphJoin::new(*left, *left, "Author").is_err());
732 assert!(ParagraphJoin::new(ExGuid::default(), *right, "Author").is_err());
733 assert!(ParagraphJoin::new(*left, *right, "a\0b").is_err());
734 let intent = ParagraphJoin::new(*left, *right, "Author").unwrap();
735 for (field, value) in [
736 ("left", serde_json::to_value(ExGuid::default()).unwrap()),
737 ("right", serde_json::to_value(left).unwrap()),
738 ("author", serde_json::json!("a\0b")),
739 ] {
740 let mut encoded = serde_json::to_value(&intent).unwrap();
741 encoded[field] = value;
742 let invalid = serde_json::from_value(encoded).unwrap();
743 assert!(PreparedEdit::join(source, *sid, &invalid).is_err());
744 }
745 for intent in [
746 ParagraphJoin::new(*right, *left, "Author").unwrap(),
747 ParagraphJoin::new(*left, cases[1].2, "Author").unwrap(),
748 ] {
749 assert!(PreparedEdit::join(source, *sid, &intent).is_err());
750 }
751 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
752 let paragraph = *view
753 .nodes
754 .iter()
755 .find(|(_, node)| node.content == [*left])
756 .unwrap()
757 .0;
758 let parent = view
759 .nodes
760 .values()
761 .find(|node| node.children.contains(&paragraph))
762 .unwrap();
763 let last = view.nodes[parent.children.last().unwrap()].content[0];
764 let nonadjacent = ParagraphJoin::new(*left, last, "Author").unwrap();
765 assert!(PreparedEdit::join(source, *sid, &nonadjacent).is_err());
766 let title = view
767 .nodes
768 .iter()
769 .find_map(|(_, node)| matches!(node.kind, Kind::Title).then_some(node))
770 .unwrap();
771 let mut pending = title.children.clone();
772 let mut rejected = 0;
773 while let Some(id) = pending.pop() {
774 let node = &view.nodes[&id];
775 pending.extend(node.children.iter().chain(&node.content).copied());
776 if matches!(node.kind, Kind::RichText { .. }) {
777 let intent = ParagraphJoin::new(id, *right, "Author").unwrap();
778 assert!(PreparedEdit::join(source, *sid, &intent).is_err());
779 rejected += 1;
780 }
781 }
782 assert!(rejected > 0);
783}
fuzz/fuzz_targets/paragraph.rs+83-44
......@@ -1,8 +1,8 @@
11#![no_main]
22use libfuzzer_sys::fuzz_target;
33use onestore::{
4 CommitState, ExGuid, Insertion, ParagraphSplit, PreparedEdit, RevisionIndex, Store,
5 TextAttribute as A,
4 CommitState, ExGuid, Insertion, ParagraphJoin, ParagraphSplit, PreparedEdit, RevisionIndex,
5 Store, TextAttribute as A,
66 document::{Document, Kind},
77};
88use std::sync::LazyLock;
......@@ -43,6 +43,11 @@ fuzz_target!(|input: &[u8]| {
4343 {
4444 current::current(edit.as_bytes());
4545 }
46 if let Ok(intent) = serde_json::from_slice::<ParagraphJoin>(input)
47 && let Ok(edit) = PreparedEdit::join(source, *sid, &intent)
48 {
49 current::current(edit.as_bytes());
50 }
4651 let mut persisted = source.clone();
4752 let mut caches = std::array::from_fn::<_, 12, _>(|_| source.clone());
4853 for step in input.chunks_exact(8).take(20) {
......@@ -65,57 +70,91 @@ fuzz_target!(|input: &[u8]| {
6570 paragraphs.push(id);
6671 }
6772 }
68 let paragraph = paragraphs[usize::from(step[2]) % paragraphs.len()];
69 let text = view.nodes[&paragraph].content[0];
70 let characters: Vec<_> = view
71 .text_runs(text)
72 .unwrap()
73 .into_iter()
74 .flat_map(|run| {
75 let style = serde_json::to_value(run.format).unwrap();
76 run.text.chars().map(move |c| (c, style.clone()))
73 let characters = |view: &onestore::document::Revision<'_>, id| {
74 view.text_runs(id)
75 .unwrap()
76 .into_iter()
77 .flat_map(|run| {
78 let style = serde_json::to_value(run.format).unwrap();
79 run.text.chars().map(move |c| (c, style.clone()))
80 })
81 .collect::<Vec<_>>()
82 };
83 let pairs: Vec<_> = view
84 .nodes
85 .iter()
86 .flat_map(|(parent, node)| {
87 node.children.windows(2).filter_map(|pair| {
88 (paragraphs.contains(&pair[0])
89 && paragraphs.contains(&pair[1])
90 && view.nodes[&pair[0]].children.is_empty())
91 .then_some((*parent, pair[0], pair[1]))
92 })
7793 })
7894 .collect();
79 let offsets: Vec<u32> = std::iter::once(0)
80 .chain(characters.iter().scan(0, |n, (c, _)| {
81 *n += u32::try_from(c.len_utf16()).unwrap();
82 Some(*n)
83 }))
84 .collect();
85 let offset = u32::from(step[3]) % (offsets.last().unwrap() + 2);
86 let intent = ParagraphSplit::new(text, offset, "Paragraph fuzz").unwrap();
87 let intent = serde_json::from_value(serde_json::to_value(intent).unwrap()).unwrap();
88 let edit = PreparedEdit::split(source, *sid, &intent);
89 let Some(position) = offsets.iter().position(|n| *n == offset) else {
90 assert!(edit.is_err());
91 continue;
95 let mut expected_text = Vec::new();
96 let mut expected_graph = Vec::new();
97 let edit = if step[1] & 8 != 0 && !pairs.is_empty() {
98 let (parent, left, right) = pairs[usize::from(step[2]) % pairs.len()];
99 let a = view.nodes[&left].content[0];
100 let b = view.nodes[&right].content[0];
101 let mut expected = characters(view, a);
102 let survivor = if expected.is_empty() { b } else { a };
103 expected.extend(characters(view, b));
104 expected_text.push((survivor, expected));
105 let children: Vec<_> = view.nodes[&parent]
106 .children
107 .iter()
108 .filter(|id| **id != right)
109 .copied()
110 .collect();
111 expected_graph.push((parent, children, view.nodes[&parent].content.clone()));
112 expected_graph.push((left, view.nodes[&right].children.clone(), vec![survivor]));
113 let intent = ParagraphJoin::new(a, b, "Paragraph fuzz").unwrap();
114 let restored = serde_json::from_value(serde_json::to_value(&intent).unwrap()).unwrap();
115 assert_eq!(intent, restored);
116 PreparedEdit::join(source, *sid, &restored).unwrap()
117 } else {
118 let paragraph = paragraphs[usize::from(step[2]) % paragraphs.len()];
119 let text = view.nodes[&paragraph].content[0];
120 let before = characters(view, text);
121 let offsets: Vec<u32> = std::iter::once(0)
122 .chain(before.iter().scan(0, |n, (c, _)| {
123 *n += u32::try_from(c.len_utf16()).unwrap();
124 Some(*n)
125 }))
126 .collect();
127 let offset = u32::from(step[3]) % (offsets.last().unwrap() + 2);
128 let intent = ParagraphSplit::new(text, offset, "Paragraph fuzz").unwrap();
129 let restored = serde_json::from_value(serde_json::to_value(&intent).unwrap()).unwrap();
130 assert_eq!(intent, restored);
131 let edit = PreparedEdit::split(source, *sid, &restored);
132 let Some(position) = offsets.iter().position(|n| *n == offset) else {
133 assert!(edit.is_err());
134 continue;
135 };
136 expected_text.push((text, before[..position].to_vec()));
137 expected_text.push((intent.text_object(), before[position..].to_vec()));
138 expected_graph.push((paragraph, vec![], vec![text]));
139 expected_graph.push((
140 intent.object(),
141 view.nodes[&paragraph].children.clone(),
142 vec![intent.text_object()],
143 ));
144 edit.unwrap()
92145 };
93 let edit = edit.unwrap();
94146 let after_store = Store::parse(edit.as_bytes()).unwrap();
95147 let after_index = RevisionIndex::parse(&after_store).unwrap();
96148 let after_document = Document::parse(&after_index).unwrap();
97149 let space = &after_document.spaces[sid];
98150 let after_view = &space.revisions[&space.contexts[&ExGuid::default()]];
99 for (id, expected) in [
100 (text, &characters[..position]),
101 (intent.text_object(), &characters[position..]),
102 ] {
103 let actual: Vec<_> = after_view
104 .text_runs(id)
105 .unwrap()
106 .into_iter()
107 .flat_map(|run| {
108 let style = serde_json::to_value(run.format).unwrap();
109 run.text.chars().map(move |c| (c, style.clone()))
110 })
111 .collect();
112 assert_eq!(actual, expected);
151 for (id, expected) in expected_text {
152 assert_eq!(characters(after_view, id), expected);
153 }
154 for (id, children, content) in expected_graph {
155 assert_eq!(after_view.nodes[&id].children, children);
156 assert_eq!(after_view.nodes[&id].content, content);
113157 }
114 assert!(after_view.nodes[&paragraph].children.is_empty());
115 assert_eq!(
116 after_view.nodes[&intent.object()].children,
117 view.nodes[&paragraph].children
118 );
119158 let before = current::current(&persisted);
120159 let after = current::current(edit.as_bytes());
121160 let mut disk = disk::Disk {
tools/native/paragraph-tag-joins.ps1 created+39
......@@ -0,0 +1,39 @@
1param([Parameter(Mandatory=$true)][string]$Root, [Parameter(Mandatory=$true)][string]$CloneHost)
2Set-StrictMode -Version Latest
3$ErrorActionPreference = 'Stop'
4& "$PSScriptRoot\cold-current.ps1" -Root $Root -CloneHost $CloneHost
5$app = New-Object -ComObject OneNote.Application
6$notebook = ''
7try {
8 $app.OpenHierarchy((Join-Path $Root 'notebook'), '', [ref]$notebook, 0)
9 $section = ''
10 $app.OpenHierarchy('synthetic.one', $notebook, [ref]$section, 0)
11 $mixed = '<b>Bold</b> <i>italic</i> ' + [char]::ConvertFromUtf32(0x1F980) + ' e' + [char]0x0301 + ' <span style="color:#123456">tail</span>'
12 $text = '<one:T><![CDATA[' + $mixed + ']]></one:T>'
13 $child = '<one:OEChildren><one:OE><one:T>Preserved child</one:T></one:OE></one:OEChildren>'
14 $tag = '<one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/>'
15 $rightTag = $tag.Replace('index="0"', 'index="1"')
16 $cases = @(
17 @{ name = 'Join empty left right tag'; body = '<one:OE><one:T><![CDATA[]]></one:T></one:OE><one:OE>' + $rightTag + $text + '</one:OE>' },
18 @{ name = 'Join empty left two tags'; body = '<one:OE>' + $tag + '<one:T><![CDATA[]]></one:T></one:OE><one:OE>' + $rightTag + $text + '</one:OE>' },
19 @{ name = 'Join different tags'; body = '<one:OE>' + $tag + '<one:T>Left</one:T></one:OE><one:OE>' + $rightTag + $text + '</one:OE>' }
20 )
21 $inputs = Join-Path $Root 'inputs'
22 New-Item -ItemType Directory -Path $inputs | Out-Null
23 foreach ($case in $cases) {
24 $page = ''
25 $app.CreateNewPage($section, [ref]$page, 0)
26 $xml = '<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="' + $page + '"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Fixture task"/><one:TagDef index="1" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Other task"/><one:QuickStyleDef index="0" name="p" font="Calibri" fontSize="11" fontColor="automatic" highlightColor="automatic"/><one:QuickStyleDef index="1" name="h1" font="Georgia" fontSize="18" bold="true" fontColor="#123456" highlightColor="automatic" spaceBefore="8" spaceAfter="4"/><one:Title><one:OE><one:T>' + $case.name + '</one:T></one:OE></one:Title><one:Outline><one:Position x="72" y="108"/><one:OEChildren>' + $case.body + '<one:OE><one:T>Preserved sibling</one:T></one:OE></one:OEChildren></one:Outline></one:Page>'
27 [IO.File]::WriteAllText((Join-Path $inputs ($case.name + '.xml')), $xml, [Text.Encoding]::UTF8)
28 $app.UpdatePageContent($xml, [DateTime]::MinValue, 1, $true)
29 $case.page = $page
30 }
31 $cases | ConvertTo-Json -Depth 6 | Set-Content (Join-Path $Root 'cases.json') -Encoding UTF8
32 $app.SyncHierarchy($notebook)
33} finally {
34 if ($notebook) { $app.CloseNotebook($notebook, $false) }
35 [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
36 $app = $null
37 [GC]::Collect()
38 [GC]::WaitForPendingFinalizers()
39}
tools/test_paragraph_edit.py+93-53
......@@ -17,60 +17,100 @@ compare = runpy.run_path(str(ROOT / 'tools/verify-document.py'))['compare']
1717
1818
1919class ParagraphEditTest(unittest.TestCase):
20 def test_rust_joins_match_native_controls_and_retain_cold_graph_identities(self):
21 for name, control in [('split', FIXTURE / 'joined/read'),
22 ('inheritance', FIXTURE / 'join-edges/joined/read'),
23 ('tags', FIXTURE / 'join-tags/joined/read')]:
24 fixture = FIXTURE / 'rust-join' / name
25 manifest = json.loads((fixture / 'manifest.json').read_text())
26 with self.subTest(fixture=name), TemporaryDirectory() as temporary:
27 models = []
28 for source in ('candidate', 'native/notebook'):
29 folder = Path(temporary) / source.replace('/', '-')
30 shutil.copytree(fixture / 'native/read', folder / 'read')
31 compare(fixture / source, folder / 'read')
32 subprocess.run([EXPORTER, fixture / source / 'synthetic.one', folder / 'model'], check=True)
33 document = json.loads((folder / 'model/document.json').read_text())
34 models.append({r['nodes'][r['roots']['2']]['kind']['title']: (r, page)
35 for _, _, r, page in ordered_pages(document)})
36 self.assertEqual(models[0].keys(), models[1].keys())
37 for title, (old, page) in models[0].items():
38 saved, saved_page = models[1][title]
39 self.assertEqual(page, saved_page)
40 for outline in old['nodes'][page]['children']:
41 if old['nodes'][outline]['kind']['type'] != 'Outline': continue
42 for oid, node in walk(old, outline):
43 self.assertEqual(saved['nodes'][oid]['children'], node['children'])
44 self.assertEqual(saved['nodes'][oid]['content'], node['content'])
45 captures = []
46 for folder in (control, fixture / 'native/read'):
47 captures.append({page.get('name'): native_characters(page, page.findall('one:Outline', ns))
48 for path in folder.glob('page-*.xml') for page in [ET.parse(path).getroot()]})
49 self.assertEqual(len(manifest), {'split': 12, 'inheritance': 5, 'tags': 3}[name])
50 for case in manifest:
51 for a, b in zip(captures[0][case['case']], captures[1][case['case']], strict=True):
52 for (x, old), (y, new) in zip(a, b, strict=True):
53 self.assertEqual(x, y)
54 for key in old.keys() | new.keys():
55 default = 'automatic' if key in ('color', 'highlight') else False
56 self.assertEqual(old.get(key, default), new.get(key, default))
57
2058 def test_native_joins_preserve_inherited_styles_and_follow_tag_and_child_rules(self):
21 fixture = FIXTURE / 'join-edges'
22 models, captures = {}, {}
23 with TemporaryDirectory() as temporary:
24 for phase in ('before', 'joined'):
25 folder = Path(temporary) / phase
26 shutil.copytree(fixture / phase / 'read', folder / 'read')
27 compare(fixture / phase / 'notebook', folder / 'read')
28 subprocess.run([EXPORTER, fixture / phase / 'notebook/synthetic.one', folder / 'model'], check=True)
29 model = json.loads((folder / 'model/document.json').read_text())
30 models[phase] = {r['nodes'][r['roots']['2']]['kind']['title']: (r, page)
31 for _, _, r, page in ordered_pages(model)}
32 captures[phase] = {}
33 for path in (folder / 'read').glob('page-*.xml'):
34 page = ET.parse(path).getroot()
35 captures[phase][page.get('name')] = native_characters(page, page.findall('one:Outline', ns))
36 self.assertEqual(len(models[phase]), 6)
37 cases = json.loads((fixture / 'cases.json').read_text(encoding='utf-8-sig'))
38 self.assertEqual(len(cases), 5)
39 for case in cases:
40 name = case['name']
41 with self.subTest(case=name):
42 old, page = models['before'][name]
43 new, new_page = models['joined'][name]
44 self.assertEqual(page, new_page)
45 outline, = [oid for oid in old['nodes'][page]['children'] if old['nodes'][oid]['kind']['type'] == 'Outline']
46 left, right, sibling = old['nodes'][outline]['children']
47 self.assertEqual(new['nodes'][outline]['children'], [left, sibling])
48 target = old['nodes'][left]['children'][-1] if name == 'Join both children' else left
49 a, = old['nodes'][target]['content']
50 b, = old['nodes'][right]['content']
51 empty = old['nodes'][a]['kind']['text'] == ''
52 survivor = b if empty else a
53 self.assertEqual(new['nodes'][target]['content'], [survivor])
54 self.assertEqual(new['nodes'][survivor]['kind']['text'], old['nodes'][a]['kind']['text'] + old['nodes'][b]['kind']['text'])
55 self.assertEqual(new['nodes'][survivor]['tags'], old['nodes'][a]['tags'])
56 children = old['nodes'][left]['children'] + old['nodes'][right]['children']
57 self.assertEqual(new['nodes'][left]['children'], children)
58 self.assertEqual(new['nodes'][sibling], old['nodes'][sibling])
59 if name == 'Join both children':
60 parent_text, = old['nodes'][left]['content']
61 original = old['nodes'][parent_text]
62 current = new['nodes'][parent_text]
63 self.assertGreater(current['modified'], original['modified'])
64 expected = {**original, 'modified': current['modified'],
65 'extra': [original['extra'][0] + [{'id': 0x880034dd, 'value': 'NoData'}]]}
66 self.assertEqual(current, expected)
67 before = [v for paragraph in captures['before'][name] for v in paragraph]
68 after = [v for paragraph in captures['joined'][name] for v in paragraph]
69 for (a, old_style), (b, new_style) in zip(before, after, strict=True):
70 self.assertEqual(a, b)
71 for key in old_style.keys() | new_style.keys():
72 default = 'automatic' if key in ('color', 'highlight') else False
73 self.assertEqual(old_style.get(key, default), new_style.get(key, default))
59 for fixture_name, expected_cases in [('join-edges', 5), ('join-tags', 3)]:
60 fixture = FIXTURE / fixture_name
61 models, captures = {}, {}
62 with TemporaryDirectory() as temporary:
63 for phase in ('before', 'joined'):
64 folder = Path(temporary) / phase
65 shutil.copytree(fixture / phase / 'read', folder / 'read')
66 compare(fixture / phase / 'notebook', folder / 'read')
67 subprocess.run([EXPORTER, fixture / phase / 'notebook/synthetic.one', folder / 'model'], check=True)
68 model = json.loads((folder / 'model/document.json').read_text())
69 models[phase] = {r['nodes'][r['roots']['2']]['kind']['title']: (r, page)
70 for _, _, r, page in ordered_pages(model)}
71 captures[phase] = {}
72 for path in (folder / 'read').glob('page-*.xml'):
73 page = ET.parse(path).getroot()
74 captures[phase][page.get('name')] = native_characters(page, page.findall('one:Outline', ns))
75 self.assertEqual(len(models[phase]), expected_cases + 1)
76 cases = json.loads((fixture / 'cases.json').read_text(encoding='utf-8-sig'))
77 self.assertEqual(len(cases), expected_cases)
78 for case in cases:
79 name = case['name']
80 with self.subTest(case=name):
81 old, page = models['before'][name]
82 new, new_page = models['joined'][name]
83 self.assertEqual(page, new_page)
84 outline, = [oid for oid in old['nodes'][page]['children'] if old['nodes'][oid]['kind']['type'] == 'Outline']
85 left, right, sibling = old['nodes'][outline]['children']
86 self.assertEqual(new['nodes'][outline]['children'], [left, sibling])
87 target = old['nodes'][left]['children'][-1] if name == 'Join both children' else left
88 a, = old['nodes'][target]['content']
89 b, = old['nodes'][right]['content']
90 empty = old['nodes'][a]['kind']['text'] == ''
91 survivor = b if empty else a
92 self.assertEqual(new['nodes'][target]['content'], [survivor])
93 self.assertEqual(new['nodes'][survivor]['kind']['text'], old['nodes'][a]['kind']['text'] + old['nodes'][b]['kind']['text'])
94 self.assertEqual(new['nodes'][survivor]['tags'], old['nodes'][a]['tags'])
95 children = old['nodes'][left]['children'] + old['nodes'][right]['children']
96 self.assertEqual(new['nodes'][left]['children'], children)
97 self.assertEqual(new['nodes'][sibling], old['nodes'][sibling])
98 if name == 'Join both children':
99 parent_text, = old['nodes'][left]['content']
100 original = old['nodes'][parent_text]
101 current = new['nodes'][parent_text]
102 self.assertGreater(current['modified'], original['modified'])
103 expected = {**original, 'modified': current['modified'],
104 'extra': [original['extra'][0] + [{'id': 0x880034dd, 'value': 'NoData'}]]}
105 self.assertEqual(current, expected)
106 before = [v for paragraph in captures['before'][name] for v in paragraph]
107 after = [v for paragraph in captures['joined'][name] for v in paragraph]
108 for (a, old_style), (b, new_style) in zip(before, after, strict=True):
109 self.assertEqual(a, b)
110 for key in old_style.keys() | new_style.keys():
111 default = 'automatic' if key in ('color', 'highlight') else False
112 self.assertEqual(old_style.get(key, default), new_style.get(key, default))
113
74114
75115 def test_rust_splits_match_native_controls_and_preserve_empty_typing_styles(self):
76116 fixture = FIXTURE / 'rust-split'