authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-08 03:14:00-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-08 03:14:00-07:00
log4cd338a8b4d4e4acf3fc1f4e48cf94ac7596e5ea
tree463d11d3561ea5b5207254c4a885048b20f46d60
parent02d45d734921e4a41dddbf7887dd3224bb4c7f4c
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

feat: reconcile durable offline paragraph splits and joins

Retain allocated split identities and observed paragraph structure through queue reopen, boundary rebasing and explicit conflict review. Preserve uncertain publication evidence and migrate existing cache receipts and operation ordering to schema 6. Validate dependent edits and structural conflicts with production SQLite replicas, fault schedules and native OneNote keyboard controls. Cold reopening verifies 32 operations across 38 pages; the public gate passes 192 Rust tests, 8 doctests and 131 Python tests. Add a twelve-replica stateful ASan target. Assisted-by: gpt-6-astra

93 files changed, 2534 insertions(+), 40 deletions(-)

corpus/paragraph-edit/README.md+8
......@@ -60,3 +60,11 @@ XML, preserve exact child/content identities across reopening, and compare
6060the native-rendered result to the corresponding keyboard-generated control.
6161`export_native_paragraph_joins` regenerates candidates in a new directory specified
6262by `ONESTORE_PARAGRAPH_JOIN_OUTPUT`.
63
64`offline` retains reconciled split/join candidates and cold native captures.
65The cache queues edits before observing independent remote `☂` prefixes, reopens
66between acknowledgements, and confirms modeled lost replies without replaying
67operations. Tests compare native text/styles to the original keyboard controls,
68account for the remote prefixes, and retain exact graph identities after reopening.
69The ignored offline test `export_native_offline_paragraphs` regenerates candidates
70in a new directory specified by `ONESTORE_OFFLINE_PARAGRAPH_OUTPUT`.
corpus/paragraph-edit/offline/inheritance/candidate/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/offline/inheritance/candidate/synthetic.one differ
corpus/paragraph-edit/offline/inheritance/manifest.json created+62
......@@ -0,0 +1,62 @@
1[
2 {
3 "case": "Join inherited styles",
4 "id": 1,
5 "intent": {
6 "author": "Rust join author",
7 "left": "{10952FC5-A849-0DE5-03A7-5D9304627DA7},44",
8 "right": "{10952FC5-A849-0DE5-03A7-5D9304627DA7},50"
9 },
10 "remote_prefix": true,
11 "revision": "{E109048E-057D-41DC-B2A8-B9F11839B9C9},1",
12 "space": "{D6CEB12D-D1B7-033B-20A2-E9B6FF501602},1"
13 },
14 {
15 "case": "Join empty left tag",
16 "id": 2,
17 "intent": {
18 "author": "Rust join author",
19 "left": "{8BAFC64A-8457-0253-3906-57D27F8546B7},45",
20 "right": "{8BAFC64A-8457-0253-3906-57D27F8546B7},51"
21 },
22 "remote_prefix": false,
23 "revision": "{19A52AE8-D897-467C-956F-26CA42CEDA1B},1",
24 "space": "{F6E7A6F0-805F-0858-2B5A-4B1260940CF9},1"
25 },
26 {
27 "case": "Join right tag",
28 "id": 3,
29 "intent": {
30 "author": "Rust join author",
31 "left": "{2E9A20A9-1E0E-0F04-0FCE-8497CF032068},44",
32 "right": "{2E9A20A9-1E0E-0F04-0FCE-8497CF032068},49"
33 },
34 "remote_prefix": true,
35 "revision": "{BDDFFFE4-439A-4466-9D85-20B88D5A5E61},1",
36 "space": "{04EE80E5-584B-04E0-1F05-07D86902DFA1},1"
37 },
38 {
39 "case": "Join both tags",
40 "id": 4,
41 "intent": {
42 "author": "Rust join author",
43 "left": "{7A5195E3-F792-0C49-1F3C-EBBFCE774FE6},44",
44 "right": "{7A5195E3-F792-0C49-1F3C-EBBFCE774FE6},50"
45 },
46 "remote_prefix": true,
47 "revision": "{694A9A93-E1AA-48DD-AD67-6BD2C0EF5D6F},1",
48 "space": "{631A0B54-DBB1-0988-22E1-96E8B1202B59},1"
49 },
50 {
51 "case": "Join both children",
52 "id": 5,
53 "intent": {
54 "author": "Rust join author",
55 "left": "{0384F5F6-5F0B-0164-32C6-6E97A4F1B661},49",
56 "right": "{0384F5F6-5F0B-0164-32C6-6E97A4F1B661},54"
57 },
58 "remote_prefix": true,
59 "revision": "{B3100F21-58B5-4417-9797-B397AC0F7BE3},1",
60 "space": "{D7D982D1-A3D3-0690-1181-A9703CF717E5},1"
61 }
62]
\ No newline at end of file
corpus/paragraph-edit/offline/inheritance/native/notebook/Open Notebook.onetoc2 created
Binary files /dev/null and b/corpus/paragraph-edit/offline/inheritance/native/notebook/Open Notebook.onetoc2 differ
corpus/paragraph-edit/offline/inheritance/native/notebook/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/offline/inheritance/native/notebook/synthetic.one differ
corpus/paragraph-edit/offline/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-6061365E",
5 "cold": true,
6 "onenote": "14.0.4763.1000"
7}
corpus/paragraph-edit/offline/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="{18FD5F74-B008-433E-BEAA-9D326490F3A2}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-08T10:28:26.000Z" color="#B49EDE"><one:Section name="synthetic" ID="{522B3C0E-E0D6-0179-06A9-68F0709718A0}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-08T10:28:26.000Z" color="#8AA8E4"><one:Page ID="{7E62CF88-691D-4268-81CE-C0618CAF04BF}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T10:28:26.000Z" pageLevel="1"/><one:Page ID="{CCB44051-F6B3-4DED-9C57-A128642A99A1}{1}{B0}" name="Join inherited styles" dateTime="2026-09-08T09:07:14.000Z" lastModifiedTime="2026-09-08T10:28:26.000Z" pageLevel="1"/><one:Page ID="{EC9D578C-A75B-468E-97AF-038CFBEE835A}{1}{B0}" name="Join empty left tag" dateTime="2026-09-08T09:07:15.000Z" lastModifiedTime="2026-09-08T10:28:26.000Z" pageLevel="1"/><one:Page ID="{1E947199-7F4F-4A36-A3F0-4F46F2785002}{1}{B0}" name="Join right tag" dateTime="2026-09-08T09:07:16.000Z" lastModifiedTime="2026-09-08T10:28:26.000Z" pageLevel="1"/><one:Page ID="{7960FA28-FCB5-475E-9E14-DE762A5AA4FA}{1}{B0}" name="Join both tags" dateTime="2026-09-08T09:07:16.000Z" lastModifiedTime="2026-09-08T10:28:26.000Z" pageLevel="1"/><one:Page ID="{CDA373AD-84D7-4846-AD74-E1EEA78D9846}{1}{B0}" name="Join both children" dateTime="2026-09-08T09:07:17.000Z" lastModifiedTime="2026-09-08T10:28:26.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/paragraph-edit/offline/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="{1E947199-7F4F-4A36-A3F0-4F46F2785002}{1}{B0}" name="Join right tag" dateTime="2026-09-08T09:07:16.000Z" lastModifiedTime="2026-09-08T10:28:26.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="{34E0D1D5-390A-41D2-B33B-CC095479AFCB}{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-08T10:26:24.000Z" objectID="{34E0D1D5-390A-41D2-B33B-CC095479AFCB}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="139.6362152099609" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T09:07:16.000Z" lastModifiedTime="2026-09-08T10:26:24.000Z" objectID="{34E0D1D5-390A-41D2-B33B-CC095479AFCB}{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="{34E0D1D5-390A-41D2-B33B-CC095479AFCB}{54}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/inheritance/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="{7960FA28-FCB5-475E-9E14-DE762A5AA4FA}{1}{B0}" name="Join both tags" dateTime="2026-09-08T09:07:16.000Z" lastModifiedTime="2026-09-08T10:28:26.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="{602B649F-D096-429F-A3C9-A321550DC045}{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-08T10:26:24.000Z" objectID="{602B649F-D096-429F-A3C9-A321550DC045}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="139.63623046875" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T09:07:17.000Z" lastModifiedTime="2026-09-08T10:26:24.000Z" objectID="{602B649F-D096-429F-A3C9-A321550DC045}{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="{602B649F-D096-429F-A3C9-A321550DC045}{54}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/inheritance/native/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="{7E62CF88-691D-4268-81CE-C0618CAF04BF}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T10:28:26.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="{19AE6734-2268-41DE-8E3C-4CBA7333E1DE}{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="{1ECBCFC3-D115-0AD4-3E68-8FB0B8589328}{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="{19AE6734-2268-41DE-8E3C-4CBA7333E1DE}{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/offline/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="{CCB44051-F6B3-4DED-9C57-A128642A99A1}{1}{B0}" name="Join inherited styles" dateTime="2026-09-08T09:07:14.000Z" lastModifiedTime="2026-09-08T10:28:26.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="{0AEFDEB9-8F4D-4333-BF52-150D9F18F204}{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-08T10:26:24.000Z" objectID="{0AEFDEB9-8F4D-4333-BF52-150D9F18F204}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="166.0962219238281" height="33.87984466552734"/><one:OEChildren><one:OE creationTime="2026-09-08T09:07:15.000Z" lastModifiedTime="2026-09-08T10:26:24.000Z" objectID="{0AEFDEB9-8F4D-4333-BF52-150D9F18F204}{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="{0AEFDEB9-8F4D-4333-BF52-150D9F18F204}{54}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/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="{CDA373AD-84D7-4846-AD74-E1EEA78D9846}{1}{B0}" name="Join both children" dateTime="2026-09-08T09:07:17.000Z" lastModifiedTime="2026-09-08T10:28:26.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="{19FE048A-780F-4FB2-8E33-26093F8B39C2}{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-08T10:26:24.000Z" objectID="{19FE048A-780F-4FB2-8E33-26093F8B39C2}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="190.3818054199219" height="53.71086883544922"/><one:OEChildren><one:OE creationTime="2026-09-08T09:07:17.000Z" lastModifiedTime="2026-09-08T10:26:24.000Z" objectID="{19FE048A-780F-4FB2-8E33-26093F8B39C2}{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-08T10:26:24.000Z" objectID="{19FE048A-780F-4FB2-8E33-26093F8B39C2}{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="{19FE048A-780F-4FB2-8E33-26093F8B39C2}{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="{19FE048A-780F-4FB2-8E33-26093F8B39C2}{63}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/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="{EC9D578C-A75B-468E-97AF-038CFBEE835A}{1}{B0}" name="Join empty left tag" dateTime="2026-09-08T09:07:15.000Z" lastModifiedTime="2026-09-08T10:28:26.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="{91D53736-A353-4C85-85F3-1F4CE4FFC914}{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-08T10:26:24.000Z" objectID="{91D53736-A353-4C85-85F3-1F4CE4FFC914}{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-08T10:26:24.000Z" objectID="{91D53736-A353-4C85-85F3-1F4CE4FFC914}{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="{91D53736-A353-4C85-85F3-1F4CE4FFC914}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/inheritance/native/read/payloads.json created+1
......@@ -0,0 +1 @@
1../../../../before/read/payloads.json
\ No newline at end of file
corpus/paragraph-edit/offline/inheritance/native/run.json created+18
......@@ -0,0 +1,18 @@
1{
2 "notebook": "/Users/clo/dev/one/evidence/m10/offline-paragraph-native-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/offline/inheritance/native/source.json created+8
......@@ -0,0 +1,8 @@
1[
2 {
3 "path": "synthetic.one",
4 "bytes": 59016,
5 "sha256": "828e330b0dfda41ecd5ade77ea65398cf5a070170757b3f650ca4600520d99bb",
6 "mtime_ns": 1788863184799153800
7 }
8]
corpus/paragraph-edit/offline/inheritance/native/teardown.json created+1
......@@ -0,0 +1 @@
1../../../joined/teardown.json
\ No newline at end of file
corpus/paragraph-edit/offline/joins/candidate/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/offline/joins/candidate/synthetic.one differ
corpus/paragraph-edit/offline/joins/manifest.json created+146
......@@ -0,0 +1,146 @@
1[
2 {
3 "case": "Split middle",
4 "id": 1,
5 "intent": {
6 "author": "Rust join author",
7 "left": "{DFE950EF-6BEB-0F5B-2403-7B74F1AE5002},44",
8 "right": "{0BA342F5-0690-087C-24FE-8EE6D81C2041},12"
9 },
10 "remote_prefix": true,
11 "revision": "{D3E40C30-D3F7-48FC-8163-10191C9874A2},1",
12 "space": "{FE224563-B517-023E-0D75-9904B1952A26},1"
13 },
14 {
15 "case": "Split style boundary",
16 "id": 2,
17 "intent": {
18 "author": "Rust join author",
19 "left": "{5FB484E2-1E3E-0A9D-1989-271A3EBBEC42},44",
20 "right": "{051BC1DE-5AE6-0D04-0CEF-9D05F53C8EAE},12"
21 },
22 "remote_prefix": true,
23 "revision": "{B4AD652D-1152-4BFB-9F7C-3F5A37D526CC},1",
24 "space": "{DED48A05-0DB1-0712-086D-09237C982341},1"
25 },
26 {
27 "case": "Split start",
28 "id": 3,
29 "intent": {
30 "author": "Rust join author",
31 "left": "{012ED94B-43B3-079A-3050-B1CBE25C1C09},44",
32 "right": "{B230A510-9428-0CE7-1B89-7CA970F8F676},12"
33 },
34 "remote_prefix": false,
35 "revision": "{14C83ACF-0D2F-4D6C-8A85-D4B883407F57},1",
36 "space": "{7919E933-0809-00D8-1754-A21684215860},1"
37 },
38 {
39 "case": "Split end",
40 "id": 4,
41 "intent": {
42 "author": "Rust join author",
43 "left": "{DAF723A3-C2A3-0798-2EAB-34613166A794},44",
44 "right": "{601DAE0A-2A53-0C28-3077-1FE8FCB26FC2},12"
45 },
46 "remote_prefix": true,
47 "revision": "{BAE72407-55A9-4703-922B-67E6B8ADE435},1",
48 "space": "{927F2809-173C-036E-37B6-60A6568F7723},1"
49 },
50 {
51 "case": "Split empty",
52 "id": 5,
53 "intent": {
54 "author": "Rust join author",
55 "left": "{0B467F31-D287-08CD-09F4-A6FBB2F5418F},45",
56 "right": "{EACB4A2C-2444-0A1F-03D6-68187F2FB345},12"
57 },
58 "remote_prefix": false,
59 "revision": "{28937B4C-67BC-4ED8-A531-E63A6FB99F38},1",
60 "space": "{E4D8C4CC-508B-04D2-36CE-C5B8C9D2FEC4},1"
61 },
62 {
63 "case": "Split parent",
64 "id": 6,
65 "intent": {
66 "author": "Rust join author",
67 "left": "{3860EA13-D444-0598-35D6-CF96769C6A21},44",
68 "right": "{C84CA5DB-61A8-0F9B-3F91-D6E0C74536F7},12"
69 },
70 "remote_prefix": true,
71 "revision": "{E154B8F7-C84E-4AED-B436-D610448E37F1},1",
72 "space": "{7A363D4C-73C2-0ABE-0931-E6F62A90B95E},1"
73 },
74 {
75 "case": "Split child",
76 "id": 7,
77 "intent": {
78 "author": "Rust join author",
79 "left": "{B587CBC6-0BCC-0A23-16F6-E1B0B3C5D061},49",
80 "right": "{DC240C63-FC8E-0267-0AFD-A53062C25702},12"
81 },
82 "remote_prefix": true,
83 "revision": "{01D54762-D53E-4DF2-AD44-7789FAE785CE},1",
84 "space": "{3CC2C4DC-651A-07C9-2F7B-375C6663F3CA},1"
85 },
86 {
87 "case": "Split bullet",
88 "id": 8,
89 "intent": {
90 "author": "Rust join author",
91 "left": "{8C65E427-477C-02ED-1807-2F10E277019D},44",
92 "right": "{28F3F5EC-E687-02CA-2CFD-E73A184B875F},12"
93 },
94 "remote_prefix": true,
95 "revision": "{BE53841B-A5D6-447A-B1F3-C1E2D11EBBCE},1",
96 "space": "{942EF5E4-FFD1-0FE4-23D2-7429899577BD},1"
97 },
98 {
99 "case": "Split number",
100 "id": 9,
101 "intent": {
102 "author": "Rust join author",
103 "left": "{D4ABBE4F-2165-0122-1F9F-ACE3FE92C703},44",
104 "right": "{DD3FA0E7-15A6-01C4-1B77-F85F779BCBC8},12"
105 },
106 "remote_prefix": true,
107 "revision": "{53CAB283-B0D5-4D3A-B325-9FD8B214FE21},1",
108 "space": "{A4AE40FF-DD80-0003-2856-17D8295C1B91},1"
109 },
110 {
111 "case": "Split tag",
112 "id": 10,
113 "intent": {
114 "author": "Rust join author",
115 "left": "{37AA5877-2980-0227-169F-8B1E8EE0795A},44",
116 "right": "{1480427A-E9ED-04EC-0409-0653AE11FF78},12"
117 },
118 "remote_prefix": true,
119 "revision": "{BC2C9E0D-BA56-48ED-8510-CFBF7FCBDEED},1",
120 "space": "{739DB38B-4239-0A7C-2652-4E525D219638},1"
121 },
122 {
123 "case": "Split cell",
124 "id": 11,
125 "intent": {
126 "author": "Rust join author",
127 "left": "{F0832BC9-1578-0A96-08AF-99344644FFB8},50",
128 "right": "{1E353497-5C26-0459-3C0C-0E8D29ED8C5A},12"
129 },
130 "remote_prefix": true,
131 "revision": "{392FD304-233C-4A9C-A90E-16EA8F916073},1",
132 "space": "{4432E3EC-5E5F-0A04-2108-E9A2C17BDBEC},1"
133 },
134 {
135 "case": "Split soft break",
136 "id": 12,
137 "intent": {
138 "author": "Rust join author",
139 "left": "{88189BA1-2F19-0282-05ED-A0B83153F143},44",
140 "right": "{C1568E82-003F-016B-041A-CA304E1A5138},12"
141 },
142 "remote_prefix": true,
143 "revision": "{D3D41323-E06E-4253-BAA5-B69A3AAA40E5},1",
144 "space": "{D8E15673-A45E-0914-291A-7767C3222F19},1"
145 }
146]
\ No newline at end of file
corpus/paragraph-edit/offline/joins/native/notebook/Open Notebook.onetoc2 created
Binary files /dev/null and b/corpus/paragraph-edit/offline/joins/native/notebook/Open Notebook.onetoc2 differ
corpus/paragraph-edit/offline/joins/native/notebook/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/offline/joins/native/notebook/synthetic.one differ
corpus/paragraph-edit/offline/joins/native/read/environment.json created+7
......@@ -0,0 +1,7 @@
1{
2 "powershell": "5.1.14409.1005",
3 "schema": "xs2010",
4 "hostname": "ONE-M6-37FF299E",
5 "cold": true,
6 "onenote": "14.0.4763.1000"
7}
corpus/paragraph-edit/offline/joins/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="{F8A11094-DE39-448C-A439-F86F24C7F052}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-08T10:28:24.000Z" color="#B49EDE"><one:Section name="synthetic" ID="{8FD8F049-66A6-05AD-1D2C-533AB948D293}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-08T10:28:24.000Z" color="#8AA8E4"><one:Page ID="{A39103CF-EF6D-46BC-9A4B-FBAB4570CE8C}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T10:28:24.000Z" pageLevel="1"/><one:Page ID="{39AB7858-1463-483C-AA05-EA50E3306FB6}{1}{B0}" name="Split middle" dateTime="2026-09-08T08:09:29.000Z" lastModifiedTime="2026-09-08T08:11:45.000Z" pageLevel="1"/><one:Page ID="{195DB73E-ACC5-4D10-AF1D-7A772E3D66D1}{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="{BE90D408-A97D-4ADA-B024-D142D6841DF0}{1}{B0}" name="Split start" dateTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T08:11:49.000Z" pageLevel="1"/><one:Page ID="{55F61532-B648-496C-90C6-13F2042A32B3}{1}{B0}" name="Split end" dateTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T08:11:51.000Z" pageLevel="1"/><one:Page ID="{2351F9F7-F1FF-4ED0-91BE-B6EC9B77BB54}{1}{B0}" name="Split empty" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:53.000Z" pageLevel="1"/><one:Page ID="{BDBF0077-D2B6-40BC-AE41-95A27835FCCE}{1}{B0}" name="Split parent" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:55.000Z" pageLevel="1"/><one:Page ID="{FB4BF9E7-C46E-4DCB-880B-440834C6B65A}{1}{B0}" name="Split child" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:57.000Z" pageLevel="1"/><one:Page ID="{53A7C8DF-5EA5-45E6-84A2-077DDB30322D}{1}{B0}" name="Split bullet" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:11:59.000Z" pageLevel="1"/><one:Page ID="{63277DC4-7CF4-4A01-8F26-648C7BF95E01}{1}{B0}" name="Split number" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:01.000Z" pageLevel="1"/><one:Page ID="{B4148EB0-E34D-407E-8122-3D060F84D3A8}{1}{B0}" name="Split tag" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:03.000Z" pageLevel="1"/><one:Page ID="{83BBDED7-FF2B-4006-8678-9AF693DE9E7C}{1}{B0}" name="Split cell" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:12:05.000Z" pageLevel="1"/><one:Page ID="{1F686B48-052A-4316-8E6A-043391876A89}{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="{B5486CBF-39FD-4D58-8115-F5881C42D137}{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/offline/joins/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="{195DB73E-ACC5-4D10-AF1D-7A772E3D66D1}{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="{983DB9D9-BF4A-409F-BEF9-544E6C1EA9D2}{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-08T10:26:22.000Z" objectID="{983DB9D9-BF4A-409F-BEF9-544E6C1EA9D2}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="122.4970779418945" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T10:26:22.000Z" objectID="{983DB9D9-BF4A-409F-BEF9-544E6C1EA9D2}{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="{983DB9D9-BF4A-409F-BEF9-544E6C1EA9D2}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/joins/native/read/page-001.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="{1F686B48-052A-4316-8E6A-043391876A89}{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="{4F91A69A-8E6D-4880-A29D-D3EC63F6B4D3}{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-08T10:26:23.000Z" objectID="{4F91A69A-8E6D-4880-A29D-D3EC63F6B4D3}{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-08T10:26:23.000Z" objectID="{4F91A69A-8E6D-4880-A29D-D3EC63F6B4D3}{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="{4F91A69A-8E6D-4880-A29D-D3EC63F6B4D3}{45}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/joins/native/read/page-002.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="{2351F9F7-F1FF-4ED0-91BE-B6EC9B77BB54}{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="{CCCF420A-73F3-42CF-AE84-D5AFE050041F}{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-08T10:26:22.000Z" objectID="{CCCF420A-73F3-42CF-AE84-D5AFE050041F}{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-08T10:26:22.000Z" objectID="{CCCF420A-73F3-42CF-AE84-D5AFE050041F}{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="{CCCF420A-73F3-42CF-AE84-D5AFE050041F}{46}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/joins/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="{39AB7858-1463-483C-AA05-EA50E3306FB6}{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="{18606DD4-CA9F-4559-8373-0820A30B1592}{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-08T10:26:22.000Z" objectID="{18606DD4-CA9F-4559-8373-0820A30B1592}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="122.4970779418945" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:30.000Z" lastModifiedTime="2026-09-08T10:26:22.000Z" objectID="{18606DD4-CA9F-4559-8373-0820A30B1592}{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="{18606DD4-CA9F-4559-8373-0820A30B1592}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/joins/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="{53A7C8DF-5EA5-45E6-84A2-077DDB30322D}{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="{4BECD91C-E608-48EF-BF77-5C44B0D2440D}{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-08T10:26:23.000Z" objectID="{4BECD91C-E608-48EF-BF77-5C44B0D2440D}{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-08T10:26:23.000Z" objectID="{4BECD91C-E608-48EF-BF77-5C44B0D2440D}{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="{4BECD91C-E608-48EF-BF77-5C44B0D2440D}{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="{4BECD91C-E608-48EF-BF77-5C44B0D2440D}{54}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/joins/native/read/page-005.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="{55F61532-B648-496C-90C6-13F2042A32B3}{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="{1D7E1E98-63D7-4D9A-89DB-473563C3E204}{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-08T10:26:22.000Z" objectID="{1D7E1E98-63D7-4D9A-89DB-473563C3E204}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="122.4970779418945" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:26:22.000Z" objectID="{1D7E1E98-63D7-4D9A-89DB-473563C3E204}{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="{1D7E1E98-63D7-4D9A-89DB-473563C3E204}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/joins/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="{63277DC4-7CF4-4A01-8F26-648C7BF95E01}{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="{13228374-8011-4B20-B8EF-DFB7AC378293}{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-08T10:26:23.000Z" objectID="{13228374-8011-4B20-B8EF-DFB7AC378293}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="122.4970932006836" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:26:23.000Z" objectID="{13228374-8011-4B20-B8EF-DFB7AC378293}{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="{13228374-8011-4B20-B8EF-DFB7AC378293}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/joins/native/read/page-007.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="{83BBDED7-FF2B-4006-8678-9AF693DE9E7C}{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="{370A16F2-B40C-4094-AFDF-EA6014E1BA28}{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-08T10:26:23.000Z" objectID="{370A16F2-B40C-4094-AFDF-EA6014E1BA28}{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-08T10:26:23.000Z" objectID="{370A16F2-B40C-4094-AFDF-EA6014E1BA28}{40}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-08T10:26:23.000Z" objectID="{370A16F2-B40C-4094-AFDF-EA6014E1BA28}{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="{370A16F2-B40C-4094-AFDF-EA6014E1BA28}{42}{B0}" lastModifiedTime="2026-09-08T10:26:23.000Z"><one:Cell lastModifiedTime="2026-09-08T10:26:23.000Z" objectID="{370A16F2-B40C-4094-AFDF-EA6014E1BA28}{43}{B0}"><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:26:23.000Z" objectID="{370A16F2-B40C-4094-AFDF-EA6014E1BA28}{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="{370A16F2-B40C-4094-AFDF-EA6014E1BA28}{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="{370A16F2-B40C-4094-AFDF-EA6014E1BA28}{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="{370A16F2-B40C-4094-AFDF-EA6014E1BA28}{62}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/joins/native/read/page-008.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="{A39103CF-EF6D-46BC-9A4B-FBAB4570CE8C}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T10:28:24.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="{C45DAB73-A418-450A-95B9-7770BAEC2BED}{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="{C3380384-5765-0E00-25ED-B47A7187591B}{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="{C45DAB73-A418-450A-95B9-7770BAEC2BED}{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/offline/joins/native/read/page-009.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="{B4148EB0-E34D-407E-8122-3D060F84D3A8}{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="{F023654C-88F4-4825-B1EF-F84ADC453CCA}{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-08T10:26:23.000Z" objectID="{F023654C-88F4-4825-B1EF-F84ADC453CCA}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="122.4970779418945" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:26:23.000Z" objectID="{F023654C-88F4-4825-B1EF-F84ADC453CCA}{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="{F023654C-88F4-4825-B1EF-F84ADC453CCA}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/joins/native/read/page-010.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="{B5486CBF-39FD-4D58-8115-F5881C42D137}{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="{93BA24DC-FBB3-4076-B79A-4AB3FBEF9516}{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="{93BA24DC-FBB3-4076-B79A-4AB3FBEF9516}{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="{93BA24DC-FBB3-4076-B79A-4AB3FBEF9516}{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="{E93B3496-ED8A-4630-8300-034A7F7B57B0}{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="{93BA24DC-FBB3-4076-B79A-4AB3FBEF9516}{47}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/joins/native/read/page-011.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="{BDBF0077-D2B6-40BC-AE41-95A27835FCCE}{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="{FFE9D728-7530-4F9A-92A6-BCC224392FB1}{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-08T10:26:23.000Z" objectID="{FFE9D728-7530-4F9A-92A6-BCC224392FB1}{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-08T10:26:23.000Z" objectID="{FFE9D728-7530-4F9A-92A6-BCC224392FB1}{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="{FFE9D728-7530-4F9A-92A6-BCC224392FB1}{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="{FFE9D728-7530-4F9A-92A6-BCC224392FB1}{53}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/joins/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="{BE90D408-A97D-4ADA-B024-D142D6841DF0}{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="{C6A7E470-E2C7-4D98-9720-C29FB0F95999}{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-08T10:26:22.000Z" objectID="{C6A7E470-E2C7-4D98-9720-C29FB0F95999}{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-08T10:26:22.000Z" objectID="{C6A7E470-E2C7-4D98-9720-C29FB0F95999}{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="{C6A7E470-E2C7-4D98-9720-C29FB0F95999}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/joins/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="{FB4BF9E7-C46E-4DCB-880B-440834C6B65A}{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="{720EF6FD-AAB8-4021-B186-92E4E16095F1}{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-08T10:26:23.000Z" objectID="{720EF6FD-AAB8-4021-B186-92E4E16095F1}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="149.4970703125" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:26:23.000Z" objectID="{720EF6FD-AAB8-4021-B186-92E4E16095F1}{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-08T10:26:23.000Z" objectID="{720EF6FD-AAB8-4021-B186-92E4E16095F1}{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="{720EF6FD-AAB8-4021-B186-92E4E16095F1}{53}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/joins/native/read/payloads.json created+1
......@@ -0,0 +1 @@
1../../../../before/read/payloads.json
\ No newline at end of file
corpus/paragraph-edit/offline/joins/native/run.json created+18
......@@ -0,0 +1,18 @@
1{
2 "notebook": "/Users/clo/dev/one/evidence/m10/offline-paragraph-native-01/joins/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/offline/joins/native/source.json created+8
......@@ -0,0 +1,8 @@
1[
2 {
3 "path": "synthetic.one",
4 "bytes": 150648,
5 "sha256": "2fd036cddb33de5e42f66cb6a6b7647cbfeac374a4055e9afc15bf3b0d6ec1b2",
6 "mtime_ns": 1788863184056850411
7 }
8]
corpus/paragraph-edit/offline/joins/native/teardown.json created+1
......@@ -0,0 +1 @@
1../../../joined/teardown.json
\ No newline at end of file
corpus/paragraph-edit/offline/splits/candidate/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/offline/splits/candidate/synthetic.one differ
corpus/paragraph-edit/offline/splits/manifest.json created+374
......@@ -0,0 +1,374 @@
1[
2 {
3 "case": "Split middle",
4 "id": 1,
5 "intent": {
6 "author": "Rust split author",
7 "created": 1473324660,
8 "guid": [
9 228,
10 60,
11 65,
12 246,
13 194,
14 244,
15 184,
16 76,
17 170,
18 5,
19 19,
20 130,
21 32,
22 122,
23 148,
24 241
25 ],
26 "offset": 2,
27 "text": "{DFE950EF-6BEB-0F5B-2403-7B74F1AE5002},44"
28 },
29 "remote_prefix": true,
30 "revision": "{BF0EE2EB-19D1-47B7-B28D-A9ABD19EBAAE},1",
31 "space": "{FE224563-B517-023E-0D75-9904B1952A26},1"
32 },
33 {
34 "case": "Split style boundary",
35 "id": 2,
36 "intent": {
37 "author": "Rust split author",
38 "created": 1473324660,
39 "guid": [
40 163,
41 91,
42 197,
43 73,
44 195,
45 225,
46 53,
47 68,
48 140,
49 152,
50 83,
51 168,
52 172,
53 124,
54 226,
55 213
56 ],
57 "offset": 4,
58 "text": "{5FB484E2-1E3E-0A9D-1989-271A3EBBEC42},44"
59 },
60 "remote_prefix": true,
61 "revision": "{167F0C99-70D5-4B0C-8339-4A5D4FA8D101},1",
62 "space": "{DED48A05-0DB1-0712-086D-09237C982341},1"
63 },
64 {
65 "case": "Split start",
66 "id": 3,
67 "intent": {
68 "author": "Rust split author",
69 "created": 1473324660,
70 "guid": [
71 135,
72 16,
73 214,
74 248,
75 73,
76 35,
77 2,
78 69,
79 148,
80 213,
81 125,
82 28,
83 43,
84 124,
85 81,
86 35
87 ],
88 "offset": 0,
89 "text": "{012ED94B-43B3-079A-3050-B1CBE25C1C09},44"
90 },
91 "remote_prefix": false,
92 "revision": "{F45BB0FF-3E33-4039-B5DE-C436C126C52E},1",
93 "space": "{7919E933-0809-00D8-1754-A21684215860},1"
94 },
95 {
96 "case": "Split end",
97 "id": 4,
98 "intent": {
99 "author": "Rust split author",
100 "created": 1473324660,
101 "guid": [
102 37,
103 220,
104 149,
105 138,
106 85,
107 3,
108 106,
109 69,
110 168,
111 172,
112 49,
113 82,
114 247,
115 99,
116 160,
117 139
118 ],
119 "offset": 22,
120 "text": "{DAF723A3-C2A3-0798-2EAB-34613166A794},44"
121 },
122 "remote_prefix": true,
123 "revision": "{B408F227-A7DE-4350-8068-D91005E0B8E5},1",
124 "space": "{927F2809-173C-036E-37B6-60A6568F7723},1"
125 },
126 {
127 "case": "Split empty",
128 "id": 5,
129 "intent": {
130 "author": "Rust split author",
131 "created": 1473324660,
132 "guid": [
133 232,
134 204,
135 91,
136 132,
137 180,
138 27,
139 239,
140 79,
141 158,
142 115,
143 213,
144 165,
145 40,
146 35,
147 189,
148 93
149 ],
150 "offset": 0,
151 "text": "{0B467F31-D287-08CD-09F4-A6FBB2F5418F},45"
152 },
153 "remote_prefix": false,
154 "revision": "{4F8EB252-1808-40AA-9B37-DB3DE1C5C3F2},1",
155 "space": "{E4D8C4CC-508B-04D2-36CE-C5B8C9D2FEC4},1"
156 },
157 {
158 "case": "Split parent",
159 "id": 6,
160 "intent": {
161 "author": "Rust split author",
162 "created": 1473324660,
163 "guid": [
164 54,
165 186,
166 114,
167 171,
168 44,
169 189,
170 95,
171 71,
172 135,
173 190,
174 71,
175 95,
176 23,
177 246,
178 37,
179 157
180 ],
181 "offset": 2,
182 "text": "{3860EA13-D444-0598-35D6-CF96769C6A21},44"
183 },
184 "remote_prefix": true,
185 "revision": "{A291D6A6-08A8-4E06-BF29-0479851E9E12},1",
186 "space": "{7A363D4C-73C2-0ABE-0931-E6F62A90B95E},1"
187 },
188 {
189 "case": "Split child",
190 "id": 7,
191 "intent": {
192 "author": "Rust split author",
193 "created": 1473324660,
194 "guid": [
195 160,
196 247,
197 235,
198 152,
199 30,
200 98,
201 166,
202 77,
203 157,
204 204,
205 141,
206 45,
207 114,
208 151,
209 225,
210 195
211 ],
212 "offset": 2,
213 "text": "{B587CBC6-0BCC-0A23-16F6-E1B0B3C5D061},49"
214 },
215 "remote_prefix": true,
216 "revision": "{FFE1F804-FA27-4EFD-B53C-9E1D07384DA6},1",
217 "space": "{3CC2C4DC-651A-07C9-2F7B-375C6663F3CA},1"
218 },
219 {
220 "case": "Split bullet",
221 "id": 8,
222 "intent": {
223 "author": "Rust split author",
224 "created": 1473324660,
225 "guid": [
226 53,
227 245,
228 214,
229 76,
230 159,
231 56,
232 134,
233 76,
234 178,
235 90,
236 131,
237 182,
238 74,
239 114,
240 31,
241 24
242 ],
243 "offset": 2,
244 "text": "{8C65E427-477C-02ED-1807-2F10E277019D},44"
245 },
246 "remote_prefix": true,
247 "revision": "{822FBEAD-035E-47B5-9ED6-CDAE96AA5837},1",
248 "space": "{942EF5E4-FFD1-0FE4-23D2-7429899577BD},1"
249 },
250 {
251 "case": "Split number",
252 "id": 9,
253 "intent": {
254 "author": "Rust split author",
255 "created": 1473324661,
256 "guid": [
257 196,
258 133,
259 251,
260 251,
261 193,
262 175,
263 8,
264 65,
265 175,
266 180,
267 126,
268 16,
269 31,
270 185,
271 237,
272 66
273 ],
274 "offset": 2,
275 "text": "{D4ABBE4F-2165-0122-1F9F-ACE3FE92C703},44"
276 },
277 "remote_prefix": true,
278 "revision": "{F5ABC207-F9C7-45A3-B213-AA675126A592},1",
279 "space": "{A4AE40FF-DD80-0003-2856-17D8295C1B91},1"
280 },
281 {
282 "case": "Split tag",
283 "id": 10,
284 "intent": {
285 "author": "Rust split author",
286 "created": 1473324661,
287 "guid": [
288 40,
289 122,
290 110,
291 77,
292 185,
293 255,
294 122,
295 69,
296 128,
297 15,
298 14,
299 166,
300 54,
301 156,
302 154,
303 91
304 ],
305 "offset": 2,
306 "text": "{37AA5877-2980-0227-169F-8B1E8EE0795A},44"
307 },
308 "remote_prefix": true,
309 "revision": "{8021DAB3-4163-49C4-A7A1-898ACCAB3981},1",
310 "space": "{739DB38B-4239-0A7C-2652-4E525D219638},1"
311 },
312 {
313 "case": "Split cell",
314 "id": 11,
315 "intent": {
316 "author": "Rust split author",
317 "created": 1473324661,
318 "guid": [
319 150,
320 222,
321 89,
322 134,
323 80,
324 167,
325 167,
326 67,
327 179,
328 23,
329 93,
330 176,
331 27,
332 184,
333 119,
334 185
335 ],
336 "offset": 2,
337 "text": "{F0832BC9-1578-0A96-08AF-99344644FFB8},50"
338 },
339 "remote_prefix": true,
340 "revision": "{0E8285DD-840A-4C8E-BFDB-CA3589436ECD},1",
341 "space": "{4432E3EC-5E5F-0A04-2108-E9A2C17BDBEC},1"
342 },
343 {
344 "case": "Split soft break",
345 "id": 12,
346 "intent": {
347 "author": "Rust split author",
348 "created": 1473324661,
349 "guid": [
350 199,
351 62,
352 94,
353 81,
354 29,
355 157,
356 236,
357 77,
358 159,
359 34,
360 22,
361 137,
362 163,
363 57,
364 215,
365 79
366 ],
367 "offset": 2,
368 "text": "{88189BA1-2F19-0282-05ED-A0B83153F143},44"
369 },
370 "remote_prefix": true,
371 "revision": "{89D96DD2-2D50-433E-B54B-50018E3C2108},1",
372 "space": "{D8E15673-A45E-0914-291A-7767C3222F19},1"
373 }
374]
\ No newline at end of file
corpus/paragraph-edit/offline/splits/native/notebook/Open Notebook.onetoc2 created
Binary files /dev/null and b/corpus/paragraph-edit/offline/splits/native/notebook/Open Notebook.onetoc2 differ
corpus/paragraph-edit/offline/splits/native/notebook/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/offline/splits/native/notebook/synthetic.one differ
corpus/paragraph-edit/offline/splits/native/read/environment.json created+7
......@@ -0,0 +1,7 @@
1{
2 "powershell": "5.1.14409.1005",
3 "schema": "xs2010",
4 "hostname": "ONE-M6-97095A74",
5 "cold": true,
6 "onenote": "14.0.4763.1000"
7}
corpus/paragraph-edit/offline/splits/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="{FC7A3D7D-31CC-4498-ACEC-39115E6C30C4}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-08T10:28:28.000Z" color="#B49EDE"><one:Section name="synthetic" ID="{91A773B2-B056-0831-17F3-650564BEEE2D}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-08T10:28:28.000Z" color="#8AA8E4"><one:Page ID="{BDEE8034-399D-4B20-9094-CD949886F232}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T10:28:28.000Z" pageLevel="1"/><one:Page ID="{27D4FBA3-C293-45A0-A0DA-DC6F3EC65308}{1}{B0}" name="Split middle" dateTime="2026-09-08T08:09:29.000Z" lastModifiedTime="2026-09-08T10:28:28.000Z" pageLevel="1"/><one:Page ID="{072234C5-7A35-408C-A5C2-4C48F3CB5A6F}{1}{B0}" name="Split style boundary" dateTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T10:28:28.000Z" pageLevel="1"/><one:Page ID="{A0EF57F3-7F8D-4746-BAFB-E77D0B72214E}{1}{B0}" name="Split start" dateTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T10:28:28.000Z" pageLevel="1"/><one:Page ID="{4B8996C9-60B8-44F0-9A19-25CDD9DC0E0D}{1}{B0}" name="Split end" dateTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T10:28:28.000Z" pageLevel="1"/><one:Page ID="{3D2E7A0C-270F-434C-9B61-80D3468187EA}{1}{B0}" name="Split empty" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:28:28.000Z" pageLevel="1"/><one:Page ID="{A3C0838C-0446-4D20-A49E-A39DA5C3C070}{1}{B0}" name="Split parent" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:28:28.000Z" pageLevel="1"/><one:Page ID="{E5347A1C-129E-4057-82D4-7237E9308AE4}{1}{B0}" name="Split child" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:28:28.000Z" pageLevel="1"/><one:Page ID="{4DD84B24-8855-487A-8E7D-314206C60E93}{1}{B0}" name="Split bullet" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:28:28.000Z" pageLevel="1"/><one:Page ID="{7D58FE3F-AA04-479D-85F9-52B3A60F62BF}{1}{B0}" name="Split number" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:28:28.000Z" pageLevel="1"/><one:Page ID="{AA6B0D4B-35BD-4DE2-8BFD-0B39D272EF16}{1}{B0}" name="Split tag" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:28:28.000Z" pageLevel="1"/><one:Page ID="{9DC45D2C-29DB-4D9A-8CA7-ACC94E28A2C2}{1}{B0}" name="Split cell" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:28:28.000Z" pageLevel="1"/><one:Page ID="{0117E8B3-D3DA-4E8A-84B5-320C4C715637}{1}{B0}" name="Split soft break" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:28:28.000Z" pageLevel="1"/><one:Page ID="{AB37EF44-EF0D-40C4-8BCA-C3B7C1B4ED89}{1}{B0}" name="Split before hyperlink" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:28:28.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/paragraph-edit/offline/splits/native/read/page-000.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="{0117E8B3-D3DA-4E8A-84B5-320C4C715637}{1}{B0}" name="Split soft break" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:28:28.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:09:34.000Z" objectID="{51EE2561-589D-451C-A842-E5D3BE00886D}{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 split author" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{51EE2561-589D-451C-A842-E5D3BE00886D}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="468.0" height="53.71086883544922"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{51EE2561-589D-451C-A842-E5D3BE00886D}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[☂Bo]]></one:T></one:OE><one:OE author="Rust split author" creationTime="2026-09-08T08:51:01.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{88A88007-EA99-0A72-328D-53E22C6AAE61}{1}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[ld<br>
3soft break]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:09:34.000Z" objectID="{51EE2561-589D-451C-A842-E5D3BE00886D}{45}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/splits/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="{072234C5-7A35-408C-A5C2-4C48F3CB5A6F}{1}{B0}" name="Split style boundary" dateTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T10:28:28.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="{86423A22-69BA-4D03-B426-6271B1E8956C}{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 split author" lastModifiedTime="2026-09-08T10:26:19.000Z" objectID="{86423A22-69BA-4D03-B426-6271B1E8956C}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="115.6704635620117" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T10:26:19.000Z" objectID="{86423A22-69BA-4D03-B426-6271B1E8956C}{40}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[<span
3style='font-weight:bold'>☂Bold</span>]]></one:T></one:OE><one:OE author="Rust split author" creationTime="2026-09-08T08:51:00.000Z" lastModifiedTime="2026-09-08T10:26:19.000Z" objectID="{9033E563-9647-03AB-2137-16C3232F9BFB}{1}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<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-08T08:09:32.000Z" lastModifiedTime="2026-09-08T08:09:32.000Z" objectID="{86423A22-69BA-4D03-B426-6271B1E8956C}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/splits/native/read/page-002.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="{27D4FBA3-C293-45A0-A0DA-DC6F3EC65308}{1}{B0}" name="Split middle" dateTime="2026-09-08T08:09:29.000Z" lastModifiedTime="2026-09-08T10:28:28.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="{061FEE2F-1C6F-48C5-89AC-3E1F7EFD292C}{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 split author" lastModifiedTime="2026-09-08T10:26:19.000Z" objectID="{061FEE2F-1C6F-48C5-89AC-3E1F7EFD292C}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="115.6704635620117" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:30.000Z" lastModifiedTime="2026-09-08T10:26:19.000Z" objectID="{061FEE2F-1C6F-48C5-89AC-3E1F7EFD292C}{40}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[<span
3style='font-weight:bold'>☂Bo</span>]]></one:T></one:OE><one:OE author="Rust split author" creationTime="2026-09-08T08:51:00.000Z" lastModifiedTime="2026-09-08T10:26:19.000Z" objectID="{2FB78224-8346-0B26-07AA-56E9AF29EDDF}{1}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
4style='font-weight:bold' lang=en-US>ld</span><span lang=en-US> </span><span
5style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
6style='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="{061FEE2F-1C6F-48C5-89AC-3E1F7EFD292C}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/splits/native/read/page-003.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="{3D2E7A0C-270F-434C-9B61-80D3468187EA}{1}{B0}" name="Split empty" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:28:28.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:09:33.000Z" objectID="{D2B0C1F1-A503-4F53-A45B-E3903DA638A1}{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 split author" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{D2B0C1F1-A503-4F53-A45B-E3903DA638A1}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="115.6704635620117" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{D2B0C1F1-A503-4F53-A45B-E3903DA638A1}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE><one:OE author="Rust split author" creationTime="2026-09-08T08:51:00.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{5DAD7228-6C30-0871-33DC-90CEA770C473}{1}{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:09:33.000Z" objectID="{D2B0C1F1-A503-4F53-A45B-E3903DA638A1}{46}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/splits/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="{4B8996C9-60B8-44F0-9A19-25CDD9DC0E0D}{1}{B0}" name="Split end" dateTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T10:28:28.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:09:33.000Z" objectID="{03019D63-B527-4006-8304-710ABE35DEBA}{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 split author" lastModifiedTime="2026-09-08T10:26:19.000Z" objectID="{03019D63-B527-4006-8304-710ABE35DEBA}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="122.4970779418945" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:26:19.000Z" objectID="{03019D63-B527-4006-8304-710ABE35DEBA}{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 author="Rust split author" creationTime="2026-09-08T08:51:00.000Z" lastModifiedTime="2026-09-08T10:26:19.000Z" objectID="{536362E5-74D1-02F4-0503-74397830D9A5}{1}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:11.0pt;color:#123456"><one:T><![CDATA[]]></one:T></one:OE><one:OE lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T08:09:33.000Z" objectID="{03019D63-B527-4006-8304-710ABE35DEBA}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/splits/native/read/page-005.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="{4DD84B24-8855-487A-8E7D-314206C60E93}{1}{B0}" name="Split bullet" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:28:28.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:09:33.000Z" objectID="{55935AE7-30F8-4573-B5A8-6A7B6D2478B3}{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 split author" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{55935AE7-30F8-4573-B5A8-6A7B6D2478B3}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="135.3173980712891" height="53.71086883544922"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{55935AE7-30F8-4573-B5A8-6A7B6D2478B3}{40}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:11.0pt"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[<span
3style='font-weight:bold'>☂Bo</span>]]></one:T></one:OE><one:OE author="Rust split author" creationTime="2026-09-08T08:51:00.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{95204BF5-4F1B-0B18-1FF5-C6DDC5216636}{1}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[<span
4style='font-weight:bold' lang=en-US>ld</span><span lang=en-US> </span><span
5style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
6style='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:09:33.000Z" objectID="{55935AE7-30F8-4573-B5A8-6A7B6D2478B3}{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:09:33.000Z" objectID="{55935AE7-30F8-4573-B5A8-6A7B6D2478B3}{54}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/splits/native/read/page-006.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="{7D58FE3F-AA04-479D-85F9-52B3A60F62BF}{1}{B0}" name="Split number" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:28:28.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:09:34.000Z" objectID="{0D5D008F-56E1-46BC-B230-E98871C1BE2D}{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 split author" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{0D5D008F-56E1-46BC-B230-E98871C1BE2D}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="115.6704635620117" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{0D5D008F-56E1-46BC-B230-E98871C1BE2D}{40}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:11.0pt"><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'>☂Bo</span>]]></one:T></one:OE><one:OE author="Rust split author" creationTime="2026-09-08T08:51:01.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{220D3B04-D845-0696-021B-3B7B90EA946C}{1}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="IV."/></one:List><one:T><![CDATA[<span
4style='font-weight:bold' lang=en-US>ld</span><span lang=en-US> </span><span
5style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
6style='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:09:34.000Z" objectID="{0D5D008F-56E1-46BC-B230-E98871C1BE2D}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/splits/native/read/page-007.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="{9DC45D2C-29DB-4D9A-8CA7-ACC94E28A2C2}{1}{B0}" name="Split cell" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:28:28.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:09:34.000Z" objectID="{29759509-62FC-4D08-A500-DC5FC9178696}{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-08T10:26:20.000Z" objectID="{29759509-62FC-4D08-A500-DC5FC9178696}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="254.9588165283203" height="48.80315780639648"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{29759509-62FC-4D08-A500-DC5FC9178696}{40}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{29759509-62FC-4D08-A500-DC5FC9178696}{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="{29759509-62FC-4D08-A500-DC5FC9178696}{42}{B0}" lastModifiedTime="2026-09-08T10:26:20.000Z"><one:Cell lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{29759509-62FC-4D08-A500-DC5FC9178696}{43}{B0}"><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{29759509-62FC-4D08-A500-DC5FC9178696}{46}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[<span
3style='font-weight:bold'>☂Bo</span>]]></one:T></one:OE><one:OE author="Rust split author" creationTime="2026-09-08T08:51:01.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{5FAF6056-D0D4-0439-1EB8-18DB94EB0E97}{1}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
4style='font-weight:bold' lang=en-US>ld</span><span lang=en-US> </span><span
5style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
6style='color:#123456' lang=x-none>tail</span>]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-08T08:09:37.000Z" objectID="{29759509-62FC-4D08-A500-DC5FC9178696}{54}{B0}" lastModifiedBy="virtual" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:09:34.000Z" objectID="{29759509-62FC-4D08-A500-DC5FC9178696}{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:09:34.000Z" objectID="{29759509-62FC-4D08-A500-DC5FC9178696}{62}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/splits/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="{A0EF57F3-7F8D-4746-BAFB-E77D0B72214E}{1}{B0}" name="Split start" dateTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T10:28:28.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="{D8D8678B-3437-4004-9DFF-F4A06D0F6527}{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 split author" lastModifiedTime="2026-09-08T10:26:19.000Z" objectID="{D8D8678B-3437-4004-9DFF-F4A06D0F6527}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="116.921875" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:32.000Z" lastModifiedTime="2026-09-08T10:26:19.000Z" objectID="{D8D8678B-3437-4004-9DFF-F4A06D0F6527}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE><one:OE author="Rust split author" creationTime="2026-09-08T08:51:00.000Z" lastModifiedTime="2026-09-08T10:26:19.000Z" objectID="{2120AE47-54CD-029C-397A-3877A42F280D}{1}{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="{D8D8678B-3437-4004-9DFF-F4A06D0F6527}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/splits/native/read/page-009.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="{A3C0838C-0446-4D20-A49E-A39DA5C3C070}{1}{B0}" name="Split parent" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:28:28.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:09:33.000Z" objectID="{E19654D3-A3C0-4206-9879-8AFDF9CF130F}{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 split author" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{E19654D3-A3C0-4206-9879-8AFDF9CF130F}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="135.3173980712891" height="53.71086883544922"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{E19654D3-A3C0-4206-9879-8AFDF9CF130F}{40}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[<span
3style='font-weight:bold'>☂Bo</span>]]></one:T></one:OE><one:OE author="Rust split author" creationTime="2026-09-08T08:51:00.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{728404F6-CAA8-00C1-2A11-023498A55CB3}{1}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
4style='font-weight:bold' lang=en-US>ld</span><span lang=en-US> </span><span
5style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
6style='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:09:33.000Z" objectID="{E19654D3-A3C0-4206-9879-8AFDF9CF130F}{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:09:33.000Z" objectID="{E19654D3-A3C0-4206-9879-8AFDF9CF130F}{53}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/splits/native/read/page-010.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="{AA6B0D4B-35BD-4DE2-8BFD-0B39D272EF16}{1}{B0}" name="Split tag" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:28:28.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:09:34.000Z" objectID="{EE5CE6B7-5E04-45B9-BB30-CE7501B30074}{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 split author" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{EE5CE6B7-5E04-45B9-BB30-CE7501B30074}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="115.6704788208008" height="40.28315734863281"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{EE5CE6B7-5E04-45B9-BB30-CE7501B30074}{40}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:11.0pt"><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'>☂Bo</span>]]></one:T></one:OE><one:OE author="Rust split author" creationTime="2026-09-08T08:51:01.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{9498C4E8-883D-02E4-2DA0-4BCDB9CFE375}{1}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
4style='font-weight:bold' lang=en-US>ld</span><span lang=en-US> </span><span
5style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
6style='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:09:34.000Z" objectID="{EE5CE6B7-5E04-45B9-BB30-CE7501B30074}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/splits/native/read/page-011.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="{AB37EF44-EF0D-40C4-8BCA-C3B7C1B4ED89}{1}{B0}" name="Split before hyperlink" dateTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T10:28:28.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:09:34.000Z" objectID="{8DC5A727-2D43-4DEA-BD45-7C8C2619A9A8}{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:09:37.000Z" objectID="{8DC5A727-2D43-4DEA-BD45-7C8C2619A9A8}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="130.6343231201172" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:34.000Z" lastModifiedTime="2026-09-08T08:09:34.000Z" objectID="{8DC5A727-2D43-4DEA-BD45-7C8C2619A9A8}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Before <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:09:34.000Z" objectID="{8DC5A727-2D43-4DEA-BD45-7C8C2619A9A8}{47}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/splits/native/read/page-012.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="{BDEE8034-399D-4B20-9094-CD949886F232}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T10:28:28.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="{DA222888-72E8-4896-9F66-414F671A1753}{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="{DD47807F-8195-039C-2F32-8245AC7165A5}{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="{DA222888-72E8-4896-9F66-414F671A1753}{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/offline/splits/native/read/page-013.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="{E5347A1C-129E-4057-82D4-7237E9308AE4}{1}{B0}" name="Split child" dateTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:28:28.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:09:33.000Z" objectID="{6C717506-7C48-4DBD-BB59-A4DB3C96A94F}{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-08T10:26:20.000Z" objectID="{6C717506-7C48-4DBD-BB59-A4DB3C96A94F}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="131.8423614501953" height="53.71086883544922"/><one:OEChildren><one:OE creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{6C717506-7C48-4DBD-BB59-A4DB3C96A94F}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved parent]]></one:T><one:OEChildren><one:OE lastModifiedBy="Rust split author" creationTime="2026-09-08T08:09:33.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{6C717506-7C48-4DBD-BB59-A4DB3C96A94F}{45}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[<span
3style='font-weight:bold'>☂Bo</span>]]></one:T></one:OE><one:OE author="Rust split author" lastModifiedBy="Rust split author" creationTime="2026-09-08T08:51:00.000Z" lastModifiedTime="2026-09-08T10:26:20.000Z" objectID="{411D4960-159A-0A38-3063-C846FDC498ED}{1}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
4style='font-weight:bold' lang=en-US>ld</span><span lang=en-US> </span><span
5style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span
6style='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:09:33.000Z" objectID="{6C717506-7C48-4DBD-BB59-A4DB3C96A94F}{53}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/splits/native/read/payloads.json created+1
......@@ -0,0 +1 @@
1../../../../before/read/payloads.json
\ No newline at end of file
corpus/paragraph-edit/offline/splits/native/run.json created+18
......@@ -0,0 +1,18 @@
1{
2 "notebook": "/Users/clo/dev/one/evidence/m10/offline-paragraph-native-01/splits/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/offline/splits/native/source.json created+8
......@@ -0,0 +1,8 @@
1[
2 {
3 "path": "synthetic.one",
4 "bytes": 119736,
5 "sha256": "ea8264bf8df579b69755c47918ab45f77a7fdd4a4cc7564ab84813220def9322",
6 "mtime_ns": 1788863181033354746
7 }
8]
corpus/paragraph-edit/offline/splits/native/teardown.json created+1
......@@ -0,0 +1 @@
1../../../joined/teardown.json
\ No newline at end of file
corpus/paragraph-edit/offline/tags/candidate/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/offline/tags/candidate/synthetic.one differ
corpus/paragraph-edit/offline/tags/manifest.json created+38
......@@ -0,0 +1,38 @@
1[
2 {
3 "case": "Join empty left right tag",
4 "id": 1,
5 "intent": {
6 "author": "Rust join author",
7 "left": "{0D11242E-6A38-0F74-261A-A6FC444FA262},45",
8 "right": "{0D11242E-6A38-0F74-261A-A6FC444FA262},50"
9 },
10 "remote_prefix": false,
11 "revision": "{E906B028-AA9F-4772-877F-CCBED45A8645},1",
12 "space": "{FC64EF0A-5CCB-0557-20E0-6C847015B691},1"
13 },
14 {
15 "case": "Join empty left two tags",
16 "id": 2,
17 "intent": {
18 "author": "Rust join author",
19 "left": "{84061027-77E3-03F1-3E0D-A8F744B19C18},45",
20 "right": "{84061027-77E3-03F1-3E0D-A8F744B19C18},51"
21 },
22 "remote_prefix": false,
23 "revision": "{98BB5C37-4F68-408F-AE2F-4D9EFD75022F},1",
24 "space": "{A05CB25D-C95D-043F-3B9A-CE333AD171AD},1"
25 },
26 {
27 "case": "Join different tags",
28 "id": 3,
29 "intent": {
30 "author": "Rust join author",
31 "left": "{4FBF7D4A-6F4D-048E-0913-5BD9A184C25B},44",
32 "right": "{4FBF7D4A-6F4D-048E-0913-5BD9A184C25B},50"
33 },
34 "remote_prefix": true,
35 "revision": "{AA6105EC-5A3B-4038-887A-DDADB2911713},1",
36 "space": "{6F0F6556-17D3-03AF-20EF-C465A856A153},1"
37 }
38]
\ No newline at end of file
corpus/paragraph-edit/offline/tags/native/notebook/Open Notebook.onetoc2 created
Binary files /dev/null and b/corpus/paragraph-edit/offline/tags/native/notebook/Open Notebook.onetoc2 differ
corpus/paragraph-edit/offline/tags/native/notebook/synthetic.one created
Binary files /dev/null and b/corpus/paragraph-edit/offline/tags/native/notebook/synthetic.one differ
corpus/paragraph-edit/offline/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-C920A6E7",
5 "cold": true,
6 "onenote": "14.0.4763.1000"
7}
corpus/paragraph-edit/offline/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="{769450AB-E894-4A9F-859F-9B2FD539EE85}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-08T10:28:24.000Z" color="#B49EDE"><one:Section name="synthetic" ID="{5CE18FBE-653D-0A0E-2F18-E0E57F677EAC}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-08T10:28:24.000Z" color="#8AA8E4"><one:Page ID="{70A87C38-ECF6-491F-A87F-4874835F62B3}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T10:28:24.000Z" pageLevel="1"/><one:Page ID="{E8D4ADC6-FE24-40F6-B5A4-AC0FE49F5F3E}{1}{B0}" name="Join empty left right tag" dateTime="2026-09-08T09:33:01.000Z" lastModifiedTime="2026-09-08T10:28:24.000Z" pageLevel="1"/><one:Page ID="{B4ECF091-6BB2-419E-AEDE-0EB8AE5B9802}{1}{B0}" name="Join empty left two tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T10:28:24.000Z" pageLevel="1"/><one:Page ID="{7BBF279A-B53C-460E-B5AB-04EE3CDC48FC}{1}{B0}" name="Join different tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T10:28:24.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/paragraph-edit/offline/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="{70A87C38-ECF6-491F-A87F-4874835F62B3}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-08T10:28:24.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="{1764D484-A783-4AA9-A78D-C4AF7CC387D2}{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="{10017C73-54FE-01A3-17D9-07A5B7A8F524}{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="{1764D484-A783-4AA9-A78D-C4AF7CC387D2}{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/offline/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="{7BBF279A-B53C-460E-B5AB-04EE3CDC48FC}{1}{B0}" name="Join different tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T10:28:24.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="{5B0F3F86-CDA2-412F-9C57-9B52350E2BF4}{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-08T10:26:25.000Z" objectID="{5B0F3F86-CDA2-412F-9C57-9B52350E2BF4}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="139.63623046875" height="26.85543441772461"/><one:OEChildren><one:OE creationTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T10:26:25.000Z" objectID="{5B0F3F86-CDA2-412F-9C57-9B52350E2BF4}{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="{5B0F3F86-CDA2-412F-9C57-9B52350E2BF4}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/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="{B4ECF091-6BB2-419E-AEDE-0EB8AE5B9802}{1}{B0}" name="Join empty left two tags" dateTime="2026-09-08T09:33:04.000Z" lastModifiedTime="2026-09-08T10:28:24.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="{90B652EB-D50C-4650-AB49-687CD03B75B7}{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-08T10:26:25.000Z" objectID="{90B652EB-D50C-4650-AB49-687CD03B75B7}{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-08T10:26:25.000Z" objectID="{90B652EB-D50C-4650-AB49-687CD03B75B7}{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="{90B652EB-D50C-4650-AB49-687CD03B75B7}{56}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/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="{E8D4ADC6-FE24-40F6-B5A4-AC0FE49F5F3E}{1}{B0}" name="Join empty left right tag" dateTime="2026-09-08T09:33:01.000Z" lastModifiedTime="2026-09-08T10:28:24.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="{19A166E2-C8D7-4AD5-B35E-6677D0C54BCD}{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-08T10:26:24.000Z" objectID="{19A166E2-C8D7-4AD5-B35E-6677D0C54BCD}{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-08T10:26:24.000Z" objectID="{19A166E2-C8D7-4AD5-B35E-6677D0C54BCD}{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="{19A166E2-C8D7-4AD5-B35E-6677D0C54BCD}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Preserved sibling]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/paragraph-edit/offline/tags/native/read/payloads.json created+1
......@@ -0,0 +1 @@
1../../../../before/read/payloads.json
\ No newline at end of file
corpus/paragraph-edit/offline/tags/native/run.json created+18
......@@ -0,0 +1,18 @@
1{
2 "notebook": "/Users/clo/dev/one/evidence/m10/offline-paragraph-native-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/offline/tags/native/source.json created+8
......@@ -0,0 +1,8 @@
1[
2 {
3 "path": "synthetic.one",
4 "bytes": 42688,
5 "sha256": "4d6efc70b84628a281d1a64dbb3cf96d7c682d558ed7fbef708b493c51b01794",
6 "mtime_ns": 1788863185189921064
7 }
8]
corpus/paragraph-edit/offline/tags/native/teardown.json created+1
......@@ -0,0 +1 @@
1../../../joined/teardown.json
\ No newline at end of file
crates/onestore-offline/README.md+24-5
......@@ -1,7 +1,7 @@
11# onestore-offline
22
33Durable local editing for a OneNote file, in an optional sibling crate. The current
4foundation stores a complete working image and typed text, insertion and formatting intents in a local
4foundation stores a complete working image and typed editing intents in a local
55SQLite database. `sync_once` provides a reconciliation step and `start_sync` owns
66automatic polling and reconnects. Local success does not
77acknowledge publication to a shared notebook.
......@@ -29,12 +29,30 @@ assert_eq!(pending.last().map(|edit| edit.id), local_id);
2929intent identities. `Insertion::with_formatting` queues text and
3030its styles as one intent and one publication. Keep that value across retries; its `text_object()` identifies
3131the new text for subsequent offline edits. Pending entries expose
32`Operation::Text(TextEdit)`, `Operation::Insert(Insertion)` or
33`Operation::Format(FormatEdit)` through their
32`Operation::Text(TextEdit)`, `Operation::Insert(Insertion)`,
33`Operation::Format(FormatEdit)`, `Operation::Split(SplitEdit)` and
34`Operation::Join(JoinEdit)` through their
3435`operation` field. Synchronization applies these in queue order, so an inserted
3536outline can precede its paragraphs and their later edits. Missing anchors or
3637existing insertion identities preserve a conflict and the complete local image.
3738
39`split` and `join` accept the core `ParagraphSplit` and `ParagraphJoin` intents.
40Splits retain allocated identities when the original UTF-16 boundary rebases;
41dependent edits can address `ParagraphSplit::text_object()` immediately after
42local acknowledgement. Both operations retain observed parent paths, children,
43indentation, paragraph/list state and tags. Changed structure produces
44`StructureChanged`. Remote text changes must leave unambiguous boundary mappings;
45current remote character styles are preserved. Reconciliation does not silently
46discard a newly added right tag or move an unobserved child.
47
48`rebase_conflict` accepts a zero-length reviewed range for a split, preserving its
49allocated identities. `rebase_join_conflict(id, local, remote)` reviews the original
50join against current images. It rejects a change in which text identity survives,
51because dependent edits still address that identity. As with other conflict
52reviews, stale images, pending operations and uncertain attempts cannot be rebased.
53An uncertain structural operation requires its original attempted revision for
54confirmation; matching text or structure does not establish a receipt.
55
3856`rebase_paragraph_conflict(id, local, remote, parent, before)` and
3957`rebase_outline_conflict(id, local, remote, page, x, y)` accept reviewed replacement
4058placements for the oldest insertion conflict. They preserve creation time, text,
......@@ -70,8 +88,9 @@ requested value is accepted. Enabling superscript or subscript also checks the
7088opposite attribute that the operation clears. If the remote image already satisfies
7189the whole operation, guarded confirmation still precedes a durable receipt.
7290
73Recognized version-one through version-three caches migrate transactionally to the typed
74queue. The migration retains images, local IDs, publication attempts, conflicts,
91Recognized earlier caches migrate transactionally to version six, which adds
92paragraph split/join intents and structural conflicts. The migration retains
93images, local IDs, publication attempts, conflicts,
7594receipts and the autoincrement sequence; it does not reuse acknowledged IDs when
7695the pending queue is empty.
7796
crates/onestore-offline/examples/cache_probe.rs+3
......@@ -138,6 +138,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
138138 font_size = Some(*value);
139139 *value as u64 - 6
140140 }
141 onestore_offline::Operation::Split(_) | onestore_offline::Operation::Join(_) => {
142 panic!("This fixture does not generate paragraph boundary edits")
143 }
141144 };
142145 assert!(operations.last().is_none_or(|last| *last < operation));
143146 assert!(ids.last().is_none_or(|last| *last < pending.id));
crates/onestore-offline/src/lib.rs+5-1
......@@ -10,10 +10,12 @@ use std::{fs::OpenOptions, io, ops::Range, path::Path, sync::Mutex, time::Durati
1010
1111mod assets;
1212mod formatting;
13mod paragraph;
1314mod rebase;
1415mod recovery;
1516mod schema;
1617pub use formatting::FormatEdit;
18pub use paragraph::{JoinEdit, SplitEdit};
1719pub use recovery::{Recovery, RecoverySummary};
1820mod sync;
1921pub use sync::{ConflictKind, EditStatus, Remote};
......@@ -45,7 +47,7 @@ pub enum Error {
4547type Result<T> = std::result::Result<T, Error>;
4648
4749const APPLICATION_ID: u32 = 0x4f4e454f;
48const SCHEMA_VERSION: u32 = 5;
50const SCHEMA_VERSION: u32 = 6;
4951
5052/// Text and its observed precondition, retained across cache reopen and rebasing.
5153#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
......@@ -63,6 +65,8 @@ pub enum Operation {
6365 Text(TextEdit),
6466 Insert(Insertion),
6567 Format(FormatEdit),
68 Split(SplitEdit),
69 Join(JoinEdit),
6670}
6771
6872/// A locally acknowledged intent; its ID remains stable across cache reopen.
crates/onestore-offline/src/paragraph.rs created+241
......@@ -0,0 +1,241 @@
1use super::*;
2use onestore::{ParagraphJoin, ParagraphSplit};
3use serde::{Deserialize, Serialize};
4use serde_json::{Value, json};
5use std::collections::{BTreeMap, BTreeSet};
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
8#[serde(deny_unknown_fields)]
9struct Observed {
10 text: String,
11 structure: Value,
12}
13
14/// A stable split intent and the paragraph state observed before local publication.
15#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
16#[serde(deny_unknown_fields)]
17pub struct SplitEdit {
18 pub intent: ParagraphSplit,
19 observed: Observed,
20}
21
22/// A join intent and both paragraphs' observed text, placement and tag ownership.
23#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
24#[serde(deny_unknown_fields)]
25pub struct JoinEdit {
26 pub intent: ParagraphJoin,
27 observed: [Observed; 2],
28}
29
30fn observe(source: &[u8], space: ExGuid, texts: &[ExGuid]) -> Result<Option<Vec<Observed>>> {
31 let store = Store::parse(source)?;
32 let index = RevisionIndex::parse(&store)?;
33 let document = Document::parse(&index)?;
34 let Some(section) = document.spaces.get(&space) else {
35 return Ok(None);
36 };
37 let Some(view) = section
38 .contexts
39 .get(&ExGuid::default())
40 .and_then(|id| section.revisions.get(id))
41 else {
42 return Ok(None);
43 };
44 let mut pending: Vec<_> = document
45 .pages()?
46 .into_iter()
47 .filter_map(|(sid, page)| (sid == space).then_some(page))
48 .collect();
49 let pages = pending.clone();
50 let mut seen = BTreeSet::new();
51 let mut parents = BTreeMap::<_, Vec<_>>::new();
52 while let Some(id) = pending.pop() {
53 if !seen.insert(id) {
54 continue;
55 }
56 let node = &view.nodes[&id];
57 for child in node
58 .children
59 .iter()
60 .chain(&node.content)
61 .chain(&node.structure)
62 {
63 parents.entry(*child).or_default().push(id);
64 pending.push(*child);
65 }
66 }
67 let mut observed = Vec::new();
68 for text in texts {
69 let Some(node) = view.nodes.get(text).filter(|_| seen.contains(text)) else {
70 return Ok(None);
71 };
72 let Kind::RichText { text: content, .. } = &node.kind else {
73 return Ok(None);
74 };
75 let mut path = Vec::new();
76 let mut at = *text;
77 while !pages.contains(&at) {
78 let Some([parent]) = parents.get(&at).map(Vec::as_slice) else {
79 return Ok(None);
80 };
81 if path.len() >= view.nodes.len() {
82 return Ok(None);
83 }
84 path.push(*parent);
85 at = *parent;
86 }
87 let Some(paragraph) = path.first().and_then(|id| view.nodes.get(id)) else {
88 return Ok(None);
89 };
90 let Kind::Paragraph { lists, .. } = &paragraph.kind else {
91 return Ok(None);
92 };
93 if paragraph.content != [*text] {
94 return Ok(None);
95 }
96 let lists: Vec<_> = lists.iter().map(|id| (*id, &view.nodes[id].kind)).collect();
97 observed.push(Observed {
98 text: content.clone(),
99 structure: json!({
100 "path": path,
101 "children": paragraph.children,
102 "child_level": paragraph.child_level,
103 "paragraph": paragraph.kind,
104 "lists": lists,
105 "tags": node.tags,
106 }),
107 });
108 }
109 Ok(Some(observed))
110}
111
112impl Replica {
113 /// Durably queues a split with stable new identities and observed structural preconditions.
114 pub fn split(
115 &self,
116 source: &[u8],
117 space: ExGuid,
118 intent: &ParagraphSplit,
119 ) -> Result<Option<u64>> {
120 let (operation, prepared) = SplitEdit::capture(source, space, intent)?;
121 self.record(source, space, Operation::Split(operation), &prepared)
122 }
123
124 /// Durably queues a join; changed placement, children or tags require conflict review.
125 pub fn join(
126 &self,
127 source: &[u8],
128 space: ExGuid,
129 intent: &ParagraphJoin,
130 ) -> Result<Option<u64>> {
131 let (operation, prepared) = JoinEdit::capture(source, space, intent)?;
132 self.record(source, space, Operation::Join(operation), &prepared)
133 }
134}
135
136impl SplitEdit {
137 pub(crate) fn capture<'a>(
138 source: &'a [u8],
139 space: ExGuid,
140 intent: &ParagraphSplit,
141 ) -> Result<(Self, PreparedEdit<'a>)> {
142 let prepared = PreparedEdit::split(source, space, intent)?;
143 let Some(mut observed) = observe(source, space, &[intent.position().0])? else {
144 return Err(io::Error::new(
145 io::ErrorKind::InvalidData,
146 "Select text in an active paragraph",
147 )
148 .into());
149 };
150 Ok((
151 Self {
152 intent: intent.clone(),
153 observed: observed.remove(0),
154 },
155 prepared,
156 ))
157 }
158
159 pub(crate) fn prepare<'a>(
160 &self,
161 source: &'a [u8],
162 space: ExGuid,
163 ) -> Result<std::result::Result<PreparedEdit<'a>, ConflictKind>> {
164 let (text, offset) = self.intent.position();
165 let Some(observed) = observe(source, space, &[text])? else {
166 return Ok(Err(ConflictKind::TargetUnavailable));
167 };
168 if observed[0].structure != self.observed.structure {
169 return Ok(Err(ConflictKind::StructureChanged));
170 }
171 let Some(range) = rebase::rebase(&self.observed.text, &observed[0].text, offset..offset)
172 else {
173 return Ok(Err(ConflictKind::TextChanged));
174 };
175 Ok(
176 PreparedEdit::split(source, space, &self.intent.reposition(range.start))
177 .map_err(|_| ConflictKind::UnsupportedEdit),
178 )
179 }
180}
181
182impl JoinEdit {
183 pub(crate) fn review(&self, source: &[u8], space: ExGuid) -> Result<Self> {
184 let (reviewed, _) = Self::capture(source, space, &self.intent)?;
185 if reviewed.observed[0].text.is_empty() != self.observed[0].text.is_empty() {
186 return Err(io::Error::new(
187 io::ErrorKind::InvalidInput,
188 "The join would change its surviving text identity",
189 )
190 .into());
191 }
192 Ok(reviewed)
193 }
194
195 pub(crate) fn capture<'a>(
196 source: &'a [u8],
197 space: ExGuid,
198 intent: &ParagraphJoin,
199 ) -> Result<(Self, PreparedEdit<'a>)> {
200 let prepared = PreparedEdit::join(source, space, intent)?;
201 let Some(observed) = observe(source, space, &intent.texts())? else {
202 return Err(io::Error::new(
203 io::ErrorKind::InvalidData,
204 "Select text in two active paragraphs",
205 )
206 .into());
207 };
208 Ok((
209 Self {
210 intent: intent.clone(),
211 observed: observed.try_into().unwrap(),
212 },
213 prepared,
214 ))
215 }
216
217 pub(crate) fn prepare<'a>(
218 &self,
219 source: &'a [u8],
220 space: ExGuid,
221 ) -> Result<std::result::Result<PreparedEdit<'a>, ConflictKind>> {
222 let Some(observed) = observe(source, space, &self.intent.texts())? else {
223 return Ok(Err(ConflictKind::TargetUnavailable));
224 };
225 for (i, (old, new)) in self.observed.iter().zip(&observed).enumerate() {
226 if old.structure != new.structure {
227 return Ok(Err(ConflictKind::StructureChanged));
228 }
229 let at = if i == 0 {
230 u32::try_from(old.text.encode_utf16().count()).map_err(io::Error::other)?
231 } else {
232 0
233 };
234 if rebase::rebase(&old.text, &new.text, at..at).is_none() {
235 return Ok(Err(ConflictKind::TextChanged));
236 }
237 }
238 Ok(PreparedEdit::join(source, space, &self.intent)
239 .map_err(|_| ConflictKind::UnsupportedEdit))
240 }
241}
crates/onestore-offline/src/schema.rs+5-7
......@@ -3,7 +3,7 @@ use rusqlite::{OptionalExtension, Transaction};
33
44const CONFLICTS: &str = "CREATE TABLE conflicts (
55 edit_id INTEGER PRIMARY KEY REFERENCES edits(id) ON DELETE CASCADE,
6 kind INTEGER NOT NULL CHECK(kind BETWEEN 0 AND 3)
6 kind INTEGER NOT NULL CHECK(kind BETWEEN 0 AND 4)
77) STRICT;";
88
99const ASSETS: &str = "CREATE TABLE assets (
......@@ -35,17 +35,15 @@ pub(crate) fn create(transaction: &Transaction<'_>) -> Result<()> {
3535}
3636
3737pub(crate) fn migrate(transaction: &Transaction<'_>, version: u32) -> Result<()> {
38 if version == 4 {
39 transaction.execute_batch(ASSETS)?;
40 return Ok(());
41 }
42 if version == 3 {
38 if version >= 3 {
4339 transaction.execute_batch("ALTER TABLE conflicts RENAME TO old_conflicts;")?;
4440 transaction.execute_batch(CONFLICTS)?;
4541 transaction.execute_batch(
4642 "INSERT INTO conflicts SELECT * FROM old_conflicts; DROP TABLE old_conflicts;",
4743 )?;
48 transaction.execute_batch(ASSETS)?;
44 if version < 5 {
45 transaction.execute_batch(ASSETS)?;
46 }
4947 return Ok(());
5048 }
5149
crates/onestore-offline/src/sync.rs+44-1
......@@ -18,6 +18,7 @@ pub enum ConflictKind {
1818 TargetUnavailable = 1,
1919 UnsupportedEdit = 2,
2020 FormattingChanged = 3,
21 StructureChanged = 4,
2122}
2223
2324#[derive(Debug, Clone, Copy, PartialEq, Eq)]
......@@ -131,6 +132,8 @@ impl Replica {
131132 }
132133 let candidate = match &intent.operation {
133134 Operation::Format(edit) => edit.prepare(&snapshot, intent.space)?,
135 Operation::Split(edit) => edit.prepare(&snapshot, intent.space)?,
136 Operation::Join(edit) => edit.prepare(&snapshot, intent.space)?,
134137 Operation::Insert(insertion) => {
135138 PreparedEdit::insert(&snapshot, intent.space, insertion)
136139 .map_err(|_| ConflictKind::UnsupportedEdit)
......@@ -222,7 +225,7 @@ impl Replica {
222225 Ok(Some((intent.id, EditStatus::Published { revision })))
223226 }
224227
225 /// Places the oldest text or formatting conflict at a reviewed remote UTF-16 range.
228 /// Places the oldest text, formatting or split conflict at a reviewed remote UTF-16 range.
226229 /// The requested replacement/attributes, local image, intent ID and later edits are preserved.
227230 /// Both supplied images must match `snapshot` and `remote_snapshot`; stale review
228231 /// returns `Io(ResourceBusy)`. Uncertain publication attempts cannot be rebased.
......@@ -270,6 +273,30 @@ impl Replica {
270273 )?
271274 .0,
272275 ),
276 Operation::Split(edit) => {
277 if !range.is_empty() {
278 return Err(io::Error::new(
279 io::ErrorKind::InvalidInput,
280 "Choose a single split boundary",
281 )
282 .into());
283 }
284 Operation::Split(
285 crate::paragraph::SplitEdit::capture(
286 remote,
287 intent.space,
288 &edit.intent.reposition(range.start),
289 )?
290 .0,
291 )
292 }
293 Operation::Join(_) => {
294 return Err(io::Error::new(
295 io::ErrorKind::InvalidInput,
296 "Review the paragraph join using rebase_join_conflict",
297 )
298 .into());
299 }
273300 Operation::Insert(_) => {
274301 return Err(io::Error::new(
275302 io::ErrorKind::InvalidInput,
......@@ -281,6 +308,21 @@ impl Replica {
281308 })
282309 }
283310
311 /// Reviews a join against both current cache images, retaining its original text identities.
312 /// The surviving text identity must remain the same for dependent edits.
313 pub fn rebase_join_conflict(&self, id: u64, local: &[u8], remote: &[u8]) -> Result<()> {
314 self.resolve_conflict(id, local, remote, |intent| {
315 let Operation::Join(edit) = intent.operation else {
316 return Err(io::Error::new(
317 io::ErrorKind::InvalidInput,
318 "Select a paragraph join conflict",
319 )
320 .into());
321 };
322 Ok(Operation::Join(edit.review(remote, intent.space)?))
323 })
324 }
325
284326 /// Repositions the oldest paragraph insertion conflict against reviewed cache images.
285327 /// Object identities and dependent edits are retained; uncertain attempts cannot be moved.
286328 pub fn rebase_paragraph_conflict(
......@@ -449,6 +491,7 @@ pub(crate) fn status(connection: &Connection, id: u64) -> Result<Option<EditStat
449491 1 => ConflictKind::TargetUnavailable,
450492 2 => ConflictKind::UnsupportedEdit,
451493 3 => ConflictKind::FormattingChanged,
494 4 => ConflictKind::StructureChanged,
452495 _ => {
453496 return Err(io::Error::new(
454497 io::ErrorKind::InvalidData,
crates/onestore-offline/tests/cache.rs+119-4
......@@ -710,12 +710,19 @@ fn twelve_local_clients_preserve_inserted_identities_and_dependent_edits() {
710710
711711#[test]
712712fn unrecognized_persisted_operations_are_rejected_without_dropping_fields() {
713 for operation in ["Text", "Insert", "Format"] {
713 for operation in ["Text", "Insert", "Format", "Split", "Join"] {
714714 let directory = tempfile::tempdir().unwrap();
715715 let path = directory.path().join("unknown.sqlite");
716 let source = onestore::create_section("unknown.one", "Original", "Author").unwrap();
717 let cache = Replica::create(&path, &source).unwrap();
716 let mut source = onestore::create_section("unknown.one", "Original", "Author").unwrap();
718717 let (sid, oid, _) = target(&source);
718 let split = onestore::ParagraphSplit::new(oid, 3, "Author").unwrap();
719 if operation == "Join" {
720 source = onestore::PreparedEdit::split(&source, sid, &split)
721 .unwrap()
722 .as_bytes()
723 .to_vec();
724 }
725 let cache = Replica::create(&path, &source).unwrap();
719726 if operation == "Insert" {
720727 let store = Store::parse(&source).unwrap();
721728 let index = RevisionIndex::parse(&store).unwrap();
......@@ -726,6 +733,16 @@ fn unrecognized_persisted_operations_are_rejected_without_dropping_fields() {
726733 cache.insert(&source, sid, &insertion).unwrap();
727734 } else if operation == "Text" {
728735 cache.edit_text(&source, sid, oid, 0..0, "New ").unwrap();
736 } else if operation == "Split" {
737 cache.split(&source, sid, &split).unwrap();
738 } else if operation == "Join" {
739 cache
740 .join(
741 &source,
742 sid,
743 &onestore::ParagraphJoin::new(oid, split.text_object(), "Author").unwrap(),
744 )
745 .unwrap();
729746 } else {
730747 cache
731748 .format(
......@@ -746,7 +763,7 @@ fn unrecognized_persisted_operations_are_rejected_without_dropping_fields() {
746763 value[operation]["future_option"] = true.into();
747764 db.execute("UPDATE edits SET operation=?1", [value.to_string()])
748765 .unwrap();
749 if operation != "Format" {
766 if matches!(operation, "Text" | "Insert") {
750767 db.execute_batch("DROP TABLE assets; DROP TABLE conflicts; CREATE TABLE conflicts (edit_id INTEGER PRIMARY KEY REFERENCES edits(id) ON DELETE CASCADE, kind INTEGER NOT NULL CHECK(kind BETWEEN 0 AND 2)) STRICT; PRAGMA user_version=3;").unwrap();
751768 }
752769 drop(db);
......@@ -757,3 +774,101 @@ fn unrecognized_persisted_operations_are_rejected_without_dropping_fields() {
757774 assert_eq!(fs::read(&path).unwrap(), before);
758775 }
759776}
777
778#[test]
779fn version_five_migration_retains_queue_evidence_and_enables_structural_conflicts() {
780 use onestore_offline::{ConflictKind, EditStatus, Recovery};
781 let directory = tempfile::tempdir().unwrap();
782 let path = directory.path().join("version-five.sqlite");
783 let source = onestore::create_section("migration.one", "Original", "Author").unwrap();
784 let (sid, oid, _) = target(&source);
785 let cache = Replica::create(&path, &source).unwrap();
786 let first = cache
787 .edit_text(&source, sid, oid, 0..0, "New ")
788 .unwrap()
789 .unwrap();
790 let second = cache
791 .format(
792 &cache.snapshot().unwrap(),
793 sid,
794 oid,
795 0..3,
796 &[onestore::TextAttribute::Bold(true)],
797 )
798 .unwrap()
799 .unwrap();
800 let queue = cache.pending().unwrap();
801 let local = cache.snapshot().unwrap();
802 let store = Store::parse(&local).unwrap();
803 let index = RevisionIndex::parse(&store).unwrap();
804 let revision = index.spaces[&sid].labels[&(ExGuid::default(), 1)];
805 drop(cache);
806 let db = rusqlite::Connection::open(&path).unwrap();
807 db.execute_batch(
808 "DROP TABLE conflicts; CREATE TABLE conflicts (
809 edit_id INTEGER PRIMARY KEY REFERENCES edits(id) ON DELETE CASCADE,
810 kind INTEGER NOT NULL CHECK(kind BETWEEN 0 AND 3)) STRICT;
811 PRAGMA user_version=5;
812 UPDATE sqlite_sequence SET seq=1000 WHERE name='edits';",
813 )
814 .unwrap();
815 db.execute(
816 "INSERT INTO conflicts VALUES (?1,3)",
817 [i64::try_from(second).unwrap()],
818 )
819 .unwrap();
820 db.execute(
821 "INSERT INTO attempt VALUES (1,?1,?2)",
822 rusqlite::params![i64::try_from(first).unwrap(), revision.to_string()],
823 )
824 .unwrap();
825 db.execute(
826 "INSERT INTO receipts VALUES (999,?1)",
827 [revision.to_string()],
828 )
829 .unwrap();
830 drop(db);
831 let cache = Replica::open(&path).unwrap();
832 assert_eq!(cache.pending().unwrap(), queue);
833 assert!(cache.snapshot().unwrap() == local);
834 assert!(cache.remote_snapshot().unwrap() == source);
835 assert_eq!(
836 cache.status(first).unwrap(),
837 Some(EditStatus::AwaitingConfirmation { revision })
838 );
839 assert_eq!(
840 cache.status(second).unwrap(),
841 Some(EditStatus::Conflict(ConflictKind::FormattingChanged))
842 );
843 assert_eq!(
844 cache.status(999).unwrap(),
845 Some(EditStatus::Published { revision })
846 );
847 let split = onestore::ParagraphSplit::new(oid, 3, "Author").unwrap();
848 assert_eq!(cache.split(&local, sid, &split).unwrap(), Some(1001));
849 let pending = cache.pending().unwrap();
850 let archive = directory.path().join("recovery.sqlite");
851 cache.export_recovery(&archive).unwrap();
852 let recovery = Recovery::open(&archive).unwrap();
853 assert_eq!(recovery.pending().unwrap(), pending);
854 assert_eq!(
855 recovery.status(first).unwrap(),
856 cache.status(first).unwrap()
857 );
858 assert_eq!(recovery.receipts().unwrap().get(&999), Some(&revision));
859 drop(cache);
860 let db = rusqlite::Connection::open(&path).unwrap();
861 assert_eq!(
862 db.pragma_query_value(None, "user_version", |row| row.get::<_, u32>(0))
863 .unwrap(),
864 6
865 );
866 db.execute("INSERT INTO conflicts VALUES (1001,4)", [])
867 .unwrap();
868 drop(db);
869 let cache = Replica::open(&path).unwrap();
870 assert_eq!(
871 cache.status(1001).unwrap(),
872 Some(EditStatus::Conflict(ConflictKind::StructureChanged))
873 );
874}
crates/onestore-offline/tests/sync.rs+523-1
......@@ -5,6 +5,528 @@ use onestore::{
55use onestore_offline::{ConflictKind, EditStatus, Error, Remote, Replica};
66use std::io;
77
8mod paragraph {
9 use super::*;
10 use onestore::{Insertion, ParagraphJoin, ParagraphSplit, TextAttribute};
11
12 fn fixture() -> (Vec<u8>, ExGuid, ExGuid, ExGuid) {
13 let source = onestore::create_section("paragraph.one", "ab🦀cd", "Fixture").unwrap();
14 let (sid, left, _) = text(&source);
15 let store = Store::parse(&source).unwrap();
16 let index = RevisionIndex::parse(&store).unwrap();
17 let doc = Document::parse(&index).unwrap();
18 let space = &doc.spaces[&sid];
19 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
20 let parent = *view
21 .nodes
22 .iter()
23 .find(|(_, node)| node.content == [left])
24 .unwrap()
25 .0;
26 (source, sid, left, parent)
27 }
28
29 #[test]
30 fn split_join_dependencies_reopen_and_rebase_with_remote_text_and_styles() {
31 let (source, sid, left, parent) = fixture();
32 let directory = tempfile::tempdir().unwrap();
33 let path = directory.path().join("paragraph.sqlite");
34 let mut cache = Replica::create(&path, &source).unwrap();
35 let split = ParagraphSplit::new(left, 2, "Local").unwrap();
36 let child = Insertion::paragraph(split.object(), None, "Child", "Local").unwrap();
37 let join = ParagraphJoin::new(left, split.text_object(), "Local").unwrap();
38 let mut ids = Vec::new();
39 for step in 0..6 {
40 let current = cache.snapshot().unwrap();
41 let id = match step {
42 0 => cache.split(&current, sid, &split),
43 1 => cache.edit_text(&current, sid, split.text_object(), 0..2, "🦋"),
44 2 => cache.format(
45 &current,
46 sid,
47 split.text_object(),
48 2..3,
49 &[TextAttribute::Bold(true)],
50 ),
51 3 => cache.insert(&current, sid, &child),
52 4 => cache.join(&current, sid, &join),
53 5 => cache.edit_text(&current, sid, left, 5..6, "D"),
54 _ => unreachable!(),
55 }
56 .unwrap()
57 .unwrap();
58 ids.push(id);
59 let saved = cache.snapshot().unwrap();
60 let pending = cache.pending().unwrap();
61 drop(cache);
62 cache = Replica::open(&path).unwrap();
63 assert_eq!(cache.snapshot().unwrap(), saved);
64 assert_eq!(cache.pending().unwrap(), pending);
65 }
66 let remote = onestore::replace_text(&source, sid, left, 0..0, "Z").unwrap();
67 let remote =
68 PreparedEdit::format(&remote, sid, left, 1..2, &[TextAttribute::Italic(true)]).unwrap();
69 let mut server = Server::new(remote.as_bytes());
70 let local = cache.snapshot().unwrap();
71 for (i, id) in ids.iter().enumerate() {
72 let (published, state) = cache.sync_once(&mut server).unwrap().unwrap();
73 assert_eq!(published, *id);
74 assert!(matches!(state, EditStatus::Published { .. }), "{state:?}");
75 drop(cache);
76 cache = Replica::open(&path).unwrap();
77 assert_eq!(cache.status(*id).unwrap(), Some(state));
78 if i != ids.len() - 1 {
79 assert_eq!(cache.snapshot().unwrap(), local);
80 }
81 }
82 assert_eq!(server.publications, ids.len());
83 assert_eq!(server.visible, server.durable);
84 assert_eq!(cache.snapshot().unwrap(), server.durable);
85 let store = Store::parse(&server.durable).unwrap();
86 let index = RevisionIndex::parse(&store).unwrap();
87 index.validate_current().unwrap();
88 let doc = Document::parse(&index).unwrap();
89 let space = &doc.spaces[&sid];
90 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
91 assert!(
92 matches!(&view.nodes[&left].kind, Kind::RichText { text, .. } if text == "Zab🦋cD")
93 );
94 assert_eq!(view.nodes[&parent].children, [child.object()]);
95 assert_eq!(view.nodes[&parent].content, [left]);
96 let runs = view.text_runs(left).unwrap();
97 let chars: Vec<_> = runs
98 .iter()
99 .flat_map(|run| {
100 run.text
101 .chars()
102 .map(|c| (c, run.format.bold, run.format.italic))
103 })
104 .collect();
105 assert_eq!(chars.iter().find(|v| v.0 == 'a').unwrap().2, Some(true));
106 assert_eq!(chars.iter().find(|v| v.0 == 'c').unwrap().1, Some(true));
107 }
108
109 #[test]
110 fn changed_split_children_require_fresh_review_without_reallocating_dependents() {
111 let (source, sid, left, parent) = fixture();
112 let directory = tempfile::tempdir().unwrap();
113 let path = directory.path().join("paragraph.sqlite");
114 let cache = Replica::create(&path, &source).unwrap();
115 let split = ParagraphSplit::new(left, 2, "Local").unwrap();
116 let id = cache.split(&source, sid, &split).unwrap().unwrap();
117 let dependent = cache
118 .edit_text(
119 &cache.snapshot().unwrap(),
120 sid,
121 split.text_object(),
122 0..2,
123 "🦋",
124 )
125 .unwrap()
126 .unwrap();
127 let local = cache.snapshot().unwrap();
128 let child = Insertion::paragraph(parent, None, "Remote child", "Remote").unwrap();
129 let remote = PreparedEdit::insert(&source, sid, &child).unwrap();
130 let mut server = Server::new(remote.as_bytes());
131 assert_eq!(
132 cache.sync_once(&mut server).unwrap(),
133 Some((id, EditStatus::Conflict(ConflictKind::StructureChanged)))
134 );
135 assert_eq!(server.publications, 0);
136 assert_eq!(cache.snapshot().unwrap(), local);
137 assert!(
138 cache
139 .rebase_conflict(id, &source, &server.visible, 2..2)
140 .is_err()
141 );
142 assert!(
143 cache
144 .rebase_conflict(id, &local, &server.visible, 2..3)
145 .is_err()
146 );
147 cache
148 .rebase_conflict(id, &local, &server.visible, 2..2)
149 .unwrap();
150 let pending = cache.pending().unwrap();
151 let onestore_offline::Operation::Split(edit) = &pending[0].operation else {
152 panic!()
153 };
154 assert_eq!(edit.intent, split);
155 assert_eq!(pending[1].id, dependent);
156 drop(cache);
157 let cache = Replica::open(&path).unwrap();
158 for expected in [id, dependent] {
159 let (actual, state) = cache.sync_once(&mut server).unwrap().unwrap();
160 assert_eq!(actual, expected);
161 assert!(matches!(state, EditStatus::Published { .. }));
162 }
163 let store = Store::parse(&server.durable).unwrap();
164 let index = RevisionIndex::parse(&store).unwrap();
165 let doc = Document::parse(&index).unwrap();
166 let space = &doc.spaces[&sid];
167 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
168 assert_eq!(view.nodes[&split.object()].children, [child.object()]);
169 assert!(
170 matches!(&view.nodes[&split.text_object()].kind, Kind::RichText { text, .. } if text == "🦋cd")
171 );
172 }
173
174 #[test]
175 fn uncertain_paragraph_operations_keep_the_original_attempt_across_reopen() {
176 for join in [false, true] {
177 for fault in [
178 Fault::Before,
179 Fault::UnknownBefore,
180 Fault::UnknownAfter,
181 Fault::Committed,
182 ] {
183 let (source, sid, left, _) = fixture();
184 let split = ParagraphSplit::new(left, 2, "Local").unwrap();
185 let source = if join {
186 PreparedEdit::split(&source, sid, &split)
187 .unwrap()
188 .as_bytes()
189 .to_vec()
190 } else {
191 source
192 };
193 let directory = tempfile::tempdir().unwrap();
194 let path = directory.path().join("paragraph.sqlite");
195 let cache = Replica::create(&path, &source).unwrap();
196 let id = if join {
197 cache.join(
198 &source,
199 sid,
200 &ParagraphJoin::new(left, split.text_object(), "Local").unwrap(),
201 )
202 } else {
203 cache.split(&source, sid, &split)
204 }
205 .unwrap()
206 .unwrap();
207 let local = cache.snapshot().unwrap();
208 let intent = cache.pending().unwrap();
209 let mut server = Server::new(&source);
210 server.fault = fault;
211 assert!(cache.sync_once(&mut server).is_err());
212 let state = cache.status(id).unwrap().unwrap();
213 drop(cache);
214 let cache = Replica::open(&path).unwrap();
215 assert_eq!(cache.status(id).unwrap(), Some(state));
216 if matches!(fault, Fault::UnknownBefore | Fault::UnknownAfter) {
217 assert!(matches!(state, EditStatus::AwaitingConfirmation { .. }));
218 assert_eq!(cache.pending().unwrap(), intent);
219 assert_eq!(cache.snapshot().unwrap(), local);
220 if matches!(fault, Fault::UnknownBefore) {
221 assert_eq!(cache.sync_once(&mut server).unwrap(), Some((id, state)));
222 assert_eq!(server.publications, 1);
223 continue;
224 }
225 server.fault = Fault::Confirm;
226 assert!(cache.sync_once(&mut server).is_err());
227 assert_eq!(cache.status(id).unwrap(), Some(state));
228 let (_, state) = cache.sync_once(&mut server).unwrap().unwrap();
229 assert!(matches!(state, EditStatus::Published { .. }));
230 assert_eq!(server.publications, 1);
231 } else if matches!(fault, Fault::Before) {
232 assert_eq!(state, EditStatus::Pending);
233 assert!(matches!(
234 cache.sync_once(&mut server).unwrap().unwrap().1,
235 EditStatus::Published { .. }
236 ));
237 assert_eq!(server.publications, 2);
238 } else {
239 assert!(matches!(state, EditStatus::Published { .. }));
240 assert_eq!(server.publications, 1);
241 }
242 assert!(cache.pending().unwrap().is_empty());
243 let saved = cache.snapshot().unwrap();
244 if matches!(fault, Fault::UnknownAfter) {
245 // Confirmation refreshes version metadata after reading the acknowledged snapshot.
246 assert!(saved[..212] == server.durable[..212]);
247 assert!(saved[252..] == server.durable[252..]);
248 assert_eq!(
249 Store::parse(&server.durable).unwrap().header.generation,
250 Store::parse(&saved).unwrap().header.generation + 1
251 );
252 } else {
253 assert!(saved == server.durable);
254 }
255 assert_eq!(cache.sync_once(&mut server).unwrap(), None);
256 assert!(cache.snapshot().unwrap() == server.durable);
257 }
258 }
259 }
260
261 #[test]
262 fn join_reviews_preserve_survivors_and_require_current_child_placement() {
263 for empty in [false, true] {
264 let (source, sid, left, _) = fixture();
265 let split = ParagraphSplit::new(left, if empty { 0 } else { 2 }, "Local").unwrap();
266 let source = PreparedEdit::split(&source, sid, &split)
267 .unwrap()
268 .as_bytes()
269 .to_vec();
270 let directory = tempfile::tempdir().unwrap();
271 let path = directory.path().join("join.sqlite");
272 let cache = Replica::create(&path, &source).unwrap();
273 let intent = ParagraphJoin::new(left, split.text_object(), "Local").unwrap();
274 let id = cache.join(&source, sid, &intent).unwrap().unwrap();
275 let survivor = if empty { split.text_object() } else { left };
276 let dependent = cache
277 .edit_text(&cache.snapshot().unwrap(), sid, survivor, 0..0, "Local ")
278 .unwrap()
279 .unwrap();
280 let local = cache.snapshot().unwrap();
281 let child =
282 Insertion::paragraph(split.object(), None, "Remote child", "Remote").unwrap();
283 let remote = PreparedEdit::insert(&source, sid, &child)
284 .unwrap()
285 .as_bytes()
286 .to_vec();
287 let mut server = Server::new(&remote);
288 assert_eq!(
289 cache.sync_once(&mut server).unwrap(),
290 Some((id, EditStatus::Conflict(ConflictKind::StructureChanged)))
291 );
292 assert!(cache.rebase_join_conflict(id, &source, &remote).is_err());
293 assert_eq!(server.publications, 0);
294 cache.rebase_join_conflict(id, &local, &remote).unwrap();
295 assert!(
296 matches!(cache.sync_once(&mut server).unwrap(), Some((actual, EditStatus::Published { .. })) if actual == id)
297 );
298 assert!(
299 matches!(cache.sync_once(&mut server).unwrap(), Some((actual, EditStatus::Published { .. })) if actual == dependent)
300 );
301 let store = Store::parse(&server.durable).unwrap();
302 let index = RevisionIndex::parse(&store).unwrap();
303 let doc = Document::parse(&index).unwrap();
304 let space = &doc.spaces[&sid];
305 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
306 let parent = view
307 .nodes
308 .values()
309 .find(|node| node.content == [survivor])
310 .unwrap();
311 assert_eq!(parent.children, [child.object()]);
312 assert!(
313 matches!(&view.nodes[&survivor].kind, Kind::RichText { text, .. } if text == "Local ab🦀cd")
314 );
315 }
316
317 let (source, sid, left, _) = fixture();
318 let split = ParagraphSplit::new(left, 0, "Local").unwrap();
319 let source = PreparedEdit::split(&source, sid, &split)
320 .unwrap()
321 .as_bytes()
322 .to_vec();
323 let directory = tempfile::tempdir().unwrap();
324 let path = directory.path().join("identity.sqlite");
325 let cache = Replica::create(&path, &source).unwrap();
326 let intent = ParagraphJoin::new(left, split.text_object(), "Local").unwrap();
327 let id = cache.join(&source, sid, &intent).unwrap().unwrap();
328 let local = cache.snapshot().unwrap();
329 let remote = onestore::replace_text(&source, sid, left, 0..0, "Remote").unwrap();
330 let mut server = Server::new(&remote);
331 assert_eq!(
332 cache.sync_once(&mut server).unwrap(),
333 Some((id, EditStatus::Conflict(ConflictKind::TextChanged)))
334 );
335 assert!(cache.rebase_join_conflict(id, &local, &remote).is_err());
336 assert!(cache.snapshot().unwrap() == local);
337 assert_eq!(server.publications, 0);
338 assert_eq!(cache.pending().unwrap().len(), 1);
339 }
340
341 #[test]
342 fn overlapping_clients_retain_each_local_branch_without_replaying_removed_targets() {
343 let (source, sid, left, _) = fixture();
344 let split = ParagraphSplit::new(left, 2, "Seed").unwrap();
345 let source = PreparedEdit::split(&source, sid, &split)
346 .unwrap()
347 .as_bytes()
348 .to_vec();
349 let directory = tempfile::tempdir().unwrap();
350 let first = Replica::create(directory.path().join("first.sqlite"), &source).unwrap();
351 let second_path = directory.path().join("second.sqlite");
352 let second = Replica::create(&second_path, &source).unwrap();
353 let join = ParagraphJoin::new(left, split.text_object(), "First").unwrap();
354 first.join(&source, sid, &join).unwrap();
355 let right_split = ParagraphSplit::new(split.text_object(), 2, "Second").unwrap();
356 let id = second.split(&source, sid, &right_split).unwrap().unwrap();
357 second
358 .edit_text(
359 &second.snapshot().unwrap(),
360 sid,
361 right_split.text_object(),
362 0..0,
363 "Second ",
364 )
365 .unwrap();
366 let local = second.snapshot().unwrap();
367 let pending = second.pending().unwrap();
368 let mut server = Server::new(&source);
369 assert!(matches!(
370 first.sync_once(&mut server).unwrap().unwrap().1,
371 EditStatus::Published { .. }
372 ));
373 assert_eq!(
374 second.sync_once(&mut server).unwrap(),
375 Some((id, EditStatus::Conflict(ConflictKind::TargetUnavailable)))
376 );
377 drop(second);
378 let second = Replica::open(&second_path).unwrap();
379 assert_eq!(second.pending().unwrap(), pending);
380 assert!(second.snapshot().unwrap() == local);
381 assert_eq!(server.publications, 1);
382 assert_eq!(
383 second.status(id).unwrap(),
384 Some(EditStatus::Conflict(ConflictKind::TargetUnavailable))
385 );
386 }
387
388 #[test]
389 #[ignore = "exports reconciled paragraph edits for independent native validation"]
390 fn export_native_offline_paragraphs() {
391 use std::{fs, path::PathBuf};
392 let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../corpus/paragraph-edit");
393 let output = PathBuf::from(std::env::var_os("ONESTORE_OFFLINE_PARAGRAPH_OUTPUT").unwrap());
394 assert!(output.is_absolute());
395 fs::create_dir(&output).unwrap();
396 for (name, source, manifest, split) in [
397 (
398 "splits",
399 "before/notebook/synthetic.one",
400 "rust-split/manifest.json",
401 true,
402 ),
403 (
404 "joins",
405 "split/notebook/synthetic.one",
406 "rust-join/split/manifest.json",
407 false,
408 ),
409 (
410 "inheritance",
411 "join-edges/before/notebook/synthetic.one",
412 "rust-join/inheritance/manifest.json",
413 false,
414 ),
415 (
416 "tags",
417 "join-tags/before/notebook/synthetic.one",
418 "rust-join/tags/manifest.json",
419 false,
420 ),
421 ] {
422 let source = fs::read(root.join(source)).unwrap();
423 let folder = output.join(name);
424 fs::create_dir(&folder).unwrap();
425 let path = folder.join("cache.sqlite");
426 let mut cache = Replica::create(&path, &source).unwrap();
427 let mut server = Server::new(&source);
428 let manifest: serde_json::Value =
429 serde_json::from_slice(&fs::read(root.join(manifest)).unwrap()).unwrap();
430 let cases = if split { &manifest["cases"] } else { &manifest };
431 let mut recorded = Vec::new();
432 for case in cases
433 .as_array()
434 .unwrap()
435 .iter()
436 .filter(|case| case.get("intent").is_some())
437 {
438 let local = cache.snapshot().unwrap();
439 let (text, offset) = if split {
440 serde_json::from_value::<ParagraphSplit>(case["intent"].clone())
441 .unwrap()
442 .position()
443 } else {
444 (
445 serde_json::from_value::<ParagraphJoin>(case["intent"].clone())
446 .unwrap()
447 .texts()[0],
448 1,
449 )
450 };
451 let store = Store::parse(&local).unwrap();
452 let index = RevisionIndex::parse(&store).unwrap();
453 let doc = Document::parse(&index).unwrap();
454 let (sid, space) = doc
455 .spaces
456 .iter()
457 .find(|(_, space)| {
458 space.revisions[&space.contexts[&ExGuid::default()]]
459 .nodes
460 .contains_key(&text)
461 })
462 .unwrap();
463 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
464 let Kind::RichText { text: content, .. } = &view.nodes[&text].kind else {
465 panic!()
466 };
467 let remote_prefix = offset > 0 && !content.is_empty();
468 assert!(!content.contains('☂'));
469 if remote_prefix {
470 server.visible =
471 onestore::replace_text(&server.visible, *sid, text, 0..0, "☂").unwrap();
472 server.durable.clone_from(&server.visible);
473 }
474 let id = if split {
475 cache.split(
476 &local,
477 *sid,
478 &serde_json::from_value(case["intent"].clone()).unwrap(),
479 )
480 } else {
481 cache.join(
482 &local,
483 *sid,
484 &serde_json::from_value(case["intent"].clone()).unwrap(),
485 )
486 }
487 .unwrap()
488 .unwrap();
489 recorded.push(serde_json::json!({"case": case["case"], "id": id,
490 "space": sid, "intent": case["intent"], "remote_prefix": remote_prefix}));
491 drop(cache);
492 cache = Replica::open(&path).unwrap();
493 }
494 for case in &mut recorded {
495 let id = case["id"].as_u64().unwrap();
496 if id % 2 == 0 {
497 server.fault = Fault::UnknownAfter;
498 assert!(cache.sync_once(&mut server).is_err());
499 assert!(matches!(
500 cache.status(id).unwrap(),
501 Some(EditStatus::AwaitingConfirmation { .. })
502 ));
503 drop(cache);
504 cache = Replica::open(&path).unwrap();
505 }
506 let (actual, status) = cache.sync_once(&mut server).unwrap().unwrap();
507 assert_eq!(actual, id);
508 let EditStatus::Published { revision } = status else {
509 panic!("{name}: {status:?}")
510 };
511 case["revision"] = serde_json::to_value(revision).unwrap();
512 }
513 assert_eq!(server.publications, recorded.len());
514 assert_eq!(cache.sync_once(&mut server).unwrap(), None);
515 assert!(cache.snapshot().unwrap() == server.durable);
516 cache
517 .export_recovery(folder.join("recovery.sqlite"))
518 .unwrap();
519 fs::create_dir(folder.join("candidate")).unwrap();
520 fs::write(folder.join("candidate/synthetic.one"), &server.durable).unwrap();
521 fs::write(
522 folder.join("manifest.json"),
523 serde_json::to_vec_pretty(&recorded).unwrap(),
524 )
525 .unwrap();
526 }
527 }
528}
529
8530#[derive(Clone, Copy, Default)]
9531enum Fault {
10532 #[default]
......@@ -739,7 +1261,7 @@ fn version_one_cache_migration_preserves_images_intents_and_local_ids() {
7391261 assert_eq!(
7401262 db.pragma_query_value(None, "user_version", |row| row.get::<_, u32>(0))
7411263 .unwrap(),
742 5
1264 6
7431265 );
7441266}
7451267
crates/onestore/README.md+3-2
......@@ -95,8 +95,9 @@ Ambiguous ancestry, unsupported indentation transitions and unknown implicit
9595font/language inheritance reject before I/O. This is a logical join, so keyboard
9696actions that only change list or indentation state remain separate operations.
9797Generated fields, protected targets and unsupported run-data boundary changes are
98rejected before publication. Local caches expose text, insertion and formatting edits;
99the [document-writer acceptance](../../evidence/MILESTONE9.md#document-writer-and-offline-acceptance)
98rejected before publication. The [offline crate](../onestore-offline/README.md)
99documents durable local operations and reconciliation. The
100[document-writer acceptance](../../evidence/MILESTONE9.md#document-writer-and-offline-acceptance)
100101includes twelve mixed native/Rust clients, outages, lost replies and native revision retirement.
101102
102103External `.onebin` references identify payloads for the caller to obtain. Cloud
crates/onestore/src/paragraph.rs+18
......@@ -61,6 +61,19 @@ impl ParagraphSplit {
6161 }
6262 }
6363
64 /// Original text identity and UTF-16 split boundary.
65 pub fn position(&self) -> (ExGuid, u32) {
66 (self.text, self.offset)
67 }
68
69 /// Changes the split boundary while retaining allocated identities and creation time.
70 /// Preparing the edit validates the offset against the supplied text.
71 pub fn reposition(&self, offset: u32) -> Self {
72 let mut split = self.clone();
73 split.offset = offset;
74 split
75 }
76
6477 pub(crate) fn apply(&self, source: &[u8], space: ExGuid) -> Result<Vec<u8>, Error> {
6578 if self.guid == [0; 16] || self.text.guid == [0; 16] || self.author.contains('\0') {
6679 return Err(invalid(
......@@ -418,6 +431,11 @@ pub struct ParagraphJoin {
418431}
419432
420433impl ParagraphJoin {
434 /// Ordered left and right text identities.
435 pub fn texts(&self) -> [ExGuid; 2] {
436 [self.left, self.right]
437 }
438
421439 /// Select the preceding leaf paragraph's text and the following paragraph's text.
422440 pub fn new(left: ExGuid, right: ExGuid, author: &str) -> Result<Self, Error> {
423441 if left == right || left.guid == [0; 16] || right.guid == [0; 16] || author.contains('\0') {
fuzz/Cargo.lock+303
......@@ -51,6 +51,12 @@ dependencies = [
5151 "hybrid-array",
5252]
5353
54[[package]]
55name = "bumpalo"
56version = "3.20.3"
57source = "registry+https://github.com/rust-lang/crates.io-index"
58checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
59
5460[[package]]
5561name = "cbc"
5662version = "0.2.1"
......@@ -138,12 +144,46 @@ dependencies = [
138144 "zeroize",
139145]
140146
147[[package]]
148name = "errno"
149version = "0.3.14"
150source = "registry+https://github.com/rust-lang/crates.io-index"
151checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
152dependencies = [
153 "libc",
154 "windows-sys",
155]
156
157[[package]]
158name = "fallible-iterator"
159version = "0.3.0"
160source = "registry+https://github.com/rust-lang/crates.io-index"
161checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649"
162
163[[package]]
164name = "fallible-streaming-iterator"
165version = "0.1.9"
166source = "registry+https://github.com/rust-lang/crates.io-index"
167checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
168
169[[package]]
170name = "fastrand"
171version = "2.5.0"
172source = "registry+https://github.com/rust-lang/crates.io-index"
173checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223"
174
141175[[package]]
142176name = "find-msvc-tools"
143177version = "0.1.12"
144178source = "registry+https://github.com/rust-lang/crates.io-index"
145179checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
146180
181[[package]]
182name = "foldhash"
183version = "0.2.0"
184source = "registry+https://github.com/rust-lang/crates.io-index"
185checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb"
186
147187[[package]]
148188name = "getrandom"
149189version = "0.4.3"
......@@ -155,6 +195,33 @@ dependencies = [
155195 "r-efi",
156196]
157197
198[[package]]
199name = "hashbrown"
200version = "0.16.1"
201source = "registry+https://github.com/rust-lang/crates.io-index"
202checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
203dependencies = [
204 "foldhash",
205]
206
207[[package]]
208name = "hashbrown"
209version = "0.17.1"
210source = "registry+https://github.com/rust-lang/crates.io-index"
211checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
212dependencies = [
213 "foldhash",
214]
215
216[[package]]
217name = "hashlink"
218version = "0.12.2"
219source = "registry+https://github.com/rust-lang/crates.io-index"
220checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb"
221dependencies = [
222 "hashbrown 0.17.1",
223]
224
158225[[package]]
159226name = "hybrid-array"
160227version = "0.4.14"
......@@ -191,6 +258,16 @@ dependencies = [
191258 "libc",
192259]
193260
261[[package]]
262name = "js-sys"
263version = "0.3.105"
264source = "registry+https://github.com/rust-lang/crates.io-index"
265checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e"
266dependencies = [
267 "cfg-if",
268 "wasm-bindgen",
269]
270
194271[[package]]
195272name = "libc"
196273version = "0.2.189"
......@@ -207,6 +284,23 @@ dependencies = [
207284 "cc",
208285]
209286
287[[package]]
288name = "libsqlite3-sys"
289version = "0.38.2"
290source = "registry+https://github.com/rust-lang/crates.io-index"
291checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8"
292dependencies = [
293 "cc",
294 "pkg-config",
295 "vcpkg",
296]
297
298[[package]]
299name = "linux-raw-sys"
300version = "0.12.1"
301source = "registry+https://github.com/rust-lang/crates.io-index"
302checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53"
303
210304[[package]]
211305name = "md5"
212306version = "0.8.1"
......@@ -231,6 +325,12 @@ dependencies = [
231325 "libc",
232326]
233327
328[[package]]
329name = "once_cell"
330version = "1.21.4"
331source = "registry+https://github.com/rust-lang/crates.io-index"
332checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
333
234334[[package]]
235335name = "onestore"
236336version = "0.1.0"
......@@ -255,9 +355,40 @@ dependencies = [
255355 "libfuzzer-sys",
256356 "md5",
257357 "onestore",
358 "onestore-offline",
258359 "serde_json",
360 "tempfile",
259361]
260362
363[[package]]
364name = "onestore-notebook"
365version = "0.1.0"
366dependencies = [
367 "onestore",
368 "serde",
369 "thiserror",
370]
371
372[[package]]
373name = "onestore-offline"
374version = "0.1.0"
375dependencies = [
376 "onestore",
377 "onestore-notebook",
378 "rusqlite",
379 "serde",
380 "serde_json",
381 "sha2",
382 "tempfile",
383 "thiserror",
384]
385
386[[package]]
387name = "pkg-config"
388version = "0.3.34"
389source = "registry+https://github.com/rust-lang/crates.io-index"
390checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548"
391
261392[[package]]
262393name = "proc-macro2"
263394version = "1.0.107"
......@@ -291,6 +422,50 @@ dependencies = [
291422 "memchr",
292423]
293424
425[[package]]
426name = "rsqlite-vfs"
427version = "0.1.1"
428source = "registry+https://github.com/rust-lang/crates.io-index"
429checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c"
430dependencies = [
431 "hashbrown 0.16.1",
432 "thiserror",
433]
434
435[[package]]
436name = "rusqlite"
437version = "0.40.2"
438source = "registry+https://github.com/rust-lang/crates.io-index"
439checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3"
440dependencies = [
441 "bitflags",
442 "fallible-iterator",
443 "fallible-streaming-iterator",
444 "hashlink",
445 "libsqlite3-sys",
446 "smallvec",
447 "sqlite-wasm-rs",
448]
449
450[[package]]
451name = "rustix"
452version = "1.1.4"
453source = "registry+https://github.com/rust-lang/crates.io-index"
454checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190"
455dependencies = [
456 "bitflags",
457 "errno",
458 "libc",
459 "linux-raw-sys",
460 "windows-sys",
461]
462
463[[package]]
464name = "rustversion"
465version = "1.0.23"
466source = "registry+https://github.com/rust-lang/crates.io-index"
467checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
468
294469[[package]]
295470name = "serde"
296471version = "1.0.229"
......@@ -345,12 +520,41 @@ dependencies = [
345520 "digest",
346521]
347522
523[[package]]
524name = "sha2"
525version = "0.11.0"
526source = "registry+https://github.com/rust-lang/crates.io-index"
527checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4"
528dependencies = [
529 "cfg-if",
530 "cpufeatures",
531 "digest",
532]
533
348534[[package]]
349535name = "shlex"
350536version = "2.0.1"
351537source = "registry+https://github.com/rust-lang/crates.io-index"
352538checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
353539
540[[package]]
541name = "smallvec"
542version = "1.16.0"
543source = "registry+https://github.com/rust-lang/crates.io-index"
544checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f"
545
546[[package]]
547name = "sqlite-wasm-rs"
548version = "0.5.5"
549source = "registry+https://github.com/rust-lang/crates.io-index"
550checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75"
551dependencies = [
552 "cc",
553 "js-sys",
554 "rsqlite-vfs",
555 "wasm-bindgen",
556]
557
354558[[package]]
355559name = "subtle"
356560version = "2.6.1"
......@@ -368,6 +572,39 @@ dependencies = [
368572 "unicode-ident",
369573]
370574
575[[package]]
576name = "tempfile"
577version = "3.27.0"
578source = "registry+https://github.com/rust-lang/crates.io-index"
579checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd"
580dependencies = [
581 "fastrand",
582 "getrandom",
583 "once_cell",
584 "rustix",
585 "windows-sys",
586]
587
588[[package]]
589name = "thiserror"
590version = "2.0.20"
591source = "registry+https://github.com/rust-lang/crates.io-index"
592checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f"
593dependencies = [
594 "thiserror-impl",
595]
596
597[[package]]
598name = "thiserror-impl"
599version = "2.0.20"
600source = "registry+https://github.com/rust-lang/crates.io-index"
601checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af"
602dependencies = [
603 "proc-macro2",
604 "quote",
605 "syn",
606]
607
371608[[package]]
372609name = "typenum"
373610version = "1.20.1"
......@@ -380,6 +617,72 @@ version = "1.0.24"
380617source = "registry+https://github.com/rust-lang/crates.io-index"
381618checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
382619
620[[package]]
621name = "vcpkg"
622version = "0.2.15"
623source = "registry+https://github.com/rust-lang/crates.io-index"
624checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
625
626[[package]]
627name = "wasm-bindgen"
628version = "0.2.128"
629source = "registry+https://github.com/rust-lang/crates.io-index"
630checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf"
631dependencies = [
632 "cfg-if",
633 "once_cell",
634 "rustversion",
635 "wasm-bindgen-macro",
636 "wasm-bindgen-shared",
637]
638
639[[package]]
640name = "wasm-bindgen-macro"
641version = "0.2.128"
642source = "registry+https://github.com/rust-lang/crates.io-index"
643checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed"
644dependencies = [
645 "quote",
646 "wasm-bindgen-macro-support",
647]
648
649[[package]]
650name = "wasm-bindgen-macro-support"
651version = "0.2.128"
652source = "registry+https://github.com/rust-lang/crates.io-index"
653checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a"
654dependencies = [
655 "bumpalo",
656 "proc-macro2",
657 "quote",
658 "syn",
659 "wasm-bindgen-shared",
660]
661
662[[package]]
663name = "wasm-bindgen-shared"
664version = "0.2.128"
665source = "registry+https://github.com/rust-lang/crates.io-index"
666checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e"
667dependencies = [
668 "unicode-ident",
669]
670
671[[package]]
672name = "windows-link"
673version = "0.2.1"
674source = "registry+https://github.com/rust-lang/crates.io-index"
675checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
676
677[[package]]
678name = "windows-sys"
679version = "0.61.2"
680source = "registry+https://github.com/rust-lang/crates.io-index"
681checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
682dependencies = [
683 "windows-link",
684]
685
383686[[package]]
384687name = "zeroize"
385688version = "1.9.0"
fuzz/Cargo.toml+9
......@@ -10,6 +10,8 @@ cargo-fuzz = true
1010[dependencies]
1111libfuzzer-sys = "0.4"
1212onestore = { path = "../crates/onestore", features = ["protected"] }
13onestore-offline = { path = "../crates/onestore-offline" }
14tempfile = "3"
1315md5 = "0.8.1"
1416serde_json = "1"
1517
......@@ -91,3 +93,10 @@ path = "fuzz_targets/paragraph.rs"
9193test = false
9294doc = false
9395bench = false
96
97[[bin]]
98name = "offline_paragraph"
99path = "fuzz_targets/offline_paragraph.rs"
100test = false
101doc = false
102bench = false
fuzz/fuzz_targets/offline_paragraph.rs created+165
......@@ -0,0 +1,165 @@
1#![no_main]
2use libfuzzer_sys::fuzz_target;
3use onestore::{
4 CommitError, ExGuid, ParagraphJoin, ParagraphSplit, PreparedEdit, RevisionIndex, Store,
5 document::{Document, Kind},
6};
7use onestore_offline::{EditStatus, Remote, Replica};
8use std::{io, sync::LazyLock};
9
10#[path = "../../crates/onestore/tests/support/disk.rs"]
11mod disk;
12
13const TEXT: &str = "a🦀 e\u{301} 東京\rEnd";
14
15static SOURCE: LazyLock<(Vec<u8>, ExGuid, ExGuid)> = LazyLock::new(|| {
16 let source = onestore::create_section("offline-paragraph.one", TEXT, "Fixture").unwrap();
17 let store = Store::parse(&source).unwrap();
18 let index = RevisionIndex::parse(&store).unwrap();
19 let doc = Document::parse(&index).unwrap();
20 let (sid, page) = doc.pages().unwrap()[0];
21 let space = &doc.spaces[&sid];
22 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
23 let outline = *view.nodes[&page]
24 .children
25 .iter()
26 .find(|id| matches!(view.nodes[id].kind, Kind::Outline { .. }))
27 .unwrap();
28 (source, sid, outline)
29});
30
31struct Server(disk::Disk);
32
33impl Remote for Server {
34 fn read(&mut self) -> io::Result<Vec<u8>> {
35 Ok(self.0.visible.clone())
36 }
37 fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> {
38 edit.commit(&mut self.0)
39 }
40 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {
41 onestore::confirm_snapshot(&mut self.0, snapshot)
42 }
43}
44
45fn paragraphs(source: &[u8], sid: ExGuid, outline: ExGuid) -> Vec<(ExGuid, String)> {
46 let store = Store::parse(source).unwrap();
47 assert!(store.checksum_mismatches.is_empty());
48 let index = RevisionIndex::parse(&store).unwrap();
49 index.validate_current().unwrap();
50 let doc = Document::parse(&index).unwrap();
51 let space = &doc.spaces[&sid];
52 let view = &space.revisions[&space.contexts[&ExGuid::default()]];
53 let mut output = Vec::new();
54 for paragraph in &view.nodes[&outline].children {
55 let node = &view.nodes[paragraph];
56 assert!(node.children.is_empty());
57 let [text] = node.content.as_slice() else {
58 panic!()
59 };
60 let Kind::RichText { text: content, .. } = &view.nodes[text].kind else {
61 panic!()
62 };
63 output.push((*text, content.clone()));
64 }
65 assert_eq!(
66 output
67 .iter()
68 .map(|(_, text)| text.as_str())
69 .collect::<String>(),
70 TEXT
71 );
72 output
73}
74
75fuzz_target!(|input: &[u8]| {
76 if input.len() < 8 {
77 return;
78 }
79 let (source, sid, outline) = &*SOURCE;
80 let directory = tempfile::tempdir().unwrap();
81 let mut replicas: [Option<Replica>; 12] = std::array::from_fn(|_| None);
82 let mut server = Server(disk::Disk {
83 visible: source.clone(),
84 durable: source.clone(),
85 operation: 0,
86 fail_at: None,
87 write_limit: 4096,
88 random: 1,
89 });
90 for step in input.chunks_exact(8).take(24) {
91 let actor = usize::from(step[0]) % replicas.len();
92 let path = directory.path().join(format!("{actor}.sqlite"));
93 let cache = replicas[actor].get_or_insert_with(|| Replica::create(&path, source).unwrap());
94 let snapshot = cache.snapshot().unwrap();
95 let pending = cache.pending().unwrap();
96 let rows = paragraphs(&snapshot, *sid, *outline);
97 match step[1] % 5 {
98 0 => {
99 let (text, content) = &rows[usize::from(step[2]) % rows.len()];
100 let offsets: Vec<u32> = std::iter::once(0)
101 .chain(content.chars().scan(0, |at, c| {
102 *at += c.len_utf16() as u32;
103 Some(*at)
104 }))
105 .collect();
106 let offset = offsets[usize::from(step[3]) % offsets.len()];
107 let intent = ParagraphSplit::new(*text, offset, "Fuzz").unwrap();
108 let id = cache.split(&snapshot, *sid, &intent).unwrap().unwrap();
109 assert_eq!(cache.status(id).unwrap(), Some(EditStatus::Pending));
110 let next = cache.pending().unwrap();
111 assert_eq!(next[..pending.len()], pending);
112 assert_eq!(next.len(), pending.len() + 1);
113 }
114 1 if rows.len() > 1 => {
115 let pair = usize::from(step[2]) % (rows.len() - 1);
116 let intent = ParagraphJoin::new(rows[pair].0, rows[pair + 1].0, "Fuzz").unwrap();
117 let id = cache.join(&snapshot, *sid, &intent).unwrap().unwrap();
118 assert_eq!(cache.status(id).unwrap(), Some(EditStatus::Pending));
119 let next = cache.pending().unwrap();
120 assert_eq!(next[..pending.len()], pending);
121 assert_eq!(next.len(), pending.len() + 1);
122 }
123 2 | 3 => {
124 server.0.operation = 0;
125 server.0.fail_at =
126 (step[4] != 0).then_some(usize::from(u16::from_le_bytes([step[4], step[5]])));
127 server.0.write_limit = if step[6] & 1 == 0 { 17 } else { 4096 };
128 server.0.random = u64::from(step[7]) + 1;
129 let _ = cache.sync_once(&mut server);
130 let after = cache.pending().unwrap();
131 assert!(after.len() == pending.len() || after.len() + 1 == pending.len());
132 if after.len() == pending.len() {
133 assert_eq!(after, pending);
134 } else {
135 assert_eq!(after, pending[1..]);
136 let Some(EditStatus::Published { revision }) =
137 cache.status(pending[0].id).unwrap()
138 else {
139 panic!()
140 };
141 let store = Store::parse(&server.0.durable).unwrap();
142 let index = RevisionIndex::parse(&store).unwrap();
143 assert!(index.spaces[sid].revisions.contains_key(&revision));
144 }
145 if !after.is_empty() {
146 assert!(cache.snapshot().unwrap() == snapshot);
147 }
148 server.0.visible.clone_from(&server.0.durable);
149 }
150 _ => {
151 drop(replicas[actor].take());
152 let reopened = Replica::open(&path).unwrap();
153 assert!(reopened.snapshot().unwrap() == snapshot);
154 assert_eq!(reopened.pending().unwrap(), pending);
155 replicas[actor] = Some(reopened);
156 }
157 }
158 paragraphs(&server.0.durable, *sid, *outline);
159 paragraphs(
160 &replicas[actor].as_ref().unwrap().snapshot().unwrap(),
161 *sid,
162 *outline,
163 );
164 }
165});
tools/test_paragraph_edit.py+52-19
......@@ -17,31 +17,64 @@ compare = runpy.run_path(str(ROOT / 'tools/verify-document.py'))['compare']
1717
1818
1919class ParagraphEditTest(unittest.TestCase):
20 def assert_cold_graph(self, fixture):
21 with TemporaryDirectory() as temporary:
22 models = []
23 for source in ('candidate', 'native/notebook'):
24 folder = Path(temporary) / source.replace('/', '-')
25 shutil.copytree(fixture / 'native/read', folder / 'read')
26 compare(fixture / source, folder / 'read')
27 subprocess.run([EXPORTER, fixture / source / 'synthetic.one', folder / 'model'], check=True)
28 model = json.loads((folder / 'model/document.json').read_text())
29 models.append({r['nodes'][r['roots']['2']]['kind']['title']: (r, page)
30 for _, _, r, page in ordered_pages(model)})
31 self.assertEqual(models[0].keys(), models[1].keys())
32 for title, (old, page) in models[0].items():
33 saved, saved_page = models[1][title]
34 self.assertEqual(saved_page, page)
35 for outline in old['nodes'][page]['children']:
36 if old['nodes'][outline]['kind']['type'] != 'Outline': continue
37 for oid, node in walk(old, outline):
38 self.assertEqual(saved['nodes'][oid]['children'], node['children'])
39 self.assertEqual(saved['nodes'][oid]['content'], node['content'])
40
41 def test_offline_boundaries_retain_remote_edits_and_match_native_controls(self):
42 groups = [('splits', 'cold-split/read', 12), ('joins', 'joined/read', 12),
43 ('inheritance', 'join-edges/joined/read', 5), ('tags', 'join-tags/joined/read', 3)]
44 for name, control, count in groups:
45 fixture = FIXTURE / 'offline' / name
46 manifest = json.loads((fixture / 'manifest.json').read_text())
47 self.assertEqual(len(manifest), count)
48 with self.subTest(group=name):
49 self.assert_cold_graph(fixture)
50 captures = []
51 for folder in (FIXTURE / control, fixture / 'native/read'):
52 captures.append({page.get('name'): native_characters(page, page.findall('one:Outline', ns))
53 for path in folder.glob('page-*.xml') for page in [ET.parse(path).getroot()]})
54 for case in manifest:
55 self.assertTrue(case['revision'])
56 original = captures[0][case['case']]
57 current = captures[1][case['case']]
58 markers = 0
59 for old, new in zip(original, current, strict=True):
60 if new and new[0][0] == '☂':
61 markers += 1
62 new = new[1:]
63 for (a, left), (b, right) in zip(old, new, strict=True):
64 self.assertEqual(a, b)
65 for key in left.keys() | right.keys():
66 default = 'automatic' if key in ('color', 'highlight') else False
67 self.assertEqual(left.get(key, default), right.get(key, default))
68 self.assertEqual(markers, int(case['remote_prefix']))
69
2070 def test_rust_joins_match_native_controls_and_retain_cold_graph_identities(self):
2171 for name, control in [('split', FIXTURE / 'joined/read'),
2272 ('inheritance', FIXTURE / 'join-edges/joined/read'),
2373 ('tags', FIXTURE / 'join-tags/joined/read')]:
2474 fixture = FIXTURE / 'rust-join' / name
2575 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'])
76 with self.subTest(fixture=name):
77 self.assert_cold_graph(fixture)
4578 captures = []
4679 for folder in (control, fixture / 'native/read'):
4780 captures.append({page.get('name'): native_characters(page, page.findall('one:Outline', ns))