| author | |
| committer | |
| log | 4cd338a8b4d4e4acf3fc1f4e48cf94ac7596e5ea |
| tree | 463d11d3561ea5b5207254c4a885048b20f46d60 |
| parent | 02d45d734921e4a41dddbf7887dd3224bb4c7f4c |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
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-astra93 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 | ... | @@ -60,3 +60,11 @@ XML, preserve exact child/content identities across reopening, and compare |
| 60 | the native-rendered result to the corresponding keyboard-generated control. | 60 | the native-rendered result to the corresponding keyboard-generated control. |
| 61 | `export_native_paragraph_joins` regenerates candidates in a new directory specified | 61 | `export_native_paragraph_joins` regenerates candidates in a new directory specified |
| 62 | by `ONESTORE_PARAGRAPH_JOIN_OUTPUT`. | 62 | by `ONESTORE_PARAGRAPH_JOIN_OUTPUT`. |
| 63 | |||
| 64 | `offline` retains reconciled split/join candidates and cold native captures. | ||
| 65 | The cache queues edits before observing independent remote `☂` prefixes, reopens | ||
| 66 | between acknowledgements, and confirms modeled lost replies without replaying | ||
| 67 | operations. Tests compare native text/styles to the original keyboard controls, | ||
| 68 | account for the remote prefixes, and retain exact graph identities after reopening. | ||
| 69 | The ignored offline test `export_native_offline_paragraphs` regenerates candidates | ||
| 70 | in 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 | ||
| 3 | lang=en-US>☂Left</span><span style='font-weight:bold' lang=en-US>Bold</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=en-US>italic</span><span | ||
| 5 | lang=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 | ||
| 3 | lang=en-US>☂Left</span><span style='font-weight:bold' lang=en-US>Bold</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=en-US>italic</span><span | ||
| 5 | lang=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; | ||
| 3 | text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span | ||
| 4 | style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family: | ||
| 5 | Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri; | ||
| 6 | font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through; | ||
| 7 | font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family: | ||
| 8 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family: | ||
| 9 | Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size: | ||
| 10 | 11.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238; | ||
| 11 | background:yellow;mso-highlight:yellow'>color</span><span style='font-family: | ||
| 12 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family: | ||
| 13 | Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size: | ||
| 14 | 11.0pt'><br> | ||
| 15 | End</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 | ||
| 16 | style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span | ||
| 17 | style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span | ||
| 18 | style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span | ||
| 19 | style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed' | ||
| 20 | lang=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; | ||
| 3 | font-size:11.0pt'>☂Left</span><span style='font-weight:bold;font-family:Georgia; | ||
| 4 | font-size:18.0pt;color:#123456'>Right </span><span style='font-weight:bold; | ||
| 5 | font-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 | ||
| 3 | lang=en-US>☂Left child</span><span style='font-weight:bold' lang=en-US>Bold</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=en-US>italic</span><span | ||
| 5 | lang=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 | ||
| 3 | style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span | ||
| 4 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 5 | 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="{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 | ||
| 3 | style='font-weight:bold' lang=en-US>☂Bold</span><span lang=en-US> </span><span | ||
| 4 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 5 | 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: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> | ||
| 3 | soft 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 | ||
| 3 | style='font-weight:bold' lang=en-US>☂Bold</span><span lang=en-US> </span><span | ||
| 4 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 5 | 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="{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 | ||
| 3 | style='font-weight:bold' lang=en-US>☂Bold</span><span lang=en-US> </span><span | ||
| 4 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 5 | style='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 | ||
| 3 | style='font-weight:bold' lang=en-US>☂Bold</span><span lang=en-US> </span><span | ||
| 4 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 5 | style='color:#123456' lang=x-none>tai</span><span style='color:#123456' | ||
| 6 | lang=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 | ||
| 3 | style='font-weight:bold' lang=en-US>☂Bold</span><span lang=en-US> </span><span | ||
| 4 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 5 | style='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 | ||
| 3 | style='font-weight:bold' lang=en-US>☂Bold</span><span lang=en-US> </span><span | ||
| 4 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 5 | style='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; | ||
| 3 | text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span | ||
| 4 | style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family: | ||
| 5 | Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri; | ||
| 6 | font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through; | ||
| 7 | font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family: | ||
| 8 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family: | ||
| 9 | Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size: | ||
| 10 | 11.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238; | ||
| 11 | background:yellow;mso-highlight:yellow'>color</span><span style='font-family: | ||
| 12 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family: | ||
| 13 | Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size: | ||
| 14 | 11.0pt'><br> | ||
| 15 | End</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 | ||
| 16 | style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span | ||
| 17 | style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span | ||
| 18 | style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span | ||
| 19 | style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed' | ||
| 20 | lang=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 | ||
| 3 | style='font-weight:bold' lang=en-US>☂Bold</span><span lang=en-US> </span><span | ||
| 4 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 5 | style='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 | ||
| 3 | href="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 | ||
| 3 | style='font-weight:bold' lang=en-US>☂Bold</span><span lang=en-US> </span><span | ||
| 4 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 5 | style='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 | ||
| 3 | style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span | ||
| 4 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 5 | 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: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 | ||
| 3 | style='font-weight:bold' lang=en-US>☂Bold</span><span lang=en-US> </span><span | ||
| 4 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 5 | style='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> | ||
| 3 | soft 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 | ||
| 3 | style='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 | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=en-US>italic</span><span | ||
| 5 | lang=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 | ||
| 3 | style='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 | ||
| 4 | style='font-weight:bold' lang=en-US>ld</span><span lang=en-US> </span><span | ||
| 5 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 6 | 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="{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 | ||
| 3 | style='font-weight:bold' lang=en-US>☂Bold</span><span lang=en-US> </span><span | ||
| 4 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 5 | style='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 | ||
| 3 | style='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 | ||
| 4 | style='font-weight:bold' lang=en-US>ld</span><span lang=en-US> </span><span | ||
| 5 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 6 | style='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 | ||
| 3 | style='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 | ||
| 4 | style='font-weight:bold' lang=en-US>ld</span><span lang=en-US> </span><span | ||
| 5 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 6 | style='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 | ||
| 3 | style='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 | ||
| 4 | style='font-weight:bold' lang=en-US>ld</span><span lang=en-US> </span><span | ||
| 5 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 6 | style='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 | ||
| 3 | style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span | ||
| 4 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 5 | 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="{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 | ||
| 3 | style='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 | ||
| 4 | style='font-weight:bold' lang=en-US>ld</span><span lang=en-US> </span><span | ||
| 5 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 6 | style='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 | ||
| 3 | style='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 | ||
| 4 | style='font-weight:bold' lang=en-US>ld</span><span lang=en-US> </span><span | ||
| 5 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 6 | style='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 | ||
| 3 | href="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; | ||
| 3 | text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span | ||
| 4 | style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family: | ||
| 5 | Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri; | ||
| 6 | font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through; | ||
| 7 | font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family: | ||
| 8 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family: | ||
| 9 | Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size: | ||
| 10 | 11.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238; | ||
| 11 | background:yellow;mso-highlight:yellow'>color</span><span style='font-family: | ||
| 12 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family: | ||
| 13 | Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size: | ||
| 14 | 11.0pt'><br> | ||
| 15 | End</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 | ||
| 16 | style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span | ||
| 17 | style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span | ||
| 18 | style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span | ||
| 19 | style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed' | ||
| 20 | lang=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 | ||
| 3 | style='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 | ||
| 4 | style='font-weight:bold' lang=en-US>ld</span><span lang=en-US> </span><span | ||
| 5 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 6 | style='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; | ||
| 3 | text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span | ||
| 4 | style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family: | ||
| 5 | Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri; | ||
| 6 | font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through; | ||
| 7 | font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family: | ||
| 8 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family: | ||
| 9 | Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size: | ||
| 10 | 11.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238; | ||
| 11 | background:yellow;mso-highlight:yellow'>color</span><span style='font-family: | ||
| 12 | Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family: | ||
| 13 | Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size: | ||
| 14 | 11.0pt'><br> | ||
| 15 | End</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 | ||
| 16 | style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span | ||
| 17 | style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span | ||
| 18 | style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span | ||
| 19 | style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed' | ||
| 20 | lang=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 | ||
| 3 | lang=en-US>☂Left</span><span style='font-weight:bold' lang=en-US>Bold</span><span | ||
| 4 | lang=en-US> </span><span style='font-style:italic' lang=en-US>italic</span><span | ||
| 5 | lang=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 | ||
| 3 | style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span | ||
| 4 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 5 | 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="{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 | ||
| 3 | style='font-weight:bold' lang=en-US>Bold</span><span lang=en-US> </span><span | ||
| 4 | style='font-style:italic' lang=en-US>italic</span><span lang=en-US> &#129408; é </span><span | ||
| 5 | style='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 @@ | ... | @@ -1,7 +1,7 @@ |
| 1 | # onestore-offline | 1 | # onestore-offline |
| 2 | 2 | ||
| 3 | Durable local editing for a OneNote file, in an optional sibling crate. The current | 3 | Durable local editing for a OneNote file, in an optional sibling crate. The current |
| 4 | foundation stores a complete working image and typed text, insertion and formatting intents in a local | 4 | foundation stores a complete working image and typed editing intents in a local |
| 5 | SQLite database. `sync_once` provides a reconciliation step and `start_sync` owns | 5 | SQLite database. `sync_once` provides a reconciliation step and `start_sync` owns |
| 6 | automatic polling and reconnects. Local success does not | 6 | automatic polling and reconnects. Local success does not |
| 7 | acknowledge publication to a shared notebook. | 7 | acknowledge publication to a shared notebook. |
| ... | @@ -29,12 +29,30 @@ assert_eq!(pending.last().map(|edit| edit.id), local_id); | ... | @@ -29,12 +29,30 @@ assert_eq!(pending.last().map(|edit| edit.id), local_id); |
| 29 | intent identities. `Insertion::with_formatting` queues text and | 29 | intent identities. `Insertion::with_formatting` queues text and |
| 30 | its styles as one intent and one publication. Keep that value across retries; its `text_object()` identifies | 30 | its styles as one intent and one publication. Keep that value across retries; its `text_object()` identifies |
| 31 | the new text for subsequent offline edits. Pending entries expose | 31 | the new text for subsequent offline edits. Pending entries expose |
| 32 | `Operation::Text(TextEdit)`, `Operation::Insert(Insertion)` or | 32 | `Operation::Text(TextEdit)`, `Operation::Insert(Insertion)`, |
| 33 | `Operation::Format(FormatEdit)` through their | 33 | `Operation::Format(FormatEdit)`, `Operation::Split(SplitEdit)` and |
| 34 | `Operation::Join(JoinEdit)` through their | ||
| 34 | `operation` field. Synchronization applies these in queue order, so an inserted | 35 | `operation` field. Synchronization applies these in queue order, so an inserted |
| 35 | outline can precede its paragraphs and their later edits. Missing anchors or | 36 | outline can precede its paragraphs and their later edits. Missing anchors or |
| 36 | existing insertion identities preserve a conflict and the complete local image. | 37 | existing insertion identities preserve a conflict and the complete local image. |
| 37 | 38 | ||
| 39 | `split` and `join` accept the core `ParagraphSplit` and `ParagraphJoin` intents. | ||
| 40 | Splits retain allocated identities when the original UTF-16 boundary rebases; | ||
| 41 | dependent edits can address `ParagraphSplit::text_object()` immediately after | ||
| 42 | local acknowledgement. Both operations retain observed parent paths, children, | ||
| 43 | indentation, paragraph/list state and tags. Changed structure produces | ||
| 44 | `StructureChanged`. Remote text changes must leave unambiguous boundary mappings; | ||
| 45 | current remote character styles are preserved. Reconciliation does not silently | ||
| 46 | discard 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 | ||
| 49 | allocated identities. `rebase_join_conflict(id, local, remote)` reviews the original | ||
| 50 | join against current images. It rejects a change in which text identity survives, | ||
| 51 | because dependent edits still address that identity. As with other conflict | ||
| 52 | reviews, stale images, pending operations and uncertain attempts cannot be rebased. | ||
| 53 | An uncertain structural operation requires its original attempted revision for | ||
| 54 | confirmation; matching text or structure does not establish a receipt. | ||
| 55 | |||
| 38 | `rebase_paragraph_conflict(id, local, remote, parent, before)` and | 56 | `rebase_paragraph_conflict(id, local, remote, parent, before)` and |
| 39 | `rebase_outline_conflict(id, local, remote, page, x, y)` accept reviewed replacement | 57 | `rebase_outline_conflict(id, local, remote, page, x, y)` accept reviewed replacement |
| 40 | placements for the oldest insertion conflict. They preserve creation time, text, | 58 | placements 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 | ... | @@ -70,8 +88,9 @@ requested value is accepted. Enabling superscript or subscript also checks the |
| 70 | opposite attribute that the operation clears. If the remote image already satisfies | 88 | opposite attribute that the operation clears. If the remote image already satisfies |
| 71 | the whole operation, guarded confirmation still precedes a durable receipt. | 89 | the whole operation, guarded confirmation still precedes a durable receipt. |
| 72 | 90 | ||
| 73 | Recognized version-one through version-three caches migrate transactionally to the typed | 91 | Recognized earlier caches migrate transactionally to version six, which adds |
| 74 | queue. The migration retains images, local IDs, publication attempts, conflicts, | 92 | paragraph split/join intents and structural conflicts. The migration retains |
| 93 | images, local IDs, publication attempts, conflicts, | ||
| 75 | receipts and the autoincrement sequence; it does not reuse acknowledged IDs when | 94 | receipts and the autoincrement sequence; it does not reuse acknowledged IDs when |
| 76 | the pending queue is empty. | 95 | the pending queue is empty. |
| 77 | 96 |
crates/onestore-offline/examples/cache_probe.rs+3| ... | @@ -138,6 +138,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { | ... | @@ -138,6 +138,9 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { |
| 138 | font_size = Some(*value); | 138 | font_size = Some(*value); |
| 139 | *value as u64 - 6 | 139 | *value as u64 - 6 |
| 140 | } | 140 | } |
| 141 | onestore_offline::Operation::Split(_) | onestore_offline::Operation::Join(_) => { | ||
| 142 | panic!("This fixture does not generate paragraph boundary edits") | ||
| 143 | } | ||
| 141 | }; | 144 | }; |
| 142 | assert!(operations.last().is_none_or(|last| *last < operation)); | 145 | assert!(operations.last().is_none_or(|last| *last < operation)); |
| 143 | assert!(ids.last().is_none_or(|last| *last < pending.id)); | 146 | 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 | ... | @@ -10,10 +10,12 @@ use std::{fs::OpenOptions, io, ops::Range, path::Path, sync::Mutex, time::Durati |
| 10 | 10 | ||
| 11 | mod assets; | 11 | mod assets; |
| 12 | mod formatting; | 12 | mod formatting; |
| 13 | mod paragraph; | ||
| 13 | mod rebase; | 14 | mod rebase; |
| 14 | mod recovery; | 15 | mod recovery; |
| 15 | mod schema; | 16 | mod schema; |
| 16 | pub use formatting::FormatEdit; | 17 | pub use formatting::FormatEdit; |
| 18 | pub use paragraph::{JoinEdit, SplitEdit}; | ||
| 17 | pub use recovery::{Recovery, RecoverySummary}; | 19 | pub use recovery::{Recovery, RecoverySummary}; |
| 18 | mod sync; | 20 | mod sync; |
| 19 | pub use sync::{ConflictKind, EditStatus, Remote}; | 21 | pub use sync::{ConflictKind, EditStatus, Remote}; |
| ... | @@ -45,7 +47,7 @@ pub enum Error { | ... | @@ -45,7 +47,7 @@ pub enum Error { |
| 45 | type Result<T> = std::result::Result<T, Error>; | 47 | type Result<T> = std::result::Result<T, Error>; |
| 46 | 48 | ||
| 47 | const APPLICATION_ID: u32 = 0x4f4e454f; | 49 | const APPLICATION_ID: u32 = 0x4f4e454f; |
| 48 | const SCHEMA_VERSION: u32 = 5; | 50 | const SCHEMA_VERSION: u32 = 6; |
| 49 | 51 | ||
| 50 | /// Text and its observed precondition, retained across cache reopen and rebasing. | 52 | /// Text and its observed precondition, retained across cache reopen and rebasing. |
| 51 | #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] | 53 | #[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)] |
| ... | @@ -63,6 +65,8 @@ pub enum Operation { | ... | @@ -63,6 +65,8 @@ pub enum Operation { |
| 63 | Text(TextEdit), | 65 | Text(TextEdit), |
| 64 | Insert(Insertion), | 66 | Insert(Insertion), |
| 65 | Format(FormatEdit), | 67 | Format(FormatEdit), |
| 68 | Split(SplitEdit), | ||
| 69 | Join(JoinEdit), | ||
| 66 | } | 70 | } |
| 67 | 71 | ||
| 68 | /// A locally acknowledged intent; its ID remains stable across cache reopen. | 72 | /// A locally acknowledged intent; its ID remains stable across cache reopen. |
crates/onestore-offline/src/paragraph.rs created+241| ... | @@ -0,0 +1,241 @@ | ||
| 1 | use super::*; | ||
| 2 | use onestore::{ParagraphJoin, ParagraphSplit}; | ||
| 3 | use serde::{Deserialize, Serialize}; | ||
| 4 | use serde_json::{Value, json}; | ||
| 5 | use std::collections::{BTreeMap, BTreeSet}; | ||
| 6 | |||
| 7 | #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] | ||
| 8 | #[serde(deny_unknown_fields)] | ||
| 9 | struct 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)] | ||
| 17 | pub 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)] | ||
| 25 | pub struct JoinEdit { | ||
| 26 | pub intent: ParagraphJoin, | ||
| 27 | observed: [Observed; 2], | ||
| 28 | } | ||
| 29 | |||
| 30 | fn 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 | |||
| 112 | impl 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 | |||
| 136 | impl 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 | |||
| 182 | impl 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}; | ... | @@ -3,7 +3,7 @@ use rusqlite::{OptionalExtension, Transaction}; |
| 3 | 3 | ||
| 4 | const CONFLICTS: &str = "CREATE TABLE conflicts ( | 4 | const CONFLICTS: &str = "CREATE TABLE conflicts ( |
| 5 | edit_id INTEGER PRIMARY KEY REFERENCES edits(id) ON DELETE CASCADE, | 5 | 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) |
| 7 | ) STRICT;"; | 7 | ) STRICT;"; |
| 8 | 8 | ||
| 9 | const ASSETS: &str = "CREATE TABLE assets ( | 9 | const ASSETS: &str = "CREATE TABLE assets ( |
| ... | @@ -35,17 +35,15 @@ pub(crate) fn create(transaction: &Transaction<'_>) -> Result<()> { | ... | @@ -35,17 +35,15 @@ pub(crate) fn create(transaction: &Transaction<'_>) -> Result<()> { |
| 35 | } | 35 | } |
| 36 | 36 | ||
| 37 | pub(crate) fn migrate(transaction: &Transaction<'_>, version: u32) -> Result<()> { | 37 | pub(crate) fn migrate(transaction: &Transaction<'_>, version: u32) -> Result<()> { |
| 38 | if version == 4 { | 38 | if version >= 3 { |
| 39 | transaction.execute_batch(ASSETS)?; | ||
| 40 | return Ok(()); | ||
| 41 | } | ||
| 42 | if version == 3 { | ||
| 43 | transaction.execute_batch("ALTER TABLE conflicts RENAME TO old_conflicts;")?; | 39 | transaction.execute_batch("ALTER TABLE conflicts RENAME TO old_conflicts;")?; |
| 44 | transaction.execute_batch(CONFLICTS)?; | 40 | transaction.execute_batch(CONFLICTS)?; |
| 45 | transaction.execute_batch( | 41 | transaction.execute_batch( |
| 46 | "INSERT INTO conflicts SELECT * FROM old_conflicts; DROP TABLE old_conflicts;", | 42 | "INSERT INTO conflicts SELECT * FROM old_conflicts; DROP TABLE old_conflicts;", |
| 47 | )?; | 43 | )?; |
| 48 | transaction.execute_batch(ASSETS)?; | 44 | if version < 5 { |
| 45 | transaction.execute_batch(ASSETS)?; | ||
| 46 | } | ||
| 49 | return Ok(()); | 47 | return Ok(()); |
| 50 | } | 48 | } |
| 51 | 49 |
crates/onestore-offline/src/sync.rs+44-1| ... | @@ -18,6 +18,7 @@ pub enum ConflictKind { | ... | @@ -18,6 +18,7 @@ pub enum ConflictKind { |
| 18 | TargetUnavailable = 1, | 18 | TargetUnavailable = 1, |
| 19 | UnsupportedEdit = 2, | 19 | UnsupportedEdit = 2, |
| 20 | FormattingChanged = 3, | 20 | FormattingChanged = 3, |
| 21 | StructureChanged = 4, | ||
| 21 | } | 22 | } |
| 22 | 23 | ||
| 23 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | 24 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| ... | @@ -131,6 +132,8 @@ impl Replica { | ... | @@ -131,6 +132,8 @@ impl Replica { |
| 131 | } | 132 | } |
| 132 | let candidate = match &intent.operation { | 133 | let candidate = match &intent.operation { |
| 133 | Operation::Format(edit) => edit.prepare(&snapshot, intent.space)?, | 134 | 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)?, | ||
| 134 | Operation::Insert(insertion) => { | 137 | Operation::Insert(insertion) => { |
| 135 | PreparedEdit::insert(&snapshot, intent.space, insertion) | 138 | PreparedEdit::insert(&snapshot, intent.space, insertion) |
| 136 | .map_err(|_| ConflictKind::UnsupportedEdit) | 139 | .map_err(|_| ConflictKind::UnsupportedEdit) |
| ... | @@ -222,7 +225,7 @@ impl Replica { | ... | @@ -222,7 +225,7 @@ impl Replica { |
| 222 | Ok(Some((intent.id, EditStatus::Published { revision }))) | 225 | Ok(Some((intent.id, EditStatus::Published { revision }))) |
| 223 | } | 226 | } |
| 224 | 227 | ||
| 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. |
| 226 | /// The requested replacement/attributes, local image, intent ID and later edits are preserved. | 229 | /// The requested replacement/attributes, local image, intent ID and later edits are preserved. |
| 227 | /// Both supplied images must match `snapshot` and `remote_snapshot`; stale review | 230 | /// Both supplied images must match `snapshot` and `remote_snapshot`; stale review |
| 228 | /// returns `Io(ResourceBusy)`. Uncertain publication attempts cannot be rebased. | 231 | /// returns `Io(ResourceBusy)`. Uncertain publication attempts cannot be rebased. |
| ... | @@ -270,6 +273,30 @@ impl Replica { | ... | @@ -270,6 +273,30 @@ impl Replica { |
| 270 | )? | 273 | )? |
| 271 | .0, | 274 | .0, |
| 272 | ), | 275 | ), |
| 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 | } | ||
| 273 | Operation::Insert(_) => { | 300 | Operation::Insert(_) => { |
| 274 | return Err(io::Error::new( | 301 | return Err(io::Error::new( |
| 275 | io::ErrorKind::InvalidInput, | 302 | io::ErrorKind::InvalidInput, |
| ... | @@ -281,6 +308,21 @@ impl Replica { | ... | @@ -281,6 +308,21 @@ impl Replica { |
| 281 | }) | 308 | }) |
| 282 | } | 309 | } |
| 283 | 310 | ||
| 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 | |||
| 284 | /// Repositions the oldest paragraph insertion conflict against reviewed cache images. | 326 | /// Repositions the oldest paragraph insertion conflict against reviewed cache images. |
| 285 | /// Object identities and dependent edits are retained; uncertain attempts cannot be moved. | 327 | /// Object identities and dependent edits are retained; uncertain attempts cannot be moved. |
| 286 | pub fn rebase_paragraph_conflict( | 328 | pub fn rebase_paragraph_conflict( |
| ... | @@ -449,6 +491,7 @@ pub(crate) fn status(connection: &Connection, id: u64) -> Result<Option<EditStat | ... | @@ -449,6 +491,7 @@ pub(crate) fn status(connection: &Connection, id: u64) -> Result<Option<EditStat |
| 449 | 1 => ConflictKind::TargetUnavailable, | 491 | 1 => ConflictKind::TargetUnavailable, |
| 450 | 2 => ConflictKind::UnsupportedEdit, | 492 | 2 => ConflictKind::UnsupportedEdit, |
| 451 | 3 => ConflictKind::FormattingChanged, | 493 | 3 => ConflictKind::FormattingChanged, |
| 494 | 4 => ConflictKind::StructureChanged, | ||
| 452 | _ => { | 495 | _ => { |
| 453 | return Err(io::Error::new( | 496 | return Err(io::Error::new( |
| 454 | io::ErrorKind::InvalidData, | 497 | 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() { | ... | @@ -710,12 +710,19 @@ fn twelve_local_clients_preserve_inserted_identities_and_dependent_edits() { |
| 710 | 710 | ||
| 711 | #[test] | 711 | #[test] |
| 712 | fn unrecognized_persisted_operations_are_rejected_without_dropping_fields() { | 712 | fn unrecognized_persisted_operations_are_rejected_without_dropping_fields() { |
| 713 | for operation in ["Text", "Insert", "Format"] { | 713 | for operation in ["Text", "Insert", "Format", "Split", "Join"] { |
| 714 | let directory = tempfile::tempdir().unwrap(); | 714 | let directory = tempfile::tempdir().unwrap(); |
| 715 | let path = directory.path().join("unknown.sqlite"); | 715 | let path = directory.path().join("unknown.sqlite"); |
| 716 | let source = onestore::create_section("unknown.one", "Original", "Author").unwrap(); | 716 | let mut source = onestore::create_section("unknown.one", "Original", "Author").unwrap(); |
| 717 | let cache = Replica::create(&path, &source).unwrap(); | ||
| 718 | let (sid, oid, _) = target(&source); | 717 | 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(); | ||
| 719 | if operation == "Insert" { | 726 | if operation == "Insert" { |
| 720 | let store = Store::parse(&source).unwrap(); | 727 | let store = Store::parse(&source).unwrap(); |
| 721 | let index = RevisionIndex::parse(&store).unwrap(); | 728 | let index = RevisionIndex::parse(&store).unwrap(); |
| ... | @@ -726,6 +733,16 @@ fn unrecognized_persisted_operations_are_rejected_without_dropping_fields() { | ... | @@ -726,6 +733,16 @@ fn unrecognized_persisted_operations_are_rejected_without_dropping_fields() { |
| 726 | cache.insert(&source, sid, &insertion).unwrap(); | 733 | cache.insert(&source, sid, &insertion).unwrap(); |
| 727 | } else if operation == "Text" { | 734 | } else if operation == "Text" { |
| 728 | cache.edit_text(&source, sid, oid, 0..0, "New ").unwrap(); | 735 | 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(); | ||
| 729 | } else { | 746 | } else { |
| 730 | cache | 747 | cache |
| 731 | .format( | 748 | .format( |
| ... | @@ -746,7 +763,7 @@ fn unrecognized_persisted_operations_are_rejected_without_dropping_fields() { | ... | @@ -746,7 +763,7 @@ fn unrecognized_persisted_operations_are_rejected_without_dropping_fields() { |
| 746 | value[operation]["future_option"] = true.into(); | 763 | value[operation]["future_option"] = true.into(); |
| 747 | db.execute("UPDATE edits SET operation=?1", [value.to_string()]) | 764 | db.execute("UPDATE edits SET operation=?1", [value.to_string()]) |
| 748 | .unwrap(); | 765 | .unwrap(); |
| 749 | if operation != "Format" { | 766 | if matches!(operation, "Text" | "Insert") { |
| 750 | 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(); | 767 | 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(); |
| 751 | } | 768 | } |
| 752 | drop(db); | 769 | drop(db); |
| ... | @@ -757,3 +774,101 @@ fn unrecognized_persisted_operations_are_rejected_without_dropping_fields() { | ... | @@ -757,3 +774,101 @@ fn unrecognized_persisted_operations_are_rejected_without_dropping_fields() { |
| 757 | assert_eq!(fs::read(&path).unwrap(), before); | 774 | assert_eq!(fs::read(&path).unwrap(), before); |
| 758 | } | 775 | } |
| 759 | } | 776 | } |
| 777 | |||
| 778 | #[test] | ||
| 779 | fn 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::{ | ... | @@ -5,6 +5,528 @@ use onestore::{ |
| 5 | use onestore_offline::{ConflictKind, EditStatus, Error, Remote, Replica}; | 5 | use onestore_offline::{ConflictKind, EditStatus, Error, Remote, Replica}; |
| 6 | use std::io; | 6 | use std::io; |
| 7 | 7 | ||
| 8 | mod 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 | |||
| 8 | #[derive(Clone, Copy, Default)] | 530 | #[derive(Clone, Copy, Default)] |
| 9 | enum Fault { | 531 | enum Fault { |
| 10 | #[default] | 532 | #[default] |
| ... | @@ -739,7 +1261,7 @@ fn version_one_cache_migration_preserves_images_intents_and_local_ids() { | ... | @@ -739,7 +1261,7 @@ fn version_one_cache_migration_preserves_images_intents_and_local_ids() { |
| 739 | assert_eq!( | 1261 | assert_eq!( |
| 740 | db.pragma_query_value(None, "user_version", |row| row.get::<_, u32>(0)) | 1262 | db.pragma_query_value(None, "user_version", |row| row.get::<_, u32>(0)) |
| 741 | .unwrap(), | 1263 | .unwrap(), |
| 742 | 5 | 1264 | 6 |
| 743 | ); | 1265 | ); |
| 744 | } | 1266 | } |
| 745 | 1267 |
crates/onestore/README.md+3-2| ... | @@ -95,8 +95,9 @@ Ambiguous ancestry, unsupported indentation transitions and unknown implicit | ... | @@ -95,8 +95,9 @@ Ambiguous ancestry, unsupported indentation transitions and unknown implicit |
| 95 | font/language inheritance reject before I/O. This is a logical join, so keyboard | 95 | font/language inheritance reject before I/O. This is a logical join, so keyboard |
| 96 | actions that only change list or indentation state remain separate operations. | 96 | actions that only change list or indentation state remain separate operations. |
| 97 | Generated fields, protected targets and unsupported run-data boundary changes are | 97 | Generated fields, protected targets and unsupported run-data boundary changes are |
| 98 | rejected before publication. Local caches expose text, insertion and formatting edits; | 98 | rejected before publication. The [offline crate](../onestore-offline/README.md) |
| 99 | the [document-writer acceptance](../../evidence/MILESTONE9.md#document-writer-and-offline-acceptance) | 99 | documents durable local operations and reconciliation. The |
| 100 | [document-writer acceptance](../../evidence/MILESTONE9.md#document-writer-and-offline-acceptance) | ||
| 100 | includes twelve mixed native/Rust clients, outages, lost replies and native revision retirement. | 101 | includes twelve mixed native/Rust clients, outages, lost replies and native revision retirement. |
| 101 | 102 | ||
| 102 | External `.onebin` references identify payloads for the caller to obtain. Cloud | 103 | External `.onebin` references identify payloads for the caller to obtain. Cloud |
crates/onestore/src/paragraph.rs+18| ... | @@ -61,6 +61,19 @@ impl ParagraphSplit { | ... | @@ -61,6 +61,19 @@ impl ParagraphSplit { |
| 61 | } | 61 | } |
| 62 | } | 62 | } |
| 63 | 63 | ||
| 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 | |||
| 64 | pub(crate) fn apply(&self, source: &[u8], space: ExGuid) -> Result<Vec<u8>, Error> { | 77 | pub(crate) fn apply(&self, source: &[u8], space: ExGuid) -> Result<Vec<u8>, Error> { |
| 65 | if self.guid == [0; 16] || self.text.guid == [0; 16] || self.author.contains('\0') { | 78 | if self.guid == [0; 16] || self.text.guid == [0; 16] || self.author.contains('\0') { |
| 66 | return Err(invalid( | 79 | return Err(invalid( |
| ... | @@ -418,6 +431,11 @@ pub struct ParagraphJoin { | ... | @@ -418,6 +431,11 @@ pub struct ParagraphJoin { |
| 418 | } | 431 | } |
| 419 | 432 | ||
| 420 | impl ParagraphJoin { | 433 | impl ParagraphJoin { |
| 434 | /// Ordered left and right text identities. | ||
| 435 | pub fn texts(&self) -> [ExGuid; 2] { | ||
| 436 | [self.left, self.right] | ||
| 437 | } | ||
| 438 | |||
| 421 | /// Select the preceding leaf paragraph's text and the following paragraph's text. | 439 | /// Select the preceding leaf paragraph's text and the following paragraph's text. |
| 422 | pub fn new(left: ExGuid, right: ExGuid, author: &str) -> Result<Self, Error> { | 440 | pub fn new(left: ExGuid, right: ExGuid, author: &str) -> Result<Self, Error> { |
| 423 | if left == right || left.guid == [0; 16] || right.guid == [0; 16] || author.contains('\0') { | 441 | if left == right || left.guid == [0; 16] || right.guid == [0; 16] || author.contains('\0') { |
fuzz/Cargo.lock+303| ... | @@ -51,6 +51,12 @@ dependencies = [ | ... | @@ -51,6 +51,12 @@ dependencies = [ |
| 51 | "hybrid-array", | 51 | "hybrid-array", |
| 52 | ] | 52 | ] |
| 53 | 53 | ||
| 54 | [[package]] | ||
| 55 | name = "bumpalo" | ||
| 56 | version = "3.20.3" | ||
| 57 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 58 | checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" | ||
| 59 | |||
| 54 | [[package]] | 60 | [[package]] |
| 55 | name = "cbc" | 61 | name = "cbc" |
| 56 | version = "0.2.1" | 62 | version = "0.2.1" |
| ... | @@ -138,12 +144,46 @@ dependencies = [ | ... | @@ -138,12 +144,46 @@ dependencies = [ |
| 138 | "zeroize", | 144 | "zeroize", |
| 139 | ] | 145 | ] |
| 140 | 146 | ||
| 147 | [[package]] | ||
| 148 | name = "errno" | ||
| 149 | version = "0.3.14" | ||
| 150 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 151 | checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" | ||
| 152 | dependencies = [ | ||
| 153 | "libc", | ||
| 154 | "windows-sys", | ||
| 155 | ] | ||
| 156 | |||
| 157 | [[package]] | ||
| 158 | name = "fallible-iterator" | ||
| 159 | version = "0.3.0" | ||
| 160 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 161 | checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" | ||
| 162 | |||
| 163 | [[package]] | ||
| 164 | name = "fallible-streaming-iterator" | ||
| 165 | version = "0.1.9" | ||
| 166 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 167 | checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" | ||
| 168 | |||
| 169 | [[package]] | ||
| 170 | name = "fastrand" | ||
| 171 | version = "2.5.0" | ||
| 172 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 173 | checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" | ||
| 174 | |||
| 141 | [[package]] | 175 | [[package]] |
| 142 | name = "find-msvc-tools" | 176 | name = "find-msvc-tools" |
| 143 | version = "0.1.12" | 177 | version = "0.1.12" |
| 144 | source = "registry+https://github.com/rust-lang/crates.io-index" | 178 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 145 | checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" | 179 | checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" |
| 146 | 180 | ||
| 181 | [[package]] | ||
| 182 | name = "foldhash" | ||
| 183 | version = "0.2.0" | ||
| 184 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 185 | checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" | ||
| 186 | |||
| 147 | [[package]] | 187 | [[package]] |
| 148 | name = "getrandom" | 188 | name = "getrandom" |
| 149 | version = "0.4.3" | 189 | version = "0.4.3" |
| ... | @@ -155,6 +195,33 @@ dependencies = [ | ... | @@ -155,6 +195,33 @@ dependencies = [ |
| 155 | "r-efi", | 195 | "r-efi", |
| 156 | ] | 196 | ] |
| 157 | 197 | ||
| 198 | [[package]] | ||
| 199 | name = "hashbrown" | ||
| 200 | version = "0.16.1" | ||
| 201 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 202 | checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" | ||
| 203 | dependencies = [ | ||
| 204 | "foldhash", | ||
| 205 | ] | ||
| 206 | |||
| 207 | [[package]] | ||
| 208 | name = "hashbrown" | ||
| 209 | version = "0.17.1" | ||
| 210 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 211 | checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" | ||
| 212 | dependencies = [ | ||
| 213 | "foldhash", | ||
| 214 | ] | ||
| 215 | |||
| 216 | [[package]] | ||
| 217 | name = "hashlink" | ||
| 218 | version = "0.12.2" | ||
| 219 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 220 | checksum = "a596f1b20ed2cc5ecac41a164aaebc7258057060f06c0cf7a2ba3991ee7990fb" | ||
| 221 | dependencies = [ | ||
| 222 | "hashbrown 0.17.1", | ||
| 223 | ] | ||
| 224 | |||
| 158 | [[package]] | 225 | [[package]] |
| 159 | name = "hybrid-array" | 226 | name = "hybrid-array" |
| 160 | version = "0.4.14" | 227 | version = "0.4.14" |
| ... | @@ -191,6 +258,16 @@ dependencies = [ | ... | @@ -191,6 +258,16 @@ dependencies = [ |
| 191 | "libc", | 258 | "libc", |
| 192 | ] | 259 | ] |
| 193 | 260 | ||
| 261 | [[package]] | ||
| 262 | name = "js-sys" | ||
| 263 | version = "0.3.105" | ||
| 264 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 265 | checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" | ||
| 266 | dependencies = [ | ||
| 267 | "cfg-if", | ||
| 268 | "wasm-bindgen", | ||
| 269 | ] | ||
| 270 | |||
| 194 | [[package]] | 271 | [[package]] |
| 195 | name = "libc" | 272 | name = "libc" |
| 196 | version = "0.2.189" | 273 | version = "0.2.189" |
| ... | @@ -207,6 +284,23 @@ dependencies = [ | ... | @@ -207,6 +284,23 @@ dependencies = [ |
| 207 | "cc", | 284 | "cc", |
| 208 | ] | 285 | ] |
| 209 | 286 | ||
| 287 | [[package]] | ||
| 288 | name = "libsqlite3-sys" | ||
| 289 | version = "0.38.2" | ||
| 290 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 291 | checksum = "f1d20bef17f513b9b3004532233187769cd072d790971f4e4da0e346eb6401e8" | ||
| 292 | dependencies = [ | ||
| 293 | "cc", | ||
| 294 | "pkg-config", | ||
| 295 | "vcpkg", | ||
| 296 | ] | ||
| 297 | |||
| 298 | [[package]] | ||
| 299 | name = "linux-raw-sys" | ||
| 300 | version = "0.12.1" | ||
| 301 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 302 | checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" | ||
| 303 | |||
| 210 | [[package]] | 304 | [[package]] |
| 211 | name = "md5" | 305 | name = "md5" |
| 212 | version = "0.8.1" | 306 | version = "0.8.1" |
| ... | @@ -231,6 +325,12 @@ dependencies = [ | ... | @@ -231,6 +325,12 @@ dependencies = [ |
| 231 | "libc", | 325 | "libc", |
| 232 | ] | 326 | ] |
| 233 | 327 | ||
| 328 | [[package]] | ||
| 329 | name = "once_cell" | ||
| 330 | version = "1.21.4" | ||
| 331 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 332 | checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" | ||
| 333 | |||
| 234 | [[package]] | 334 | [[package]] |
| 235 | name = "onestore" | 335 | name = "onestore" |
| 236 | version = "0.1.0" | 336 | version = "0.1.0" |
| ... | @@ -255,9 +355,40 @@ dependencies = [ | ... | @@ -255,9 +355,40 @@ dependencies = [ |
| 255 | "libfuzzer-sys", | 355 | "libfuzzer-sys", |
| 256 | "md5", | 356 | "md5", |
| 257 | "onestore", | 357 | "onestore", |
| 358 | "onestore-offline", | ||
| 258 | "serde_json", | 359 | "serde_json", |
| 360 | "tempfile", | ||
| 259 | ] | 361 | ] |
| 260 | 362 | ||
| 363 | [[package]] | ||
| 364 | name = "onestore-notebook" | ||
| 365 | version = "0.1.0" | ||
| 366 | dependencies = [ | ||
| 367 | "onestore", | ||
| 368 | "serde", | ||
| 369 | "thiserror", | ||
| 370 | ] | ||
| 371 | |||
| 372 | [[package]] | ||
| 373 | name = "onestore-offline" | ||
| 374 | version = "0.1.0" | ||
| 375 | dependencies = [ | ||
| 376 | "onestore", | ||
| 377 | "onestore-notebook", | ||
| 378 | "rusqlite", | ||
| 379 | "serde", | ||
| 380 | "serde_json", | ||
| 381 | "sha2", | ||
| 382 | "tempfile", | ||
| 383 | "thiserror", | ||
| 384 | ] | ||
| 385 | |||
| 386 | [[package]] | ||
| 387 | name = "pkg-config" | ||
| 388 | version = "0.3.34" | ||
| 389 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 390 | checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" | ||
| 391 | |||
| 261 | [[package]] | 392 | [[package]] |
| 262 | name = "proc-macro2" | 393 | name = "proc-macro2" |
| 263 | version = "1.0.107" | 394 | version = "1.0.107" |
| ... | @@ -291,6 +422,50 @@ dependencies = [ | ... | @@ -291,6 +422,50 @@ dependencies = [ |
| 291 | "memchr", | 422 | "memchr", |
| 292 | ] | 423 | ] |
| 293 | 424 | ||
| 425 | [[package]] | ||
| 426 | name = "rsqlite-vfs" | ||
| 427 | version = "0.1.1" | ||
| 428 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 429 | checksum = "c51c9ae4df8a7fba42103df5c621fa3c37eccf3a3c650879e90fc48b11cc192c" | ||
| 430 | dependencies = [ | ||
| 431 | "hashbrown 0.16.1", | ||
| 432 | "thiserror", | ||
| 433 | ] | ||
| 434 | |||
| 435 | [[package]] | ||
| 436 | name = "rusqlite" | ||
| 437 | version = "0.40.2" | ||
| 438 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 439 | checksum = "23f2a97da3e3873c73cb2a2e71b35c40ff95e0b1eefa8d72d8499a6928c3b5b3" | ||
| 440 | dependencies = [ | ||
| 441 | "bitflags", | ||
| 442 | "fallible-iterator", | ||
| 443 | "fallible-streaming-iterator", | ||
| 444 | "hashlink", | ||
| 445 | "libsqlite3-sys", | ||
| 446 | "smallvec", | ||
| 447 | "sqlite-wasm-rs", | ||
| 448 | ] | ||
| 449 | |||
| 450 | [[package]] | ||
| 451 | name = "rustix" | ||
| 452 | version = "1.1.4" | ||
| 453 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 454 | checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" | ||
| 455 | dependencies = [ | ||
| 456 | "bitflags", | ||
| 457 | "errno", | ||
| 458 | "libc", | ||
| 459 | "linux-raw-sys", | ||
| 460 | "windows-sys", | ||
| 461 | ] | ||
| 462 | |||
| 463 | [[package]] | ||
| 464 | name = "rustversion" | ||
| 465 | version = "1.0.23" | ||
| 466 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 467 | checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" | ||
| 468 | |||
| 294 | [[package]] | 469 | [[package]] |
| 295 | name = "serde" | 470 | name = "serde" |
| 296 | version = "1.0.229" | 471 | version = "1.0.229" |
| ... | @@ -345,12 +520,41 @@ dependencies = [ | ... | @@ -345,12 +520,41 @@ dependencies = [ |
| 345 | "digest", | 520 | "digest", |
| 346 | ] | 521 | ] |
| 347 | 522 | ||
| 523 | [[package]] | ||
| 524 | name = "sha2" | ||
| 525 | version = "0.11.0" | ||
| 526 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 527 | checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" | ||
| 528 | dependencies = [ | ||
| 529 | "cfg-if", | ||
| 530 | "cpufeatures", | ||
| 531 | "digest", | ||
| 532 | ] | ||
| 533 | |||
| 348 | [[package]] | 534 | [[package]] |
| 349 | name = "shlex" | 535 | name = "shlex" |
| 350 | version = "2.0.1" | 536 | version = "2.0.1" |
| 351 | source = "registry+https://github.com/rust-lang/crates.io-index" | 537 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 352 | checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" | 538 | checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" |
| 353 | 539 | ||
| 540 | [[package]] | ||
| 541 | name = "smallvec" | ||
| 542 | version = "1.16.0" | ||
| 543 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 544 | checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" | ||
| 545 | |||
| 546 | [[package]] | ||
| 547 | name = "sqlite-wasm-rs" | ||
| 548 | version = "0.5.5" | ||
| 549 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 550 | checksum = "dc3efc0da82635d7e1ced0053bbbfa8c7ab9645d0bf36ceb4f7127bb85315d75" | ||
| 551 | dependencies = [ | ||
| 552 | "cc", | ||
| 553 | "js-sys", | ||
| 554 | "rsqlite-vfs", | ||
| 555 | "wasm-bindgen", | ||
| 556 | ] | ||
| 557 | |||
| 354 | [[package]] | 558 | [[package]] |
| 355 | name = "subtle" | 559 | name = "subtle" |
| 356 | version = "2.6.1" | 560 | version = "2.6.1" |
| ... | @@ -368,6 +572,39 @@ dependencies = [ | ... | @@ -368,6 +572,39 @@ dependencies = [ |
| 368 | "unicode-ident", | 572 | "unicode-ident", |
| 369 | ] | 573 | ] |
| 370 | 574 | ||
| 575 | [[package]] | ||
| 576 | name = "tempfile" | ||
| 577 | version = "3.27.0" | ||
| 578 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 579 | checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" | ||
| 580 | dependencies = [ | ||
| 581 | "fastrand", | ||
| 582 | "getrandom", | ||
| 583 | "once_cell", | ||
| 584 | "rustix", | ||
| 585 | "windows-sys", | ||
| 586 | ] | ||
| 587 | |||
| 588 | [[package]] | ||
| 589 | name = "thiserror" | ||
| 590 | version = "2.0.20" | ||
| 591 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 592 | checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" | ||
| 593 | dependencies = [ | ||
| 594 | "thiserror-impl", | ||
| 595 | ] | ||
| 596 | |||
| 597 | [[package]] | ||
| 598 | name = "thiserror-impl" | ||
| 599 | version = "2.0.20" | ||
| 600 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 601 | checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" | ||
| 602 | dependencies = [ | ||
| 603 | "proc-macro2", | ||
| 604 | "quote", | ||
| 605 | "syn", | ||
| 606 | ] | ||
| 607 | |||
| 371 | [[package]] | 608 | [[package]] |
| 372 | name = "typenum" | 609 | name = "typenum" |
| 373 | version = "1.20.1" | 610 | version = "1.20.1" |
| ... | @@ -380,6 +617,72 @@ version = "1.0.24" | ... | @@ -380,6 +617,72 @@ version = "1.0.24" |
| 380 | source = "registry+https://github.com/rust-lang/crates.io-index" | 617 | source = "registry+https://github.com/rust-lang/crates.io-index" |
| 381 | checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" | 618 | checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" |
| 382 | 619 | ||
| 620 | [[package]] | ||
| 621 | name = "vcpkg" | ||
| 622 | version = "0.2.15" | ||
| 623 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 624 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" | ||
| 625 | |||
| 626 | [[package]] | ||
| 627 | name = "wasm-bindgen" | ||
| 628 | version = "0.2.128" | ||
| 629 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 630 | checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" | ||
| 631 | dependencies = [ | ||
| 632 | "cfg-if", | ||
| 633 | "once_cell", | ||
| 634 | "rustversion", | ||
| 635 | "wasm-bindgen-macro", | ||
| 636 | "wasm-bindgen-shared", | ||
| 637 | ] | ||
| 638 | |||
| 639 | [[package]] | ||
| 640 | name = "wasm-bindgen-macro" | ||
| 641 | version = "0.2.128" | ||
| 642 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 643 | checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" | ||
| 644 | dependencies = [ | ||
| 645 | "quote", | ||
| 646 | "wasm-bindgen-macro-support", | ||
| 647 | ] | ||
| 648 | |||
| 649 | [[package]] | ||
| 650 | name = "wasm-bindgen-macro-support" | ||
| 651 | version = "0.2.128" | ||
| 652 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 653 | checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" | ||
| 654 | dependencies = [ | ||
| 655 | "bumpalo", | ||
| 656 | "proc-macro2", | ||
| 657 | "quote", | ||
| 658 | "syn", | ||
| 659 | "wasm-bindgen-shared", | ||
| 660 | ] | ||
| 661 | |||
| 662 | [[package]] | ||
| 663 | name = "wasm-bindgen-shared" | ||
| 664 | version = "0.2.128" | ||
| 665 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 666 | checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" | ||
| 667 | dependencies = [ | ||
| 668 | "unicode-ident", | ||
| 669 | ] | ||
| 670 | |||
| 671 | [[package]] | ||
| 672 | name = "windows-link" | ||
| 673 | version = "0.2.1" | ||
| 674 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 675 | checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" | ||
| 676 | |||
| 677 | [[package]] | ||
| 678 | name = "windows-sys" | ||
| 679 | version = "0.61.2" | ||
| 680 | source = "registry+https://github.com/rust-lang/crates.io-index" | ||
| 681 | checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" | ||
| 682 | dependencies = [ | ||
| 683 | "windows-link", | ||
| 684 | ] | ||
| 685 | |||
| 383 | [[package]] | 686 | [[package]] |
| 384 | name = "zeroize" | 687 | name = "zeroize" |
| 385 | version = "1.9.0" | 688 | version = "1.9.0" |
fuzz/Cargo.toml+9| ... | @@ -10,6 +10,8 @@ cargo-fuzz = true | ... | @@ -10,6 +10,8 @@ cargo-fuzz = true |
| 10 | [dependencies] | 10 | [dependencies] |
| 11 | libfuzzer-sys = "0.4" | 11 | libfuzzer-sys = "0.4" |
| 12 | onestore = { path = "../crates/onestore", features = ["protected"] } | 12 | onestore = { path = "../crates/onestore", features = ["protected"] } |
| 13 | onestore-offline = { path = "../crates/onestore-offline" } | ||
| 14 | tempfile = "3" | ||
| 13 | md5 = "0.8.1" | 15 | md5 = "0.8.1" |
| 14 | serde_json = "1" | 16 | serde_json = "1" |
| 15 | 17 | ||
| ... | @@ -91,3 +93,10 @@ path = "fuzz_targets/paragraph.rs" | ... | @@ -91,3 +93,10 @@ path = "fuzz_targets/paragraph.rs" |
| 91 | test = false | 93 | test = false |
| 92 | doc = false | 94 | doc = false |
| 93 | bench = false | 95 | bench = false |
| 96 | |||
| 97 | [[bin]] | ||
| 98 | name = "offline_paragraph" | ||
| 99 | path = "fuzz_targets/offline_paragraph.rs" | ||
| 100 | test = false | ||
| 101 | doc = false | ||
| 102 | bench = false |
fuzz/fuzz_targets/offline_paragraph.rs created+165| ... | @@ -0,0 +1,165 @@ | ||
| 1 | #![no_main] | ||
| 2 | use libfuzzer_sys::fuzz_target; | ||
| 3 | use onestore::{ | ||
| 4 | CommitError, ExGuid, ParagraphJoin, ParagraphSplit, PreparedEdit, RevisionIndex, Store, | ||
| 5 | document::{Document, Kind}, | ||
| 6 | }; | ||
| 7 | use onestore_offline::{EditStatus, Remote, Replica}; | ||
| 8 | use std::{io, sync::LazyLock}; | ||
| 9 | |||
| 10 | #[path = "../../crates/onestore/tests/support/disk.rs"] | ||
| 11 | mod disk; | ||
| 12 | |||
| 13 | const TEXT: &str = "a🦀 e\u{301} 東京\rEnd"; | ||
| 14 | |||
| 15 | static 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 | |||
| 31 | struct Server(disk::Disk); | ||
| 32 | |||
| 33 | impl 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 | |||
| 45 | fn 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 | |||
| 75 | fuzz_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'] | ... | @@ -17,31 +17,64 @@ compare = runpy.run_path(str(ROOT / 'tools/verify-document.py'))['compare'] |
| 17 | 17 | ||
| 18 | 18 | ||
| 19 | class ParagraphEditTest(unittest.TestCase): | 19 | class 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 | |||
| 20 | def test_rust_joins_match_native_controls_and_retain_cold_graph_identities(self): | 70 | def test_rust_joins_match_native_controls_and_retain_cold_graph_identities(self): |
| 21 | for name, control in [('split', FIXTURE / 'joined/read'), | 71 | for name, control in [('split', FIXTURE / 'joined/read'), |
| 22 | ('inheritance', FIXTURE / 'join-edges/joined/read'), | 72 | ('inheritance', FIXTURE / 'join-edges/joined/read'), |
| 23 | ('tags', FIXTURE / 'join-tags/joined/read')]: | 73 | ('tags', FIXTURE / 'join-tags/joined/read')]: |
| 24 | fixture = FIXTURE / 'rust-join' / name | 74 | fixture = FIXTURE / 'rust-join' / name |
| 25 | manifest = json.loads((fixture / 'manifest.json').read_text()) | 75 | manifest = json.loads((fixture / 'manifest.json').read_text()) |
| 26 | with self.subTest(fixture=name), TemporaryDirectory() as temporary: | 76 | with self.subTest(fixture=name): |
| 27 | models = [] | 77 | self.assert_cold_graph(fixture) |
| 28 | for source in ('candidate', 'native/notebook'): | ||
| 29 | folder = Path(temporary) / source.replace('/', '-') | ||
| 30 | shutil.copytree(fixture / 'native/read', folder / 'read') | ||
| 31 | compare(fixture / source, folder / 'read') | ||
| 32 | subprocess.run([EXPORTER, fixture / source / 'synthetic.one', folder / 'model'], check=True) | ||
| 33 | document = json.loads((folder / 'model/document.json').read_text()) | ||
| 34 | models.append({r['nodes'][r['roots']['2']]['kind']['title']: (r, page) | ||
| 35 | for _, _, r, page in ordered_pages(document)}) | ||
| 36 | self.assertEqual(models[0].keys(), models[1].keys()) | ||
| 37 | for title, (old, page) in models[0].items(): | ||
| 38 | saved, saved_page = models[1][title] | ||
| 39 | self.assertEqual(page, saved_page) | ||
| 40 | for outline in old['nodes'][page]['children']: | ||
| 41 | if old['nodes'][outline]['kind']['type'] != 'Outline': continue | ||
| 42 | for oid, node in walk(old, outline): | ||
| 43 | self.assertEqual(saved['nodes'][oid]['children'], node['children']) | ||
| 44 | self.assertEqual(saved['nodes'][oid]['content'], node['content']) | ||
| 45 | captures = [] | 78 | captures = [] |
| 46 | for folder in (control, fixture / 'native/read'): | 79 | for folder in (control, fixture / 'native/read'): |
| 47 | captures.append({page.get('name'): native_characters(page, page.findall('one:Outline', ns)) | 80 | captures.append({page.get('name'): native_characters(page, page.findall('one:Outline', ns)) |