authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-25 23:03:43-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-27 04:23:49-07:00
log878b4a52ab2bcb1aa6ef93be7f006594429deb4a
treef3b529755e49a5528ac507310a8616de821dc1c4
parent05027137f9f30b0179a4f73df88c69aaf898d2de
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

feat(data): edits as ops, header-checked transactions, and a long-lived section

Edits enter onestore as ops through Section::apply and leave as one appended revision: a keystroke on a 3000-paragraph page lowers, applies and seals in about 35 µs and appends ~0.8 KB. Publishing compares the 1 KiB header and length instead of the whole file, idle polls read only the header (SMB gains a stamp), and nearby patches merge into one write. Ops match the page writer across 33 edit families and cold-open in OneNote 2010. The writer also fixes splits in lists, emits OneNote's indent table and page margins, rewrites emptied inline math, and sends two split/reformat merges to review as OneNote does. Assisted-by: claude-opus-5.5 Assisted-by: claude-opus-5

330 files changed, 15199 insertions(+), 4527 deletions(-)

corpus/link-edit/internal/candidate/Open Notebook.onetoc2
Binary files a/corpus/link-edit/internal/candidate/Open Notebook.onetoc2 and b/corpus/link-edit/internal/candidate/Open Notebook.onetoc2 differ
corpus/link-edit/internal/candidate/links.one
Binary files a/corpus/link-edit/internal/candidate/links.one and b/corpus/link-edit/internal/candidate/links.one differ
corpus/link-edit/internal/cold/commands.jsonl+1-1
...@@ -1,3 +1,3 @@...@@ -1,3 +1,3 @@
1{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}1{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
2{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-A4418B81 -ExpectedPages 2", "exit": 0, "stdout": "Read 1 sections and 2 pages.\r\n", "stderr": "", "error": null}2{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-AFCAA40A -ExpectedPages 2", "exit": 0, "stdout": "Read 1 sections and 2 pages.\r\n", "stderr": "", "error": null}
3{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}3{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
corpus/link-edit/internal/cold/machine.json+1-1
...@@ -1 +1 @@...@@ -1 +1 @@
1{"name": "m6-a4418b81", "hostname": "ONE-M6-A4418B81"}1{"name": "m6-afcaa40a", "hostname": "ONE-M6-AFCAA40A"}
corpus/link-edit/internal/cold/notebook/Open Notebook.onetoc2
Binary files a/corpus/link-edit/internal/cold/notebook/Open Notebook.onetoc2 and b/corpus/link-edit/internal/cold/notebook/Open Notebook.onetoc2 differ
corpus/link-edit/internal/cold/notebook/links.one
Binary files a/corpus/link-edit/internal/cold/notebook/links.one and b/corpus/link-edit/internal/cold/notebook/links.one differ
corpus/link-edit/internal/cold/read/environment.json+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1{ 1{
2 "powershell": "5.1.14409.1005", 2 "powershell": "5.1.14409.1005",
3 "schema": "xs2010", 3 "schema": "xs2010",
4 "hostname": "ONE-M6-A4418B81", 4 "hostname": "ONE-M6-AFCAA40A",
5 "cold": true, 5 "cold": true,
6 "onenote": "14.0.4763.1000" 6 "onenote": "14.0.4763.1000"
7} 7}
corpus/link-edit/internal/cold/read/hierarchy.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{A475638B-F081-40E1-B0A7-056122CE68EA}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-12T22:13:46.000Z" color="#B49EDE"><one:Section name="links" ID="{D78A6D3C-7091-0DF6-2EFC-514D3F2E4C85}{1}{B0}" path="C:\one-tests\runs\capture\notebook\links.one" lastModifiedTime="2026-09-12T22:13:46.000Z" color="#8AA8E4"><one:Page ID="{8D0B3F30-F486-0365-1D6A-714CBE91E07B}{1}{B0}" name="Linking page ﷟HYPERLINK &quot;onenote:#Link%20target&amp;section-id={A45E149D-BEE4-4BB2-AF69-072EF644999A}&amp;page-id={1E1AB5A6-E512-42BE-A763-78914DE104F9}&amp;end&amp;base-path=C:\one-tests\runs\capture\notebook\links.one&quot;Link target" dateTime="2026-09-12T22:12:09.000Z" lastModifiedTime="2026-09-12T22:13:46.000Z" pageLevel="1"/><one:Page ID="{109B4059-8CAB-09BB-0559-34EA53917DD2}{1}{B0}" name="Link target" dateTime="2026-09-12T22:12:09.000Z" lastModifiedTime="2026-09-12T22:13:46.000Z" pageLevel="1"/></one:Section></one:Notebook> 2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{B6B691A5-ABB0-4D79-A8B4-E7B3BF418EA1}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:43:14.000Z" color="#B49EDE"><one:Section name="links" ID="{ADD0F0B5-86CF-08D7-1232-F70E1DB09222}{1}{B0}" path="C:\one-tests\runs\capture\notebook\links.one" lastModifiedTime="2026-09-26T06:43:14.000Z" color="#8AA8E4"><one:Page ID="{6DBDA204-8CAD-0128-3EA6-FC8763E619DC}{1}{B0}" name="Linking page ﷟HYPERLINK &quot;onenote:#Link%20target&amp;section-id={912D18F5-8E97-4C22-B11D-AA61E6E7D4B1}&amp;page-id={54104D3B-F876-47C6-8009-D90D3DEC5217}&amp;end&amp;base-path=C:\one-tests\runs\capture\notebook\links.one&quot;Link target" dateTime="2026-09-26T06:32:48.000Z" lastModifiedTime="2026-09-26T06:43:14.000Z" pageLevel="1"/><one:Page ID="{6EB3ECAC-4EB9-0237-0924-FBB7F1FFE5E5}{1}{B0}" name="Link target" dateTime="2026-09-26T06:32:48.000Z" lastModifiedTime="2026-09-26T06:43:14.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/link-edit/internal/cold/read/page-000.xml+2-1
...@@ -1,2 +1,3 @@...@@ -1,2 +1,3 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{109B4059-8CAB-09BB-0559-34EA53917DD2}{1}{B0}" name="Link target" dateTime="2026-09-12T22:12:09.000Z" lastModifiedTime="2026-09-12T22:13:46.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-12T22:12:09.000Z" lastModifiedTime="2026-09-12T22:12:09.000Z" objectID="{109B4059-8CAB-09BB-0559-34EA53917DD2}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Link target]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6DBDA204-8CAD-0128-3EA6-FC8763E619DC}{1}{B0}" name="Linking page ﷟HYPERLINK &quot;onenote:#Link%20target&amp;section-id={912D18F5-8E97-4C22-B11D-AA61E6E7D4B1}&amp;page-id={54104D3B-F876-47C6-8009-D90D3DEC5217}&amp;end&amp;base-path=C:\one-tests\runs\capture\notebook\links.one&quot;Link target" dateTime="2026-09-26T06:32:48.000Z" lastModifiedTime="2026-09-26T06:43:14.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:32:48.000Z" objectID="{ADD0F0B5-86CF-08D7-1232-F70E1DB09222}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="172.3725128173828" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:32:48.000Z" lastModifiedTime="2026-09-26T06:32:48.000Z" objectID="{ADD0F0B5-86CF-08D7-1232-F70E1DB09222}{24}{B0}" alignment="left"><one:T><![CDATA[Linking page <a
3href="onenote:#Link%20target&amp;section-id={912D18F5-8E97-4C22-B11D-AA61E6E7D4B1}&amp;page-id={54104D3B-F876-47C6-8009-D90D3DEC5217}&amp;end&amp;base-path=C:\one-tests\runs\capture\notebook\links.one">Link target</a>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/link-edit/internal/cold/read/page-001.xml+1-2
...@@ -1,3 +1,2 @@...@@ -1,3 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8D0B3F30-F486-0365-1D6A-714CBE91E07B}{1}{B0}" name="Linking page ﷟HYPERLINK &quot;onenote:#Link%20target&amp;section-id={A45E149D-BEE4-4BB2-AF69-072EF644999A}&amp;page-id={1E1AB5A6-E512-42BE-A763-78914DE104F9}&amp;end&amp;base-path=C:\one-tests\runs\capture\notebook\links.one&quot;Link target" dateTime="2026-09-12T22:12:09.000Z" lastModifiedTime="2026-09-12T22:13:46.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-12T22:12:09.000Z" objectID="{D78A6D3C-7091-0DF6-2EFC-514D3F2E4C85}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="172.3725128173828" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-12T22:12:09.000Z" lastModifiedTime="2026-09-12T22:12:09.000Z" objectID="{D78A6D3C-7091-0DF6-2EFC-514D3F2E4C85}{24}{B0}" alignment="left"><one:T><![CDATA[Linking page <a 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6EB3ECAC-4EB9-0237-0924-FBB7F1FFE5E5}{1}{B0}" name="Link target" dateTime="2026-09-26T06:32:48.000Z" lastModifiedTime="2026-09-26T06:43:14.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:32:48.000Z" lastModifiedTime="2026-09-26T06:32:48.000Z" objectID="{6EB3ECAC-4EB9-0237-0924-FBB7F1FFE5E5}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Link target]]></one:T></one:OE></one:Title></one:Page>
3href="onenote:#Link%20target&amp;section-id={A45E149D-BEE4-4BB2-AF69-072EF644999A}&amp;page-id={1E1AB5A6-E512-42BE-A763-78914DE104F9}&amp;end&amp;base-path=C:\one-tests\runs\capture\notebook\links.one">Link target</a>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/link-edit/internal/cold/run.json+1-1
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1{1{
2 "notebook": "/private/tmp/m4/ilink",2 "notebook": "/Users/clo/dev/one/evidence/titles/link-internal",
3 "expected_pages": 2,3 "expected_pages": 2,
4 "author": null,4 "author": null,
5 "author_timeout_seconds": 600,5 "author_timeout_seconds": 600,
corpus/link-edit/internal/cold/source.json+5-5
...@@ -2,13 +2,13 @@...@@ -2,13 +2,13 @@
2 {2 {
3 "path": "Open Notebook.onetoc2",3 "path": "Open Notebook.onetoc2",
4 "bytes": 1604,4 "bytes": 1604,
5 "sha256": "fdee47831417ef5d21624890fe8d9f3d73a4ded56cacbbe2ebb978788786681b",5 "sha256": "7cf4bd0ba068eaccb8e2d3306472ac133a08966eb9dc42cc2b4f7c3b0f44b03f",
6 "mtime_ns": 17892511297981782396 "mtime_ns": 1790404368862297325
7 },7 },
8 {8 {
9 "path": "links.one",9 "path": "links.one",
10 "bytes": 9200,10 "bytes": 10120,
11 "sha256": "16b69f8178ce8a23e37ca844b89ae736f4e2c57827205fcfc1e51501b2865d17",11 "sha256": "740b61f789cd951855f67a811394b77ed1cbcb500e728d4433b1e71904ab1986",
12 "mtime_ns": 178925112979789382112 "mtime_ns": 1790404368862105283
13 }13 }
14]14]
corpus/page-copy/candidate/Open Notebook.onetoc2
Binary files a/corpus/page-copy/candidate/Open Notebook.onetoc2 and b/corpus/page-copy/candidate/Open Notebook.onetoc2 differ
corpus/page-copy/candidate/copies.one
Binary files a/corpus/page-copy/candidate/copies.one and b/corpus/page-copy/candidate/copies.one differ
corpus/page-copy/cold/commands.jsonl+1-1
...@@ -1,3 +1,3 @@...@@ -1,3 +1,3 @@
1{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}1{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
2{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-33218F72 -ExpectedPages 17 -KeepOpen", "exit": 0, "stdout": "Read 1 sections and 17 pages.\r\n", "stderr": "", "error": null}2{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-DD3BC9D2 -ExpectedPages 17 -KeepOpen", "exit": 0, "stdout": "Read 1 sections and 17 pages.\r\n", "stderr": "", "error": null}
3{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}3{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
corpus/page-copy/cold/machine.json+1-1
...@@ -1 +1 @@...@@ -1 +1 @@
1{"name": "m6-33218f72", "hostname": "ONE-M6-33218F72"}1{"name": "m6-dd3bc9d2", "hostname": "ONE-M6-DD3BC9D2"}
corpus/page-copy/cold/notebook/Open Notebook.onetoc2
Binary files a/corpus/page-copy/cold/notebook/Open Notebook.onetoc2 and b/corpus/page-copy/cold/notebook/Open Notebook.onetoc2 differ
corpus/page-copy/cold/notebook/copies.one
Binary files a/corpus/page-copy/cold/notebook/copies.one and b/corpus/page-copy/cold/notebook/copies.one differ
corpus/page-copy/cold/read/environment.json+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1{ 1{
2 "powershell": "5.1.14409.1005", 2 "powershell": "5.1.14409.1005",
3 "schema": "xs2010", 3 "schema": "xs2010",
4 "hostname": "ONE-M6-33218F72", 4 "hostname": "ONE-M6-DD3BC9D2",
5 "cold": true, 5 "cold": true,
6 "onenote": "14.0.4763.1000" 6 "onenote": "14.0.4763.1000"
7} 7}
corpus/page-copy/cold/read/hierarchy.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{3D7C62EC-5CB3-485B-BB1F-23FA56C52E8D}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-13T08:14:37.000Z" color="#B49EDE"><one:Section name="copies" ID="{8631621B-321A-0F71-03FE-3BCA53E86583}{1}{B0}" path="C:\one-tests\runs\capture\notebook\copies.one" lastModifiedTime="2026-09-13T08:14:37.000Z" color="#8AA8E4"><one:Page ID="{0A5A2909-7069-0DA0-2457-FE8F9CA25885}{1}{B0}" name="Destination" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1"/><one:Page ID="{62E12C49-D5FE-0850-17D5-B4DC95B736E9}{1}{B0}" name="Before the file" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1"/><one:Page ID="{FBBA4589-4114-0DBB-1DDE-9AEBE04435E8}{1}{B0}" name="Before the table" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1"/><one:Page ID="{5E04C302-1E83-0CCD-1715-B0A2566E0191}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1"/><one:Page ID="{55B596AA-4540-085D-142D-9F788431ACE5}{1}{B0}" name="Indent first subtree" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{BA810F75-DBC0-0827-0990-F9FBFC84B7D7}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{493E9D0C-5976-0886-2ADB-636A30B81D20}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{79EFBB50-C028-09DE-005B-75E21D0ED54C}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{6EC9E652-85BD-0524-17E7-232F5951B731}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{25E90FFA-6C7E-0A8D-3F8B-FC658F7AAF3B}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{AA29BE5C-9249-0610-1036-E2735CEC0BB9}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{1E9DFA9B-2BEB-0C4E-3FEA-12F1C294A76E}{1}{B0}" name="Before the picture" dateTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{D28E7576-C55F-0047-2C51-4C855C536D13}{1}{B0}" name="Beside the drawing" dateTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{8E5634C2-26CB-0458-2810-125D129037C8}{1}{B0}" name="Open task" dateTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{35E45165-B253-07BF-1324-6FC7DCE3552B}{1}{B0}" name="Bullet item" dateTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{78595C91-C5E1-0D03-3A98-A98CA31DE5A8}{1}{B0}" name="Equations below" dateTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/><one:Page ID="{EF547C09-87EA-0A35-039B-F5512549E931}{1}{B0}" name="A notebook created in Rust." dateTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1"/></one:Section></one:Notebook> 2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{296735F4-6C81-4634-82FA-64EAA51CBFAE}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:51:08.000Z" color="#B49EDE"><one:Section name="copies" ID="{D9F74361-435D-0594-006D-92ECD10C11D3}{1}{B0}" path="C:\one-tests\runs\capture\notebook\copies.one" lastModifiedTime="2026-09-26T06:51:08.000Z" color="#8AA8E4"><one:Page ID="{64D50BBC-095D-0B40-049D-EE63CB34BC4D}{1}{B0}" name="Destination" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1"/><one:Page ID="{0C0ECE53-81B9-0DE0-2721-D119E26F7632}{1}{B0}" name="Before the file" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1"/><one:Page ID="{8BD579FF-C812-0827-2B91-968E287CBC55}{1}{B0}" name="Before the table" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1"/><one:Page ID="{E0635CAD-AE50-05FE-22E9-82F2F75DB84D}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1"/><one:Page ID="{BFC636CB-B7BE-022A-1F7C-066F34B2A754}{1}{B0}" name="Indent first subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1"/><one:Page ID="{885E9DE1-BDB8-02C4-1534-B3C832F89C02}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1"/><one:Page ID="{1EBCDDF8-1393-04D1-0266-929499FE03EC}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{25A63247-8FFB-0C98-22D9-2B99C3661F5A}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{92FCCBE6-488E-0D05-1C9E-91FA438C6A79}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{364CF1D3-423A-0B9F-1019-BE369236958F}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{6E15C33B-F88D-0966-239D-1EB519063D70}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{918C50B7-7848-0240-056C-816588770AB5}{1}{B0}" name="Before the picture" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{CCD62F34-CB06-0E93-2181-938C6C097557}{1}{B0}" name="Beside the drawing" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{DE01F667-2BDD-03D0-14A4-27E9A5F8CC0C}{1}{B0}" name="Open task" dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{9A577992-FE92-07C3-0C41-250E4E607861}{1}{B0}" name="Bullet item" dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:51:08.000Z" pageLevel="1"/><one:Page ID="{2C4189F4-1EE4-047C-160D-979FD528FBCF}{1}{B0}" name="Equations below" dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/><one:Page ID="{CF7197EA-123A-02D7-12CE-E9F5A3788534}{1}{B0}" name="A notebook created in Rust." dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/page-copy/cold/read/page-000.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Destination - Microsoft OneNote",9 "title": "Before the file - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-000.png
Binary files a/corpus/page-copy/cold/read/page-000.png and b/corpus/page-copy/cold/read/page-000.png differ
corpus/page-copy/cold/read/page-000.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{0A5A2909-7069-0DA0-2457-FE8F9CA25885}{1}{B0}" name="Destination" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{8631621B-321A-0F71-03FE-3BCA53E86583}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="118.3393707275391" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{8631621B-321A-0F71-03FE-3BCA53E86583}{24}{B0}" alignment="left"><one:T><![CDATA[Destination]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{0C0ECE53-81B9-0DE0-2721-D119E26F7632}{1}{B0}" name="Before the file" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{0C0ECE53-81B9-0DE0-2721-D119E26F7632}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Before the file]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{8}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="130.4619598388672" height="89.85542297363281"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{10}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[Before the file]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{12}{B0}" alignment="left"><one:InsertedFile pathCache="C:\Users\clover\AppData\Local\Microsoft\OneNote\14.0\OneNoteOfflineCache_Files\ac39c6fd-e843-438f-8c2a-61db83402513.txt" preferredName="notes 🦀.txt"/></one:OE><one:OE creationTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{14}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[After the file]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-copy/cold/read/page-001.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Before the picture - Microsoft OneNote",9 "title": "Delete sole cell paragraph - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-001.png
Binary files a/corpus/page-copy/cold/read/page-001.png and b/corpus/page-copy/cold/read/page-001.png differ
corpus/page-copy/cold/read/page-001.xml+3-3
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1E9DFA9B-2BEB-0C4E-3FEA-12F1C294A76E}{1}{B0}" name="Before the picture" dateTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{1E9DFA9B-2BEB-0C4E-3FEA-12F1C294A76E}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Before the picture]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{4DA80AD4-95E4-0563-13A1-48595B5FF4B8}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="148.3100738525391" height="27.60543251037598"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{AFD96465-6096-07A5-2297-9F5092B2C4D0}{1}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[Before the picture]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{EAFF9A35-4964-0B98-232B-BE4FC1E5AC7A}{1}{B0}" alignment="left"><one:Image format="png"><one:Data>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z8AAAAMBAQAY3Y2w 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1EBCDDF8-1393-04D1-0266-929499FE03EC}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{1EBCDDF8-1393-04D1-0266-929499FE03EC}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete sole cell paragraph]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{40}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="21.94771385192871"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{49}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{41}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{48}{B0}" lastModifiedTime="2026-09-26T06:33:48.000Z"><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{42}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{44}{B0}" alignment="left" quickStyleIndex="1"><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
3AAAAAElFTkSuQmCC 3style='font-weight:bold'>Target &#129408; é</span> <a
4</one:Data></one:Image></one:OE><one:OE creationTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{30FDBCD9-EBE5-0C42-3241-15D4E7312FCD}{1}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[After the picture]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4href="https://example.invalid/tree">Link label</a>]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{45}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{47}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{50}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{52}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-copy/cold/read/page-002.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Indent bullet subtree - Microsoft OneNote",9 "title": "Move numbered subtree down - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-002.png
Binary files a/corpus/page-copy/cold/read/page-002.png and b/corpus/page-copy/cold/read/page-002.png differ
corpus/page-copy/cold/read/page-002.xml+3-3
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{25E90FFA-6C7E-0A8D-3F8B-FC658F7AAF3B}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{25E90FFA-6C7E-0A8D-3F8B-FC658F7AAF3B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent bullet subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{E70D26B4-5E53-0439-14D6-AB435B274370}{1}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{C20780E2-91FA-0C79-3266-C1E8843F8C27}{1}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{B9F245E8-A24F-019A-3F3E-540901F3F3F1}{1}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{25A63247-8FFB-0C98-22D9-2B99C3661F5A}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{25A63247-8FFB-0C98-22D9-2B99C3661F5A}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move numbered subtree down]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{97}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{99}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{101}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span
3style='font-weight:bold'>Target &#129408; é</span> <a 3style='font-weight:bold'>Target &#129408; é</span> <a
4href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{F762F81C-0C7D-0D7E-318B-18934A658911}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span 4href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{103}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{25F7C65A-45FD-03F5-31CF-DCA9A335FC64}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{91205826-B132-0A1C-0828-370A55059BE9}{1}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{7E8151C3-CE0D-083C-29FA-66EB4C60734C}{1}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{A50B7E04-F685-06D4-3602-48EC3C9D5381}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{105}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{107}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{108}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{110}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-copy/cold/read/page-003.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Bullet item - Microsoft OneNote",9 "title": "Equations below - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-003.png
Binary files a/corpus/page-copy/cold/read/page-003.png and b/corpus/page-copy/cold/read/page-003.png differ
corpus/page-copy/cold/read/page-003.xml+2-1
...@@ -1,2 +1,3 @@...@@ -1,2 +1,3 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{35E45165-B253-07BF-1324-6FC7DCE3552B}{1}{B0}" name="Bullet item" dateTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{35E45165-B253-07BF-1324-6FC7DCE3552B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Bullet item]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{1030C98D-AF8F-01AF-0FE4-4821037C552D}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="174.7353515625" height="93.99401092529296"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{41E8A28A-ADD0-072D-0780-D5A47F095985}{1}{B0}" alignment="left"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Bullet item]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{45462BD4-B2B9-0403-1A75-2A2CFE1C6368}{1}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[First numbered]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{0547114E-6093-0FBB-0257-A78B4D838BFB}{1}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[Second numbered]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{BBD74A7A-615E-04F9-0758-5D319A106A96}{1}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Nested numbered]]></one:T></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{73CB0260-61B9-026E-068D-9106DD4C284F}{1}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Restarted at three]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{D4851817-9021-0C2E-1FB9-78CACB7483E5}{1}{B0}" alignment="left"><one:T><![CDATA[Nested plain]]></one:T></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{7F1ACAD9-03E3-0D2C-0854-83C7B46AA88A}{1}{B0}" alignment="left"><one:T><![CDATA[Plain again]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2C4189F4-1EE4-047C-160D-979FD528FBCF}{1}{B0}" name="Equations below" dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{2C4189F4-1EE4-047C-160D-979FD528FBCF}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Equations below]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{182}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="140.9678649902344" height="208.9916534423828"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{184}{B0}" alignment="left"><one:T><![CDATA[Equations below]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{186}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msup><mml:mi>x</mml:mi><mml:mn>2</mml:mn></mml:msup><mml:mo>+</mml:mo><mml:msup><mml:mi>y</mml:mi><mml:mn>2</mml:mn></mml:msup><mml:mo>=</mml:mo><mml:msup><mml:mi>z</mml:mi><mml:mn>2</mml:mn></mml:msup></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{188}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mfrac><mml:mrow><mml:mi>a</mml:mi><mml:mo>+</mml:mo><mml:mi>b</mml:mi></mml:mrow><mml:mrow><mml:mi>c</mml:mi><mml:mo>+</mml:mo><mml:mi>d</mml:mi></mml:mrow></mml:mfrac></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{190}{B0}" alignment="left" style="font-size:11.0pt"><one:T><![CDATA[<span
3style='font-family:Calibri' lang=en-US>Inline </span><!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mi>&#120572;</mml:mi><mml:mo>+</mml:mo><mml:mi>&#120573;</mml:mi></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{192}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msub><mml:mi>a</mml:mi><mml:mn>1</mml:mn></mml:msub><mml:mo>+</mml:mo><mml:msub><mml:mi>b</mml:mi><mml:mn>2</mml:mn></mml:msub></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{194}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msubsup><mml:mi>x</mml:mi><mml:mi>i</mml:mi><mml:mn>2</mml:mn></mml:msubsup></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{196}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mfenced><mml:mrow><mml:mi>a</mml:mi><mml:mo>+</mml:mo><mml:mi>b</mml:mi></mml:mrow></mml:mfenced></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{198}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msubsup><mml:mo stretchy="false">∫</mml:mo><mml:mn>0</mml:mn><mml:mn>1</mml:mn></mml:msubsup><mml:mrow><mml:mi>x</mml:mi><mml:mo>&nbsp;</mml:mo><mml:mi>d</mml:mi><mml:mi>x</mml:mi><mml:mo>&nbsp;</mml:mo></mml:mrow></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{200}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:munderover><mml:mo stretchy="false">∑</mml:mo><mml:mrow><mml:mi>i</mml:mi><mml:mo>=</mml:mo><mml:mn>1</mml:mn></mml:mrow><mml:mi>n</mml:mi></mml:munderover><mml:mrow><mml:mi>i</mml:mi><mml:mo>&nbsp;</mml:mo></mml:mrow></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{202}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msup><mml:mi>x</mml:mi><mml:mn>2</mml:mn></mml:msup><mml:mo>+</mml:mo><mml:mn>1</mml:mn></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{204}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mfrac><mml:mrow><mml:mi>a</mml:mi><mml:mo>+</mml:mo><mml:mi>b</mml:mi></mml:mrow><mml:mrow><mml:msup><mml:mi>c</mml:mi><mml:mn>2</mml:mn></mml:msup></mml:mrow></mml:mfrac></mml:math><![endif]-->]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-copy/cold/read/page-004.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Delete sole cell paragraph - Microsoft OneNote",9 "title": "Indent bullet subtree - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-004.png
Binary files a/corpus/page-copy/cold/read/page-004.png and b/corpus/page-copy/cold/read/page-004.png differ
corpus/page-copy/cold/read/page-004.xml+3-2
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{493E9D0C-5976-0886-2ADB-636A30B81D20}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{493E9D0C-5976-0886-2ADB-636A30B81D20}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete sole cell paragraph]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{9721CF72-8007-0180-29C3-D0F422003C1B}{1}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="21.94771385192871"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{76A5FFBC-C8EC-0E30-2663-51714300B179}{1}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{EFDBE2D8-39D5-0EDF-075C-26A7D704FD28}{1}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{8D1D5F63-2DD1-0274-04FA-38A73347200A}{1}{B0}" lastModifiedTime="2026-09-13T08:12:57.000Z"><one:Cell lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{7258A0AD-D622-0854-0122-8EBEB7B70203}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{63217049-E82C-0F04-08AE-9CB184551B5D}{1}{B0}" alignment="left" quickStyleIndex="1"><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 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{364CF1D3-423A-0B9F-1019-BE369236958F}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{364CF1D3-423A-0B9F-1019-BE369236958F}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent bullet subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{213}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{215}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{217}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[<span
3style='font-weight:bold'>Target &#129408; é</span> <a 3style='font-weight:bold'>Target &#129408; é</span> <a
4href="https://example.invalid/tree">Link label</a>]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{05B95457-4B57-0270-392F-6988B33CE2A8}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{D490ACFE-4E2D-09CE-1F5F-0866F94CA145}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{4D870AE7-2513-0CB4-3066-2217A47AB044}{1}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{EFA33014-C3C9-0764-0876-5357D230EE70}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{219}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{221}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{223}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{224}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{226}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-copy/cold/read/page-005.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Indent first subtree - Microsoft OneNote",9 "title": "Destination - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-005.png
Binary files a/corpus/page-copy/cold/read/page-005.png and b/corpus/page-copy/cold/read/page-005.png differ
corpus/page-copy/cold/read/page-005.xml+1-4
...@@ -1,5 +1,2 @@...@@ -1,5 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{55B596AA-4540-085D-142D-9F788431ACE5}{1}{B0}" name="Indent first subtree" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{55B596AA-4540-085D-142D-9F788431ACE5}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent first subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{5BD6B4E1-21C1-08FD-38E8-4C7ADD81FBB1}{1}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="53.71086883544922"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{8B6DEB92-F345-02E1-1FB6-90BBC691E721}{1}{B0}" alignment="left" quickStyleIndex="1"><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 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{64D50BBC-095D-0B40-049D-EE63CB34BC4D}{1}{B0}" name="Destination" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{D9F74361-435D-0594-006D-92ECD10C11D3}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="118.3393707275391" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{D9F74361-435D-0594-006D-92ECD10C11D3}{24}{B0}" alignment="left"><one:T><![CDATA[Destination]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
3style='font-weight:bold'>Target &#129408; é</span> <a
4href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{365B8C25-37F9-0C75-1AD0-A969AB393EEC}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{53D9A482-FB38-09F8-318E-E7A2A0EA655A}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{C7BC8628-6A45-0A7E-10CA-1A957E36B827}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{B5066461-BBDD-09E8-223D-5B839E2A8637}{1}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{EDB23709-B01D-0CD2-08A7-A9EB953087ED}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-copy/cold/read/page-006.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Bold \ud83e\udd80 italic e\u0301 color \u6771\u4eac - Microsoft OneNote",9 "title": "Move cell subtree down - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-006.png
Binary files a/corpus/page-copy/cold/read/page-006.png and b/corpus/page-copy/cold/read/page-006.png differ
corpus/page-copy/cold/read/page-006.xml+4-19
...@@ -1,20 +1,5 @@...@@ -1,20 +1,5 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5E04C302-1E83-0CCD-1715-B0A2566E0191}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{5E04C302-1E83-0CCD-1715-B0A2566E0191}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Bold &#129408; italic é color 東京]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{B63306AF-2B95-0428-2EB2-67405305D5CD}{1}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="127.5136947631836" height="42.03070831298828"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{07F96F7E-EE93-0108-00D0-9E4BE9011C40}{1}{B0}" alignment="left"><one:T><![CDATA[<span style='font-weight:bold; 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6E15C33B-F88D-0966-239D-1EB519063D70}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{6E15C33B-F88D-0966-239D-1EB519063D70}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move cell subtree down]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{43}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.23086929321289"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{58}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{44}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{57}{B0}" lastModifiedTime="2026-09-26T06:33:49.000Z"><one:Cell lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{45}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{47}{B0}" alignment="left" quickStyleIndex="1"><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
3text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span 3style='font-weight:bold'>Target &#129408; é</span> <a
4style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family: 4href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{49}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri; 5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{51}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{53}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{54}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{56}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{59}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{61}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
6font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through;
7font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family:
8Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family:
9Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size:
1011.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238;
11background:yellow;mso-highlight:yellow'>color</span><span style='font-family:
12Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family:
13Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size:
1411.0pt'><br>
15End</span>]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{EEA4A13A-3CBD-04DA-02B1-47CF8F1BE77E}{1}{B0}" alignment="left" quickStyleIndex="1" style="font-size:11.0pt;color:#1F4E79"><one:T><![CDATA[<span
16style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span
17style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span
18style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span
19style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed'
20lang=ar-SA>مرحبا</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-copy/cold/read/page-007.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Before the file - Microsoft OneNote",9 "title": "Delete cell subtree - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-007.png
Binary files a/corpus/page-copy/cold/read/page-007.png and b/corpus/page-copy/cold/read/page-007.png differ
corpus/page-copy/cold/read/page-007.xml+4-1
...@@ -1,2 +1,5 @@...@@ -1,2 +1,5 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{62E12C49-D5FE-0850-17D5-B4DC95B736E9}{1}{B0}" name="Before the file" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{62E12C49-D5FE-0850-17D5-B4DC95B736E9}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Before the file]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{FBCA9C23-1A13-020A-35BC-1365A45C381E}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="130.4619598388672" height="89.85542297363281"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{2A6BB41D-EDD1-0546-0571-2950BFD2D187}{1}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[Before the file]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{9E89195D-B7EC-0AC4-34FD-F47D633F0533}{1}{B0}" alignment="left"><one:InsertedFile pathCache="C:\Users\clover\AppData\Local\Microsoft\OneNote\14.0\OneNoteOfflineCache_Files\6bf086f4-be12-4374-9140-4ddaf36216fe.txt" preferredName="notes 🦀.txt"/></one:OE><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{48D46AA0-F634-084B-171A-1D72CF298968}{1}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[After the file]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{885E9DE1-BDB8-02C4-1534-B3C832F89C02}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{885E9DE1-BDB8-02C4-1534-B3C832F89C02}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete cell subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{229}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.30315780639648"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{254}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{230}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{253}{B0}" lastModifiedTime="2026-09-26T06:33:48.000Z"><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{231}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{233}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold'>Target &#129408; é</span> <a
4href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{235}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{237}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{239}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{240}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{242}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other cell]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{252}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{243}{B0}"><one:Columns><one:Column index="0" width="72.0" isLocked="true"/></one:Columns><one:Row objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{247}{B0}" lastModifiedTime="2026-09-26T06:33:48.000Z"><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{244}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{246}{B0}" alignment="left"><one:T><![CDATA[Inner one]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{251}{B0}" lastModifiedTime="2026-09-26T06:33:48.000Z"><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{248}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{250}{B0}" alignment="left"><one:T><![CDATA[Inner two]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{255}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{2}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-copy/cold/read/page-008.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Delete numbered subtree - Microsoft OneNote",9 "title": "Before the table - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-008.png
Binary files a/corpus/page-copy/cold/read/page-008.png and b/corpus/page-copy/cold/read/page-008.png differ
corpus/page-copy/cold/read/page-008.xml+1-4
...@@ -1,5 +1,2 @@...@@ -1,5 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6EC9E652-85BD-0524-17E7-232F5951B731}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{6EC9E652-85BD-0524-17E7-232F5951B731}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete numbered subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{7D694249-C0B4-02DD-1DC7-3136ED2FFA27}{1}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{758447BB-8217-0430-1A24-108E47546937}{1}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{72575BCE-8279-0163-3BD9-443858B25152}{1}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8BD579FF-C812-0827-2B91-968E287CBC55}{1}{B0}" name="Before the table" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{8BD579FF-C812-0827-2B91-968E287CBC55}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Before the table]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{36}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="395.1299438476562" height="67.21086883544922"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{38}{B0}" alignment="left"><one:T><![CDATA[Before the table]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{54}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{39}{B0}"><one:Columns><one:Column index="0" width="144.0" isLocked="true"/><one:Column index="1" width="216.0" isLocked="true"/></one:Columns><one:Row objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{46}{B0}" lastModifiedTime="2026-09-26T06:33:48.000Z"><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{40}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{42}{B0}" alignment="left"><one:T><![CDATA[Name]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{43}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{45}{B0}" alignment="left"><one:T><![CDATA[Value]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{53}{B0}" lastModifiedTime="2026-09-26T06:33:48.000Z"><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{47}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{49}{B0}" alignment="left"><one:T><![CDATA[Rust &#129408;]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{50}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{52}{B0}" alignment="left"><one:T><![CDATA[Written natively]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{56}{B0}" alignment="left"><one:T><![CDATA[After the table]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
3style='font-weight:bold'>Target &#129408; é</span> <a
4href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{59A91EE5-A5B6-06B1-328F-DCF3FC7EDAAC}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{9B857AAD-2B9A-0AB3-1341-A048617E97BC}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{C4A6021B-B90F-0CD2-2BDB-0BDD084AB0A6}{1}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{194A9C6A-9CCD-07AA-13B8-DE3F2E45394E}{1}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{D3A6B926-9395-0950-1BB5-A6C460D39349}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-copy/cold/read/page-009.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Equations below - Microsoft OneNote",9 "title": "Before the picture - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-009.png
Binary files a/corpus/page-copy/cold/read/page-009.png and b/corpus/page-copy/cold/read/page-009.png differ
corpus/page-copy/cold/read/page-009.xml+3-2
...@@ -1,3 +1,4 @@...@@ -1,3 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{78595C91-C5E1-0D03-3A98-A98CA31DE5A8}{1}{B0}" name="Equations below" dateTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{78595C91-C5E1-0D03-3A98-A98CA31DE5A8}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Equations below]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{FF465D92-B6F0-0E57-3F42-D3F428E0F163}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="140.9678649902344" height="208.9916534423828"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{25D933DF-AC4B-0333-0643-3483B2E2652A}{1}{B0}" alignment="left"><one:T><![CDATA[Equations below]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{73426626-83B5-05D8-26F7-5D6887ED2AB5}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msup><mml:mi>x</mml:mi><mml:mn>2</mml:mn></mml:msup><mml:mo>+</mml:mo><mml:msup><mml:mi>y</mml:mi><mml:mn>2</mml:mn></mml:msup><mml:mo>=</mml:mo><mml:msup><mml:mi>z</mml:mi><mml:mn>2</mml:mn></mml:msup></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{45DEE337-060F-078E-1093-87D9A54DF41A}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mfrac><mml:mrow><mml:mi>a</mml:mi><mml:mo>+</mml:mo><mml:mi>b</mml:mi></mml:mrow><mml:mrow><mml:mi>c</mml:mi><mml:mo>+</mml:mo><mml:mi>d</mml:mi></mml:mrow></mml:mfrac></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{68218407-AC5B-0355-295E-667A238EAC19}{1}{B0}" alignment="left" style="font-size:11.0pt"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{918C50B7-7848-0240-056C-816588770AB5}{1}{B0}" name="Before the picture" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{918C50B7-7848-0240-056C-816588770AB5}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Before the picture]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{78}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="148.3100738525391" height="27.60543251037598"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{80}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[Before the picture]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{82}{B0}" alignment="left"><one:Image format="png"><one:Data>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z8AAAAMBAQAY3Y2w
3style='font-family:Calibri' lang=en-US>Inline </span><!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mi>&#120572;</mml:mi><mml:mo>+</mml:mo><mml:mi>&#120573;</mml:mi></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{3F067690-E557-00BC-3846-0628BB3D7133}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msub><mml:mi>a</mml:mi><mml:mn>1</mml:mn></mml:msub><mml:mo>+</mml:mo><mml:msub><mml:mi>b</mml:mi><mml:mn>2</mml:mn></mml:msub></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{7156930C-7D25-0CD1-1992-346BE0DD05FD}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msubsup><mml:mi>x</mml:mi><mml:mi>i</mml:mi><mml:mn>2</mml:mn></mml:msubsup></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{1DE19BC8-B93A-02DB-2FA8-92B03D373A73}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mfenced><mml:mrow><mml:mi>a</mml:mi><mml:mo>+</mml:mo><mml:mi>b</mml:mi></mml:mrow></mml:mfenced></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{5C43FBBD-302D-09D2-28A9-18789A71A072}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msubsup><mml:mo stretchy="false">∫</mml:mo><mml:mn>0</mml:mn><mml:mn>1</mml:mn></mml:msubsup><mml:mrow><mml:mi>x</mml:mi><mml:mo>&nbsp;</mml:mo><mml:mi>d</mml:mi><mml:mi>x</mml:mi><mml:mo>&nbsp;</mml:mo></mml:mrow></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{8DBDED4C-17CA-0463-240E-91F1134B5E9F}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:munderover><mml:mo stretchy="false">∑</mml:mo><mml:mrow><mml:mi>i</mml:mi><mml:mo>=</mml:mo><mml:mn>1</mml:mn></mml:mrow><mml:mi>n</mml:mi></mml:munderover><mml:mrow><mml:mi>i</mml:mi><mml:mo>&nbsp;</mml:mo></mml:mrow></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{214F8EE1-64A0-0957-0A3F-F8E7D05B6586}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:msup><mml:mi>x</mml:mi><mml:mn>2</mml:mn></mml:msup><mml:mo>+</mml:mo><mml:mn>1</mml:mn></mml:math><![endif]-->]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{3715C26F-37BB-031C-1197-F79A9E19EE60}{1}{B0}" alignment="left" style="font-family:'Cambria Math';font-size:11.0pt"><one:T><![CDATA[<!--[if mathML]><mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML" display="block"><mml:mfrac><mml:mrow><mml:mi>a</mml:mi><mml:mo>+</mml:mo><mml:mi>b</mml:mi></mml:mrow><mml:mrow><mml:msup><mml:mi>c</mml:mi><mml:mn>2</mml:mn></mml:msup></mml:mrow></mml:mfrac></mml:math><![endif]-->]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 3AAAAAElFTkSuQmCC
4</one:Data></one:Image></one:OE><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{84}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[After the picture]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-copy/cold/read/page-010.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Move numbered subtree down - Microsoft OneNote",9 "title": "Delete numbered subtree - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-010.png
Binary files a/corpus/page-copy/cold/read/page-010.png and b/corpus/page-copy/cold/read/page-010.png differ
corpus/page-copy/cold/read/page-010.xml+3-3
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{79EFBB50-C028-09DE-005B-75E21D0ED54C}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{79EFBB50-C028-09DE-005B-75E21D0ED54C}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move numbered subtree down]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{0A534E63-2DFE-07C6-0A28-71334DEE9E4D}{1}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{ED619F07-F6A1-0AC3-1461-04C34315FB37}{1}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{055B6314-3B1F-00E1-2734-2684ACC53EA4}{1}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{92FCCBE6-488E-0D05-1C9E-91FA438C6A79}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{92FCCBE6-488E-0D05-1C9E-91FA438C6A79}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete numbered subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{155}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{157}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{159}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span
3style='font-weight:bold'>Target &#129408; é</span> <a 3style='font-weight:bold'>Target &#129408; é</span> <a
4href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{BC5317C5-A40F-01F9-038E-5AED4EB7626B}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span 4href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{161}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{7F1F3536-4683-03A0-2FCB-4FF372F15926}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{4ECF5731-6B02-0ABE-1485-8D4A0B859DBE}{1}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{4A7372E3-CEB5-09D5-18D7-2A37D082BE91}{1}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:57.000Z" lastModifiedTime="2026-09-13T08:12:57.000Z" objectID="{DB6D2123-9BB9-016D-3D56-77B46F2BEE20}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{163}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{165}{B0}" alignment="left" quickStyleIndex="1"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{166}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{5778544D-D680-0FAA-328B-B80D7F87C704}{168}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-copy/cold/read/page-011.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Open task - Microsoft OneNote",9 "title": "Bullet item - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-011.png
Binary files a/corpus/page-copy/cold/read/page-011.png and b/corpus/page-copy/cold/read/page-011.png differ
corpus/page-copy/cold/read/page-011.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8E5634C2-26CB-0458-2810-125D129037C8}{1}{B0}" name="Open task" dateTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Rust task"/><one:TagDef index="1" type="1" symbol="13" fontColor="#FF0000" highlightColor="#FFFF00" name="Important"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{8E5634C2-26CB-0458-2810-125D129037C8}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Open task]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{0F36D11A-3FE8-0019-2E63-9AD912439D52}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="158.2036895751953" height="53.71086883544922"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{89DFA126-B932-01B1-1C58-55D810AA0133}{1}{B0}" alignment="left"><one:Tag index="0" completed="false" disabled="false" creationDate="2020-01-02T03:04:05.000Z"/><one:T><![CDATA[Open task]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{AD861E4A-D898-031B-25E9-10EBF29DCD25}{1}{B0}" alignment="left"><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[Completed task]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{EF2ED991-5964-0515-2042-9727E177E093}{1}{B0}" alignment="left"><one:Tag index="0" completed="false" disabled="false" creationDate="2020-01-02T03:04:05.000Z"/><one:Tag index="1" completed="false" disabled="false" creationDate="2020-01-02T03:04:05.000Z"/><one:T><![CDATA[Important open task]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{C1E4EF5F-DDB5-09B3-355F-A6449BF88678}{1}{B0}" alignment="left"><one:T><![CDATA[Untagged]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9A577992-FE92-07C3-0C41-250E4E607861}{1}{B0}" name="Bullet item" dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:51:08.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{9A577992-FE92-07C3-0C41-250E4E607861}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Bullet item]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{144}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="174.7353515625" height="93.99401092529296"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{146}{B0}" alignment="left"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Bullet item]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{148}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[First numbered]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{150}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[Second numbered]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{152}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Nested numbered]]></one:T></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{154}{B0}" alignment="left"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Restarted at three]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{156}{B0}" alignment="left"><one:T><![CDATA[Nested plain]]></one:T></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{158}{B0}" alignment="left"><one:T><![CDATA[Plain again]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-copy/cold/read/page-012.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Move cell subtree down - Microsoft OneNote",9 "title": "Indent first subtree - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-012.png
Binary files a/corpus/page-copy/cold/read/page-012.png and b/corpus/page-copy/cold/read/page-012.png differ
corpus/page-copy/cold/read/page-012.xml+3-3
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{AA29BE5C-9249-0610-1036-E2735CEC0BB9}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{AA29BE5C-9249-0610-1036-E2735CEC0BB9}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move cell subtree down]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{5DFCE2F7-804C-019E-3F4A-DD0FF39FFE62}{1}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.23086929321289"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{2A47635E-2030-0AED-007C-5D9D0EE60320}{1}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{132A6189-E6A3-06C8-2C3B-1301E8466F92}{1}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{FFF81FBB-C1B7-0BED-2BAB-5A466882083D}{1}{B0}" lastModifiedTime="2026-09-13T08:12:59.000Z"><one:Cell lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{DE630286-2FC5-0B54-347C-FF399A93BA12}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{4B5A9D99-8F4C-0DE8-0E0A-5A618E02264F}{1}{B0}" alignment="left" quickStyleIndex="1"><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 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BFC636CB-B7BE-022A-1F7C-066F34B2A754}{1}{B0}" name="Indent first subtree" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{BFC636CB-B7BE-022A-1F7C-066F34B2A754}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent first subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{96}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="53.71086883544922"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{98}{B0}" alignment="left" quickStyleIndex="1"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold'>Target &#129408; é</span> <a 3style='font-weight:bold'>Target &#129408; é</span> <a
4href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{27BE0601-EB2D-059C-24C6-205D71642566}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span 4href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{100}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{E96A9E1E-A5AB-0668-1244-4836285586EF}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{6E58F561-F9E2-085E-1864-3AEDF1A5E9F7}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{703BD781-4848-05D8-0381-00C40C756723}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{3A828837-47ED-0922-39C9-7A291414EFE2}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{68C4CF29-651E-0F55-2987-5FF0DC3E0D05}{1}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{901AFAAD-146E-0CB5-1C0A-135DFC01AF28}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{102}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{104}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{105}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{107}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-copy/cold/read/page-013.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Delete cell subtree - Microsoft OneNote",9 "title": "Beside the drawing - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-013.png
Binary files a/corpus/page-copy/cold/read/page-013.png and b/corpus/page-copy/cold/read/page-013.png differ
corpus/page-copy/cold/read/page-013.xml+6-4
...@@ -1,5 +1,7 @@...@@ -1,5 +1,7 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BA810F75-DBC0-0827-0990-F9FBFC84B7D7}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved task"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{BA810F75-DBC0-0827-0990-F9FBFC84B7D7}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete cell subtree]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{EF2F80B0-92D7-0C40-3469-9AB6923AC934}{1}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.30315780639648"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{A3D5DD07-CAAA-0FC1-3434-2BFCD5EAFB3A}{1}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{1C90E39E-CEB9-072B-1045-5350DE91C9BA}{1}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{00FE4CB7-3AD9-0A13-02EC-F45825DA0837}{1}{B0}" lastModifiedTime="2026-09-13T08:12:56.000Z"><one:Cell lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{C0215DC2-A310-09E1-2C56-611011EEDEDA}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{B168AA68-D0D0-0783-3211-7CBCF62C1878}{1}{B0}" alignment="left" quickStyleIndex="1"><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 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CCD62F34-CB06-0E93-2181-938C6C097557}{1}{B0}" name="Beside the drawing" dateTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{CCD62F34-CB06-0E93-2181-938C6C097557}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Beside the drawing]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{91}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="152.4244079589843" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{93}{B0}" alignment="left"><one:T><![CDATA[Beside the drawing]]></one:T></one:OE></one:OEChildren></one:Outline><one:InkDrawing lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{94}{B0}"><one:Position x="299.9905395507812" y="119.9905471801758" z="1"/><one:Size width="60.03781127929687" height="60.03779220581055"/><one:Data>AM8CHQSiAaIBAWAqABofi0JwRYR1gL7QRdkE+oeVMvLsF0G4wBa/8yv0et67E4vlXrNInJuhJVq3
3style='font-weight:bold'>Target &#129408; é</span> <a 3URK/7HHMzCKxQYkOuxMs7nqhWM9UiuaXxU+PBvi60uGbIsRmbacNslhJmh1OQDQGbBACEQZIEEUj
4href="https://example.invalid/tree">Link label</a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{51E78125-5D88-02B2-1F60-3564BF9AB799}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span 4RiMJSBBE/wFFI0YjBQgLZU9mZ2hkaRkUMggAgB4CIeLiQTMIAPAQAovF4kFkAxVGAEAKXAWH8gln
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{9EE1315E-F0F0-01F2-236C-CED603CE6C83}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{FD8B95A9-B069-065B-24ED-358E77DA9126}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{C371CC3D-4164-0E42-20D2-92CDC0E0162E}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{3A7C1016-52CD-0EE0-1D98-5D6464F47C40}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other cell]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{D4CC12A4-5B3C-0061-0FC8-329CFD2237A8}{1}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:Table bordersVisible="true" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{2BDFCA04-550A-0B11-3229-C359C02CEC1A}{1}{B0}"><one:Columns><one:Column index="0" width="72.0" isLocked="true"/></one:Columns><one:Row objectID="{933BCC33-B101-006B-1B06-D1EA10BD608E}{1}{B0}" lastModifiedTime="2026-09-13T08:12:56.000Z"><one:Cell lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{C9AC97DE-B8A6-079A-05EA-1C006AE7F1F6}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{F3B663D9-B871-0F4A-0480-14D1C093E86C}{1}{B0}" alignment="left"><one:T><![CDATA[Inner one]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{49CDA646-4B5C-05D4-1D4A-377146640436}{1}{B0}" lastModifiedTime="2026-09-13T08:12:56.000Z"><one:Cell lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{4C20D881-145C-0B85-3B52-D9FC9DA41CF8}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{23ACBCB6-41CC-0F28-29A2-649DCD1F070A}{1}{B0}" alignment="left"><one:T><![CDATA[Inner two]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{DD272184-6150-017E-0C7B-E0A6C9D33F67}{1}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-13T08:12:56.000Z" lastModifiedTime="2026-09-13T08:12:56.000Z" objectID="{E23E61E5-7407-0548-006A-201CB1623B82}{1}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 5jCj7wnvCe8IAh/B8h4Pke8I7wnvCQAUURgBAAAAJBAUURgAAAAASABEgZQaCZUd9r0ufE8XliyT+
6gAoAESCARDH9gE3dAQMVRgBACj9AIm9GCC70WAqACQEKTAIPUq7GcA5CJYzgBRRGAIAAAAkEBRRG
7AAAAABIAESBryQ6p9QoiSLYaYOAFf0kKCgARIIBEMf2ATd0BAxVGAEAKP0Aib0YILvRYCoA=</one:Data></one:InkDrawing></one:Page>
corpus/page-copy/cold/read/page-014.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Beside the drawing - Microsoft OneNote",9 "title": "A notebook created in Rust. - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-014.png
Binary files a/corpus/page-copy/cold/read/page-014.png and b/corpus/page-copy/cold/read/page-014.png differ
corpus/page-copy/cold/read/page-014.xml+1-6
...@@ -1,7 +1,2 @@...@@ -1,7 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D28E7576-C55F-0047-2C51-4C855C536D13}{1}{B0}" name="Beside the drawing" dateTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:58.000Z" lastModifiedTime="2026-09-13T08:12:58.000Z" objectID="{D28E7576-C55F-0047-2C51-4C855C536D13}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Beside the drawing]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{110E90F1-B6EC-0EE0-1F13-367858D96194}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="152.4244079589843" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:12:59.000Z" lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{14A2DDB9-EF56-053E-2D5F-B9FF92056636}{1}{B0}" alignment="left"><one:T><![CDATA[Beside the drawing]]></one:T></one:OE></one:OEChildren></one:Outline><one:InkDrawing lastModifiedTime="2026-09-13T08:12:59.000Z" objectID="{A013EA2B-3C74-0FE2-0325-A02BD639D5F9}{1}{B0}"><one:Position x="299.9905395507812" y="119.9905471801758" z="1"/><one:Size width="60.03781127929687" height="60.03779220581055"/><one:Data>AM8CHQSiAaIBAWAqABofi0JwRYR1gL7QRdkE+oeVMvLsF0G4wBa/8yv0et67E4vlXrNInJuhJVq3 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CF7197EA-123A-02D7-12CE-E9F5A3788534}{1}{B0}" name="A notebook created in Rust." dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{CF7197EA-123A-02D7-12CE-E9F5A3788534}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[A notebook created in Rust.]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{208}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="191.1661224365234" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{210}{B0}" alignment="left"><one:T><![CDATA[A notebook created in Rust.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
3URK/7HHMzCKxQYkOuxMs7nqhWM9UiuaXxU+PBvi60uGbIsRmbacNslhJmh1OQDQGbBACEQZIEEUj
4RiMJSBBE/wFFI0YjBQgLZU9mZ2hkaRkUMggAgB4CIeLiQTMIAPAQAovF4kFkAxVGAEAKXAWH8gln
5jCj7wnvCe8IAh/B8h4Pke8I7wnvCQAUURgBAAAAJBAUURgAAAAASABEgNO2fBK2iXEeJsDGBngJF
6LQoAESCAd0ywV0PdAQMVRgBACj9AIm9GCC70WAqACQEKTAIPUq7GcA5CJYzgBRRGAIAAAAkEBRRG
7AAAAABIAESDm2ofXdOgLSJUWT/x4VvZtCgARIIB3TLBXQ90BAxVGAEAKP0Aib0YILvRYCoA=</one:Data></one:InkDrawing></one:Page>
corpus/page-copy/cold/read/page-015.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "A notebook created in Rust. - Microsoft OneNote",9 "title": "Open task - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-015.png
Binary files a/corpus/page-copy/cold/read/page-015.png and b/corpus/page-copy/cold/read/page-015.png differ
corpus/page-copy/cold/read/page-015.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{EF547C09-87EA-0A35-039B-F5512549E931}{1}{B0}" name="A notebook created in Rust." dateTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:14:27.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:13:00.000Z" lastModifiedTime="2026-09-13T08:13:00.000Z" objectID="{EF547C09-87EA-0A35-039B-F5512549E931}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[A notebook created in Rust.]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{B7774BCB-9107-00A0-2BFE-95DC3CB2134F}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="191.1661224365234" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:13:01.000Z" lastModifiedTime="2026-09-13T08:13:01.000Z" objectID="{C3848036-370C-04C5-0022-5090B87608DD}{1}{B0}" alignment="left"><one:T><![CDATA[A notebook created in Rust.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{DE01F667-2BDD-03D0-14A4-27E9A5F8CC0C}{1}{B0}" name="Open task" dateTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:50:56.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Rust task"/><one:TagDef index="1" type="1" symbol="13" fontColor="#FF0000" highlightColor="#FFFF00" name="Important"/><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:49.000Z" lastModifiedTime="2026-09-26T06:33:49.000Z" objectID="{DE01F667-2BDD-03D0-14A4-27E9A5F8CC0C}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Open task]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{110}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="158.2036895751953" height="53.71086883544922"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{112}{B0}" alignment="left"><one:Tag index="0" completed="false" disabled="false" creationDate="2020-01-02T03:04:05.000Z"/><one:T><![CDATA[Open task]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{114}{B0}" alignment="left"><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[Completed task]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{116}{B0}" alignment="left"><one:Tag index="0" completed="false" disabled="false" creationDate="2020-01-02T03:04:05.000Z"/><one:Tag index="1" completed="false" disabled="false" creationDate="2020-01-02T03:04:05.000Z"/><one:T><![CDATA[Important open task]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:50.000Z" lastModifiedTime="2026-09-26T06:33:50.000Z" objectID="{99D3BC6D-6836-006D-0F0B-732214F7504A}{118}{B0}" alignment="left"><one:T><![CDATA[Untagged]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-copy/cold/read/page-016.navigation.json+1-1
...@@ -6,7 +6,7 @@...@@ -6,7 +6,7 @@
6 "h": 600,6 "h": 600,
7 "error": null,7 "error": null,
8 "win": {8 "win": {
9 "title": "Before the table - Microsoft OneNote",9 "title": "Bold \ud83e\udd80 italic e\u0301 color \u6771\u4eac - Microsoft OneNote",
10 "class": "Framework::CFrame",10 "class": "Framework::CFrame",
11 "dialog": false11 "dialog": false
12 }12 }
corpus/page-copy/cold/read/page-016.png
Binary files a/corpus/page-copy/cold/read/page-016.png and b/corpus/page-copy/cold/read/page-016.png differ
corpus/page-copy/cold/read/page-016.xml+19-1
...@@ -1,2 +1,20 @@...@@ -1,2 +1,20 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{FBBA4589-4114-0DBB-1DDE-9AEBE04435E8}{1}{B0}" name="Before the table" dateTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:14:26.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{FBBA4589-4114-0DBB-1DDE-9AEBE04435E8}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Before the table]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{B66EC4E0-90E0-065D-16BD-8AE6D17D38E2}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="395.1299438476562" height="67.21086883544922"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{D00B3AB9-E98D-02AC-00A5-64F7F0B73D24}{1}{B0}" alignment="left"><one:T><![CDATA[Before the table]]></one:T></one:OE><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{A910B5E4-106E-091B-042B-85260A415D85}{1}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{E2111011-156E-0E62-3045-C3CA9B0171B1}{1}{B0}"><one:Columns><one:Column index="0" width="144.0" isLocked="true"/><one:Column index="1" width="216.0" isLocked="true"/></one:Columns><one:Row objectID="{EFFCB8A1-87E0-0D1C-2007-873484353285}{1}{B0}" lastModifiedTime="2026-09-13T08:12:55.000Z"><one:Cell lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{58537E0F-3889-000A-30F5-5B63A4AEDDF4}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{766E8FDE-7B1E-0BE1-2EA2-786315A5EE3A}{1}{B0}" alignment="left"><one:T><![CDATA[Name]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{33808FC6-C25F-040B-157F-8170CE036733}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{90D3178B-5FBE-03CD-3CC8-4D2F6E8B7FD3}{1}{B0}" alignment="left"><one:T><![CDATA[Value]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{EA57AB8A-1061-0815-135C-39D97AF028C6}{1}{B0}" lastModifiedTime="2026-09-13T08:12:55.000Z"><one:Cell lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{92B2BEB0-1EE8-0D4A-1D27-79EC8DB2F9A2}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{DCEBCF8B-F7DA-0C20-0F76-44AC9B43F901}{1}{B0}" alignment="left"><one:T><![CDATA[Rust &#129408;]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{71E65FA6-5919-0FAD-16C0-ADD201204FEE}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{A457D70F-1E86-09AC-0205-D8DFEA8EEC54}{1}{B0}" alignment="left"><one:T><![CDATA[Written natively]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE><one:OE creationTime="2026-09-13T08:12:55.000Z" lastModifiedTime="2026-09-13T08:12:55.000Z" objectID="{534343F8-0718-0974-08DF-524AD8D8860E}{1}{B0}" alignment="left"><one:T><![CDATA[After the table]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E0635CAD-AE50-05FE-22E9-82F2F75DB84D}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:50:55.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Copier" lastModifiedBy="Copier" creationTime="2026-09-26T06:33:47.000Z" lastModifiedTime="2026-09-26T06:33:47.000Z" objectID="{E0635CAD-AE50-05FE-22E9-82F2F75DB84D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Bold &#129408; italic é color 東京]]></one:T></one:OE></one:Title><one:Outline author="Copier" lastModifiedBy="Copier" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{64}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="127.5136947631836" height="42.03070831298828"/><one:OEChildren><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{66}{B0}" alignment="left"><one:T><![CDATA[<span style='font-weight:bold;
3text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span
4style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family:
5Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri;
6font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through;
7font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family:
8Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family:
9Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size:
1011.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238;
11background:yellow;mso-highlight:yellow'>color</span><span style='font-family:
12Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family:
13Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size:
1411.0pt'><br>
15End</span>]]></one:T></one:OE><one:OE creationTime="2026-09-26T06:33:48.000Z" lastModifiedTime="2026-09-26T06:33:48.000Z" objectID="{82855B32-C890-0FF0-3238-816FD0683ADC}{68}{B0}" alignment="left" quickStyleIndex="1" style="font-size:11.0pt;color:#1F4E79"><one:T><![CDATA[<span
16style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span
17style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span
18style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span
19style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed'
20lang=ar-SA>مرحبا</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-copy/cold/read/payloads.json+2-2
...@@ -2,9 +2,9 @@...@@ -2,9 +2,9 @@
2 { 2 {
3 "sha256": "b20a14e044e33eac39b4665e7d79813b8a3672dbbd27ed9d2e4ac866c13ec4b6", 3 "sha256": "b20a14e044e33eac39b4665e7d79813b8a3672dbbd27ed9d2e4ac866c13ec4b6",
4 "name": "notes 🦀.txt", 4 "name": "notes 🦀.txt",
5 "object": "{9E89195D-B7EC-0AC4-34FD-F47D633F0533}{1}{B0}", 5 "object": "{82855B32-C890-0FF0-3238-816FD0683ADC}{12}{B0}",
6 "kind": "InsertedFile", 6 "kind": "InsertedFile",
7 "page": "{62E12C49-D5FE-0850-17D5-B4DC95B736E9}{1}{B0}", 7 "page": "{0C0ECE53-81B9-0DE0-2721-D119E26F7632}{1}{B0}",
8 "bytes": 28 8 "bytes": 28
9 } 9 }
10]10]
\ No newline at end of file
corpus/page-copy/cold/run.json+1-1
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1{1{
2 "notebook": "/private/tmp/m4/copy",2 "notebook": "/Users/clo/dev/one/evidence/titles/page-copy",
3 "expected_pages": 17,3 "expected_pages": 17,
4 "author": null,4 "author": null,
5 "author_timeout_seconds": 600,5 "author_timeout_seconds": 600,
corpus/page-copy/cold/source.json+6-6
...@@ -2,19 +2,19 @@...@@ -2,19 +2,19 @@
2 {2 {
3 "path": "Open Notebook.onetoc2",3 "path": "Open Notebook.onetoc2",
4 "bytes": 1604,4 "bytes": 1604,
5 "sha256": "acb0747c250f7bebed1e90b18d5dbbf8c32731f9287571b1e732681921fd323a",5 "sha256": "61afe63a5058198015858383382d2776cecfae45b6afea1a81c7d379f8b78ca8",
6 "mtime_ns": 17892871835778578486 "mtime_ns": 1790404431194548185
7 },7 },
8 {8 {
9 "path": "copies.one",9 "path": "copies.one",
10 "bytes": 185400,10 "bytes": 163632,
11 "sha256": "dcd7c226d86477adc1ad2f8da8a408ffa2b993b372517ab8d464e5746a971aac",11 "sha256": "75253d18459607aee385ceb73499d3780903e39fe436132666e842fd100b8d40",
12 "mtime_ns": 178928718357514193812 "mtime_ns": 1790404431191555148
13 },13 },
14 {14 {
15 "path": "expected-count.txt",15 "path": "expected-count.txt",
16 "bytes": 2,16 "bytes": 2,
17 "sha256": "4523540f1504cd17100c4835e85b7eefd49911580f8efff0599a8f283be6b9e3",17 "sha256": "4523540f1504cd17100c4835e85b7eefd49911580f8efff0599a8f283be6b9e3",
18 "mtime_ns": 178928718357793051418 "mtime_ns": 1790404431194611352
19 }19 }
20]20]
corpus/page-lifecycle/creation/README.md+2-1
...@@ -14,7 +14,8 @@ The retained `provenance.json` records the native capture inputs and clone remov...@@ -14,7 +14,8 @@ The retained `provenance.json` records the native capture inputs and clone remov
14After building workspace examples, run `python -m unittest discover -s tools14After building workspace examples, run `python -m unittest discover -s tools
15-p test_page_creation.py -v`. It compares native XML content and character formats,15-p test_page_creation.py -v`. It compares native XML content and character formats,
16ordered identities, and complete active object graphs across each cold reopen.16ordered identities, and complete active object graphs across each cold reopen.
17These thirteen-page comparisons need no graph normalization.17Only `followup/cold` differs: OneNote refreshes the section's cached copies of the
18two renamed titles, which the Rust title edit leaves stale.
1819
19Export fresh Rust candidates by setting each variable to a new output directory:20Export fresh Rust candidates by setting each variable to a new output directory:
2021
corpus/page-lifecycle/creation/candidate/Lifecycle.one
Binary files a/corpus/page-lifecycle/creation/candidate/Lifecycle.one and b/corpus/page-lifecycle/creation/candidate/Lifecycle.one differ
corpus/page-lifecycle/creation/cold/notebook/Lifecycle.one
Binary files a/corpus/page-lifecycle/creation/cold/notebook/Lifecycle.one and b/corpus/page-lifecycle/creation/cold/notebook/Lifecycle.one differ
corpus/page-lifecycle/creation/cold/read/hierarchy.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{3BFEF56D-B4F8-4F2E-A1BC-468DDA94A2D5}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T04:23:59.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{C65593FF-0E35-0BF7-1FC3-16CBBB799594}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-09T04:23:59.000Z" color="#91BAAE"><one:Page ID="{39C1FCAE-45F8-0FA4-3FC6-46AF3A75DC8A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{266FCA0A-41B5-03F0-0ADE-86136233B704}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{6C02B3F9-D11E-0A62-1E9D-86328FA37C2A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{4C0D72CA-D8C6-0D40-0E00-40C67411544F}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{58C13BB3-DE03-4816-B303-087ACC456B97}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{054D1020-5845-4210-8DEE-30AE886FFF66}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{5891D2E3-C28F-4C4F-91C8-E802A2DDA3B0}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{8B5220CE-1103-4F42-AD6F-443875AA4EFC}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"/><one:Page ID="{E0B7DA6E-27FF-4573-92CF-4663E293DAE6}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"/><one:Page ID="{9BFEBE8D-72F6-4211-B975-2E62E65AF7D7}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"/><one:Page ID="{3894C714-5BE5-03FC-208C-EFB4E9A37951}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"/><one:Page ID="{067A6101-C793-0F15-0101-74668CB91D5E}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"/><one:Page ID="{C6BB9F1B-5105-064D-086F-0B103A30C43B}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"/></one:Section></one:Notebook> 2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{30D39FB3-2305-41E5-96E8-7E153D82228E}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:36:22.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{E28A0F5E-BCA3-0FE8-0F78-76EF8E6F5E50}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-26T06:36:22.000Z" color="#91BAAE"><one:Page ID="{677392C8-6B9B-099F-32D5-118DD212AA11}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{02B056AB-F323-07EF-1A65-E63757257CC0}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{48DD2F58-6388-0E7D-0E26-E616BAB5B7EE}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{68D2EE6B-6A50-095F-1EBB-20E241079F8B}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{7C1EA712-6C95-4C09-A3B8-685EF953A053}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{21928C81-EAD3-460F-9D55-508ABD7934A2}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{7C4E4E42-7019-4850-8173-882697CB6874}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{AF8DBC6F-A395-4B5D-BDD4-241C40BC8538}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{C46846CF-9569-416C-8274-2647D7851122}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{BF21222C-C060-460E-A9CE-4E46D34C3C13}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"/><one:Page ID="{0F6FFE25-3632-0AD1-3AE1-41B9FD2DEB48}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:22.000Z" pageLevel="1"/><one:Page ID="{CCABB477-9855-0E14-1DCB-06D00F5CFE0E}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:22.000Z" pageLevel="1"/><one:Page ID="{82AE846C-D1FD-07B3-23E5-002BB0B8F1F6}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:22.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/page-lifecycle/creation/cold/read/page-000.xml+1-3
...@@ -1,4 +1,2 @@...@@ -1,4 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{054D1020-5845-4210-8DEE-30AE886FFF66}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:23:58.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-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{8F196553-BA68-46B8-A5A9-2FFC33D426C2}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{8F196553-BA68-46B8-A5A9-2FFC33D426C2}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{8F196553-BA68-46B8-A5A9-2FFC33D426C2}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{02B056AB-F323-07EF-1A65-E63757257CC0}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{CCC527B0-CF96-0F88-31E2-BD07CD56F03F}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page>
3style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/cold/read/page-001.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{067A6101-C793-0F15-0101-74668CB91D5E}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{067A6101-C793-0F15-0101-74668CB91D5E}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{0F6FFE25-3632-0AD1-3AE1-41B9FD2DEB48}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:22.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page>
corpus/page-lifecycle/creation/cold/read/page-002.xml+3-1
...@@ -1,2 +1,4 @@...@@ -1,2 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{266FCA0A-41B5-03F0-0ADE-86136233B704}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{E81ABB11-7D00-0B97-2159-DD23F8403BFB}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{21928C81-EAD3-460F-9D55-508ABD7934A2}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:36:21.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-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{ABC6F9F2-08FE-42A7-B512-4FD806C2ED06}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{ABC6F9F2-08FE-42A7-B512-4FD806C2ED06}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{ABC6F9F2-08FE-42A7-B512-4FD806C2ED06}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/cold/read/page-003.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{3894C714-5BE5-03FC-208C-EFB4E9A37951}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{48DD2F58-6388-0E7D-0E26-E616BAB5B7EE}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{6739EA83-CCDD-0264-254C-9EA896D3696C}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/creation/cold/read/page-004.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{39C1FCAE-45F8-0FA4-3FC6-46AF3A75DC8A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{39C1FCAE-45F8-0FA4-3FC6-46AF3A75DC8A}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{677392C8-6B9B-099F-32D5-118DD212AA11}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{677392C8-6B9B-099F-32D5-118DD212AA11}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/creation/cold/read/page-005.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{4C0D72CA-D8C6-0D40-0E00-40C67411544F}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{68D2EE6B-6A50-095F-1EBB-20E241079F8B}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:36:21.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page>
corpus/page-lifecycle/creation/cold/read/page-006.xml+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5891D2E3-C28F-4C4F-91C8-E802A2DDA3B0}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:23:58.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-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{C844F950-C3B5-4FE0-ADCD-588A8B55C0F1}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{C844F950-C3B5-4FE0-ADCD-588A8B55C0F1}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{C844F950-C3B5-4FE0-ADCD-588A8B55C0F1}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7C1EA712-6C95-4C09-A3B8-685EF953A053}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T06:36:21.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-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{7E053E68-3833-469C-9187-A251D7A8BEEE}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{7E053E68-3833-469C-9187-A251D7A8BEEE}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{7E053E68-3833-469C-9187-A251D7A8BEEE}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span 3style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/cold/read/page-007.xml+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{58C13BB3-DE03-4816-B303-087ACC456B97}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:23:58.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-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{5ADAA2C9-8AA5-4283-813C-C275E2BE752A}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{5ADAA2C9-8AA5-4283-813C-C275E2BE752A}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{5ADAA2C9-8AA5-4283-813C-C275E2BE752A}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7C4E4E42-7019-4850-8173-882697CB6874}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:36:21.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-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{EC9B65F1-7123-4BFF-BD76-38AEBE430B35}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{EC9B65F1-7123-4BFF-BD76-38AEBE430B35}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{EC9B65F1-7123-4BFF-BD76-38AEBE430B35}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span 3style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/cold/read/page-008.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6C02B3F9-D11E-0A62-1E9D-86328FA37C2A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:23:58.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{43E67622-7E4B-067B-35F7-FE8CA3C5A2A8}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{82AE846C-D1FD-07B3-23E5-002BB0B8F1F6}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:22.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{82AE846C-D1FD-07B3-23E5-002BB0B8F1F6}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/creation/cold/read/page-009.xml+1-1
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8B5220CE-1103-4F42-AD6F-443875AA4EFC}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:23:58.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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{A6AAC17A-F7B8-4832-BB11-A47AA3FCCB83}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{EDDDEAD0-9F85-44A8-BE51-3B1A009C7ABD}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{EDDDEAD0-9F85-44A8-BE51-3B1A009C7ABD}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{AF8DBC6F-A395-4B5D-BDD4-241C40BC8538}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:36:21.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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{82755DDB-452E-4C2D-ABAA-C45E96EA0047}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{C9027671-2D13-40B7-AEEA-5B3E358AB179}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{C9027671-2D13-40B7-AEEA-5B3E358AB179}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span 3style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/cold/read/page-010.xml+1-1
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9BFEBE8D-72F6-4211-B975-2E62E65AF7D7}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:23: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:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{2C80C4C5-DA5C-4DF0-BBB7-F55CC50454C4}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{2C80C4C5-DA5C-4DF0-BBB7-F55CC50454C4}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BF21222C-C060-460E-A9CE-4E46D34C3C13}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:36:21.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="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{085F5864-68CA-49EF-AB0C-9578F0129F00}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{085F5864-68CA-49EF-AB0C-9578F0129F00}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span 3style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/cold/read/page-011.xml+3-1
...@@ -1,2 +1,4 @@...@@ -1,2 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C6BB9F1B-5105-064D-086F-0B103A30C43B}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:23:59.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{C6BB9F1B-5105-064D-086F-0B103A30C43B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C46846CF-9569-416C-8274-2647D7851122}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:36:21.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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{81E65742-9824-44DE-97A5-9A479B990192}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{555966B2-BAF9-4B5C-B021-44A3F102FB6E}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{555966B2-BAF9-4B5C-B021-44A3F102FB6E}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/cold/read/page-012.xml+1-3
...@@ -1,4 +1,2 @@...@@ -1,4 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E0B7DA6E-27FF-4573-92CF-4663E293DAE6}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:23: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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{A539CBE3-2AB2-40C1-871E-FA63AE8FCA56}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{7186FA13-086F-4F43-A09A-2487C41430AA}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{7186FA13-086F-4F43-A09A-2487C41430AA}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CCABB477-9855-0E14-1DCB-06D00F5CFE0E}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:36:22.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{CCABB477-9855-0E14-1DCB-06D00F5CFE0E}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title></one:Page>
3style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/followup/Lifecycle.one
Binary files a/corpus/page-lifecycle/creation/followup/Lifecycle.one and b/corpus/page-lifecycle/creation/followup/Lifecycle.one differ
corpus/page-lifecycle/creation/followup/cold/notebook/Lifecycle.one
Binary files a/corpus/page-lifecycle/creation/followup/cold/notebook/Lifecycle.one and b/corpus/page-lifecycle/creation/followup/cold/notebook/Lifecycle.one differ
corpus/page-lifecycle/creation/followup/cold/read/hierarchy.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{6DFB689D-2E8E-4686-9997-32046753F9FD}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T04:44:45.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{C3953BFE-3B75-0740-087B-6C4AD8A1EE03}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-09T04:44:45.000Z" color="#91BAAE"><one:Page ID="{3C0154AF-70B8-0313-287E-3C2E59ADA71D}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{23AF620B-74F5-0F47-1D66-FC9201EBCC93}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{69C21BF8-E45E-06D5-0925-FCB3EC7B07BD}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{49CDDACB-ED86-01F7-19B8-3A4717C92FD8}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{5D0193B2-EB43-44A1-A4BB-72FBAF9D1000}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{008DB821-6D05-4EA7-9A56-4A2FEBB784F1}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{5D517AE2-F7CF-40F8-8670-9283C105D827}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{8E9288CF-2443-43F5-BAD7-3EB91672356B}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{E577726F-12BF-49C4-8577-3CE2814BA171}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{9E3E168C-47B6-4EA6-AECD-54E385828C40}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{3D546F15-6EA5-0F4B-3734-95358A7B02C6}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{03BAC900-F2D3-03A2-16B9-0EE7EF6166C9}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/><one:Page ID="{C37B371A-6445-0AFA-1FD7-719159E8BFAC}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"/></one:Section></one:Notebook> 2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{9911D09B-348F-485C-A103-C4023440DE63}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T07:00:22.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{4BBA0B35-96CC-0ED2-1A80-B96B6AA11DAF}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-26T07:00:22.000Z" color="#91BAAE"><one:Page ID="{CE4396A3-41F4-08A5-272D-DE0936DCE9EE}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{AB8052C0-D94C-06D5-0F9D-29B3B3EB3F3F}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{E1ED2B33-49E7-0F47-1BDE-29925E7BF411}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{C1E2EA00-403F-0865-0B43-EF66A5C9DC74}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{D52EA379-46FA-4D33-B640-A7DA1D9DE3AC}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{88A288EA-C0BC-4735-88AD-9F0E59B7775D}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{D57E4A29-5A76-496A-948B-47A273052B8B}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{06BDB804-89FA-4A67-A82C-EB98A472C6C7}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{6D5842A4-BF06-4056-978C-E9C3334B52DD}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{16112647-EA0F-4734-BC36-81C237827FEC}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{A65FFA4E-1C5D-0BEB-2F19-8E3D19E3A8B7}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{659BB01C-B23A-0F2E-0833-C954EB92BDF1}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/><one:Page ID="{2B9E8007-FB92-0689-361D-CFAF5476B209}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/page-lifecycle/creation/followup/cold/read/page-000.xml+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{008DB821-6D05-4EA7-9A56-4A2FEBB784F1}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:44: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-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{8AD9CD52-8F28-4A0F-B211-557D500C5D55}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{8AD9CD52-8F28-4A0F-B211-557D500C5D55}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{8AD9CD52-8F28-4A0F-B211-557D500C5D55}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{06BDB804-89FA-4A67-A82C-EB98A472C6C7}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T07:00:13.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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{2B4559B0-6F41-4D17-BE52-0BDA722443B8}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{6032721A-077C-418D-BB12-94BAD144F286}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{6032721A-077C-418D-BB12-94BAD144F286}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span 3style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/followup/cold/read/page-001.xml+3-1
...@@ -1,2 +1,4 @@...@@ -1,2 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{03BAC900-F2D3-03A2-16B9-0EE7EF6166C9}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:42:47.000Z" objectID="{03BAC900-F2D3-03A2-16B9-0EE7EF6166C9}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Reviewed New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T04:42:47.000Z" objectID="{80CDD07A-F21F-468D-A2FA-A92C4EC6F53F}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="244.6467742919922" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T04:33:45.000Z" lastModifiedTime="2026-09-09T04:42:47.000Z" objectID="{80CDD07A-F21F-468D-A2FA-A92C4EC6F53F}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Rust + Native body 0 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{16112647-EA0F-4734-BC36-81C237827FEC}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T07:00:13.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="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{A16F5C0F-42A5-48D5-BEF4-5AFC14DCDCFF}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{A16F5C0F-42A5-48D5-BEF4-5AFC14DCDCFF}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/followup/cold/read/page-002.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{23AF620B-74F5-0F47-1D66-FC9201EBCC93}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{EDDA1310-4840-0720-36E1-A7A29B98406C}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2B9E8007-FB92-0689-361D-CFAF5476B209}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:58:25.000Z" objectID="{2B9E8007-FB92-0689-361D-CFAF5476B209}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Reviewed New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-26T06:58:24.000Z" objectID="{7EFD74E4-6962-403F-8EFD-EE1FA6BD77EC}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="244.6467742919922" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:51:05.000Z" lastModifiedTime="2026-09-26T06:58:24.000Z" objectID="{7EFD74E4-6962-403F-8EFD-EE1FA6BD77EC}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Rust + Native body 1 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/followup/cold/read/page-003.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{3C0154AF-70B8-0313-287E-3C2E59ADA71D}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{3C0154AF-70B8-0313-287E-3C2E59ADA71D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{659BB01C-B23A-0F2E-0833-C954EB92BDF1}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:58:24.000Z" objectID="{659BB01C-B23A-0F2E-0833-C954EB92BDF1}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Reviewed New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-26T06:58:24.000Z" objectID="{10234C69-755D-4058-B87F-1D7258B660AD}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="244.6467742919922" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:51:05.000Z" lastModifiedTime="2026-09-26T06:58:24.000Z" objectID="{10234C69-755D-4058-B87F-1D7258B660AD}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Rust + Native body 0 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/followup/cold/read/page-004.xml+3-1
...@@ -1,2 +1,4 @@...@@ -1,2 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{3D546F15-6EA5-0F4B-3734-95358A7B02C6}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6D5842A4-BF06-4056-978C-E9C3334B52DD}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T07:00:13.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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{28D65329-B24B-45E4-825D-55C37F57426D}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{FC6962D9-9096-4A66-A5D9-8B2715CCB891}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{FC6962D9-9096-4A66-A5D9-8B2715CCB891}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/followup/cold/read/page-005.xml+3-1
...@@ -1,2 +1,4 @@...@@ -1,2 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{49CDDACB-ED86-01F7-19B8-3A4717C92FD8}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{88A288EA-C0BC-4735-88AD-9F0E59B7775D}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T07:00:13.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-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{02F6FD99-2291-439D-A0EA-805CE20CAEF9}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{02F6FD99-2291-439D-A0EA-805CE20CAEF9}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{02F6FD99-2291-439D-A0EA-805CE20CAEF9}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/followup/cold/read/page-006.xml+1-3
...@@ -1,4 +1,2 @@...@@ -1,4 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5D0193B2-EB43-44A1-A4BB-72FBAF9D1000}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:44: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-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{5F1A0AC8-BFE5-4E34-9684-B8F481660EBD}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{5F1A0AC8-BFE5-4E34-9684-B8F481660EBD}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{5F1A0AC8-BFE5-4E34-9684-B8F481660EBD}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{A65FFA4E-1C5D-0BEB-2F19-8E3D19E3A8B7}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page>
3style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/followup/cold/read/page-007.xml+1-3
...@@ -1,4 +1,2 @@...@@ -1,4 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5D517AE2-F7CF-40F8-8670-9283C105D827}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:44: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-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{CD845151-F6F5-4357-BA75-220BE88DBB66}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{CD845151-F6F5-4357-BA75-220BE88DBB66}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{CD845151-F6F5-4357-BA75-220BE88DBB66}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{AB8052C0-D94C-06D5-0F9D-29B3B3EB3F3F}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{65F523DB-E5F9-0EB2-241A-72832998B3C0}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page>
3style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/followup/cold/read/page-008.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{69C21BF8-E45E-06D5-0925-FCB3EC7B07BD}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{4626DE23-4B0B-0ACC-224F-840DC01DD93F}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C1E2EA00-403F-0865-0B43-EF66A5C9DC74}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page>
corpus/page-lifecycle/creation/followup/cold/read/page-009.xml+1-3
...@@ -1,4 +1,2 @@...@@ -1,4 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8E9288CF-2443-43F5-BAD7-3EB91672356B}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:44: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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{A36A697B-C2F8-4485-ACA9-DEFBC024B014}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{E81D42D1-AAC5-481F-A9E9-419B6344012A}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{E81D42D1-AAC5-481F-A9E9-419B6344012A}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CE4396A3-41F4-08A5-272D-DE0936DCE9EE}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{CE4396A3-41F4-08A5-272D-DE0936DCE9EE}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page>
3style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/followup/cold/read/page-010.xml+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9E3E168C-47B6-4EA6-AECD-54E385828C40}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:44: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:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{29406CC4-EF1C-4147-AC0F-8FDDA6DC2F53}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{29406CC4-EF1C-4147-AC0F-8FDDA6DC2F53}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D52EA379-46FA-4D33-B640-A7DA1D9DE3AC}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T07:00:13.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-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{D7353A03-125C-47A6-847F-6DD53366FD11}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{D7353A03-125C-47A6-847F-6DD53366FD11}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{D7353A03-125C-47A6-847F-6DD53366FD11}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span 3style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/followup/cold/read/page-011.xml+3-1
...@@ -1,2 +1,4 @@...@@ -1,2 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C37B371A-6445-0AFA-1FD7-719159E8BFAC}{1}{B0}" name="Reviewed New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:44:45.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:42:47.000Z" objectID="{C37B371A-6445-0AFA-1FD7-719159E8BFAC}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Reviewed New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T04:42:47.000Z" objectID="{DA84E582-1E18-4AD2-984E-701D70B4CB24}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="244.6467742919922" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T04:33:45.000Z" lastModifiedTime="2026-09-09T04:42:47.000Z" objectID="{DA84E582-1E18-4AD2-984E-701D70B4CB24}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Rust + Native body 1 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D57E4A29-5A76-496A-948B-47A273052B8B}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T07:00:13.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-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{45AB619A-5B4C-4AC5-A88E-F72A5A8D48CA}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{45AB619A-5B4C-4AC5-A88E-F72A5A8D48CA}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{45AB619A-5B4C-4AC5-A88E-F72A5A8D48CA}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/followup/cold/read/page-012.xml+1-3
...@@ -1,4 +1,2 @@...@@ -1,4 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E577726F-12BF-49C4-8577-3CE2814BA171}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:44: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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{A0F963E2-1FF2-4C76-90A6-80E2CD57B1C1}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{74465212-3D2F-43F4-B722-5E06A7CC4B3D}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{74465212-3D2F-43F4-B722-5E06A7CC4B3D}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E1ED2B33-49E7-0F47-1BDE-29925E7BF411}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T07:00:13.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{CE09EEE8-E6B2-035E-30B4-512C721D2A93}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page>
3style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/cold/read/hierarchy.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{89446429-1F4A-475B-A18F-A3C179D6E0AB}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T04:37:57.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{82D2B6AE-B1AA-0F47-17A3-639D555107BE}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-09T04:37:57.000Z" color="#91BAAE"><one:Page ID="{7D46D9FF-FA67-0B14-37A6-33F9D45D4EA0}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{62E8EF5B-FE2A-0740-02BE-F3458C1B252E}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{288596A8-6E81-0ED2-16FD-F364618BEE00}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{088A579B-6759-09F0-0660-35909A39C665}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{1C461EE2-619C-4CA6-BB63-7D2C226DF9BD}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{41CA3571-E7DA-46A0-858E-45F866476D4C}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{1C16F7B2-7D10-48FF-99A8-9D544CF5319A}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{CFD5059F-AE9C-4BF2-A50F-316E9B82DCD6}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{A430FF3F-9860-41C3-9AAF-33350CBB48CC}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{DF799BDC-CD69-46A1-B115-5B34087265FD}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{7C13E245-E47A-074C-28EC-9AE2078BEB7B}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{42FD4450-780C-0BA5-0961-013062918F74}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/><one:Page ID="{823CBA4A-EE9A-02FD-000F-7E46D4185611}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"/></one:Section></one:Notebook> 2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{D359911F-7062-45AF-8CB2-62243A804169}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:56:14.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{78DF607A-1198-074B-3AAD-512A7379337A}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-26T06:56:14.000Z" color="#91BAAE"><one:Page ID="{FD26FDEC-C6A0-013C-0700-36482F04C73B}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"/><one:Page ID="{98E5398F-5E18-0F4C-2FB0-C1F2AA3311EA}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"/><one:Page ID="{D288407C-CEB3-06DE-3BF3-C1D347A3DAC4}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"/><one:Page ID="{F287814F-C76B-01FC-2B6E-0727BC11F2A1}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"/><one:Page ID="{E64BC836-C1AE-44AA-966D-4F9B0445CD79}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"/><one:Page ID="{BBC7E3A5-47E8-4EAC-A880-774F406F5988}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"/><one:Page ID="{E61B2166-DD22-40F3-B4A6-AFE36ADD055E}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"/><one:Page ID="{35D8D34B-0EAE-43FE-8801-03D9BDAAE812}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1"/><one:Page ID="{5E3D29EB-3852-49CF-B7A1-01822A937C08}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1"/><one:Page ID="{25744D08-6D5B-4EAD-9C1B-69832E5A5139}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1"/><one:Page ID="{953A9101-9B09-0272-0F34-667C003B8662}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1"/><one:Page ID="{56FEDB53-356E-06B7-281E-2115F24A9324}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1"/><one:Page ID="{18FBEB48-7CC6-0F10-1630-27EE4DAE9CDC}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/page-lifecycle/creation/native/cold/read/page-000.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{088A579B-6759-09F0-0660-35909A39C665}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{18FBEB48-7CC6-0F10-1630-27EE4DAE9CDC}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{18FBEB48-7CC6-0F10-1630-27EE4DAE9CDC}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-26T06:51:06.000Z" objectID="{4D981FAB-EE36-49A6-AED0-065EBF655939}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:51:05.000Z" lastModifiedTime="2026-09-26T06:51:05.000Z" objectID="{4D981FAB-EE36-49A6-AED0-065EBF655939}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 1 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/cold/read/page-001.xml+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1C16F7B2-7D10-48FF-99A8-9D544CF5319A}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:37: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-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{8CC3DC01-7C2A-4B50-A5AD-2DDC657D52DB}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{8CC3DC01-7C2A-4B50-A5AD-2DDC657D52DB}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{8CC3DC01-7C2A-4B50-A5AD-2DDC657D52DB}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{25744D08-6D5B-4EAD-9C1B-69832E5A5139}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:56:14.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="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{920A3740-C5F1-414C-9ED9-B2BD0D04F22A}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{920A3740-C5F1-414C-9ED9-B2BD0D04F22A}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span 3style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/cold/read/page-002.xml+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1C461EE2-619C-4CA6-BB63-7D2C226DF9BD}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:37: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-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{1E5D8798-353A-4633-895C-B7230C96E700}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{1E5D8798-353A-4633-895C-B7230C96E700}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{1E5D8798-353A-4633-895C-B7230C96E700}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{35D8D34B-0EAE-43FE-8801-03D9BDAAE812}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:56:14.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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{182032FF-E815-448E-9E7F-E39B6BFC6D6D}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{53571955-8028-4814-9B3F-7CFBC89CDC53}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{53571955-8028-4814-9B3F-7CFBC89CDC53}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span 3style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/cold/read/page-003.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{288596A8-6E81-0ED2-16FD-F364618BEE00}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{07615373-C1D4-02CB-3D97-8BDA4DED3082}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{56FEDB53-356E-06B7-281E-2115F24A9324}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{56FEDB53-356E-06B7-281E-2115F24A9324}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-26T06:51:05.000Z" objectID="{23462726-F209-49C1-9852-F533416E4E78}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:51:05.000Z" lastModifiedTime="2026-09-26T06:51:05.000Z" objectID="{23462726-F209-49C1-9852-F533416E4E78}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 0 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/cold/read/page-004.xml+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{41CA3571-E7DA-46A0-858E-45F866476D4C}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:37: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-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{CB9E4002-05F7-4208-ADC9-5AAADDFCB4E8}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{CB9E4002-05F7-4208-ADC9-5AAADDFCB4E8}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{CB9E4002-05F7-4208-ADC9-5AAADDFCB4E8}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5E3D29EB-3852-49CF-B7A1-01822A937C08}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:56:14.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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{1BB33866-351F-4C7D-A270-BD82668F6CB8}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{CF0C0996-17C2-43FF-85F4-63660C149644}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{CF0C0996-17C2-43FF-85F4-63660C149644}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span 3style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/cold/read/page-005.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{42FD4450-780C-0BA5-0961-013062918F74}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{42FD4450-780C-0BA5-0961-013062918F74}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{C18A5D2A-78C0-4E8A-BD22-A6FBC3361C82}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T04:33:45.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{C18A5D2A-78C0-4E8A-BD22-A6FBC3361C82}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 0 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{953A9101-9B09-0272-0F34-667C003B8662}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:14.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page>
corpus/page-lifecycle/creation/native/cold/read/page-006.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{62E8EF5B-FE2A-0740-02BE-F3458C1B252E}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{AC9D9E40-C29F-0F27-2939-A8751668A9D1}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{98E5398F-5E18-0F4C-2FB0-C1F2AA3311EA}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{56904894-62AD-072B-0437-9AC230409D15}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/creation/native/cold/read/page-007.xml+3-1
...@@ -1,2 +1,4 @@...@@ -1,2 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7C13E245-E47A-074C-28EC-9AE2078BEB7B}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BBC7E3A5-47E8-4EAC-A880-774F406F5988}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:56:13.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-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{319396D6-A5C5-4A04-80C7-681DFBD4802C}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{319396D6-A5C5-4A04-80C7-681DFBD4802C}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{319396D6-A5C5-4A04-80C7-681DFBD4802C}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/cold/read/page-008.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7D46D9FF-FA67-0B14-37A6-33F9D45D4EA0}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{7D46D9FF-FA67-0B14-37A6-33F9D45D4EA0}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D288407C-CEB3-06DE-3BF3-C1D347A3DAC4}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{FD6C85A7-61E6-0AC7-1099-B96D6BC50446}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/creation/native/cold/read/page-009.xml+3-1
...@@ -1,2 +1,4 @@...@@ -1,2 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{823CBA4A-EE9A-02FD-000F-7E46D4185611}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:37:57.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{823CBA4A-EE9A-02FD-000F-7E46D4185611}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{9BC368D2-94C7-42D5-8796-7FCAFD442299}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T04:33:45.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{9BC368D2-94C7-42D5-8796-7FCAFD442299}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 1 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E61B2166-DD22-40F3-B4A6-AFE36ADD055E}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:56:13.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-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{76CE0AD5-DC18-435C-88A3-1F6B4355661F}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{76CE0AD5-DC18-435C-88A3-1F6B4355661F}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{76CE0AD5-DC18-435C-88A3-1F6B4355661F}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/cold/read/page-010.xml+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{A430FF3F-9860-41C3-9AAF-33350CBB48CC}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:37: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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{E1BEEEB2-952D-4471-8F7E-8F3540A7587C}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{3501DF42-B7F0-4BF3-A8FA-51D12A3CA280}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{3501DF42-B7F0-4BF3-A8FA-51D12A3CA280}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E64BC836-C1AE-44AA-966D-4F9B0445CD79}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T06:56:13.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-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{E450514C-9508-4E3F-A452-85942ABED3C4}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{E450514C-9508-4E3F-A452-85942ABED3C4}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{E450514C-9508-4E3F-A452-85942ABED3C4}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span 3style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/cold/read/page-011.xml+1-3
...@@ -1,4 +1,2 @@...@@ -1,4 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CFD5059F-AE9C-4BF2-A50F-316E9B82DCD6}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:37: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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{E22DE42B-4827-4C82-B371-D12C4DD459A9}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{A95ACF81-201A-4018-B631-4E4CEEB4E897}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{A95ACF81-201A-4018-B631-4E4CEEB4E897}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F287814F-C76B-01FC-2B6E-0727BC11F2A1}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page>
3style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/cold/read/page-012.xml+1-3
...@@ -1,4 +1,2 @@...@@ -1,4 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{DF799BDC-CD69-46A1-B115-5B34087265FD}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:37: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:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{6807E194-65C3-4940-B3D7-800A2B2CC6EE}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{6807E194-65C3-4940-B3D7-800A2B2CC6EE}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{FD26FDEC-C6A0-013C-0700-36482F04C73B}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:56:13.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{FD26FDEC-C6A0-013C-0700-36482F04C73B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page>
3style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/notebook/Lifecycle.one
Binary files a/corpus/page-lifecycle/creation/native/notebook/Lifecycle.one and b/corpus/page-lifecycle/creation/native/notebook/Lifecycle.one differ
corpus/page-lifecycle/creation/native/read/hierarchy.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{477DD477-3150-478E-84D1-0D30A0CB781D}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T04:35:14.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{2E7A04FF-1560-053C-194A-E3E92C86F3EF}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-09T04:35:14.000Z" color="#91BAAE"><one:Page ID="{D1EE6BAE-5EAD-016F-394F-B38DAD8ABAF1}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"/><one:Page ID="{CE405D0A-5AE0-0D3B-0C57-7331F5CCD17F}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"/><one:Page ID="{842D24F9-CA4B-04A9-1814-7310185C1A51}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"/><one:Page ID="{A422E5CA-C393-038B-0889-B5E4E3EE3234}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"/><one:Page ID="{B0EEACB3-C556-46DD-B58A-FD585BBA0DEC}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"/><one:Page ID="{ED628720-4310-4CDB-8B67-C58C1F90991D}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"/><one:Page ID="{B0BE45E3-D9DA-4284-9741-1D203522C5CB}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"/><one:Page ID="{637DB7CE-0A56-4189-ABE6-B11AE2552887}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:33:42.000Z" pageLevel="1"/><one:Page ID="{08984D6E-3CAA-4BB8-9446-B341756CBC9D}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:33:42.000Z" pageLevel="1"/><one:Page ID="{73D1298D-69A3-4CDA-BFFC-DB4071A591AC}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:33:42.000Z" pageLevel="1"/><one:Page ID="{D0BB5014-40B0-0D37-2605-1A967E5C1F2A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:42.000Z" pageLevel="1"/><one:Page ID="{EE55F601-DCC6-01DE-0788-81441B467B25}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" pageLevel="1"/><one:Page ID="{2E94081B-4A50-0886-0EE6-FE32ADCFA240}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" pageLevel="1"/></one:Section></one:Notebook> 2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{F0EF8762-C1B7-43BB-AF98-F0B81C916A40}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:52:30.000Z" color="#B49EDE"><one:Section name="Lifecycle" ID="{5F0C301D-2DB7-0FF8-0A4D-AB589AADC2FA}{1}{B0}" path="C:\one-tests\runs\capture\notebook\Lifecycle.one" lastModifiedTime="2026-09-26T06:52:30.000Z" color="#91BAAE"><one:Page ID="{DAF5AD8B-FA8F-098F-37E0-CC3AC6D036BB}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{BF3669E8-6237-07FF-1F50-3B8043E7E06A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{F55B101B-F29C-0E6D-0B13-3BA1AE772B44}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{D554D128-FB44-094F-1B8E-FD5555C50321}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{C1989851-FD81-4C19-A68D-B5E9ED913CF9}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{9C14B3C2-7BC7-461F-9860-8D3DA9BBA808}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{C1C87101-E10D-4840-8446-55918309F4DE}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{120B832C-3281-4B4D-B8E1-F9AB547E1992}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{79EE798C-047D-417C-8741-FBF0C3478D88}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{02A71D6F-5174-461E-ACFB-93F1C78EA0B9}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{B2E9C166-A726-0AC1-3FD4-9C0EE9EF77E2}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"/><one:Page ID="{712D8B34-0941-0E04-18FE-DB671B9E62A4}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:06.000Z" pageLevel="1"/><one:Page ID="{3F28BB2F-40E9-07A3-26D0-DD9CA47A6D5C}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:06.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/page-lifecycle/creation/native/read/page-000.xml+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{08984D6E-3CAA-4BB8-9446-B341756CBC9D}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:33:42.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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{4D165CE3-31E7-4E0A-8197-0F413970AC2D}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{99A96D13-133A-4188-A613-D1A553EB56D1}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{99A96D13-133A-4188-A613-D1A553EB56D1}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{02A71D6F-5174-461E-ACFB-93F1C78EA0B9}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:51: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:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{B5D96727-F9DE-49FF-AE39-48CFE4D003AA}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{B5D96727-F9DE-49FF-AE39-48CFE4D003AA}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span 3style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/read/page-001.xml+3-1
...@@ -1,2 +1,4 @@...@@ -1,2 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2E94081B-4A50-0886-0EE6-FE32ADCFA240}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{2E94081B-4A50-0886-0EE6-FE32ADCFA240}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{376BDA83-300D-48AE-897F-FFBE8493D6C8}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T04:33:45.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{376BDA83-300D-48AE-897F-FFBE8493D6C8}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 1 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{120B832C-3281-4B4D-B8E1-F9AB547E1992}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:51: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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{3FF36298-D43A-4C3D-AE9F-19E982289CED}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{74844932-BC07-40A7-ABDF-868921482DD3}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{74844932-BC07-40A7-ABDF-868921482DD3}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/read/page-002.xml+1-3
...@@ -1,4 +1,2 @@...@@ -1,4 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{637DB7CE-0A56-4189-ABE6-B11AE2552887}{1}{B0}" name="Renamed 🦋 é" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:33:42.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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{4E85567A-ECED-46F9-BD98-51583403ADF8}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Renamed &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{05F27DD0-84D0-4A63-B8D8-CE3897631CC6}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="170.2942504882812" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{05F27DD0-84D0-4A63-B8D8-CE3897631CC6}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{3F28BB2F-40E9-07A3-26D0-DD9CA47A6D5C}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:06.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{3F28BB2F-40E9-07A3-26D0-DD9CA47A6D5C}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-26T06:51:06.000Z" objectID="{6A4B4FCC-D219-4115-9E30-FC2C56B1A8B9}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:51:05.000Z" lastModifiedTime="2026-09-26T06:51:05.000Z" objectID="{6A4B4FCC-D219-4115-9E30-FC2C56B1A8B9}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 1 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
3style='font-weight:bold' lang=en-US>Body duplicate &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/read/page-003.xml+1-3
...@@ -1,4 +1,2 @@...@@ -1,4 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{73D1298D-69A3-4CDA-BFFC-DB4071A591AC}{1}{B0}" name="Body automatic 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T04:33:42.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="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{C4AF53C5-C109-433B-BD3E-007E52FB32BF}{14}{B0}"><one:Position x="36.0" y="14.40000057220459" z="0"/><one:Size width="174.3547973632812" height="13.42771625518799"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{C4AF53C5-C109-433B-BD3E-007E52FB32BF}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{712D8B34-0941-0E04-18FE-DB671B9E62A4}{1}{B0}" name="New 🦋 é" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:06.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{712D8B34-0941-0E04-18FE-DB671B9E62A4}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-26T06:51:05.000Z" objectID="{04957741-CE26-4172-A8B2-0F41A8BABFF8}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-26T06:51:05.000Z" lastModifiedTime="2026-09-26T06:51:05.000Z" objectID="{04957741-CE26-4172-A8B2-0F41A8BABFF8}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 0 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
3style='font-weight:bold' lang=en-US>Body automatic &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/read/page-004.xml+3-1
...@@ -1,2 +1,4 @@...@@ -1,2 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{842D24F9-CA4B-04A9-1814-7310185C1A51}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{ABC9E122-651E-08B0-337E-0BAE343AC4D3}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{79EE798C-047D-417C-8741-FBF0C3478D88}{1}{B0}" name="Body trailing 🦀 é preserved" dateTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-26T06:51: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-09T03:09:34.000Z" lastModifiedTime="2026-09-09T03:09:34.000Z" objectID="{3C606801-0930-44CE-9290-47F08F5B9D38}{16}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{E8DF59F1-2BED-4B4C-B514-9914E5C067C4}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="160.1752014160156" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{E8DF59F1-2BED-4B4C-B514-9914E5C067C4}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body trailing &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/read/page-005.xml+3-1
...@@ -1,2 +1,4 @@...@@ -1,2 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{A422E5CA-C393-038B-0889-B5E4E3EE3234}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9C14B3C2-7BC7-461F-9860-8D3DA9BBA808}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:51: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-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{1640C6B1-99EA-42B7-B027-926F120071AC}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{1640C6B1-99EA-42B7-B027-926F120071AC}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{1640C6B1-99EA-42B7-B027-926F120071AC}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/read/page-006.xml+1-3
...@@ -1,4 +1,2 @@...@@ -1,4 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B0BE45E3-D9DA-4284-9741-1D203522C5CB}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:33:41.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-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{206B6E50-D8E0-412B-AB44-ADA81CAAA68A}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{206B6E50-D8E0-412B-AB44-ADA81CAAA68A}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{206B6E50-D8E0-412B-AB44-ADA81CAAA68A}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B2E9C166-A726-0AC1-3FD4-9C0EE9EF77E2}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page>
3style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/read/page-007.xml+1-3
...@@ -1,4 +1,2 @@...@@ -1,4 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B0EEACB3-C556-46DD-B58A-FD585BBA0DEC}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T04:33:41.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-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{B2F535C9-91F0-4C48-87B5-375775411351}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{B2F535C9-91F0-4C48-87B5-375775411351}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{B2F535C9-91F0-4C48-87B5-375775411351}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BF3669E8-6237-07FF-1F50-3B8043E7E06A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{714318F3-5E82-0F98-34D7-60B0D9946C95}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page>
3style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/read/page-008.xml+3-1
...@@ -1,2 +1,4 @@...@@ -1,2 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CE405D0A-5AE0-0D3B-0C57-7331F5CCD17F}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{00352C11-6655-055C-27D0-28016FBF5D80}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C1989851-FD81-4C19-A68D-B5E9ED913CF9}{1}{B0}" name="Same title" dateTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-26T06:51: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-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:27.000Z" objectID="{C383012B-A927-468C-94B2-7FE6C36A2244}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{C383012B-A927-468C-94B2-7FE6C36A2244}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="158.2952728271484" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:27.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{C383012B-A927-468C-94B2-7FE6C36A2244}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body parent &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/read/page-009.xml+3-1
...@@ -1,2 +1,4 @@...@@ -1,2 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D0BB5014-40B0-0D37-2605-1A967E5C1F2A}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:42.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C1C87101-E10D-4840-8446-55918309F4DE}{1}{B0}" name="grandchild" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-26T06:51: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-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{511D5AB2-E037-4BEF-B843-E519AA81979F}{32}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[grandchild]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{511D5AB2-E037-4BEF-B843-E519AA81979F}{39}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="175.9715728759766" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:30.000Z" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{511D5AB2-E037-4BEF-B843-E519AA81979F}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Body grandchild &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/read/page-010.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D1EE6BAE-5EAD-016F-394F-B38DAD8ABAF1}{1}{B0}" name="Untitled page" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:41.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{D1EE6BAE-5EAD-016F-394F-B38DAD8ABAF1}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D554D128-FB44-094F-1B8E-FD5555C50321}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings></one:Page>
corpus/page-lifecycle/creation/native/read/page-011.xml+1-3
...@@ -1,4 +1,2 @@...@@ -1,4 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{ED628720-4310-4CDB-8B67-C58C1F90991D}{1}{B0}" name="child" dateTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T04:33:41.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-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{6736F253-A13D-4873-A320-DADEA42B40B9}{28}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[child]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:09:30.000Z" objectID="{6736F253-A13D-4873-A320-DADEA42B40B9}{35}{B0}"><one:Position x="36.0" y="86.4000015258789" z="0"/><one:Size width="149.6102294921875" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:09:29.000Z" lastModifiedTime="2026-09-09T03:09:29.000Z" objectID="{6736F253-A13D-4873-A320-DADEA42B40B9}{36}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{DAF5AD8B-FA8F-098F-37E0-CC3AC6D036BB}{1}{B0}" name="Untitled page" dateTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-26T06:32:12.000Z" lastModifiedTime="2026-09-26T06:32:12.000Z" objectID="{DAF5AD8B-FA8F-098F-37E0-CC3AC6D036BB}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page>
3style='font-weight:bold' lang=en-US>Body child &#129408; é</span><span
4lang=en-US> </span><span style='font-style:italic' lang=x-none>preserved</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/creation/native/read/page-012.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{EE55F601-DCC6-01DE-0788-81441B467B25}{1}{B0}" name="New 🦋 é" dateTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:QuickStyleDef index="1" 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 showDate="false" showTime="false" lang="en-US"><one:OE author="Rust author" lastModifiedBy="Rust author" creationTime="2026-09-09T04:22:39.000Z" lastModifiedTime="2026-09-09T04:22:39.000Z" objectID="{EE55F601-DCC6-01DE-0788-81441B467B25}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[New &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{6D22EF7B-DC0A-44F1-B3CB-268FBAE1E8D3}{10}{B0}"><one:Position x="72.0" y="144.0" z="0"/><one:Size width="214.4557342529297" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T04:33:45.000Z" lastModifiedTime="2026-09-09T04:33:45.000Z" objectID="{6D22EF7B-DC0A-44F1-B3CB-268FBAE1E8D3}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Native body 0 after Rust page creation.]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F55B101B-F29C-0E6D-0B13-3BA1AE772B44}{1}{B0}" name="Untitled page" dateTime="2026-09-09T03:09:19.000Z" lastModifiedTime="2026-09-26T06:51:01.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.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-09T03:09:19.000Z" lastModifiedTime="2026-09-09T03:09:19.000Z" objectID="{DABFD5C0-5DC9-0274-2079-431F8211F5C6}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/creation/provenance.json+16-16
...@@ -1,8 +1,8 @@...@@ -1,8 +1,8 @@
1{1{
2 "cold": {2 "cold": {
3 "capture": "evidence/m10/page-create-cold",3 "capture": "evidence/titles/cold-creation",
4 "run": {4 "run": {
5 "notebook": "/Users/clo/dev/one/evidence/m10/page-create-candidate",5 "notebook": "/Users/clo/dev/one/evidence/titles/creation",
6 "expected_pages": 13,6 "expected_pages": 13,
7 "author": null,7 "author": null,
8 "author_timeout_seconds": 600,8 "author_timeout_seconds": 600,
...@@ -20,17 +20,17 @@...@@ -20,17 +20,17 @@
20 }20 }
21 },21 },
22 "machine": {22 "machine": {
23 "name": "m6-4b25125e",23 "name": "m6-5472ef31",
24 "hostname": "ONE-M6-4B25125E"24 "hostname": "ONE-M6-5472EF31"
25 },25 },
26 "teardown": {26 "teardown": {
27 "absent": true27 "absent": true
28 }28 }
29 },29 },
30 "native": {30 "native": {
31 "capture": "evidence/m10/page-create-native-edit",31 "capture": "evidence/titles/native",
32 "run": {32 "run": {
33 "notebook": "/Users/clo/dev/one/evidence/m10/page-create-cold/notebook",33 "notebook": "/Users/clo/dev/one/evidence/titles/native-in",
34 "expected_pages": 13,34 "expected_pages": 13,
35 "author": "tools/native/created-pages.ps1",35 "author": "tools/native/created-pages.ps1",
36 "author_timeout_seconds": 600,36 "author_timeout_seconds": 600,
...@@ -49,17 +49,17 @@...@@ -49,17 +49,17 @@
49 }49 }
50 },50 },
51 "machine": {51 "machine": {
52 "name": "m6-71dafa89",52 "name": "m6-ba7e55f4",
53 "hostname": "ONE-M6-71DAFA89"53 "hostname": "ONE-M6-BA7E55F4"
54 },54 },
55 "teardown": {55 "teardown": {
56 "absent": true56 "absent": true
57 }57 }
58 },58 },
59 "native/cold": {59 "native/cold": {
60 "capture": "evidence/m10/page-create-native-edit-cold",60 "capture": "evidence/titles/native-cold",
61 "run": {61 "run": {
62 "notebook": "/Users/clo/dev/one/evidence/m10/page-create-native-edit/notebook",62 "notebook": "/Users/clo/dev/one/evidence/titles/native/notebook",
63 "expected_pages": 13,63 "expected_pages": 13,
64 "author": null,64 "author": null,
65 "author_timeout_seconds": 600,65 "author_timeout_seconds": 600,
...@@ -77,17 +77,17 @@...@@ -77,17 +77,17 @@
77 }77 }
78 },78 },
79 "machine": {79 "machine": {
80 "name": "m6-eabc83c1",80 "name": "m6-426eea56",
81 "hostname": "ONE-M6-EABC83C1"81 "hostname": "ONE-M6-426EEA56"
82 },82 },
83 "teardown": {83 "teardown": {
84 "absent": true84 "absent": true
85 }85 }
86 },86 },
87 "followup/cold": {87 "followup/cold": {
88 "capture": "evidence/m10/page-create-rust-followup-cold",88 "capture": "evidence/titles/followup-cold",
89 "run": {89 "run": {
90 "notebook": "/Users/clo/dev/one/evidence/m10/page-create-rust-followup",90 "notebook": "/Users/clo/dev/one/evidence/titles/followup",
91 "expected_pages": 13,91 "expected_pages": 13,
92 "author": null,92 "author": null,
93 "author_timeout_seconds": 600,93 "author_timeout_seconds": 600,
...@@ -105,8 +105,8 @@...@@ -105,8 +105,8 @@
105 }105 }
106 },106 },
107 "machine": {107 "machine": {
108 "name": "m6-5a91d9d1",108 "name": "m6-44be46f3",
109 "hostname": "ONE-M6-5A91D9D1"109 "hostname": "ONE-M6-44BE46F3"
110 },110 },
111 "teardown": {111 "teardown": {
112 "absent": true112 "absent": true
corpus/page-lifecycle/offline-creation/candidate/pages.one
Binary files a/corpus/page-lifecycle/offline-creation/candidate/pages.one and b/corpus/page-lifecycle/offline-creation/candidate/pages.one differ
corpus/page-lifecycle/offline-creation/cold/notebook/pages.one
Binary files a/corpus/page-lifecycle/offline-creation/cold/notebook/pages.one and b/corpus/page-lifecycle/offline-creation/cold/notebook/pages.one differ
corpus/page-lifecycle/offline-creation/cold/read/hierarchy.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{F830A298-FD46-4884-B4C0-6F0BF45C3B90}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T05:08:15.000Z" color="#B49EDE"><one:Section name="pages" ID="{9ADFA36E-F0EB-051E-115A-102FF772CA03}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pages.one" lastModifiedTime="2026-09-09T05:08:15.000Z" color="#8AA8E4"><one:Page ID="{1F0CC9C0-C49B-0C91-08C6-AA89176EF5BF}{1}{B0}" name="Original" dateTime="2026-09-09T05:06:23.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{D3E5FB89-79C3-05BA-1805-E25CE31ED175}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{AC84259B-D9B6-0A59-2748-93C35602B0BD}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{CC3DEC87-5707-084C-0528-649AB24AD8B7}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{7CA0430A-8905-04E4-0455-CFE53ECEE2AA}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{557FC918-74CB-09B0-19E7-3D1809D43E13}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{54F2D7D9-CAFE-0C6B-232B-2AE669F85BEA}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{9EE67EFB-DA69-05C0-334D-18E6DCBBE33B}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{52FFB786-C5E9-07F5-1BE1-C5095C3F3F9B}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{1606DF11-F2FE-0BF3-2AD6-DF46E0A08ECA}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{6CB51A17-61D4-0DBF-2C43-4CB4C9D2B6A1}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{87E24DEE-963B-09DA-1E68-D198DC52EDA5}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/><one:Page ID="{AC1E7B39-43AC-0370-2960-486A10D01E43}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:27.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1"/></one:Section></one:Notebook> 2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{EDDEDB7B-13B7-4165-A533-B9C06F49385A}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:39:54.000Z" color="#B49EDE"><one:Section name="pages" ID="{3A067CBC-DC0D-05D8-02F3-B2A77833F748}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pages.one" lastModifiedTime="2026-09-26T06:39:54.000Z" color="#8AA8E4"><one:Page ID="{2B1E474F-E067-06BF-29A3-033B4987A848}{1}{B0}" name="Original" dateTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:39:44.000Z" pageLevel="1"/><one:Page ID="{849FA331-A262-0BFD-1424-6ABB50BCD8C7}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:39:44.000Z" pageLevel="1"/><one:Page ID="{91186C32-65B4-0578-030C-10DA1738633A}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{363E253C-E20B-038F-3B6E-740FF08C2966}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{DAF8F05F-4D73-0BFF-1A5C-60D6A0F48D49}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{6BD09F5F-AC47-06D8-3796-A163670C2EFD}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{06EF31AC-FE47-058C-07BA-DF78D1983F03}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{5BCF0FAC-7A3F-0144-15E4-A5B7CD35716B}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{8EE0AC66-288F-02EF-1E6D-FC56C315C68F}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{670CA118-DAE0-03FD-32C3-C0BBC132147E}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{535A6811-A498-0BA3-2BE5-52623B656182}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{CC15B979-9239-00D3-0F06-26192E32E6AE}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/><one:Page ID="{3720123D-2FCD-0950-2E1C-389C62F8A52F}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/page-lifecycle/offline-creation/cold/read/page-000.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1606DF11-F2FE-0BF3-2AD6-DF46E0A08ECA}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{1606DF11-F2FE-0BF3-2AD6-DF46E0A08ECA}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{4DC55091-E701-07D1-1F57-520E4B9C14AD}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{4DC55091-E701-07D1-1F57-520E4B9C14AD}{3}{B0}" alignment="left"><one:T><![CDATA[Body 8]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{06EF31AC-FE47-058C-07BA-DF78D1983F03}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{06EF31AC-FE47-058C-07BA-DF78D1983F03}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{17}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{18}{B0}" alignment="left"><one:T><![CDATA[Body 5]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-creation/cold/read/page-001.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1F0CC9C0-C49B-0C91-08C6-AA89176EF5BF}{1}{B0}" name="Original" dateTime="2026-09-09T05:06:23.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:23.000Z" objectID="{9ADFA36E-F0EB-051E-115A-102FF772CA03}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="101.66748046875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:23.000Z" lastModifiedTime="2026-09-09T05:06:23.000Z" objectID="{9ADFA36E-F0EB-051E-115A-102FF772CA03}{24}{B0}" alignment="left"><one:T><![CDATA[Original]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2B1E474F-E067-06BF-29A3-033B4987A848}{1}{B0}" name="Original" dateTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:39:44.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:06.000Z" objectID="{3A067CBC-DC0D-05D8-02F3-B2A77833F748}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="101.66748046875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:33:06.000Z" objectID="{3A067CBC-DC0D-05D8-02F3-B2A77833F748}{24}{B0}" alignment="left"><one:T><![CDATA[Original]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-creation/cold/read/page-002.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{52FFB786-C5E9-07F5-1BE1-C5095C3F3F9B}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{52FFB786-C5E9-07F5-1BE1-C5095C3F3F9B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{6FF29CE3-FA4E-08F5-0E60-DF83A72CF205}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{6FF29CE3-FA4E-08F5-0E60-DF83A72CF205}{3}{B0}" alignment="left"><one:T><![CDATA[Body 7]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{363E253C-E20B-038F-3B6E-740FF08C2966}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{363E253C-E20B-038F-3B6E-740FF08C2966}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{8}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{9}{B0}" alignment="left"><one:T><![CDATA[Body 2]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-creation/cold/read/page-003.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{54F2D7D9-CAFE-0C6B-232B-2AE669F85BEA}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{54F2D7D9-CAFE-0C6B-232B-2AE669F85BEA}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{90835CA0-7D2C-0889-17BA-5D11B3E7B486}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{90835CA0-7D2C-0889-17BA-5D11B3E7B486}{3}{B0}" alignment="left"><one:T><![CDATA[Body 5]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{3720123D-2FCD-0950-2E1C-389C62F8A52F}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{3720123D-2FCD-0950-2E1C-389C62F8A52F}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{35}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="102.9296875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{36}{B0}" alignment="left"><one:T><![CDATA[Body 11]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-creation/cold/read/page-004.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{557FC918-74CB-09B0-19E7-3D1809D43E13}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{557FC918-74CB-09B0-19E7-3D1809D43E13}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{51D93968-7A84-0AD0-3C31-985726CCBCC2}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{51D93968-7A84-0AD0-3C31-985726CCBCC2}{3}{B0}" alignment="left"><one:T><![CDATA[Body 4]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{535A6811-A498-0BA3-2BE5-52623B656182}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{535A6811-A498-0BA3-2BE5-52623B656182}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{29}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{30}{B0}" alignment="left"><one:T><![CDATA[Body 9]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-creation/cold/read/page-005.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6CB51A17-61D4-0DBF-2C43-4CB4C9D2B6A1}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{6CB51A17-61D4-0DBF-2C43-4CB4C9D2B6A1}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{7725B82C-645F-0B2B-37FE-E1431BEF768F}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{7725B82C-645F-0B2B-37FE-E1431BEF768F}{3}{B0}" alignment="left"><one:T><![CDATA[Body 9]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5BCF0FAC-7A3F-0144-15E4-A5B7CD35716B}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{5BCF0FAC-7A3F-0144-15E4-A5B7CD35716B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{20}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{21}{B0}" alignment="left"><one:T><![CDATA[Body 6]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-creation/cold/read/page-006.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7CA0430A-8905-04E4-0455-CFE53ECEE2AA}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{7CA0430A-8905-04E4-0455-CFE53ECEE2AA}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{A1B77445-30F3-071B-2616-CBF8748C32AB}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{A1B77445-30F3-071B-2616-CBF8748C32AB}{3}{B0}" alignment="left"><one:T><![CDATA[Body 3]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{670CA118-DAE0-03FD-32C3-C0BBC132147E}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{670CA118-DAE0-03FD-32C3-C0BBC132147E}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{26}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{27}{B0}" alignment="left"><one:T><![CDATA[Body 8]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-creation/cold/read/page-007.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{87E24DEE-963B-09DA-1E68-D198DC52EDA5}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{87E24DEE-963B-09DA-1E68-D198DC52EDA5}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:27.000Z" objectID="{C3B0AAD1-B11A-0DCA-287E-D00A5BDC7028}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="102.9296875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:26.000Z" lastModifiedTime="2026-09-09T05:06:27.000Z" objectID="{C3B0AAD1-B11A-0DCA-287E-D00A5BDC7028}{3}{B0}" alignment="left"><one:T><![CDATA[Body 10]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6BD09F5F-AC47-06D8-3796-A163670C2EFD}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{6BD09F5F-AC47-06D8-3796-A163670C2EFD}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{14}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{15}{B0}" alignment="left"><one:T><![CDATA[Body 4]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-creation/cold/read/page-008.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9EE67EFB-DA69-05C0-334D-18E6DCBBE33B}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{9EE67EFB-DA69-05C0-334D-18E6DCBBE33B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{DF4AA824-AF22-0491-1F7A-0EEF3AF04369}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:25.000Z" lastModifiedTime="2026-09-09T05:06:26.000Z" objectID="{DF4AA824-AF22-0491-1F7A-0EEF3AF04369}{3}{B0}" alignment="left"><one:T><![CDATA[Body 6]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{849FA331-A262-0BFD-1424-6ABB50BCD8C7}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:39:44.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:33:06.000Z" objectID="{849FA331-A262-0BFD-1424-6ABB50BCD8C7}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:06.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{2}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:33:06.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{3}{B0}" alignment="left"><one:T><![CDATA[Body 0]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-creation/cold/read/page-009.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{AC1E7B39-43AC-0370-2960-486A10D01E43}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:27.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:27.000Z" lastModifiedTime="2026-09-09T05:06:27.000Z" objectID="{AC1E7B39-43AC-0370-2960-486A10D01E43}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:27.000Z" objectID="{423E0C00-FA84-099F-113B-397CAEE28758}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="102.9296875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:27.000Z" lastModifiedTime="2026-09-09T05:06:27.000Z" objectID="{423E0C00-FA84-099F-113B-397CAEE28758}{3}{B0}" alignment="left"><one:T><![CDATA[Body 11]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8EE0AC66-288F-02EF-1E6D-FC56C315C68F}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{8EE0AC66-288F-02EF-1E6D-FC56C315C68F}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{24}{B0}" alignment="left"><one:T><![CDATA[Body 7]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-creation/cold/read/page-010.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{AC84259B-D9B6-0A59-2748-93C35602B0BD}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:06:24.000Z" objectID="{AC84259B-D9B6-0A59-2748-93C35602B0BD}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:24.000Z" objectID="{2A37A85A-3C9C-0CBE-2D9A-253D334E6301}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:06:24.000Z" objectID="{2A37A85A-3C9C-0CBE-2D9A-253D334E6301}{3}{B0}" alignment="left"><one:T><![CDATA[Body 1]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{91186C32-65B4-0578-030C-10DA1738633A}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:06.000Z" lastModifiedTime="2026-09-26T06:33:06.000Z" objectID="{91186C32-65B4-0578-030C-10DA1738633A}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{5}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{6}{B0}" alignment="left"><one:T><![CDATA[Body 1]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-creation/cold/read/page-011.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CC3DEC87-5707-084C-0528-649AB24AD8B7}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:06:24.000Z" objectID="{CC3DEC87-5707-084C-0528-649AB24AD8B7}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{DD7A7A6D-CF78-0C0A-3512-66C69436AC88}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:06:25.000Z" objectID="{DD7A7A6D-CF78-0C0A-3512-66C69436AC88}{3}{B0}" alignment="left"><one:T><![CDATA[Body 2]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CC15B979-9239-00D3-0F06-26192E32E6AE}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{CC15B979-9239-00D3-0F06-26192E32E6AE}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{32}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="102.9296875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:08.000Z" lastModifiedTime="2026-09-26T06:33:08.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{33}{B0}" alignment="left"><one:T><![CDATA[Body 10]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-creation/cold/read/page-012.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D3E5FB89-79C3-05BA-1805-E25CE31ED175}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:08:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:06:24.000Z" objectID="{D3E5FB89-79C3-05BA-1805-E25CE31ED175}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T05:06:24.000Z" objectID="{72A18D56-4556-035E-3AD5-CF9B88EA399E}{1}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T05:06:24.000Z" lastModifiedTime="2026-09-09T05:06:24.000Z" objectID="{72A18D56-4556-035E-3AD5-CF9B88EA399E}{3}{B0}" alignment="left"><one:T><![CDATA[Body 0]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{DAF8F05F-4D73-0BFF-1A5C-60D6A0F48D49}{1}{B0}" name="Same 🦋 é" dateTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:39:45.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Offline author" lastModifiedBy="Offline author" creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{DAF8F05F-4D73-0BFF-1A5C-60D6A0F48D49}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same &#129419; é]]></one:T></one:OE></one:Title><one:Outline author="Offline author" lastModifiedBy="Offline author" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{11}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="97.35448455810547" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:07.000Z" lastModifiedTime="2026-09-26T06:33:07.000Z" objectID="{FB857531-64AC-0D81-2D4A-97656A27EA6B}{12}{B0}" alignment="left"><one:T><![CDATA[Body 3]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-creation/provenance.json+3-3
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1{1{
2 "run": {2 "run": {
3 "notebook": "/Users/clo/dev/one/evidence/m10/offline-page-candidate",3 "notebook": "/Users/clo/dev/one/evidence/titles/offline-creation",
4 "expected_pages": 13,4 "expected_pages": 13,
5 "author": null,5 "author": null,
6 "author_timeout_seconds": 600,6 "author_timeout_seconds": 600,
...@@ -18,8 +18,8 @@...@@ -18,8 +18,8 @@
18 }18 }
19 },19 },
20 "machine": {20 "machine": {
21 "name": "m6-cc15a5ee",21 "name": "m6-4d84f1a7",
22 "hostname": "ONE-M6-CC15A5EE"22 "hostname": "ONE-M6-4D84F1A7"
23 },23 },
24 "teardown": {24 "teardown": {
25 "absent": true25 "absent": true
corpus/page-lifecycle/offline-edits/provenance.json+12-6
...@@ -633,7 +633,7 @@...@@ -633,7 +633,7 @@
633 },633 },
634 "twelve-clients": {634 "twelve-clients": {
635 "run": {635 "run": {
636 "notebook": "/private/tmp/m33/page-clients2/candidate",636 "notebook": "/Users/clo/dev/one/evidence/titles/twelve-clients",
637 "expected_pages": 37,637 "expected_pages": 37,
638 "author": null,638 "author": null,
639 "author_timeout_seconds": 600,639 "author_timeout_seconds": 600,
...@@ -651,18 +651,24 @@...@@ -651,18 +651,24 @@
651 }651 }
652 },652 },
653 "machine": {653 "machine": {
654 "name": "m6-16c2e4d4",654 "name": "m6-8eabb42e",
655 "hostname": "ONE-M6-16C2E4D4"655 "hostname": "ONE-M6-8EABB42E"
656 },656 },
657 "teardown": {657 "teardown": {
658 "absent": true658 "absent": true
659 },659 },
660 "source": [660 "source": [
661 {
662 "path": "manifest.json",
663 "bytes": 292907,
664 "sha256": "fea1d64b22da84d023d4c9014175f8e92a13acba099c7a1a3f8eae30d6c6407f",
665 "mtime_ns": 1790404395466294564
666 },
661 {667 {
662 "path": "pages.one",668 "path": "pages.one",
663 "bytes": 155344,669 "bytes": 152360,
664 "sha256": "623ade8b5ee6fc837ae63d5293791039ecd17da9cb8bc5e243ea20cadf37958c",670 "sha256": "302b6d2d31bc8f539a2217aed3391973678576b0aea1ce82c6e21760d6402e14",
665 "mtime_ns": 1789124063040528117671 "mtime_ns": 1790404395457867619
666 }672 }
667 ]673 ]
668 },674 },
corpus/page-lifecycle/offline-edits/twelve-clients/candidate/manifest.json+1996-1204
...@@ -1,151 +1,151 @@...@@ -1,151 +1,151 @@
1{1{
2 "expected_order": [2 "expected_order": [
3 [3 [
4 "{869D5929-6E5E-4EDB-9639-4C56977DD447},1",4 "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1",
5 15 1
6 ],6 ],
7 [7 [
8 "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1",8 "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1",
9 19 1
10 ],10 ],
11 [11 [
12 "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1",12 "{74492C4A-164B-40E9-B297-4D3B4685347D},1",
13 113 1
14 ],14 ],
15 [15 [
16 "{C837A033-F055-4455-B45F-17D82016921C},1",16 "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1",
17 217 2
18 ],18 ],
19 [19 [
20 "{57E835EA-2799-4118-B55C-7BB1E51C6941},1",20 "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1",
21 121 1
22 ],22 ],
23 [23 [
24 "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1",24 "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1",
25 125 1
26 ],26 ],
27 [27 [
28 "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1",28 "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1",
29 229 2
30 ],30 ],
31 [31 [
32 "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1",32 "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1",
33 133 1
34 ],34 ],
35 [35 [
36 "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1",36 "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1",
37 137 1
38 ],38 ],
39 [39 [
40 "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1",40 "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1",
41 241 2
42 ],42 ],
43 [43 [
44 "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1",44 "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1",
45 145 1
46 ],46 ],
47 [47 [
48 "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1",48 "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1",
49 149 1
50 ],50 ],
51 [51 [
52 "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1",52 "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1",
53 253 2
54 ],54 ],
55 [55 [
56 "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1",56 "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1",
57 157 1
58 ],58 ],
59 [59 [
60 "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1",60 "{AE89305A-73F4-46B5-B41D-18884685D896},1",
61 161 1
62 ],62 ],
63 [63 [
64 "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1",64 "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1",
65 265 2
66 ],66 ],
67 [67 [
68 "{7E9041BD-301B-4B5B-9456-1C92050E4914},1",68 "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1",
69 169 1
70 ],70 ],
71 [71 [
72 "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1",72 "{42010094-E377-4A97-9395-AD45808D0552},1",
73 173 1
74 ],74 ],
75 [75 [
76 "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1",76 "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1",
77 277 2
78 ],78 ],
79 [79 [
80 "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1",80 "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1",
81 181 1
82 ],82 ],
83 [83 [
84 "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1",84 "{439B80E0-9D4C-4A2F-9163-4E301491364D},1",
85 185 1
86 ],86 ],
87 [87 [
88 "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1",88 "{A459C474-E6EB-4BE5-9193-271799B2860B},1",
89 289 2
90 ],90 ],
91 [91 [
92 "{7F6702B3-C977-436F-82AF-403E2BE950E7},1",92 "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1",
93 193 1
94 ],94 ],
95 [95 [
96 "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1",96 "{4ED14CEE-8560-4125-B488-9945C116741E},1",
97 197 1
98 ],98 ],
99 [99 [
100 "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1",100 "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1",
101 2101 2
102 ],102 ],
103 [103 [
104 "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1",104 "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1",
105 1105 1
106 ],106 ],
107 [107 [
108 "{754FA957-C432-4502-A5CF-EE44449462C0},1",108 "{9B755368-1D49-4D7E-8D33-6DC536F57848},1",
109 1109 1
110 ],110 ],
111 [111 [
112 "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1",112 "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1",
113 2113 2
114 ],114 ],
115 [115 [
116 "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1",116 "{C6E54364-6CD0-4764-8180-873030B9EA53},1",
117 1117 1
118 ],118 ],
119 [119 [
120 "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1",120 "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1",
121 1121 1
122 ],122 ],
123 [123 [
124 "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1",124 "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1",
125 2125 2
126 ],126 ],
127 [127 [
128 "{189EA744-BE13-467A-95BA-2667A134C0B0},1",128 "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1",
129 1129 1
130 ],130 ],
131 [131 [
132 "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1",132 "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1",
133 1133 1
134 ],134 ],
135 [135 [
136 "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1",136 "{00B10C58-6649-428D-A081-3EBBA6E88E97},1",
137 2137 2
138 ],138 ],
139 [139 [
140 "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1",140 "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1",
141 1141 1
142 ],142 ],
143 [143 [
144 "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1",144 "{58E99482-710A-4938-8CFB-88A1C9B946C9},1",
145 1145 1
146 ],146 ],
147 [147 [
148 "{2B736B19-2D9C-4E76-A732-478D533D717A},1",148 "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1",
149 2149 2
150 ]150 ]
151 ],151 ],
...@@ -158,215 +158,215 @@...@@ -158,215 +158,215 @@
158 "edits": [158 "edits": [
159 {159 {
160 "guid": [160 "guid": [
161 86,161 155,
162 0,162 49,
163 111,163 40,
164 192,164 55,
165 110,
166 61,
167 248,
168 78,
169 186,
170 245,
171 33,
172 60,
173 24,
165 138,174 138,
166 101,175 51,
167 192,176 39
168 76,
169 185,
170 152,
171 225,
172 226,
173 79,
174 192,
175 46,
176 40
177 ],177 ],
178 "level": 1,178 "level": 1,
179 "position": {179 "position": {
180 "Before": "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1"180 "Before": "{74492C4A-164B-40E9-B297-4D3B4685347D},1"
181 },181 },
182 "space": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1"182 "space": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1"
183 },183 },
184 {184 {
185 "guid": [185 "guid": [
186 133,186 124,
187 52,187 69,
188 164,
189 134,
190 185,
191 207,
192 146,
188 67,193 67,
189 104,194 144,
190 247,195 255,
191 13,196 194,
192 120,197 10,
193 79,198 29,
194 190,199 94,
195 78,200 178,
196 126,201 200
197 126,
198 12,
199 116,
200 230,
201 238
202 ],202 ],
203 "level": 2,203 "level": 2,
204 "position": "Keep",204 "position": "Keep",
205 "space": "{C837A033-F055-4455-B45F-17D82016921C},1"205 "space": "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1"
206 }206 }
207 ],207 ],
208 "observed": [208 "observed": [
209 [209 [
210 "{869D5929-6E5E-4EDB-9639-4C56977DD447},1",210 "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1",
211 1211 1
212 ],212 ],
213 [213 [
214 "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1",214 "{74492C4A-164B-40E9-B297-4D3B4685347D},1",
215 1215 1
216 ],216 ],
217 [217 [
218 "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1",218 "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1",
219 1219 1
220 ],220 ],
221 [221 [
222 "{C837A033-F055-4455-B45F-17D82016921C},1",222 "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1",
223 1223 1
224 ],224 ],
225 [225 [
226 "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1",226 "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1",
227 1227 1
228 ],228 ],
229 [229 [
230 "{57E835EA-2799-4118-B55C-7BB1E51C6941},1",230 "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1",
231 1231 1
232 ],232 ],
233 [233 [
234 "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1",234 "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1",
235 1235 1
236 ],236 ],
237 [237 [
238 "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1",238 "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1",
239 1239 1
240 ],240 ],
241 [241 [
242 "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1",242 "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1",
243 1243 1
244 ],244 ],
245 [245 [
246 "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1",246 "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1",
247 1247 1
248 ],248 ],
249 [249 [
250 "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1",250 "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1",
251 1251 1
252 ],252 ],
253 [253 [
254 "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1",254 "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1",
255 1255 1
256 ],256 ],
257 [257 [
258 "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1",258 "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1",
259 1259 1
260 ],260 ],
261 [261 [
262 "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1",262 "{AE89305A-73F4-46B5-B41D-18884685D896},1",
263 1263 1
264 ],264 ],
265 [265 [
266 "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1",266 "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1",
267 1267 1
268 ],268 ],
269 [269 [
270 "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1",270 "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1",
271 1271 1
272 ],272 ],
273 [273 [
274 "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1",274 "{42010094-E377-4A97-9395-AD45808D0552},1",
275 1275 1
276 ],276 ],
277 [277 [
278 "{7E9041BD-301B-4B5B-9456-1C92050E4914},1",278 "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1",
279 1279 1
280 ],280 ],
281 [281 [
282 "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1",282 "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1",
283 1283 1
284 ],284 ],
285 [285 [
286 "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1",286 "{439B80E0-9D4C-4A2F-9163-4E301491364D},1",
287 1287 1
288 ],288 ],
289 [289 [
290 "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1",290 "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1",
291 1291 1
292 ],292 ],
293 [293 [
294 "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1",294 "{A459C474-E6EB-4BE5-9193-271799B2860B},1",
295 1295 1
296 ],296 ],
297 [297 [
298 "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1",298 "{4ED14CEE-8560-4125-B488-9945C116741E},1",
299 1299 1
300 ],300 ],
301 [301 [
302 "{7F6702B3-C977-436F-82AF-403E2BE950E7},1",302 "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1",
303 1303 1
304 ],304 ],
305 [305 [
306 "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1",306 "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1",
307 1307 1
308 ],308 ],
309 [309 [
310 "{754FA957-C432-4502-A5CF-EE44449462C0},1",310 "{9B755368-1D49-4D7E-8D33-6DC536F57848},1",
311 1311 1
312 ],312 ],
313 [313 [
314 "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1",314 "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1",
315 1315 1
316 ],316 ],
317 [317 [
318 "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1",318 "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1",
319 1319 1
320 ],320 ],
321 [321 [
322 "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1",322 "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1",
323 1323 1
324 ],324 ],
325 [325 [
326 "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1",326 "{C6E54364-6CD0-4764-8180-873030B9EA53},1",
327 1327 1
328 ],328 ],
329 [329 [
330 "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1",330 "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1",
331 1331 1
332 ],332 ],
333 [333 [
334 "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1",334 "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1",
335 1335 1
336 ],336 ],
337 [337 [
338 "{189EA744-BE13-467A-95BA-2667A134C0B0},1",338 "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1",
339 1339 1
340 ],340 ],
341 [341 [
342 "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1",342 "{00B10C58-6649-428D-A081-3EBBA6E88E97},1",
343 1343 1
344 ],344 ],
345 [345 [
346 "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1",346 "{58E99482-710A-4938-8CFB-88A1C9B946C9},1",
347 1347 1
348 ],348 ],
349 [349 [
350 "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1",350 "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1",
351 1351 1
352 ],352 ],
353 [353 [
354 "{2B736B19-2D9C-4E76-A732-478D533D717A},1",354 "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1",
355 1355 1
356 ]356 ]
357 ]357 ]
358 }358 }
359 },359 },
360 "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1"360 "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1"
361 },361 },
362 {362 {
363 "id": 2,363 "id": 2,
364 "operation": {364 "operation": {
365 "Page": {365 "Page": {
366 "after": {366 "after": {
367 "created": 134335976560000000,367 "created": 134348779890000000,
368 "definitions": {368 "definitions": {
369 "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},18": {369 "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},18": {
370 "format": {370 "format": {
371 "alignment": null,371 "alignment": null,
372 "bold": null,372 "bold": null,
...@@ -383,6 +383,7 @@...@@ -383,6 +383,7 @@
383 "line_spacing": null,383 "line_spacing": null,
384 "list_spacing": null,384 "list_spacing": null,
385 "math": null,385 "math": null,
386 "math_object": null,
386 "rtl": null,387 "rtl": null,
387 "space_after": null,388 "space_after": null,
388 "space_before": null,389 "space_before": null,
...@@ -397,6 +398,24 @@...@@ -397,6 +398,24 @@
397 }398 }
398 }399 }
399 },400 },
401 "identity": [
402 61,
403 83,
404 255,
405 218,
406 139,
407 96,
408 53,
409 71,
410 186,
411 29,
412 35,
413 84,
414 232,
415 203,
416 138,
417 105
418 ],
400 "margin_origin": [419 "margin_origin": [
401 0.0,420 0.0,
402 0.0421 0.0
...@@ -404,7 +423,7 @@...@@ -404,7 +423,7 @@
404 "objects": [423 "objects": [
405 {424 {
406 "Outline": {425 "Outline": {
407 "id": "{3517C5BC-B40B-45E6-8324-BAB9403F7F3B},0",426 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},2",
408 "indents": [],427 "indents": [],
409 "layout": {428 "layout": {
410 "max_height": null,429 "max_height": null,
...@@ -421,7 +440,7 @@...@@ -421,7 +440,7 @@
421 "content": {440 "content": {
422 "Text": {441 "Text": {
423 "date_field": null,442 "date_field": null,
424 "id": "{D76D17C9-C3D7-49A2-A78E-305604ABA8AF},0",443 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},1",
425 "tags": [],444 "tags": [],
426 "text": {445 "text": {
427 "spans": [446 "spans": [
...@@ -443,6 +462,7 @@...@@ -443,6 +462,7 @@
443 "line_spacing": null,462 "line_spacing": null,
444 "list_spacing": null,463 "list_spacing": null,
445 "math": null,464 "math": null,
465 "math_object": null,
446 "rtl": null,466 "rtl": null,
447 "space_after": null,467 "space_after": null,
448 "space_before": null,468 "space_before": null,
...@@ -473,6 +493,7 @@...@@ -473,6 +493,7 @@
473 "line_spacing": null,493 "line_spacing": null,
474 "list_spacing": null,494 "list_spacing": null,
475 "math": null,495 "math": null,
496 "math_object": null,
476 "rtl": null,497 "rtl": null,
477 "space_after": null,498 "space_after": null,
478 "space_before": null,499 "space_before": null,
...@@ -481,9 +502,13 @@...@@ -481,9 +502,13 @@
481 "superscript": null,502 "superscript": null,
482 "underline": null503 "underline": null
483 },504 },
484 "id": "{1A80E3C3-180F-42AB-B946-1A365F043826},0",505 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},3",
485 "level": 1,506 "level": 1,
486 "lists": [],507 "lists": [],
508 "media": {
509 "recordings": [],
510 "time_ms": null
511 },
487 "parent": null,512 "parent": null,
488 "style": null,513 "style": null,
489 "tags": []514 "tags": []
...@@ -496,7 +521,7 @@...@@ -496,7 +521,7 @@
496 {521 {
497 "Title": {522 "Title": {
498 "date": null,523 "date": null,
499 "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},13",524 "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},13",
500 "layout": {525 "layout": {
501 "max_height": null,526 "max_height": null,
502 "max_width": null,527 "max_width": null,
...@@ -507,24 +532,29 @@...@@ -507,24 +532,29 @@
507 },532 },
508 "outlines": [533 "outlines": [
509 {534 {
510 "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},14",535 "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},14",
511 "indents": [],536 "indents": [
537 18.0,
538 0.0,
539 27.0,
540 27.0
541 ],
512 "layout": {542 "layout": {
513 "max_height": null,543 "max_height": 21.600000381469727,
514 "max_width": null,544 "max_width": null,
515 "reserved_width": null,545 "reserved_width": null,
516 "width_set_by_user": null,546 "width_set_by_user": null,
517 "x": null,547 "x": null,
518 "y": null548 "y": null
519 },549 },
520 "min_width": null,550 "min_width": 162.0,
521 "paragraphs": [551 "paragraphs": [
522 {552 {
523 "collapsed": false,553 "collapsed": false,
524 "content": {554 "content": {
525 "Text": {555 "Text": {
526 "date_field": null,556 "date_field": null,
527 "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},16",557 "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},16",
528 "tags": [],558 "tags": [],
529 "text": {559 "text": {
530 "spans": [560 "spans": [
...@@ -546,6 +576,7 @@...@@ -546,6 +576,7 @@
546 "line_spacing": null,576 "line_spacing": null,
547 "list_spacing": null,577 "list_spacing": null,
548 "math": null,578 "math": null,
579 "math_object": null,
549 "rtl": null,580 "rtl": null,
550 "space_after": null,581 "space_after": null,
551 "space_before": null,582 "space_before": null,
...@@ -576,6 +607,7 @@...@@ -576,6 +607,7 @@
576 "line_spacing": null,607 "line_spacing": null,
577 "list_spacing": null,608 "list_spacing": null,
578 "math": null,609 "math": null,
610 "math_object": null,
579 "rtl": null,611 "rtl": null,
580 "space_after": null,612 "space_after": null,
581 "space_before": null,613 "space_before": null,
...@@ -584,15 +616,19 @@...@@ -584,15 +616,19 @@
584 "superscript": null,616 "superscript": null,
585 "underline": null617 "underline": null
586 },618 },
587 "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},15",619 "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},15",
588 "level": 1,620 "level": 1,
589 "lists": [],621 "lists": [],
622 "media": {
623 "recordings": [],
624 "time_ms": null
625 },
590 "parent": null,626 "parent": null,
591 "style": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},18",627 "style": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},18",
592 "tags": []628 "tags": []
593 }629 }
594 ],630 ],
595 "title": false,631 "title": true,
596 "unsupported": []632 "unsupported": []
597 }633 }
598 ]634 ]
...@@ -603,9 +639,9 @@...@@ -603,9 +639,9 @@
603 },639 },
604 "author": "Author",640 "author": "Author",
605 "before": {641 "before": {
606 "created": 134335976560000000,642 "created": 134348779890000000,
607 "definitions": {643 "definitions": {
608 "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},18": {644 "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},18": {
609 "format": {645 "format": {
610 "alignment": null,646 "alignment": null,
611 "bold": null,647 "bold": null,
...@@ -622,6 +658,7 @@...@@ -622,6 +658,7 @@
622 "line_spacing": null,658 "line_spacing": null,
623 "list_spacing": null,659 "list_spacing": null,
624 "math": null,660 "math": null,
661 "math_object": null,
625 "rtl": null,662 "rtl": null,
626 "space_after": null,663 "space_after": null,
627 "space_before": null,664 "space_before": null,
...@@ -636,6 +673,24 @@...@@ -636,6 +673,24 @@
636 }673 }
637 }674 }
638 },675 },
676 "identity": [
677 61,
678 83,
679 255,
680 218,
681 139,
682 96,
683 53,
684 71,
685 186,
686 29,
687 35,
688 84,
689 232,
690 203,
691 138,
692 105
693 ],
639 "margin_origin": [694 "margin_origin": [
640 0.0,695 0.0,
641 0.0696 0.0
...@@ -644,7 +699,7 @@...@@ -644,7 +699,7 @@
644 {699 {
645 "Title": {700 "Title": {
646 "date": null,701 "date": null,
647 "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},13",702 "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},13",
648 "layout": {703 "layout": {
649 "max_height": null,704 "max_height": null,
650 "max_width": null,705 "max_width": null,
...@@ -655,24 +710,29 @@...@@ -655,24 +710,29 @@
655 },710 },
656 "outlines": [711 "outlines": [
657 {712 {
658 "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},14",713 "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},14",
659 "indents": [],714 "indents": [
715 18.0,
716 0.0,
717 27.0,
718 27.0
719 ],
660 "layout": {720 "layout": {
661 "max_height": null,721 "max_height": 21.600000381469727,
662 "max_width": null,722 "max_width": null,
663 "reserved_width": null,723 "reserved_width": null,
664 "width_set_by_user": null,724 "width_set_by_user": null,
665 "x": null,725 "x": null,
666 "y": null726 "y": null
667 },727 },
668 "min_width": null,728 "min_width": 162.0,
669 "paragraphs": [729 "paragraphs": [
670 {730 {
671 "collapsed": false,731 "collapsed": false,
672 "content": {732 "content": {
673 "Text": {733 "Text": {
674 "date_field": null,734 "date_field": null,
675 "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},16",735 "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},16",
676 "tags": [],736 "tags": [],
677 "text": {737 "text": {
678 "spans": [738 "spans": [
...@@ -694,6 +754,7 @@...@@ -694,6 +754,7 @@
694 "line_spacing": null,754 "line_spacing": null,
695 "list_spacing": null,755 "list_spacing": null,
696 "math": null,756 "math": null,
757 "math_object": null,
697 "rtl": null,758 "rtl": null,
698 "space_after": null,759 "space_after": null,
699 "space_before": null,760 "space_before": null,
...@@ -724,6 +785,7 @@...@@ -724,6 +785,7 @@
724 "line_spacing": null,785 "line_spacing": null,
725 "list_spacing": null,786 "list_spacing": null,
726 "math": null,787 "math": null,
788 "math_object": null,
727 "rtl": null,789 "rtl": null,
728 "space_after": null,790 "space_after": null,
729 "space_before": null,791 "space_before": null,
...@@ -732,15 +794,19 @@...@@ -732,15 +794,19 @@
732 "superscript": null,794 "superscript": null,
733 "underline": null795 "underline": null
734 },796 },
735 "id": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},15",797 "id": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},15",
736 "level": 1,798 "level": 1,
737 "lists": [],799 "lists": [],
800 "media": {
801 "recordings": [],
802 "time_ms": null
803 },
738 "parent": null,804 "parent": null,
739 "style": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},18",805 "style": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},18",
740 "tags": []806 "tags": []
741 }807 }
742 ],808 ],
743 "title": false,809 "title": true,
744 "unsupported": []810 "unsupported": []
745 }811 }
746 ]812 ]
...@@ -751,7 +817,7 @@...@@ -751,7 +817,7 @@
751 }817 }
752 }818 }
753 },819 },
754 "space": "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1"820 "space": "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1"
755 }821 }
756 ],822 ],
757 [823 [
...@@ -762,215 +828,215 @@...@@ -762,215 +828,215 @@
762 "edits": [828 "edits": [
763 {829 {
764 "guid": [830 "guid": [
765 183,831 17,
766 11,
767 130,
768 190,
769 141,
770 83,
771 119,
772 77,
773 133,
774 112,
775 123,
776 250,
777 37,
778 199,
779 26,832 26,
780 252833 44,
834 166,
835 129,
836 151,
837 120,
838 77,
839 170,
840 191,
841 124,
842 194,
843 153,
844 11,
845 230,
846 226
781 ],847 ],
782 "level": 1,848 "level": 1,
783 "position": {849 "position": {
784 "Before": "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1"850 "Before": "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1"
785 },851 },
786 "space": "{57E835EA-2799-4118-B55C-7BB1E51C6941},1"852 "space": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1"
787 },853 },
788 {854 {
789 "guid": [855 "guid": [
790 252,856 6,
791 97,857 151,
792 42,858 55,
793 186,859 70,
794 167,860 228,
795 236,861 106,
796 97,862 247,
863 64,
864 149,
865 253,
866 46,
867 250,
868 242,
869 9,
797 65,870 65,
798 143,871 14
799 137,
800 54,
801 20,
802 165,
803 72,
804 170,
805 170
806 ],872 ],
807 "level": 2,873 "level": 2,
808 "position": "Keep",874 "position": "Keep",
809 "space": "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1"875 "space": "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1"
810 }876 }
811 ],877 ],
812 "observed": [878 "observed": [
813 [879 [
814 "{869D5929-6E5E-4EDB-9639-4C56977DD447},1",880 "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1",
815 1881 1
816 ],882 ],
817 [883 [
818 "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1",884 "{74492C4A-164B-40E9-B297-4D3B4685347D},1",
819 1885 1
820 ],886 ],
821 [887 [
822 "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1",888 "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1",
823 1889 1
824 ],890 ],
825 [891 [
826 "{C837A033-F055-4455-B45F-17D82016921C},1",892 "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1",
827 1893 1
828 ],894 ],
829 [895 [
830 "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1",896 "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1",
831 1897 1
832 ],898 ],
833 [899 [
834 "{57E835EA-2799-4118-B55C-7BB1E51C6941},1",900 "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1",
835 1901 1
836 ],902 ],
837 [903 [
838 "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1",904 "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1",
839 1905 1
840 ],906 ],
841 [907 [
842 "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1",908 "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1",
843 1909 1
844 ],910 ],
845 [911 [
846 "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1",912 "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1",
847 1913 1
848 ],914 ],
849 [915 [
850 "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1",916 "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1",
851 1917 1
852 ],918 ],
853 [919 [
854 "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1",920 "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1",
855 1921 1
856 ],922 ],
857 [923 [
858 "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1",924 "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1",
859 1925 1
860 ],926 ],
861 [927 [
862 "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1",928 "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1",
863 1929 1
864 ],930 ],
865 [931 [
866 "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1",932 "{AE89305A-73F4-46B5-B41D-18884685D896},1",
867 1933 1
868 ],934 ],
869 [935 [
870 "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1",936 "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1",
871 1937 1
872 ],938 ],
873 [939 [
874 "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1",940 "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1",
875 1941 1
876 ],942 ],
877 [943 [
878 "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1",944 "{42010094-E377-4A97-9395-AD45808D0552},1",
879 1945 1
880 ],946 ],
881 [947 [
882 "{7E9041BD-301B-4B5B-9456-1C92050E4914},1",948 "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1",
883 1949 1
884 ],950 ],
885 [951 [
886 "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1",952 "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1",
887 1953 1
888 ],954 ],
889 [955 [
890 "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1",956 "{439B80E0-9D4C-4A2F-9163-4E301491364D},1",
891 1957 1
892 ],958 ],
893 [959 [
894 "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1",960 "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1",
895 1961 1
896 ],962 ],
897 [963 [
898 "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1",964 "{A459C474-E6EB-4BE5-9193-271799B2860B},1",
899 1965 1
900 ],966 ],
901 [967 [
902 "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1",968 "{4ED14CEE-8560-4125-B488-9945C116741E},1",
903 1969 1
904 ],970 ],
905 [971 [
906 "{7F6702B3-C977-436F-82AF-403E2BE950E7},1",972 "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1",
907 1973 1
908 ],974 ],
909 [975 [
910 "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1",976 "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1",
911 1977 1
912 ],978 ],
913 [979 [
914 "{754FA957-C432-4502-A5CF-EE44449462C0},1",980 "{9B755368-1D49-4D7E-8D33-6DC536F57848},1",
915 1981 1
916 ],982 ],
917 [983 [
918 "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1",984 "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1",
919 1985 1
920 ],986 ],
921 [987 [
922 "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1",988 "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1",
923 1989 1
924 ],990 ],
925 [991 [
926 "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1",992 "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1",
927 1993 1
928 ],994 ],
929 [995 [
930 "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1",996 "{C6E54364-6CD0-4764-8180-873030B9EA53},1",
931 1997 1
932 ],998 ],
933 [999 [
934 "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1",1000 "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1",
935 11001 1
936 ],1002 ],
937 [1003 [
938 "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1",1004 "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1",
939 11005 1
940 ],1006 ],
941 [1007 [
942 "{189EA744-BE13-467A-95BA-2667A134C0B0},1",1008 "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1",
943 11009 1
944 ],1010 ],
945 [1011 [
946 "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1",1012 "{00B10C58-6649-428D-A081-3EBBA6E88E97},1",
947 11013 1
948 ],1014 ],
949 [1015 [
950 "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1",1016 "{58E99482-710A-4938-8CFB-88A1C9B946C9},1",
951 11017 1
952 ],1018 ],
953 [1019 [
954 "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1",1020 "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1",
955 11021 1
956 ],1022 ],
957 [1023 [
958 "{2B736B19-2D9C-4E76-A732-478D533D717A},1",1024 "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1",
959 11025 1
960 ]1026 ]
961 ]1027 ]
962 }1028 }
963 },1029 },
964 "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1"1030 "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1"
965 },1031 },
966 {1032 {
967 "id": 2,1033 "id": 2,
968 "operation": {1034 "operation": {
969 "Page": {1035 "Page": {
970 "after": {1036 "after": {
971 "created": 134335976560000000,1037 "created": 134348779890000000,
972 "definitions": {1038 "definitions": {
973 "{57E835EA-2799-4118-B55C-7BB1E51C6941},18": {1039 "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},18": {
974 "format": {1040 "format": {
975 "alignment": null,1041 "alignment": null,
976 "bold": null,1042 "bold": null,
...@@ -987,6 +1053,7 @@...@@ -987,6 +1053,7 @@
987 "line_spacing": null,1053 "line_spacing": null,
988 "list_spacing": null,1054 "list_spacing": null,
989 "math": null,1055 "math": null,
1056 "math_object": null,
990 "rtl": null,1057 "rtl": null,
991 "space_after": null,1058 "space_after": null,
992 "space_before": null,1059 "space_before": null,
...@@ -1001,6 +1068,24 @@...@@ -1001,6 +1068,24 @@
1001 }1068 }
1002 }1069 }
1003 },1070 },
1071 "identity": [
1072 249,
1073 165,
1074 255,
1075 49,
1076 184,
1077 193,
1078 45,
1079 65,
1080 162,
1081 214,
1082 122,
1083 204,
1084 60,
1085 193,
1086 111,
1087 208
1088 ],
1004 "margin_origin": [1089 "margin_origin": [
1005 0.0,1090 0.0,
1006 0.01091 0.0
...@@ -1008,7 +1093,7 @@...@@ -1008,7 +1093,7 @@
1008 "objects": [1093 "objects": [
1009 {1094 {
1010 "Outline": {1095 "Outline": {
1011 "id": "{0817794A-7E1B-41EF-A811-A0F65EE1CE99},0",1096 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},5",
1012 "indents": [],1097 "indents": [],
1013 "layout": {1098 "layout": {
1014 "max_height": null,1099 "max_height": null,
...@@ -1025,7 +1110,7 @@...@@ -1025,7 +1110,7 @@
1025 "content": {1110 "content": {
1026 "Text": {1111 "Text": {
1027 "date_field": null,1112 "date_field": null,
1028 "id": "{BB519837-B3F7-4C9B-A428-3D9696CA0070},0",1113 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},4",
1029 "tags": [],1114 "tags": [],
1030 "text": {1115 "text": {
1031 "spans": [1116 "spans": [
...@@ -1047,6 +1132,7 @@...@@ -1047,6 +1132,7 @@
1047 "line_spacing": null,1132 "line_spacing": null,
1048 "list_spacing": null,1133 "list_spacing": null,
1049 "math": null,1134 "math": null,
1135 "math_object": null,
1050 "rtl": null,1136 "rtl": null,
1051 "space_after": null,1137 "space_after": null,
1052 "space_before": null,1138 "space_before": null,
...@@ -1077,6 +1163,7 @@...@@ -1077,6 +1163,7 @@
1077 "line_spacing": null,1163 "line_spacing": null,
1078 "list_spacing": null,1164 "list_spacing": null,
1079 "math": null,1165 "math": null,
1166 "math_object": null,
1080 "rtl": null,1167 "rtl": null,
1081 "space_after": null,1168 "space_after": null,
1082 "space_before": null,1169 "space_before": null,
...@@ -1085,9 +1172,13 @@...@@ -1085,9 +1172,13 @@
1085 "superscript": null,1172 "superscript": null,
1086 "underline": null1173 "underline": null
1087 },1174 },
1088 "id": "{88AA1A5C-2020-4249-BF25-62D5B4957CC6},0",1175 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},6",
1089 "level": 1,1176 "level": 1,
1090 "lists": [],1177 "lists": [],
1178 "media": {
1179 "recordings": [],
1180 "time_ms": null
1181 },
1091 "parent": null,1182 "parent": null,
1092 "style": null,1183 "style": null,
1093 "tags": []1184 "tags": []
...@@ -1100,7 +1191,7 @@...@@ -1100,7 +1191,7 @@
1100 {1191 {
1101 "Title": {1192 "Title": {
1102 "date": null,1193 "date": null,
1103 "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},13",1194 "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},13",
1104 "layout": {1195 "layout": {
1105 "max_height": null,1196 "max_height": null,
1106 "max_width": null,1197 "max_width": null,
...@@ -1111,24 +1202,29 @@...@@ -1111,24 +1202,29 @@
1111 },1202 },
1112 "outlines": [1203 "outlines": [
1113 {1204 {
1114 "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},14",1205 "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},14",
1115 "indents": [],1206 "indents": [
1207 18.0,
1208 0.0,
1209 27.0,
1210 27.0
1211 ],
1116 "layout": {1212 "layout": {
1117 "max_height": null,1213 "max_height": 21.600000381469727,
1118 "max_width": null,1214 "max_width": null,
1119 "reserved_width": null,1215 "reserved_width": null,
1120 "width_set_by_user": null,1216 "width_set_by_user": null,
1121 "x": null,1217 "x": null,
1122 "y": null1218 "y": null
1123 },1219 },
1124 "min_width": null,1220 "min_width": 162.0,
1125 "paragraphs": [1221 "paragraphs": [
1126 {1222 {
1127 "collapsed": false,1223 "collapsed": false,
1128 "content": {1224 "content": {
1129 "Text": {1225 "Text": {
1130 "date_field": null,1226 "date_field": null,
1131 "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},16",1227 "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},16",
1132 "tags": [],1228 "tags": [],
1133 "text": {1229 "text": {
1134 "spans": [1230 "spans": [
...@@ -1150,6 +1246,7 @@...@@ -1150,6 +1246,7 @@
1150 "line_spacing": null,1246 "line_spacing": null,
1151 "list_spacing": null,1247 "list_spacing": null,
1152 "math": null,1248 "math": null,
1249 "math_object": null,
1153 "rtl": null,1250 "rtl": null,
1154 "space_after": null,1251 "space_after": null,
1155 "space_before": null,1252 "space_before": null,
...@@ -1180,6 +1277,7 @@...@@ -1180,6 +1277,7 @@
1180 "line_spacing": null,1277 "line_spacing": null,
1181 "list_spacing": null,1278 "list_spacing": null,
1182 "math": null,1279 "math": null,
1280 "math_object": null,
1183 "rtl": null,1281 "rtl": null,
1184 "space_after": null,1282 "space_after": null,
1185 "space_before": null,1283 "space_before": null,
...@@ -1188,15 +1286,19 @@...@@ -1188,15 +1286,19 @@
1188 "superscript": null,1286 "superscript": null,
1189 "underline": null1287 "underline": null
1190 },1288 },
1191 "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},15",1289 "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},15",
1192 "level": 1,1290 "level": 1,
1193 "lists": [],1291 "lists": [],
1292 "media": {
1293 "recordings": [],
1294 "time_ms": null
1295 },
1194 "parent": null,1296 "parent": null,
1195 "style": "{57E835EA-2799-4118-B55C-7BB1E51C6941},18",1297 "style": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},18",
1196 "tags": []1298 "tags": []
1197 }1299 }
1198 ],1300 ],
1199 "title": false,1301 "title": true,
1200 "unsupported": []1302 "unsupported": []
1201 }1303 }
1202 ]1304 ]
...@@ -1207,9 +1309,9 @@...@@ -1207,9 +1309,9 @@
1207 },1309 },
1208 "author": "Author",1310 "author": "Author",
1209 "before": {1311 "before": {
1210 "created": 134335976560000000,1312 "created": 134348779890000000,
1211 "definitions": {1313 "definitions": {
1212 "{57E835EA-2799-4118-B55C-7BB1E51C6941},18": {1314 "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},18": {
1213 "format": {1315 "format": {
1214 "alignment": null,1316 "alignment": null,
1215 "bold": null,1317 "bold": null,
...@@ -1226,6 +1328,7 @@...@@ -1226,6 +1328,7 @@
1226 "line_spacing": null,1328 "line_spacing": null,
1227 "list_spacing": null,1329 "list_spacing": null,
1228 "math": null,1330 "math": null,
1331 "math_object": null,
1229 "rtl": null,1332 "rtl": null,
1230 "space_after": null,1333 "space_after": null,
1231 "space_before": null,1334 "space_before": null,
...@@ -1240,6 +1343,24 @@...@@ -1240,6 +1343,24 @@
1240 }1343 }
1241 }1344 }
1242 },1345 },
1346 "identity": [
1347 249,
1348 165,
1349 255,
1350 49,
1351 184,
1352 193,
1353 45,
1354 65,
1355 162,
1356 214,
1357 122,
1358 204,
1359 60,
1360 193,
1361 111,
1362 208
1363 ],
1243 "margin_origin": [1364 "margin_origin": [
1244 0.0,1365 0.0,
1245 0.01366 0.0
...@@ -1248,7 +1369,7 @@...@@ -1248,7 +1369,7 @@
1248 {1369 {
1249 "Title": {1370 "Title": {
1250 "date": null,1371 "date": null,
1251 "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},13",1372 "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},13",
1252 "layout": {1373 "layout": {
1253 "max_height": null,1374 "max_height": null,
1254 "max_width": null,1375 "max_width": null,
...@@ -1259,24 +1380,29 @@...@@ -1259,24 +1380,29 @@
1259 },1380 },
1260 "outlines": [1381 "outlines": [
1261 {1382 {
1262 "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},14",1383 "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},14",
1263 "indents": [],1384 "indents": [
1385 18.0,
1386 0.0,
1387 27.0,
1388 27.0
1389 ],
1264 "layout": {1390 "layout": {
1265 "max_height": null,1391 "max_height": 21.600000381469727,
1266 "max_width": null,1392 "max_width": null,
1267 "reserved_width": null,1393 "reserved_width": null,
1268 "width_set_by_user": null,1394 "width_set_by_user": null,
1269 "x": null,1395 "x": null,
1270 "y": null1396 "y": null
1271 },1397 },
1272 "min_width": null,1398 "min_width": 162.0,
1273 "paragraphs": [1399 "paragraphs": [
1274 {1400 {
1275 "collapsed": false,1401 "collapsed": false,
1276 "content": {1402 "content": {
1277 "Text": {1403 "Text": {
1278 "date_field": null,1404 "date_field": null,
1279 "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},16",1405 "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},16",
1280 "tags": [],1406 "tags": [],
1281 "text": {1407 "text": {
1282 "spans": [1408 "spans": [
...@@ -1298,6 +1424,7 @@...@@ -1298,6 +1424,7 @@
1298 "line_spacing": null,1424 "line_spacing": null,
1299 "list_spacing": null,1425 "list_spacing": null,
1300 "math": null,1426 "math": null,
1427 "math_object": null,
1301 "rtl": null,1428 "rtl": null,
1302 "space_after": null,1429 "space_after": null,
1303 "space_before": null,1430 "space_before": null,
...@@ -1328,6 +1455,7 @@...@@ -1328,6 +1455,7 @@
1328 "line_spacing": null,1455 "line_spacing": null,
1329 "list_spacing": null,1456 "list_spacing": null,
1330 "math": null,1457 "math": null,
1458 "math_object": null,
1331 "rtl": null,1459 "rtl": null,
1332 "space_after": null,1460 "space_after": null,
1333 "space_before": null,1461 "space_before": null,
...@@ -1336,15 +1464,19 @@...@@ -1336,15 +1464,19 @@
1336 "superscript": null,1464 "superscript": null,
1337 "underline": null1465 "underline": null
1338 },1466 },
1339 "id": "{57E835EA-2799-4118-B55C-7BB1E51C6941},15",1467 "id": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},15",
1340 "level": 1,1468 "level": 1,
1341 "lists": [],1469 "lists": [],
1470 "media": {
1471 "recordings": [],
1472 "time_ms": null
1473 },
1342 "parent": null,1474 "parent": null,
1343 "style": "{57E835EA-2799-4118-B55C-7BB1E51C6941},18",1475 "style": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},18",
1344 "tags": []1476 "tags": []
1345 }1477 }
1346 ],1478 ],
1347 "title": false,1479 "title": true,
1348 "unsupported": []1480 "unsupported": []
1349 }1481 }
1350 ]1482 ]
...@@ -1355,7 +1487,7 @@...@@ -1355,7 +1487,7 @@
1355 }1487 }
1356 }1488 }
1357 },1489 },
1358 "space": "{57E835EA-2799-4118-B55C-7BB1E51C6941},1"1490 "space": "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1"
1359 }1491 }
1360 ],1492 ],
1361 [1493 [
...@@ -1366,215 +1498,215 @@...@@ -1366,215 +1498,215 @@
1366 "edits": [1498 "edits": [
1367 {1499 {
1368 "guid": [1500 "guid": [
1369 172,
1370 90,
1371 29,
1372 103,
1373 245,
1374 250,
1375 220,
1376 74,
1377 178,1501 178,
1378 30,1502 65,
1379 220,1503 17,
1380 119,1504 57,
1381 124,1505 15,
1382 185,1506 50,
1383 174,1507 208,
1384 1981508 79,
1509 129,
1510 89,
1511 215,
1512 81,
1513 45,
1514 182,
1515 161,
1516 182
1385 ],1517 ],
1386 "level": 1,1518 "level": 1,
1387 "position": {1519 "position": {
1388 "Before": "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1"1520 "Before": "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1"
1389 },1521 },
1390 "space": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1"1522 "space": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1"
1391 },1523 },
1392 {1524 {
1393 "guid": [1525 "guid": [
1394 43,1526 92,
1395 65,1527 129,
1528 87,
1529 179,
1530 42,
1531 26,
1532 81,
1533 77,
1534 175,
1396 11,1535 11,
1397 224,1536 45,
1398 49,1537 117,
1399 201,1538 57,
1400 193,1539 12,
1401 78,1540 114,
1402 183,1541 224
1403 88,
1404 16,
1405 59,
1406 113,
1407 75,
1408 60,
1409 236
1410 ],1542 ],
1411 "level": 2,1543 "level": 2,
1412 "position": "Keep",1544 "position": "Keep",
1413 "space": "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1"1545 "space": "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1"
1414 }1546 }
1415 ],1547 ],
1416 "observed": [1548 "observed": [
1417 [1549 [
1418 "{869D5929-6E5E-4EDB-9639-4C56977DD447},1",1550 "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1",
1419 11551 1
1420 ],1552 ],
1421 [1553 [
1422 "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1",1554 "{74492C4A-164B-40E9-B297-4D3B4685347D},1",
1423 11555 1
1424 ],1556 ],
1425 [1557 [
1426 "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1",1558 "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1",
1427 11559 1
1428 ],1560 ],
1429 [1561 [
1430 "{C837A033-F055-4455-B45F-17D82016921C},1",1562 "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1",
1431 11563 1
1432 ],1564 ],
1433 [1565 [
1434 "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1",1566 "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1",
1435 11567 1
1436 ],1568 ],
1437 [1569 [
1438 "{57E835EA-2799-4118-B55C-7BB1E51C6941},1",1570 "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1",
1439 11571 1
1440 ],1572 ],
1441 [1573 [
1442 "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1",1574 "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1",
1443 11575 1
1444 ],1576 ],
1445 [1577 [
1446 "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1",1578 "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1",
1447 11579 1
1448 ],1580 ],
1449 [1581 [
1450 "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1",1582 "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1",
1451 11583 1
1452 ],1584 ],
1453 [1585 [
1454 "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1",1586 "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1",
1455 11587 1
1456 ],1588 ],
1457 [1589 [
1458 "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1",1590 "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1",
1459 11591 1
1460 ],1592 ],
1461 [1593 [
1462 "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1",1594 "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1",
1463 11595 1
1464 ],1596 ],
1465 [1597 [
1466 "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1",1598 "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1",
1467 11599 1
1468 ],1600 ],
1469 [1601 [
1470 "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1",1602 "{AE89305A-73F4-46B5-B41D-18884685D896},1",
1471 11603 1
1472 ],1604 ],
1473 [1605 [
1474 "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1",1606 "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1",
1475 11607 1
1476 ],1608 ],
1477 [1609 [
1478 "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1",1610 "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1",
1479 11611 1
1480 ],1612 ],
1481 [1613 [
1482 "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1",1614 "{42010094-E377-4A97-9395-AD45808D0552},1",
1483 11615 1
1484 ],1616 ],
1485 [1617 [
1486 "{7E9041BD-301B-4B5B-9456-1C92050E4914},1",1618 "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1",
1487 11619 1
1488 ],1620 ],
1489 [1621 [
1490 "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1",1622 "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1",
1491 11623 1
1492 ],1624 ],
1493 [1625 [
1494 "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1",1626 "{439B80E0-9D4C-4A2F-9163-4E301491364D},1",
1495 11627 1
1496 ],1628 ],
1497 [1629 [
1498 "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1",1630 "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1",
1499 11631 1
1500 ],1632 ],
1501 [1633 [
1502 "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1",1634 "{A459C474-E6EB-4BE5-9193-271799B2860B},1",
1503 11635 1
1504 ],1636 ],
1505 [1637 [
1506 "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1",1638 "{4ED14CEE-8560-4125-B488-9945C116741E},1",
1507 11639 1
1508 ],1640 ],
1509 [1641 [
1510 "{7F6702B3-C977-436F-82AF-403E2BE950E7},1",1642 "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1",
1511 11643 1
1512 ],1644 ],
1513 [1645 [
1514 "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1",1646 "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1",
1515 11647 1
1516 ],1648 ],
1517 [1649 [
1518 "{754FA957-C432-4502-A5CF-EE44449462C0},1",1650 "{9B755368-1D49-4D7E-8D33-6DC536F57848},1",
1519 11651 1
1520 ],1652 ],
1521 [1653 [
1522 "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1",1654 "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1",
1523 11655 1
1524 ],1656 ],
1525 [1657 [
1526 "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1",1658 "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1",
1527 11659 1
1528 ],1660 ],
1529 [1661 [
1530 "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1",1662 "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1",
1531 11663 1
1532 ],1664 ],
1533 [1665 [
1534 "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1",1666 "{C6E54364-6CD0-4764-8180-873030B9EA53},1",
1535 11667 1
1536 ],1668 ],
1537 [1669 [
1538 "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1",1670 "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1",
1539 11671 1
1540 ],1672 ],
1541 [1673 [
1542 "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1",1674 "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1",
1543 11675 1
1544 ],1676 ],
1545 [1677 [
1546 "{189EA744-BE13-467A-95BA-2667A134C0B0},1",1678 "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1",
1547 11679 1
1548 ],1680 ],
1549 [1681 [
1550 "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1",1682 "{00B10C58-6649-428D-A081-3EBBA6E88E97},1",
1551 11683 1
1552 ],1684 ],
1553 [1685 [
1554 "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1",1686 "{58E99482-710A-4938-8CFB-88A1C9B946C9},1",
1555 11687 1
1556 ],1688 ],
1557 [1689 [
1558 "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1",1690 "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1",
1559 11691 1
1560 ],1692 ],
1561 [1693 [
1562 "{2B736B19-2D9C-4E76-A732-478D533D717A},1",1694 "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1",
1563 11695 1
1564 ]1696 ]
1565 ]1697 ]
1566 }1698 }
1567 },1699 },
1568 "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1"1700 "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1"
1569 },1701 },
1570 {1702 {
1571 "id": 2,1703 "id": 2,
1572 "operation": {1704 "operation": {
1573 "Page": {1705 "Page": {
1574 "after": {1706 "after": {
1575 "created": 134335976560000000,1707 "created": 134348779890000000,
1576 "definitions": {1708 "definitions": {
1577 "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},18": {1709 "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},18": {
1578 "format": {1710 "format": {
1579 "alignment": null,1711 "alignment": null,
1580 "bold": null,1712 "bold": null,
...@@ -1591,6 +1723,7 @@...@@ -1591,6 +1723,7 @@
1591 "line_spacing": null,1723 "line_spacing": null,
1592 "list_spacing": null,1724 "list_spacing": null,
1593 "math": null,1725 "math": null,
1726 "math_object": null,
1594 "rtl": null,1727 "rtl": null,
1595 "space_after": null,1728 "space_after": null,
1596 "space_before": null,1729 "space_before": null,
...@@ -1605,6 +1738,24 @@...@@ -1605,6 +1738,24 @@
1605 }1738 }
1606 }1739 }
1607 },1740 },
1741 "identity": [
1742 15,
1743 122,
1744 179,
1745 90,
1746 2,
1747 87,
1748 142,
1749 76,
1750 168,
1751 198,
1752 121,
1753 237,
1754 110,
1755 209,
1756 230,
1757 159
1758 ],
1608 "margin_origin": [1759 "margin_origin": [
1609 0.0,1760 0.0,
1610 0.01761 0.0
...@@ -1612,7 +1763,7 @@...@@ -1612,7 +1763,7 @@
1612 "objects": [1763 "objects": [
1613 {1764 {
1614 "Outline": {1765 "Outline": {
1615 "id": "{5C473AF1-9E02-4E9A-BFC8-9E9CBBB289E1},0",1766 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},8",
1616 "indents": [],1767 "indents": [],
1617 "layout": {1768 "layout": {
1618 "max_height": null,1769 "max_height": null,
...@@ -1629,7 +1780,7 @@...@@ -1629,7 +1780,7 @@
1629 "content": {1780 "content": {
1630 "Text": {1781 "Text": {
1631 "date_field": null,1782 "date_field": null,
1632 "id": "{B412B53F-86FF-4462-8750-FB4D73532A63},0",1783 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},7",
1633 "tags": [],1784 "tags": [],
1634 "text": {1785 "text": {
1635 "spans": [1786 "spans": [
...@@ -1651,6 +1802,7 @@...@@ -1651,6 +1802,7 @@
1651 "line_spacing": null,1802 "line_spacing": null,
1652 "list_spacing": null,1803 "list_spacing": null,
1653 "math": null,1804 "math": null,
1805 "math_object": null,
1654 "rtl": null,1806 "rtl": null,
1655 "space_after": null,1807 "space_after": null,
1656 "space_before": null,1808 "space_before": null,
...@@ -1681,6 +1833,7 @@...@@ -1681,6 +1833,7 @@
1681 "line_spacing": null,1833 "line_spacing": null,
1682 "list_spacing": null,1834 "list_spacing": null,
1683 "math": null,1835 "math": null,
1836 "math_object": null,
1684 "rtl": null,1837 "rtl": null,
1685 "space_after": null,1838 "space_after": null,
1686 "space_before": null,1839 "space_before": null,
...@@ -1689,9 +1842,13 @@...@@ -1689,9 +1842,13 @@
1689 "superscript": null,1842 "superscript": null,
1690 "underline": null1843 "underline": null
1691 },1844 },
1692 "id": "{ADE09A18-DF3F-4995-BF31-D453831561D7},0",1845 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},9",
1693 "level": 1,1846 "level": 1,
1694 "lists": [],1847 "lists": [],
1848 "media": {
1849 "recordings": [],
1850 "time_ms": null
1851 },
1695 "parent": null,1852 "parent": null,
1696 "style": null,1853 "style": null,
1697 "tags": []1854 "tags": []
...@@ -1704,7 +1861,7 @@...@@ -1704,7 +1861,7 @@
1704 {1861 {
1705 "Title": {1862 "Title": {
1706 "date": null,1863 "date": null,
1707 "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},13",1864 "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},13",
1708 "layout": {1865 "layout": {
1709 "max_height": null,1866 "max_height": null,
1710 "max_width": null,1867 "max_width": null,
...@@ -1715,24 +1872,29 @@...@@ -1715,24 +1872,29 @@
1715 },1872 },
1716 "outlines": [1873 "outlines": [
1717 {1874 {
1718 "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},14",1875 "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},14",
1719 "indents": [],1876 "indents": [
1877 18.0,
1878 0.0,
1879 27.0,
1880 27.0
1881 ],
1720 "layout": {1882 "layout": {
1721 "max_height": null,1883 "max_height": 21.600000381469727,
1722 "max_width": null,1884 "max_width": null,
1723 "reserved_width": null,1885 "reserved_width": null,
1724 "width_set_by_user": null,1886 "width_set_by_user": null,
1725 "x": null,1887 "x": null,
1726 "y": null1888 "y": null
1727 },1889 },
1728 "min_width": null,1890 "min_width": 162.0,
1729 "paragraphs": [1891 "paragraphs": [
1730 {1892 {
1731 "collapsed": false,1893 "collapsed": false,
1732 "content": {1894 "content": {
1733 "Text": {1895 "Text": {
1734 "date_field": null,1896 "date_field": null,
1735 "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},16",1897 "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},16",
1736 "tags": [],1898 "tags": [],
1737 "text": {1899 "text": {
1738 "spans": [1900 "spans": [
...@@ -1754,6 +1916,7 @@...@@ -1754,6 +1916,7 @@
1754 "line_spacing": null,1916 "line_spacing": null,
1755 "list_spacing": null,1917 "list_spacing": null,
1756 "math": null,1918 "math": null,
1919 "math_object": null,
1757 "rtl": null,1920 "rtl": null,
1758 "space_after": null,1921 "space_after": null,
1759 "space_before": null,1922 "space_before": null,
...@@ -1784,6 +1947,7 @@...@@ -1784,6 +1947,7 @@
1784 "line_spacing": null,1947 "line_spacing": null,
1785 "list_spacing": null,1948 "list_spacing": null,
1786 "math": null,1949 "math": null,
1950 "math_object": null,
1787 "rtl": null,1951 "rtl": null,
1788 "space_after": null,1952 "space_after": null,
1789 "space_before": null,1953 "space_before": null,
...@@ -1792,15 +1956,19 @@...@@ -1792,15 +1956,19 @@
1792 "superscript": null,1956 "superscript": null,
1793 "underline": null1957 "underline": null
1794 },1958 },
1795 "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},15",1959 "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},15",
1796 "level": 1,1960 "level": 1,
1797 "lists": [],1961 "lists": [],
1962 "media": {
1963 "recordings": [],
1964 "time_ms": null
1965 },
1798 "parent": null,1966 "parent": null,
1799 "style": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},18",1967 "style": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},18",
1800 "tags": []1968 "tags": []
1801 }1969 }
1802 ],1970 ],
1803 "title": false,1971 "title": true,
1804 "unsupported": []1972 "unsupported": []
1805 }1973 }
1806 ]1974 ]
...@@ -1811,9 +1979,9 @@...@@ -1811,9 +1979,9 @@
1811 },1979 },
1812 "author": "Author",1980 "author": "Author",
1813 "before": {1981 "before": {
1814 "created": 134335976560000000,1982 "created": 134348779890000000,
1815 "definitions": {1983 "definitions": {
1816 "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},18": {1984 "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},18": {
1817 "format": {1985 "format": {
1818 "alignment": null,1986 "alignment": null,
1819 "bold": null,1987 "bold": null,
...@@ -1830,6 +1998,7 @@...@@ -1830,6 +1998,7 @@
1830 "line_spacing": null,1998 "line_spacing": null,
1831 "list_spacing": null,1999 "list_spacing": null,
1832 "math": null,2000 "math": null,
2001 "math_object": null,
1833 "rtl": null,2002 "rtl": null,
1834 "space_after": null,2003 "space_after": null,
1835 "space_before": null,2004 "space_before": null,
...@@ -1844,6 +2013,24 @@...@@ -1844,6 +2013,24 @@
1844 }2013 }
1845 }2014 }
1846 },2015 },
2016 "identity": [
2017 15,
2018 122,
2019 179,
2020 90,
2021 2,
2022 87,
2023 142,
2024 76,
2025 168,
2026 198,
2027 121,
2028 237,
2029 110,
2030 209,
2031 230,
2032 159
2033 ],
1847 "margin_origin": [2034 "margin_origin": [
1848 0.0,2035 0.0,
1849 0.02036 0.0
...@@ -1852,7 +2039,7 @@...@@ -1852,7 +2039,7 @@
1852 {2039 {
1853 "Title": {2040 "Title": {
1854 "date": null,2041 "date": null,
1855 "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},13",2042 "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},13",
1856 "layout": {2043 "layout": {
1857 "max_height": null,2044 "max_height": null,
1858 "max_width": null,2045 "max_width": null,
...@@ -1863,24 +2050,29 @@...@@ -1863,24 +2050,29 @@
1863 },2050 },
1864 "outlines": [2051 "outlines": [
1865 {2052 {
1866 "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},14",2053 "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},14",
1867 "indents": [],2054 "indents": [
2055 18.0,
2056 0.0,
2057 27.0,
2058 27.0
2059 ],
1868 "layout": {2060 "layout": {
1869 "max_height": null,2061 "max_height": 21.600000381469727,
1870 "max_width": null,2062 "max_width": null,
1871 "reserved_width": null,2063 "reserved_width": null,
1872 "width_set_by_user": null,2064 "width_set_by_user": null,
1873 "x": null,2065 "x": null,
1874 "y": null2066 "y": null
1875 },2067 },
1876 "min_width": null,2068 "min_width": 162.0,
1877 "paragraphs": [2069 "paragraphs": [
1878 {2070 {
1879 "collapsed": false,2071 "collapsed": false,
1880 "content": {2072 "content": {
1881 "Text": {2073 "Text": {
1882 "date_field": null,2074 "date_field": null,
1883 "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},16",2075 "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},16",
1884 "tags": [],2076 "tags": [],
1885 "text": {2077 "text": {
1886 "spans": [2078 "spans": [
...@@ -1902,6 +2094,7 @@...@@ -1902,6 +2094,7 @@
1902 "line_spacing": null,2094 "line_spacing": null,
1903 "list_spacing": null,2095 "list_spacing": null,
1904 "math": null,2096 "math": null,
2097 "math_object": null,
1905 "rtl": null,2098 "rtl": null,
1906 "space_after": null,2099 "space_after": null,
1907 "space_before": null,2100 "space_before": null,
...@@ -1932,6 +2125,7 @@...@@ -1932,6 +2125,7 @@
1932 "line_spacing": null,2125 "line_spacing": null,
1933 "list_spacing": null,2126 "list_spacing": null,
1934 "math": null,2127 "math": null,
2128 "math_object": null,
1935 "rtl": null,2129 "rtl": null,
1936 "space_after": null,2130 "space_after": null,
1937 "space_before": null,2131 "space_before": null,
...@@ -1940,15 +2134,19 @@...@@ -1940,15 +2134,19 @@
1940 "superscript": null,2134 "superscript": null,
1941 "underline": null2135 "underline": null
1942 },2136 },
1943 "id": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},15",2137 "id": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},15",
1944 "level": 1,2138 "level": 1,
1945 "lists": [],2139 "lists": [],
2140 "media": {
2141 "recordings": [],
2142 "time_ms": null
2143 },
1946 "parent": null,2144 "parent": null,
1947 "style": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},18",2145 "style": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},18",
1948 "tags": []2146 "tags": []
1949 }2147 }
1950 ],2148 ],
1951 "title": false,2149 "title": true,
1952 "unsupported": []2150 "unsupported": []
1953 }2151 }
1954 ]2152 ]
...@@ -1959,7 +2157,7 @@...@@ -1959,7 +2157,7 @@
1959 }2157 }
1960 }2158 }
1961 },2159 },
1962 "space": "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1"2160 "space": "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1"
1963 }2161 }
1964 ],2162 ],
1965 [2163 [
...@@ -1970,215 +2168,215 @@...@@ -1970,215 +2168,215 @@
1970 "edits": [2168 "edits": [
1971 {2169 {
1972 "guid": [2170 "guid": [
1973 220,2171 202,
1974 22,2172 70,
2173 93,
2174 136,
2175 36,
2176 165,
1975 109,2177 109,
1976 148,2178 65,
1977 199,2179 139,
1978 94,2180 122,
1979 248,2181 213,
1980 76,2182 126,
1981 168,2183 228,
1982 183,2184 97,
1983 186,2185 103,
1984 119,2186 255
1985 172,
1986 76,
1987 100,
1988 21
1989 ],2187 ],
1990 "level": 1,2188 "level": 1,
1991 "position": {2189 "position": {
1992 "Before": "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1"2190 "Before": "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1"
1993 },2191 },
1994 "space": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1"2192 "space": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1"
1995 },2193 },
1996 {2194 {
1997 "guid": [2195 "guid": [
1998 177,2196 233,
1999 33,2197 86,
2000 19,2198 59,
2001 250,2199 11,
2002 24,2200 202,
2003 200,2201 9,
2004 209,2202 167,
2005 74,2203 68,
2006 175,2204 172,
2007 22,2205 213,
2008 210,2206 140,
2009 214,2207 32,
2010 251,2208 40,
2011 44,2209 205,
2012 125,2210 125,
2013 1232211 172
2014 ],2212 ],
2015 "level": 2,2213 "level": 2,
2016 "position": "Keep",2214 "position": "Keep",
2017 "space": "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1"2215 "space": "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1"
2018 }2216 }
2019 ],2217 ],
2020 "observed": [2218 "observed": [
2021 [2219 [
2022 "{869D5929-6E5E-4EDB-9639-4C56977DD447},1",2220 "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1",
2023 12221 1
2024 ],2222 ],
2025 [2223 [
2026 "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1",2224 "{74492C4A-164B-40E9-B297-4D3B4685347D},1",
2027 12225 1
2028 ],2226 ],
2029 [2227 [
2030 "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1",2228 "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1",
2031 12229 1
2032 ],2230 ],
2033 [2231 [
2034 "{C837A033-F055-4455-B45F-17D82016921C},1",2232 "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1",
2035 12233 1
2036 ],2234 ],
2037 [2235 [
2038 "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1",2236 "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1",
2039 12237 1
2040 ],2238 ],
2041 [2239 [
2042 "{57E835EA-2799-4118-B55C-7BB1E51C6941},1",2240 "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1",
2043 12241 1
2044 ],2242 ],
2045 [2243 [
2046 "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1",2244 "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1",
2047 12245 1
2048 ],2246 ],
2049 [2247 [
2050 "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1",2248 "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1",
2051 12249 1
2052 ],2250 ],
2053 [2251 [
2054 "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1",2252 "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1",
2055 12253 1
2056 ],2254 ],
2057 [2255 [
2058 "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1",2256 "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1",
2059 12257 1
2060 ],2258 ],
2061 [2259 [
2062 "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1",2260 "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1",
2063 12261 1
2064 ],2262 ],
2065 [2263 [
2066 "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1",2264 "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1",
2067 12265 1
2068 ],2266 ],
2069 [2267 [
2070 "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1",2268 "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1",
2071 12269 1
2072 ],2270 ],
2073 [2271 [
2074 "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1",2272 "{AE89305A-73F4-46B5-B41D-18884685D896},1",
2075 12273 1
2076 ],2274 ],
2077 [2275 [
2078 "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1",2276 "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1",
2079 12277 1
2080 ],2278 ],
2081 [2279 [
2082 "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1",2280 "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1",
2083 12281 1
2084 ],2282 ],
2085 [2283 [
2086 "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1",2284 "{42010094-E377-4A97-9395-AD45808D0552},1",
2087 12285 1
2088 ],2286 ],
2089 [2287 [
2090 "{7E9041BD-301B-4B5B-9456-1C92050E4914},1",2288 "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1",
2091 12289 1
2092 ],2290 ],
2093 [2291 [
2094 "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1",2292 "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1",
2095 12293 1
2096 ],2294 ],
2097 [2295 [
2098 "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1",2296 "{439B80E0-9D4C-4A2F-9163-4E301491364D},1",
2099 12297 1
2100 ],2298 ],
2101 [2299 [
2102 "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1",2300 "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1",
2103 12301 1
2104 ],2302 ],
2105 [2303 [
2106 "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1",2304 "{A459C474-E6EB-4BE5-9193-271799B2860B},1",
2107 12305 1
2108 ],2306 ],
2109 [2307 [
2110 "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1",2308 "{4ED14CEE-8560-4125-B488-9945C116741E},1",
2111 12309 1
2112 ],2310 ],
2113 [2311 [
2114 "{7F6702B3-C977-436F-82AF-403E2BE950E7},1",2312 "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1",
2115 12313 1
2116 ],2314 ],
2117 [2315 [
2118 "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1",2316 "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1",
2119 12317 1
2120 ],2318 ],
2121 [2319 [
2122 "{754FA957-C432-4502-A5CF-EE44449462C0},1",2320 "{9B755368-1D49-4D7E-8D33-6DC536F57848},1",
2123 12321 1
2124 ],2322 ],
2125 [2323 [
2126 "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1",2324 "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1",
2127 12325 1
2128 ],2326 ],
2129 [2327 [
2130 "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1",2328 "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1",
2131 12329 1
2132 ],2330 ],
2133 [2331 [
2134 "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1",2332 "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1",
2135 12333 1
2136 ],2334 ],
2137 [2335 [
2138 "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1",2336 "{C6E54364-6CD0-4764-8180-873030B9EA53},1",
2139 12337 1
2140 ],2338 ],
2141 [2339 [
2142 "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1",2340 "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1",
2143 12341 1
2144 ],2342 ],
2145 [2343 [
2146 "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1",2344 "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1",
2147 12345 1
2148 ],2346 ],
2149 [2347 [
2150 "{189EA744-BE13-467A-95BA-2667A134C0B0},1",2348 "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1",
2151 12349 1
2152 ],2350 ],
2153 [2351 [
2154 "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1",2352 "{00B10C58-6649-428D-A081-3EBBA6E88E97},1",
2155 12353 1
2156 ],2354 ],
2157 [2355 [
2158 "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1",2356 "{58E99482-710A-4938-8CFB-88A1C9B946C9},1",
2159 12357 1
2160 ],2358 ],
2161 [2359 [
2162 "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1",2360 "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1",
2163 12361 1
2164 ],2362 ],
2165 [2363 [
2166 "{2B736B19-2D9C-4E76-A732-478D533D717A},1",2364 "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1",
2167 12365 1
2168 ]2366 ]
2169 ]2367 ]
2170 }2368 }
2171 },2369 },
2172 "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1"2370 "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1"
2173 },2371 },
2174 {2372 {
2175 "id": 2,2373 "id": 2,
2176 "operation": {2374 "operation": {
2177 "Page": {2375 "Page": {
2178 "after": {2376 "after": {
2179 "created": 134335976560000000,2377 "created": 134348779890000000,
2180 "definitions": {2378 "definitions": {
2181 "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},18": {2379 "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},18": {
2182 "format": {2380 "format": {
2183 "alignment": null,2381 "alignment": null,
2184 "bold": null,2382 "bold": null,
...@@ -2195,6 +2393,7 @@...@@ -2195,6 +2393,7 @@
2195 "line_spacing": null,2393 "line_spacing": null,
2196 "list_spacing": null,2394 "list_spacing": null,
2197 "math": null,2395 "math": null,
2396 "math_object": null,
2198 "rtl": null,2397 "rtl": null,
2199 "space_after": null,2398 "space_after": null,
2200 "space_before": null,2399 "space_before": null,
...@@ -2209,6 +2408,24 @@...@@ -2209,6 +2408,24 @@
2209 }2408 }
2210 }2409 }
2211 },2410 },
2411 "identity": [
2412 248,
2413 110,
2414 150,
2415 62,
2416 180,
2417 183,
2418 63,
2419 72,
2420 178,
2421 65,
2422 143,
2423 33,
2424 223,
2425 49,
2426 138,
2427 179
2428 ],
2212 "margin_origin": [2429 "margin_origin": [
2213 0.0,2430 0.0,
2214 0.02431 0.0
...@@ -2216,7 +2433,7 @@...@@ -2216,7 +2433,7 @@
2216 "objects": [2433 "objects": [
2217 {2434 {
2218 "Outline": {2435 "Outline": {
2219 "id": "{930570FE-0B42-4598-A6B7-74E90CE526D4},0",2436 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},11",
2220 "indents": [],2437 "indents": [],
2221 "layout": {2438 "layout": {
2222 "max_height": null,2439 "max_height": null,
...@@ -2233,7 +2450,7 @@...@@ -2233,7 +2450,7 @@
2233 "content": {2450 "content": {
2234 "Text": {2451 "Text": {
2235 "date_field": null,2452 "date_field": null,
2236 "id": "{917ABDDE-781F-45C9-90B5-A63A0FBCAEE4},0",2453 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},10",
2237 "tags": [],2454 "tags": [],
2238 "text": {2455 "text": {
2239 "spans": [2456 "spans": [
...@@ -2255,6 +2472,7 @@...@@ -2255,6 +2472,7 @@
2255 "line_spacing": null,2472 "line_spacing": null,
2256 "list_spacing": null,2473 "list_spacing": null,
2257 "math": null,2474 "math": null,
2475 "math_object": null,
2258 "rtl": null,2476 "rtl": null,
2259 "space_after": null,2477 "space_after": null,
2260 "space_before": null,2478 "space_before": null,
...@@ -2285,6 +2503,7 @@...@@ -2285,6 +2503,7 @@
2285 "line_spacing": null,2503 "line_spacing": null,
2286 "list_spacing": null,2504 "list_spacing": null,
2287 "math": null,2505 "math": null,
2506 "math_object": null,
2288 "rtl": null,2507 "rtl": null,
2289 "space_after": null,2508 "space_after": null,
2290 "space_before": null,2509 "space_before": null,
...@@ -2293,9 +2512,13 @@...@@ -2293,9 +2512,13 @@
2293 "superscript": null,2512 "superscript": null,
2294 "underline": null2513 "underline": null
2295 },2514 },
2296 "id": "{A4C1F94C-C7D9-4C9A-8F97-D7437FF3F499},0",2515 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},12",
2297 "level": 1,2516 "level": 1,
2298 "lists": [],2517 "lists": [],
2518 "media": {
2519 "recordings": [],
2520 "time_ms": null
2521 },
2299 "parent": null,2522 "parent": null,
2300 "style": null,2523 "style": null,
2301 "tags": []2524 "tags": []
...@@ -2308,7 +2531,7 @@...@@ -2308,7 +2531,7 @@
2308 {2531 {
2309 "Title": {2532 "Title": {
2310 "date": null,2533 "date": null,
2311 "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},13",2534 "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},13",
2312 "layout": {2535 "layout": {
2313 "max_height": null,2536 "max_height": null,
2314 "max_width": null,2537 "max_width": null,
...@@ -2319,24 +2542,29 @@...@@ -2319,24 +2542,29 @@
2319 },2542 },
2320 "outlines": [2543 "outlines": [
2321 {2544 {
2322 "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},14",2545 "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},14",
2323 "indents": [],2546 "indents": [
2547 18.0,
2548 0.0,
2549 27.0,
2550 27.0
2551 ],
2324 "layout": {2552 "layout": {
2325 "max_height": null,2553 "max_height": 21.600000381469727,
2326 "max_width": null,2554 "max_width": null,
2327 "reserved_width": null,2555 "reserved_width": null,
2328 "width_set_by_user": null,2556 "width_set_by_user": null,
2329 "x": null,2557 "x": null,
2330 "y": null2558 "y": null
2331 },2559 },
2332 "min_width": null,2560 "min_width": 162.0,
2333 "paragraphs": [2561 "paragraphs": [
2334 {2562 {
2335 "collapsed": false,2563 "collapsed": false,
2336 "content": {2564 "content": {
2337 "Text": {2565 "Text": {
2338 "date_field": null,2566 "date_field": null,
2339 "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},16",2567 "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},16",
2340 "tags": [],2568 "tags": [],
2341 "text": {2569 "text": {
2342 "spans": [2570 "spans": [
...@@ -2358,6 +2586,7 @@...@@ -2358,6 +2586,7 @@
2358 "line_spacing": null,2586 "line_spacing": null,
2359 "list_spacing": null,2587 "list_spacing": null,
2360 "math": null,2588 "math": null,
2589 "math_object": null,
2361 "rtl": null,2590 "rtl": null,
2362 "space_after": null,2591 "space_after": null,
2363 "space_before": null,2592 "space_before": null,
...@@ -2388,6 +2617,7 @@...@@ -2388,6 +2617,7 @@
2388 "line_spacing": null,2617 "line_spacing": null,
2389 "list_spacing": null,2618 "list_spacing": null,
2390 "math": null,2619 "math": null,
2620 "math_object": null,
2391 "rtl": null,2621 "rtl": null,
2392 "space_after": null,2622 "space_after": null,
2393 "space_before": null,2623 "space_before": null,
...@@ -2396,15 +2626,19 @@...@@ -2396,15 +2626,19 @@
2396 "superscript": null,2626 "superscript": null,
2397 "underline": null2627 "underline": null
2398 },2628 },
2399 "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},15",2629 "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},15",
2400 "level": 1,2630 "level": 1,
2401 "lists": [],2631 "lists": [],
2632 "media": {
2633 "recordings": [],
2634 "time_ms": null
2635 },
2402 "parent": null,2636 "parent": null,
2403 "style": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},18",2637 "style": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},18",
2404 "tags": []2638 "tags": []
2405 }2639 }
2406 ],2640 ],
2407 "title": false,2641 "title": true,
2408 "unsupported": []2642 "unsupported": []
2409 }2643 }
2410 ]2644 ]
...@@ -2415,9 +2649,9 @@...@@ -2415,9 +2649,9 @@
2415 },2649 },
2416 "author": "Author",2650 "author": "Author",
2417 "before": {2651 "before": {
2418 "created": 134335976560000000,2652 "created": 134348779890000000,
2419 "definitions": {2653 "definitions": {
2420 "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},18": {2654 "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},18": {
2421 "format": {2655 "format": {
2422 "alignment": null,2656 "alignment": null,
2423 "bold": null,2657 "bold": null,
...@@ -2434,6 +2668,7 @@...@@ -2434,6 +2668,7 @@
2434 "line_spacing": null,2668 "line_spacing": null,
2435 "list_spacing": null,2669 "list_spacing": null,
2436 "math": null,2670 "math": null,
2671 "math_object": null,
2437 "rtl": null,2672 "rtl": null,
2438 "space_after": null,2673 "space_after": null,
2439 "space_before": null,2674 "space_before": null,
...@@ -2448,6 +2683,24 @@...@@ -2448,6 +2683,24 @@
2448 }2683 }
2449 }2684 }
2450 },2685 },
2686 "identity": [
2687 248,
2688 110,
2689 150,
2690 62,
2691 180,
2692 183,
2693 63,
2694 72,
2695 178,
2696 65,
2697 143,
2698 33,
2699 223,
2700 49,
2701 138,
2702 179
2703 ],
2451 "margin_origin": [2704 "margin_origin": [
2452 0.0,2705 0.0,
2453 0.02706 0.0
...@@ -2456,7 +2709,7 @@...@@ -2456,7 +2709,7 @@
2456 {2709 {
2457 "Title": {2710 "Title": {
2458 "date": null,2711 "date": null,
2459 "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},13",2712 "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},13",
2460 "layout": {2713 "layout": {
2461 "max_height": null,2714 "max_height": null,
2462 "max_width": null,2715 "max_width": null,
...@@ -2467,24 +2720,29 @@...@@ -2467,24 +2720,29 @@
2467 },2720 },
2468 "outlines": [2721 "outlines": [
2469 {2722 {
2470 "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},14",2723 "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},14",
2471 "indents": [],2724 "indents": [
2725 18.0,
2726 0.0,
2727 27.0,
2728 27.0
2729 ],
2472 "layout": {2730 "layout": {
2473 "max_height": null,2731 "max_height": 21.600000381469727,
2474 "max_width": null,2732 "max_width": null,
2475 "reserved_width": null,2733 "reserved_width": null,
2476 "width_set_by_user": null,2734 "width_set_by_user": null,
2477 "x": null,2735 "x": null,
2478 "y": null2736 "y": null
2479 },2737 },
2480 "min_width": null,2738 "min_width": 162.0,
2481 "paragraphs": [2739 "paragraphs": [
2482 {2740 {
2483 "collapsed": false,2741 "collapsed": false,
2484 "content": {2742 "content": {
2485 "Text": {2743 "Text": {
2486 "date_field": null,2744 "date_field": null,
2487 "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},16",2745 "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},16",
2488 "tags": [],2746 "tags": [],
2489 "text": {2747 "text": {
2490 "spans": [2748 "spans": [
...@@ -2506,6 +2764,7 @@...@@ -2506,6 +2764,7 @@
2506 "line_spacing": null,2764 "line_spacing": null,
2507 "list_spacing": null,2765 "list_spacing": null,
2508 "math": null,2766 "math": null,
2767 "math_object": null,
2509 "rtl": null,2768 "rtl": null,
2510 "space_after": null,2769 "space_after": null,
2511 "space_before": null,2770 "space_before": null,
...@@ -2536,6 +2795,7 @@...@@ -2536,6 +2795,7 @@
2536 "line_spacing": null,2795 "line_spacing": null,
2537 "list_spacing": null,2796 "list_spacing": null,
2538 "math": null,2797 "math": null,
2798 "math_object": null,
2539 "rtl": null,2799 "rtl": null,
2540 "space_after": null,2800 "space_after": null,
2541 "space_before": null,2801 "space_before": null,
...@@ -2544,15 +2804,19 @@...@@ -2544,15 +2804,19 @@
2544 "superscript": null,2804 "superscript": null,
2545 "underline": null2805 "underline": null
2546 },2806 },
2547 "id": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},15",2807 "id": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},15",
2548 "level": 1,2808 "level": 1,
2549 "lists": [],2809 "lists": [],
2810 "media": {
2811 "recordings": [],
2812 "time_ms": null
2813 },
2550 "parent": null,2814 "parent": null,
2551 "style": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},18",2815 "style": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},18",
2552 "tags": []2816 "tags": []
2553 }2817 }
2554 ],2818 ],
2555 "title": false,2819 "title": true,
2556 "unsupported": []2820 "unsupported": []
2557 }2821 }
2558 ]2822 ]
...@@ -2563,7 +2827,7 @@...@@ -2563,7 +2827,7 @@
2563 }2827 }
2564 }2828 }
2565 },2829 },
2566 "space": "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1"2830 "space": "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1"
2567 }2831 }
2568 ],2832 ],
2569 [2833 [
...@@ -2574,215 +2838,215 @@...@@ -2574,215 +2838,215 @@
2574 "edits": [2838 "edits": [
2575 {2839 {
2576 "guid": [2840 "guid": [
2577 240,2841 164,
2578 213,2842 233,
2579 212,2843 248,
2580 204,2844 53,
2581 25,2845 50,
2582 156,2846 163,
2583 182,2847 52,
2584 67,2848 65,
2585 143,2849 145,
2586 206,2850 221,
2587 96,2851 232,
2588 209,2852 231,
2589 2,2853 172,
2590 198,2854 110,
2591 208,2855 51,
2592 942856 85
2593 ],2857 ],
2594 "level": 1,2858 "level": 1,
2595 "position": {2859 "position": {
2596 "Before": "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1"2860 "Before": "{AE89305A-73F4-46B5-B41D-18884685D896},1"
2597 },2861 },
2598 "space": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1"2862 "space": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1"
2599 },2863 },
2600 {2864 {
2601 "guid": [2865 "guid": [
2602 82,2866 127,
2603 173,2867 137,
2604 19,2868 251,
2605 143,2869 136,
2606 3,2870 118,
2607 17,
2608 177,
2609 66,
2610 183,
2611 218,
2612 131,
2613 72,2871 72,
2614 199,2872 18,
2615 191,2873 67,
2874 156,
2616 233,2875 233,
2617 2142876 237,
2877 236,
2878 165,
2879 97,
2880 49,
2881 92
2618 ],2882 ],
2619 "level": 2,2883 "level": 2,
2620 "position": "Keep",2884 "position": "Keep",
2621 "space": "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1"2885 "space": "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1"
2622 }2886 }
2623 ],2887 ],
2624 "observed": [2888 "observed": [
2625 [2889 [
2626 "{869D5929-6E5E-4EDB-9639-4C56977DD447},1",2890 "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1",
2627 12891 1
2628 ],2892 ],
2629 [2893 [
2630 "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1",2894 "{74492C4A-164B-40E9-B297-4D3B4685347D},1",
2631 12895 1
2632 ],2896 ],
2633 [2897 [
2634 "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1",2898 "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1",
2635 12899 1
2636 ],2900 ],
2637 [2901 [
2638 "{C837A033-F055-4455-B45F-17D82016921C},1",2902 "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1",
2639 12903 1
2640 ],2904 ],
2641 [2905 [
2642 "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1",2906 "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1",
2643 12907 1
2644 ],2908 ],
2645 [2909 [
2646 "{57E835EA-2799-4118-B55C-7BB1E51C6941},1",2910 "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1",
2647 12911 1
2648 ],2912 ],
2649 [2913 [
2650 "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1",2914 "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1",
2651 12915 1
2652 ],2916 ],
2653 [2917 [
2654 "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1",2918 "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1",
2655 12919 1
2656 ],2920 ],
2657 [2921 [
2658 "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1",2922 "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1",
2659 12923 1
2660 ],2924 ],
2661 [2925 [
2662 "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1",2926 "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1",
2663 12927 1
2664 ],2928 ],
2665 [2929 [
2666 "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1",2930 "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1",
2667 12931 1
2668 ],2932 ],
2669 [2933 [
2670 "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1",2934 "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1",
2671 12935 1
2672 ],2936 ],
2673 [2937 [
2674 "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1",2938 "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1",
2675 12939 1
2676 ],2940 ],
2677 [2941 [
2678 "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1",2942 "{AE89305A-73F4-46B5-B41D-18884685D896},1",
2679 12943 1
2680 ],2944 ],
2681 [2945 [
2682 "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1",2946 "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1",
2683 12947 1
2684 ],2948 ],
2685 [2949 [
2686 "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1",2950 "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1",
2687 12951 1
2688 ],2952 ],
2689 [2953 [
2690 "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1",2954 "{42010094-E377-4A97-9395-AD45808D0552},1",
2691 12955 1
2692 ],2956 ],
2693 [2957 [
2694 "{7E9041BD-301B-4B5B-9456-1C92050E4914},1",2958 "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1",
2695 12959 1
2696 ],2960 ],
2697 [2961 [
2698 "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1",2962 "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1",
2699 12963 1
2700 ],2964 ],
2701 [2965 [
2702 "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1",2966 "{439B80E0-9D4C-4A2F-9163-4E301491364D},1",
2703 12967 1
2704 ],2968 ],
2705 [2969 [
2706 "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1",2970 "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1",
2707 12971 1
2708 ],2972 ],
2709 [2973 [
2710 "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1",2974 "{A459C474-E6EB-4BE5-9193-271799B2860B},1",
2711 12975 1
2712 ],2976 ],
2713 [2977 [
2714 "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1",2978 "{4ED14CEE-8560-4125-B488-9945C116741E},1",
2715 12979 1
2716 ],2980 ],
2717 [2981 [
2718 "{7F6702B3-C977-436F-82AF-403E2BE950E7},1",2982 "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1",
2719 12983 1
2720 ],2984 ],
2721 [2985 [
2722 "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1",2986 "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1",
2723 12987 1
2724 ],2988 ],
2725 [2989 [
2726 "{754FA957-C432-4502-A5CF-EE44449462C0},1",2990 "{9B755368-1D49-4D7E-8D33-6DC536F57848},1",
2727 12991 1
2728 ],2992 ],
2729 [2993 [
2730 "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1",2994 "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1",
2731 12995 1
2732 ],2996 ],
2733 [2997 [
2734 "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1",2998 "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1",
2735 12999 1
2736 ],3000 ],
2737 [3001 [
2738 "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1",3002 "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1",
2739 13003 1
2740 ],3004 ],
2741 [3005 [
2742 "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1",3006 "{C6E54364-6CD0-4764-8180-873030B9EA53},1",
2743 13007 1
2744 ],3008 ],
2745 [3009 [
2746 "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1",3010 "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1",
2747 13011 1
2748 ],3012 ],
2749 [3013 [
2750 "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1",3014 "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1",
2751 13015 1
2752 ],3016 ],
2753 [3017 [
2754 "{189EA744-BE13-467A-95BA-2667A134C0B0},1",3018 "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1",
2755 13019 1
2756 ],3020 ],
2757 [3021 [
2758 "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1",3022 "{00B10C58-6649-428D-A081-3EBBA6E88E97},1",
2759 13023 1
2760 ],3024 ],
2761 [3025 [
2762 "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1",3026 "{58E99482-710A-4938-8CFB-88A1C9B946C9},1",
2763 13027 1
2764 ],3028 ],
2765 [3029 [
2766 "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1",3030 "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1",
2767 13031 1
2768 ],3032 ],
2769 [3033 [
2770 "{2B736B19-2D9C-4E76-A732-478D533D717A},1",3034 "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1",
2771 13035 1
2772 ]3036 ]
2773 ]3037 ]
2774 }3038 }
2775 },3039 },
2776 "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1"3040 "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1"
2777 },3041 },
2778 {3042 {
2779 "id": 2,3043 "id": 2,
2780 "operation": {3044 "operation": {
2781 "Page": {3045 "Page": {
2782 "after": {3046 "after": {
2783 "created": 134335976560000000,3047 "created": 134348779890000000,
2784 "definitions": {3048 "definitions": {
2785 "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},18": {3049 "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},18": {
2786 "format": {3050 "format": {
2787 "alignment": null,3051 "alignment": null,
2788 "bold": null,3052 "bold": null,
...@@ -2799,6 +3063,7 @@...@@ -2799,6 +3063,7 @@
2799 "line_spacing": null,3063 "line_spacing": null,
2800 "list_spacing": null,3064 "list_spacing": null,
2801 "math": null,3065 "math": null,
3066 "math_object": null,
2802 "rtl": null,3067 "rtl": null,
2803 "space_after": null,3068 "space_after": null,
2804 "space_before": null,3069 "space_before": null,
...@@ -2813,6 +3078,24 @@...@@ -2813,6 +3078,24 @@
2813 }3078 }
2814 }3079 }
2815 },3080 },
3081 "identity": [
3082 55,
3083 38,
3084 29,
3085 85,
3086 167,
3087 42,
3088 35,
3089 73,
3090 178,
3091 221,
3092 49,
3093 194,
3094 225,
3095 72,
3096 159,
3097 210
3098 ],
2816 "margin_origin": [3099 "margin_origin": [
2817 0.0,3100 0.0,
2818 0.03101 0.0
...@@ -2820,7 +3103,7 @@...@@ -2820,7 +3103,7 @@
2820 "objects": [3103 "objects": [
2821 {3104 {
2822 "Outline": {3105 "Outline": {
2823 "id": "{6A6E0E73-D2A2-4C82-B0B4-E8A3695B4D5A},0",3106 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},14",
2824 "indents": [],3107 "indents": [],
2825 "layout": {3108 "layout": {
2826 "max_height": null,3109 "max_height": null,
...@@ -2837,7 +3120,7 @@...@@ -2837,7 +3120,7 @@
2837 "content": {3120 "content": {
2838 "Text": {3121 "Text": {
2839 "date_field": null,3122 "date_field": null,
2840 "id": "{A11F6C01-70A2-44C7-90E9-91C455D075E7},0",3123 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},13",
2841 "tags": [],3124 "tags": [],
2842 "text": {3125 "text": {
2843 "spans": [3126 "spans": [
...@@ -2859,6 +3142,7 @@...@@ -2859,6 +3142,7 @@
2859 "line_spacing": null,3142 "line_spacing": null,
2860 "list_spacing": null,3143 "list_spacing": null,
2861 "math": null,3144 "math": null,
3145 "math_object": null,
2862 "rtl": null,3146 "rtl": null,
2863 "space_after": null,3147 "space_after": null,
2864 "space_before": null,3148 "space_before": null,
...@@ -2889,6 +3173,7 @@...@@ -2889,6 +3173,7 @@
2889 "line_spacing": null,3173 "line_spacing": null,
2890 "list_spacing": null,3174 "list_spacing": null,
2891 "math": null,3175 "math": null,
3176 "math_object": null,
2892 "rtl": null,3177 "rtl": null,
2893 "space_after": null,3178 "space_after": null,
2894 "space_before": null,3179 "space_before": null,
...@@ -2897,9 +3182,13 @@...@@ -2897,9 +3182,13 @@
2897 "superscript": null,3182 "superscript": null,
2898 "underline": null3183 "underline": null
2899 },3184 },
2900 "id": "{1B1F5EFD-3EE8-4031-BEB2-C3D27A332144},0",3185 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},15",
2901 "level": 1,3186 "level": 1,
2902 "lists": [],3187 "lists": [],
3188 "media": {
3189 "recordings": [],
3190 "time_ms": null
3191 },
2903 "parent": null,3192 "parent": null,
2904 "style": null,3193 "style": null,
2905 "tags": []3194 "tags": []
...@@ -2912,7 +3201,7 @@...@@ -2912,7 +3201,7 @@
2912 {3201 {
2913 "Title": {3202 "Title": {
2914 "date": null,3203 "date": null,
2915 "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},13",3204 "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},13",
2916 "layout": {3205 "layout": {
2917 "max_height": null,3206 "max_height": null,
2918 "max_width": null,3207 "max_width": null,
...@@ -2923,24 +3212,29 @@...@@ -2923,24 +3212,29 @@
2923 },3212 },
2924 "outlines": [3213 "outlines": [
2925 {3214 {
2926 "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},14",3215 "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},14",
2927 "indents": [],3216 "indents": [
3217 18.0,
3218 0.0,
3219 27.0,
3220 27.0
3221 ],
2928 "layout": {3222 "layout": {
2929 "max_height": null,3223 "max_height": 21.600000381469727,
2930 "max_width": null,3224 "max_width": null,
2931 "reserved_width": null,3225 "reserved_width": null,
2932 "width_set_by_user": null,3226 "width_set_by_user": null,
2933 "x": null,3227 "x": null,
2934 "y": null3228 "y": null
2935 },3229 },
2936 "min_width": null,3230 "min_width": 162.0,
2937 "paragraphs": [3231 "paragraphs": [
2938 {3232 {
2939 "collapsed": false,3233 "collapsed": false,
2940 "content": {3234 "content": {
2941 "Text": {3235 "Text": {
2942 "date_field": null,3236 "date_field": null,
2943 "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},16",3237 "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},16",
2944 "tags": [],3238 "tags": [],
2945 "text": {3239 "text": {
2946 "spans": [3240 "spans": [
...@@ -2962,6 +3256,7 @@...@@ -2962,6 +3256,7 @@
2962 "line_spacing": null,3256 "line_spacing": null,
2963 "list_spacing": null,3257 "list_spacing": null,
2964 "math": null,3258 "math": null,
3259 "math_object": null,
2965 "rtl": null,3260 "rtl": null,
2966 "space_after": null,3261 "space_after": null,
2967 "space_before": null,3262 "space_before": null,
...@@ -2992,6 +3287,7 @@...@@ -2992,6 +3287,7 @@
2992 "line_spacing": null,3287 "line_spacing": null,
2993 "list_spacing": null,3288 "list_spacing": null,
2994 "math": null,3289 "math": null,
3290 "math_object": null,
2995 "rtl": null,3291 "rtl": null,
2996 "space_after": null,3292 "space_after": null,
2997 "space_before": null,3293 "space_before": null,
...@@ -3000,15 +3296,19 @@...@@ -3000,15 +3296,19 @@
3000 "superscript": null,3296 "superscript": null,
3001 "underline": null3297 "underline": null
3002 },3298 },
3003 "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},15",3299 "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},15",
3004 "level": 1,3300 "level": 1,
3005 "lists": [],3301 "lists": [],
3302 "media": {
3303 "recordings": [],
3304 "time_ms": null
3305 },
3006 "parent": null,3306 "parent": null,
3007 "style": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},18",3307 "style": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},18",
3008 "tags": []3308 "tags": []
3009 }3309 }
3010 ],3310 ],
3011 "title": false,3311 "title": true,
3012 "unsupported": []3312 "unsupported": []
3013 }3313 }
3014 ]3314 ]
...@@ -3019,9 +3319,9 @@...@@ -3019,9 +3319,9 @@
3019 },3319 },
3020 "author": "Author",3320 "author": "Author",
3021 "before": {3321 "before": {
3022 "created": 134335976560000000,3322 "created": 134348779890000000,
3023 "definitions": {3323 "definitions": {
3024 "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},18": {3324 "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},18": {
3025 "format": {3325 "format": {
3026 "alignment": null,3326 "alignment": null,
3027 "bold": null,3327 "bold": null,
...@@ -3038,6 +3338,7 @@...@@ -3038,6 +3338,7 @@
3038 "line_spacing": null,3338 "line_spacing": null,
3039 "list_spacing": null,3339 "list_spacing": null,
3040 "math": null,3340 "math": null,
3341 "math_object": null,
3041 "rtl": null,3342 "rtl": null,
3042 "space_after": null,3343 "space_after": null,
3043 "space_before": null,3344 "space_before": null,
...@@ -3052,6 +3353,24 @@...@@ -3052,6 +3353,24 @@
3052 }3353 }
3053 }3354 }
3054 },3355 },
3356 "identity": [
3357 55,
3358 38,
3359 29,
3360 85,
3361 167,
3362 42,
3363 35,
3364 73,
3365 178,
3366 221,
3367 49,
3368 194,
3369 225,
3370 72,
3371 159,
3372 210
3373 ],
3055 "margin_origin": [3374 "margin_origin": [
3056 0.0,3375 0.0,
3057 0.03376 0.0
...@@ -3060,7 +3379,7 @@...@@ -3060,7 +3379,7 @@
3060 {3379 {
3061 "Title": {3380 "Title": {
3062 "date": null,3381 "date": null,
3063 "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},13",3382 "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},13",
3064 "layout": {3383 "layout": {
3065 "max_height": null,3384 "max_height": null,
3066 "max_width": null,3385 "max_width": null,
...@@ -3071,24 +3390,29 @@...@@ -3071,24 +3390,29 @@
3071 },3390 },
3072 "outlines": [3391 "outlines": [
3073 {3392 {
3074 "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},14",3393 "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},14",
3075 "indents": [],3394 "indents": [
3395 18.0,
3396 0.0,
3397 27.0,
3398 27.0
3399 ],
3076 "layout": {3400 "layout": {
3077 "max_height": null,3401 "max_height": 21.600000381469727,
3078 "max_width": null,3402 "max_width": null,
3079 "reserved_width": null,3403 "reserved_width": null,
3080 "width_set_by_user": null,3404 "width_set_by_user": null,
3081 "x": null,3405 "x": null,
3082 "y": null3406 "y": null
3083 },3407 },
3084 "min_width": null,3408 "min_width": 162.0,
3085 "paragraphs": [3409 "paragraphs": [
3086 {3410 {
3087 "collapsed": false,3411 "collapsed": false,
3088 "content": {3412 "content": {
3089 "Text": {3413 "Text": {
3090 "date_field": null,3414 "date_field": null,
3091 "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},16",3415 "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},16",
3092 "tags": [],3416 "tags": [],
3093 "text": {3417 "text": {
3094 "spans": [3418 "spans": [
...@@ -3110,6 +3434,7 @@...@@ -3110,6 +3434,7 @@
3110 "line_spacing": null,3434 "line_spacing": null,
3111 "list_spacing": null,3435 "list_spacing": null,
3112 "math": null,3436 "math": null,
3437 "math_object": null,
3113 "rtl": null,3438 "rtl": null,
3114 "space_after": null,3439 "space_after": null,
3115 "space_before": null,3440 "space_before": null,
...@@ -3140,6 +3465,7 @@...@@ -3140,6 +3465,7 @@
3140 "line_spacing": null,3465 "line_spacing": null,
3141 "list_spacing": null,3466 "list_spacing": null,
3142 "math": null,3467 "math": null,
3468 "math_object": null,
3143 "rtl": null,3469 "rtl": null,
3144 "space_after": null,3470 "space_after": null,
3145 "space_before": null,3471 "space_before": null,
...@@ -3148,15 +3474,19 @@...@@ -3148,15 +3474,19 @@
3148 "superscript": null,3474 "superscript": null,
3149 "underline": null3475 "underline": null
3150 },3476 },
3151 "id": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},15",3477 "id": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},15",
3152 "level": 1,3478 "level": 1,
3153 "lists": [],3479 "lists": [],
3480 "media": {
3481 "recordings": [],
3482 "time_ms": null
3483 },
3154 "parent": null,3484 "parent": null,
3155 "style": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},18",3485 "style": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},18",
3156 "tags": []3486 "tags": []
3157 }3487 }
3158 ],3488 ],
3159 "title": false,3489 "title": true,
3160 "unsupported": []3490 "unsupported": []
3161 }3491 }
3162 ]3492 ]
...@@ -3167,7 +3497,7 @@...@@ -3167,7 +3497,7 @@
3167 }3497 }
3168 }3498 }
3169 },3499 },
3170 "space": "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1"3500 "space": "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1"
3171 }3501 }
3172 ],3502 ],
3173 [3503 [
...@@ -3178,215 +3508,215 @@...@@ -3178,215 +3508,215 @@
3178 "edits": [3508 "edits": [
3179 {3509 {
3180 "guid": [3510 "guid": [
3181 170,3511 2,
3182 199,
3183 91,
3184 6,
3185 149,
3186 108,3512 108,
3187 28,3513 104,
3188 78,3514 0,
3189 169,3515 158,
3516 109,
3517 176,
3518 79,
3519 171,
3520 232,
3521 110,
3522 168,
3523 146,
3524 200,
3190 241,3525 241,
3191 201,3526 35
3192 191,
3193 131,
3194 111,
3195 13,
3196 167
3197 ],3527 ],
3198 "level": 1,3528 "level": 1,
3199 "position": {3529 "position": {
3200 "Before": "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1"3530 "Before": "{42010094-E377-4A97-9395-AD45808D0552},1"
3201 },3531 },
3202 "space": "{7E9041BD-301B-4B5B-9456-1C92050E4914},1"3532 "space": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1"
3203 },3533 },
3204 {3534 {
3205 "guid": [3535 "guid": [
3206 18,3536 98,
3207 19,
3208 211,
3209 10,
3210 209,
3211 7,
3212 195,3537 195,
3213 66,3538 206,
3214 172,3539 208,
3215 223,3540 54,
3216 64,3541 190,
3217 131,3542 72,
3218 73,3543 65,
3219 37,3544 153,
3220 59,3545 87,
3221 1873546 208,
3547 13,
3548 10,
3549 87,
3550 170,
3551 34
3222 ],3552 ],
3223 "level": 2,3553 "level": 2,
3224 "position": "Keep",3554 "position": "Keep",
3225 "space": "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1"3555 "space": "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1"
3226 }3556 }
3227 ],3557 ],
3228 "observed": [3558 "observed": [
3229 [3559 [
3230 "{869D5929-6E5E-4EDB-9639-4C56977DD447},1",3560 "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1",
3231 13561 1
3232 ],3562 ],
3233 [3563 [
3234 "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1",3564 "{74492C4A-164B-40E9-B297-4D3B4685347D},1",
3235 13565 1
3236 ],3566 ],
3237 [3567 [
3238 "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1",3568 "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1",
3239 13569 1
3240 ],3570 ],
3241 [3571 [
3242 "{C837A033-F055-4455-B45F-17D82016921C},1",3572 "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1",
3243 13573 1
3244 ],3574 ],
3245 [3575 [
3246 "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1",3576 "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1",
3247 13577 1
3248 ],3578 ],
3249 [3579 [
3250 "{57E835EA-2799-4118-B55C-7BB1E51C6941},1",3580 "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1",
3251 13581 1
3252 ],3582 ],
3253 [3583 [
3254 "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1",3584 "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1",
3255 13585 1
3256 ],3586 ],
3257 [3587 [
3258 "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1",3588 "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1",
3259 13589 1
3260 ],3590 ],
3261 [3591 [
3262 "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1",3592 "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1",
3263 13593 1
3264 ],3594 ],
3265 [3595 [
3266 "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1",3596 "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1",
3267 13597 1
3268 ],3598 ],
3269 [3599 [
3270 "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1",3600 "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1",
3271 13601 1
3272 ],3602 ],
3273 [3603 [
3274 "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1",3604 "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1",
3275 13605 1
3276 ],3606 ],
3277 [3607 [
3278 "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1",3608 "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1",
3279 13609 1
3280 ],3610 ],
3281 [3611 [
3282 "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1",3612 "{AE89305A-73F4-46B5-B41D-18884685D896},1",
3283 13613 1
3284 ],3614 ],
3285 [3615 [
3286 "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1",3616 "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1",
3287 13617 1
3288 ],3618 ],
3289 [3619 [
3290 "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1",3620 "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1",
3291 13621 1
3292 ],3622 ],
3293 [3623 [
3294 "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1",3624 "{42010094-E377-4A97-9395-AD45808D0552},1",
3295 13625 1
3296 ],3626 ],
3297 [3627 [
3298 "{7E9041BD-301B-4B5B-9456-1C92050E4914},1",3628 "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1",
3299 13629 1
3300 ],3630 ],
3301 [3631 [
3302 "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1",3632 "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1",
3303 13633 1
3304 ],3634 ],
3305 [3635 [
3306 "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1",3636 "{439B80E0-9D4C-4A2F-9163-4E301491364D},1",
3307 13637 1
3308 ],3638 ],
3309 [3639 [
3310 "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1",3640 "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1",
3311 13641 1
3312 ],3642 ],
3313 [3643 [
3314 "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1",3644 "{A459C474-E6EB-4BE5-9193-271799B2860B},1",
3315 13645 1
3316 ],3646 ],
3317 [3647 [
3318 "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1",3648 "{4ED14CEE-8560-4125-B488-9945C116741E},1",
3319 13649 1
3320 ],3650 ],
3321 [3651 [
3322 "{7F6702B3-C977-436F-82AF-403E2BE950E7},1",3652 "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1",
3323 13653 1
3324 ],3654 ],
3325 [3655 [
3326 "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1",3656 "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1",
3327 13657 1
3328 ],3658 ],
3329 [3659 [
3330 "{754FA957-C432-4502-A5CF-EE44449462C0},1",3660 "{9B755368-1D49-4D7E-8D33-6DC536F57848},1",
3331 13661 1
3332 ],3662 ],
3333 [3663 [
3334 "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1",3664 "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1",
3335 13665 1
3336 ],3666 ],
3337 [3667 [
3338 "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1",3668 "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1",
3339 13669 1
3340 ],3670 ],
3341 [3671 [
3342 "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1",3672 "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1",
3343 13673 1
3344 ],3674 ],
3345 [3675 [
3346 "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1",3676 "{C6E54364-6CD0-4764-8180-873030B9EA53},1",
3347 13677 1
3348 ],3678 ],
3349 [3679 [
3350 "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1",3680 "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1",
3351 13681 1
3352 ],3682 ],
3353 [3683 [
3354 "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1",3684 "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1",
3355 13685 1
3356 ],3686 ],
3357 [3687 [
3358 "{189EA744-BE13-467A-95BA-2667A134C0B0},1",3688 "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1",
3359 13689 1
3360 ],3690 ],
3361 [3691 [
3362 "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1",3692 "{00B10C58-6649-428D-A081-3EBBA6E88E97},1",
3363 13693 1
3364 ],3694 ],
3365 [3695 [
3366 "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1",3696 "{58E99482-710A-4938-8CFB-88A1C9B946C9},1",
3367 13697 1
3368 ],3698 ],
3369 [3699 [
3370 "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1",3700 "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1",
3371 13701 1
3372 ],3702 ],
3373 [3703 [
3374 "{2B736B19-2D9C-4E76-A732-478D533D717A},1",3704 "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1",
3375 13705 1
3376 ]3706 ]
3377 ]3707 ]
3378 }3708 }
3379 },3709 },
3380 "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1"3710 "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1"
3381 },3711 },
3382 {3712 {
3383 "id": 2,3713 "id": 2,
3384 "operation": {3714 "operation": {
3385 "Page": {3715 "Page": {
3386 "after": {3716 "after": {
3387 "created": 134335976560000000,3717 "created": 134348779890000000,
3388 "definitions": {3718 "definitions": {
3389 "{7E9041BD-301B-4B5B-9456-1C92050E4914},18": {3719 "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},18": {
3390 "format": {3720 "format": {
3391 "alignment": null,3721 "alignment": null,
3392 "bold": null,3722 "bold": null,
...@@ -3403,6 +3733,7 @@...@@ -3403,6 +3733,7 @@
3403 "line_spacing": null,3733 "line_spacing": null,
3404 "list_spacing": null,3734 "list_spacing": null,
3405 "math": null,3735 "math": null,
3736 "math_object": null,
3406 "rtl": null,3737 "rtl": null,
3407 "space_after": null,3738 "space_after": null,
3408 "space_before": null,3739 "space_before": null,
...@@ -3417,6 +3748,24 @@...@@ -3417,6 +3748,24 @@
3417 }3748 }
3418 }3749 }
3419 },3750 },
3751 "identity": [
3752 94,
3753 187,
3754 106,
3755 15,
3756 33,
3757 160,
3758 29,
3759 78,
3760 177,
3761 135,
3762 122,
3763 114,
3764 231,
3765 64,
3766 0,
3767 220
3768 ],
3420 "margin_origin": [3769 "margin_origin": [
3421 0.0,3770 0.0,
3422 0.03771 0.0
...@@ -3424,7 +3773,7 @@...@@ -3424,7 +3773,7 @@
3424 "objects": [3773 "objects": [
3425 {3774 {
3426 "Outline": {3775 "Outline": {
3427 "id": "{9D60419E-FC09-4547-8A45-5083DF888B11},0",3776 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},17",
3428 "indents": [],3777 "indents": [],
3429 "layout": {3778 "layout": {
3430 "max_height": null,3779 "max_height": null,
...@@ -3441,7 +3790,7 @@...@@ -3441,7 +3790,7 @@
3441 "content": {3790 "content": {
3442 "Text": {3791 "Text": {
3443 "date_field": null,3792 "date_field": null,
3444 "id": "{A30B6C43-D76B-4629-B1C4-AC8F2FF6CBEE},0",3793 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},16",
3445 "tags": [],3794 "tags": [],
3446 "text": {3795 "text": {
3447 "spans": [3796 "spans": [
...@@ -3463,6 +3812,7 @@...@@ -3463,6 +3812,7 @@
3463 "line_spacing": null,3812 "line_spacing": null,
3464 "list_spacing": null,3813 "list_spacing": null,
3465 "math": null,3814 "math": null,
3815 "math_object": null,
3466 "rtl": null,3816 "rtl": null,
3467 "space_after": null,3817 "space_after": null,
3468 "space_before": null,3818 "space_before": null,
...@@ -3493,6 +3843,7 @@...@@ -3493,6 +3843,7 @@
3493 "line_spacing": null,3843 "line_spacing": null,
3494 "list_spacing": null,3844 "list_spacing": null,
3495 "math": null,3845 "math": null,
3846 "math_object": null,
3496 "rtl": null,3847 "rtl": null,
3497 "space_after": null,3848 "space_after": null,
3498 "space_before": null,3849 "space_before": null,
...@@ -3501,9 +3852,13 @@...@@ -3501,9 +3852,13 @@
3501 "superscript": null,3852 "superscript": null,
3502 "underline": null3853 "underline": null
3503 },3854 },
3504 "id": "{AD6C0452-3D66-42DF-8865-A679E80B3F05},0",3855 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},18",
3505 "level": 1,3856 "level": 1,
3506 "lists": [],3857 "lists": [],
3858 "media": {
3859 "recordings": [],
3860 "time_ms": null
3861 },
3507 "parent": null,3862 "parent": null,
3508 "style": null,3863 "style": null,
3509 "tags": []3864 "tags": []
...@@ -3516,7 +3871,7 @@...@@ -3516,7 +3871,7 @@
3516 {3871 {
3517 "Title": {3872 "Title": {
3518 "date": null,3873 "date": null,
3519 "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},13",3874 "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},13",
3520 "layout": {3875 "layout": {
3521 "max_height": null,3876 "max_height": null,
3522 "max_width": null,3877 "max_width": null,
...@@ -3527,24 +3882,29 @@...@@ -3527,24 +3882,29 @@
3527 },3882 },
3528 "outlines": [3883 "outlines": [
3529 {3884 {
3530 "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},14",3885 "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},14",
3531 "indents": [],3886 "indents": [
3887 18.0,
3888 0.0,
3889 27.0,
3890 27.0
3891 ],
3532 "layout": {3892 "layout": {
3533 "max_height": null,3893 "max_height": 21.600000381469727,
3534 "max_width": null,3894 "max_width": null,
3535 "reserved_width": null,3895 "reserved_width": null,
3536 "width_set_by_user": null,3896 "width_set_by_user": null,
3537 "x": null,3897 "x": null,
3538 "y": null3898 "y": null
3539 },3899 },
3540 "min_width": null,3900 "min_width": 162.0,
3541 "paragraphs": [3901 "paragraphs": [
3542 {3902 {
3543 "collapsed": false,3903 "collapsed": false,
3544 "content": {3904 "content": {
3545 "Text": {3905 "Text": {
3546 "date_field": null,3906 "date_field": null,
3547 "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},16",3907 "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},16",
3548 "tags": [],3908 "tags": [],
3549 "text": {3909 "text": {
3550 "spans": [3910 "spans": [
...@@ -3566,6 +3926,7 @@...@@ -3566,6 +3926,7 @@
3566 "line_spacing": null,3926 "line_spacing": null,
3567 "list_spacing": null,3927 "list_spacing": null,
3568 "math": null,3928 "math": null,
3929 "math_object": null,
3569 "rtl": null,3930 "rtl": null,
3570 "space_after": null,3931 "space_after": null,
3571 "space_before": null,3932 "space_before": null,
...@@ -3596,6 +3957,7 @@...@@ -3596,6 +3957,7 @@
3596 "line_spacing": null,3957 "line_spacing": null,
3597 "list_spacing": null,3958 "list_spacing": null,
3598 "math": null,3959 "math": null,
3960 "math_object": null,
3599 "rtl": null,3961 "rtl": null,
3600 "space_after": null,3962 "space_after": null,
3601 "space_before": null,3963 "space_before": null,
...@@ -3604,15 +3966,19 @@...@@ -3604,15 +3966,19 @@
3604 "superscript": null,3966 "superscript": null,
3605 "underline": null3967 "underline": null
3606 },3968 },
3607 "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},15",3969 "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},15",
3608 "level": 1,3970 "level": 1,
3609 "lists": [],3971 "lists": [],
3972 "media": {
3973 "recordings": [],
3974 "time_ms": null
3975 },
3610 "parent": null,3976 "parent": null,
3611 "style": "{7E9041BD-301B-4B5B-9456-1C92050E4914},18",3977 "style": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},18",
3612 "tags": []3978 "tags": []
3613 }3979 }
3614 ],3980 ],
3615 "title": false,3981 "title": true,
3616 "unsupported": []3982 "unsupported": []
3617 }3983 }
3618 ]3984 ]
...@@ -3623,9 +3989,9 @@...@@ -3623,9 +3989,9 @@
3623 },3989 },
3624 "author": "Author",3990 "author": "Author",
3625 "before": {3991 "before": {
3626 "created": 134335976560000000,3992 "created": 134348779890000000,
3627 "definitions": {3993 "definitions": {
3628 "{7E9041BD-301B-4B5B-9456-1C92050E4914},18": {3994 "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},18": {
3629 "format": {3995 "format": {
3630 "alignment": null,3996 "alignment": null,
3631 "bold": null,3997 "bold": null,
...@@ -3642,6 +4008,7 @@...@@ -3642,6 +4008,7 @@
3642 "line_spacing": null,4008 "line_spacing": null,
3643 "list_spacing": null,4009 "list_spacing": null,
3644 "math": null,4010 "math": null,
4011 "math_object": null,
3645 "rtl": null,4012 "rtl": null,
3646 "space_after": null,4013 "space_after": null,
3647 "space_before": null,4014 "space_before": null,
...@@ -3656,6 +4023,24 @@...@@ -3656,6 +4023,24 @@
3656 }4023 }
3657 }4024 }
3658 },4025 },
4026 "identity": [
4027 94,
4028 187,
4029 106,
4030 15,
4031 33,
4032 160,
4033 29,
4034 78,
4035 177,
4036 135,
4037 122,
4038 114,
4039 231,
4040 64,
4041 0,
4042 220
4043 ],
3659 "margin_origin": [4044 "margin_origin": [
3660 0.0,4045 0.0,
3661 0.04046 0.0
...@@ -3664,7 +4049,7 @@...@@ -3664,7 +4049,7 @@
3664 {4049 {
3665 "Title": {4050 "Title": {
3666 "date": null,4051 "date": null,
3667 "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},13",4052 "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},13",
3668 "layout": {4053 "layout": {
3669 "max_height": null,4054 "max_height": null,
3670 "max_width": null,4055 "max_width": null,
...@@ -3675,24 +4060,29 @@...@@ -3675,24 +4060,29 @@
3675 },4060 },
3676 "outlines": [4061 "outlines": [
3677 {4062 {
3678 "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},14",4063 "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},14",
3679 "indents": [],4064 "indents": [
4065 18.0,
4066 0.0,
4067 27.0,
4068 27.0
4069 ],
3680 "layout": {4070 "layout": {
3681 "max_height": null,4071 "max_height": 21.600000381469727,
3682 "max_width": null,4072 "max_width": null,
3683 "reserved_width": null,4073 "reserved_width": null,
3684 "width_set_by_user": null,4074 "width_set_by_user": null,
3685 "x": null,4075 "x": null,
3686 "y": null4076 "y": null
3687 },4077 },
3688 "min_width": null,4078 "min_width": 162.0,
3689 "paragraphs": [4079 "paragraphs": [
3690 {4080 {
3691 "collapsed": false,4081 "collapsed": false,
3692 "content": {4082 "content": {
3693 "Text": {4083 "Text": {
3694 "date_field": null,4084 "date_field": null,
3695 "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},16",4085 "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},16",
3696 "tags": [],4086 "tags": [],
3697 "text": {4087 "text": {
3698 "spans": [4088 "spans": [
...@@ -3714,6 +4104,7 @@...@@ -3714,6 +4104,7 @@
3714 "line_spacing": null,4104 "line_spacing": null,
3715 "list_spacing": null,4105 "list_spacing": null,
3716 "math": null,4106 "math": null,
4107 "math_object": null,
3717 "rtl": null,4108 "rtl": null,
3718 "space_after": null,4109 "space_after": null,
3719 "space_before": null,4110 "space_before": null,
...@@ -3744,6 +4135,7 @@...@@ -3744,6 +4135,7 @@
3744 "line_spacing": null,4135 "line_spacing": null,
3745 "list_spacing": null,4136 "list_spacing": null,
3746 "math": null,4137 "math": null,
4138 "math_object": null,
3747 "rtl": null,4139 "rtl": null,
3748 "space_after": null,4140 "space_after": null,
3749 "space_before": null,4141 "space_before": null,
...@@ -3752,15 +4144,19 @@...@@ -3752,15 +4144,19 @@
3752 "superscript": null,4144 "superscript": null,
3753 "underline": null4145 "underline": null
3754 },4146 },
3755 "id": "{7E9041BD-301B-4B5B-9456-1C92050E4914},15",4147 "id": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},15",
3756 "level": 1,4148 "level": 1,
3757 "lists": [],4149 "lists": [],
4150 "media": {
4151 "recordings": [],
4152 "time_ms": null
4153 },
3758 "parent": null,4154 "parent": null,
3759 "style": "{7E9041BD-301B-4B5B-9456-1C92050E4914},18",4155 "style": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},18",
3760 "tags": []4156 "tags": []
3761 }4157 }
3762 ],4158 ],
3763 "title": false,4159 "title": true,
3764 "unsupported": []4160 "unsupported": []
3765 }4161 }
3766 ]4162 ]
...@@ -3771,7 +4167,7 @@...@@ -3771,7 +4167,7 @@
3771 }4167 }
3772 }4168 }
3773 },4169 },
3774 "space": "{7E9041BD-301B-4B5B-9456-1C92050E4914},1"4170 "space": "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1"
3775 }4171 }
3776 ],4172 ],
3777 [4173 [
...@@ -3782,215 +4178,215 @@...@@ -3782,215 +4178,215 @@
3782 "edits": [4178 "edits": [
3783 {4179 {
3784 "guid": [4180 "guid": [
3785 235,4181 83,
3786 237,4182 35,
3787 5,4183 225,
3788 138,4184 13,
3789 5,4185 184,
3790 243,4186 191,
3791 246,4187 39,
3792 64,4188 73,
3793 171,4189 187,
3794 74,4190 161,
3795 60,4191 191,
3796 18,4192 106,
3797 85,4193 185,
3798 228,4194 164,
3799 190,4195 169,
3800 2254196 110
3801 ],4197 ],
3802 "level": 1,4198 "level": 1,
3803 "position": {4199 "position": {
3804 "Before": "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1"4200 "Before": "{439B80E0-9D4C-4A2F-9163-4E301491364D},1"
3805 },4201 },
3806 "space": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1"4202 "space": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1"
3807 },4203 },
3808 {4204 {
3809 "guid": [4205 "guid": [
3810 154,4206 235,
3811 175,4207 25,
3812 180,4208 178,
3813 52,4209 87,
3814 102,4210 182,
3815 86,4211 233,
3816 241,4212 248,
3817 66,4213 74,
3818 129,4214 163,
3819 168,4215 238,
3820 227,4216 127,
3821 222,4217 45,
3822 69,4218 119,
3823 213,4219 155,
3824 66,4220 249,
3825 1874221 77
3826 ],4222 ],
3827 "level": 2,4223 "level": 2,
3828 "position": "Keep",4224 "position": "Keep",
3829 "space": "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1"4225 "space": "{A459C474-E6EB-4BE5-9193-271799B2860B},1"
3830 }4226 }
3831 ],4227 ],
3832 "observed": [4228 "observed": [
3833 [4229 [
3834 "{869D5929-6E5E-4EDB-9639-4C56977DD447},1",4230 "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1",
3835 14231 1
3836 ],4232 ],
3837 [4233 [
3838 "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1",4234 "{74492C4A-164B-40E9-B297-4D3B4685347D},1",
3839 14235 1
3840 ],4236 ],
3841 [4237 [
3842 "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1",4238 "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1",
3843 14239 1
3844 ],4240 ],
3845 [4241 [
3846 "{C837A033-F055-4455-B45F-17D82016921C},1",4242 "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1",
3847 14243 1
3848 ],4244 ],
3849 [4245 [
3850 "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1",4246 "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1",
3851 14247 1
3852 ],4248 ],
3853 [4249 [
3854 "{57E835EA-2799-4118-B55C-7BB1E51C6941},1",4250 "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1",
3855 14251 1
3856 ],4252 ],
3857 [4253 [
3858 "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1",4254 "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1",
3859 14255 1
3860 ],4256 ],
3861 [4257 [
3862 "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1",4258 "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1",
3863 14259 1
3864 ],4260 ],
3865 [4261 [
3866 "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1",4262 "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1",
3867 14263 1
3868 ],4264 ],
3869 [4265 [
3870 "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1",4266 "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1",
3871 14267 1
3872 ],4268 ],
3873 [4269 [
3874 "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1",4270 "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1",
3875 14271 1
3876 ],4272 ],
3877 [4273 [
3878 "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1",4274 "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1",
3879 14275 1
3880 ],4276 ],
3881 [4277 [
3882 "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1",4278 "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1",
3883 14279 1
3884 ],4280 ],
3885 [4281 [
3886 "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1",4282 "{AE89305A-73F4-46B5-B41D-18884685D896},1",
3887 14283 1
3888 ],4284 ],
3889 [4285 [
3890 "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1",4286 "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1",
3891 14287 1
3892 ],4288 ],
3893 [4289 [
3894 "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1",4290 "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1",
3895 14291 1
3896 ],4292 ],
3897 [4293 [
3898 "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1",4294 "{42010094-E377-4A97-9395-AD45808D0552},1",
3899 14295 1
3900 ],4296 ],
3901 [4297 [
3902 "{7E9041BD-301B-4B5B-9456-1C92050E4914},1",4298 "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1",
3903 14299 1
3904 ],4300 ],
3905 [4301 [
3906 "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1",4302 "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1",
3907 14303 1
3908 ],4304 ],
3909 [4305 [
3910 "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1",4306 "{439B80E0-9D4C-4A2F-9163-4E301491364D},1",
3911 14307 1
3912 ],4308 ],
3913 [4309 [
3914 "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1",4310 "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1",
3915 14311 1
3916 ],4312 ],
3917 [4313 [
3918 "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1",4314 "{A459C474-E6EB-4BE5-9193-271799B2860B},1",
3919 14315 1
3920 ],4316 ],
3921 [4317 [
3922 "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1",4318 "{4ED14CEE-8560-4125-B488-9945C116741E},1",
3923 14319 1
3924 ],4320 ],
3925 [4321 [
3926 "{7F6702B3-C977-436F-82AF-403E2BE950E7},1",4322 "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1",
3927 14323 1
3928 ],4324 ],
3929 [4325 [
3930 "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1",4326 "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1",
3931 14327 1
3932 ],4328 ],
3933 [4329 [
3934 "{754FA957-C432-4502-A5CF-EE44449462C0},1",4330 "{9B755368-1D49-4D7E-8D33-6DC536F57848},1",
3935 14331 1
3936 ],4332 ],
3937 [4333 [
3938 "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1",4334 "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1",
3939 14335 1
3940 ],4336 ],
3941 [4337 [
3942 "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1",4338 "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1",
3943 14339 1
3944 ],4340 ],
3945 [4341 [
3946 "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1",4342 "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1",
3947 14343 1
3948 ],4344 ],
3949 [4345 [
3950 "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1",4346 "{C6E54364-6CD0-4764-8180-873030B9EA53},1",
3951 14347 1
3952 ],4348 ],
3953 [4349 [
3954 "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1",4350 "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1",
3955 14351 1
3956 ],4352 ],
3957 [4353 [
3958 "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1",4354 "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1",
3959 14355 1
3960 ],4356 ],
3961 [4357 [
3962 "{189EA744-BE13-467A-95BA-2667A134C0B0},1",4358 "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1",
3963 14359 1
3964 ],4360 ],
3965 [4361 [
3966 "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1",4362 "{00B10C58-6649-428D-A081-3EBBA6E88E97},1",
3967 14363 1
3968 ],4364 ],
3969 [4365 [
3970 "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1",4366 "{58E99482-710A-4938-8CFB-88A1C9B946C9},1",
3971 14367 1
3972 ],4368 ],
3973 [4369 [
3974 "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1",4370 "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1",
3975 14371 1
3976 ],4372 ],
3977 [4373 [
3978 "{2B736B19-2D9C-4E76-A732-478D533D717A},1",4374 "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1",
3979 14375 1
3980 ]4376 ]
3981 ]4377 ]
3982 }4378 }
3983 },4379 },
3984 "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1"4380 "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1"
3985 },4381 },
3986 {4382 {
3987 "id": 2,4383 "id": 2,
3988 "operation": {4384 "operation": {
3989 "Page": {4385 "Page": {
3990 "after": {4386 "after": {
3991 "created": 134335976560000000,4387 "created": 134348779890000000,
3992 "definitions": {4388 "definitions": {
3993 "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},18": {4389 "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},18": {
3994 "format": {4390 "format": {
3995 "alignment": null,4391 "alignment": null,
3996 "bold": null,4392 "bold": null,
...@@ -4007,6 +4403,7 @@...@@ -4007,6 +4403,7 @@
4007 "line_spacing": null,4403 "line_spacing": null,
4008 "list_spacing": null,4404 "list_spacing": null,
4009 "math": null,4405 "math": null,
4406 "math_object": null,
4010 "rtl": null,4407 "rtl": null,
4011 "space_after": null,4408 "space_after": null,
4012 "space_before": null,4409 "space_before": null,
...@@ -4021,6 +4418,24 @@...@@ -4021,6 +4418,24 @@
4021 }4418 }
4022 }4419 }
4023 },4420 },
4421 "identity": [
4422 60,
4423 151,
4424 59,
4425 176,
4426 188,
4427 80,
4428 154,
4429 75,
4430 153,
4431 20,
4432 177,
4433 213,
4434 192,
4435 207,
4436 97,
4437 151
4438 ],
4024 "margin_origin": [4439 "margin_origin": [
4025 0.0,4440 0.0,
4026 0.04441 0.0
...@@ -4028,7 +4443,7 @@...@@ -4028,7 +4443,7 @@
4028 "objects": [4443 "objects": [
4029 {4444 {
4030 "Outline": {4445 "Outline": {
4031 "id": "{69602664-892F-4537-9BFE-860DAD71A595},0",4446 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},20",
4032 "indents": [],4447 "indents": [],
4033 "layout": {4448 "layout": {
4034 "max_height": null,4449 "max_height": null,
...@@ -4045,7 +4460,7 @@...@@ -4045,7 +4460,7 @@
4045 "content": {4460 "content": {
4046 "Text": {4461 "Text": {
4047 "date_field": null,4462 "date_field": null,
4048 "id": "{C533D56B-11C4-4867-B1E2-3E7520E7939A},0",4463 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},19",
4049 "tags": [],4464 "tags": [],
4050 "text": {4465 "text": {
4051 "spans": [4466 "spans": [
...@@ -4067,6 +4482,7 @@...@@ -4067,6 +4482,7 @@
4067 "line_spacing": null,4482 "line_spacing": null,
4068 "list_spacing": null,4483 "list_spacing": null,
4069 "math": null,4484 "math": null,
4485 "math_object": null,
4070 "rtl": null,4486 "rtl": null,
4071 "space_after": null,4487 "space_after": null,
4072 "space_before": null,4488 "space_before": null,
...@@ -4097,6 +4513,7 @@...@@ -4097,6 +4513,7 @@
4097 "line_spacing": null,4513 "line_spacing": null,
4098 "list_spacing": null,4514 "list_spacing": null,
4099 "math": null,4515 "math": null,
4516 "math_object": null,
4100 "rtl": null,4517 "rtl": null,
4101 "space_after": null,4518 "space_after": null,
4102 "space_before": null,4519 "space_before": null,
...@@ -4105,9 +4522,13 @@...@@ -4105,9 +4522,13 @@
4105 "superscript": null,4522 "superscript": null,
4106 "underline": null4523 "underline": null
4107 },4524 },
4108 "id": "{FB8451BB-46B9-44F1-972F-A543F55F06B5},0",4525 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},21",
4109 "level": 1,4526 "level": 1,
4110 "lists": [],4527 "lists": [],
4528 "media": {
4529 "recordings": [],
4530 "time_ms": null
4531 },
4111 "parent": null,4532 "parent": null,
4112 "style": null,4533 "style": null,
4113 "tags": []4534 "tags": []
...@@ -4120,7 +4541,7 @@...@@ -4120,7 +4541,7 @@
4120 {4541 {
4121 "Title": {4542 "Title": {
4122 "date": null,4543 "date": null,
4123 "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},13",4544 "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},13",
4124 "layout": {4545 "layout": {
4125 "max_height": null,4546 "max_height": null,
4126 "max_width": null,4547 "max_width": null,
...@@ -4131,24 +4552,29 @@...@@ -4131,24 +4552,29 @@
4131 },4552 },
4132 "outlines": [4553 "outlines": [
4133 {4554 {
4134 "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},14",4555 "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},14",
4135 "indents": [],4556 "indents": [
4557 18.0,
4558 0.0,
4559 27.0,
4560 27.0
4561 ],
4136 "layout": {4562 "layout": {
4137 "max_height": null,4563 "max_height": 21.600000381469727,
4138 "max_width": null,4564 "max_width": null,
4139 "reserved_width": null,4565 "reserved_width": null,
4140 "width_set_by_user": null,4566 "width_set_by_user": null,
4141 "x": null,4567 "x": null,
4142 "y": null4568 "y": null
4143 },4569 },
4144 "min_width": null,4570 "min_width": 162.0,
4145 "paragraphs": [4571 "paragraphs": [
4146 {4572 {
4147 "collapsed": false,4573 "collapsed": false,
4148 "content": {4574 "content": {
4149 "Text": {4575 "Text": {
4150 "date_field": null,4576 "date_field": null,
4151 "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},16",4577 "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},16",
4152 "tags": [],4578 "tags": [],
4153 "text": {4579 "text": {
4154 "spans": [4580 "spans": [
...@@ -4170,6 +4596,7 @@...@@ -4170,6 +4596,7 @@
4170 "line_spacing": null,4596 "line_spacing": null,
4171 "list_spacing": null,4597 "list_spacing": null,
4172 "math": null,4598 "math": null,
4599 "math_object": null,
4173 "rtl": null,4600 "rtl": null,
4174 "space_after": null,4601 "space_after": null,
4175 "space_before": null,4602 "space_before": null,
...@@ -4200,6 +4627,7 @@...@@ -4200,6 +4627,7 @@
4200 "line_spacing": null,4627 "line_spacing": null,
4201 "list_spacing": null,4628 "list_spacing": null,
4202 "math": null,4629 "math": null,
4630 "math_object": null,
4203 "rtl": null,4631 "rtl": null,
4204 "space_after": null,4632 "space_after": null,
4205 "space_before": null,4633 "space_before": null,
...@@ -4208,15 +4636,19 @@...@@ -4208,15 +4636,19 @@
4208 "superscript": null,4636 "superscript": null,
4209 "underline": null4637 "underline": null
4210 },4638 },
4211 "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},15",4639 "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},15",
4212 "level": 1,4640 "level": 1,
4213 "lists": [],4641 "lists": [],
4642 "media": {
4643 "recordings": [],
4644 "time_ms": null
4645 },
4214 "parent": null,4646 "parent": null,
4215 "style": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},18",4647 "style": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},18",
4216 "tags": []4648 "tags": []
4217 }4649 }
4218 ],4650 ],
4219 "title": false,4651 "title": true,
4220 "unsupported": []4652 "unsupported": []
4221 }4653 }
4222 ]4654 ]
...@@ -4227,9 +4659,9 @@...@@ -4227,9 +4659,9 @@
4227 },4659 },
4228 "author": "Author",4660 "author": "Author",
4229 "before": {4661 "before": {
4230 "created": 134335976560000000,4662 "created": 134348779890000000,
4231 "definitions": {4663 "definitions": {
4232 "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},18": {4664 "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},18": {
4233 "format": {4665 "format": {
4234 "alignment": null,4666 "alignment": null,
4235 "bold": null,4667 "bold": null,
...@@ -4246,6 +4678,7 @@...@@ -4246,6 +4678,7 @@
4246 "line_spacing": null,4678 "line_spacing": null,
4247 "list_spacing": null,4679 "list_spacing": null,
4248 "math": null,4680 "math": null,
4681 "math_object": null,
4249 "rtl": null,4682 "rtl": null,
4250 "space_after": null,4683 "space_after": null,
4251 "space_before": null,4684 "space_before": null,
...@@ -4260,6 +4693,24 @@...@@ -4260,6 +4693,24 @@
4260 }4693 }
4261 }4694 }
4262 },4695 },
4696 "identity": [
4697 60,
4698 151,
4699 59,
4700 176,
4701 188,
4702 80,
4703 154,
4704 75,
4705 153,
4706 20,
4707 177,
4708 213,
4709 192,
4710 207,
4711 97,
4712 151
4713 ],
4263 "margin_origin": [4714 "margin_origin": [
4264 0.0,4715 0.0,
4265 0.04716 0.0
...@@ -4268,7 +4719,7 @@...@@ -4268,7 +4719,7 @@
4268 {4719 {
4269 "Title": {4720 "Title": {
4270 "date": null,4721 "date": null,
4271 "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},13",4722 "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},13",
4272 "layout": {4723 "layout": {
4273 "max_height": null,4724 "max_height": null,
4274 "max_width": null,4725 "max_width": null,
...@@ -4279,24 +4730,29 @@...@@ -4279,24 +4730,29 @@
4279 },4730 },
4280 "outlines": [4731 "outlines": [
4281 {4732 {
4282 "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},14",4733 "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},14",
4283 "indents": [],4734 "indents": [
4735 18.0,
4736 0.0,
4737 27.0,
4738 27.0
4739 ],
4284 "layout": {4740 "layout": {
4285 "max_height": null,4741 "max_height": 21.600000381469727,
4286 "max_width": null,4742 "max_width": null,
4287 "reserved_width": null,4743 "reserved_width": null,
4288 "width_set_by_user": null,4744 "width_set_by_user": null,
4289 "x": null,4745 "x": null,
4290 "y": null4746 "y": null
4291 },4747 },
4292 "min_width": null,4748 "min_width": 162.0,
4293 "paragraphs": [4749 "paragraphs": [
4294 {4750 {
4295 "collapsed": false,4751 "collapsed": false,
4296 "content": {4752 "content": {
4297 "Text": {4753 "Text": {
4298 "date_field": null,4754 "date_field": null,
4299 "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},16",4755 "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},16",
4300 "tags": [],4756 "tags": [],
4301 "text": {4757 "text": {
4302 "spans": [4758 "spans": [
...@@ -4318,6 +4774,7 @@...@@ -4318,6 +4774,7 @@
4318 "line_spacing": null,4774 "line_spacing": null,
4319 "list_spacing": null,4775 "list_spacing": null,
4320 "math": null,4776 "math": null,
4777 "math_object": null,
4321 "rtl": null,4778 "rtl": null,
4322 "space_after": null,4779 "space_after": null,
4323 "space_before": null,4780 "space_before": null,
...@@ -4348,6 +4805,7 @@...@@ -4348,6 +4805,7 @@
4348 "line_spacing": null,4805 "line_spacing": null,
4349 "list_spacing": null,4806 "list_spacing": null,
4350 "math": null,4807 "math": null,
4808 "math_object": null,
4351 "rtl": null,4809 "rtl": null,
4352 "space_after": null,4810 "space_after": null,
4353 "space_before": null,4811 "space_before": null,
...@@ -4356,15 +4814,19 @@...@@ -4356,15 +4814,19 @@
4356 "superscript": null,4814 "superscript": null,
4357 "underline": null4815 "underline": null
4358 },4816 },
4359 "id": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},15",4817 "id": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},15",
4360 "level": 1,4818 "level": 1,
4361 "lists": [],4819 "lists": [],
4820 "media": {
4821 "recordings": [],
4822 "time_ms": null
4823 },
4362 "parent": null,4824 "parent": null,
4363 "style": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},18",4825 "style": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},18",
4364 "tags": []4826 "tags": []
4365 }4827 }
4366 ],4828 ],
4367 "title": false,4829 "title": true,
4368 "unsupported": []4830 "unsupported": []
4369 }4831 }
4370 ]4832 ]
...@@ -4375,7 +4837,7 @@...@@ -4375,7 +4837,7 @@
4375 }4837 }
4376 }4838 }
4377 },4839 },
4378 "space": "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1"4840 "space": "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1"
4379 }4841 }
4380 ],4842 ],
4381 [4843 [
...@@ -4386,215 +4848,215 @@...@@ -4386,215 +4848,215 @@
4386 "edits": [4848 "edits": [
4387 {4849 {
4388 "guid": [4850 "guid": [
4389 133,4851 73,
4390 13,4852 224,
4391 22,4853 214,
4392 96,4854 216,
4393 19,4855 142,
4394 247,4856 197,
4395 50,4857 81,
4396 69,4858 64,
4397 147,4859 131,
4398 11,4860 8,
4399 146,4861 236,
4400 207,4862 81,
4401 6,4863 238,
4402 153,4864 204,
4403 24,4865 152,
4404 944866 204
4405 ],4867 ],
4406 "level": 1,4868 "level": 1,
4407 "position": {4869 "position": {
4408 "Before": "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1"4870 "Before": "{4ED14CEE-8560-4125-B488-9945C116741E},1"
4409 },4871 },
4410 "space": "{7F6702B3-C977-436F-82AF-403E2BE950E7},1"4872 "space": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1"
4411 },4873 },
4412 {4874 {
4413 "guid": [4875 "guid": [
4414 159,4876 187,
4415 110,4877 19,
4416 141,4878 240,
4417 39,4879 247,
4418 253,4880 140,
4419 156,4881 21,
4420 37,4882 1,
4421 64,4883 78,
4422 172,4884 181,
4423 95,4885 106,
4886 58,
4424 144,4887 144,
4425 82,4888 123,
4426 194,4889 115,
4427 61,4890 155,
4428 40,4891 206
4429 198
4430 ],4892 ],
4431 "level": 2,4893 "level": 2,
4432 "position": "Keep",4894 "position": "Keep",
4433 "space": "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1"4895 "space": "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1"
4434 }4896 }
4435 ],4897 ],
4436 "observed": [4898 "observed": [
4437 [4899 [
4438 "{869D5929-6E5E-4EDB-9639-4C56977DD447},1",4900 "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1",
4439 14901 1
4440 ],4902 ],
4441 [4903 [
4442 "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1",4904 "{74492C4A-164B-40E9-B297-4D3B4685347D},1",
4443 14905 1
4444 ],4906 ],
4445 [4907 [
4446 "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1",4908 "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1",
4447 14909 1
4448 ],4910 ],
4449 [4911 [
4450 "{C837A033-F055-4455-B45F-17D82016921C},1",4912 "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1",
4451 14913 1
4452 ],4914 ],
4453 [4915 [
4454 "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1",4916 "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1",
4455 14917 1
4456 ],4918 ],
4457 [4919 [
4458 "{57E835EA-2799-4118-B55C-7BB1E51C6941},1",4920 "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1",
4459 14921 1
4460 ],4922 ],
4461 [4923 [
4462 "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1",4924 "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1",
4463 14925 1
4464 ],4926 ],
4465 [4927 [
4466 "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1",4928 "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1",
4467 14929 1
4468 ],4930 ],
4469 [4931 [
4470 "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1",4932 "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1",
4471 14933 1
4472 ],4934 ],
4473 [4935 [
4474 "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1",4936 "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1",
4475 14937 1
4476 ],4938 ],
4477 [4939 [
4478 "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1",4940 "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1",
4479 14941 1
4480 ],4942 ],
4481 [4943 [
4482 "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1",4944 "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1",
4483 14945 1
4484 ],4946 ],
4485 [4947 [
4486 "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1",4948 "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1",
4487 14949 1
4488 ],4950 ],
4489 [4951 [
4490 "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1",4952 "{AE89305A-73F4-46B5-B41D-18884685D896},1",
4491 14953 1
4492 ],4954 ],
4493 [4955 [
4494 "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1",4956 "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1",
4495 14957 1
4496 ],4958 ],
4497 [4959 [
4498 "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1",4960 "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1",
4499 14961 1
4500 ],4962 ],
4501 [4963 [
4502 "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1",4964 "{42010094-E377-4A97-9395-AD45808D0552},1",
4503 14965 1
4504 ],4966 ],
4505 [4967 [
4506 "{7E9041BD-301B-4B5B-9456-1C92050E4914},1",4968 "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1",
4507 14969 1
4508 ],4970 ],
4509 [4971 [
4510 "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1",4972 "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1",
4511 14973 1
4512 ],4974 ],
4513 [4975 [
4514 "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1",4976 "{439B80E0-9D4C-4A2F-9163-4E301491364D},1",
4515 14977 1
4516 ],4978 ],
4517 [4979 [
4518 "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1",4980 "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1",
4519 14981 1
4520 ],4982 ],
4521 [4983 [
4522 "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1",4984 "{A459C474-E6EB-4BE5-9193-271799B2860B},1",
4523 14985 1
4524 ],4986 ],
4525 [4987 [
4526 "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1",4988 "{4ED14CEE-8560-4125-B488-9945C116741E},1",
4527 14989 1
4528 ],4990 ],
4529 [4991 [
4530 "{7F6702B3-C977-436F-82AF-403E2BE950E7},1",4992 "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1",
4531 14993 1
4532 ],4994 ],
4533 [4995 [
4534 "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1",4996 "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1",
4535 14997 1
4536 ],4998 ],
4537 [4999 [
4538 "{754FA957-C432-4502-A5CF-EE44449462C0},1",5000 "{9B755368-1D49-4D7E-8D33-6DC536F57848},1",
4539 15001 1
4540 ],5002 ],
4541 [5003 [
4542 "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1",5004 "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1",
4543 15005 1
4544 ],5006 ],
4545 [5007 [
4546 "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1",5008 "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1",
4547 15009 1
4548 ],5010 ],
4549 [5011 [
4550 "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1",5012 "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1",
4551 15013 1
4552 ],5014 ],
4553 [5015 [
4554 "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1",5016 "{C6E54364-6CD0-4764-8180-873030B9EA53},1",
4555 15017 1
4556 ],5018 ],
4557 [5019 [
4558 "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1",5020 "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1",
4559 15021 1
4560 ],5022 ],
4561 [5023 [
4562 "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1",5024 "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1",
4563 15025 1
4564 ],5026 ],
4565 [5027 [
4566 "{189EA744-BE13-467A-95BA-2667A134C0B0},1",5028 "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1",
4567 15029 1
4568 ],5030 ],
4569 [5031 [
4570 "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1",5032 "{00B10C58-6649-428D-A081-3EBBA6E88E97},1",
4571 15033 1
4572 ],5034 ],
4573 [5035 [
4574 "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1",5036 "{58E99482-710A-4938-8CFB-88A1C9B946C9},1",
4575 15037 1
4576 ],5038 ],
4577 [5039 [
4578 "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1",5040 "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1",
4579 15041 1
4580 ],5042 ],
4581 [5043 [
4582 "{2B736B19-2D9C-4E76-A732-478D533D717A},1",5044 "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1",
4583 15045 1
4584 ]5046 ]
4585 ]5047 ]
4586 }5048 }
4587 },5049 },
4588 "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1"5050 "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1"
4589 },5051 },
4590 {5052 {
4591 "id": 2,5053 "id": 2,
4592 "operation": {5054 "operation": {
4593 "Page": {5055 "Page": {
4594 "after": {5056 "after": {
4595 "created": 134335976560000000,5057 "created": 134348779900000000,
4596 "definitions": {5058 "definitions": {
4597 "{7F6702B3-C977-436F-82AF-403E2BE950E7},18": {5059 "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},18": {
4598 "format": {5060 "format": {
4599 "alignment": null,5061 "alignment": null,
4600 "bold": null,5062 "bold": null,
...@@ -4611,6 +5073,7 @@...@@ -4611,6 +5073,7 @@
4611 "line_spacing": null,5073 "line_spacing": null,
4612 "list_spacing": null,5074 "list_spacing": null,
4613 "math": null,5075 "math": null,
5076 "math_object": null,
4614 "rtl": null,5077 "rtl": null,
4615 "space_after": null,5078 "space_after": null,
4616 "space_before": null,5079 "space_before": null,
...@@ -4625,6 +5088,24 @@...@@ -4625,6 +5088,24 @@
4625 }5088 }
4626 }5089 }
4627 },5090 },
5091 "identity": [
5092 228,
5093 143,
5094 255,
5095 196,
5096 67,
5097 76,
5098 31,
5099 70,
5100 152,
5101 221,
5102 235,
5103 31,
5104 242,
5105 168,
5106 99,
5107 178
5108 ],
4628 "margin_origin": [5109 "margin_origin": [
4629 0.0,5110 0.0,
4630 0.05111 0.0
...@@ -4632,7 +5113,7 @@...@@ -4632,7 +5113,7 @@
4632 "objects": [5113 "objects": [
4633 {5114 {
4634 "Outline": {5115 "Outline": {
4635 "id": "{F5ABCB2B-29D3-411E-A836-02A85749FF09},0",5116 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},23",
4636 "indents": [],5117 "indents": [],
4637 "layout": {5118 "layout": {
4638 "max_height": null,5119 "max_height": null,
...@@ -4649,7 +5130,7 @@...@@ -4649,7 +5130,7 @@
4649 "content": {5130 "content": {
4650 "Text": {5131 "Text": {
4651 "date_field": null,5132 "date_field": null,
4652 "id": "{3500B3EC-E613-4C17-8520-F7B22DB266FD},0",5133 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},22",
4653 "tags": [],5134 "tags": [],
4654 "text": {5135 "text": {
4655 "spans": [5136 "spans": [
...@@ -4671,6 +5152,7 @@...@@ -4671,6 +5152,7 @@
4671 "line_spacing": null,5152 "line_spacing": null,
4672 "list_spacing": null,5153 "list_spacing": null,
4673 "math": null,5154 "math": null,
5155 "math_object": null,
4674 "rtl": null,5156 "rtl": null,
4675 "space_after": null,5157 "space_after": null,
4676 "space_before": null,5158 "space_before": null,
...@@ -4701,6 +5183,7 @@...@@ -4701,6 +5183,7 @@
4701 "line_spacing": null,5183 "line_spacing": null,
4702 "list_spacing": null,5184 "list_spacing": null,
4703 "math": null,5185 "math": null,
5186 "math_object": null,
4704 "rtl": null,5187 "rtl": null,
4705 "space_after": null,5188 "space_after": null,
4706 "space_before": null,5189 "space_before": null,
...@@ -4709,9 +5192,13 @@...@@ -4709,9 +5192,13 @@
4709 "superscript": null,5192 "superscript": null,
4710 "underline": null5193 "underline": null
4711 },5194 },
4712 "id": "{DFF3BBDE-C14D-4D31-8E0D-CBCED9BB1195},0",5195 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},24",
4713 "level": 1,5196 "level": 1,
4714 "lists": [],5197 "lists": [],
5198 "media": {
5199 "recordings": [],
5200 "time_ms": null
5201 },
4715 "parent": null,5202 "parent": null,
4716 "style": null,5203 "style": null,
4717 "tags": []5204 "tags": []
...@@ -4724,7 +5211,7 @@...@@ -4724,7 +5211,7 @@
4724 {5211 {
4725 "Title": {5212 "Title": {
4726 "date": null,5213 "date": null,
4727 "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},13",5214 "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},13",
4728 "layout": {5215 "layout": {
4729 "max_height": null,5216 "max_height": null,
4730 "max_width": null,5217 "max_width": null,
...@@ -4735,24 +5222,29 @@...@@ -4735,24 +5222,29 @@
4735 },5222 },
4736 "outlines": [5223 "outlines": [
4737 {5224 {
4738 "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},14",5225 "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},14",
4739 "indents": [],5226 "indents": [
5227 18.0,
5228 0.0,
5229 27.0,
5230 27.0
5231 ],
4740 "layout": {5232 "layout": {
4741 "max_height": null,5233 "max_height": 21.600000381469727,
4742 "max_width": null,5234 "max_width": null,
4743 "reserved_width": null,5235 "reserved_width": null,
4744 "width_set_by_user": null,5236 "width_set_by_user": null,
4745 "x": null,5237 "x": null,
4746 "y": null5238 "y": null
4747 },5239 },
4748 "min_width": null,5240 "min_width": 162.0,
4749 "paragraphs": [5241 "paragraphs": [
4750 {5242 {
4751 "collapsed": false,5243 "collapsed": false,
4752 "content": {5244 "content": {
4753 "Text": {5245 "Text": {
4754 "date_field": null,5246 "date_field": null,
4755 "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},16",5247 "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},16",
4756 "tags": [],5248 "tags": [],
4757 "text": {5249 "text": {
4758 "spans": [5250 "spans": [
...@@ -4774,6 +5266,7 @@...@@ -4774,6 +5266,7 @@
4774 "line_spacing": null,5266 "line_spacing": null,
4775 "list_spacing": null,5267 "list_spacing": null,
4776 "math": null,5268 "math": null,
5269 "math_object": null,
4777 "rtl": null,5270 "rtl": null,
4778 "space_after": null,5271 "space_after": null,
4779 "space_before": null,5272 "space_before": null,
...@@ -4804,6 +5297,7 @@...@@ -4804,6 +5297,7 @@
4804 "line_spacing": null,5297 "line_spacing": null,
4805 "list_spacing": null,5298 "list_spacing": null,
4806 "math": null,5299 "math": null,
5300 "math_object": null,
4807 "rtl": null,5301 "rtl": null,
4808 "space_after": null,5302 "space_after": null,
4809 "space_before": null,5303 "space_before": null,
...@@ -4812,15 +5306,19 @@...@@ -4812,15 +5306,19 @@
4812 "superscript": null,5306 "superscript": null,
4813 "underline": null5307 "underline": null
4814 },5308 },
4815 "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},15",5309 "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},15",
4816 "level": 1,5310 "level": 1,
4817 "lists": [],5311 "lists": [],
5312 "media": {
5313 "recordings": [],
5314 "time_ms": null
5315 },
4818 "parent": null,5316 "parent": null,
4819 "style": "{7F6702B3-C977-436F-82AF-403E2BE950E7},18",5317 "style": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},18",
4820 "tags": []5318 "tags": []
4821 }5319 }
4822 ],5320 ],
4823 "title": false,5321 "title": true,
4824 "unsupported": []5322 "unsupported": []
4825 }5323 }
4826 ]5324 ]
...@@ -4831,9 +5329,9 @@...@@ -4831,9 +5329,9 @@
4831 },5329 },
4832 "author": "Author",5330 "author": "Author",
4833 "before": {5331 "before": {
4834 "created": 134335976560000000,5332 "created": 134348779900000000,
4835 "definitions": {5333 "definitions": {
4836 "{7F6702B3-C977-436F-82AF-403E2BE950E7},18": {5334 "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},18": {
4837 "format": {5335 "format": {
4838 "alignment": null,5336 "alignment": null,
4839 "bold": null,5337 "bold": null,
...@@ -4850,6 +5348,7 @@...@@ -4850,6 +5348,7 @@
4850 "line_spacing": null,5348 "line_spacing": null,
4851 "list_spacing": null,5349 "list_spacing": null,
4852 "math": null,5350 "math": null,
5351 "math_object": null,
4853 "rtl": null,5352 "rtl": null,
4854 "space_after": null,5353 "space_after": null,
4855 "space_before": null,5354 "space_before": null,
...@@ -4864,6 +5363,24 @@...@@ -4864,6 +5363,24 @@
4864 }5363 }
4865 }5364 }
4866 },5365 },
5366 "identity": [
5367 228,
5368 143,
5369 255,
5370 196,
5371 67,
5372 76,
5373 31,
5374 70,
5375 152,
5376 221,
5377 235,
5378 31,
5379 242,
5380 168,
5381 99,
5382 178
5383 ],
4867 "margin_origin": [5384 "margin_origin": [
4868 0.0,5385 0.0,
4869 0.05386 0.0
...@@ -4872,7 +5389,7 @@...@@ -4872,7 +5389,7 @@
4872 {5389 {
4873 "Title": {5390 "Title": {
4874 "date": null,5391 "date": null,
4875 "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},13",5392 "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},13",
4876 "layout": {5393 "layout": {
4877 "max_height": null,5394 "max_height": null,
4878 "max_width": null,5395 "max_width": null,
...@@ -4883,24 +5400,29 @@...@@ -4883,24 +5400,29 @@
4883 },5400 },
4884 "outlines": [5401 "outlines": [
4885 {5402 {
4886 "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},14",5403 "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},14",
4887 "indents": [],5404 "indents": [
5405 18.0,
5406 0.0,
5407 27.0,
5408 27.0
5409 ],
4888 "layout": {5410 "layout": {
4889 "max_height": null,5411 "max_height": 21.600000381469727,
4890 "max_width": null,5412 "max_width": null,
4891 "reserved_width": null,5413 "reserved_width": null,
4892 "width_set_by_user": null,5414 "width_set_by_user": null,
4893 "x": null,5415 "x": null,
4894 "y": null5416 "y": null
4895 },5417 },
4896 "min_width": null,5418 "min_width": 162.0,
4897 "paragraphs": [5419 "paragraphs": [
4898 {5420 {
4899 "collapsed": false,5421 "collapsed": false,
4900 "content": {5422 "content": {
4901 "Text": {5423 "Text": {
4902 "date_field": null,5424 "date_field": null,
4903 "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},16",5425 "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},16",
4904 "tags": [],5426 "tags": [],
4905 "text": {5427 "text": {
4906 "spans": [5428 "spans": [
...@@ -4922,6 +5444,7 @@...@@ -4922,6 +5444,7 @@
4922 "line_spacing": null,5444 "line_spacing": null,
4923 "list_spacing": null,5445 "list_spacing": null,
4924 "math": null,5446 "math": null,
5447 "math_object": null,
4925 "rtl": null,5448 "rtl": null,
4926 "space_after": null,5449 "space_after": null,
4927 "space_before": null,5450 "space_before": null,
...@@ -4952,6 +5475,7 @@...@@ -4952,6 +5475,7 @@
4952 "line_spacing": null,5475 "line_spacing": null,
4953 "list_spacing": null,5476 "list_spacing": null,
4954 "math": null,5477 "math": null,
5478 "math_object": null,
4955 "rtl": null,5479 "rtl": null,
4956 "space_after": null,5480 "space_after": null,
4957 "space_before": null,5481 "space_before": null,
...@@ -4960,15 +5484,19 @@...@@ -4960,15 +5484,19 @@
4960 "superscript": null,5484 "superscript": null,
4961 "underline": null5485 "underline": null
4962 },5486 },
4963 "id": "{7F6702B3-C977-436F-82AF-403E2BE950E7},15",5487 "id": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},15",
4964 "level": 1,5488 "level": 1,
4965 "lists": [],5489 "lists": [],
5490 "media": {
5491 "recordings": [],
5492 "time_ms": null
5493 },
4966 "parent": null,5494 "parent": null,
4967 "style": "{7F6702B3-C977-436F-82AF-403E2BE950E7},18",5495 "style": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},18",
4968 "tags": []5496 "tags": []
4969 }5497 }
4970 ],5498 ],
4971 "title": false,5499 "title": true,
4972 "unsupported": []5500 "unsupported": []
4973 }5501 }
4974 ]5502 ]
...@@ -4979,7 +5507,7 @@...@@ -4979,7 +5507,7 @@
4979 }5507 }
4980 }5508 }
4981 },5509 },
4982 "space": "{7F6702B3-C977-436F-82AF-403E2BE950E7},1"5510 "space": "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1"
4983 }5511 }
4984 ],5512 ],
4985 [5513 [
...@@ -4990,215 +5518,215 @@...@@ -4990,215 +5518,215 @@
4990 "edits": [5518 "edits": [
4991 {5519 {
4992 "guid": [5520 "guid": [
5521 171,
5522 123,
5523 160,
5524 66,
5525 159,
5526 123,
5527 23,
5528 73,
5529 161,
4993 60,5530 60,
4994 96,5531 51,
4995 70,5532 152,
4996 223,5533 144,
4997 197,5534 104,
4998 137,5535 92,
4999 63,5536 228
5000 78,
5001 170,
5002 199,
5003 81,
5004 158,
5005 99,
5006 18,
5007 163,
5008 133
5009 ],5537 ],
5010 "level": 1,5538 "level": 1,
5011 "position": {5539 "position": {
5012 "Before": "{754FA957-C432-4502-A5CF-EE44449462C0},1"5540 "Before": "{9B755368-1D49-4D7E-8D33-6DC536F57848},1"
5013 },5541 },
5014 "space": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1"5542 "space": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1"
5015 },5543 },
5016 {5544 {
5017 "guid": [5545 "guid": [
5018 218,5546 243,
5019 181,
5020 208,
5021 213,
5022 72,
5023 152,
5024 253,
5025 76,5547 76,
5026 176,5548 116,
5027 213,5549 146,
5028 138,5550 38,
5029 85,5551 89,
5030 141,5552 244,
5031 127,5553 65,
5032 170,5554 170,
5033 1605555 135,
5556 180,
5557 163,
5558 152,
5559 15,
5560 148,
5561 54
5034 ],5562 ],
5035 "level": 2,5563 "level": 2,
5036 "position": "Keep",5564 "position": "Keep",
5037 "space": "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1"5565 "space": "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1"
5038 }5566 }
5039 ],5567 ],
5040 "observed": [5568 "observed": [
5041 [5569 [
5042 "{869D5929-6E5E-4EDB-9639-4C56977DD447},1",5570 "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1",
5043 15571 1
5044 ],5572 ],
5045 [5573 [
5046 "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1",5574 "{74492C4A-164B-40E9-B297-4D3B4685347D},1",
5047 15575 1
5048 ],5576 ],
5049 [5577 [
5050 "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1",5578 "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1",
5051 15579 1
5052 ],5580 ],
5053 [5581 [
5054 "{C837A033-F055-4455-B45F-17D82016921C},1",5582 "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1",
5055 15583 1
5056 ],5584 ],
5057 [5585 [
5058 "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1",5586 "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1",
5059 15587 1
5060 ],5588 ],
5061 [5589 [
5062 "{57E835EA-2799-4118-B55C-7BB1E51C6941},1",5590 "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1",
5063 15591 1
5064 ],5592 ],
5065 [5593 [
5066 "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1",5594 "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1",
5067 15595 1
5068 ],5596 ],
5069 [5597 [
5070 "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1",5598 "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1",
5071 15599 1
5072 ],5600 ],
5073 [5601 [
5074 "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1",5602 "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1",
5075 15603 1
5076 ],5604 ],
5077 [5605 [
5078 "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1",5606 "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1",
5079 15607 1
5080 ],5608 ],
5081 [5609 [
5082 "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1",5610 "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1",
5083 15611 1
5084 ],5612 ],
5085 [5613 [
5086 "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1",5614 "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1",
5087 15615 1
5088 ],5616 ],
5089 [5617 [
5090 "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1",5618 "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1",
5091 15619 1
5092 ],5620 ],
5093 [5621 [
5094 "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1",5622 "{AE89305A-73F4-46B5-B41D-18884685D896},1",
5095 15623 1
5096 ],5624 ],
5097 [5625 [
5098 "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1",5626 "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1",
5099 15627 1
5100 ],5628 ],
5101 [5629 [
5102 "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1",5630 "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1",
5103 15631 1
5104 ],5632 ],
5105 [5633 [
5106 "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1",5634 "{42010094-E377-4A97-9395-AD45808D0552},1",
5107 15635 1
5108 ],5636 ],
5109 [5637 [
5110 "{7E9041BD-301B-4B5B-9456-1C92050E4914},1",5638 "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1",
5111 15639 1
5112 ],5640 ],
5113 [5641 [
5114 "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1",5642 "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1",
5115 15643 1
5116 ],5644 ],
5117 [5645 [
5118 "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1",5646 "{439B80E0-9D4C-4A2F-9163-4E301491364D},1",
5119 15647 1
5120 ],5648 ],
5121 [5649 [
5122 "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1",5650 "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1",
5123 15651 1
5124 ],5652 ],
5125 [5653 [
5126 "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1",5654 "{A459C474-E6EB-4BE5-9193-271799B2860B},1",
5127 15655 1
5128 ],5656 ],
5129 [5657 [
5130 "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1",5658 "{4ED14CEE-8560-4125-B488-9945C116741E},1",
5131 15659 1
5132 ],5660 ],
5133 [5661 [
5134 "{7F6702B3-C977-436F-82AF-403E2BE950E7},1",5662 "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1",
5135 15663 1
5136 ],5664 ],
5137 [5665 [
5138 "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1",5666 "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1",
5139 15667 1
5140 ],5668 ],
5141 [5669 [
5142 "{754FA957-C432-4502-A5CF-EE44449462C0},1",5670 "{9B755368-1D49-4D7E-8D33-6DC536F57848},1",
5143 15671 1
5144 ],5672 ],
5145 [5673 [
5146 "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1",5674 "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1",
5147 15675 1
5148 ],5676 ],
5149 [5677 [
5150 "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1",5678 "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1",
5151 15679 1
5152 ],5680 ],
5153 [5681 [
5154 "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1",5682 "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1",
5155 15683 1
5156 ],5684 ],
5157 [5685 [
5158 "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1",5686 "{C6E54364-6CD0-4764-8180-873030B9EA53},1",
5159 15687 1
5160 ],5688 ],
5161 [5689 [
5162 "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1",5690 "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1",
5163 15691 1
5164 ],5692 ],
5165 [5693 [
5166 "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1",5694 "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1",
5167 15695 1
5168 ],5696 ],
5169 [5697 [
5170 "{189EA744-BE13-467A-95BA-2667A134C0B0},1",5698 "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1",
5171 15699 1
5172 ],5700 ],
5173 [5701 [
5174 "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1",5702 "{00B10C58-6649-428D-A081-3EBBA6E88E97},1",
5175 15703 1
5176 ],5704 ],
5177 [5705 [
5178 "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1",5706 "{58E99482-710A-4938-8CFB-88A1C9B946C9},1",
5179 15707 1
5180 ],5708 ],
5181 [5709 [
5182 "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1",5710 "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1",
5183 15711 1
5184 ],5712 ],
5185 [5713 [
5186 "{2B736B19-2D9C-4E76-A732-478D533D717A},1",5714 "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1",
5187 15715 1
5188 ]5716 ]
5189 ]5717 ]
5190 }5718 }
5191 },5719 },
5192 "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1"5720 "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1"
5193 },5721 },
5194 {5722 {
5195 "id": 2,5723 "id": 2,
5196 "operation": {5724 "operation": {
5197 "Page": {5725 "Page": {
5198 "after": {5726 "after": {
5199 "created": 134335976560000000,5727 "created": 134348779900000000,
5200 "definitions": {5728 "definitions": {
5201 "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},18": {5729 "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},18": {
5202 "format": {5730 "format": {
5203 "alignment": null,5731 "alignment": null,
5204 "bold": null,5732 "bold": null,
...@@ -5215,6 +5743,7 @@...@@ -5215,6 +5743,7 @@
5215 "line_spacing": null,5743 "line_spacing": null,
5216 "list_spacing": null,5744 "list_spacing": null,
5217 "math": null,5745 "math": null,
5746 "math_object": null,
5218 "rtl": null,5747 "rtl": null,
5219 "space_after": null,5748 "space_after": null,
5220 "space_before": null,5749 "space_before": null,
...@@ -5229,6 +5758,24 @@...@@ -5229,6 +5758,24 @@
5229 }5758 }
5230 }5759 }
5231 },5760 },
5761 "identity": [
5762 175,
5763 234,
5764 69,
5765 208,
5766 233,
5767 172,
5768 79,
5769 75,
5770 151,
5771 155,
5772 227,
5773 72,
5774 164,
5775 173,
5776 230,
5777 147
5778 ],
5232 "margin_origin": [5779 "margin_origin": [
5233 0.0,5780 0.0,
5234 0.05781 0.0
...@@ -5236,7 +5783,7 @@...@@ -5236,7 +5783,7 @@
5236 "objects": [5783 "objects": [
5237 {5784 {
5238 "Outline": {5785 "Outline": {
5239 "id": "{5A041F4B-D387-4A35-8723-9F16A833AC7E},0",5786 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},26",
5240 "indents": [],5787 "indents": [],
5241 "layout": {5788 "layout": {
5242 "max_height": null,5789 "max_height": null,
...@@ -5253,7 +5800,7 @@...@@ -5253,7 +5800,7 @@
5253 "content": {5800 "content": {
5254 "Text": {5801 "Text": {
5255 "date_field": null,5802 "date_field": null,
5256 "id": "{8A12422A-9108-4831-9889-D95B1B47F85A},0",5803 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},25",
5257 "tags": [],5804 "tags": [],
5258 "text": {5805 "text": {
5259 "spans": [5806 "spans": [
...@@ -5275,6 +5822,7 @@...@@ -5275,6 +5822,7 @@
5275 "line_spacing": null,5822 "line_spacing": null,
5276 "list_spacing": null,5823 "list_spacing": null,
5277 "math": null,5824 "math": null,
5825 "math_object": null,
5278 "rtl": null,5826 "rtl": null,
5279 "space_after": null,5827 "space_after": null,
5280 "space_before": null,5828 "space_before": null,
...@@ -5305,6 +5853,7 @@...@@ -5305,6 +5853,7 @@
5305 "line_spacing": null,5853 "line_spacing": null,
5306 "list_spacing": null,5854 "list_spacing": null,
5307 "math": null,5855 "math": null,
5856 "math_object": null,
5308 "rtl": null,5857 "rtl": null,
5309 "space_after": null,5858 "space_after": null,
5310 "space_before": null,5859 "space_before": null,
...@@ -5313,9 +5862,13 @@...@@ -5313,9 +5862,13 @@
5313 "superscript": null,5862 "superscript": null,
5314 "underline": null5863 "underline": null
5315 },5864 },
5316 "id": "{8906C7DF-89B1-40E4-9D47-6C794C39F974},0",5865 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},27",
5317 "level": 1,5866 "level": 1,
5318 "lists": [],5867 "lists": [],
5868 "media": {
5869 "recordings": [],
5870 "time_ms": null
5871 },
5319 "parent": null,5872 "parent": null,
5320 "style": null,5873 "style": null,
5321 "tags": []5874 "tags": []
...@@ -5328,7 +5881,7 @@...@@ -5328,7 +5881,7 @@
5328 {5881 {
5329 "Title": {5882 "Title": {
5330 "date": null,5883 "date": null,
5331 "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},13",5884 "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},13",
5332 "layout": {5885 "layout": {
5333 "max_height": null,5886 "max_height": null,
5334 "max_width": null,5887 "max_width": null,
...@@ -5339,24 +5892,29 @@...@@ -5339,24 +5892,29 @@
5339 },5892 },
5340 "outlines": [5893 "outlines": [
5341 {5894 {
5342 "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},14",5895 "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},14",
5343 "indents": [],5896 "indents": [
5897 18.0,
5898 0.0,
5899 27.0,
5900 27.0
5901 ],
5344 "layout": {5902 "layout": {
5345 "max_height": null,5903 "max_height": 21.600000381469727,
5346 "max_width": null,5904 "max_width": null,
5347 "reserved_width": null,5905 "reserved_width": null,
5348 "width_set_by_user": null,5906 "width_set_by_user": null,
5349 "x": null,5907 "x": null,
5350 "y": null5908 "y": null
5351 },5909 },
5352 "min_width": null,5910 "min_width": 162.0,
5353 "paragraphs": [5911 "paragraphs": [
5354 {5912 {
5355 "collapsed": false,5913 "collapsed": false,
5356 "content": {5914 "content": {
5357 "Text": {5915 "Text": {
5358 "date_field": null,5916 "date_field": null,
5359 "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},16",5917 "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},16",
5360 "tags": [],5918 "tags": [],
5361 "text": {5919 "text": {
5362 "spans": [5920 "spans": [
...@@ -5378,6 +5936,7 @@...@@ -5378,6 +5936,7 @@
5378 "line_spacing": null,5936 "line_spacing": null,
5379 "list_spacing": null,5937 "list_spacing": null,
5380 "math": null,5938 "math": null,
5939 "math_object": null,
5381 "rtl": null,5940 "rtl": null,
5382 "space_after": null,5941 "space_after": null,
5383 "space_before": null,5942 "space_before": null,
...@@ -5408,6 +5967,7 @@...@@ -5408,6 +5967,7 @@
5408 "line_spacing": null,5967 "line_spacing": null,
5409 "list_spacing": null,5968 "list_spacing": null,
5410 "math": null,5969 "math": null,
5970 "math_object": null,
5411 "rtl": null,5971 "rtl": null,
5412 "space_after": null,5972 "space_after": null,
5413 "space_before": null,5973 "space_before": null,
...@@ -5416,15 +5976,19 @@...@@ -5416,15 +5976,19 @@
5416 "superscript": null,5976 "superscript": null,
5417 "underline": null5977 "underline": null
5418 },5978 },
5419 "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},15",5979 "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},15",
5420 "level": 1,5980 "level": 1,
5421 "lists": [],5981 "lists": [],
5982 "media": {
5983 "recordings": [],
5984 "time_ms": null
5985 },
5422 "parent": null,5986 "parent": null,
5423 "style": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},18",5987 "style": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},18",
5424 "tags": []5988 "tags": []
5425 }5989 }
5426 ],5990 ],
5427 "title": false,5991 "title": true,
5428 "unsupported": []5992 "unsupported": []
5429 }5993 }
5430 ]5994 ]
...@@ -5435,9 +5999,9 @@...@@ -5435,9 +5999,9 @@
5435 },5999 },
5436 "author": "Author",6000 "author": "Author",
5437 "before": {6001 "before": {
5438 "created": 134335976560000000,6002 "created": 134348779900000000,
5439 "definitions": {6003 "definitions": {
5440 "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},18": {6004 "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},18": {
5441 "format": {6005 "format": {
5442 "alignment": null,6006 "alignment": null,
5443 "bold": null,6007 "bold": null,
...@@ -5454,6 +6018,7 @@...@@ -5454,6 +6018,7 @@
5454 "line_spacing": null,6018 "line_spacing": null,
5455 "list_spacing": null,6019 "list_spacing": null,
5456 "math": null,6020 "math": null,
6021 "math_object": null,
5457 "rtl": null,6022 "rtl": null,
5458 "space_after": null,6023 "space_after": null,
5459 "space_before": null,6024 "space_before": null,
...@@ -5468,6 +6033,24 @@...@@ -5468,6 +6033,24 @@
5468 }6033 }
5469 }6034 }
5470 },6035 },
6036 "identity": [
6037 175,
6038 234,
6039 69,
6040 208,
6041 233,
6042 172,
6043 79,
6044 75,
6045 151,
6046 155,
6047 227,
6048 72,
6049 164,
6050 173,
6051 230,
6052 147
6053 ],
5471 "margin_origin": [6054 "margin_origin": [
5472 0.0,6055 0.0,
5473 0.06056 0.0
...@@ -5476,7 +6059,7 @@...@@ -5476,7 +6059,7 @@
5476 {6059 {
5477 "Title": {6060 "Title": {
5478 "date": null,6061 "date": null,
5479 "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},13",6062 "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},13",
5480 "layout": {6063 "layout": {
5481 "max_height": null,6064 "max_height": null,
5482 "max_width": null,6065 "max_width": null,
...@@ -5487,24 +6070,29 @@...@@ -5487,24 +6070,29 @@
5487 },6070 },
5488 "outlines": [6071 "outlines": [
5489 {6072 {
5490 "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},14",6073 "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},14",
5491 "indents": [],6074 "indents": [
6075 18.0,
6076 0.0,
6077 27.0,
6078 27.0
6079 ],
5492 "layout": {6080 "layout": {
5493 "max_height": null,6081 "max_height": 21.600000381469727,
5494 "max_width": null,6082 "max_width": null,
5495 "reserved_width": null,6083 "reserved_width": null,
5496 "width_set_by_user": null,6084 "width_set_by_user": null,
5497 "x": null,6085 "x": null,
5498 "y": null6086 "y": null
5499 },6087 },
5500 "min_width": null,6088 "min_width": 162.0,
5501 "paragraphs": [6089 "paragraphs": [
5502 {6090 {
5503 "collapsed": false,6091 "collapsed": false,
5504 "content": {6092 "content": {
5505 "Text": {6093 "Text": {
5506 "date_field": null,6094 "date_field": null,
5507 "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},16",6095 "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},16",
5508 "tags": [],6096 "tags": [],
5509 "text": {6097 "text": {
5510 "spans": [6098 "spans": [
...@@ -5526,6 +6114,7 @@...@@ -5526,6 +6114,7 @@
5526 "line_spacing": null,6114 "line_spacing": null,
5527 "list_spacing": null,6115 "list_spacing": null,
5528 "math": null,6116 "math": null,
6117 "math_object": null,
5529 "rtl": null,6118 "rtl": null,
5530 "space_after": null,6119 "space_after": null,
5531 "space_before": null,6120 "space_before": null,
...@@ -5556,6 +6145,7 @@...@@ -5556,6 +6145,7 @@
5556 "line_spacing": null,6145 "line_spacing": null,
5557 "list_spacing": null,6146 "list_spacing": null,
5558 "math": null,6147 "math": null,
6148 "math_object": null,
5559 "rtl": null,6149 "rtl": null,
5560 "space_after": null,6150 "space_after": null,
5561 "space_before": null,6151 "space_before": null,
...@@ -5564,15 +6154,19 @@...@@ -5564,15 +6154,19 @@
5564 "superscript": null,6154 "superscript": null,
5565 "underline": null6155 "underline": null
5566 },6156 },
5567 "id": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},15",6157 "id": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},15",
5568 "level": 1,6158 "level": 1,
5569 "lists": [],6159 "lists": [],
6160 "media": {
6161 "recordings": [],
6162 "time_ms": null
6163 },
5570 "parent": null,6164 "parent": null,
5571 "style": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},18",6165 "style": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},18",
5572 "tags": []6166 "tags": []
5573 }6167 }
5574 ],6168 ],
5575 "title": false,6169 "title": true,
5576 "unsupported": []6170 "unsupported": []
5577 }6171 }
5578 ]6172 ]
...@@ -5583,7 +6177,7 @@...@@ -5583,7 +6177,7 @@
5583 }6177 }
5584 }6178 }
5585 },6179 },
5586 "space": "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1"6180 "space": "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1"
5587 }6181 }
5588 ],6182 ],
5589 [6183 [
...@@ -5594,215 +6188,215 @@...@@ -5594,215 +6188,215 @@
5594 "edits": [6188 "edits": [
5595 {6189 {
5596 "guid": [6190 "guid": [
5597 99,
5598 82,
5599 79,
5600 67,
5601 136,
5602 178,
5603 110,
5604 79,
5605 138,
5606 9,6191 9,
5607 172,
5608 171,
5609 67,
5610 243,6192 243,
5611 71,6193 107,
5612 1426194 19,
6195 20,
6196 192,
6197 128,
6198 78,
6199 179,
6200 30,
6201 140,
6202 253,
6203 10,
6204 214,
6205 221,
6206 155
5613 ],6207 ],
5614 "level": 1,6208 "level": 1,
5615 "position": {6209 "position": {
5616 "Before": "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1"6210 "Before": "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1"
5617 },6211 },
5618 "space": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1"6212 "space": "{C6E54364-6CD0-4764-8180-873030B9EA53},1"
5619 },6213 },
5620 {6214 {
5621 "guid": [6215 "guid": [
5622 198,6216 9,
5623 58,6217 192,
5624 35,6218 29,
5625 75,6219 108,
5626 157,6220 238,
5627 18,6221 78,
5628 195,6222 236,
5629 77,6223 72,
5630 154,6224 128,
5631 80,6225 200,
5632 148,6226 112,
5633 1,6227 224,
5634 210,6228 246,
5635 118,6229 115,
5636 195,6230 92,
5637 1156231 133
5638 ],6232 ],
5639 "level": 2,6233 "level": 2,
5640 "position": "Keep",6234 "position": "Keep",
5641 "space": "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1"6235 "space": "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1"
5642 }6236 }
5643 ],6237 ],
5644 "observed": [6238 "observed": [
5645 [6239 [
5646 "{869D5929-6E5E-4EDB-9639-4C56977DD447},1",6240 "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1",
5647 16241 1
5648 ],6242 ],
5649 [6243 [
5650 "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1",6244 "{74492C4A-164B-40E9-B297-4D3B4685347D},1",
5651 16245 1
5652 ],6246 ],
5653 [6247 [
5654 "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1",6248 "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1",
5655 16249 1
5656 ],6250 ],
5657 [6251 [
5658 "{C837A033-F055-4455-B45F-17D82016921C},1",6252 "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1",
5659 16253 1
5660 ],6254 ],
5661 [6255 [
5662 "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1",6256 "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1",
5663 16257 1
5664 ],6258 ],
5665 [6259 [
5666 "{57E835EA-2799-4118-B55C-7BB1E51C6941},1",6260 "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1",
5667 16261 1
5668 ],6262 ],
5669 [6263 [
5670 "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1",6264 "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1",
5671 16265 1
5672 ],6266 ],
5673 [6267 [
5674 "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1",6268 "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1",
5675 16269 1
5676 ],6270 ],
5677 [6271 [
5678 "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1",6272 "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1",
5679 16273 1
5680 ],6274 ],
5681 [6275 [
5682 "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1",6276 "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1",
5683 16277 1
5684 ],6278 ],
5685 [6279 [
5686 "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1",6280 "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1",
5687 16281 1
5688 ],6282 ],
5689 [6283 [
5690 "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1",6284 "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1",
5691 16285 1
5692 ],6286 ],
5693 [6287 [
5694 "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1",6288 "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1",
5695 16289 1
5696 ],6290 ],
5697 [6291 [
5698 "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1",6292 "{AE89305A-73F4-46B5-B41D-18884685D896},1",
5699 16293 1
5700 ],6294 ],
5701 [6295 [
5702 "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1",6296 "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1",
5703 16297 1
5704 ],6298 ],
5705 [6299 [
5706 "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1",6300 "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1",
5707 16301 1
5708 ],6302 ],
5709 [6303 [
5710 "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1",6304 "{42010094-E377-4A97-9395-AD45808D0552},1",
5711 16305 1
5712 ],6306 ],
5713 [6307 [
5714 "{7E9041BD-301B-4B5B-9456-1C92050E4914},1",6308 "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1",
5715 16309 1
5716 ],6310 ],
5717 [6311 [
5718 "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1",6312 "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1",
5719 16313 1
5720 ],6314 ],
5721 [6315 [
5722 "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1",6316 "{439B80E0-9D4C-4A2F-9163-4E301491364D},1",
5723 16317 1
5724 ],6318 ],
5725 [6319 [
5726 "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1",6320 "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1",
5727 16321 1
5728 ],6322 ],
5729 [6323 [
5730 "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1",6324 "{A459C474-E6EB-4BE5-9193-271799B2860B},1",
5731 16325 1
5732 ],6326 ],
5733 [6327 [
5734 "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1",6328 "{4ED14CEE-8560-4125-B488-9945C116741E},1",
5735 16329 1
5736 ],6330 ],
5737 [6331 [
5738 "{7F6702B3-C977-436F-82AF-403E2BE950E7},1",6332 "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1",
5739 16333 1
5740 ],6334 ],
5741 [6335 [
5742 "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1",6336 "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1",
5743 16337 1
5744 ],6338 ],
5745 [6339 [
5746 "{754FA957-C432-4502-A5CF-EE44449462C0},1",6340 "{9B755368-1D49-4D7E-8D33-6DC536F57848},1",
5747 16341 1
5748 ],6342 ],
5749 [6343 [
5750 "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1",6344 "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1",
5751 16345 1
5752 ],6346 ],
5753 [6347 [
5754 "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1",6348 "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1",
5755 16349 1
5756 ],6350 ],
5757 [6351 [
5758 "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1",6352 "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1",
5759 16353 1
5760 ],6354 ],
5761 [6355 [
5762 "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1",6356 "{C6E54364-6CD0-4764-8180-873030B9EA53},1",
5763 16357 1
5764 ],6358 ],
5765 [6359 [
5766 "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1",6360 "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1",
5767 16361 1
5768 ],6362 ],
5769 [6363 [
5770 "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1",6364 "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1",
5771 16365 1
5772 ],6366 ],
5773 [6367 [
5774 "{189EA744-BE13-467A-95BA-2667A134C0B0},1",6368 "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1",
5775 16369 1
5776 ],6370 ],
5777 [6371 [
5778 "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1",6372 "{00B10C58-6649-428D-A081-3EBBA6E88E97},1",
5779 16373 1
5780 ],6374 ],
5781 [6375 [
5782 "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1",6376 "{58E99482-710A-4938-8CFB-88A1C9B946C9},1",
5783 16377 1
5784 ],6378 ],
5785 [6379 [
5786 "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1",6380 "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1",
5787 16381 1
5788 ],6382 ],
5789 [6383 [
5790 "{2B736B19-2D9C-4E76-A732-478D533D717A},1",6384 "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1",
5791 16385 1
5792 ]6386 ]
5793 ]6387 ]
5794 }6388 }
5795 },6389 },
5796 "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1"6390 "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1"
5797 },6391 },
5798 {6392 {
5799 "id": 2,6393 "id": 2,
5800 "operation": {6394 "operation": {
5801 "Page": {6395 "Page": {
5802 "after": {6396 "after": {
5803 "created": 134335976560000000,6397 "created": 134348779900000000,
5804 "definitions": {6398 "definitions": {
5805 "{406687D7-F9E9-491B-85DA-166DE6CD5A60},18": {6399 "{C6E54364-6CD0-4764-8180-873030B9EA53},18": {
5806 "format": {6400 "format": {
5807 "alignment": null,6401 "alignment": null,
5808 "bold": null,6402 "bold": null,
...@@ -5819,6 +6413,7 @@...@@ -5819,6 +6413,7 @@
5819 "line_spacing": null,6413 "line_spacing": null,
5820 "list_spacing": null,6414 "list_spacing": null,
5821 "math": null,6415 "math": null,
6416 "math_object": null,
5822 "rtl": null,6417 "rtl": null,
5823 "space_after": null,6418 "space_after": null,
5824 "space_before": null,6419 "space_before": null,
...@@ -5833,6 +6428,24 @@...@@ -5833,6 +6428,24 @@
5833 }6428 }
5834 }6429 }
5835 },6430 },
6431 "identity": [
6432 100,
6433 67,
6434 229,
6435 198,
6436 208,
6437 108,
6438 100,
6439 71,
6440 129,
6441 128,
6442 135,
6443 48,
6444 48,
6445 185,
6446 234,
6447 83
6448 ],
5836 "margin_origin": [6449 "margin_origin": [
5837 0.0,6450 0.0,
5838 0.06451 0.0
...@@ -5840,7 +6453,7 @@...@@ -5840,7 +6453,7 @@
5840 "objects": [6453 "objects": [
5841 {6454 {
5842 "Outline": {6455 "Outline": {
5843 "id": "{69906E37-5C35-4F2F-B74F-B5D45950739B},0",6456 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},29",
5844 "indents": [],6457 "indents": [],
5845 "layout": {6458 "layout": {
5846 "max_height": null,6459 "max_height": null,
...@@ -5857,7 +6470,7 @@...@@ -5857,7 +6470,7 @@
5857 "content": {6470 "content": {
5858 "Text": {6471 "Text": {
5859 "date_field": null,6472 "date_field": null,
5860 "id": "{3A6E4FCA-F6C5-4418-ADF4-9E92782F26CD},0",6473 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},28",
5861 "tags": [],6474 "tags": [],
5862 "text": {6475 "text": {
5863 "spans": [6476 "spans": [
...@@ -5879,6 +6492,7 @@...@@ -5879,6 +6492,7 @@
5879 "line_spacing": null,6492 "line_spacing": null,
5880 "list_spacing": null,6493 "list_spacing": null,
5881 "math": null,6494 "math": null,
6495 "math_object": null,
5882 "rtl": null,6496 "rtl": null,
5883 "space_after": null,6497 "space_after": null,
5884 "space_before": null,6498 "space_before": null,
...@@ -5909,6 +6523,7 @@...@@ -5909,6 +6523,7 @@
5909 "line_spacing": null,6523 "line_spacing": null,
5910 "list_spacing": null,6524 "list_spacing": null,
5911 "math": null,6525 "math": null,
6526 "math_object": null,
5912 "rtl": null,6527 "rtl": null,
5913 "space_after": null,6528 "space_after": null,
5914 "space_before": null,6529 "space_before": null,
...@@ -5917,9 +6532,13 @@...@@ -5917,9 +6532,13 @@
5917 "superscript": null,6532 "superscript": null,
5918 "underline": null6533 "underline": null
5919 },6534 },
5920 "id": "{FFE94E14-C2A1-4CB1-BBDB-0D72059C0A9D},0",6535 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},30",
5921 "level": 1,6536 "level": 1,
5922 "lists": [],6537 "lists": [],
6538 "media": {
6539 "recordings": [],
6540 "time_ms": null
6541 },
5923 "parent": null,6542 "parent": null,
5924 "style": null,6543 "style": null,
5925 "tags": []6544 "tags": []
...@@ -5932,7 +6551,7 @@...@@ -5932,7 +6551,7 @@
5932 {6551 {
5933 "Title": {6552 "Title": {
5934 "date": null,6553 "date": null,
5935 "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},13",6554 "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},13",
5936 "layout": {6555 "layout": {
5937 "max_height": null,6556 "max_height": null,
5938 "max_width": null,6557 "max_width": null,
...@@ -5943,24 +6562,29 @@...@@ -5943,24 +6562,29 @@
5943 },6562 },
5944 "outlines": [6563 "outlines": [
5945 {6564 {
5946 "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},14",6565 "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},14",
5947 "indents": [],6566 "indents": [
6567 18.0,
6568 0.0,
6569 27.0,
6570 27.0
6571 ],
5948 "layout": {6572 "layout": {
5949 "max_height": null,6573 "max_height": 21.600000381469727,
5950 "max_width": null,6574 "max_width": null,
5951 "reserved_width": null,6575 "reserved_width": null,
5952 "width_set_by_user": null,6576 "width_set_by_user": null,
5953 "x": null,6577 "x": null,
5954 "y": null6578 "y": null
5955 },6579 },
5956 "min_width": null,6580 "min_width": 162.0,
5957 "paragraphs": [6581 "paragraphs": [
5958 {6582 {
5959 "collapsed": false,6583 "collapsed": false,
5960 "content": {6584 "content": {
5961 "Text": {6585 "Text": {
5962 "date_field": null,6586 "date_field": null,
5963 "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},16",6587 "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},16",
5964 "tags": [],6588 "tags": [],
5965 "text": {6589 "text": {
5966 "spans": [6590 "spans": [
...@@ -5982,6 +6606,7 @@...@@ -5982,6 +6606,7 @@
5982 "line_spacing": null,6606 "line_spacing": null,
5983 "list_spacing": null,6607 "list_spacing": null,
5984 "math": null,6608 "math": null,
6609 "math_object": null,
5985 "rtl": null,6610 "rtl": null,
5986 "space_after": null,6611 "space_after": null,
5987 "space_before": null,6612 "space_before": null,
...@@ -6012,6 +6637,7 @@...@@ -6012,6 +6637,7 @@
6012 "line_spacing": null,6637 "line_spacing": null,
6013 "list_spacing": null,6638 "list_spacing": null,
6014 "math": null,6639 "math": null,
6640 "math_object": null,
6015 "rtl": null,6641 "rtl": null,
6016 "space_after": null,6642 "space_after": null,
6017 "space_before": null,6643 "space_before": null,
...@@ -6020,15 +6646,19 @@...@@ -6020,15 +6646,19 @@
6020 "superscript": null,6646 "superscript": null,
6021 "underline": null6647 "underline": null
6022 },6648 },
6023 "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},15",6649 "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},15",
6024 "level": 1,6650 "level": 1,
6025 "lists": [],6651 "lists": [],
6652 "media": {
6653 "recordings": [],
6654 "time_ms": null
6655 },
6026 "parent": null,6656 "parent": null,
6027 "style": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},18",6657 "style": "{C6E54364-6CD0-4764-8180-873030B9EA53},18",
6028 "tags": []6658 "tags": []
6029 }6659 }
6030 ],6660 ],
6031 "title": false,6661 "title": true,
6032 "unsupported": []6662 "unsupported": []
6033 }6663 }
6034 ]6664 ]
...@@ -6039,9 +6669,9 @@...@@ -6039,9 +6669,9 @@
6039 },6669 },
6040 "author": "Author",6670 "author": "Author",
6041 "before": {6671 "before": {
6042 "created": 134335976560000000,6672 "created": 134348779900000000,
6043 "definitions": {6673 "definitions": {
6044 "{406687D7-F9E9-491B-85DA-166DE6CD5A60},18": {6674 "{C6E54364-6CD0-4764-8180-873030B9EA53},18": {
6045 "format": {6675 "format": {
6046 "alignment": null,6676 "alignment": null,
6047 "bold": null,6677 "bold": null,
...@@ -6058,6 +6688,7 @@...@@ -6058,6 +6688,7 @@
6058 "line_spacing": null,6688 "line_spacing": null,
6059 "list_spacing": null,6689 "list_spacing": null,
6060 "math": null,6690 "math": null,
6691 "math_object": null,
6061 "rtl": null,6692 "rtl": null,
6062 "space_after": null,6693 "space_after": null,
6063 "space_before": null,6694 "space_before": null,
...@@ -6072,6 +6703,24 @@...@@ -6072,6 +6703,24 @@
6072 }6703 }
6073 }6704 }
6074 },6705 },
6706 "identity": [
6707 100,
6708 67,
6709 229,
6710 198,
6711 208,
6712 108,
6713 100,
6714 71,
6715 129,
6716 128,
6717 135,
6718 48,
6719 48,
6720 185,
6721 234,
6722 83
6723 ],
6075 "margin_origin": [6724 "margin_origin": [
6076 0.0,6725 0.0,
6077 0.06726 0.0
...@@ -6080,7 +6729,7 @@...@@ -6080,7 +6729,7 @@
6080 {6729 {
6081 "Title": {6730 "Title": {
6082 "date": null,6731 "date": null,
6083 "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},13",6732 "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},13",
6084 "layout": {6733 "layout": {
6085 "max_height": null,6734 "max_height": null,
6086 "max_width": null,6735 "max_width": null,
...@@ -6091,24 +6740,29 @@...@@ -6091,24 +6740,29 @@
6091 },6740 },
6092 "outlines": [6741 "outlines": [
6093 {6742 {
6094 "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},14",6743 "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},14",
6095 "indents": [],6744 "indents": [
6745 18.0,
6746 0.0,
6747 27.0,
6748 27.0
6749 ],
6096 "layout": {6750 "layout": {
6097 "max_height": null,6751 "max_height": 21.600000381469727,
6098 "max_width": null,6752 "max_width": null,
6099 "reserved_width": null,6753 "reserved_width": null,
6100 "width_set_by_user": null,6754 "width_set_by_user": null,
6101 "x": null,6755 "x": null,
6102 "y": null6756 "y": null
6103 },6757 },
6104 "min_width": null,6758 "min_width": 162.0,
6105 "paragraphs": [6759 "paragraphs": [
6106 {6760 {
6107 "collapsed": false,6761 "collapsed": false,
6108 "content": {6762 "content": {
6109 "Text": {6763 "Text": {
6110 "date_field": null,6764 "date_field": null,
6111 "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},16",6765 "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},16",
6112 "tags": [],6766 "tags": [],
6113 "text": {6767 "text": {
6114 "spans": [6768 "spans": [
...@@ -6130,6 +6784,7 @@...@@ -6130,6 +6784,7 @@
6130 "line_spacing": null,6784 "line_spacing": null,
6131 "list_spacing": null,6785 "list_spacing": null,
6132 "math": null,6786 "math": null,
6787 "math_object": null,
6133 "rtl": null,6788 "rtl": null,
6134 "space_after": null,6789 "space_after": null,
6135 "space_before": null,6790 "space_before": null,
...@@ -6160,6 +6815,7 @@...@@ -6160,6 +6815,7 @@
6160 "line_spacing": null,6815 "line_spacing": null,
6161 "list_spacing": null,6816 "list_spacing": null,
6162 "math": null,6817 "math": null,
6818 "math_object": null,
6163 "rtl": null,6819 "rtl": null,
6164 "space_after": null,6820 "space_after": null,
6165 "space_before": null,6821 "space_before": null,
...@@ -6168,15 +6824,19 @@...@@ -6168,15 +6824,19 @@
6168 "superscript": null,6824 "superscript": null,
6169 "underline": null6825 "underline": null
6170 },6826 },
6171 "id": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},15",6827 "id": "{C6E54364-6CD0-4764-8180-873030B9EA53},15",
6172 "level": 1,6828 "level": 1,
6173 "lists": [],6829 "lists": [],
6830 "media": {
6831 "recordings": [],
6832 "time_ms": null
6833 },
6174 "parent": null,6834 "parent": null,
6175 "style": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},18",6835 "style": "{C6E54364-6CD0-4764-8180-873030B9EA53},18",
6176 "tags": []6836 "tags": []
6177 }6837 }
6178 ],6838 ],
6179 "title": false,6839 "title": true,
6180 "unsupported": []6840 "unsupported": []
6181 }6841 }
6182 ]6842 ]
...@@ -6187,7 +6847,7 @@...@@ -6187,7 +6847,7 @@
6187 }6847 }
6188 }6848 }
6189 },6849 },
6190 "space": "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1"6850 "space": "{C6E54364-6CD0-4764-8180-873030B9EA53},1"
6191 }6851 }
6192 ],6852 ],
6193 [6853 [
...@@ -6198,215 +6858,215 @@...@@ -6198,215 +6858,215 @@
6198 "edits": [6858 "edits": [
6199 {6859 {
6200 "guid": [6860 "guid": [
6201 99,6861 223,
6202 113,6862 187,
6203 79,
6204 243,
6205 201,
6206 17,6863 17,
6207 203,6864 5,
6208 78,6865 4,
6209 185,6866 237,
6210 122,6867 130,
6211 45,6868 69,
6212 83,6869 161,
6213 216,6870 20,
6214 140,6871 5,
6215 115,6872 179,
6216 526873 161,
6874 87,
6875 94,
6876 148
6217 ],6877 ],
6218 "level": 1,6878 "level": 1,
6219 "position": {6879 "position": {
6220 "Before": "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1"6880 "Before": "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1"
6221 },6881 },
6222 "space": "{189EA744-BE13-467A-95BA-2667A134C0B0},1"6882 "space": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1"
6223 },6883 },
6224 {6884 {
6225 "guid": [6885 "guid": [
6226 240,6886 219,
6227 160,6887 20,
6228 93,6888 156,
6229 114,6889 115,
6230 5,6890 106,
6231 25,6891 105,
6232 128,6892 134,
6233 75,6893 64,
6234 161,6894 131,
6235 252,6895 56,
6236 39,6896 1,
6237 123,6897 213,
6238 203,6898 21,
6239 137,6899 147,
6240 98,6900 139,
6241 1066901 107
6242 ],6902 ],
6243 "level": 2,6903 "level": 2,
6244 "position": "Keep",6904 "position": "Keep",
6245 "space": "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1"6905 "space": "{00B10C58-6649-428D-A081-3EBBA6E88E97},1"
6246 }6906 }
6247 ],6907 ],
6248 "observed": [6908 "observed": [
6249 [6909 [
6250 "{869D5929-6E5E-4EDB-9639-4C56977DD447},1",6910 "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1",
6251 16911 1
6252 ],6912 ],
6253 [6913 [
6254 "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1",6914 "{74492C4A-164B-40E9-B297-4D3B4685347D},1",
6255 16915 1
6256 ],6916 ],
6257 [6917 [
6258 "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1",6918 "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1",
6259 16919 1
6260 ],6920 ],
6261 [6921 [
6262 "{C837A033-F055-4455-B45F-17D82016921C},1",6922 "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1",
6263 16923 1
6264 ],6924 ],
6265 [6925 [
6266 "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1",6926 "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1",
6267 16927 1
6268 ],6928 ],
6269 [6929 [
6270 "{57E835EA-2799-4118-B55C-7BB1E51C6941},1",6930 "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1",
6271 16931 1
6272 ],6932 ],
6273 [6933 [
6274 "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1",6934 "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1",
6275 16935 1
6276 ],6936 ],
6277 [6937 [
6278 "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1",6938 "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1",
6279 16939 1
6280 ],6940 ],
6281 [6941 [
6282 "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1",6942 "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1",
6283 16943 1
6284 ],6944 ],
6285 [6945 [
6286 "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1",6946 "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1",
6287 16947 1
6288 ],6948 ],
6289 [6949 [
6290 "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1",6950 "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1",
6291 16951 1
6292 ],6952 ],
6293 [6953 [
6294 "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1",6954 "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1",
6295 16955 1
6296 ],6956 ],
6297 [6957 [
6298 "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1",6958 "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1",
6299 16959 1
6300 ],6960 ],
6301 [6961 [
6302 "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1",6962 "{AE89305A-73F4-46B5-B41D-18884685D896},1",
6303 16963 1
6304 ],6964 ],
6305 [6965 [
6306 "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1",6966 "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1",
6307 16967 1
6308 ],6968 ],
6309 [6969 [
6310 "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1",6970 "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1",
6311 16971 1
6312 ],6972 ],
6313 [6973 [
6314 "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1",6974 "{42010094-E377-4A97-9395-AD45808D0552},1",
6315 16975 1
6316 ],6976 ],
6317 [6977 [
6318 "{7E9041BD-301B-4B5B-9456-1C92050E4914},1",6978 "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1",
6319 16979 1
6320 ],6980 ],
6321 [6981 [
6322 "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1",6982 "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1",
6323 16983 1
6324 ],6984 ],
6325 [6985 [
6326 "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1",6986 "{439B80E0-9D4C-4A2F-9163-4E301491364D},1",
6327 16987 1
6328 ],6988 ],
6329 [6989 [
6330 "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1",6990 "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1",
6331 16991 1
6332 ],6992 ],
6333 [6993 [
6334 "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1",6994 "{A459C474-E6EB-4BE5-9193-271799B2860B},1",
6335 16995 1
6336 ],6996 ],
6337 [6997 [
6338 "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1",6998 "{4ED14CEE-8560-4125-B488-9945C116741E},1",
6339 16999 1
6340 ],7000 ],
6341 [7001 [
6342 "{7F6702B3-C977-436F-82AF-403E2BE950E7},1",7002 "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1",
6343 17003 1
6344 ],7004 ],
6345 [7005 [
6346 "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1",7006 "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1",
6347 17007 1
6348 ],7008 ],
6349 [7009 [
6350 "{754FA957-C432-4502-A5CF-EE44449462C0},1",7010 "{9B755368-1D49-4D7E-8D33-6DC536F57848},1",
6351 17011 1
6352 ],7012 ],
6353 [7013 [
6354 "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1",7014 "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1",
6355 17015 1
6356 ],7016 ],
6357 [7017 [
6358 "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1",7018 "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1",
6359 17019 1
6360 ],7020 ],
6361 [7021 [
6362 "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1",7022 "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1",
6363 17023 1
6364 ],7024 ],
6365 [7025 [
6366 "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1",7026 "{C6E54364-6CD0-4764-8180-873030B9EA53},1",
6367 17027 1
6368 ],7028 ],
6369 [7029 [
6370 "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1",7030 "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1",
6371 17031 1
6372 ],7032 ],
6373 [7033 [
6374 "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1",7034 "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1",
6375 17035 1
6376 ],7036 ],
6377 [7037 [
6378 "{189EA744-BE13-467A-95BA-2667A134C0B0},1",7038 "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1",
6379 17039 1
6380 ],7040 ],
6381 [7041 [
6382 "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1",7042 "{00B10C58-6649-428D-A081-3EBBA6E88E97},1",
6383 17043 1
6384 ],7044 ],
6385 [7045 [
6386 "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1",7046 "{58E99482-710A-4938-8CFB-88A1C9B946C9},1",
6387 17047 1
6388 ],7048 ],
6389 [7049 [
6390 "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1",7050 "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1",
6391 17051 1
6392 ],7052 ],
6393 [7053 [
6394 "{2B736B19-2D9C-4E76-A732-478D533D717A},1",7054 "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1",
6395 17055 1
6396 ]7056 ]
6397 ]7057 ]
6398 }7058 }
6399 },7059 },
6400 "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1"7060 "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1"
6401 },7061 },
6402 {7062 {
6403 "id": 2,7063 "id": 2,
6404 "operation": {7064 "operation": {
6405 "Page": {7065 "Page": {
6406 "after": {7066 "after": {
6407 "created": 134335976560000000,7067 "created": 134348779900000000,
6408 "definitions": {7068 "definitions": {
6409 "{189EA744-BE13-467A-95BA-2667A134C0B0},18": {7069 "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},18": {
6410 "format": {7070 "format": {
6411 "alignment": null,7071 "alignment": null,
6412 "bold": null,7072 "bold": null,
...@@ -6423,6 +7083,7 @@...@@ -6423,6 +7083,7 @@
6423 "line_spacing": null,7083 "line_spacing": null,
6424 "list_spacing": null,7084 "list_spacing": null,
6425 "math": null,7085 "math": null,
7086 "math_object": null,
6426 "rtl": null,7087 "rtl": null,
6427 "space_after": null,7088 "space_after": null,
6428 "space_before": null,7089 "space_before": null,
...@@ -6437,6 +7098,24 @@...@@ -6437,6 +7098,24 @@
6437 }7098 }
6438 }7099 }
6439 },7100 },
7101 "identity": [
7102 141,
7103 116,
7104 241,
7105 137,
7106 73,
7107 103,
7108 208,
7109 74,
7110 186,
7111 246,
7112 7,
7113 233,
7114 249,
7115 192,
7116 88,
7117 229
7118 ],
6440 "margin_origin": [7119 "margin_origin": [
6441 0.0,7120 0.0,
6442 0.07121 0.0
...@@ -6444,7 +7123,7 @@...@@ -6444,7 +7123,7 @@
6444 "objects": [7123 "objects": [
6445 {7124 {
6446 "Outline": {7125 "Outline": {
6447 "id": "{A05DF797-7FAA-4593-A1F4-7E35812A5257},0",7126 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},32",
6448 "indents": [],7127 "indents": [],
6449 "layout": {7128 "layout": {
6450 "max_height": null,7129 "max_height": null,
...@@ -6461,7 +7140,7 @@...@@ -6461,7 +7140,7 @@
6461 "content": {7140 "content": {
6462 "Text": {7141 "Text": {
6463 "date_field": null,7142 "date_field": null,
6464 "id": "{D134F5D0-A81E-4663-9AE8-5FF66C8882F1},0",7143 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},31",
6465 "tags": [],7144 "tags": [],
6466 "text": {7145 "text": {
6467 "spans": [7146 "spans": [
...@@ -6483,6 +7162,7 @@...@@ -6483,6 +7162,7 @@
6483 "line_spacing": null,7162 "line_spacing": null,
6484 "list_spacing": null,7163 "list_spacing": null,
6485 "math": null,7164 "math": null,
7165 "math_object": null,
6486 "rtl": null,7166 "rtl": null,
6487 "space_after": null,7167 "space_after": null,
6488 "space_before": null,7168 "space_before": null,
...@@ -6513,6 +7193,7 @@...@@ -6513,6 +7193,7 @@
6513 "line_spacing": null,7193 "line_spacing": null,
6514 "list_spacing": null,7194 "list_spacing": null,
6515 "math": null,7195 "math": null,
7196 "math_object": null,
6516 "rtl": null,7197 "rtl": null,
6517 "space_after": null,7198 "space_after": null,
6518 "space_before": null,7199 "space_before": null,
...@@ -6521,9 +7202,13 @@...@@ -6521,9 +7202,13 @@
6521 "superscript": null,7202 "superscript": null,
6522 "underline": null7203 "underline": null
6523 },7204 },
6524 "id": "{D783BABC-D1D5-45A9-86F8-1169830488FC},0",7205 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},33",
6525 "level": 1,7206 "level": 1,
6526 "lists": [],7207 "lists": [],
7208 "media": {
7209 "recordings": [],
7210 "time_ms": null
7211 },
6527 "parent": null,7212 "parent": null,
6528 "style": null,7213 "style": null,
6529 "tags": []7214 "tags": []
...@@ -6536,7 +7221,7 @@...@@ -6536,7 +7221,7 @@
6536 {7221 {
6537 "Title": {7222 "Title": {
6538 "date": null,7223 "date": null,
6539 "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},13",7224 "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},13",
6540 "layout": {7225 "layout": {
6541 "max_height": null,7226 "max_height": null,
6542 "max_width": null,7227 "max_width": null,
...@@ -6547,24 +7232,29 @@...@@ -6547,24 +7232,29 @@
6547 },7232 },
6548 "outlines": [7233 "outlines": [
6549 {7234 {
6550 "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},14",7235 "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},14",
6551 "indents": [],7236 "indents": [
7237 18.0,
7238 0.0,
7239 27.0,
7240 27.0
7241 ],
6552 "layout": {7242 "layout": {
6553 "max_height": null,7243 "max_height": 21.600000381469727,
6554 "max_width": null,7244 "max_width": null,
6555 "reserved_width": null,7245 "reserved_width": null,
6556 "width_set_by_user": null,7246 "width_set_by_user": null,
6557 "x": null,7247 "x": null,
6558 "y": null7248 "y": null
6559 },7249 },
6560 "min_width": null,7250 "min_width": 162.0,
6561 "paragraphs": [7251 "paragraphs": [
6562 {7252 {
6563 "collapsed": false,7253 "collapsed": false,
6564 "content": {7254 "content": {
6565 "Text": {7255 "Text": {
6566 "date_field": null,7256 "date_field": null,
6567 "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},16",7257 "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},16",
6568 "tags": [],7258 "tags": [],
6569 "text": {7259 "text": {
6570 "spans": [7260 "spans": [
...@@ -6586,6 +7276,7 @@...@@ -6586,6 +7276,7 @@
6586 "line_spacing": null,7276 "line_spacing": null,
6587 "list_spacing": null,7277 "list_spacing": null,
6588 "math": null,7278 "math": null,
7279 "math_object": null,
6589 "rtl": null,7280 "rtl": null,
6590 "space_after": null,7281 "space_after": null,
6591 "space_before": null,7282 "space_before": null,
...@@ -6616,6 +7307,7 @@...@@ -6616,6 +7307,7 @@
6616 "line_spacing": null,7307 "line_spacing": null,
6617 "list_spacing": null,7308 "list_spacing": null,
6618 "math": null,7309 "math": null,
7310 "math_object": null,
6619 "rtl": null,7311 "rtl": null,
6620 "space_after": null,7312 "space_after": null,
6621 "space_before": null,7313 "space_before": null,
...@@ -6624,15 +7316,19 @@...@@ -6624,15 +7316,19 @@
6624 "superscript": null,7316 "superscript": null,
6625 "underline": null7317 "underline": null
6626 },7318 },
6627 "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},15",7319 "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},15",
6628 "level": 1,7320 "level": 1,
6629 "lists": [],7321 "lists": [],
7322 "media": {
7323 "recordings": [],
7324 "time_ms": null
7325 },
6630 "parent": null,7326 "parent": null,
6631 "style": "{189EA744-BE13-467A-95BA-2667A134C0B0},18",7327 "style": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},18",
6632 "tags": []7328 "tags": []
6633 }7329 }
6634 ],7330 ],
6635 "title": false,7331 "title": true,
6636 "unsupported": []7332 "unsupported": []
6637 }7333 }
6638 ]7334 ]
...@@ -6643,9 +7339,9 @@...@@ -6643,9 +7339,9 @@
6643 },7339 },
6644 "author": "Author",7340 "author": "Author",
6645 "before": {7341 "before": {
6646 "created": 134335976560000000,7342 "created": 134348779900000000,
6647 "definitions": {7343 "definitions": {
6648 "{189EA744-BE13-467A-95BA-2667A134C0B0},18": {7344 "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},18": {
6649 "format": {7345 "format": {
6650 "alignment": null,7346 "alignment": null,
6651 "bold": null,7347 "bold": null,
...@@ -6662,6 +7358,7 @@...@@ -6662,6 +7358,7 @@
6662 "line_spacing": null,7358 "line_spacing": null,
6663 "list_spacing": null,7359 "list_spacing": null,
6664 "math": null,7360 "math": null,
7361 "math_object": null,
6665 "rtl": null,7362 "rtl": null,
6666 "space_after": null,7363 "space_after": null,
6667 "space_before": null,7364 "space_before": null,
...@@ -6676,6 +7373,24 @@...@@ -6676,6 +7373,24 @@
6676 }7373 }
6677 }7374 }
6678 },7375 },
7376 "identity": [
7377 141,
7378 116,
7379 241,
7380 137,
7381 73,
7382 103,
7383 208,
7384 74,
7385 186,
7386 246,
7387 7,
7388 233,
7389 249,
7390 192,
7391 88,
7392 229
7393 ],
6679 "margin_origin": [7394 "margin_origin": [
6680 0.0,7395 0.0,
6681 0.07396 0.0
...@@ -6684,7 +7399,7 @@...@@ -6684,7 +7399,7 @@
6684 {7399 {
6685 "Title": {7400 "Title": {
6686 "date": null,7401 "date": null,
6687 "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},13",7402 "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},13",
6688 "layout": {7403 "layout": {
6689 "max_height": null,7404 "max_height": null,
6690 "max_width": null,7405 "max_width": null,
...@@ -6695,24 +7410,29 @@...@@ -6695,24 +7410,29 @@
6695 },7410 },
6696 "outlines": [7411 "outlines": [
6697 {7412 {
6698 "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},14",7413 "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},14",
6699 "indents": [],7414 "indents": [
7415 18.0,
7416 0.0,
7417 27.0,
7418 27.0
7419 ],
6700 "layout": {7420 "layout": {
6701 "max_height": null,7421 "max_height": 21.600000381469727,
6702 "max_width": null,7422 "max_width": null,
6703 "reserved_width": null,7423 "reserved_width": null,
6704 "width_set_by_user": null,7424 "width_set_by_user": null,
6705 "x": null,7425 "x": null,
6706 "y": null7426 "y": null
6707 },7427 },
6708 "min_width": null,7428 "min_width": 162.0,
6709 "paragraphs": [7429 "paragraphs": [
6710 {7430 {
6711 "collapsed": false,7431 "collapsed": false,
6712 "content": {7432 "content": {
6713 "Text": {7433 "Text": {
6714 "date_field": null,7434 "date_field": null,
6715 "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},16",7435 "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},16",
6716 "tags": [],7436 "tags": [],
6717 "text": {7437 "text": {
6718 "spans": [7438 "spans": [
...@@ -6734,6 +7454,7 @@...@@ -6734,6 +7454,7 @@
6734 "line_spacing": null,7454 "line_spacing": null,
6735 "list_spacing": null,7455 "list_spacing": null,
6736 "math": null,7456 "math": null,
7457 "math_object": null,
6737 "rtl": null,7458 "rtl": null,
6738 "space_after": null,7459 "space_after": null,
6739 "space_before": null,7460 "space_before": null,
...@@ -6764,6 +7485,7 @@...@@ -6764,6 +7485,7 @@
6764 "line_spacing": null,7485 "line_spacing": null,
6765 "list_spacing": null,7486 "list_spacing": null,
6766 "math": null,7487 "math": null,
7488 "math_object": null,
6767 "rtl": null,7489 "rtl": null,
6768 "space_after": null,7490 "space_after": null,
6769 "space_before": null,7491 "space_before": null,
...@@ -6772,15 +7494,19 @@...@@ -6772,15 +7494,19 @@
6772 "superscript": null,7494 "superscript": null,
6773 "underline": null7495 "underline": null
6774 },7496 },
6775 "id": "{189EA744-BE13-467A-95BA-2667A134C0B0},15",7497 "id": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},15",
6776 "level": 1,7498 "level": 1,
6777 "lists": [],7499 "lists": [],
7500 "media": {
7501 "recordings": [],
7502 "time_ms": null
7503 },
6778 "parent": null,7504 "parent": null,
6779 "style": "{189EA744-BE13-467A-95BA-2667A134C0B0},18",7505 "style": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},18",
6780 "tags": []7506 "tags": []
6781 }7507 }
6782 ],7508 ],
6783 "title": false,7509 "title": true,
6784 "unsupported": []7510 "unsupported": []
6785 }7511 }
6786 ]7512 ]
...@@ -6791,7 +7517,7 @@...@@ -6791,7 +7517,7 @@
6791 }7517 }
6792 }7518 }
6793 },7519 },
6794 "space": "{189EA744-BE13-467A-95BA-2667A134C0B0},1"7520 "space": "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1"
6795 }7521 }
6796 ],7522 ],
6797 [7523 [
...@@ -6802,215 +7528,215 @@...@@ -6802,215 +7528,215 @@
6802 "edits": [7528 "edits": [
6803 {7529 {
6804 "guid": [7530 "guid": [
6805 137,7531 69,
6806 150,7532 87,
6807 22,7533 190,
6808 112,7534 154,
6809 113,7535 161,
6810 130,7536 45,
6811 213,7537 245,
6812 65,7538 76,
6813 143,7539 131,
6814 6,7540 158,
6815 73,7541 203,
6816 88,7542 234,
6817 176,7543 216,
6818 192,7544 244,
6819 23,7545 254,
6820 1197546 235
6821 ],7547 ],
6822 "level": 1,7548 "level": 1,
6823 "position": {7549 "position": {
6824 "Before": "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1"7550 "Before": "{58E99482-710A-4938-8CFB-88A1C9B946C9},1"
6825 },7551 },
6826 "space": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1"7552 "space": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1"
6827 },7553 },
6828 {7554 {
6829 "guid": [7555 "guid": [
6830 65,7556 35,
7557 252,
7558 68,
7559 95,
7560 80,
7561 200,
7562 215,
6831 77,7563 77,
6832 116,7564 190,
6833 151,7565 11,
6834 20,7566 114,
6835 16,7567 173,
6836 115,7568 3,
6837 66,7569 70,
6838 138,7570 77,
6839 169,7571 30
6840 25,
6841 24,
6842 111,
6843 125,
6844 154,
6845 123
6846 ],7572 ],
6847 "level": 2,7573 "level": 2,
6848 "position": "Keep",7574 "position": "Keep",
6849 "space": "{2B736B19-2D9C-4E76-A732-478D533D717A},1"7575 "space": "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1"
6850 }7576 }
6851 ],7577 ],
6852 "observed": [7578 "observed": [
6853 [7579 [
6854 "{869D5929-6E5E-4EDB-9639-4C56977DD447},1",7580 "{0B37E922-551A-48F8-BCAC-4AB3503B4D28},1",
6855 17581 1
6856 ],7582 ],
6857 [7583 [
6858 "{B0B53F94-0105-4C59-B65B-9473F49BFF41},1",7584 "{74492C4A-164B-40E9-B297-4D3B4685347D},1",
6859 17585 1
6860 ],7586 ],
6861 [7587 [
6862 "{0E98EE82-B78F-4872-8BD9-5D9B23A817C5},1",7588 "{DAFF533D-608B-4735-BA1D-2354E8CB8A69},1",
6863 17589 1
6864 ],7590 ],
6865 [7591 [
6866 "{C837A033-F055-4455-B45F-17D82016921C},1",7592 "{8B766F11-2A4F-4D95-8473-D3CA68129E11},1",
6867 17593 1
6868 ],7594 ],
6869 [7595 [
6870 "{89D86121-BF5C-418B-BB92-CBFFA8EF545F},1",7596 "{28E0CC5F-68BC-4A86-9ED7-114FFD3BAE9A},1",
6871 17597 1
6872 ],7598 ],
6873 [7599 [
6874 "{57E835EA-2799-4118-B55C-7BB1E51C6941},1",7600 "{31FFA5F9-C1B8-412D-A2D6-7ACC3CC16FD0},1",
6875 17601 1
6876 ],7602 ],
6877 [7603 [
6878 "{F7EECCCF-B6B4-44F2-A543-36837EC18B8A},1",7604 "{AFCD09F6-1085-4582-BDB2-4B91E15D2088},1",
6879 17605 1
6880 ],7606 ],
6881 [7607 [
6882 "{361A0F09-EA63-4F2D-9620-C061D4E4707E},1",7608 "{A94D5882-C902-4CAE-B38E-2ED56EEF177F},1",
6883 17609 1
6884 ],7610 ],
6885 [7611 [
6886 "{EB3940F1-F255-42BE-8C31-DD09E86DBAAF},1",7612 "{5AB37A0F-5702-4C8E-A8C6-79ED6ED1E69F},1",
6887 17613 1
6888 ],7614 ],
6889 [7615 [
6890 "{C2870D07-8ADC-44BB-9F12-15D6C3708689},1",7616 "{F212795E-0E4B-4844-A8A6-ABD7F7025124},1",
6891 17617 1
6892 ],7618 ],
6893 [7619 [
6894 "{1E0C6331-32C9-4EF9-8087-150A8669FA91},1",7620 "{1786F2CA-0A43-4081-8B55-B207EEE0331C},1",
6895 17621 1
6896 ],7622 ],
6897 [7623 [
6898 "{ED9769DD-99D6-4BE2-B980-580DA9C4DFCE},1",7624 "{3E966EF8-B7B4-483F-B241-8F21DF318AB3},1",
6899 17625 1
6900 ],7626 ],
6901 [7627 [
6902 "{17C30054-7129-4A10-9D00-4D5545E4F4BA},1",7628 "{0D361C76-92FD-4B76-901B-809C2C3C3CE6},1",
6903 17629 1
6904 ],7630 ],
6905 [7631 [
6906 "{4C5FBA5B-E348-4750-ACE4-E9694D527BFF},1",7632 "{AE89305A-73F4-46B5-B41D-18884685D896},1",
6907 17633 1
6908 ],7634 ],
6909 [7635 [
6910 "{BD0EF658-4D0F-42A7-B546-B4F4A77D246B},1",7636 "{551D2637-2AA7-4923-B2DD-31C2E1489FD2},1",
6911 17637 1
6912 ],7638 ],
6913 [7639 [
6914 "{6FEA70FA-1451-4A27-8A8E-C4CC3B713CF6},1",7640 "{CBF82C86-3A8B-457F-92A4-1AC7BF436CE9},1",
6915 17641 1
6916 ],7642 ],
6917 [7643 [
6918 "{68A1E0E1-7A39-4AB6-AD71-4D8A18878D79},1",7644 "{42010094-E377-4A97-9395-AD45808D0552},1",
6919 17645 1
6920 ],7646 ],
6921 [7647 [
6922 "{7E9041BD-301B-4B5B-9456-1C92050E4914},1",7648 "{0F6ABB5E-A021-4E1D-B187-7A72E74000DC},1",
6923 17649 1
6924 ],7650 ],
6925 [7651 [
6926 "{EFA77BAE-8A98-4176-B536-B615D1D2ED2B},1",7652 "{F9DA3139-5856-4B11-95C0-356683E3ACFE},1",
6927 17653 1
6928 ],7654 ],
6929 [7655 [
6930 "{87AC0239-51A5-47C6-BAF4-926CCF56385C},1",7656 "{439B80E0-9D4C-4A2F-9163-4E301491364D},1",
6931 17657 1
6932 ],7658 ],
6933 [7659 [
6934 "{C50BF7FC-F848-4B91-B8C8-743D0AA5F195},1",7660 "{B03B973C-50BC-4B9A-9914-B1D5C0CF6197},1",
6935 17661 1
6936 ],7662 ],
6937 [7663 [
6938 "{FD8D07D0-C447-4516-A7DB-F8522B77D4A7},1",7664 "{A459C474-E6EB-4BE5-9193-271799B2860B},1",
6939 17665 1
6940 ],7666 ],
6941 [7667 [
6942 "{D2E1FE57-F45D-49C3-B388-95222ACA3886},1",7668 "{4ED14CEE-8560-4125-B488-9945C116741E},1",
6943 17669 1
6944 ],7670 ],
6945 [7671 [
6946 "{7F6702B3-C977-436F-82AF-403E2BE950E7},1",7672 "{C4FF8FE4-4C43-461F-98DD-EB1FF2A863B2},1",
6947 17673 1
6948 ],7674 ],
6949 [7675 [
6950 "{688C4EA1-80F5-4D85-B9BB-F8633FF3C0CA},1",7676 "{932BE37F-05AA-4EFB-85D8-A33445E738B3},1",
6951 17677 1
6952 ],7678 ],
6953 [7679 [
6954 "{754FA957-C432-4502-A5CF-EE44449462C0},1",7680 "{9B755368-1D49-4D7E-8D33-6DC536F57848},1",
6955 17681 1
6956 ],7682 ],
6957 [7683 [
6958 "{D2197C55-D962-4DF0-8B14-26F88C06D6FA},1",7684 "{D045EAAF-ACE9-4B4F-979B-E348A4ADE693},1",
6959 17685 1
6960 ],7686 ],
6961 [7687 [
6962 "{87B83BA8-A906-4F71-A6CA-3E8133A75D34},1",7688 "{5CA4C59F-B400-4BC6-8B90-30ECAC35CB43},1",
6963 17689 1
6964 ],7690 ],
6965 [7691 [
6966 "{F2D6AEBB-98EA-4F10-991F-5243E74AF450},1",7692 "{D63FB383-FB37-44F9-AEBB-E868E903A2BB},1",
6967 17693 1
6968 ],7694 ],
6969 [7695 [
6970 "{406687D7-F9E9-491B-85DA-166DE6CD5A60},1",7696 "{C6E54364-6CD0-4764-8180-873030B9EA53},1",
6971 17697 1
6972 ],7698 ],
6973 [7699 [
6974 "{1F73BF36-9FC2-495D-844D-B370D5C47DE7},1",7700 "{DEEC391B-F2D1-4EB1-BEB1-57598DFBB982},1",
6975 17701 1
6976 ],7702 ],
6977 [7703 [
6978 "{E9BD343F-4A66-4DE2-81F9-3789CCA22818},1",7704 "{9E7616ED-DE19-4625-8EA0-0C5DEB18B690},1",
6979 17705 1
6980 ],7706 ],
6981 [7707 [
6982 "{189EA744-BE13-467A-95BA-2667A134C0B0},1",7708 "{89F1748D-6749-4AD0-BAF6-07E9F9C058E5},1",
6983 17709 1
6984 ],7710 ],
6985 [7711 [
6986 "{017F5881-F818-4C7C-B9F9-6BCB4085F1D0},1",7712 "{00B10C58-6649-428D-A081-3EBBA6E88E97},1",
6987 17713 1
6988 ],7714 ],
6989 [7715 [
6990 "{CD0D17F4-25D8-4076-9AF7-47D9CDEFCF0A},1",7716 "{58E99482-710A-4938-8CFB-88A1C9B946C9},1",
6991 17717 1
6992 ],7718 ],
6993 [7719 [
6994 "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1",7720 "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1",
6995 17721 1
6996 ],7722 ],
6997 [7723 [
6998 "{2B736B19-2D9C-4E76-A732-478D533D717A},1",7724 "{15A45C01-A9E8-42E2-A267-C7FCE728815E},1",
6999 17725 1
7000 ]7726 ]
7001 ]7727 ]
7002 }7728 }
7003 },7729 },
7004 "space": "{90FCDB0C-36AE-422F-B4FE-67676826FE36},1"7730 "space": "{A5A2AFBE-1DF1-4190-AAC7-1AD29C9564F2},1"
7005 },7731 },
7006 {7732 {
7007 "id": 2,7733 "id": 2,
7008 "operation": {7734 "operation": {
7009 "Page": {7735 "Page": {
7010 "after": {7736 "after": {
7011 "created": 134335976560000000,7737 "created": 134348779900000000,
7012 "definitions": {7738 "definitions": {
7013 "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},18": {7739 "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},18": {
7014 "format": {7740 "format": {
7015 "alignment": null,7741 "alignment": null,
7016 "bold": null,7742 "bold": null,
...@@ -7027,6 +7753,7 @@...@@ -7027,6 +7753,7 @@
7027 "line_spacing": null,7753 "line_spacing": null,
7028 "list_spacing": null,7754 "list_spacing": null,
7029 "math": null,7755 "math": null,
7756 "math_object": null,
7030 "rtl": null,7757 "rtl": null,
7031 "space_after": null,7758 "space_after": null,
7032 "space_before": null,7759 "space_before": null,
...@@ -7041,6 +7768,24 @@...@@ -7041,6 +7768,24 @@
7041 }7768 }
7042 }7769 }
7043 },7770 },
7771 "identity": [
7772 213,
7773 201,
7774 33,
7775 247,
7776 106,
7777 154,
7778 95,
7779 64,
7780 148,
7781 201,
7782 155,
7783 133,
7784 167,
7785 238,
7786 185,
7787 128
7788 ],
7044 "margin_origin": [7789 "margin_origin": [
7045 0.0,7790 0.0,
7046 0.07791 0.0
...@@ -7048,7 +7793,7 @@...@@ -7048,7 +7793,7 @@
7048 "objects": [7793 "objects": [
7049 {7794 {
7050 "Outline": {7795 "Outline": {
7051 "id": "{00E55A56-1362-4A11-ACD3-2117BCCE619E},0",7796 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},35",
7052 "indents": [],7797 "indents": [],
7053 "layout": {7798 "layout": {
7054 "max_height": null,7799 "max_height": null,
...@@ -7065,7 +7810,7 @@...@@ -7065,7 +7810,7 @@
7065 "content": {7810 "content": {
7066 "Text": {7811 "Text": {
7067 "date_field": null,7812 "date_field": null,
7068 "id": "{AD3FBA83-B057-48DF-9731-DEBD6F95BDBE},0",7813 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},34",
7069 "tags": [],7814 "tags": [],
7070 "text": {7815 "text": {
7071 "spans": [7816 "spans": [
...@@ -7087,6 +7832,7 @@...@@ -7087,6 +7832,7 @@
7087 "line_spacing": null,7832 "line_spacing": null,
7088 "list_spacing": null,7833 "list_spacing": null,
7089 "math": null,7834 "math": null,
7835 "math_object": null,
7090 "rtl": null,7836 "rtl": null,
7091 "space_after": null,7837 "space_after": null,
7092 "space_before": null,7838 "space_before": null,
...@@ -7117,6 +7863,7 @@...@@ -7117,6 +7863,7 @@
7117 "line_spacing": null,7863 "line_spacing": null,
7118 "list_spacing": null,7864 "list_spacing": null,
7119 "math": null,7865 "math": null,
7866 "math_object": null,
7120 "rtl": null,7867 "rtl": null,
7121 "space_after": null,7868 "space_after": null,
7122 "space_before": null,7869 "space_before": null,
...@@ -7125,9 +7872,13 @@...@@ -7125,9 +7872,13 @@
7125 "superscript": null,7872 "superscript": null,
7126 "underline": null7873 "underline": null
7127 },7874 },
7128 "id": "{77E85A17-9EF5-4C79-B11B-48A5CE7FE0FD},0",7875 "id": "{91379CE6-91DA-48BA-B306-77E4CEB068B6},36",
7129 "level": 1,7876 "level": 1,
7130 "lists": [],7877 "lists": [],
7878 "media": {
7879 "recordings": [],
7880 "time_ms": null
7881 },
7131 "parent": null,7882 "parent": null,
7132 "style": null,7883 "style": null,
7133 "tags": []7884 "tags": []
...@@ -7140,7 +7891,7 @@...@@ -7140,7 +7891,7 @@
7140 {7891 {
7141 "Title": {7892 "Title": {
7142 "date": null,7893 "date": null,
7143 "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},13",7894 "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},13",
7144 "layout": {7895 "layout": {
7145 "max_height": null,7896 "max_height": null,
7146 "max_width": null,7897 "max_width": null,
...@@ -7151,24 +7902,29 @@...@@ -7151,24 +7902,29 @@
7151 },7902 },
7152 "outlines": [7903 "outlines": [
7153 {7904 {
7154 "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},14",7905 "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},14",
7155 "indents": [],7906 "indents": [
7907 18.0,
7908 0.0,
7909 27.0,
7910 27.0
7911 ],
7156 "layout": {7912 "layout": {
7157 "max_height": null,7913 "max_height": 21.600000381469727,
7158 "max_width": null,7914 "max_width": null,
7159 "reserved_width": null,7915 "reserved_width": null,
7160 "width_set_by_user": null,7916 "width_set_by_user": null,
7161 "x": null,7917 "x": null,
7162 "y": null7918 "y": null
7163 },7919 },
7164 "min_width": null,7920 "min_width": 162.0,
7165 "paragraphs": [7921 "paragraphs": [
7166 {7922 {
7167 "collapsed": false,7923 "collapsed": false,
7168 "content": {7924 "content": {
7169 "Text": {7925 "Text": {
7170 "date_field": null,7926 "date_field": null,
7171 "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},16",7927 "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},16",
7172 "tags": [],7928 "tags": [],
7173 "text": {7929 "text": {
7174 "spans": [7930 "spans": [
...@@ -7190,6 +7946,7 @@...@@ -7190,6 +7946,7 @@
7190 "line_spacing": null,7946 "line_spacing": null,
7191 "list_spacing": null,7947 "list_spacing": null,
7192 "math": null,7948 "math": null,
7949 "math_object": null,
7193 "rtl": null,7950 "rtl": null,
7194 "space_after": null,7951 "space_after": null,
7195 "space_before": null,7952 "space_before": null,
...@@ -7220,6 +7977,7 @@...@@ -7220,6 +7977,7 @@
7220 "line_spacing": null,7977 "line_spacing": null,
7221 "list_spacing": null,7978 "list_spacing": null,
7222 "math": null,7979 "math": null,
7980 "math_object": null,
7223 "rtl": null,7981 "rtl": null,
7224 "space_after": null,7982 "space_after": null,
7225 "space_before": null,7983 "space_before": null,
...@@ -7228,15 +7986,19 @@...@@ -7228,15 +7986,19 @@
7228 "superscript": null,7986 "superscript": null,
7229 "underline": null7987 "underline": null
7230 },7988 },
7231 "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},15",7989 "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},15",
7232 "level": 1,7990 "level": 1,
7233 "lists": [],7991 "lists": [],
7992 "media": {
7993 "recordings": [],
7994 "time_ms": null
7995 },
7234 "parent": null,7996 "parent": null,
7235 "style": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},18",7997 "style": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},18",
7236 "tags": []7998 "tags": []
7237 }7999 }
7238 ],8000 ],
7239 "title": false,8001 "title": true,
7240 "unsupported": []8002 "unsupported": []
7241 }8003 }
7242 ]8004 ]
...@@ -7247,9 +8009,9 @@...@@ -7247,9 +8009,9 @@
7247 },8009 },
7248 "author": "Author",8010 "author": "Author",
7249 "before": {8011 "before": {
7250 "created": 134335976560000000,8012 "created": 134348779900000000,
7251 "definitions": {8013 "definitions": {
7252 "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},18": {8014 "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},18": {
7253 "format": {8015 "format": {
7254 "alignment": null,8016 "alignment": null,
7255 "bold": null,8017 "bold": null,
...@@ -7266,6 +8028,7 @@...@@ -7266,6 +8028,7 @@
7266 "line_spacing": null,8028 "line_spacing": null,
7267 "list_spacing": null,8029 "list_spacing": null,
7268 "math": null,8030 "math": null,
8031 "math_object": null,
7269 "rtl": null,8032 "rtl": null,
7270 "space_after": null,8033 "space_after": null,
7271 "space_before": null,8034 "space_before": null,
...@@ -7280,6 +8043,24 @@...@@ -7280,6 +8043,24 @@
7280 }8043 }
7281 }8044 }
7282 },8045 },
8046 "identity": [
8047 213,
8048 201,
8049 33,
8050 247,
8051 106,
8052 154,
8053 95,
8054 64,
8055 148,
8056 201,
8057 155,
8058 133,
8059 167,
8060 238,
8061 185,
8062 128
8063 ],
7283 "margin_origin": [8064 "margin_origin": [
7284 0.0,8065 0.0,
7285 0.08066 0.0
...@@ -7288,7 +8069,7 @@...@@ -7288,7 +8069,7 @@
7288 {8069 {
7289 "Title": {8070 "Title": {
7290 "date": null,8071 "date": null,
7291 "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},13",8072 "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},13",
7292 "layout": {8073 "layout": {
7293 "max_height": null,8074 "max_height": null,
7294 "max_width": null,8075 "max_width": null,
...@@ -7299,24 +8080,29 @@...@@ -7299,24 +8080,29 @@
7299 },8080 },
7300 "outlines": [8081 "outlines": [
7301 {8082 {
7302 "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},14",8083 "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},14",
7303 "indents": [],8084 "indents": [
8085 18.0,
8086 0.0,
8087 27.0,
8088 27.0
8089 ],
7304 "layout": {8090 "layout": {
7305 "max_height": null,8091 "max_height": 21.600000381469727,
7306 "max_width": null,8092 "max_width": null,
7307 "reserved_width": null,8093 "reserved_width": null,
7308 "width_set_by_user": null,8094 "width_set_by_user": null,
7309 "x": null,8095 "x": null,
7310 "y": null8096 "y": null
7311 },8097 },
7312 "min_width": null,8098 "min_width": 162.0,
7313 "paragraphs": [8099 "paragraphs": [
7314 {8100 {
7315 "collapsed": false,8101 "collapsed": false,
7316 "content": {8102 "content": {
7317 "Text": {8103 "Text": {
7318 "date_field": null,8104 "date_field": null,
7319 "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},16",8105 "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},16",
7320 "tags": [],8106 "tags": [],
7321 "text": {8107 "text": {
7322 "spans": [8108 "spans": [
...@@ -7338,6 +8124,7 @@...@@ -7338,6 +8124,7 @@
7338 "line_spacing": null,8124 "line_spacing": null,
7339 "list_spacing": null,8125 "list_spacing": null,
7340 "math": null,8126 "math": null,
8127 "math_object": null,
7341 "rtl": null,8128 "rtl": null,
7342 "space_after": null,8129 "space_after": null,
7343 "space_before": null,8130 "space_before": null,
...@@ -7368,6 +8155,7 @@...@@ -7368,6 +8155,7 @@
7368 "line_spacing": null,8155 "line_spacing": null,
7369 "list_spacing": null,8156 "list_spacing": null,
7370 "math": null,8157 "math": null,
8158 "math_object": null,
7371 "rtl": null,8159 "rtl": null,
7372 "space_after": null,8160 "space_after": null,
7373 "space_before": null,8161 "space_before": null,
...@@ -7376,15 +8164,19 @@...@@ -7376,15 +8164,19 @@
7376 "superscript": null,8164 "superscript": null,
7377 "underline": null8165 "underline": null
7378 },8166 },
7379 "id": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},15",8167 "id": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},15",
7380 "level": 1,8168 "level": 1,
7381 "lists": [],8169 "lists": [],
8170 "media": {
8171 "recordings": [],
8172 "time_ms": null
8173 },
7382 "parent": null,8174 "parent": null,
7383 "style": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},18",8175 "style": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},18",
7384 "tags": []8176 "tags": []
7385 }8177 }
7386 ],8178 ],
7387 "title": false,8179 "title": true,
7388 "unsupported": []8180 "unsupported": []
7389 }8181 }
7390 ]8182 ]
...@@ -7395,7 +8187,7 @@...@@ -7395,7 +8187,7 @@
7395 }8187 }
7396 }8188 }
7397 },8189 },
7398 "space": "{CCBB5969-BCE8-498B-BE1A-98FD3A44DB1A},1"8190 "space": "{F721C9D5-9A6A-405F-94C9-9B85A7EEB980},1"
7399 }8191 }
7400 ]8192 ]
7401 ],8193 ],
corpus/page-lifecycle/offline-edits/twelve-clients/candidate/pages.one
Binary files a/corpus/page-lifecycle/offline-edits/twelve-clients/candidate/pages.one and b/corpus/page-lifecycle/offline-edits/twelve-clients/candidate/pages.one differ
corpus/page-lifecycle/offline-edits/twelve-clients/cold/commands.jsonl+1-1
...@@ -1,3 +1,3 @@...@@ -1,3 +1,3 @@
1{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}1{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
2{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-16C2E4D4 -ExpectedPages 37", "exit": 0, "stdout": "Read 1 sections and 37 pages.\r\n", "stderr": "", "error": null}2{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-8EABB42E -ExpectedPages 37", "exit": 0, "stdout": "Read 1 sections and 37 pages.\r\n", "stderr": "", "error": null}
3{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}3{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
corpus/page-lifecycle/offline-edits/twelve-clients/cold/machine.json+1-1
...@@ -1 +1 @@...@@ -1 +1 @@
1{"name": "m6-16c2e4d4", "hostname": "ONE-M6-16C2E4D4"}1{"name": "m6-8eabb42e", "hostname": "ONE-M6-8EABB42E"}
corpus/page-lifecycle/offline-edits/twelve-clients/cold/notebook/Open Notebook.onetoc2
Binary files a/corpus/page-lifecycle/offline-edits/twelve-clients/cold/notebook/Open Notebook.onetoc2 and b/corpus/page-lifecycle/offline-edits/twelve-clients/cold/notebook/Open Notebook.onetoc2 differ
corpus/page-lifecycle/offline-edits/twelve-clients/cold/notebook/pages.one
Binary files a/corpus/page-lifecycle/offline-edits/twelve-clients/cold/notebook/pages.one and b/corpus/page-lifecycle/offline-edits/twelve-clients/cold/notebook/pages.one differ
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/environment.json+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1{ 1{
2 "powershell": "5.1.14409.1005", 2 "powershell": "5.1.14409.1005",
3 "schema": "xs2010", 3 "schema": "xs2010",
4 "hostname": "ONE-M6-16C2E4D4", 4 "hostname": "ONE-M6-8EABB42E",
5 "cold": true, 5 "cold": true,
6 "onenote": "14.0.4763.1000" 6 "onenote": "14.0.4763.1000"
7} 7}
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/hierarchy.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{DBBB79D4-5B5D-4081-818D-136DFB209E97}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-11T10:55:58.000Z" color="#B49EDE"><one:Section name="pages" ID="{E215CFF8-B98E-0DF4-196B-B38D8F97EB71}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pages.one" lastModifiedTime="2026-09-11T10:55:58.000Z" color="#8AA8E4"><one:Page ID="{F4744DDD-E17E-0100-3BAC-98BC70CCC100}{1}{B0}" name="Sentinel" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{7C71FA76-38AF-07A9-264C-8971C4190282}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{C25C2B60-8E25-0382-1BCE-4099132AEA06}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{BADEB4C7-7F75-0B8E-19CA-C332C7A7875B}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{2501211E-A8B9-0EC3-18C9-AF5B02AD7C06}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{FB3175D5-307C-0E50-1607-1F154F5E4118}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{8507D83B-3994-0B29-08D6-E26999709ECD}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{99D05405-7D75-0D65-21A4-09E30FDCAFE8}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{44F31BFD-6543-00F6-3BB5-148B33556539}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{B06E19F3-05FC-0B60-3287-C13C24C193CE}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{9F7E7D29-16F6-0439-1415-8CE74E75CA89}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{6CE577C5-BDE9-0122-2D12-C1E061D8EFD6}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{652A14A0-FE09-05CB-3095-99BFA255E1FD}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{CFE7E2AC-C22F-0D7C-18D3-601E40CC312C}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{3EB6AEAF-6C68-088B-0171-3D83AAE36EB8}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{1D03640E-9B71-05FC-271B-1026DCC029B1}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{0C795549-BF3B-0480-39C3-C878E2BF5C53}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{1A48F415-F519-056D-00E4-9960FF36983E}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{9D4E6F5A-05B8-0EAD-18A3-62FF3663F86C}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{B7E2E308-7768-044A-155D-A0D7ED14E4D2}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{F54516CD-DE85-081D-1761-468628E72D1B}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{8F641324-4B67-0ACD-0A4E-2CB8CCC6C1E0}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{0D8E1647-4657-0CB4-2F3A-94D4CC5845A0}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{A008EAA3-7B7D-0618-1E1D-41C8CD7B2DC1}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{1A655A55-0FD5-025E-142E-2C89D842D58D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"/><one:Page ID="{A0F068A1-5642-022B-2681-F2126BB7C3BD}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{07A6BDA3-4B12-0AD9-085A-3AAEA3257787}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"/><one:Page ID="{F5512F5C-2626-00AA-0B5F-EA6BD4164873}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"/><one:Page ID="{328F9323-76C9-06C0-284F-C287017C4F27}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"/><one:Page ID="{803FBA4F-17CA-00CB-348A-86A900FBE117}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"/><one:Page ID="{6D9AABC2-10E2-0686-29D8-679A327568A0}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"/><one:Page ID="{6A77B3B0-3133-09A1-382F-F28D4685D5F7}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"/><one:Page ID="{9B5420CB-C546-0239-2C6C-E3632B133D5F}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"/><one:Page ID="{73964C75-7738-03A7-146C-BF21A734E497}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"/><one:Page ID="{BE524D9D-33C8-0650-138F-4C17DDF5CE5D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"/><one:Page ID="{BFE40300-AAF8-0FAD-3762-93332A5EDA4D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"/><one:Page ID="{599A7FED-A2BC-01AD-0AA7-9367B48C643D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"/></one:Section></one:Notebook> 2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{885E80BA-2527-46AB-B60B-807DE2E83A73}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:36:34.000Z" color="#B49EDE"><one:Section name="pages" ID="{7FD5C268-3371-0110-1F8E-BEADD565CD56}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pages.one" lastModifiedTime="2026-09-26T06:36:34.000Z" color="#8AA8E4"><one:Page ID="{D14084F4-7B9A-0878-09E5-EECC19CBE48C}{1}{B0}" name="Sentinel" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:24.000Z" pageLevel="1"/><one:Page ID="{00883EEB-4E0B-07B5-0F54-872BA13B23CD}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:24.000Z" pageLevel="1"/><one:Page ID="{AE3E419C-38CB-0069-07DE-E9440F759DD9}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:24.000Z" pageLevel="1"/><one:Page ID="{510102C7-04CF-0D15-313A-77B521E237B5}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{EB88C82F-EF38-01AD-179F-DEB37531C674}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{F297A189-463C-0A06-2B9E-B530B4CB073E}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{75BA6420-3E05-0502-08FB-EFEEA8AD892C}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{80C417D9-7982-0C0E-1D8F-DD9227214F3B}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{733A3554-E782-0C2E-06C7-8AAA271FBEDB}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{28651488-20CB-08C4-1DEF-0FA8BEF2F880}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{E4E1032E-9934-08BF-0708-2B5E96C12317}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{CDF19F1C-24C3-0001-3E1C-1678A7109AB8}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{D74171A0-BC7D-0BF6-2552-24E365CC9542}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{8F6A4BE1-0427-09A3-0794-95BDA8B83676}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{74FE5D8C-5D74-0635-0154-BCF70F757132}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{118F4150-140B-05FF-27ED-BEB8F6B3C54D}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{D51DD688-8EA1-0E9D-04CE-DE0DAEB0A978}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{98766D42-CDF7-0A17-26DC-093AC97DACF6}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{23AD5CEF-76D6-0B91-2089-9119CA13055A}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{6A4CFAEA-7E3C-0B1A-2C5D-15AA893FC833}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{99ECED36-B3CC-0AAF-242A-EA4F5D619FE9}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{7E2EA9A2-C86B-0B65-24DA-8368D0422FAF}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{1E88E232-62C3-069F-2D94-4F60BB58CA16}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{94A62138-ABE0-01A5-01C1-3D3A88E6DDBA}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{495C8EA9-2B2A-0E7B-3091-074B0C179117}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{0A328779-8269-0BCF-22D2-4737ED5D4F37}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{41023EBE-33C9-0DFE-387A-C9BA7F05D1EC}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{86D3A849-9A80-0B46-3ED9-9493E5C562E7}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"/><one:Page ID="{1C922EB2-4250-07E4-34C9-234F794943F7}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{0C48DE55-D5B7-0479-1BF2-4C17A0F30B1F}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"/><one:Page ID="{049B54CD-DC51-0E31-0BF8-F326C40B1026}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="2"/><one:Page ID="{5386195B-49C9-0A50-0FBF-A396B030F141}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1"/><one:Page ID="{44017B3B-F099-06A5-3BE9-A822A2E81F34}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1"/><one:Page ID="{DAC6618E-48C9-020D-15C8-9AC4EF182733}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="2"/><one:Page ID="{2D56A403-B4EA-00DF-2180-3FFAEE1E1024}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1"/><one:Page ID="{829EF954-5F8A-09B8-39B2-2CDE8049EF6D}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1"/><one:Page ID="{CFD331D7-8768-0262-172E-6383AED828FA}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="2"/></one:Section></one:Notebook>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-000.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{07A6BDA3-4B12-0AD9-085A-3AAEA3257787}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{07A6BDA3-4B12-0AD9-085A-3AAEA3257787}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{00883EEB-4E0B-07B5-0F54-872BA13B23CD}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:24.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{00883EEB-4E0B-07B5-0F54-872BA13B23CD}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:11.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{2}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:11.000Z" lastModifiedTime="2026-09-26T06:33:11.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{3}{B0}" alignment="left"><one:T><![CDATA[Actor 0 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-001.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{0C795549-BF3B-0480-39C3-C878E2BF5C53}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{0C795549-BF3B-0480-39C3-C878E2BF5C53}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{EF89556A-7329-0A9C-27D0-846938399E56}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:20.000Z" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{DF8510A6-B246-0D04-25F0-72930FBA2A42}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 5 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{049B54CD-DC51-0E31-0BF8-F326C40B1026}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{049B54CD-DC51-0E31-0BF8-F326C40B1026}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-002.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{0D8E1647-4657-0CB4-2F3A-94D4CC5845A0}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{0D8E1647-4657-0CB4-2F3A-94D4CC5845A0}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{8742DFDF-A6F3-0EC5-05A3-D642B0F8EA4E}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:20.000Z" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{AD1AAF2A-4E6D-02EA-2398-1F243E0A04D2}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 7 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{0A328779-8269-0BCF-22D2-4737ED5D4F37}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{0A328779-8269-0BCF-22D2-4737ED5D4F37}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{26}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:13.000Z" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{27}{B0}" alignment="left"><one:T><![CDATA[Actor 8 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-003.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1A48F415-F519-056D-00E4-9960FF36983E}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{1A48F415-F519-056D-00E4-9960FF36983E}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{0C48DE55-D5B7-0479-1BF2-4C17A0F30B1F}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{0C48DE55-D5B7-0479-1BF2-4C17A0F30B1F}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-004.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1A655A55-0FD5-025E-142E-2C89D842D58D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{1A655A55-0FD5-025E-142E-2C89D842D58D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{118F4150-140B-05FF-27ED-BEB8F6B3C54D}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{118F4150-140B-05FF-27ED-BEB8F6B3C54D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-005.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1D03640E-9B71-05FC-271B-1026DCC029B1}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{1D03640E-9B71-05FC-271B-1026DCC029B1}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1C922EB2-4250-07E4-34C9-234F794943F7}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{1C922EB2-4250-07E4-34C9-234F794943F7}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{29}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:13.000Z" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{30}{B0}" alignment="left"><one:T><![CDATA[Actor 9 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-006.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2501211E-A8B9-0EC3-18C9-AF5B02AD7C06}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{2501211E-A8B9-0EC3-18C9-AF5B02AD7C06}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{7AFE6DBE-F13B-0E34-0584-741CB950DBDE}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:19.000Z" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{FA430EA8-AF00-0D92-12B0-B63F53246981}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 1 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1E88E232-62C3-069F-2D94-4F60BB58CA16}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{1E88E232-62C3-069F-2D94-4F60BB58CA16}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:13.000Z" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{24}{B0}" alignment="left"><one:T><![CDATA[Actor 7 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-007.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{328F9323-76C9-06C0-284F-C287017C4F27}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{328F9323-76C9-06C0-284F-C287017C4F27}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:21.000Z" objectID="{1B797AC3-D315-00F4-1ADA-613EBEE166DC}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:21.000Z" lastModifiedTime="2026-09-11T10:54:21.000Z" objectID="{8D005AE0-4D81-036A-164E-D998E22D1FDA}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 9 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{23AD5CEF-76D6-0B91-2089-9119CA13055A}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{23AD5CEF-76D6-0B91-2089-9119CA13055A}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-008.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{3EB6AEAF-6C68-088B-0171-3D83AAE36EB8}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{3EB6AEAF-6C68-088B-0171-3D83AAE36EB8}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{28651488-20CB-08C4-1DEF-0FA8BEF2F880}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{28651488-20CB-08C4-1DEF-0FA8BEF2F880}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-009.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{44F31BFD-6543-00F6-3BB5-148B33556539}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{44F31BFD-6543-00F6-3BB5-148B33556539}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{2D56A403-B4EA-00DF-2180-3FFAEE1E1024}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{2D56A403-B4EA-00DF-2180-3FFAEE1E1024}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:14.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{35}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="120.7456665039062" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:14.000Z" lastModifiedTime="2026-09-26T06:33:14.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{36}{B0}" alignment="left"><one:T><![CDATA[Actor 11 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-010.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{599A7FED-A2BC-01AD-0AA7-9367B48C643D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{599A7FED-A2BC-01AD-0AA7-9367B48C643D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{41023EBE-33C9-0DFE-387A-C9BA7F05D1EC}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{41023EBE-33C9-0DFE-387A-C9BA7F05D1EC}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-011.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{652A14A0-FE09-05CB-3095-99BFA255E1FD}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{652A14A0-FE09-05CB-3095-99BFA255E1FD}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{44017B3B-F099-06A5-3BE9-A822A2E81F34}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{44017B3B-F099-06A5-3BE9-A822A2E81F34}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-012.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6A77B3B0-3133-09A1-382F-F28D4685D5F7}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{6A77B3B0-3133-09A1-382F-F28D4685D5F7}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:21.000Z" objectID="{D2B4E363-F08A-0A48-0C61-AADF669B4710}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="120.7456665039062" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:21.000Z" lastModifiedTime="2026-09-11T10:54:21.000Z" objectID="{A56AAE48-5EF5-0A72-2B6D-C58364B59DBB}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 10 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{495C8EA9-2B2A-0E7B-3091-074B0C179117}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{495C8EA9-2B2A-0E7B-3091-074B0C179117}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-013.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6CE577C5-BDE9-0122-2D12-C1E061D8EFD6}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{6CE577C5-BDE9-0122-2D12-C1E061D8EFD6}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{510102C7-04CF-0D15-313A-77B521E237B5}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{510102C7-04CF-0D15-313A-77B521E237B5}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-014.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6D9AABC2-10E2-0686-29D8-679A327568A0}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{6D9AABC2-10E2-0686-29D8-679A327568A0}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{5386195B-49C9-0A50-0FBF-A396B030F141}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{5386195B-49C9-0A50-0FBF-A396B030F141}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:14.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{32}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="120.7456665039062" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:14.000Z" lastModifiedTime="2026-09-26T06:33:14.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{33}{B0}" alignment="left"><one:T><![CDATA[Actor 10 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-015.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{73964C75-7738-03A7-146C-BF21A734E497}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{73964C75-7738-03A7-146C-BF21A734E497}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{6A4CFAEA-7E3C-0B1A-2C5D-15AA893FC833}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{6A4CFAEA-7E3C-0B1A-2C5D-15AA893FC833}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{20}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:13.000Z" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{21}{B0}" alignment="left"><one:T><![CDATA[Actor 6 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-016.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7C71FA76-38AF-07A9-264C-8971C4190282}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{7C71FA76-38AF-07A9-264C-8971C4190282}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{47FED148-3B2B-0A3D-2EB1-6E53A78E6A7C}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:19.000Z" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{6869F737-972F-0D70-14D3-CEDCB8B52D61}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 0 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{733A3554-E782-0C2E-06C7-8AAA271FBEDB}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{733A3554-E782-0C2E-06C7-8AAA271FBEDB}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-017.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{803FBA4F-17CA-00CB-348A-86A900FBE117}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{803FBA4F-17CA-00CB-348A-86A900FBE117}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{74FE5D8C-5D74-0635-0154-BCF70F757132}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{74FE5D8C-5D74-0635-0154-BCF70F757132}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-018.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8507D83B-3994-0B29-08D6-E26999709ECD}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{8507D83B-3994-0B29-08D6-E26999709ECD}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{75BA6420-3E05-0502-08FB-EFEEA8AD892C}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{75BA6420-3E05-0502-08FB-EFEEA8AD892C}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-019.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8F641324-4B67-0ACD-0A4E-2CB8CCC6C1E0}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{8F641324-4B67-0ACD-0A4E-2CB8CCC6C1E0}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7E2EA9A2-C86B-0B65-24DA-8368D0422FAF}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{7E2EA9A2-C86B-0B65-24DA-8368D0422FAF}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-020.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{99D05405-7D75-0D65-21A4-09E30FDCAFE8}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{99D05405-7D75-0D65-21A4-09E30FDCAFE8}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{2EAE2E05-1122-0141-125D-4A765C039CA6}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:19.000Z" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{DF098EEC-501F-064E-12A4-00B964A47490}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 2 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{80C417D9-7982-0C0E-1D8F-DD9227214F3B}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{80C417D9-7982-0C0E-1D8F-DD9227214F3B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{8}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:12.000Z" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{9}{B0}" alignment="left"><one:T><![CDATA[Actor 2 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-021.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9B5420CB-C546-0239-2C6C-E3632B133D5F}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{9B5420CB-C546-0239-2C6C-E3632B133D5F}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{829EF954-5F8A-09B8-39B2-2CDE8049EF6D}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{829EF954-5F8A-09B8-39B2-2CDE8049EF6D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-022.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9D4E6F5A-05B8-0EAD-18A3-62FF3663F86C}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{9D4E6F5A-05B8-0EAD-18A3-62FF3663F86C}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{86D3A849-9A80-0B46-3ED9-9493E5C562E7}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{86D3A849-9A80-0B46-3ED9-9493E5C562E7}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-023.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{9F7E7D29-16F6-0439-1415-8CE74E75CA89}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{9F7E7D29-16F6-0439-1415-8CE74E75CA89}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{E1EC640A-8462-0A43-0B22-A003EB543393}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:19.000Z" lastModifiedTime="2026-09-11T10:54:19.000Z" objectID="{D628EDB8-48F9-0341-2202-03A99842E1DE}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 3 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8F6A4BE1-0427-09A3-0794-95BDA8B83676}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{8F6A4BE1-0427-09A3-0794-95BDA8B83676}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{14}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:12.000Z" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{15}{B0}" alignment="left"><one:T><![CDATA[Actor 4 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-024.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{A008EAA3-7B7D-0618-1E1D-41C8CD7B2DC1}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{A008EAA3-7B7D-0618-1E1D-41C8CD7B2DC1}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{94A62138-ABE0-01A5-01C1-3D3A88E6DDBA}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{94A62138-ABE0-01A5-01C1-3D3A88E6DDBA}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-025.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{A0F068A1-5642-022B-2681-F2126BB7C3BD}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{A0F068A1-5642-022B-2681-F2126BB7C3BD}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:21.000Z" objectID="{28ED0BBF-5CA7-05EE-2AB6-4BFC4F82B939}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:21.000Z" lastModifiedTime="2026-09-11T10:54:21.000Z" objectID="{FBEFD32B-0691-0F3F-30D2-B893AB88EC33}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 8 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{98766D42-CDF7-0A17-26DC-093AC97DACF6}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{98766D42-CDF7-0A17-26DC-093AC97DACF6}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-026.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B06E19F3-05FC-0B60-3287-C13C24C193CE}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{B06E19F3-05FC-0B60-3287-C13C24C193CE}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{99ECED36-B3CC-0AAF-242A-EA4F5D619FE9}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{99ECED36-B3CC-0AAF-242A-EA4F5D619FE9}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-027.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B7E2E308-7768-044A-155D-A0D7ED14E4D2}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{B7E2E308-7768-044A-155D-A0D7ED14E4D2}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{1B893290-060F-0AEC-366B-52E74AC0B0D2}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:20.000Z" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{896D454F-C999-0B2A-3ABA-71A912EE13F2}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 6 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{AE3E419C-38CB-0069-07DE-E9440F759DD9}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:24.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{AE3E419C-38CB-0069-07DE-E9440F759DD9}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-028.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BADEB4C7-7F75-0B8E-19CA-C332C7A7875B}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{BADEB4C7-7F75-0B8E-19CA-C332C7A7875B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CDF19F1C-24C3-0001-3E1C-1678A7109AB8}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{CDF19F1C-24C3-0001-3E1C-1678A7109AB8}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-029.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BE524D9D-33C8-0650-138F-4C17DDF5CE5D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{BE524D9D-33C8-0650-138F-4C17DDF5CE5D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:22.000Z" objectID="{720C4EA2-9C42-05CA-0146-F5FD5B7F74D9}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="120.7456665039062" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:22.000Z" lastModifiedTime="2026-09-11T10:54:22.000Z" objectID="{05014EE3-11D5-03A2-1C8E-9C4F29CEF5BA}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 11 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CFD331D7-8768-0262-172E-6383AED828FA}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{CFD331D7-8768-0262-172E-6383AED828FA}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-030.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BFE40300-AAF8-0FAD-3762-93332A5EDA4D}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{BFE40300-AAF8-0FAD-3762-93332A5EDA4D}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D14084F4-7B9A-0878-09E5-EECC19CBE48C}{1}{B0}" name="Sentinel" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:24.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{7FD5C268-3371-0110-1F8E-BEADD565CD56}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="103.0424346923828" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{7FD5C268-3371-0110-1F8E-BEADD565CD56}{24}{B0}" alignment="left"><one:T><![CDATA[Sentinel]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-031.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C25C2B60-8E25-0382-1BCE-4099132AEA06}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{C25C2B60-8E25-0382-1BCE-4099132AEA06}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D51DD688-8EA1-0E9D-04CE-DE0DAEB0A978}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{D51DD688-8EA1-0E9D-04CE-DE0DAEB0A978}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{17}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:13.000Z" lastModifiedTime="2026-09-26T06:33:13.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{18}{B0}" alignment="left"><one:T><![CDATA[Actor 5 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-032.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CFE7E2AC-C22F-0D7C-18D3-601E40CC312C}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{CFE7E2AC-C22F-0D7C-18D3-601E40CC312C}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{18871A87-5D82-0359-1D21-3C498EEA581D}{0}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:20.000Z" lastModifiedTime="2026-09-11T10:54:20.000Z" objectID="{69F64A09-B1C8-0FEA-1327-17389D823403}{0}{B0}" alignment="left"><one:T><![CDATA[Actor 4 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D74171A0-BC7D-0BF6-2552-24E365CC9542}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{D74171A0-BC7D-0BF6-2552-24E365CC9542}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-033.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F4744DDD-E17E-0100-3BAC-98BC70CCC100}{1}{B0}" name="Sentinel" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{E215CFF8-B98E-0DF4-196B-B38D8F97EB71}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="103.0424346923828" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{E215CFF8-B98E-0DF4-196B-B38D8F97EB71}{24}{B0}" alignment="left"><one:T><![CDATA[Sentinel]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{DAC6618E-48C9-020D-15C8-9AC4EF182733}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:36:26.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:10.000Z" lastModifiedTime="2026-09-26T06:33:10.000Z" objectID="{DAC6618E-48C9-020D-15C8-9AC4EF182733}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-034.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F54516CD-DE85-081D-1761-468628E72D1B}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{F54516CD-DE85-081D-1761-468628E72D1B}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E4E1032E-9934-08BF-0708-2B5E96C12317}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{E4E1032E-9934-08BF-0708-2B5E96C12317}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{11}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:12.000Z" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{12}{B0}" alignment="left"><one:T><![CDATA[Actor 3 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-035.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F5512F5C-2626-00AA-0B5F-EA6BD4164873}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:49.000Z" pageLevel="2"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{F5512F5C-2626-00AA-0B5F-EA6BD4164873}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{EB88C82F-EF38-01AD-179F-DEB37531C674}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{EB88C82F-EF38-01AD-179F-DEB37531C674}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{5}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="115.1704635620117" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:33:12.000Z" lastModifiedTime="2026-09-26T06:33:12.000Z" objectID="{4B40F130-BF5A-083A-064F-D39B8740C112}{6}{B0}" alignment="left"><one:T><![CDATA[Actor 1 &#129408; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/read/page-036.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{FB3175D5-307C-0E50-1607-1F154F5E4118}{1}{B0}" name="Same title" dateTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:55:48.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-11T10:54:16.000Z" lastModifiedTime="2026-09-11T10:54:16.000Z" objectID="{FB3175D5-307C-0E50-1607-1F154F5E4118}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F297A189-463C-0A06-2B9E-B530B4CB073E}{1}{B0}" name="Same title" dateTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:36:25.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:33:09.000Z" lastModifiedTime="2026-09-26T06:33:09.000Z" objectID="{F297A189-463C-0A06-2B9E-B530B4CB073E}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Same title]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/offline-edits/twelve-clients/cold/run.json+1-1
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1{1{
2 "notebook": "/private/tmp/m33/page-clients2/candidate",2 "notebook": "/Users/clo/dev/one/evidence/titles/twelve-clients",
3 "expected_pages": 37,3 "expected_pages": 37,
4 "author": null,4 "author": null,
5 "author_timeout_seconds": 600,5 "author_timeout_seconds": 600,
corpus/page-lifecycle/offline-edits/twelve-clients/cold/source.json+9-3
...@@ -1,8 +1,14 @@...@@ -1,8 +1,14 @@
1[1[
2 {
3 "path": "manifest.json",
4 "bytes": 292907,
5 "sha256": "fea1d64b22da84d023d4c9014175f8e92a13acba099c7a1a3f8eae30d6c6407f",
6 "mtime_ns": 1790404395466294564
7 },
2 {8 {
3 "path": "pages.one",9 "path": "pages.one",
4 "bytes": 155344,10 "bytes": 152360,
5 "sha256": "623ade8b5ee6fc837ae63d5293791039ecd17da9cb8bc5e243ea20cadf37958c",11 "sha256": "302b6d2d31bc8f539a2217aed3391973678576b0aea1ce82c6e21760d6402e14",
6 "mtime_ns": 178912406304052811712 "mtime_ns": 1790404395457867619
7 }13 }
8]14]
corpus/page-lifecycle/page-edits/stress/candidate/movement.one
Binary files a/corpus/page-lifecycle/page-edits/stress/candidate/movement.one and b/corpus/page-lifecycle/page-edits/stress/candidate/movement.one differ
corpus/page-lifecycle/page-edits/stress/cold/notebook/Open Notebook.onetoc2
Binary files a/corpus/page-lifecycle/page-edits/stress/cold/notebook/Open Notebook.onetoc2 and b/corpus/page-lifecycle/page-edits/stress/cold/notebook/Open Notebook.onetoc2 differ
corpus/page-lifecycle/page-edits/stress/cold/notebook/movement.one
Binary files a/corpus/page-lifecycle/page-edits/stress/cold/notebook/movement.one and b/corpus/page-lifecycle/page-edits/stress/cold/notebook/movement.one differ
corpus/page-lifecycle/page-edits/stress/cold/read/environment.json+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1{ 1{
2 "powershell": "5.1.14409.1005", 2 "powershell": "5.1.14409.1005",
3 "schema": "xs2010", 3 "schema": "xs2010",
4 "hostname": "ONE-M6-40A088EC", 4 "hostname": "ONE-M6-02A94B20",
5 "cold": true, 5 "cold": true,
6 "onenote": "14.0.4763.1000" 6 "onenote": "14.0.4763.1000"
7} 7}
corpus/page-lifecycle/page-edits/stress/cold/read/hierarchy.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{D322FB5E-1D61-4189-93EE-7E0AB4F8002F}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T06:44:35.000Z" color="#B49EDE"><one:Section name="movement" ID="{E9C7E704-C66E-0144-364E-95B272D68AF3}{1}{B0}" path="C:\one-tests\runs\capture\notebook\movement.one" lastModifiedTime="2026-09-09T06:44:35.000Z" color="#8AA8E4"><one:Page ID="{651B4683-48D9-0D97-27C7-D2F85B4E8F63}{1}{B0}" name="Original" dateTime="2026-09-09T06:39:25.000Z" lastModifiedTime="2026-09-09T06:44:35.000Z" pageLevel="1"/><one:Page ID="{83F7E375-3B9D-01C7-16E7-885AC0D5F9C5}{1}{B0}" name="Child" dateTime="2026-09-09T06:39:25.000Z" lastModifiedTime="2026-09-09T06:44:35.000Z" pageLevel="1"/></one:Section></one:Notebook> 2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{F5F68BC6-5330-453A-AFAE-91BBA8C96783}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:39:47.000Z" color="#B49EDE"><one:Section name="movement" ID="{5E701DC7-30F2-0B02-006D-13258C6FBE79}{1}{B0}" path="C:\one-tests\runs\capture\notebook\movement.one" lastModifiedTime="2026-09-26T06:39:47.000Z" color="#8AA8E4"><one:Page ID="{D2EBE0FB-41A5-0CC9-1173-37795AF95DA7}{1}{B0}" name="Original" dateTime="2026-09-26T06:32:13.000Z" lastModifiedTime="2026-09-26T06:39:47.000Z" pageLevel="1"/><one:Page ID="{87328C7E-CC45-0126-1AE0-F144B8CE03BF}{1}{B0}" name="Child" dateTime="2026-09-26T06:32:13.000Z" lastModifiedTime="2026-09-26T06:39:47.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/page-lifecycle/page-edits/stress/cold/read/page-000.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{651B4683-48D9-0D97-27C7-D2F85B4E8F63}{1}{B0}" name="Original" dateTime="2026-09-09T06:39:25.000Z" lastModifiedTime="2026-09-09T06:44:35.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-09T06:39:25.000Z" objectID="{E9C7E704-C66E-0144-364E-95B272D68AF3}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="101.66748046875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-09T06:39:25.000Z" lastModifiedTime="2026-09-09T06:39:25.000Z" objectID="{E9C7E704-C66E-0144-364E-95B272D68AF3}{24}{B0}" alignment="left"><one:T><![CDATA[Original]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{87328C7E-CC45-0126-1AE0-F144B8CE03BF}{1}{B0}" name="Child" dateTime="2026-09-26T06:32:13.000Z" lastModifiedTime="2026-09-26T06:39:47.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-26T06:32:13.000Z" lastModifiedTime="2026-09-26T06:32:13.000Z" objectID="{87328C7E-CC45-0126-1AE0-F144B8CE03BF}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Child]]></one:T></one:OE></one:Title></one:Page>
corpus/page-lifecycle/page-edits/stress/cold/read/page-001.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{83F7E375-3B9D-01C7-16E7-885AC0D5F9C5}{1}{B0}" name="Child" dateTime="2026-09-09T06:39:25.000Z" lastModifiedTime="2026-09-09T06:44:35.000Z" pageLevel="1"><one:QuickStyleDef index="0" name="PageTitle" font="Calibri" fontSize="17.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title showDate="false" showTime="false" lang="en-US"><one:OE author="Author" lastModifiedBy="Author" creationTime="2026-09-09T06:39:25.000Z" lastModifiedTime="2026-09-09T06:39:25.000Z" objectID="{83F7E375-3B9D-01C7-16E7-885AC0D5F9C5}{15}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Child]]></one:T></one:OE></one:Title></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D2EBE0FB-41A5-0CC9-1173-37795AF95DA7}{1}{B0}" name="Original" dateTime="2026-09-26T06:32:13.000Z" lastModifiedTime="2026-09-26T06:39:47.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:32:13.000Z" objectID="{5E701DC7-30F2-0B02-006D-13258C6FBE79}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="101.66748046875" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:32:13.000Z" lastModifiedTime="2026-09-26T06:32:13.000Z" objectID="{5E701DC7-30F2-0B02-006D-13258C6FBE79}{24}{B0}" alignment="left"><one:T><![CDATA[Original]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/page-lifecycle/page-edits/stress/provenance.json+6-6
...@@ -1,10 +1,10 @@...@@ -1,10 +1,10 @@
1{1{
2 "generator": "crates/onestore/tests/page_movement.rs::repeated_nesting_retains_series_history_across_revision_checkpoints",2 "generator": "crates/onestore/tests/page_movement.rs::repeated_nesting_retains_series_history_across_revision_checkpoints",
3 "edits": 520,3 "edits": 520,
4 "capture": "evidence/m10/page-edit-stress-cold-01",4 "capture": "evidence/titles/cold-stress",
5 "machine": {5 "machine": {
6 "name": "m6-40a088ec",6 "name": "m6-02a94b20",
7 "hostname": "ONE-M6-40A088EC"7 "hostname": "ONE-M6-02A94B20"
8 },8 },
9 "teardown": {9 "teardown": {
10 "absent": true10 "absent": true
...@@ -12,9 +12,9 @@...@@ -12,9 +12,9 @@
12 "source": [12 "source": [
13 {13 {
14 "path": "movement.one",14 "path": "movement.one",
15 "bytes": 2635536,15 "bytes": 959216,
16 "sha256": "77dbec39ca25558b596b477d57117416f23ee133519f5179d7ebf1a33b38c248",16 "sha256": "e2948ff3d01839a2c6a7908633739e97c4ebbe28e584c105bb1166669508758f",
17 "mtime_ns": 178893608283947021417 "mtime_ns": 1790404366792422729
18 }18 }
19 ]19 ]
20}20}
corpus/paragraph-edit/README.md+2-1
...@@ -28,7 +28,8 @@ when regenerating the corpus....@@ -28,7 +28,8 @@ when regenerating the corpus.
2828
29`rust-split` retains twelve `ParagraphSplit` outputs, their cold native captures,29`rust-split` retains twelve `ParagraphSplit` outputs, their cold native captures,
30and a second native session typing into four empty boundary paragraphs. The30and a second native session typing into four empty boundary paragraphs. The
31hyperlink case stays unchanged because this operation rejects fields. The Rust31hyperlink case has no output: the operation rejected fields when this row was
32captured, and the writer now splits before a link as OneNote does. The Rust
32test `export_native_paragraph_splits` generates candidate notebooks; its output33test `export_native_paragraph_splits` generates candidate notebooks; its output
33directory comes from `ONESTORE_PARAGRAPH_OUTPUT`. Native character/style comparisons34directory comes from `ONESTORE_PARAGRAPH_OUTPUT`. Native character/style comparisons
34use both the keyboard-generated controls and the Rust-written notebooks. Empty35use both the keyboard-generated controls and the Rust-written notebooks. Empty
corpus/picture-edit/inserted/candidate/Open Notebook.onetoc2
Binary files a/corpus/picture-edit/inserted/candidate/Open Notebook.onetoc2 and b/corpus/picture-edit/inserted/candidate/Open Notebook.onetoc2 differ
corpus/picture-edit/inserted/candidate/pictures.one
Binary files a/corpus/picture-edit/inserted/candidate/pictures.one and b/corpus/picture-edit/inserted/candidate/pictures.one differ
corpus/picture-edit/inserted/cold/commands.jsonl+1-1
...@@ -1,3 +1,3 @@...@@ -1,3 +1,3 @@
1{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}1{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
2{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-A5A8705B -ExpectedPages 1", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null}2{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-424D1706 -ExpectedPages 1", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null}
3{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}3{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
corpus/picture-edit/inserted/cold/machine.json+1-1
...@@ -1 +1 @@...@@ -1 +1 @@
1{"name": "m6-a5a8705b", "hostname": "ONE-M6-A5A8705B"}1{"name": "m6-424d1706", "hostname": "ONE-M6-424D1706"}
corpus/picture-edit/inserted/cold/notebook/Open Notebook.onetoc2
Binary files a/corpus/picture-edit/inserted/cold/notebook/Open Notebook.onetoc2 and b/corpus/picture-edit/inserted/cold/notebook/Open Notebook.onetoc2 differ
corpus/picture-edit/inserted/cold/notebook/pictures.one
Binary files a/corpus/picture-edit/inserted/cold/notebook/pictures.one and b/corpus/picture-edit/inserted/cold/notebook/pictures.one differ
corpus/picture-edit/inserted/cold/read/environment.json+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1{ 1{
2 "powershell": "5.1.14409.1005", 2 "powershell": "5.1.14409.1005",
3 "schema": "xs2010", 3 "schema": "xs2010",
4 "hostname": "ONE-M6-A5A8705B", 4 "hostname": "ONE-M6-424D1706",
5 "cold": true, 5 "cold": true,
6 "onenote": "14.0.4763.1000" 6 "onenote": "14.0.4763.1000"
7} 7}
corpus/picture-edit/inserted/cold/read/hierarchy.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{BB903F04-A992-47A1-B747-4D7C2D151005}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-12T20:08:56.000Z" color="#B49EDE"><one:Section name="pictures" ID="{54DE5097-F4BE-0BCC-0392-19FB4210AAAD}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pictures.one" lastModifiedTime="2026-09-12T20:08:56.000Z" color="#8AA8E4"><one:Page ID="{D3A0AD9C-D12D-02B2-1683-4D32345076A5}{1}{B0}" name="Before the picture" dateTime="2026-09-12T20:07:15.000Z" lastModifiedTime="2026-09-12T20:08:56.000Z" pageLevel="1"/></one:Section></one:Notebook> 2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{3A1D7F13-0843-4A0D-AB53-1EBAC00D091F}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:43:25.000Z" color="#B49EDE"><one:Section name="pictures" ID="{A1F8FA80-B0EC-0254-0FC4-F867B3DD72E7}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pictures.one" lastModifiedTime="2026-09-26T06:43:25.000Z" color="#8AA8E4"><one:Page ID="{A3568077-FCA2-0E53-0B6A-615C7F205498}{1}{B0}" name="Before the picture" dateTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:43:15.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/picture-edit/inserted/cold/read/page-000.xml+2-2
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D3A0AD9C-D12D-02B2-1683-4D32345076A5}{1}{B0}" name="Before the picture" dateTime="2026-09-12T20:07:15.000Z" lastModifiedTime="2026-09-12T20:08:56.000Z" pageLevel="1" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-12T20:07:15.000Z" objectID="{54DE5097-F4BE-0BCC-0392-19FB4210AAAD}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="148.3100738525391" height="27.60543251037598"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-12T20:07:15.000Z" lastModifiedTime="2026-09-12T20:07:15.000Z" objectID="{54DE5097-F4BE-0BCC-0392-19FB4210AAAD}{24}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[Before the picture]]></one:T></one:OE><one:OE author="Picture author" lastModifiedBy="Picture author" creationTime="2026-09-12T20:07:15.000Z" lastModifiedTime="2026-09-12T20:07:15.000Z" objectID="{496D54CF-B74C-091A-2D00-F268C53481D1}{0}{B0}" alignment="left"><one:Image format="png"><one:Data>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z8AAAAMBAQAY3Y2w 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{A3568077-FCA2-0E53-0B6A-615C7F205498}{1}{B0}" name="Before the picture" dateTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:43:15.000Z" pageLevel="1" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:32:50.000Z" objectID="{A1F8FA80-B0EC-0254-0FC4-F867B3DD72E7}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="148.3100738525391" height="27.60543251037598"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:32:50.000Z" objectID="{A1F8FA80-B0EC-0254-0FC4-F867B3DD72E7}{24}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[Before the picture]]></one:T></one:OE><one:OE author="Picture author" lastModifiedBy="Picture author" creationTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:32:50.000Z" objectID="{21F2D72A-8493-0751-389D-FA29710E456D}{1}{B0}" alignment="left"><one:Image format="png"><one:Data>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z8AAAAMBAQAY3Y2w
3AAAAAElFTkSuQmCC 3AAAAAElFTkSuQmCC
4</one:Data></one:Image></one:OE><one:OE author="Picture author" lastModifiedBy="Picture author" creationTime="2026-09-12T20:07:15.000Z" lastModifiedTime="2026-09-12T20:07:15.000Z" objectID="{39F25FF2-3F08-07F1-06E1-F7F0075DE8F1}{0}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[After the picture]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4</one:Data></one:Image></one:OE><one:OE author="Picture author" lastModifiedBy="Picture author" creationTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:32:50.000Z" objectID="{21F2D72A-8493-0751-389D-FA29710E456D}{4}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:T><![CDATA[After the picture]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/picture-edit/inserted/cold/run.json+1-1
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1{1{
2 "notebook": "/private/tmp/m4/pictures",2 "notebook": "/Users/clo/dev/one/evidence/titles/picture-inserted",
3 "expected_pages": 1,3 "expected_pages": 1,
4 "author": null,4 "author": null,
5 "author_timeout_seconds": 600,5 "author_timeout_seconds": 600,
corpus/picture-edit/inserted/cold/source.json+5-5
...@@ -2,13 +2,13 @@...@@ -2,13 +2,13 @@
2 {2 {
3 "path": "Open Notebook.onetoc2",3 "path": "Open Notebook.onetoc2",
4 "bytes": 1604,4 "bytes": 1604,
5 "sha256": "0eddd691b52d26bac222cf7e52add3c9909446647b452fb193f03d49e273426a",5 "sha256": "52e4f16e4fc2b25971d0b1e2504679373b413d65a5127833f874335115ace5c9",
6 "mtime_ns": 17892436353882464386 "mtime_ns": 1790404370185679445
7 },7 },
8 {8 {
9 "path": "pictures.one",9 "path": "pictures.one",
10 "bytes": 7064,10 "bytes": 7776,
11 "sha256": "3076880e02cf74eb85400852c133060ab8ed50383c6b9df8a07bde8389891a70",11 "sha256": "c468fdcd8d55b5161c9e8f104d846c4191c40efb4bdc9a6aa134f8d824627436",
12 "mtime_ns": 178924363538787943812 "mtime_ns": 1790404370185326028
13 }13 }
14]14]
corpus/picture-edit/page-level/candidate/Open Notebook.onetoc2
Binary files a/corpus/picture-edit/page-level/candidate/Open Notebook.onetoc2 and b/corpus/picture-edit/page-level/candidate/Open Notebook.onetoc2 differ
corpus/picture-edit/page-level/candidate/pictures.one
Binary files a/corpus/picture-edit/page-level/candidate/pictures.one and b/corpus/picture-edit/page-level/candidate/pictures.one differ
corpus/picture-edit/page-level/cold/commands.jsonl+1-1
...@@ -1,3 +1,3 @@...@@ -1,3 +1,3 @@
1{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}1{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
2{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-3E57E5E3 -ExpectedPages 1", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null}2{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-F9B47642 -ExpectedPages 1", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null}
3{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}3{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
corpus/picture-edit/page-level/cold/machine.json+1-1
...@@ -1 +1 @@...@@ -1 +1 @@
1{"name": "m6-3e57e5e3", "hostname": "ONE-M6-3E57E5E3"}1{"name": "m6-f9b47642", "hostname": "ONE-M6-F9B47642"}
corpus/picture-edit/page-level/cold/notebook/Open Notebook.onetoc2
Binary files a/corpus/picture-edit/page-level/cold/notebook/Open Notebook.onetoc2 and b/corpus/picture-edit/page-level/cold/notebook/Open Notebook.onetoc2 differ
corpus/picture-edit/page-level/cold/notebook/pictures.one
Binary files a/corpus/picture-edit/page-level/cold/notebook/pictures.one and b/corpus/picture-edit/page-level/cold/notebook/pictures.one differ
corpus/picture-edit/page-level/cold/read/environment.json+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1{ 1{
2 "powershell": "5.1.14409.1005", 2 "powershell": "5.1.14409.1005",
3 "schema": "xs2010", 3 "schema": "xs2010",
4 "hostname": "ONE-M6-3E57E5E3", 4 "hostname": "ONE-M6-F9B47642",
5 "cold": true, 5 "cold": true,
6 "onenote": "14.0.4763.1000" 6 "onenote": "14.0.4763.1000"
7} 7}
corpus/picture-edit/page-level/cold/read/hierarchy.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{7979B79B-55CA-4BD2-9516-7F2833A0B2D3}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-12T21:40:13.000Z" color="#B49EDE"><one:Section name="pictures" ID="{1037AF3E-61F8-0E78-3745-6713BE53BE60}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pictures.one" lastModifiedTime="2026-09-12T21:40:13.000Z" color="#8AA8E4"><one:Page ID="{8EDE3CB0-E79B-0683-237C-820507E0B6A6}{1}{B0}" name="Beside the picture" dateTime="2026-09-12T21:38:24.000Z" lastModifiedTime="2026-09-12T21:40:13.000Z" pageLevel="1"/></one:Section></one:Notebook> 2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{B542D228-D5B7-427D-8774-02257F272F0D}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:46:48.000Z" color="#B49EDE"><one:Section name="pictures" ID="{54923A9D-64F0-0307-13E1-F2E6E667FEEE}{1}{B0}" path="C:\one-tests\runs\capture\notebook\pictures.one" lastModifiedTime="2026-09-26T06:46:48.000Z" color="#8AA8E4"><one:Page ID="{7D08EA18-6182-09A0-304C-6E0D82ECE15F}{1}{B0}" name="Beside the picture" dateTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:46:39.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/picture-edit/page-level/cold/read/page-000.xml+1-1
...@@ -1,4 +1,4 @@...@@ -1,4 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8EDE3CB0-E79B-0683-237C-820507E0B6A6}{1}{B0}" name="Beside the picture" dateTime="2026-09-12T21:38:24.000Z" lastModifiedTime="2026-09-12T21:40:13.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-12T21:38:24.000Z" objectID="{1037AF3E-61F8-0E78-3745-6713BE53BE60}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="147.9233703613281" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-12T21:38:24.000Z" lastModifiedTime="2026-09-12T21:38:24.000Z" objectID="{1037AF3E-61F8-0E78-3745-6713BE53BE60}{24}{B0}" alignment="left"><one:T><![CDATA[Beside the picture]]></one:T></one:OE></one:OEChildren></one:Outline><one:Image format="png" alt="Placed in Rust" lastModifiedTime="2026-09-12T21:38:24.000Z" objectID="{03E09DC9-97C4-05DC-32C4-49615A981E9C}{0}{B0}"><one:Position x="360.0" y="240.0" z="1"/><one:Size width="96.00000762939453" height="71.99998474121093" isSetByUser="true"/><one:Data>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z8AAAAMBAQAY3Y2w 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7D08EA18-6182-09A0-304C-6E0D82ECE15F}{1}{B0}" name="Beside the picture" dateTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:46:39.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:32:50.000Z" objectID="{54923A9D-64F0-0307-13E1-F2E6E667FEEE}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="147.9233703613281" height="13.4277172088623"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:32:50.000Z" lastModifiedTime="2026-09-26T06:32:50.000Z" objectID="{54923A9D-64F0-0307-13E1-F2E6E667FEEE}{24}{B0}" alignment="left"><one:T><![CDATA[Beside the picture]]></one:T></one:OE></one:OEChildren></one:Outline><one:Image format="png" alt="Placed in Rust" lastModifiedTime="2026-09-26T06:32:50.000Z" objectID="{D1E701D5-AF6D-0088-33CF-6A1696900977}{1}{B0}"><one:Position x="360.0" y="240.0" z="1"/><one:Size width="96.00000762939453" height="71.99998474121093" isSetByUser="true"/><one:Data>iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVQI12P4z8AAAAMBAQAY3Y2w
3AAAAAElFTkSuQmCC 3AAAAAElFTkSuQmCC
4</one:Data></one:Image></one:Page> 4</one:Data></one:Image></one:Page>
corpus/picture-edit/page-level/cold/run.json+1-1
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1{1{
2 "notebook": "/private/tmp/m4/pagelevel",2 "notebook": "/Users/clo/dev/one/evidence/titles/picture-page-level",
3 "expected_pages": 1,3 "expected_pages": 1,
4 "author": null,4 "author": null,
5 "author_timeout_seconds": 600,5 "author_timeout_seconds": 600,
corpus/picture-edit/page-level/cold/source.json+5-5
...@@ -2,13 +2,13 @@...@@ -2,13 +2,13 @@
2 {2 {
3 "path": "Open Notebook.onetoc2",3 "path": "Open Notebook.onetoc2",
4 "bytes": 1604,4 "bytes": 1604,
5 "sha256": "3df6c788afbfe6ecda97d63cdbb3f42b269d61107e78dd0b3babaf55ae567389",5 "sha256": "8d7fed8f27dfd76533e619068e37c1ee0de5187faebb129c5047c01a814f4c9f",
6 "mtime_ns": 17892491047662532466 "mtime_ns": 1790404370294442700
7 },7 },
8 {8 {
9 "path": "pictures.one",9 "path": "pictures.one",
10 "bytes": 5656,10 "bytes": 6560,
11 "sha256": "b6c0a2e604c038a2d0fa93a6096c3fecfa08f370f268ad010d7419ee87dd1f02",11 "sha256": "80dd6cc455bbbffe37d9cb7ad30d69a4a6a65b550a5de3d87a514bc0f99c5f41",
12 "mtime_ns": 178924910476600612012 "mtime_ns": 1790404370294185825
13 }13 }
14]14]
corpus/table-edit/README.md+2-2
...@@ -23,8 +23,8 @@ two-row table with locked 72 pt column and visible borders. `nested/cold` is...@@ -23,8 +23,8 @@ two-row table with locked 72 pt column and visible borders. `nested/cold` is
23its cold read with both tables.23its cold read with both tables.
2424
25Native rows and cells are plain containers; the writer creates them with the25Native rows and cells are plain containers; the writer creates them with the
26modification time, the indent array copied from a sibling cell, and the flags26modification time, the indent array copied from a sibling cell (OneNote's
27every native cell carries, rebuilds each table's row and cell order to the27default without one), and the flags every native cell carries, rebuilds each table's row and cell order to the
28model's, and writes the column count, widths, locks and border flag. New28model's, and writes the column count, widths, locks and border flag. New
29tables start as an empty paragraph whose content the structure pass replaces.29tables start as an empty paragraph whose content the structure pass replaces.
30An emptied cell keeps a replacement paragraph, as the tree writer provides.30An emptied cell keeps a replacement paragraph, as the tree writer provides.
corpus/table-edit/created/candidate/Open Notebook.onetoc2
Binary files a/corpus/table-edit/created/candidate/Open Notebook.onetoc2 and b/corpus/table-edit/created/candidate/Open Notebook.onetoc2 differ
corpus/table-edit/created/candidate/tables.one
Binary files a/corpus/table-edit/created/candidate/tables.one and b/corpus/table-edit/created/candidate/tables.one differ
corpus/table-edit/created/cold/commands.jsonl+1-1
...@@ -1,3 +1,3 @@...@@ -1,3 +1,3 @@
1{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}1{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
2{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-838F23A9 -ExpectedPages 1", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null}2{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-D27A9E1E -ExpectedPages 1", "exit": 0, "stdout": "Read 1 sections and 1 pages.\r\n", "stderr": "", "error": null}
3{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}3{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
corpus/table-edit/created/cold/machine.json+1-1
...@@ -1 +1 @@...@@ -1 +1 @@
1{"name": "m6-838f23a9", "hostname": "ONE-M6-838F23A9"}1{"name": "m6-d27a9e1e", "hostname": "ONE-M6-D27A9E1E"}
corpus/table-edit/created/cold/notebook/Open Notebook.onetoc2
Binary files a/corpus/table-edit/created/cold/notebook/Open Notebook.onetoc2 and b/corpus/table-edit/created/cold/notebook/Open Notebook.onetoc2 differ
corpus/table-edit/created/cold/notebook/tables.one
Binary files a/corpus/table-edit/created/cold/notebook/tables.one and b/corpus/table-edit/created/cold/notebook/tables.one differ
corpus/table-edit/created/cold/read/environment.json+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1{ 1{
2 "powershell": "5.1.14409.1005", 2 "powershell": "5.1.14409.1005",
3 "schema": "xs2010", 3 "schema": "xs2010",
4 "hostname": "ONE-M6-838F23A9", 4 "hostname": "ONE-M6-D27A9E1E",
5 "cold": true, 5 "cold": true,
6 "onenote": "14.0.4763.1000" 6 "onenote": "14.0.4763.1000"
7} 7}
corpus/table-edit/created/cold/read/hierarchy.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{1BB56A3F-9C37-4838-B671-213C7548F681}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-12T18:57:57.000Z" color="#B49EDE"><one:Section name="tables" ID="{1C2D662F-7BFD-0276-1BFA-B3E7E7E01562}{1}{B0}" path="C:\one-tests\runs\capture\notebook\tables.one" lastModifiedTime="2026-09-12T18:57:57.000Z" color="#8AA8E4"><one:Page ID="{1A74DE87-A9CF-0A2A-057E-CDD4A592DF04}{1}{B0}" name="Before the table" dateTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:57:46.000Z" pageLevel="1"/></one:Section></one:Notebook> 2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{391C6ED8-573A-4D96-A456-F04ECBADE242}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:43:15.000Z" color="#B49EDE"><one:Section name="tables" ID="{04A30DC8-E0D5-038E-0AC2-29BF67C23E5C}{1}{B0}" path="C:\one-tests\runs\capture\notebook\tables.one" lastModifiedTime="2026-09-26T06:43:15.000Z" color="#8AA8E4"><one:Page ID="{FAB80BEA-DF75-0534-21D7-A812FF413639}{1}{B0}" name="Before the table" dateTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:43:15.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/table-edit/created/cold/read/page-000.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1A74DE87-A9CF-0A2A-057E-CDD4A592DF04}{1}{B0}" name="Before the table" dateTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:57:46.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{1C2D662F-7BFD-0276-1BFA-B3E7E7E01562}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="395.1299438476562" height="67.21086883544922"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{1C2D662F-7BFD-0276-1BFA-B3E7E7E01562}{24}{B0}" alignment="left"><one:T><![CDATA[Before the table]]></one:T></one:OE><one:OE author="Table author" lastModifiedBy="Table author" creationTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{3A30E194-5242-09E5-2837-A8BE31520023}{0}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{2BAC850E-BEEF-0DFE-101D-E7A08FA3EF36}{0}{B0}"><one:Columns><one:Column index="0" width="144.0" isLocked="true"/><one:Column index="1" width="216.0" isLocked="true"/></one:Columns><one:Row objectID="{32B84587-8B60-0900-2C7D-8B03AA1021FC}{0}{B0}" lastModifiedTime="2026-09-12T18:55:42.000Z"><one:Cell lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{35FB065B-82B3-06B6-23B5-4A866D422733}{0}{B0}"><one:OEChildren><one:OE creationTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{6961CA5C-5601-0109-3ABA-9C6E1D87807B}{0}{B0}" alignment="left"><one:T><![CDATA[Name]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{6460510E-A7E1-04D3-21FE-7B255A488496}{0}{B0}"><one:OEChildren><one:OE creationTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{251F55AF-E7E8-0E35-3FFF-A0188DB1E006}{0}{B0}" alignment="left"><one:T><![CDATA[Value]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{E5A88DF0-4872-0A5B-03CE-6AD2D88CC6A2}{0}{B0}" lastModifiedTime="2026-09-12T18:55:42.000Z"><one:Cell lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{65FC0F6F-A7F8-01D8-2F86-E81451D010FB}{0}{B0}"><one:OEChildren><one:OE creationTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{1F1346CC-AB72-0AC2-0F02-C82B60ACE63B}{0}{B0}" alignment="left"><one:T><![CDATA[Rust &#129408;]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{4A655FAB-04B2-0641-3895-C87F7F551A21}{0}{B0}"><one:OEChildren><one:OE creationTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{5BBFE54E-D6A1-0A3C-0302-2F0ED58341F1}{0}{B0}" alignment="left"><one:T><![CDATA[Written natively]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE><one:OE author="Table author" lastModifiedBy="Table author" creationTime="2026-09-12T18:55:42.000Z" lastModifiedTime="2026-09-12T18:55:42.000Z" objectID="{A750F1FF-86E2-0E24-2656-CFDC574202E9}{0}{B0}" alignment="left"><one:T><![CDATA[After the table]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{FAB80BEA-DF75-0534-21D7-A812FF413639}{1}{B0}" name="Before the table" dateTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:43:15.000Z" pageLevel="1" style="font-family:Calibri;font-size:11.0pt" lang="en-US"><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Author" lastModifiedBy="Author" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{04A30DC8-E0D5-038E-0AC2-29BF67C23E5C}{23}{B0}"><one:Position x="36.0" y="36.0" z="0"/><one:Size width="395.1299438476562" height="67.21086883544922"/><one:Indents><one:Indent level="0" indent="0.0"/></one:Indents><one:OEChildren><one:OE creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{04A30DC8-E0D5-038E-0AC2-29BF67C23E5C}{24}{B0}" alignment="left"><one:T><![CDATA[Before the table]]></one:T></one:OE><one:OE author="Table author" lastModifiedBy="Table author" creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{16}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{1}{B0}"><one:Columns><one:Column index="0" width="144.0" isLocked="true"/><one:Column index="1" width="216.0" isLocked="true"/></one:Columns><one:Row objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{2}{B0}" lastModifiedTime="2026-09-26T06:32:49.000Z"><one:Cell lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{3}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{4}{B0}" alignment="left"><one:T><![CDATA[Name]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{6}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{7}{B0}" alignment="left"><one:T><![CDATA[Value]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{9}{B0}" lastModifiedTime="2026-09-26T06:32:49.000Z"><one:Cell lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{10}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{11}{B0}" alignment="left"><one:T><![CDATA[Rust &#129408;]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{13}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{14}{B0}" alignment="left"><one:T><![CDATA[Written natively]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE><one:OE author="Table author" lastModifiedBy="Table author" creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{E8153D58-8C73-0F63-0636-2ACA309F04F1}{18}{B0}" alignment="left"><one:T><![CDATA[After the table]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/table-edit/created/cold/run.json+1-1
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1{1{
2 "notebook": "/private/tmp/m4/tables",2 "notebook": "/Users/clo/dev/one/evidence/titles/table-created",
3 "expected_pages": 1,3 "expected_pages": 1,
4 "author": null,4 "author": null,
5 "author_timeout_seconds": 600,5 "author_timeout_seconds": 600,
corpus/table-edit/created/cold/source.json+5-5
...@@ -2,13 +2,13 @@...@@ -2,13 +2,13 @@
2 {2 {
3 "path": "Open Notebook.onetoc2",3 "path": "Open Notebook.onetoc2",
4 "bytes": 1604,4 "bytes": 1604,
5 "sha256": "b39b2e142e0adce0de37ad336f40c3e9c2ba92ce6927c9d109139e74c1fb4a6e",5 "sha256": "8e64e22a5381e45d42536c1d75cf266a70970828d0258c15571adc6f4d75391f",
6 "mtime_ns": 17892393427552204656 "mtime_ns": 1790404369445796875
7 },7 },
8 {8 {
9 "path": "tables.one",9 "path": "tables.one",
10 "bytes": 11016,10 "bytes": 10936,
11 "sha256": "e1c014220e05352a01c98752031b32c17512407295aa812ff8e9c776e48eef6e",11 "sha256": "d66db22225eaf0064e53f14e6dcfdbefd822c292d839be6296bc442153c278e3",
12 "mtime_ns": 178923934275490346512 "mtime_ns": 1790404369445435251
13 }13 }
14]14]
corpus/table-edit/nested/candidate/Open Notebook.onetoc2
Binary files a/corpus/table-edit/nested/candidate/Open Notebook.onetoc2 and b/corpus/table-edit/nested/candidate/Open Notebook.onetoc2 differ
corpus/table-edit/nested/candidate/synthetic.one
Binary files a/corpus/table-edit/nested/candidate/synthetic.one and b/corpus/table-edit/nested/candidate/synthetic.one differ
corpus/table-edit/nested/cold/commands.jsonl+1-1
...@@ -1,3 +1,3 @@...@@ -1,3 +1,3 @@
1{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}1{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
2{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-8E63A852 -ExpectedPages 12", "exit": 0, "stdout": "Read 1 sections and 12 pages.\r\n", "stderr": "", "error": null}2{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-B7496B80 -ExpectedPages 12", "exit": 0, "stdout": "Read 1 sections and 12 pages.\r\n", "stderr": "", "error": null}
3{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}3{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
corpus/table-edit/nested/cold/machine.json+1-1
...@@ -1 +1 @@...@@ -1 +1 @@
1{"name": "m6-8e63a852", "hostname": "ONE-M6-8E63A852"}1{"name": "m6-b7496b80", "hostname": "ONE-M6-B7496B80"}
corpus/table-edit/nested/cold/notebook/Open Notebook.onetoc2
Binary files a/corpus/table-edit/nested/cold/notebook/Open Notebook.onetoc2 and b/corpus/table-edit/nested/cold/notebook/Open Notebook.onetoc2 differ
corpus/table-edit/nested/cold/notebook/synthetic.one
Binary files a/corpus/table-edit/nested/cold/notebook/synthetic.one and b/corpus/table-edit/nested/cold/notebook/synthetic.one differ
corpus/table-edit/nested/cold/read/environment.json+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1{ 1{
2 "powershell": "5.1.14409.1005", 2 "powershell": "5.1.14409.1005",
3 "schema": "xs2010", 3 "schema": "xs2010",
4 "hostname": "ONE-M6-8E63A852", 4 "hostname": "ONE-M6-B7496B80",
5 "cold": true, 5 "cold": true,
6 "onenote": "14.0.4763.1000" 6 "onenote": "14.0.4763.1000"
7} 7}
corpus/table-edit/nested/cold/read/hierarchy.xml+1-1
...@@ -1,2 +1,2 @@...@@ -1,2 +1,2 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{D5C61ACD-0C18-47AB-9BB7-EA0453092F1A}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-12T21:55:30.000Z" color="#B49EDE"><one:Section name="synthetic" ID="{DCA72CDB-70D0-04AA-3F56-92B886A9FA01}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-12T21:55:30.000Z" color="#8AA8E4"><one:Page ID="{F0EEDF5D-F91B-47BB-B831-3A297A91E61E}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{C131A2C2-2BC5-4502-8CFF-F5FCAC5673F9}{1}{B0}" name="Indent first subtree" dateTime="2026-09-08T19:25:53.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{80C90902-0F49-4C7B-8E7F-596216EE4379}{1}{B0}" name="Outdent first group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{B78DF4E7-9F1A-454E-BE5B-F35A57E182C9}{1}{B0}" name="Delete only grouped subtree" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{F0F484B0-2100-4784-B9E5-4A8B81EF98E2}{1}{B0}" name="Delete unindented sibling after group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{56E30880-7E3F-485A-BAC8-09FC9BA6B44B}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{D1132B3D-E3A6-4819-8341-86F33DC7D2E2}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{676EECC9-3E35-4A9E-AF43-A9882BDDAC59}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{E63E6C84-AAC4-4272-82DB-0820DBFF5E89}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{8C93E36A-F0C0-4425-A30B-B2B8A0719684}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{789DA77C-5409-4002-B4AC-695817E35153}{1}{B0}" name="Outdent child across indentation gap" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/><one:Page ID="{A04B2BEA-D010-4C40-BAAC-F5BDB53D09E0}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1"/></one:Section></one:Notebook> 2<one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{B6FC55C7-91E6-4220-ACE0-617887D15952}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-26T06:39:56.000Z" color="#B49EDE"><one:Section name="synthetic" ID="{699BC697-816F-0570-1454-C5A7CDC73E3A}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-26T06:39:56.000Z" color="#8AA8E4"><one:Page ID="{45D23511-08A4-4661-9333-6D3631FF2225}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{740D488E-DA7A-44D8-A7FD-A2E3E738B7C2}{1}{B0}" name="Indent first subtree" dateTime="2026-09-08T19:25:53.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{35F5E34E-FEF6-4DA1-A57D-0E7D5D808742}{1}{B0}" name="Outdent first group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{02B11EAB-6EA5-4494-9559-A4451C8F46F2}{1}{B0}" name="Delete only grouped subtree" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{45C86EFC-D0BF-465E-92E7-1D94CA815CD9}{1}{B0}" name="Delete unindented sibling after group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{E3DFE2CC-8F80-4980-91CA-5EE3D0C87070}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{642FC171-1219-49C3-A843-D1EC76A916D9}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{D2520685-CF8A-4B44-8441-FE9760B36862}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{530286C8-5B7B-43A8-A9D9-5F3F90919AB2}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{39AF0926-017F-45FF-8809-E5A7EB1F52BF}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{CDA14D30-A5B6-41D8-9FAE-3E475C8D9568}{1}{B0}" name="Outdent child across indentation gap" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/><one:Page ID="{1577C1A6-21AF-4D9A-91AE-A2A2FE53CDDB}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1"/></one:Section></one:Notebook>
corpus/table-edit/nested/cold/read/page-000.xml+3-3
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{56E30880-7E3F-485A-BAC8-09FC9BA6B44B}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete cell subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.30315780639648"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{40}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{41}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{42}{B0}" lastModifiedTime="2026-09-12T21:53:40.000Z"><one:Cell lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{43}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{02B11EAB-6EA5-4494-9559-A4451C8F46F2}{1}{B0}" name="Delete only grouped subtree" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete only grouped subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="169.6107788085937" height="53.71086883544922"/><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a 3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a
4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span 4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{66}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{71}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other cell]]></one:T></one:OE><one:OE author="Table author" lastModifiedBy="Table author" creationTime="2026-09-12T21:53:40.000Z" lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{DFC0C49E-E046-0AFB-1BF8-ABC555E425B9}{0}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:Table bordersVisible="true" lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{89987300-2101-0CE3-210D-9562C6C4ADB1}{0}{B0}"><one:Columns><one:Column index="0" width="72.0" isLocked="true"/></one:Columns><one:Row objectID="{CF32A2C1-04E7-0743-39C0-9C765A8E44CC}{0}{B0}" lastModifiedTime="2026-09-12T21:53:40.000Z"><one:Cell lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{1BAB3712-BACF-009D-0E46-EBA95350D396}{0}{B0}"><one:OEChildren><one:OE creationTime="2026-09-12T21:53:40.000Z" lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{82BC0B4C-FCA6-0778-2A66-7B5A5496D0A5}{0}{B0}" alignment="left"><one:T><![CDATA[Inner one]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{A337C7AB-1858-0A2B-34E0-881F2933D0FB}{0}{B0}" lastModifiedTime="2026-09-12T21:53:40.000Z"><one:Cell lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{6E7D1347-DF88-06E2-392D-6543DAD4FBFE}{0}{B0}"><one:OEChildren><one:OE creationTime="2026-09-12T21:53:40.000Z" lastModifiedTime="2026-09-12T21:53:40.000Z" objectID="{5E489B68-01D9-0719-0234-7A5E46894E1A}{0}{B0}" alignment="left"><one:T><![CDATA[Inner two]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{79}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{C8D3C6B7-FB34-46C1-B4B5-3FE5439E350E}{80}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE></one:OEChildren><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{66}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1DADDA58-BF77-4990-A3B0-DD48A8C24E06}{67}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/table-edit/nested/cold/read/page-001.xml+3-3
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{676EECC9-3E35-4A9E-AF43-A9882BDDAC59}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move numbered subtree down]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{40}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{1577C1A6-21AF-4D9A-91AE-A2A2FE53CDDB}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move cell subtree down]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.23086929321289"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{40}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{41}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{42}{B0}" lastModifiedTime="2026-09-08T19:26:01.000Z"><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{43}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a 3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a
4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span 4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{67}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{73}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0D5508DA-3F74-4EFD-8A2C-68449D01A988}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{66}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{71}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{79}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{7E4295D4-7E60-4F12-BF8D-AA4BEAFCF664}{80}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/table-edit/nested/cold/read/page-002.xml+3-3
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{789DA77C-5409-4002-B4AC-695817E35153}{1}{B0}" name="Outdent child across indentation gap" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Outdent child across indentation gap]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="196.6107788085937" height="80.56629943847656"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Anchor]]></one:T><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{45}{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 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{35F5E34E-FEF6-4DA1-A57D-0E7D5D808742}{1}{B0}" name="Outdent first group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Outdent first group]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="169.6107788085937" height="53.71086883544922"/><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a 3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a
4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{54}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span 4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{65}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other child]]></one:T></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{70}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{76}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{5DAD8763-3535-46F5-8981-F32E9714936D}{77}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE></one:OEChildren><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{66}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{AE8288C3-7617-4A49-9E4C-C628929357F8}{67}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/table-edit/nested/cold/read/page-003.xml+3-3
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{80C90902-0F49-4C7B-8E7F-596216EE4379}{1}{B0}" name="Outdent first group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Outdent first group]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="169.6107788085937" height="53.71086883544922"/><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{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 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{39AF0926-017F-45FF-8809-E5A7EB1F52BF}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent bullet subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{40}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a 3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a
4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span 4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE></one:OEChildren><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{66}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{1BBE628F-87A8-4B93-B54E-9137D9FD93C3}{67}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{67}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{73}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{00847BD4-E464-4DDD-945A-2947FB4E31B2}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/table-edit/nested/cold/read/page-004.xml+2-3
...@@ -1,5 +1,4 @@...@@ -1,5 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{8C93E36A-F0C0-4425-A30B-B2B8A0719684}{1}{B0}" name="Indent bullet subtree" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent bullet subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{40}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{45C86EFC-D0BF-465E-92E7-1D94CA815CD9}{1}{B0}" name="Delete unindented sibling after group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{349AC90C-FE91-4368-BC4B-E202977EB986}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete unindented sibling after group]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{349AC90C-FE91-4368-BC4B-E202977EB986}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="129.6621246337891" height="26.85543441772461"/><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{349AC90C-FE91-4368-BC4B-E202977EB986}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Anchor]]></one:T></one:OE></one:OEChildren><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{349AC90C-FE91-4368-BC4B-E202977EB986}{45}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a 3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a
4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span 4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{349AC90C-FE91-4368-BC4B-E202977EB986}{55}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{349AC90C-FE91-4368-BC4B-E202977EB986}{56}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{67}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Bullet bullet="3" fontSize="11.0"/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{73}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{B5B89198-15DB-4C07-BF58-7E58B020F589}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/table-edit/nested/cold/read/page-005.xml+19-4
...@@ -1,5 +1,20 @@...@@ -1,5 +1,20 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{A04B2BEA-D010-4C40-BAAC-F5BDB53D09E0}{1}{B0}" name="Move cell subtree down" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move cell subtree down]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.23086929321289"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{40}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{41}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{42}{B0}" lastModifiedTime="2026-09-08T19:26:01.000Z"><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{43}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{45D23511-08A4-4661-9333-6D3631FF2225}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Outline author="Rich author" lastModifiedBy="Rich author" lastModifiedTime="2026-09-08T04:47:24.000Z" objectID="{221E9DAD-43D1-45D7-9CC1-E1EDCE63C744}{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="{257B355A-B0AC-0EDD-2C95-22E70508B5B2}{1}{B0}" alignment="left"><one:T><![CDATA[<span style='font-weight:bold;
3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a 3text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span
4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span 4style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family:
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{66}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{71}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{79}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{CB7E7F98-8FDF-4EC8-948F-FD54A192325F}{80}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 5Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri;
6font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through;
7font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family:
8Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family:
9Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size:
1011.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238;
11background:yellow;mso-highlight:yellow'>color</span><span style='font-family:
12Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family:
13Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size:
1411.0pt'><br>
15End</span>]]></one:T></one:OE><one:OE author="snow" authorInitials="S" lastModifiedBy="snow" lastModifiedByInitials="S" creationTime="2026-09-05T05:06:55.000Z" lastModifiedTime="2026-09-05T05:06:56.000Z" objectID="{221E9DAD-43D1-45D7-9CC1-E1EDCE63C744}{11}{B0}" alignment="left" quickStyleIndex="0" style="font-size:11.0pt;color:#1F4E79"><one:T><![CDATA[<span
16style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span
17style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span
18style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span
19style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed'
20lang=ar-SA>مرحبا</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/table-edit/nested/cold/read/page-006.xml+3-3
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{B78DF4E7-9F1A-454E-BE5B-F35A57E182C9}{1}{B0}" name="Delete only grouped subtree" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete only grouped subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="169.6107788085937" height="53.71086883544922"/><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{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 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{530286C8-5B7B-43A8-A9D9-5F3F90919AB2}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete numbered subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{40}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a 3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a
4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span 4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE></one:OEChildren><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{66}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{A8913014-4EC8-484A-88B2-8A57E3AC8A3D}{67}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{67}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{73}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{6DA0CF5F-713E-4AE2-86D9-8366208BBF56}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/table-edit/nested/cold/read/page-007.xml+2-3
...@@ -1,5 +1,4 @@...@@ -1,5 +1,4 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{C131A2C2-2BC5-4502-8CFF-F5FCAC5673F9}{1}{B0}" name="Indent first subtree" dateTime="2026-09-08T19:25:53.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent first subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="53.71086883544922"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:54.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{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 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{642FC171-1219-49C3-A843-D1EC76A916D9}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{17}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete sole cell paragraph]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{23}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="21.94771385192871"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{24}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{25}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{26}{B0}" lastModifiedTime="2026-09-08T19:26:01.000Z"><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{27}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{30}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a 3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a
4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span 4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{39}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{42}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{47}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{8F51E7BC-E879-45ED-8C29-9A3216E01E8C}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{65}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{0E6415D5-ECAA-442C-AED2-B0701782A11F}{66}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/table-edit/nested/cold/read/page-008.xml+3-2
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D1132B3D-E3A6-4819-8341-86F33DC7D2E2}{1}{B0}" name="Delete sole cell paragraph" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{17}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete sole cell paragraph]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{23}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="21.94771385192871"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{24}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{25}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{26}{B0}" lastModifiedTime="2026-09-08T19:26:01.000Z"><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{27}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{30}{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 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{740D488E-DA7A-44D8-A7FD-A2E3E738B7C2}{1}{B0}" name="Indent first subtree" dateTime="2026-09-08T19:25:53.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Indent first subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="53.71086883544922"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:54.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{40}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a 3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a
4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{39}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{42}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other cell]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{47}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{3A6D0DF0-19C6-4437-A72B-CD2D5D8EDAB7}{48}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{49}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{65}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-08T19:25:55.000Z" objectID="{BB58FF99-1D15-45F6-85D0-E76F5CEC6524}{66}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/table-edit/nested/cold/read/page-009.xml+3-3
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E63E6C84-AAC4-4272-82DB-0820DBFF5E89}{1}{B0}" name="Delete numbered subtree" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete numbered subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{40}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{CDA14D30-A5B6-41D8-9FAE-3E475C8D9568}{1}{B0}" name="Outdent child across indentation gap" dateTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Outdent child across indentation gap]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="196.6107788085937" height="80.56629943847656"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Anchor]]></one:T><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{45}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a 3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a
4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span 4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{54}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{67}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{73}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{D89C2513-8081-4B38-ADDB-D4796BE57B6D}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{60}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{65}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other child]]></one:T></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{70}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{76}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:58.000Z" lastModifiedTime="2026-09-08T19:25:58.000Z" objectID="{E8916D2F-C48A-472F-A283-A431DC7A5756}{77}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/table-edit/nested/cold/read/page-010.xml+4-19
...@@ -1,20 +1,5 @@...@@ -1,20 +1,5 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F0EEDF5D-F91B-47BB-B831-3A297A91E61E}{1}{B0}" name="Bold 🦀 italic é color 東京" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-12T21:55:19.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="{972277E1-B26E-440D-B7C3-B6F2850D037F}{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="{9047DF16-4113-0F07-0797-75F84E667189}{1}{B0}" alignment="left"><one:T><![CDATA[<span style='font-weight:bold; 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D2520685-CF8A-4B44-8441-FE9760B36862}{1}{B0}" name="Move numbered subtree down" dateTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Move numbered subtree down]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="142.6107788085937" height="67.13858032226562"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{40}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="I."/></one:List><one:T><![CDATA[Anchor]]></one:T></one:OE><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="II."/></one:List><one:T><![CDATA[<span
3text-decoration:underline;font-family:Calibri;font-size:11.0pt'>Bold</span><span 3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a
4style='font-family:Calibri;font-size:11.0pt'> </span><span style='font-family: 4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5Arial;font-size:13.5pt'>&#129408;</span><span style='font-family:Calibri; 5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{67}{B0}" alignment="left" quickStyleIndex="0"><one:List><one:Number numberSequence="1" numberFormat="##." fontSize="11.0" font="Calibri" text="III."/></one:List><one:T><![CDATA[Trailing sibling]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:26:01.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{73}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:57.000Z" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{B869E296-CECB-4F27-A12E-3F5BD66F6DB3}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
6font-size:11.0pt'> </span><span style='font-style:italic;text-decoration:line-through;
7font-family:Calibri;font-size:11.0pt'>italic</span><span style='font-family:
8Calibri;font-size:11.0pt'> </span><span style='vertical-align:super;font-family:
9Calibri;font-size:11.0pt'>é</span><span style='font-family:Calibri;font-size:
1011.0pt'> </span><span style='font-family:Calibri;font-size:11.0pt;color:#0C2238;
11background:yellow;mso-highlight:yellow'>color</span><span style='font-family:
12Calibri;font-size:11.0pt'> </span><span style='vertical-align:sub;font-family:
13Calibri;font-size:11.0pt'>東京</span><span style='font-family:Calibri;font-size:
1411.0pt'><br>
15End</span>]]></one:T></one:OE><one:OE author="snow" authorInitials="S" lastModifiedBy="snow" lastModifiedByInitials="S" creationTime="2026-09-05T05:06:55.000Z" lastModifiedTime="2026-09-05T05:06:56.000Z" objectID="{972277E1-B26E-440D-B7C3-B6F2850D037F}{11}{B0}" alignment="left" quickStyleIndex="0" style="font-size:11.0pt;color:#1F4E79"><one:T><![CDATA[<span
16style='font-weight:bold;font-family:Calibri' lang=en-US>Fictitious: café, </span><span
17style='font-weight:bold;font-family:SimSun' lang=en-US>東京</span><span
18style='font-weight:bold;font-family:Calibri' lang=en-US>, </span><span
19style='font-weight:bold;font-family:Arial;direction:rtl;unicode-bidi:embed'
20lang=ar-SA>مرحبا</span>]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/table-edit/nested/cold/read/page-011.xml+3-2
...@@ -1,4 +1,5 @@...@@ -1,4 +1,5 @@
1<?xml version="1.0"?> 1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{F0F484B0-2100-4784-B9E5-4A8B81EF98E2}{1}{B0}" name="Delete unindented sibling after group" dateTime="2026-09-08T19:25:55.000Z" lastModifiedTime="2026-09-12T21:55:19.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{81A62340-0F2E-42B2-9749-B51DDC107DBD}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete unindented sibling after group]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{81A62340-0F2E-42B2-9749-B51DDC107DBD}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="129.6621246337891" height="26.85543441772461"/><one:OEChildren indent="2"><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{81A62340-0F2E-42B2-9749-B51DDC107DBD}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Anchor]]></one:T></one:OE></one:OEChildren><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{81A62340-0F2E-42B2-9749-B51DDC107DBD}{45}{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 2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{E3DFE2CC-8F80-4980-91CA-5EE3D0C87070}{1}{B0}" name="Delete cell subtree" dateTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-26T06:39:46.000Z" pageLevel="1" lang="en-US"><one:TagDef index="0" type="0" symbol="3" fontColor="automatic" highlightColor="none" name="Preserved 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-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Delete cell subtree]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="428.1300048828125" height="62.30315780639648"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{40}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{41}{B0}"><one:Columns><one:Column index="0" width="240.0" isLocked="true"/><one:Column index="1" width="180.0" isLocked="true"/></one:Columns><one:Row objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{42}{B0}" lastModifiedTime="2026-09-26T06:32:49.000Z"><one:Cell lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{43}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{46}{B0}" alignment="left" quickStyleIndex="0"><one:Tag index="0" completed="true" disabled="false" creationDate="2020-01-02T03:04:05.000Z" completionDate="2020-01-02T04:05:06.000Z"/><one:T><![CDATA[<span
3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a 3style='font-weight:bold' lang=en-US>Target &#129408; é</span><span lang=en-US> </span><a
4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{81A62340-0F2E-42B2-9749-B51DDC107DBD}{55}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{81A62340-0F2E-42B2-9749-B51DDC107DBD}{56}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> 4href="https://example.invalid/tree"><span lang=x-none>Link label</span></a>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{55}{B0}" alignment="left" quickStyleIndex="0"><one:T style="font-family:Calibri;font-size:11.0pt"><![CDATA[<span
5style='font-style:italic'>Child</span>]]></one:T><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{61}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Grandchild]]></one:T></one:OE></one:OEChildren></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{66}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Cell tail]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{71}{B0}" lastModifiedByInitials="V"><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{74}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other cell]]></one:T></one:OE><one:OE author="Table author" lastModifiedBy="Table author" creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{10}{B0}" alignment="left" style="font-family:Calibri;font-size:11.0pt"><one:Table bordersVisible="true" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{1}{B0}"><one:Columns><one:Column index="0" width="72.0" isLocked="true"/></one:Columns><one:Row objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{2}{B0}" lastModifiedTime="2026-09-26T06:32:49.000Z"><one:Cell lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{3}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{4}{B0}" alignment="left"><one:T><![CDATA[Inner one]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{6}{B0}" lastModifiedTime="2026-09-26T06:32:49.000Z"><one:Cell lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{7}{B0}"><one:OEChildren><one:OE creationTime="2026-09-26T06:32:49.000Z" lastModifiedTime="2026-09-26T06:32:49.000Z" objectID="{D749AEBE-E6B0-0495-0344-3BA186216B7C}{8}{B0}" alignment="left"><one:T><![CDATA[Inner two]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Cell></one:Row></one:Table></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-08T19:25:57.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{79}{B0}"><one:Position x="540.0" y="108.0" z="1"/><one:Size width="99.85796356201171" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-08T19:25:56.000Z" lastModifiedTime="2026-09-08T19:25:56.000Z" objectID="{7DEF2CFB-0A8B-471B-9FB7-68FA08F0F135}{80}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Other outline]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page>
corpus/table-edit/nested/cold/run.json+1-1
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1{1{
2 "notebook": "/private/tmp/m4/nested",2 "notebook": "/Users/clo/dev/one/evidence/titles/table-nested",
3 "expected_pages": 12,3 "expected_pages": 12,
4 "author": null,4 "author": null,
5 "author_timeout_seconds": 600,5 "author_timeout_seconds": 600,
corpus/table-edit/nested/cold/source.json+5-5
...@@ -2,13 +2,13 @@...@@ -2,13 +2,13 @@
2 {2 {
3 "path": "Open Notebook.onetoc2",3 "path": "Open Notebook.onetoc2",
4 "bytes": 1612,4 "bytes": 1612,
5 "sha256": "1c7c0e30cc2c2f029f87e20bba0981f0bf8a7f2f6643ad67a24e80834f539991",5 "sha256": "1290deaddfd9b3dc7c08ee83da786bc057c7e18ec6a9212b0dc8453a7254c09b",
6 "mtime_ns": 17892500207534600256 "mtime_ns": 1790404369595025987
7 },7 },
8 {8 {
9 "path": "synthetic.one",9 "path": "synthetic.one",
10 "bytes": 91192,10 "bytes": 90608,
11 "sha256": "6f3b8737c318834e2958368ed9fd9bc2ff4c7490bbd4a29907fa76e472355b43",11 "sha256": "2d0333a99c6cdf9499ebf972fb53a6cf987ceba1950738f01158e028e8b82fc1",
12 "mtime_ns": 178925002075255139712 "mtime_ns": 1790404369594093530
13 }13 }
14]14]
crates/notebook/README.md+8-2
...@@ -188,6 +188,9 @@ object space, which survives the tested native compaction that replaces the file...@@ -188,6 +188,9 @@ object space, which survives the tested native compaction that replaces the file
188An `Arc<Replica>` can own one background worker. Supply a connection factory, poll188An `Arc<Replica>` can own one background worker. Supply a connection factory, poll
189interval and observer; successful publications drain immediately, durable local189interval and observer; successful publications drain immediately, durable local
190edits wake the worker, and `wake()` requests an immediate reachability retry.190edits wake the worker, and `wake()` requests an immediate reachability retry.
191While nothing is queued, a remote whose `Remote::stamp` (its header and length, read
192without coordination) equals the last observed image's is not read again; publishing
193against an unchanged stamp needs no read either.
191Transport failures discard the old connection and retry through the factory;194Transport failures discard the old connection and retry through the factory;
192Read contention and `NotCommitted` operations with `WouldBlock` or `ResourceBusy`195Read contention and `NotCommitted` operations with `WouldBlock` or `ResourceBusy`
193reuse the connection. Contended `NotCommitted` operations use randomized backoff,196reuse the connection. Contended `NotCommitted` operations use randomized backoff,
...@@ -383,6 +386,7 @@ experimental Rust API with native interoperability evidence in the repository's...@@ -383,6 +386,7 @@ experimental Rust API with native interoperability evidence in the repository's
383[Milestone 9](../../evidence/MILESTONE9.md).386[Milestone 9](../../evidence/MILESTONE9.md).
384387
385```no_run388```no_run
389# #[cfg(feature = "smb")] {
386use notebook::smb::{Client, Credentials};390use notebook::smb::{Client, Credentials};
387use std::time::Duration;391use std::time::Duration;
388392
...@@ -396,6 +400,7 @@ let snapshot = client.read("Personal/Video.one", 64 * 1024 * 1024)?;...@@ -396,6 +400,7 @@ let snapshot = client.read("Personal/Video.one", 64 * 1024 * 1024)?;
396let store = onestore::Store::parse(&snapshot)?;400let store = onestore::Store::parse(&snapshot)?;
397let revisions = onestore::RevisionIndex::parse(&store)?;401let revisions = onestore::RevisionIndex::parse(&store)?;
398let document = onestore::document::Document::parse(&revisions)?;402let document = onestore::document::Document::parse(&revisions)?;
403# }
399# Ok::<(), Box<dyn std::error::Error>>(())404# Ok::<(), Box<dyn std::error::Error>>(())
400```405```
401406
...@@ -404,8 +409,9 @@ snapshot. Call from a background thread outside a Tokio runtime. Use identities...@@ -404,8 +409,9 @@ snapshot. Call from a background thread outside a Tokio runtime. Use identities
404from the document and the same snapshot with `Client::commit_text` or409from the document and the same snapshot with `Client::commit_text` or
405`Client::commit_property_bytes`; their errors retain `onestore::CommitState`.410`Client::commit_property_bytes`; their errors retain `onestore::CommitState`.
406`PreparedEdit::page` separates preparation from I/O: inspect the immutable image411`PreparedEdit::page` separates preparation from I/O: inspect the immutable image
407and persist the intended revision identity before `Client::commit_prepared`.412and persist the intended revision identity before `Client::commit_transaction` publishes
408`Client::confirm_snapshot` compares and flushes an observed image, then refreshes413its `transaction()`. `Client::stamp` reads a file's header and length without coordination,
414for polling. `Client::confirm_snapshot` checks and flushes an observed image's stamp, then refreshes
409its header version metadata without adding a revision. The caller must first415its header version metadata without adding a revision. The caller must first
410establish which intents that image contains and reread before another commit.416establish which intents that image contains and reread before another commit.
411417
crates/notebook/examples/queue_scale.rs+3-3
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1use notebook::{EditStatus, Remote, Replica};1use notebook::{EditStatus, Remote, Replica};
2use onestore::{2use onestore::{
3 CommitError, PageCreation, PreparedEdit, RevisionIndex, Store,3 CommitError, PageCreation, PreparedEdit, RevisionIndex, Store, Transaction,
4 document::Document,4 document::Document,
5 page::{Page, PageObject, Paragraph, text::Edit},5 page::{Page, PageObject, Paragraph, text::Edit},
6};6};
...@@ -12,8 +12,8 @@ impl Remote for FileRemote {...@@ -12,8 +12,8 @@ impl Remote for FileRemote {
12 fn read(&mut self) -> io::Result<Vec<u8>> {12 fn read(&mut self) -> io::Result<Vec<u8>> {
13 onestore::read_file(&self.0)13 onestore::read_file(&self.0)
14 }14 }
15 fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> {15 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
16 edit.commit_file(&self.0)16 transaction.commit_file(&self.0)
17 }17 }
18 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {18 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {
19 onestore::confirm_file_snapshot(&self.0, snapshot)19 onestore::confirm_file_snapshot(&self.0, snapshot)
crates/notebook/examples/recovery_probe.rs+3-3
...@@ -4,7 +4,7 @@ mod support {...@@ -4,7 +4,7 @@ mod support {
4use support::view::view;4use support::view::view;
55
6use notebook::{EditStatus, Remote, Replica};6use notebook::{EditStatus, Remote, Replica};
7use onestore::{CommitError, CommitIo, PreparedEdit};7use onestore::{CommitError, CommitIo, Transaction};
8use serde_json::json;8use serde_json::json;
9use std::{9use std::{
10 env,10 env,
...@@ -67,9 +67,9 @@ impl Remote for Disk {...@@ -67,9 +67,9 @@ impl Remote for Disk {
67 phase("read-after");67 phase("read-after");
68 result68 result
69 }69 }
70 fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> {70 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
71 phase("publish-before");71 phase("publish-before");
72 let result = edit.commit(self);72 let result = transaction.commit(self);
73 phase("publish-after");73 phase("publish-after");
74 result74 result
75 }75 }
crates/notebook/examples/smb_offline_client.rs+14-6
...@@ -4,7 +4,7 @@ mod concurrent;...@@ -4,7 +4,7 @@ mod concurrent;
4use notebook::smb::{Client, Credentials};4use notebook::smb::{Client, Credentials};
5use notebook::{EditStatus, Error, Remote, Replica, SmbRemote};5use notebook::{EditStatus, Error, Remote, Replica, SmbRemote};
6use onestore::{6use onestore::{
7 CommitError, CommitState, ExGuid, PreparedEdit, RevisionIndex, Store, page::Paragraph,7 CommitError, CommitState, ExGuid, RevisionIndex, Store, Transaction, page::Paragraph,
8};8};
9use serde_json::json;9use serde_json::json;
10use std::{10use std::{
...@@ -56,6 +56,8 @@ enum Pause {...@@ -56,6 +56,8 @@ enum Pause {
56struct Traced<R> {56struct Traced<R> {
57 remote: R,57 remote: R,
58 before: Option<(String, Option<DocumentView>)>,58 before: Option<(String, Option<DocumentView>)>,
59 /// The last image read, which publications apply to.
60 read: Vec<u8>,
59 pause: Option<Pause>,61 pause: Option<Pause>,
60 documents: bool,62 documents: bool,
61}63}
...@@ -75,15 +77,21 @@ impl<R: Remote> Remote for Traced<R> {...@@ -75,15 +77,21 @@ impl<R: Remote> Remote for Traced<R> {
75 json!({"event":"read", "started_us":started, "finished_us":now(), "text":observed.text, "documents":documents.as_ref().map(|view| &view.texts), "document_graph":documents.as_ref().map(|view| &view.graph)})77 json!({"event":"read", "started_us":started, "finished_us":now(), "text":observed.text, "documents":documents.as_ref().map(|view| &view.texts), "document_graph":documents.as_ref().map(|view| &view.graph)})
76 );78 );
77 self.before = Some((observed.text, documents));79 self.before = Some((observed.text, documents));
80 self.read.clone_from(&bytes);
78 Ok(bytes)81 Ok(bytes)
79 }82 }
80 fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> {83 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
81 let after = view(edit.as_bytes()).map_err(|error| CommitError {84 let mut image = self.read.clone();
85 transaction.apply(&mut image).map_err(|error| CommitError {
86 state: CommitState::NotCommitted,
87 error: io::Error::other(error.to_string()),
88 })?;
89 let after = view(&image).map_err(|error| CommitError {
82 state: CommitState::NotCommitted,90 state: CommitState::NotCommitted,
83 error: io::Error::other(error.to_string()),91 error: io::Error::other(error.to_string()),
84 })?;92 })?;
85 let documents = if self.documents {93 let documents = if self.documents {
86 Some(document_view(edit.as_bytes()).map_err(|error| CommitError {94 Some(document_view(&image).map_err(|error| CommitError {
87 state: CommitState::NotCommitted,95 state: CommitState::NotCommitted,
88 error: io::Error::other(error),96 error: io::Error::other(error),
89 })?)97 })?)
...@@ -151,7 +159,7 @@ impl<R: Remote> Remote for Traced<R> {...@@ -151,7 +159,7 @@ impl<R: Remote> Remote for Traced<R> {
151 }159 }
152 }160 }
153 let started = now();161 let started = now();
154 let result = self.remote.publish(edit);162 let result = self.remote.publish(transaction);
155 let finished = now();163 let finished = now();
156 println!(164 println!(
157 "{}",165 "{}",
...@@ -399,7 +407,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {...@@ -399,7 +407,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
399 let worker = cache.start_sync(Duration::from_millis(50), move || {407 let worker = cache.start_sync(Duration::from_millis(50), move || {
400 let client = Client::connect(&address, &share, Credentials::default(), Duration::from_secs(5))?;408 let client = Client::connect(&address, &share, Credentials::default(), Duration::from_secs(5))?;
401 println!("{}", json!({"event":"transport_connected", "at_us":now()}));409 println!("{}", json!({"event":"transport_connected", "at_us":now()}));
402 Ok(Traced { remote: SmbRemote::new(client, &path, 256 * 1024 * 1024), before:None, pause:pause.clone(), documents })410 Ok(Traced { remote: SmbRemote::new(client, &path, 256 * 1024 * 1024), before:None, read:Vec::new(), pause:pause.clone(), documents })
403 }, move |result| {411 }, move |result| {
404 if let Err(error) = result {412 if let Err(error) = result {
405 println!("{}", json!({"event":"sync_error", "error":error.to_string(), "at_us":now()}));413 println!("{}", json!({"event":"sync_error", "error":error.to_string(), "at_us":now()}));
crates/notebook/src/images.rs+29-2
...@@ -3,6 +3,7 @@...@@ -3,6 +3,7 @@
3//! instead of the section.3//! instead of the section.
44
5use crate::Result;5use crate::Result;
6use onestore::Stamp;
6use rusqlite::{Connection, params};7use rusqlite::{Connection, params};
7use std::io;8use std::io;
89
...@@ -91,8 +92,9 @@ pub(crate) fn working_length(connection: &Connection) -> Result<u64> {...@@ -91,8 +92,9 @@ pub(crate) fn working_length(connection: &Connection) -> Result<u64> {
91 "SELECT length(base), substr(working, 1, 8) FROM replica WHERE id=1",92 "SELECT length(base), substr(working, 1, 8) FROM replica WHERE id=1",
92 [],93 [],
93 |row| {94 |row| {
94 let header: Vec<u8> = row.get(1)?;95 // SQLite's substr of an empty blob is NULL.
95 Ok(match header.first_chunk::<8>() {96 let header: Option<Vec<u8>> = row.get(1)?;
97 Ok(match header.as_deref().and_then(<[u8]>::first_chunk::<8>) {
96 Some(length) => u64::from_le_bytes(*length),98 Some(length) => u64::from_le_bytes(*length),
97 None => row.get::<_, i64>(0)? as u64,99 None => row.get::<_, i64>(0)? as u64,
98 })100 })
...@@ -100,6 +102,31 @@ pub(crate) fn working_length(connection: &Connection) -> Result<u64> {...@@ -100,6 +102,31 @@ pub(crate) fn working_length(connection: &Connection) -> Result<u64> {
100 )?)102 )?)
101}103}
102104
105/// Whether no edit is queued and the working image is the base image, without reading
106/// either image.
107pub(crate) fn settled(connection: &Connection) -> Result<bool> {
108 Ok(connection.query_row(
109 "SELECT length(working) = 0 AND NOT EXISTS (SELECT 1 FROM edits) FROM replica WHERE id=1",
110 [],
111 |row| row.get(0),
112 )?)
113}
114
115/// The base image's stamp, without reading the image.
116pub(crate) fn stamp(connection: &Connection) -> Result<Stamp> {
117 let (header, length): (Vec<u8>, i64) = connection.query_row(
118 "SELECT substr(base, 1, 1024), length(base) FROM replica WHERE id=1",
119 [],
120 |row| Ok((row.get(0)?, row.get(1)?)),
121 )?;
122 Ok(Stamp {
123 header: header
124 .try_into()
125 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "Damaged base image"))?,
126 length: u64::try_from(length).map_err(io::Error::other)?,
127 })
128}
129
103pub(crate) fn working(connection: &Connection) -> Result<Vec<u8>> {130pub(crate) fn working(connection: &Connection) -> Result<Vec<u8>> {
104 let (base, patch): (Vec<u8>, Vec<u8>) =131 let (base, patch): (Vec<u8>, Vec<u8>) =
105 connection.query_row("SELECT base, working FROM replica WHERE id=1", [], |row| {132 connection.query_row("SELECT base, working FROM replica WHERE id=1", [], |row| {
crates/notebook/src/lib.rs+44
...@@ -255,6 +255,31 @@ impl Replica {...@@ -255,6 +255,31 @@ impl Replica {
255 images::working(&connection)255 images::working(&connection)
256 }256 }
257257
258 /// The page in `space` as the working image stores it.
259 pub fn page(&self, space: ExGuid) -> Result<Page> {
260 let snapshot = self.snapshot()?;
261 let store = Store::parse(&snapshot)?;
262 let index = RevisionIndex::parse(&store)?;
263 Ok(Page::from_space(&Document::parse(&index)?, space)?)
264 }
265
266 /// Whether no edit is queued and the remote still has the last observed image's stamp,
267 /// reading neither image; the cache is not locked during remote I/O.
268 pub(crate) fn settled(&self, remote: &mut impl Remote) -> Result<bool> {
269 let lock = || {
270 self.connection
271 .lock()
272 .map_err(|_| io::Error::other("Cache owner panicked"))
273 };
274 if !images::settled(&*lock()?)? {
275 return Ok(false);
276 }
277 let Some(stamp) = remote.stamp().map_err(Error::RemoteIo)? else {
278 return Ok(false);
279 };
280 Ok(images::stamp(&*lock()?)? == stamp)
281 }
282
258 pub fn pending(&self) -> Result<Vec<PendingEdit>> {283 pub fn pending(&self) -> Result<Vec<PendingEdit>> {
259 let connection = self284 let connection = self
260 .connection285 .connection
...@@ -263,6 +288,25 @@ impl Replica {...@@ -263,6 +288,25 @@ impl Replica {
263 pending(&connection)288 pending(&connection)
264 }289 }
265290
291 /// Identities and page spaces of the pending edits, oldest first, without reading their
292 /// operations.
293 pub(crate) fn queued(&self) -> Result<Vec<(u64, ExGuid)>> {
294 let connection = self
295 .connection
296 .lock()
297 .map_err(|_| io::Error::other("Cache owner panicked"))?;
298 let mut query = connection.prepare("SELECT id, space FROM edits ORDER BY id")?;
299 let mut rows = query.query([])?;
300 let mut edits = Vec::new();
301 while let Some(row) = rows.next()? {
302 edits.push((
303 u64::try_from(row.get::<_, i64>(0)?).map_err(io::Error::other)?,
304 row.get::<_, String>(1)?.parse()?,
305 ));
306 }
307 Ok(edits)
308 }
309
266 /// Durably queues an edited page model using the supplied local snapshot.310 /// Durably queues an edited page model using the supplied local snapshot.
267 /// While the newest pending edit is an unattempted save of the same page, a new save311 /// While the newest pending edit is an unattempted save of the same page, a new save
268 /// replaces its result instead of queueing another publication, as OneNote does312 /// replaces its result instead of queueing another publication, as OneNote does
crates/notebook/src/merge.rs+16
...@@ -257,6 +257,18 @@ fn merge_paragraphs(...@@ -257,6 +257,18 @@ fn merge_paragraphs(
257 }257 }
258 // Child order per container: the remote order with our repositioned paragraphs re-placed.258 // Child order per container: the remote order with our repositioned paragraphs re-placed.
259 let (bc, oc, tc) = (children(base), children(ours), children(theirs));259 let (bc, oc, tc) = (children(base), children(ours), children(theirs));
260 // A paragraph we added after one they gave new children would sit after or among those
261 // children; Enter splitting a paragraph carries its children below, so neither is assumed.
262 for pair in oc.values().flat_map(|list| list.windows(2)) {
263 if b.contains_key(&pair[0])
264 && !b.contains_key(&pair[1])
265 && tc
266 .get(&Some(pair[0]))
267 .is_some_and(|children| children.iter().any(|id| !b.contains_key(id)))
268 {
269 return None;
270 }
271 }
260 let mut order: BTreeMap<Option<ExGuid>, Vec<ExGuid>> = BTreeMap::new();272 let mut order: BTreeMap<Option<ExGuid>, Vec<ExGuid>> = BTreeMap::new();
261 let containers: BTreeSet<Option<ExGuid>> = bc273 let containers: BTreeSet<Option<ExGuid>> = bc
262 .keys()274 .keys()
...@@ -459,6 +471,10 @@ fn merge_text(...@@ -459,6 +471,10 @@ fn merge_text(
459 return None;471 return None;
460 }472 }
461 let mapped = crate::rebase::rebase(b, theirs.text(), start..end)?;473 let mapped = crate::rebase::rebase(b, theirs.text(), start..end)?;
474 // Text they reformatted is not ours to replace, nor to move as a split would.
475 if start < end && theirs.slice(mapped.clone()).ok()? != base.slice(start..end).ok()? {
476 return None;
477 }
462 let mut merged = theirs.clone();478 let mut merged = theirs.clone();
463 merged479 merged
464 .apply(Edit {480 .apply(Edit {
crates/notebook/src/session.rs+44-18
...@@ -5,8 +5,8 @@ use crate::{...@@ -5,8 +5,8 @@ use crate::{
5 ConflictKind, EditStatus, Error, PendingEdit, Remote, Replica, Result, SyncWorker, discover,5 ConflictKind, EditStatus, Error, PendingEdit, Remote, Replica, Result, SyncWorker, discover,
6};6};
7use onestore::{7use onestore::{
8 CommitError, ExGuid, PageCreation, PreparedEdit, RevisionIndex, Store, document::Document,8 CommitError, ExGuid, PageCreation, PreparedEdit, RevisionIndex, Stamp, Store, Transaction,
9 page::Page,9 document::Document, page::Page,
10};10};
11use std::{11use std::{
12 collections::{BTreeMap, VecDeque},12 collections::{BTreeMap, VecDeque},
...@@ -15,6 +15,7 @@ use std::{...@@ -15,6 +15,7 @@ use std::{
15 path::{Path, PathBuf},15 path::{Path, PathBuf},
16 sync::{16 sync::{
17 Arc, Mutex,17 Arc, Mutex,
18 atomic::{AtomicUsize, Ordering},
18 mpsc::{self, Receiver, Sender},19 mpsc::{self, Receiver, Sender},
19 },20 },
20 thread::{self, JoinHandle},21 thread::{self, JoinHandle},
...@@ -227,7 +228,9 @@ impl Storage for Share {...@@ -227,7 +228,9 @@ impl Storage for Share {
227 }228 }
228229
229 fn commit(&self, path: &str, edit: &PreparedEdit<'_>) -> Result<()> {230 fn commit(&self, path: &str, edit: &PreparedEdit<'_>) -> Result<()> {
230 Ok(self.client.commit_prepared(&self.path(path), edit)?)231 Ok(self
232 .client
233 .commit_transaction(&self.path(path), &edit.transaction())?)
231 }234 }
232235
233 fn set_property(236 fn set_property(
...@@ -760,6 +763,8 @@ pub struct Section {...@@ -760,6 +763,8 @@ pub struct Section {
760 events: Receiver<Event>,763 events: Receiver<Event>,
761 saves: Option<Sender<PageSave>>,764 saves: Option<Sender<PageSave>>,
762 saved: Receiver<(ExGuid, std::result::Result<Save, String>)>,765 saved: Receiver<(ExGuid, std::result::Result<Save, String>)>,
766 /// Saves queued and not yet reported.
767 unsaved: Arc<AtomicUsize>,
763 saver: Option<JoinHandle<()>>,768 saver: Option<JoinHandle<()>>,
764}769}
765770
...@@ -850,12 +855,15 @@ impl Section {...@@ -850,12 +855,15 @@ impl Section {
850 }855 }
851 };856 };
852 let (saves, requests) = mpsc::channel();857 let (saves, requests) = mpsc::channel();
858 let unsaved = Arc::new(AtomicUsize::new(0));
853 let saver = {859 let saver = {
854 let (replica, signal) = (Arc::clone(&replica), signal.clone());860 let (replica, signal) = (Arc::clone(&replica), signal.clone());
861 let unsaved = Arc::clone(&unsaved);
855 thread::Builder::new()862 thread::Builder::new()
856 .name("onestore-save".into())863 .name("onestore-save".into())
857 .spawn(move || {864 .spawn(move || {
858 save_pages(&replica, &requests, |outcome| {865 save_pages(&replica, &requests, |outcome, count| {
866 unsaved.fetch_sub(count, Ordering::Release);
859 if saved_sender.send(outcome).is_ok() {867 if saved_sender.send(outcome).is_ok() {
860 signal();868 signal();
861 }869 }
...@@ -889,6 +897,7 @@ impl Section {...@@ -889,6 +897,7 @@ impl Section {
889 events,897 events,
890 saves: Some(saves),898 saves: Some(saves),
891 saved,899 saved,
900 unsaved,
892 saver: Some(saver),901 saver: Some(saver),
893 })902 })
894 }903 }
...@@ -930,10 +939,7 @@ impl Section {...@@ -930,10 +939,7 @@ impl Section {
930 }939 }
931940
932 pub fn page(&self, space: ExGuid) -> Result<Page> {941 pub fn page(&self, space: ExGuid) -> Result<Page> {
933 let snapshot = self.replica.snapshot()?;942 self.replica.page(space)
934 let store = Store::parse(&snapshot)?;
935 let index = RevisionIndex::parse(&store)?;
936 Ok(Page::from_space(&Document::parse(&index)?, space)?)
937 }943 }
938944
939 /// Copies a page (usually from another section) to the end of this section as a945 /// Copies a page (usually from another section) to the end of this section as a
...@@ -999,12 +1005,18 @@ impl Section {...@@ -999,12 +1005,18 @@ impl Section {
999 after,1005 after,
1000 author: author.to_owned(),1006 author: author.to_owned(),
1001 };1007 };
1008 self.unsaved.fetch_add(1, Ordering::Relaxed);
1002 self.saves1009 self.saves
1003 .as_ref()1010 .as_ref()
1004 .and_then(|saves| saves.send(save).ok())1011 .and_then(|saves| saves.send(save).ok())
1005 .ok_or_else(|| io::Error::other("The save thread stopped").into())1012 .ok_or_else(|| io::Error::other("The save thread stopped").into())
1006 }1013 }
10071014
1015 /// Whether a queued save has yet to report, so the stored page may trail the editor's.
1016 pub fn saving(&self) -> bool {
1017 self.unsaved.load(Ordering::Acquire) > 0
1018 }
1019
1008 pub fn status(&self, id: u64) -> Result<Option<EditStatus>> {1020 pub fn status(&self, id: u64) -> Result<Option<EditStatus>> {
1009 self.replica.status(id)1021 self.replica.status(id)
1010 }1022 }
...@@ -1017,13 +1029,13 @@ impl Section {...@@ -1017,13 +1029,13 @@ impl Section {
1017 /// attempt, or a conflict awaiting review.1029 /// attempt, or a conflict awaiting review.
1018 pub fn queue(&self) -> Result<Vec<QueuedEdit>> {1030 pub fn queue(&self) -> Result<Vec<QueuedEdit>> {
1019 self.replica1031 self.replica
1020 .pending()?1032 .queued()?
1021 .into_iter()1033 .into_iter()
1022 .map(|edit| {1034 .map(|(id, space)| {
1023 Ok(QueuedEdit {1035 Ok(QueuedEdit {
1024 id: edit.id,1036 id,
1025 space: edit.space,1037 space,
1026 status: self.replica.status(edit.id)?.unwrap_or(EditStatus::Pending),1038 status: self.replica.status(id)?.unwrap_or(EditStatus::Pending),
1027 })1039 })
1028 })1040 })
1029 .collect()1041 .collect()
...@@ -1094,7 +1106,8 @@ impl Section {...@@ -1094,7 +1106,8 @@ impl Section {
1094 }1106 }
1095 }1107 }
10961108
1097 pub fn replica(&self) -> &Replica {1109 /// The replica, which other threads may read pages from while the section is open.
1110 pub fn replica(&self) -> &Arc<Replica> {
1098 &self.replica1111 &self.replica
1099 }1112 }
11001113
...@@ -1160,7 +1173,7 @@ fn save(...@@ -1160,7 +1173,7 @@ fn save(
1160fn save_pages(1173fn save_pages(
1161 replica: &Replica,1174 replica: &Replica,
1162 requests: &Receiver<PageSave>,1175 requests: &Receiver<PageSave>,
1163 report: impl Fn((ExGuid, std::result::Result<Save, String>)),1176 report: impl Fn((ExGuid, std::result::Result<Save, String>), usize),
1164) {1177) {
1165 let mut queue = VecDeque::new();1178 let mut queue = VecDeque::new();
1166 // The last save's page and the working image it left.1179 // The last save's page and the working image it left.
...@@ -1174,11 +1187,13 @@ fn save_pages(...@@ -1174,11 +1187,13 @@ fn save_pages(
1174 }1187 }
1175 queue.extend(requests.try_iter());1188 queue.extend(requests.try_iter());
1176 let mut request = queue.pop_front().unwrap();1189 let mut request = queue.pop_front().unwrap();
1190 let mut count = 1;
1177 while let Some(next) = queue.front()1191 while let Some(next) = queue.front()
1178 && (next.space, &next.before, &next.author)1192 && (next.space, &next.before, &next.author)
1179 == (request.space, &request.after, &request.author)1193 == (request.space, &request.after, &request.author)
1180 {1194 {
1181 request.after = queue.pop_front().unwrap().after;1195 request.after = queue.pop_front().unwrap().after;
1196 count += 1;
1182 }1197 }
1183 let known = last1198 let known = last
1184 .take()1199 .take()
...@@ -1200,7 +1215,7 @@ fn save_pages(...@@ -1200,7 +1215,7 @@ fn save_pages(
1200 }1215 }
1201 Err(error) => Err(error.to_string()),1216 Err(error) => Err(error.to_string()),
1202 };1217 };
1203 report((request.space, save));1218 report((request.space, save), count);
1204 }1219 }
1205}1220}
12061221
...@@ -1212,8 +1227,19 @@ impl Remote for FileRemote {...@@ -1212,8 +1227,19 @@ impl Remote for FileRemote {
1212 onestore::read_file(&self.0)1227 onestore::read_file(&self.0)
1213 }1228 }
12141229
1215 fn publish(&mut self, edit: &PreparedEdit<'_>) -> std::result::Result<(), CommitError> {1230 /// Read without the whole-file lock, which would block OneNote's readers: a change
1216 edit.commit_file(&self.0)1231 /// detector, never a snapshot to edit.
1232 fn stamp(&mut self) -> io::Result<Option<Stamp>> {
1233 use std::io::Read;
1234 let mut file = std::fs::File::open(&self.0)?;
1235 let mut header = [0; 1024];
1236 file.read_exact(&mut header)?;
1237 let length = file.metadata()?.len();
1238 Ok(Some(Stamp { header, length }))
1239 }
1240
1241 fn publish(&mut self, transaction: &Transaction) -> std::result::Result<(), CommitError> {
1242 transaction.commit_file(&self.0)
1217 }1243 }
12181244
1219 fn confirm(&mut self, snapshot: &[u8]) -> std::result::Result<(), CommitError> {1245 fn confirm(&mut self, snapshot: &[u8]) -> std::result::Result<(), CommitError> {
crates/notebook/src/smb/directory.rs+1
...@@ -38,6 +38,7 @@ impl Client {...@@ -38,6 +38,7 @@ impl Client {
38 let file = File {38 let file = File {
39 client: self,39 client: self,
40 id: Some(response.file_id),40 id: Some(response.file_id),
41 length: response.end_of_file,
41 };42 };
42 let mut entries = BTreeMap::new();43 let mut entries = BTreeMap::new();
43 loop {44 loop {
crates/notebook/src/smb/mod.rs+30-4
...@@ -218,6 +218,7 @@ impl Client {...@@ -218,6 +218,7 @@ impl Client {
218 Ok(File {218 Ok(File {
219 client: self,219 client: self,
220 id: Some(response.file_id),220 id: Some(response.file_id),
221 length: response.end_of_file,
221 })222 })
222 }223 }
223224
...@@ -374,13 +375,36 @@ impl Client {...@@ -374,13 +375,36 @@ impl Client {
374 })375 })
375 }376 }
376377
377 /// Publishes a prepared edit using the same native writer coordination as text commits.378 /// Publishes a transaction using the same native writer coordination as text commits.
378 pub fn commit_prepared(379 pub fn commit_transaction(
379 &self,380 &self,
380 path: &str,381 path: &str,
381 edit: &onestore::PreparedEdit<'_>,382 transaction: &onestore::Transaction,
382 ) -> Result<(), CommitError> {383 ) -> Result<(), CommitError> {
383 self.commit(path, |file| edit.commit(file))384 self.commit(path, |file| transaction.commit(file))
385 }
386
387 /// The header and length of a revision store, read without writer coordination or path
388 /// identity checks: a change detector for polling, never a snapshot to edit.
389 pub fn stamp(&self, path: &str) -> io::Result<onestore::Stamp> {
390 let mut file = self.open(path, false)?;
391 let mut header = [0; 1024];
392 let mut read = 0;
393 let result = loop {
394 match file.read_at(read as u64, &mut header[read..]) {
395 Ok(0) => break Err(io::ErrorKind::UnexpectedEof.into()),
396 Ok(count) => read += count,
397 Err(error) => break Err(error),
398 }
399 if read == header.len() {
400 break Ok(());
401 }
402 };
403 let length = file.length;
404 let closed = file.close();
405 result?;
406 closed?;
407 Ok(onestore::Stamp { header, length })
384 }408 }
385409
386 /// Confirms an observed snapshot's durability under native writer coordination.410 /// Confirms an observed snapshot's durability under native writer coordination.
...@@ -419,6 +443,8 @@ impl Drop for Client {...@@ -419,6 +443,8 @@ impl Drop for Client {
419struct File<'a> {443struct File<'a> {
420 client: &'a Client,444 client: &'a Client,
421 id: Option<FileId>,445 id: Option<FileId>,
446 /// The end of file when opened.
447 length: u64,
422}448}
423impl File<'_> {449impl File<'_> {
424 fn coordinate(self, path: &str, write: bool) -> io::Result<Self> {450 fn coordinate(self, path: &str, write: bool) -> io::Result<Self> {
crates/notebook/src/smb/remote.rs+7-3
...@@ -1,6 +1,6 @@...@@ -1,6 +1,6 @@
1use super::Client;1use super::Client;
2use crate::Remote;2use crate::Remote;
3use onestore::{CommitError, PreparedEdit};3use onestore::{CommitError, Stamp, Transaction};
4use std::io;4use std::io;
55
6/// Binds every reconciliation operation to one share-relative file and read limit.6/// Binds every reconciliation operation to one share-relative file and read limit.
...@@ -26,8 +26,12 @@ impl Remote for SmbRemote {...@@ -26,8 +26,12 @@ impl Remote for SmbRemote {
26 self.client.read(&self.path, self.limit)26 self.client.read(&self.path, self.limit)
27 }27 }
2828
29 fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> {29 fn stamp(&mut self) -> io::Result<Option<Stamp>> {
30 self.client.commit_prepared(&self.path, edit)30 self.client.stamp(&self.path).map(Some)
31 }
32
33 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
34 self.client.commit_transaction(&self.path, transaction)
31 }35 }
3236
33 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {37 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {
crates/notebook/src/smb/tests.rs+48
...@@ -120,6 +120,7 @@ fn create(client: &Client, path: &str, bytes: &[u8]) {...@@ -120,6 +120,7 @@ fn create(client: &Client, path: &str, bytes: &[u8]) {
120 let mut file = File {120 let mut file = File {
121 client,121 client,
122 id: Some(response.file_id),122 id: Some(response.file_id),
123 length: response.end_of_file,
123 };124 };
124 let mut offset = 0;125 let mut offset = 0;
125 while offset < bytes.len() {126 while offset < bytes.len() {
...@@ -344,6 +345,53 @@ fn live_coordination() {...@@ -344,6 +345,53 @@ fn live_coordination() {
344 );345 );
345}346}
346347
348/// Polling reads only the header, even while maintenance excludes snapshot readers, and a
349/// transaction commits against the header it was built on.
350#[test]
351#[ignore = "requires ONESTORE_SMB_LAB pointing to disposable Samba"]
352fn live_stamp_and_transaction() {
353 let poller = client();
354 let source = onestore::create_section("stamp.one", "Stamped", "Author").unwrap();
355 let path = format!(
356 "stamp-{}.one",
357 SystemTime::now()
358 .duration_since(UNIX_EPOCH)
359 .unwrap()
360 .as_nanos()
361 );
362 create(&poller, &path, &source);
363 assert_eq!(
364 poller.stamp(&path).unwrap(),
365 onestore::Stamp::of(&source).unwrap()
366 );
367 let maintenance = client();
368 let guard = maintenance.open(&path, false).unwrap();
369 guard.lock(0xfffffffb, 0x12).unwrap();
370 assert_eq!(
371 poller.read(&path, 1 << 20).unwrap_err().kind(),
372 io::ErrorKind::WouldBlock
373 );
374 assert_eq!(
375 poller.stamp(&path).unwrap(),
376 onestore::Stamp::of(&source).unwrap()
377 );
378 guard.close().unwrap();
379 let (sid, oid, _) = text(&source);
380 let edit = onestore::PreparedEdit::text(&source, sid, oid, 0..0, "Changed ").unwrap();
381 let transaction = edit.transaction();
382 poller.commit_transaction(&path, &transaction).unwrap();
383 assert_eq!(
384 poller.stamp(&path).unwrap(),
385 onestore::Stamp::of(edit.as_bytes()).unwrap()
386 );
387 assert_eq!(poller.read(&path, 1 << 20).unwrap(), edit.as_bytes());
388 let stale = poller.commit_transaction(&path, &transaction).unwrap_err();
389 assert_eq!(stale.state, CommitState::NotCommitted);
390 assert_eq!(stale.error.kind(), io::ErrorKind::ResourceBusy);
391 assert_eq!(poller.read(&path, 1 << 20).unwrap(), edit.as_bytes());
392 poller.delete(&path).unwrap();
393}
394
347#[test]395#[test]
348#[ignore = "requires ONESTORE_SMB_LAB pointing to disposable Samba"]396#[ignore = "requires ONESTORE_SMB_LAB pointing to disposable Samba"]
349fn live_storage_inspection() {397fn live_storage_inspection() {
crates/notebook/src/sync.rs+20-10
...@@ -1,5 +1,5 @@...@@ -1,5 +1,5 @@
1use super::*;1use super::*;
2use onestore::{CommitError, CommitState};2use onestore::{CommitError, CommitState, Stamp, Transaction};
3use rusqlite::OptionalExtension;3use rusqlite::OptionalExtension;
4use std::{4use std::{
5 collections::BTreeMap,5 collections::BTreeMap,
...@@ -7,10 +7,16 @@ use std::{...@@ -7,10 +7,16 @@ use std::{
7};7};
88
9/// A single remote file with fresh reads and native-compatible guarded publication.9/// A single remote file with fresh reads and native-compatible guarded publication.
10/// Errors retain publication state; confirmation compares, flushes, and notifies cached readers.10/// Errors retain publication state; confirmation checks the stamp, flushes, and notifies
11/// cached readers.
11pub trait Remote {12pub trait Remote {
12 fn read(&mut self) -> io::Result<Vec<u8>>;13 fn read(&mut self) -> io::Result<Vec<u8>>;
13 fn publish(&mut self, edit: &PreparedEdit<'_>) -> std::result::Result<(), CommitError>;14 /// The file's stamp without reading its body, when the remote can tell. While it is the
15 /// last observed image's, synchronization neither reads nor revalidates the file.
16 fn stamp(&mut self) -> io::Result<Option<Stamp>> {
17 Ok(None)
18 }
19 fn publish(&mut self, transaction: &Transaction) -> std::result::Result<(), CommitError>;
14 fn confirm(&mut self, snapshot: &[u8]) -> std::result::Result<(), CommitError>;20 fn confirm(&mut self, snapshot: &[u8]) -> std::result::Result<(), CommitError>;
15}21}
1622
...@@ -78,13 +84,14 @@ impl Replica {...@@ -78,13 +84,14 @@ impl Replica {
78 }84 }
79 }85 }
80 let _step = Step(self);86 let _step = Step(self);
81 let snapshot = remote.read().map_err(Error::RemoteIo)?;87 let base = self.remote_snapshot()?;
82 // An unchanged remote is the base image, validated when it was stored.88 let changed = match remote.stamp().map_err(Error::RemoteIo)? {
83 let identity = if snapshot == self.remote_snapshot()? {89 Some(stamp) if stamp == Stamp::of(&base)? => None,
84 None90 _ => Some(remote.read().map_err(Error::RemoteIo)?).filter(|read| *read != base),
85 } else {
86 Some(validate(&snapshot)?)
87 };91 };
92 // An unchanged remote is the base image, validated when it was stored.
93 let identity = changed.as_deref().map(validate).transpose()?;
94 let snapshot = changed.unwrap_or(base);
88 let (intent, attempted) = {95 let (intent, attempted) = {
89 let mut connection = self96 let mut connection = self
90 .connection97 .connection
...@@ -92,6 +99,9 @@ impl Replica {...@@ -92,6 +99,9 @@ impl Replica {
92 .map_err(|_| io::Error::other("Cache owner panicked"))?;99 .map_err(|_| io::Error::other("Cache owner panicked"))?;
93 let transaction =100 let transaction =
94 connection.transaction_with_behavior(TransactionBehavior::Immediate)?;101 connection.transaction_with_behavior(TransactionBehavior::Immediate)?;
102 if identity.is_none() && images::settled(&transaction)? {
103 return Ok(None);
104 }
95 let (base, working) = images::both(&transaction)?;105 let (base, working) = images::both(&transaction)?;
96 if let Some(identity) = identity106 if let Some(identity) = identity
97 && RevisionIndex::parse(&Store::parse(&base)?)?.root != identity107 && RevisionIndex::parse(&Store::parse(&base)?)?.root != identity
...@@ -267,7 +277,7 @@ impl Replica {...@@ -267,7 +277,7 @@ impl Replica {
267 )?;277 )?;
268 transaction.commit()?;278 transaction.commit()?;
269 }279 }
270 match remote.publish(&prepared) {280 match remote.publish(&prepared.transaction()) {
271 Ok(()) => {}281 Ok(()) => {}
272 Err(error) if error.state == CommitState::NotCommitted => {282 Err(error) if error.state == CommitState::NotCommitted => {
273 let connection = self283 let connection = self
crates/notebook/src/worker.rs+14-3
...@@ -60,7 +60,8 @@ impl Drop for SyncWorker {...@@ -60,7 +60,8 @@ impl Drop for SyncWorker {
6060
61impl Replica {61impl Replica {
62 /// Starts one worker, reconnecting through `connect` after transport failures.62 /// Starts one worker, reconnecting through `connect` after transport failures.
63 /// Local edits wake it; `interval` controls idle polling and transport retries.63 /// Local edits wake it; `interval` controls idle polling and transport retries. While
64 /// nothing is queued, a remote whose `stamp` holds is not read again.
64 /// Contended operations returning `NotCommitted` also back off by up to one second.65 /// Contended operations returning `NotCommitted` also back off by up to one second.
65 /// `observe` runs on the worker after each attempt, including connection errors.66 /// `observe` runs on the worker after each attempt, including connection errors.
66 /// Cache/document errors stop the worker; inspect them through `observe` or `stop`.67 /// Cache/document errors stop the worker; inspect them through `observe` or `stop`.
...@@ -99,12 +100,22 @@ impl Replica {...@@ -99,12 +100,22 @@ impl Replica {
99 let thread = thread::Builder::new()100 let thread = thread::Builder::new()
100 .name("onestore-sync".into())101 .name("onestore-sync".into())
101 .spawn(move || {102 .spawn(move || {
102 let mut remote = None;103 let mut remote: Option<R> = None;
103 let jitter = RandomState::new();104 let jitter = RandomState::new();
104 let mut contention = 0_u32;105 let mut contention = 0_u32;
106 // The first attempt always runs, so `observe` hears once that the remote is
107 // reachable.
108 let mut attempted = false;
105 while !worker_signal.stopped.load(Ordering::Acquire) {109 while !worker_signal.stopped.load(Ordering::Acquire) {
106 let result = match remote.as_mut() {110 let result = match remote.as_mut() {
107 Some(remote) => replica.sync_once(remote),111 Some(remote) => {
112 if attempted && replica.settled(remote).unwrap_or(false) {
113 let _ = receiver.recv_timeout(interval);
114 continue;
115 }
116 attempted = true;
117 replica.sync_once(remote)
118 }
108 None => match connect() {119 None => match connect() {
109 Ok(connected) => {120 Ok(connected) => {
110 remote = Some(connected);121 remote = Some(connected);
crates/notebook/tests/assets.rs+1-1
...@@ -271,7 +271,7 @@ fn a_native_refresh_removing_the_reference_rejects_an_inflight_download() {...@@ -271,7 +271,7 @@ fn a_native_refresh_removing_the_reference_rejects_an_inflight_download() {
271 fn read(&mut self) -> io::Result<Vec<u8>> {271 fn read(&mut self) -> io::Result<Vec<u8>> {
272 fs::read("../../corpus/native-external-assets/native/synthetic.one")272 fs::read("../../corpus/native-external-assets/native/synthetic.one")
273 }273 }
274 fn publish(&mut self, _: &onestore::PreparedEdit<'_>) -> Result<(), onestore::CommitError> {274 fn publish(&mut self, _: &onestore::Transaction) -> Result<(), onestore::CommitError> {
275 panic!("Unexpected publication")275 panic!("Unexpected publication")
276 }276 }
277 fn confirm(&mut self, _: &[u8]) -> Result<(), onestore::CommitError> {277 fn confirm(&mut self, _: &[u8]) -> Result<(), onestore::CommitError> {
crates/notebook/tests/support/model_schedule.rs+8-5
...@@ -5,7 +5,7 @@ use crate::model_ops::{self, AUTHOR};...@@ -5,7 +5,7 @@ use crate::model_ops::{self, AUTHOR};
5use crate::server::{Fault, Server};5use crate::server::{Fault, Server};
6use notebook::{EditStatus, Operation, Remote, Replica};6use notebook::{EditStatus, Operation, Remote, Replica};
7use onestore::{7use onestore::{
8 CommitError, ExGuid, PreparedEdit, RevisionIndex, Store,8 CommitError, ExGuid, PreparedEdit, RevisionIndex, Store, Transaction,
9 document::Document,9 document::Document,
10 page::{Outline, Page, PageObject},10 page::{Outline, Page, PageObject},
11};11};
...@@ -140,20 +140,23 @@ impl Remote for Session<'_> {...@@ -140,20 +140,23 @@ impl Remote for Session<'_> {
140 self.server.read()140 self.server.read()
141 }141 }
142142
143 fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> {143 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
144 assert!(!self.retired, "a retired attempt was replayed");144 assert!(!self.retired, "a retired attempt was replayed");
145 self.publications += 1;145 self.publications += 1;
146 assert_eq!(self.publications, 1);146 assert_eq!(self.publications, 1);
147 let Some((_, Operation::Page(intent))) = &self.intent else {147 let Some((_, Operation::Page(intent))) = &self.intent else {
148 panic!("the schedule queues page saves only")148 panic!("the schedule queues page saves only")
149 };149 };
150 if model_ops::page_of(&self.server.visible, SOURCE.1) == intent.before {150 let mut image = self.server.visible.clone();
151 if transaction.apply(&mut image).is_ok()
152 && model_ops::page_of(&self.server.visible, SOURCE.1) == intent.before
153 {
151 assert_eq!(154 assert_eq!(
152 shape(&model_ops::page_of(edit.as_bytes(), SOURCE.1)),155 shape(&model_ops::page_of(&image, SOURCE.1)),
153 shape(&intent.after)156 shape(&intent.after)
154 );157 );
155 }158 }
156 self.server.publish(edit)159 self.server.publish(transaction)
157 }160 }
158161
159 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {162 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {
crates/notebook/tests/support/page_schedule.rs+8-6
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1use crate::disk;1use crate::disk;
2use notebook::{EditStatus, Operation, Remote, Replica};2use notebook::{EditStatus, Operation, Remote, Replica};
3use onestore::{3use onestore::{
4 CommitError, ExGuid, PageEdit, PagePosition, PreparedEdit, RevisionIndex, Store,4 CommitError, ExGuid, PageEdit, PagePosition, PreparedEdit, RevisionIndex, Store, Transaction,
5 document::{Document, Format, Kind, Layout},5 document::{Document, Format, Kind, Layout},
6 page::{6 page::{
7 Outline, Page, PageObject, PageParagraph, Paragraph, ParagraphContent, TextObject,7 Outline, Page, PageObject, PageParagraph, Paragraph, ParagraphContent, TextObject,
...@@ -125,7 +125,9 @@ impl Remote for Session<'_> {...@@ -125,7 +125,9 @@ impl Remote for Session<'_> {
125 Ok(self.disk.visible.clone())125 Ok(self.disk.visible.clone())
126 }126 }
127127
128 fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> {128 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
129 let mut image = self.disk.visible.clone();
130 transaction.apply(&mut image).unwrap();
129 let mut expected = pages(&self.disk.visible);131 let mut expected = pages(&self.disk.visible);
130 match self.operation.unwrap() {132 match self.operation.unwrap() {
131 Operation::CreatePage(page) => expected.push((page.space(), page.object(), 1)),133 Operation::CreatePage(page) => expected.push((page.space(), page.object(), 1)),
...@@ -144,7 +146,7 @@ impl Remote for Session<'_> {...@@ -144,7 +146,7 @@ impl Remote for Session<'_> {
144 }146 }
145 Operation::Page(intent) => {147 Operation::Page(intent) => {
146 let body = body_text(&intent.after);148 let body = body_text(&intent.after);
147 let store = Store::parse(edit.as_bytes()).unwrap();149 let store = Store::parse(&image).unwrap();
148 let index = RevisionIndex::parse(&store).unwrap();150 let index = RevisionIndex::parse(&store).unwrap();
149 let document = Document::parse(&index).unwrap();151 let document = Document::parse(&index).unwrap();
150 let matching: Vec<_> = document152 let matching: Vec<_> = document
...@@ -160,10 +162,10 @@ impl Remote for Session<'_> {...@@ -160,10 +162,10 @@ impl Remote for Session<'_> {
160 }162 }
161 _ => panic!(),163 _ => panic!(),
162 }164 }
163 assert_eq!(pages(edit.as_bytes()), expected);165 assert_eq!(pages(&image), expected);
164 let old = current::current(&self.disk.durable);166 let old = current::current(&self.disk.durable);
165 let new = current::current(edit.as_bytes());167 let new = current::current(&image);
166 let result = edit.commit(self.disk);168 let result = transaction.commit(self.disk);
167 let observed = current::current(&self.disk.durable);169 let observed = current::current(&self.disk.durable);
168 assert!(observed == old || observed == new);170 assert!(observed == old || observed == new);
169 if result.is_ok() {171 if result.is_ok() {
crates/notebook/tests/support/server.rs+6-3
...@@ -3,7 +3,7 @@...@@ -3,7 +3,7 @@
33
4use notebook::Remote;4use notebook::Remote;
5use onestore::{5use onestore::{
6 CommitError, CommitIo, CommitState, ExGuid, PreparedEdit, RevisionIndex, Store,6 CommitError, CommitIo, CommitState, ExGuid, RevisionIndex, Stamp, Store, Transaction,
7 document::{Document, Kind},7 document::{Document, Kind},
8};8};
9use std::io;9use std::io;
...@@ -75,7 +75,10 @@ impl Remote for Server {...@@ -75,7 +75,10 @@ impl Remote for Server {
75 fn read(&mut self) -> io::Result<Vec<u8>> {75 fn read(&mut self) -> io::Result<Vec<u8>> {
76 Ok(self.visible.clone())76 Ok(self.visible.clone())
77 }77 }
78 fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> {78 fn stamp(&mut self) -> io::Result<Option<Stamp>> {
79 Ok(Stamp::of(&self.visible).ok())
80 }
81 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
79 self.publications += 1;82 self.publications += 1;
80 let fault = std::mem::take(&mut self.fault);83 let fault = std::mem::take(&mut self.fault);
81 match fault {84 match fault {
...@@ -85,7 +88,7 @@ impl Remote for Server {...@@ -85,7 +88,7 @@ impl Remote for Server {
85 _ => {}88 _ => {}
86 }89 }
87 let old = self.durable.clone();90 let old = self.durable.clone();
88 edit.commit(self)?;91 transaction.commit(self)?;
89 match fault {92 match fault {
90 Fault::UnknownAfter => {93 Fault::UnknownAfter => {
91 self.durable = old;94 self.durable = old;
crates/notebook/tests/sync.rs+50-5
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1//! Replica protocol: durable receipts, retired attempts, conflict review and contention.1//! Replica protocol: durable receipts, retired attempts, conflict review and contention.
22
3use notebook::{ConflictKind, EditStatus, Error, Remote, Replica};3use notebook::{ConflictKind, EditStatus, Error, Remote, Replica};
4use onestore::{CommitError, CommitState, ExGuid, PreparedEdit, page::Page};4use onestore::{CommitError, CommitState, ExGuid, Transaction, page::Page};
5use std::{io, ops::Range};5use std::{io, ops::Range};
66
7#[path = "support/server.rs"]7#[path = "support/server.rs"]
...@@ -25,6 +25,51 @@ fn save(cache: &Replica, text: ExGuid, range: Range<u32>, replacement: &str) ->...@@ -25,6 +25,51 @@ fn save(cache: &Replica, text: ExGuid, range: Range<u32>, replacement: &str) ->
25 .unwrap()25 .unwrap()
26}26}
2727
28#[test]
29fn an_unchanged_stamp_publishes_and_settles_without_reading_the_remote() {
30 struct Counted {
31 server: Server,
32 reads: usize,
33 }
34 impl Remote for Counted {
35 fn read(&mut self) -> io::Result<Vec<u8>> {
36 self.reads += 1;
37 self.server.read()
38 }
39 fn stamp(&mut self) -> io::Result<Option<onestore::Stamp>> {
40 self.server.stamp()
41 }
42 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
43 self.server.publish(transaction)
44 }
45 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {
46 self.server.confirm(snapshot)
47 }
48 }
49 let directory = tempfile::tempdir().unwrap();
50 let source = onestore::create_section("stamp.one", "Original", "Fixture").unwrap();
51 let (sid, object, _) = text(&source);
52 let cache = Replica::create(directory.path().join("cache.sqlite"), &source).unwrap();
53 let id = save(&cache, object, 0..0, "Local ").unwrap();
54 let mut remote = Counted {
55 server: Server::new(&source),
56 reads: 0,
57 };
58 assert!(matches!(
59 cache.sync_once(&mut remote).unwrap(),
60 Some((published, EditStatus::Published { .. })) if published == id
61 ));
62 assert_eq!(cache.sync_once(&mut remote).unwrap(), None);
63 assert_eq!(remote.reads, 0);
64 // Another writer's commit moves the header, so the next step reads the file.
65 let native =
66 onestore::replace_text(&remote.server.visible, sid, object, 0..0, "Native ").unwrap();
67 remote.server.visible.clone_from(&native);
68 assert_eq!(cache.sync_once(&mut remote).unwrap(), None);
69 assert_eq!(remote.reads, 1);
70 assert_eq!(cache.snapshot().unwrap(), native);
71}
72
28#[test]73#[test]
29fn recovery_archive_preserves_the_queue_uncertainty_and_receipts_without_becoming_a_writer() {74fn recovery_archive_preserves_the_queue_uncertainty_and_receipts_without_becoming_a_writer() {
30 use notebook::{Recovery, RecoverySummary};75 use notebook::{Recovery, RecoverySummary};
...@@ -417,9 +462,9 @@ fn twelve_local_editors_progress_during_remote_reads_publication_and_confirmatio...@@ -417,9 +462,9 @@ fn twelve_local_editors_progress_during_remote_reads_publication_and_confirmatio
417 self.wait("read");462 self.wait("read");
418 self.server.read()463 self.server.read()
419 }464 }
420 fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> {465 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
421 self.wait("publish");466 self.wait("publish");
422 self.server.publish(edit)467 self.server.publish(transaction)
423 }468 }
424 fn confirm(&mut self, source: &[u8]) -> Result<(), CommitError> {469 fn confirm(&mut self, source: &[u8]) -> Result<(), CommitError> {
425 self.wait("confirm");470 self.wait("confirm");
...@@ -768,8 +813,8 @@ fn remote_changes_after_review_cannot_be_overwritten_by_the_reviewed_page() {...@@ -768,8 +813,8 @@ fn remote_changes_after_review_cannot_be_overwritten_by_the_reviewed_page() {
768 }813 }
769 Ok(snapshot)814 Ok(snapshot)
770 }815 }
771 fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> {816 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
772 self.server.publish(edit)817 self.server.publish(transaction)
773 }818 }
774 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {819 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {
775 self.server.confirm(snapshot)820 self.server.confirm(snapshot)
crates/notebook/tests/sync_page.rs+2
...@@ -209,6 +209,8 @@ fn surviving_page_revision_alone_does_not_confirm_section_publication() {...@@ -209,6 +209,8 @@ fn surviving_page_revision_alone_does_not_confirm_section_publication() {
209 // Replacing only the revision identity models maintenance retiring the section proof.209 // Replacing only the revision identity models maintenance retiring the section proof.
210 let at = positions[0] + 4;210 let at = positions[0] + 4;
211 server.visible[at] ^= 0x40;211 server.visible[at] ^= 0x40;
212 // A rewrite publishes a new file version (MS-ONESTORE 2.3.1).
213 server.visible[212] ^= 1;
212 let store = Store::parse(&server.visible).unwrap();214 let store = Store::parse(&server.visible).unwrap();
213 let index = RevisionIndex::parse(&store).unwrap();215 let index = RevisionIndex::parse(&store).unwrap();
214 index.validate_current().unwrap();216 index.validate_current().unwrap();
crates/notebook/tests/sync_paragraph.rs+12-2
...@@ -97,7 +97,9 @@ fn split_paragraph(page: &mut Page, text: ExGuid, offset: u32) {...@@ -97,7 +97,9 @@ fn split_paragraph(page: &mut Page, text: ExGuid, offset: u32) {
97}97}
9898
99/// Joins the paragraph carrying `right` into the one carrying `left`; an empty left text99/// Joins the paragraph carrying `right` into the one carrying `left`; an empty left text
100/// object is replaced by a non-empty right one, which is the identity OneNote keeps.100/// object is replaced by a non-empty right one, which is the identity OneNote keeps. The
101/// right paragraph's children keep their depth, under the left paragraph or the nearest of
102/// its ancestors above them.
101fn join_paragraphs(page: &mut Page, left: ExGuid, right: ExGuid) {103fn join_paragraphs(page: &mut Page, left: ExGuid, right: ExGuid) {
102 locate_in(page, left, |list, first| {104 locate_in(page, left, |list, first| {
103 let second = at(list, right).expect("the joined paragraphs share a container");105 let second = at(list, right).expect("the joined paragraphs share a container");
...@@ -115,9 +117,17 @@ fn join_paragraphs(page: &mut Page, left: ExGuid, right: ExGuid) {...@@ -115,9 +117,17 @@ fn join_paragraphs(page: &mut Page, left: ExGuid, right: ExGuid) {
115 }117 }
116 target.text = joined;118 target.text = joined;
117 list.remove(second);119 list.remove(second);
120 let nesting: std::collections::BTreeMap<_, _> =
121 list.iter().map(|p| (p.id, (p.level, p.parent))).collect();
118 for paragraph in list {122 for paragraph in list {
119 if paragraph.parent == Some(orphaned) {123 if paragraph.parent == Some(orphaned) {
120 paragraph.parent = Some(parent);124 let mut adopter = Some(parent);
125 while let Some(id) = adopter
126 && nesting[&id].0 >= paragraph.level
127 {
128 adopter = nesting[&id].1;
129 }
130 paragraph.parent = adopter;
121 }131 }
122 }132 }
123 });133 });
crates/notebook/tests/sync_worker.rs+14-11
...@@ -2,7 +2,7 @@...@@ -2,7 +2,7 @@
22
3use notebook::{ConflictKind, EditStatus, Error, Remote, Replica};3use notebook::{ConflictKind, EditStatus, Error, Remote, Replica};
4use onestore::{4use onestore::{
5 CommitError, CommitState, ExGuid, PreparedEdit, RevisionIndex, Store, document::Document,5 CommitError, CommitState, ExGuid, RevisionIndex, Store, Transaction, document::Document,
6};6};
7use std::io;7use std::io;
88
...@@ -25,8 +25,11 @@ impl Remote for Shared {...@@ -25,8 +25,11 @@ impl Remote for Shared {
25 fn read(&mut self) -> io::Result<Vec<u8>> {25 fn read(&mut self) -> io::Result<Vec<u8>> {
26 self.0.lock().unwrap().read()26 self.0.lock().unwrap().read()
27 }27 }
28 fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> {28 fn stamp(&mut self) -> io::Result<Option<onestore::Stamp>> {
29 self.0.lock().unwrap().publish(edit)29 self.0.lock().unwrap().stamp()
30 }
31 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
32 self.0.lock().unwrap().publish(transaction)
30 }33 }
31 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {34 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {
32 self.0.lock().unwrap().confirm(snapshot)35 self.0.lock().unwrap().confirm(snapshot)
...@@ -72,8 +75,8 @@ fn reconnects_after_connect_read_and_uncertain_publish_without_replaying() {...@@ -72,8 +75,8 @@ fn reconnects_after_connect_read_and_uncertain_publish_without_replaying() {
72 }75 }
73 self.shared.read()76 self.shared.read()
74 }77 }
75 fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> {78 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
76 self.shared.publish(edit)79 self.shared.publish(transaction)
77 }80 }
78 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {81 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {
79 self.shared.confirm(snapshot)82 self.shared.confirm(snapshot)
...@@ -235,10 +238,10 @@ fn dropping_during_publication_is_nonblocking_and_retains_ownership_until_recove...@@ -235,10 +238,10 @@ fn dropping_during_publication_is_nonblocking_and_retains_ownership_until_recove
235 fn read(&mut self) -> io::Result<Vec<u8>> {238 fn read(&mut self) -> io::Result<Vec<u8>> {
236 self.shared.read()239 self.shared.read()
237 }240 }
238 fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> {241 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
239 self.entered.send(()).unwrap();242 self.entered.send(()).unwrap();
240 self.resume.recv_timeout(Duration::from_secs(5)).unwrap();243 self.resume.recv_timeout(Duration::from_secs(5)).unwrap();
241 self.shared.publish(edit)244 self.shared.publish(transaction)
242 }245 }
243 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {246 fn confirm(&mut self, snapshot: &[u8]) -> Result<(), CommitError> {
244 self.shared.confirm(snapshot)247 self.shared.confirm(snapshot)
...@@ -580,7 +583,7 @@ fn ordinary_read_and_unpublished_write_contention_reuse_the_connection() {...@@ -580,7 +583,7 @@ fn ordinary_read_and_unpublished_write_contention_reuse_the_connection() {
580 _ => self.server.read(),583 _ => self.server.read(),
581 }584 }
582 }585 }
583 fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> {586 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
584 self.writes += 1;587 self.writes += 1;
585 match self.writes {588 match self.writes {
586 1 | 2 => Err(CommitError {589 1 | 2 => Err(CommitError {
...@@ -592,7 +595,7 @@ fn ordinary_read_and_unpublished_write_contention_reuse_the_connection() {...@@ -592,7 +595,7 @@ fn ordinary_read_and_unpublished_write_contention_reuse_the_connection() {
592 }595 }
593 .into(),596 .into(),
594 }),597 }),
595 _ => self.server.publish(edit),598 _ => self.server.publish(transaction),
596 }599 }
597 }600 }
598 fn confirm(&mut self, source: &[u8]) -> Result<(), CommitError> {601 fn confirm(&mut self, source: &[u8]) -> Result<(), CommitError> {
...@@ -638,7 +641,7 @@ fn publication_backoff_drains_local_wakes_without_waiting_for_the_idle_poll() {...@@ -638,7 +641,7 @@ fn publication_backoff_drains_local_wakes_without_waiting_for_the_idle_poll() {
638 fn read(&mut self) -> io::Result<Vec<u8>> {641 fn read(&mut self) -> io::Result<Vec<u8>> {
639 self.0.read()642 self.0.read()
640 }643 }
641 fn publish(&mut self, edit: &PreparedEdit<'_>) -> Result<(), CommitError> {644 fn publish(&mut self, transaction: &Transaction) -> Result<(), CommitError> {
642 let server = &mut self.0;645 let server = &mut self.0;
643 if server.publications == 0 {646 if server.publications == 0 {
644 server.publications += 1;647 server.publications += 1;
...@@ -647,7 +650,7 @@ fn publication_backoff_drains_local_wakes_without_waiting_for_the_idle_poll() {...@@ -647,7 +650,7 @@ fn publication_backoff_drains_local_wakes_without_waiting_for_the_idle_poll() {
647 error: io::ErrorKind::ResourceBusy.into(),650 error: io::ErrorKind::ResourceBusy.into(),
648 });651 });
649 }652 }
650 server.publish(edit)653 server.publish(transaction)
651 }654 }
652 fn confirm(&mut self, source: &[u8]) -> Result<(), CommitError> {655 fn confirm(&mut self, source: &[u8]) -> Result<(), CommitError> {
653 self.0.confirm(source)656 self.0.confirm(source)
crates/onestore/README.md+9-4
...@@ -64,10 +64,13 @@ harness also accepts `--client-profile release`....@@ -64,10 +64,13 @@ harness also accepts `--client-profile release`.
64| `OutlineEdit`, `PreparedEdit::outline` | Change ordinary outline position/width or a paragraph's saved expansion default, preserving identities and content |64| `OutlineEdit`, `PreparedEdit::outline` | Change ordinary outline position/width or a paragraph's saved expansion default, preserving identities and content |
65| `PreparedEdit::page` | Publish an edited `page::Page` as one revision per changed space: text, character and paragraph formatting, hyperlinks (external and, via `page::link`, to pages, paragraphs and sections), bullets and numbering, note tags, table rows, columns, cell shading and indents, nested tables, inserted pictures (in paragraphs or on the page) and attachments, picture position, size and description, a stored attachment's shown name, source path and icon size, paragraph styles on new paragraphs, ink drawings and handwriting (strokes added and erased), equations (built from `page::Math` trees), paragraph insertion/split/join/move/deletion, outline insertion/deletion/position/width and saved collapse state, lowered onto the typed writers with the model's identities |65| `PreparedEdit::page` | Publish an edited `page::Page` as one revision per changed space: text, character and paragraph formatting, hyperlinks (external and, via `page::link`, to pages, paragraphs and sections), bullets and numbering, note tags, table rows, columns, cell shading and indents, nested tables, inserted pictures (in paragraphs or on the page) and attachments, picture position, size and description, a stored attachment's shown name, source path and icon size, paragraph styles on new paragraphs, ink drawings and handwriting (strokes added and erased), equations (built from `page::Math` trees), paragraph insertion/split/join/move/deletion, outline insertion/deletion/position/width and saved collapse state, lowered onto the typed writers with the model's identities |
66| `TreeEdit`, `PreparedEdit::tree` | Move or delete a subtree on one page, normalize surviving containers, and replace an emptied table cell's paragraph atomically |66| `TreeEdit`, `PreparedEdit::tree` | Move or delete a subtree on one page, normalize surviving containers, and replace an emptied table cell's paragraph atomically |
67| `PreparedEdit::commit`, `PreparedEdit::commit_file` | Publish the exact prepared image under caller-held exclusion or the conservative filesystem adapter |67| `Transaction`, `Stamp`, `PreparedEdit::transaction` | The bytes a commit writes (appended data, in-place list-tail and log patches, header) and the header and length it requires unchanged; `commit` under caller-held exclusion, `commit_file` under the conservative filesystem adapter, `apply` to the base image in memory; serializable for queues |
68| `Arena`, `Section` | Keep a section parsed across edits: `open` validates an image once; `seal` appends one revision per changed space as a `Transaction` on `stamp`, checking only what it appends; `replay` applies a queued transaction; `image` and `page` read the result |
69| `op::{Edit, Op, PageOp, TableEdit, SectionOp}`, `Section::apply` | Object-level edits (text, formatting, links, equations, paragraph insertion/split/join/move/deletion/levels, outlines, lists, tags, styles, paragraph formatting, tables, pictures, attachments, ink, page creation/import/moves/removal) applied whole or not to a kept-open section with emitter-chosen identities, UTF-16 ranges and the edit's time; refusals name the target, identity or structure at fault |
70| `op::lower`, `op::lower_page`, `Section::apply_page` | The ops turning a range of paragraphs, or a whole page model, into another, computed from the models alone |
68| `read_file` | Read a snapshot under whole-file exclusion |71| `read_file` | Read a snapshot under whole-file exclusion |
69| `read_snapshot` | Read a validated snapshot through fresh positioned I/O while the caller excludes maintenance |72| `read_snapshot` | Read a validated snapshot through fresh positioned I/O while the caller excludes maintenance |
70| `commit_file_property` | Lock, compare the source snapshot, append and flush, then publish the revision |73| `commit_file_property` | Lock, check the source snapshot's stamp, append and flush, then publish the revision |
71| `CommitIo`, `commit_property_bytes` | Supply another storage backend with equivalent exclusion and ordered durability |74| `CommitIo`, `commit_property_bytes` | Supply another storage backend with equivalent exclusion and ordered durability |
7275
73Scalar edits accept encoded values and require the caller to maintain MS-ONE76Scalar edits accept encoded values and require the caller to maintain MS-ONE
...@@ -212,7 +215,7 @@ with optional explicit IDs when conflict copies contain identical text....@@ -212,7 +215,7 @@ with optional explicit IDs when conflict copies contain identical text.
212## Commit behavior215## Commit behavior
213216
214```text217```text
215exclusive lock → exact snapshot comparison218exclusive lock → header and length check
216 → append data → flush219 → append data → flush
217 → prepare header metadata → flush220 → prepare header metadata → flush
218 → publish transaction counter → flush221 → publish transaction counter → flush
...@@ -220,7 +223,9 @@ exclusive lock → exact snapshot comparison...@@ -220,7 +223,9 @@ exclusive lock → exact snapshot comparison
220 → notify cached readers → flush → unlock223 → notify cached readers → flush → unlock
221```224```
222225
223Stale snapshots fail before writing. Live readers must use equivalent exclusion.226Stale snapshots fail before writing: every committed transaction and placement rewrites
227the header (MS-ONESTORE 2.3.1), so the body is never compared. Live readers must use
228equivalent exclusion.
224Native conflict creation can still expose cross-space references before their229Native conflict creation can still expose cross-space references before their
225targets are saved; such snapshots must be rejected and reread while synchronization230targets are saved; such snapshots must be rejected and reread while synchronization
226proceeds. `read_file` and `commit_file_property` serialize within the process because231proceeds. `read_file` and `commit_file_property` serialize within the process because
crates/onestore/src/active.rs+181-55
...@@ -1,20 +1,27 @@...@@ -1,20 +1,27 @@
1use crate::{1use crate::{
2 Error, ExGuid, Object, RevisionIndex, Store,2 Error, ExGuid, FileType, Object, RevisionIndex, Store,
3 document::{Document, Element, Revision, Space},3 document::{Document, Element, Kind, Revision, Space},
4 write::{Commit, LiveRevision, PropertyObject, chain_depth, declared, differing},4 write::{Commit, LiveRevision, PropertyObject, chain_depth, declared, differing},
5};5};
6use bumpalo::Bump;6use bumpalo::Bump;
7use std::collections::{BTreeMap, BTreeSet};7use std::{
8 collections::{BTreeMap, BTreeSet},
9 sync::Arc,
10};
811
9type Result<T> = std::result::Result<T, Error>;12type Result<T> = std::result::Result<T, Error>;
1013
11/// Objects a writer stores in one revision of a page space.14/// Objects a writer stores in one revision of a page space.
12pub(crate) type Changes = BTreeMap<ExGuid, PropertyObject>;15pub(crate) type Changes = BTreeMap<ExGuid, PropertyObject>;
1316
17/// Payloads a store holds, by identity.
18pub(crate) type Files<'a> = std::rc::Rc<dyn Fn([u8; 16]) -> Result<&'a [u8]> + 'a>;
19
14/// A page space's active revision with the views its writers read, kept as appending20/// A page space's active revision with the views its writers read, kept as appending
15/// their revisions and parsing the result would leave it.21/// their revisions and parsing the result would leave it.
22#[derive(Clone)]
16pub(crate) struct ActivePage<'a> {23pub(crate) struct ActivePage<'a> {
17 store: &'a Store<'a>,24 files: Files<'a>,
18 pub pages: Vec<ExGuid>,25 pub pages: Vec<ExGuid>,
19 pub live: LiveRevision<'a>,26 pub live: LiveRevision<'a>,
20 /// Elements of the reachable objects.27 /// Elements of the reachable objects.
...@@ -42,6 +49,28 @@ impl<'a> ActivePage<'a> {...@@ -42,6 +49,28 @@ impl<'a> ActivePage<'a> {
42 })?;49 })?;
43 let rid = index.active(space)?;50 let rid = index.active(space)?;
44 let live = LiveRevision::new(index.resolve(space, rid)?, chain_depth(index, space, rid))?;51 let live = LiveRevision::new(index.resolve(space, rid)?, chain_depth(index, space, rid))?;
52 let store: &'a Store<'a> = index.store;
53 Self::viewed(std::rc::Rc::new(|guid| store.file_data(guid)), pages, live, view)
54 }
55
56 /// The page space `live` holds, whose stored payloads `files` reads.
57 pub(crate) fn open(
58 space: ExGuid,
59 live: LiveRevision<'a>,
60 file_type: FileType,
61 files: Files<'a>,
62 ) -> Result<Self> {
63 let (view, _) = Revision::parse(space, &live.revision, file_type, &mut |guid| files(guid))?;
64 let pages = manifest_pages(&view);
65 Self::viewed(files, pages, live, view)
66 }
67
68 fn viewed(
69 files: Files<'a>,
70 pages: Vec<ExGuid>,
71 live: LiveRevision<'a>,
72 view: Revision<'a>,
73 ) -> Result<Self> {
45 let parents = view.parents(&pages)?;74 let parents = view.parents(&pages)?;
46 let titles = view75 let titles = view
47 .nodes76 .nodes
...@@ -50,7 +79,7 @@ impl<'a> ActivePage<'a> {...@@ -50,7 +79,7 @@ impl<'a> ActivePage<'a> {
50 .map(|(id, _)| *id)79 .map(|(id, _)| *id)
51 .collect();80 .collect();
52 Ok(Self {81 Ok(Self {
53 store: index.store,82 files,
54 pages,83 pages,
55 live,84 live,
56 view,85 view,
...@@ -65,13 +94,13 @@ impl<'a> ActivePage<'a> {...@@ -65,13 +94,13 @@ impl<'a> ActivePage<'a> {
65 where94 where
66 'a: 'o,95 'a: 'o,
67 {96 {
68 Element::parse_with(object, self.store, &mut |guid| match self97 Element::parse_with(object, FileType::Section, &mut |guid| match self
69 .payloads98 .payloads
70 .iter()99 .iter()
71 .find(|(id, _)| *id == guid)100 .find(|(id, _)| *id == guid)
72 {101 {
73 Some((_, payload)) => Ok(*payload),102 Some((_, payload)) => Ok(*payload),
74 None => self.store.file_data(guid),103 None => (self.files)(guid),
75 })104 })
76 }105 }
77106
...@@ -108,12 +137,23 @@ impl<'a> ActivePage<'a> {...@@ -108,12 +137,23 @@ impl<'a> ActivePage<'a> {
108 payloads: &[([u8; 16], &[u8])],137 payloads: &[([u8; 16], &[u8])],
109 changes: Changes,138 changes: Changes,
110 ) -> Result<bool> {139 ) -> Result<bool> {
140 Ok(self.store(arena, payloads, changes)?.is_some())
141 }
142
143 /// `write`, returning the objects whose content, reachability or reference count may
144 /// have moved; none when it stores nothing.
145 pub(crate) fn store(
146 &mut self,
147 arena: &'a Bump,
148 payloads: &[([u8; 16], &[u8])],
149 changes: Changes,
150 ) -> Result<Option<BTreeSet<ExGuid>>> {
111 let changes = self.live.prepare(changes, true)?;151 let changes = self.live.prepare(changes, true)?;
112 for (guid, payload) in payloads {152 for (guid, payload) in payloads {
113 self.payloads.push((*guid, arena.alloc_slice_copy(payload)));153 self.payloads.push((*guid, arena.alloc_slice_copy(payload)));
114 }154 }
115 if changes.is_empty() {155 if changes.is_empty() {
116 return Ok(!payloads.is_empty());156 return Ok((!payloads.is_empty()).then(BTreeSet::new));
117 }157 }
118 let mut objects = Vec::new();158 let mut objects = Vec::new();
119 for (id, change) in changes {159 for (id, change) in changes {
...@@ -132,8 +172,49 @@ impl<'a> ActivePage<'a> {...@@ -132,8 +172,49 @@ impl<'a> ActivePage<'a> {
132 } else {172 } else {
133 self.live.settle(changed)?;173 self.live.settle(changed)?;
134 }174 }
135 self.refresh(&replaced, touched)?;175 self.refresh(&replaced, touched.clone())?;
136 Ok(true)176 Ok(Some(touched))
177 }
178
179 /// Takes the stored form of `ids` from `file`, the revision a seal appended, where the
180 /// revisions written here since the previous seal left them otherwise: a seal groups
181 /// tables and aliases read-only objects across all of them at once.
182 pub(crate) fn adopt(
183 &mut self,
184 file: &LiveRevision<'a>,
185 ids: impl IntoIterator<Item = ExGuid>,
186 ) -> Result<()> {
187 let mut replacements = Vec::new();
188 for id in ids {
189 let Some(stored) = file.revision.objects.get(&id) else {
190 continue;
191 };
192 match self.live.revision.objects.get_mut(&id) {
193 Some(object) if object.jcid == stored.jcid && object.data == stored.data => {
194 object.global_ids = Arc::clone(&stored.global_ids);
195 object.reference_count = stored.reference_count;
196 }
197 _ if file.is_reachable(id) => replacements.push((id, stored.clone())),
198 // Nothing reads an object the file leaves unreachable.
199 _ => {}
200 }
201 }
202 if !replacements.is_empty() {
203 let replaced = replacements.iter().map(|(id, _)| *id).collect();
204 let Commit { touched, .. } = self.live.commit(replacements)?;
205 for id in &touched {
206 if let (Some(object), Some(stored)) = (
207 self.live.revision.objects.get_mut(id),
208 file.revision.objects.get(id),
209 ) {
210 object.global_ids = Arc::clone(&stored.global_ids);
211 object.reference_count = stored.reference_count;
212 }
213 }
214 self.refresh(&replaced, touched)?;
215 }
216 self.live.depth = file.depth;
217 Ok(())
137 }218 }
138219
139 /// Brings the view, parents and titles up to date with the revision for the objects a220 /// Brings the view, parents and titles up to date with the revision for the objects a
...@@ -215,6 +296,24 @@ impl<'a> ActivePage<'a> {...@@ -215,6 +296,24 @@ impl<'a> ActivePage<'a> {
215 }296 }
216}297}
217298
299/// The pages a page space's manifest lists.
300pub(crate) fn manifest_pages(view: &Revision<'_>) -> Vec<ExGuid> {
301 match view.roots.get(&1).and_then(|id| view.nodes.get(id)) {
302 Some(manifest) if matches!(manifest.kind, Kind::Manifest { .. }) => manifest
303 .content
304 .iter()
305 .filter(|id| {
306 matches!(
307 view.nodes.get(id).map(|node| &node.kind),
308 Some(Kind::Page { .. })
309 )
310 })
311 .copied()
312 .collect(),
313 _ => Vec::new(),
314 }
315}
316
218fn edges<'n>(node: &'n Element<'_>) -> impl Iterator<Item = ExGuid> + 'n {317fn edges<'n>(node: &'n Element<'_>) -> impl Iterator<Item = ExGuid> + 'n {
219 node.children318 node.children
220 .iter()319 .iter()
...@@ -245,10 +344,79 @@ pub(crate) fn write(...@@ -245,10 +344,79 @@ pub(crate) fn write(
245}344}
246345
247#[cfg(test)]346#[cfg(test)]
248mod tests {347pub(crate) mod tests {
249 use super::*;348 use super::*;
250 use crate::{Insertion, ParagraphSplit, TextAttribute, TreeEdit, document::Kind};349 use crate::{Insertion, ParagraphSplit, TextAttribute, TreeEdit, document::Kind};
251350
351 /// A seeded source of the typed writers' changes: insertions, moves, deletions, splits
352 /// and formatting of what a page holds.
353 pub(crate) struct Writes(pub u64);
354
355 impl Writes {
356 fn pick(&mut self, count: usize) -> usize {
357 self.0 = self
358 .0
359 .wrapping_mul(6364136223846793005)
360 .wrapping_add(1442695040888963407);
361 (self.0 >> 33) as usize % count.max(1)
362 }
363
364 pub(crate) fn next(&mut self, active: &ActivePage<'_>) -> Result<Changes> {
365 let nodes = &active.view.nodes;
366 let listed = |kind: fn(&Kind<'_>) -> bool| -> Vec<ExGuid> {
367 nodes
368 .iter()
369 .filter(|(id, node)| kind(&node.kind) && active.parents.contains_key(id))
370 .map(|(id, _)| *id)
371 .collect()
372 };
373 let paragraphs = listed(|kind| matches!(kind, Kind::Paragraph { .. }));
374 let containers = listed(|kind| {
375 matches!(
376 kind,
377 Kind::Paragraph { .. } | Kind::Outline { .. } | Kind::Cell { .. }
378 )
379 });
380 let texts = listed(|kind| matches!(kind, Kind::RichText { .. }));
381 let unavailable = Error {
382 offset: 0,
383 message: "The page has nothing to edit",
384 };
385 let paragraph = *paragraphs
386 .get(self.pick(paragraphs.len()))
387 .ok_or(unavailable)?;
388 let container = *containers
389 .get(self.pick(containers.len()))
390 .ok_or(unavailable)?;
391 let anchor = nodes[&container].children.get(self.pick(4)).copied();
392 let text = *texts.get(self.pick(texts.len())).ok_or(unavailable)?;
393 let words = ["", "a", "Two words", "東京 🦀", "longer text here"];
394 let word = words[self.pick(words.len())];
395 match self.pick(8) {
396 0..=3 => Insertion::paragraph(container, anchor, word, "Author")
397 .and_then(|insertion| insertion.changes(active)),
398 4 => TreeEdit::move_to(paragraph, container, anchor, "Author")
399 .and_then(|edit| edit.changes(active)),
400 5 => TreeEdit::delete(paragraph, "Author").and_then(|edit| edit.changes(active)),
401 6 => {
402 let at = self.pick(3) as u32;
403 ParagraphSplit::new(text, at, "Author").and_then(|split| split.changes(active))
404 }
405 _ => {
406 let end = self.pick(2) as u32;
407 let bold = self.pick(2) == 0;
408 crate::formatting::format_changes(
409 active,
410 text,
411 0..end,
412 &[TextAttribute::Bold(bold)],
413 &[],
414 )
415 }
416 }
417 }
418 }
419
252 /// `active` equals the page `image` stores, views included.420 /// `active` equals the page `image` stores, views included.
253 fn assert_stores(active: &ActivePage<'_>, image: &[u8], space: ExGuid) {421 fn assert_stores(active: &ActivePage<'_>, image: &[u8], space: ExGuid) {
254 let store = Store::parse(image).unwrap();422 let store = Store::parse(image).unwrap();
...@@ -288,51 +456,9 @@ mod tests {...@@ -288,51 +456,9 @@ mod tests {
288 let (space, _) = Document::parse(&index).unwrap().pages().unwrap()[0];456 let (space, _) = Document::parse(&index).unwrap().pages().unwrap()[0];
289 let arena = Bump::new();457 let arena = Bump::new();
290 let mut active = ActivePage::parse(&index, space).unwrap();458 let mut active = ActivePage::parse(&index, space).unwrap();
291 let mut seed = 7_u64;459 let mut writes = Writes(7);
292 let mut pick = |count: usize| {
293 seed = seed
294 .wrapping_mul(6364136223846793005)
295 .wrapping_add(1442695040888963407);
296 (seed >> 33) as usize % count.max(1)
297 };
298 for step in 0..steps {460 for step in 0..steps {
299 let nodes = &active.view.nodes;461 let changes = writes.next(&active);
300 let listed = |kind: fn(&Kind<'_>) -> bool| -> Vec<ExGuid> {
301 nodes
302 .iter()
303 .filter(|(id, node)| kind(&node.kind) && active.parents.contains_key(id))
304 .map(|(id, _)| *id)
305 .collect()
306 };
307 let paragraphs = listed(|kind| matches!(kind, Kind::Paragraph { .. }));
308 let containers = listed(|kind| {
309 matches!(
310 kind,
311 Kind::Paragraph { .. } | Kind::Outline { .. } | Kind::Cell { .. }
312 )
313 });
314 let texts = listed(|kind| matches!(kind, Kind::RichText { .. }));
315 let paragraph = paragraphs[pick(paragraphs.len())];
316 let container = containers[pick(containers.len())];
317 let anchor = nodes[&container].children.get(pick(4)).copied();
318 let text = texts[pick(texts.len())];
319 let words = ["", "a", "Two words", "東京 🦀", "longer text here"];
320 let word = words[pick(words.len())];
321 let changes = match pick(8) {
322 0..=3 => Insertion::paragraph(container, anchor, word, "Author")
323 .and_then(|insertion| insertion.changes(&active)),
324 4 => TreeEdit::move_to(paragraph, container, anchor, "Author")
325 .and_then(|edit| edit.changes(&active)),
326 5 => TreeEdit::delete(paragraph, "Author").and_then(|edit| edit.changes(&active)),
327 6 => ParagraphSplit::new(text, pick(3) as u32, "Author")
328 .and_then(|split| split.changes(&active)),
329 _ => crate::formatting::format_changes(
330 &active,
331 text,
332 0..pick(2) as u32,
333 &[TextAttribute::Bold(pick(2) == 0)],
334 ),
335 };
336 let Ok(changes) = changes else {462 let Ok(changes) = changes else {
337 continue;463 continue;
338 };464 };
crates/onestore/src/commit.rs+197-81
...@@ -383,22 +383,20 @@ impl<'a> PreparedEdit<'a> {...@@ -383,22 +383,20 @@ impl<'a> PreparedEdit<'a> {
383 &self.written383 &self.written
384 }384 }
385385
386 /// Publishes these prepared bytes after comparing the entire original snapshot.386 /// The bytes publishing this edit writes, against its snapshot's stamp.
387 /// The caller must retain OneNote-compatible exclusion through the returned outcome.387 pub fn transaction(&self) -> Transaction {
388 Transaction::between(self.source, &self.written)
389 }
390
391 /// `Transaction::commit` of this edit.
388 pub fn commit(&self, io: &mut impl CommitIo) -> Result<(), CommitError> {392 pub fn commit(&self, io: &mut impl CommitIo) -> Result<(), CommitError> {
389 commit_bytes(io, self.source, &self.written)393 self.transaction().commit(io)
390 }394 }
391395
392 /// Publishes these exact bytes through the conservative whole-file filesystem adapter.396 /// `Transaction::commit_file` of this edit.
393 /// A changed source returns ResourceBusy; an uncertain outcome must be reconciled before replay.
394 #[cfg(any(unix, windows))]397 #[cfg(any(unix, windows))]
395 pub fn commit_file(&self, path: impl AsRef<Path>) -> Result<(), CommitError> {398 pub fn commit_file(&self, path: impl AsRef<Path>) -> Result<(), CommitError> {
396 let mut io = FileIo::open(path, true).map_err(|error| CommitError {399 self.transaction().commit_file(path)
397 state: CommitState::NotCommitted,
398 error,
399 })?;
400 let result = self.commit(&mut io);
401 io.finish(result)
402 }400 }
403}401}
404402
...@@ -413,7 +411,7 @@ pub fn confirm_file_snapshot(path: impl AsRef<Path>, source: &[u8]) -> Result<()...@@ -413,7 +411,7 @@ pub fn confirm_file_snapshot(path: impl AsRef<Path>, source: &[u8]) -> Result<()
413 io.finish(result)411 io.finish(result)
414}412}
415413
416/// Compares and flushes a snapshot, then refreshes its header version metadata.414/// Checks and flushes a snapshot's stamp, then refreshes its header version metadata.
417/// No revision is added; reread before using the snapshot for another physical commit.415/// No revision is added; reread before using the snapshot for another physical commit.
418/// The caller must hold OneNote-compatible exclusion and independently establish which416/// The caller must hold OneNote-compatible exclusion and independently establish which
419/// intents the snapshot contains. A successful read alone is not a durable acknowledgement.417/// intents the snapshot contains. A successful read alone is not a durable acknowledgement.
...@@ -429,7 +427,7 @@ pub fn confirm_snapshot(io: &mut impl CommitIo, source: &[u8]) -> Result<(), Com...@@ -429,7 +427,7 @@ pub fn confirm_snapshot(io: &mut impl CommitIo, source: &[u8]) -> Result<(), Com
429 version[..16].copy_from_slice(&crate::write::fresh_guid().map_err(io::Error::other)?);427 version[..16].copy_from_slice(&crate::write::fresh_guid().map_err(io::Error::other)?);
430 version[16..24].copy_from_slice(&generation.to_le_bytes());428 version[16..24].copy_from_slice(&generation.to_le_bytes());
431 version[24..].copy_from_slice(&crate::write::fresh_guid().map_err(io::Error::other)?);429 version[24..].copy_from_slice(&crate::write::fresh_guid().map_err(io::Error::other)?);
432 compare_snapshot(io, source)?;430 Stamp::of(source).map_err(io::Error::other)?.check(io)?;
433 state = CommitState::Unknown;431 state = CommitState::Unknown;
434 io.flush()?;432 io.flush()?;
435 write_all(io, 212, &version)?;433 write_all(io, 212, &version)?;
...@@ -474,12 +472,12 @@ impl std::error::Error for CommitError {...@@ -474,12 +472,12 @@ impl std::error::Error for CommitError {
474 }472 }
475}473}
476474
477fn write_all(io: &mut impl CommitIo, mut offset: usize, mut bytes: &[u8]) -> io::Result<()> {475fn write_all(io: &mut impl CommitIo, mut offset: u64, mut bytes: &[u8]) -> io::Result<()> {
478 while !bytes.is_empty() {476 while !bytes.is_empty() {
479 match io.write_at(offset as u64, bytes) {477 match io.write_at(offset, bytes) {
480 Ok(0) => return Err(io::Error::from(ErrorKind::WriteZero)),478 Ok(0) => return Err(io::Error::from(ErrorKind::WriteZero)),
481 Ok(count) if count <= bytes.len() => {479 Ok(count) if count <= bytes.len() => {
482 offset += count;480 offset += count as u64;
483 bytes = &bytes[count..];481 bytes = &bytes[count..];
484 }482 }
485 Ok(_) => {483 Ok(_) => {
...@@ -512,85 +510,203 @@ pub fn commit_property_bytes(...@@ -512,85 +510,203 @@ pub fn commit_property_bytes(
512 error: io::Error::new(ErrorKind::InvalidData, error),510 error: io::Error::new(ErrorKind::InvalidData, error),
513 }511 }
514 })?;512 })?;
515 commit_bytes(io, source, &written)513 Transaction::between(source, &written).commit(io)
516}514}
517515
518fn compare_snapshot(io: &mut impl CommitIo, source: &[u8]) -> io::Result<()> {516/// What a commit requires unchanged since its snapshot: the header, which every committed
519 let capacity = source.len().clamp(1, 1024 * 1024);517/// transaction and every placement rewrites (MS-ONESTORE 2.3.1 `guidFileVersion`), and the
520 let mut buffer = Vec::new();518/// length appends start from. Equal stamps name the same committed image.
521 buffer519#[derive(Clone, Debug, PartialEq, Eq)]
522 .try_reserve_exact(capacity)520pub struct Stamp {
523 .map_err(io::Error::other)?;521 pub header: [u8; 1024],
524 buffer.resize(capacity, 0);522 pub length: u64,
525 let mut offset = 0;523}
526 while offset < source.len() {524
527 let size = buffer.len().min(source.len() - offset);525impl Stamp {
528 let count = match io.read_at(offset as u64, &mut buffer[..size]) {526 pub fn of(image: &[u8]) -> Result<Self, crate::Error> {
529 Err(error) if error.kind() == ErrorKind::Interrupted => continue,527 Ok(Self {
530 result => result?,528 header: image.first_chunk().copied().ok_or(crate::Error {
531 };529 offset: 0,
532 if count == 0 {530 message: "Truncated revision-store header",
533 return Err(io::Error::from(ErrorKind::UnexpectedEof));531 })?,
532 length: image.len() as u64,
533 })
534 }
535
536 /// Reads the header and probes the length, without reading the body.
537 fn check(&self, io: &mut impl CommitIo) -> io::Result<()> {
538 let mut header = [0; 1024];
539 crate::snapshot::read_exact(
540 &mut |offset, output| io.read_at(offset, output),
541 0,
542 &mut header,
543 )?;
544 if header != self.header {
545 return Err(io::Error::new(
546 ErrorKind::ResourceBusy,
547 "The file header changed after the edit snapshot",
548 ));
534 }549 }
535 if count > size || buffer[..count] != source[offset..offset + count] {550 let last = self.length.checked_sub(1).ok_or(ErrorKind::InvalidInput)?;
551 let mut tail = [0; 2];
552 let count = loop {
553 match io.read_at(last, &mut tail) {
554 Err(error) if error.kind() == ErrorKind::Interrupted => {}
555 result => break result?,
556 }
557 };
558 if count != 1 {
536 return Err(io::Error::new(559 return Err(io::Error::new(
537 ErrorKind::ResourceBusy,560 ErrorKind::ResourceBusy,
538 "The locked file differs from the edit snapshot",561 "The file length changed after the edit snapshot",
539 ));562 ));
540 }563 }
541 offset += count;564 Ok(())
542 }
543 if io.read_at(source.len() as u64, &mut buffer[..1])? != 0 {
544 return Err(io::Error::new(
545 ErrorKind::ResourceBusy,
546 "The locked file grew after the edit snapshot",
547 ));
548 }565 }
549 Ok(())
550}566}
551567
552pub(crate) fn commit_bytes(568/// One change to a revision store as the bytes committing it writes: `append` at the base
553 io: &mut impl CommitIo,569/// length, `patches` inside the base's data area (list tails and the transaction log), and
554 source: &[u8],570/// the header that publishes them.
555 written: &[u8],571#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
556) -> Result<(), CommitError> {572#[serde(into = "Wire", try_from = "Wire")]
557 let mut state = CommitState::NotCommitted;573pub struct Transaction {
558 let result = (|| -> io::Result<()> {574 pub(crate) base: Stamp,
559 compare_snapshot(io, source)?;575 pub(crate) append: Vec<u8>,
560 if written == source {576 pub(crate) patches: Vec<(u64, Vec<u8>)>,
561 state = CommitState::Unknown;577 pub(crate) header: [u8; 1024],
562 io.flush()?;578}
563 return Ok(());579
580/// A transaction as serialized: headers as byte strings, since serde stops at 32-byte arrays.
581#[derive(serde::Serialize, serde::Deserialize)]
582struct Wire {
583 base: Vec<u8>,
584 length: u64,
585 append: Vec<u8>,
586 patches: Vec<(u64, Vec<u8>)>,
587 header: Vec<u8>,
588}
589
590impl From<Transaction> for Wire {
591 fn from(transaction: Transaction) -> Self {
592 Self {
593 base: transaction.base.header.to_vec(),
594 length: transaction.base.length,
595 append: transaction.append,
596 patches: transaction.patches,
597 header: transaction.header.to_vec(),
564 }598 }
565 write_all(io, source.len(), &written[source.len()..])?;599 }
600}
601
602impl TryFrom<Wire> for Transaction {
603 type Error = &'static str;
604
605 fn try_from(wire: Wire) -> Result<Self, Self::Error> {
606 let header = |bytes: Vec<u8>| {
607 <[u8; 1024]>::try_from(bytes).map_err(|_| "A transaction header is 1024 bytes")
608 };
609 Ok(Self {
610 base: Stamp {
611 header: header(wire.base)?,
612 length: wire.length,
613 },
614 append: wire.append,
615 patches: wire.patches,
616 header: header(wire.header)?,
617 })
618 }
619}
620
621impl Transaction {
622 /// The transaction turning `source`, a parsed image, into `written`, which extends it.
623 pub(crate) fn between(source: &[u8], written: &[u8]) -> Self {
624 let differs = |at: &usize| source[*at] != written[*at];
625 let mut patches = Vec::new();
566 let mut offset = 1024;626 let mut offset = 1024;
567 while offset < source.len() {627 while let Some(start) = (offset..source.len()).find(differs) {
568 if source[offset] == written[offset] {628 let mut end = start + 1;
569 offset += 1;629 // Equal gaps shorter than a write request's overhead join the patch.
570 continue;630 while let Some(next) = (end..source.len().min(end + 64)).find(differs) {
631 end = next + 1;
571 }632 }
572 let start = offset;633 patches.push((start as u64, written[start..end].to_vec()));
573 while offset < source.len() && source[offset] != written[offset] {634 offset = end;
574 offset += 1;
575 }
576 write_all(io, start, &written[start..offset])?;
577 }635 }
578 io.flush()?;636 Self {
579 write_all(io, 100, &written[100..212])?;637 base: Stamp {
580 write_all(io, 252, &written[252..1024])?;638 header: *source.first_chunk().unwrap(),
581 io.flush()?;639 length: source.len() as u64,
582 let highest = (96..100).rfind(|at| source[*at] != written[*at]).unwrap();640 },
583 state = CommitState::Unknown;641 append: written[source.len()..].to_vec(),
584 write_all(io, highest, &written[highest..highest + 1])?;642 patches,
585 io.flush()?;643 header: *written.first_chunk().unwrap(),
586 if highest > 96 {
587 write_all(io, 96, &written[96..highest])?;
588 io.flush()?;
589 }644 }
590 // Native readers cache the version GUID without rechecking the transaction count.645 }
591 write_all(io, 212, &written[212..252])?;646
592 io.flush()?;647 /// Writes this transaction into its base image, as a successful commit leaves the file.
648 pub fn apply(&self, image: &mut Vec<u8>) -> Result<(), crate::Error> {
649 if Stamp::of(image)? != self.base {
650 return Err(crate::Error {
651 offset: 0,
652 message: "The image is not this transaction's base",
653 });
654 }
655 image.extend_from_slice(&self.append);
656 for (offset, bytes) in &self.patches {
657 let offset = *offset as usize;
658 image[offset..offset + bytes.len()].copy_from_slice(bytes);
659 }
660 image[..1024].copy_from_slice(&self.header);
593 Ok(())661 Ok(())
594 })();662 }
595 result.map_err(|error| CommitError { state, error })663
664 /// Publishes under caller-held OneNote-compatible exclusion, provided the file still has
665 /// the base stamp; otherwise returns ResourceBusy without writing. Appended data and
666 /// patches are flushed before the header, and the transaction count commits them
667 /// (MS-ONESTORE 2.3.3). Flush must make preceding writes durable before later ones.
668 pub fn commit(&self, io: &mut impl CommitIo) -> Result<(), CommitError> {
669 let mut state = CommitState::NotCommitted;
670 let result = (|| -> io::Result<()> {
671 self.base.check(io)?;
672 if self.append.is_empty() && self.patches.is_empty() && self.header == self.base.header
673 {
674 state = CommitState::Unknown;
675 return io.flush();
676 }
677 write_all(io, self.base.length, &self.append)?;
678 for (offset, bytes) in &self.patches {
679 write_all(io, *offset, bytes)?;
680 }
681 io.flush()?;
682 write_all(io, 100, &self.header[100..212])?;
683 write_all(io, 252, &self.header[252..])?;
684 io.flush()?;
685 state = CommitState::Unknown;
686 // At a counter carry the highest changed byte commits; the lower ones follow.
687 if let Some(highest) = (96..100).rfind(|at| self.base.header[*at] != self.header[*at]) {
688 write_all(io, highest as u64, &self.header[highest..highest + 1])?;
689 io.flush()?;
690 if highest > 96 {
691 write_all(io, 96, &self.header[96..highest])?;
692 io.flush()?;
693 }
694 }
695 // Native readers cache the version GUID without rechecking the transaction count.
696 write_all(io, 212, &self.header[212..252])?;
697 io.flush()
698 })();
699 result.map_err(|error| CommitError { state, error })
700 }
701
702 /// `commit` under the conservative filesystem adapter's whole-file exclusion.
703 #[cfg(any(unix, windows))]
704 pub fn commit_file(&self, path: impl AsRef<Path>) -> Result<(), CommitError> {
705 let mut io = FileIo::open(path, true).map_err(|error| CommitError {
706 state: CommitState::NotCommitted,
707 error,
708 })?;
709 let result = self.commit(&mut io);
710 io.finish(result)
711 }
596}712}
crates/onestore/src/create.rs+56-13
...@@ -1,5 +1,6 @@...@@ -1,5 +1,6 @@
1use crate::{1use crate::{
2 Chunk, Error, ExGuid, FileType, PropertySets, Reference, RevisionIndex, Store,2 Chunk, Error, ExGuid, FileType, PropertySets, Reference, RevisionIndex, Store,
3 op::content::{NATIVE_INDENTS, measurement_bytes},
3 store::crc,4 store::crc,
4 write::{append, append_list, fresh_guid, node},5 write::{append, append_list, fresh_guid, node},
5};6};
...@@ -24,6 +25,20 @@ pub(crate) fn default_text_style() -> Vec<(u32, Vec<u8>)> {...@@ -24,6 +25,20 @@ pub(crate) fn default_text_style() -> Vec<(u32, Vec<u8>)> {
24 ]25 ]
25}26}
2627
28/// The page margins MS-ONE requires on every page, in half inches, as OneNote 2010 sets them
29/// on the pages it creates.
30pub(crate) fn page_margins() -> Vec<(u32, Vec<u8>)> {
31 [
32 (0x14001c4c, 1.0_f32),
33 (0x14001c4d, 1.0),
34 (0x14001c4e, 2.0),
35 (0x14001c4f, 2.0),
36 ]
37 .into_iter()
38 .map(|(id, value)| (id, value.to_le_bytes().to_vec()))
39 .collect()
40}
41
27pub(crate) fn properties(values: &[(u32, Vec<u8>)]) -> Result<Vec<u8>> {42pub(crate) fn properties(values: &[(u32, Vec<u8>)]) -> Result<Vec<u8>> {
28 let mut streams: [Vec<u8>; 3] = std::array::from_fn(|_| Vec::new());43 let mut streams: [Vec<u8>; 3] = std::array::from_fn(|_| Vec::new());
29 let mut fields = Vec::new();44 let mut fields = Vec::new();
...@@ -110,14 +125,38 @@ pub(crate) fn placement(ancestor: [u8; 16], name: &str) -> [u8; 20] {...@@ -110,14 +125,38 @@ pub(crate) fn placement(ancestor: [u8; 16], name: &str) -> [u8; 20] {
110 bytes125 bytes
111}126}
112127
128thread_local! {
129 /// The FILETIME `at` gives writers in place of the system clock.
130 static CLOCK: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
131}
132
133/// Runs `f` with the writers' clock reading `filetime`, as an op's modification time.
134pub(crate) fn at<T>(filetime: u64, f: impl FnOnce() -> T) -> T {
135 struct Restore(Option<u64>);
136 impl Drop for Restore {
137 fn drop(&mut self) {
138 CLOCK.set(self.0);
139 }
140 }
141 let _restore = Restore(CLOCK.replace(Some(filetime)));
142 f()
143}
144
145/// Seconds since 1980 as OneNote's Time32, and FILETIME, of the writers' clock.
113pub(crate) fn current_timestamps() -> Result<(u32, u64)> {146pub(crate) fn current_timestamps() -> Result<(u32, u64)> {
114 let now = SystemTime::now()147 let now = match CLOCK.get() {
115 .duration_since(UNIX_EPOCH)148 Some(filetime) => (filetime / 10_000_000).checked_sub(11644473600).ok_or(Error {
116 .map_err(|_| Error {
117 offset: 0,149 offset: 0,
118 message: "System time precedes the Unix epoch",150 message: "An edit time precedes the Unix epoch",
119 })?151 })?,
120 .as_secs();152 None => SystemTime::now()
153 .duration_since(UNIX_EPOCH)
154 .map_err(|_| Error {
155 offset: 0,
156 message: "System time precedes the Unix epoch",
157 })?
158 .as_secs(),
159 };
121 let modified = now160 let modified = now
122 .checked_sub(315532800)161 .checked_sub(315532800)
123 .and_then(|time| u32::try_from(time).ok())162 .and_then(|time| u32::try_from(time).ok())
...@@ -205,12 +244,16 @@ pub fn create_section(file_name: &str, text: &str, author: &str) -> Result<Vec<u...@@ -205,12 +244,16 @@ pub fn create_section(file_name: &str, text: &str, author: &str) -> Result<Vec<u
205 NewObject {244 NewObject {
206 id: 22,245 id: 22,
207 jcid: 0x6000b,246 jcid: 0x6000b,
208 properties: vec![247 properties: [
209 last_modified(),248 vec![
210 (0x24001c20, id(23)),249 last_modified(),
211 (0x1c001d75, string(author)),250 (0x24001c20, id(23)),
212 (0x1c001d3c, string(crate::edit::automatic_title(text))),251 (0x1c001d75, string(author)),
213 ],252 (0x1c001d3c, string(crate::edit::automatic_title(text))),
253 ],
254 page_margins(),
255 ]
256 .concat(),
214 },257 },
215 NewObject {258 NewObject {
216 id: 23,259 id: 23,
...@@ -219,7 +262,7 @@ pub fn create_section(file_name: &str, text: &str, author: &str) -> Result<Vec<u...@@ -219,7 +262,7 @@ pub fn create_section(file_name: &str, text: &str, author: &str) -> Result<Vec<u
219 last_modified(),262 last_modified(),
220 (0x24001c20, id(24)),263 (0x24001c20, id(24)),
221 (0x0c001c03, vec![1]),264 (0x0c001c03, vec![1]),
222 (0x1c001c12, vec![1, 0, 0, 0, 0, 0, 0, 0]),265 (0x1c001c12, measurement_bytes(&NATIVE_INDENTS, 4)?),
223 (0x14001c14, 1_f32.to_le_bytes().to_vec()),266 (0x14001c14, 1_f32.to_le_bytes().to_vec()),
224 (0x14001c15, 1_f32.to_le_bytes().to_vec()),267 (0x14001c15, 1_f32.to_le_bytes().to_vec()),
225 (0x14001c1b, 13_f32.to_le_bytes().to_vec()),268 (0x14001c1b, 13_f32.to_le_bytes().to_vec()),
crates/onestore/src/document.rs+110-88
...@@ -5,7 +5,7 @@...@@ -5,7 +5,7 @@
55
6use crate::{6use crate::{
7 Error, ExGuid, FileDataReference, IdStream, Object, ObjectData, Property, PropertySets,7 Error, ExGuid, FileDataReference, IdStream, Object, ObjectData, Property, PropertySets,
8 RevisionIndex, Store, Value, bytes::Cursor,8 RevisionIndex, Value, bytes::Cursor,
9};9};
10use serde::{Deserialize, Serialize};10use serde::{Deserialize, Serialize};
11use std::collections::{BTreeMap, BTreeSet};11use std::collections::{BTreeMap, BTreeSet};
...@@ -98,6 +98,13 @@ macro_rules! format_fields {...@@ -98,6 +98,13 @@ macro_rules! format_fields {
98 pub fn inherit(&self, parent: &Self) -> Self {98 pub fn inherit(&self, parent: &Self) -> Self {
99 Self { $($field: self.$field.as_ref().or(parent.$field.as_ref()).cloned()),* }99 Self { $($field: self.$field.as_ref().or(parent.$field.as_ref()).cloned()),* }
100 }100 }
101
102 /// The values `self` sets that `parent` does not already give.
103 pub fn over(&self, parent: &Self) -> Self {
104 Self {
105 $($field: self.$field.as_ref().filter(|value| parent.$field.as_ref() != Some(*value)).cloned()),*
106 }
107 }
101 }108 }
102 };109 };
103}110}
...@@ -275,6 +282,98 @@ impl Revision<'_> {...@@ -275,6 +282,98 @@ impl Revision<'_> {
275 }282 }
276}283}
277284
285impl<'a> Revision<'a> {
286 /// The elements reachable in `revision` of `space`, and the (space, context) pairs they
287 /// reference.
288 pub(crate) fn parse(
289 space: ExGuid,
290 revision: &crate::ResolvedRevision<'a>,
291 file_type: crate::FileType,
292 file_data: &mut impl FnMut([u8; 16]) -> Result<&'a [u8]>,
293 ) -> Result<(Self, Vec<(ExGuid, ExGuid)>)> {
294 let mut reachable = BTreeSet::new();
295 let mut objects: Vec<_> = revision.roots.values().copied().collect();
296 let mut encrypted = false;
297 while let Some(oid) = objects.pop() {
298 if !reachable.insert(oid) {
299 continue;
300 }
301 let object = revision
302 .objects
303 .get(&oid)
304 .ok_or_else(|| invalid("Document object has no declaration"))?;
305 if matches!(object.data, ObjectData::Encrypted(_)) {
306 encrypted = true;
307 } else {
308 objects.extend(object.references()?.objects);
309 }
310 }
311 if !encrypted {
312 revision.reachable()?;
313 }
314 let mut referenced = Vec::new();
315 let mut nodes = BTreeMap::new();
316 for oid in reachable {
317 let object = &revision.objects[&oid];
318 if !matches!(object.data, ObjectData::Encrypted(_)) {
319 let refs = object.references()?;
320 referenced.extend(
321 refs.object_spaces
322 .into_iter()
323 .map(|sid| (sid, ExGuid::default())),
324 );
325 referenced.extend(refs.contexts.into_iter().map(|context| (space, context)));
326 }
327 nodes.insert(oid, Element::parse_with(object, file_type, file_data)?);
328 }
329 for node in nodes.values() {
330 if let Kind::RichText {
331 runs,
332 paragraph_style,
333 ..
334 } = &node.kind
335 {
336 for target in paragraph_style
337 .iter()
338 .chain(runs.iter().filter_map(|r| r.format.as_ref()))
339 {
340 if !matches!(nodes.get(target).map(|n| &n.kind), Some(Kind::Style { .. })) {
341 return Err(invalid("Text formatting does not reference a style"));
342 }
343 }
344 }
345 let mut tag_types = BTreeSet::new();
346 for tag in &node.tags {
347 let action_type = if tag.status & 4 != 0 {
348 tag.action_type
349 } else {
350 match tag
351 .definition
352 .and_then(|id| nodes.get(&id))
353 .map(|n| &n.kind)
354 {
355 Some(Kind::TagDefinition { action_type, .. }) => *action_type,
356 _ => {
357 return Err(invalid("Note tag does not reference a tag definition"));
358 }
359 }
360 }
361 .ok_or_else(|| invalid("Note tag has no action type"))?;
362 if !tag_types.insert(action_type) {
363 return Err(invalid("Repeated note tag action type"));
364 }
365 }
366 }
367 Ok((
368 Self {
369 roots: revision.roots.clone(),
370 nodes,
371 },
372 referenced,
373 ))
374 }
375}
376
278impl Revision<'_> {377impl Revision<'_> {
279 /// Maps every element reachable from `roots` through children, content and structure378 /// Maps every element reachable from `roots` through children, content and structure
280 /// references to its referencing parents, in visiting order.379 /// references to its referencing parents, in visiting order.
...@@ -807,91 +906,14 @@ impl<'a> Document<'a> {...@@ -807,91 +906,14 @@ impl<'a> Document<'a> {
807 if space.revisions.contains_key(&rid) {906 if space.revisions.contains_key(&rid) {
808 continue;907 continue;
809 }908 }
810 let revision = resolve(id, rid)?;909 let (revision, referenced) = Revision::parse(
811 let mut reachable = BTreeSet::new();910 id,
812 let mut objects: Vec<_> = revision.roots.values().copied().collect();911 &resolve(id, rid)?,
813 let mut encrypted = false;912 index.store.header.file_type,
814 while let Some(oid) = objects.pop() {913 &mut file_data,
815 if !reachable.insert(oid) {914 )?;
816 continue;915 pending.extend(referenced);
817 }916 space.revisions.insert(rid, revision);
818 let object = revision
819 .objects
820 .get(&oid)
821 .ok_or_else(|| invalid("Document object has no declaration"))?;
822 if matches!(object.data, ObjectData::Encrypted(_)) {
823 encrypted = true;
824 } else {
825 objects.extend(object.references()?.objects);
826 }
827 }
828 if !encrypted {
829 revision.reachable()?;
830 }
831 let mut nodes = BTreeMap::new();
832 for oid in reachable {
833 let object = &revision.objects[&oid];
834 if !matches!(object.data, ObjectData::Encrypted(_)) {
835 let refs = object.references()?;
836 pending.extend(
837 refs.object_spaces
838 .into_iter()
839 .map(|sid| (sid, ExGuid::default())),
840 );
841 pending.extend(refs.contexts.into_iter().map(|context| (id, context)));
842 }
843 nodes.insert(
844 oid,
845 Element::parse_with(object, index.store, &mut file_data)?,
846 );
847 }
848 for node in nodes.values() {
849 if let Kind::RichText {
850 runs,
851 paragraph_style,
852 ..
853 } = &node.kind
854 {
855 for target in paragraph_style
856 .iter()
857 .chain(runs.iter().filter_map(|r| r.format.as_ref()))
858 {
859 if !matches!(nodes.get(target).map(|n| &n.kind), Some(Kind::Style { .. })) {
860 return Err(invalid("Text formatting does not reference a style"));
861 }
862 }
863 }
864 let mut tag_types = BTreeSet::new();
865 for tag in &node.tags {
866 let action_type = if tag.status & 4 != 0 {
867 tag.action_type
868 } else {
869 match tag
870 .definition
871 .and_then(|id| nodes.get(&id))
872 .map(|n| &n.kind)
873 {
874 Some(Kind::TagDefinition { action_type, .. }) => *action_type,
875 _ => {
876 return Err(invalid(
877 "Note tag does not reference a tag definition",
878 ));
879 }
880 }
881 }
882 .ok_or_else(|| invalid("Note tag has no action type"))?;
883 if !tag_types.insert(action_type) {
884 return Err(invalid("Repeated note tag action type"));
885 }
886 }
887 }
888 space.revisions.insert(
889 rid,
890 Revision {
891 roots: revision.roots,
892 nodes,
893 },
894 );
895 }917 }
896 for space in spaces.values() {918 for space in spaces.values() {
897 for node in space.revisions.values().flat_map(|r| r.nodes.values()) {919 for node in space.revisions.values().flat_map(|r| r.nodes.values()) {
...@@ -931,7 +953,7 @@ impl<'a> Document<'a> {...@@ -931,7 +953,7 @@ impl<'a> Document<'a> {
931impl<'a> Element<'a> {953impl<'a> Element<'a> {
932 pub(crate) fn parse_with(954 pub(crate) fn parse_with(
933 object: &Object<'a>,955 object: &Object<'a>,
934 store: &Store<'a>,956 file_type: crate::FileType,
935 file_data: &mut impl FnMut([u8; 16]) -> Result<&'a [u8]>,957 file_data: &mut impl FnMut([u8; 16]) -> Result<&'a [u8]>,
936 ) -> Result<Self> {958 ) -> Result<Self> {
937 let empty = |kind| Self {959 let empty = |kind| Self {
...@@ -1311,7 +1333,7 @@ impl<'a> Element<'a> {...@@ -1311,7 +1333,7 @@ impl<'a> Element<'a> {
1311 color: f.u32(0x14003466)?,1333 color: f.u32(0x14003466)?,
1312 highlight: f.u32(0x14003465)?,1334 highlight: f.u32(0x14003465)?,
1313 },1335 },
1314 0x20001 if store.header.file_type == crate::FileType::TableOfContents => Kind::Toc {1336 0x20001 if file_type == crate::FileType::TableOfContents => Kind::Toc {
1315 entries: f.refs(0x24001cf6, IdStream::Objects)?,1337 entries: f.refs(0x24001cf6, IdStream::Objects)?,
1316 filename: f.text(0x1c001d6b)?,1338 filename: f.text(0x1c001d6b)?,
1317 identity: f.fixed(0x1c001d94)?,1339 identity: f.fixed(0x1c001d94)?,
crates/onestore/src/files.rs+42-37
...@@ -131,42 +131,47 @@ impl<'a> Store<'a> {...@@ -131,42 +131,47 @@ impl<'a> Store<'a> {
131 offset: 0,131 offset: 0,
132 message: "File-data object is not declared",132 message: "File-data object is not declared",
133 })?;133 })?;
134 let mut c = Cursor {134 payload(
135 bytes: self.chunk_data(chunk)?,135 self.chunk_data(chunk)?,
136 offset: usize::try_from(chunk.offset).unwrap(),136 usize::try_from(chunk.offset).unwrap(),
137 };137 )
138 if c.read::<16>()?138 }
139 != [139}
140 0xe7, 0x16, 0xe3, 0xbd, 0x65, 0x26, 0x11, 0x45, 0xa4, 0xc4, 0x8d, 0x4d, 0x0b, 0x7a,140
141 0x9e, 0xac,141/// The payload of a file-data store object, whose bytes start at file offset `offset`.
142 ]142pub(crate) fn payload(bytes: &[u8], offset: usize) -> Result<&[u8], Error> {
143 {143 let mut c = Cursor { bytes, offset };
144 return Err(Error {144 if c.read::<16>()?
145 offset: c.offset - 16,145 != [
146 message: "Invalid file-data object header",146 0xe7, 0x16, 0xe3, 0xbd, 0x65, 0x26, 0x11, 0x45, 0xa4, 0xc4, 0x8d, 0x4d, 0x0b, 0x7a,
147 });147 0x9e, 0xac,
148 }148 ]
149 let length = usize::try_from(u64::from_le_bytes(c.read()?)).map_err(|_| Error {149 {
150 offset: c.offset - 8,150 return Err(Error {
151 message: "File-data length exceeds address space",151 offset: c.offset - 16,
152 })?;152 message: "Invalid file-data object header",
153 c.take(12)?;153 });
154 let bytes = c.take(length)?;154 }
155 // The object length is aligned; its position in the file need not be.155 let length = usize::try_from(u64::from_le_bytes(c.read()?)).map_err(|_| Error {
156 let padding = (8 - (36 + length) % 8) % 8;156 offset: c.offset - 8,
157 c.take(padding)?;157 message: "File-data length exceeds address space",
158 if c.read::<16>()?158 })?;
159 != [159 c.take(12)?;
160 0x22, 0xa7, 0xfb, 0x71, 0x79, 0x0f, 0x0b, 0x4a, 0xbb, 0x13, 0x89, 0x92, 0x56, 0x42,160 let bytes = c.take(length)?;
161 0x6b, 0x24,161 // The object length is aligned; its position in the file need not be.
162 ]162 let padding = (8 - (36 + length) % 8) % 8;
163 || !c.bytes.is_empty()163 c.take(padding)?;
164 {164 if c.read::<16>()?
165 return Err(Error {165 != [
166 offset: c.offset - 16,166 0x22, 0xa7, 0xfb, 0x71, 0x79, 0x0f, 0x0b, 0x4a, 0xbb, 0x13, 0x89, 0x92, 0x56, 0x42,
167 message: "Invalid file-data object footer or length",167 0x6b, 0x24,
168 });168 ]
169 }169 || !c.bytes.is_empty()
170 Ok(bytes)170 {
171 return Err(Error {
172 offset: c.offset - 16,
173 message: "Invalid file-data object footer or length",
174 });
171 }175 }
176 Ok(bytes)
172}177}
crates/onestore/src/formatting.rs+21-5
...@@ -12,6 +12,9 @@ use std::{...@@ -12,6 +12,9 @@ use std::{
12 sync::Arc,12 sync::Arc,
13};13};
1414
15/// The font sizes OneNote accepts, in points; sizes are written in half points.
16pub const FONT_SIZES: std::ops::RangeInclusive<f32> = 6.0..=130.0;
17
15fn invalid(message: &'static str) -> Error {18fn invalid(message: &'static str) -> Error {
16 Error { offset: 0, message }19 Error { offset: 0, message }
17}20}
...@@ -43,6 +46,8 @@ pub enum TextAttribute {...@@ -43,6 +46,8 @@ pub enum TextAttribute {
43 Hyperlink(bool),46 Hyperlink(bool),
44 /// The run is a hyperlink's visible label or field code rather than surrounding text.47 /// The run is a hyperlink's visible label or field code rather than surrounding text.
45 HyperlinkLabel(bool),48 HyperlinkLabel(bool),
49 /// Only false: equation text is written whole through the page model.
50 Math(bool),
46}51}
4752
48impl TextAttribute {53impl TextAttribute {
...@@ -58,6 +63,10 @@ impl TextAttribute {...@@ -58,6 +63,10 @@ impl TextAttribute {
58 Self::Hidden(value) => boolean(0x08001e16, *value),63 Self::Hidden(value) => boolean(0x08001e16, *value),
59 Self::Hyperlink(value) => boolean(0x08001e14, *value),64 Self::Hyperlink(value) => boolean(0x08001e14, *value),
60 Self::HyperlinkLabel(value) => boolean(0x08001e19, *value),65 Self::HyperlinkLabel(value) => boolean(0x08001e19, *value),
66 Self::Math(true) => {
67 return Err(invalid("Equations are written through the page model"));
68 }
69 Self::Math(false) => boolean(0x08003401, false),
61 Self::Font(font) => {70 Self::Font(font) => {
62 if font.is_empty() || font.contains('\0') {71 if font.is_empty() || font.contains('\0') {
63 return Err(invalid("Font names must be nonempty and contain no NUL"));72 return Err(invalid("Font names must be nonempty and contain no NUL"));
...@@ -66,7 +75,7 @@ impl TextAttribute {...@@ -66,7 +75,7 @@ impl TextAttribute {
66 }75 }
67 Self::FontSize(points) => {76 Self::FontSize(points) => {
68 if !points.is_finite()77 if !points.is_finite()
69 || !(6.0..=130.0).contains(points)78 || !FONT_SIZES.contains(points)
70 || (points * 2.0).fract() != 0.079 || (points * 2.0).fract() != 0.0
71 {80 {
72 return Err(invalid(81 return Err(invalid(
...@@ -99,23 +108,29 @@ pub(crate) fn format_text(...@@ -99,23 +108,29 @@ pub(crate) fn format_text(
99 attributes: &[TextAttribute],108 attributes: &[TextAttribute],
100) -> Result<Vec<u8>, Error> {109) -> Result<Vec<u8>, Error> {
101 crate::active::write(source, space, |active| {110 crate::active::write(source, space, |active| {
102 format_changes(active, object, range, attributes)111 format_changes(active, object, range, attributes, &[])
103 })112 })
104}113}
105114
106/// `format_text` on an active page.115/// `format_text` on an active page, also removing the `cleared` properties from the range's
116/// run styles so the text inherits them.
107pub(crate) fn format_changes(117pub(crate) fn format_changes(
108 active: &ActivePage<'_>,118 active: &ActivePage<'_>,
109 object: ExGuid,119 object: ExGuid,
110 range: Range<u32>,120 range: Range<u32>,
111 attributes: &[TextAttribute],121 attributes: &[TextAttribute],
122 cleared: &[crate::op::TextProperty],
112) -> Result<Changes, Error> {123) -> Result<Changes, Error> {
113 if attributes.is_empty() || range.start > range.end {124 let cleared: Vec<u32> = cleared.iter().map(|property| property.id()).collect();
125 if (attributes.is_empty() && cleared.is_empty()) || range.start > range.end {
114 return Err(invalid(126 return Err(invalid(
115 "Select a text range and at least one formatting attribute",127 "Select a text range and at least one formatting attribute",
116 ));128 ));
117 }129 }
118 let values = attribute_values(attributes)?;130 let values = match attributes {
131 [] => Vec::new(),
132 attributes => attribute_values(attributes)?,
133 };
119 let view = &active.view;134 let view = &active.view;
120 let parents = active.editable_parents(object)?;135 let parents = active.editable_parents(object)?;
121 let node = &view.nodes[&object];136 let node = &view.nodes[&object];
...@@ -217,6 +232,7 @@ pub(crate) fn format_changes(...@@ -217,6 +232,7 @@ pub(crate) fn format_changes(
217 };232 };
218 if selected {233 if selected {
219 style.set(&changes)?;234 style.set(&changes)?;
235 style.remove(&cleared)?;
220 }236 }
221 if let Some(id) = previous237 if let Some(id) = previous
222 .filter(|id| raw.objects[id].data == crate::ObjectData::Properties(&style.bytes))238 .filter(|id| raw.objects[id].data == crate::ObjectData::Properties(&style.bytes))
crates/onestore/src/insertion.rs+67-32
...@@ -3,6 +3,7 @@ use crate::{...@@ -3,6 +3,7 @@ use crate::{
3 active::{ActivePage, Changes},3 active::{ActivePage, Changes},
4 create::{current_timestamps, default_text_style, properties, string},4 create::{current_timestamps, default_text_style, properties, string},
5 document::Kind,5 document::Kind,
6 op::content::{NATIVE_INDENTS, measurement_bytes},
6 write::{PropertyObject, fresh_guid},7 write::{PropertyObject, fresh_guid},
7};8};
8use serde::{Deserialize, Serialize};9use serde::{Deserialize, Serialize};
...@@ -195,6 +196,36 @@ impl Insertion {...@@ -195,6 +196,36 @@ impl Insertion {
195 }196 }
196197
197 pub(crate) fn changes(&self, active: &ActivePage<'_>) -> Result<Changes, Error> {198 pub(crate) fn changes(&self, active: &ActivePage<'_>) -> Result<Changes, Error> {
199 let paragraph = ExGuid {
200 guid: self.guid,
201 n: if matches!(self.placement, Placement::Outline { .. }) {
202 3
203 } else {
204 1
205 },
206 };
207 let changes = self.changes_as(active, self.object(), paragraph, self.text_object())?;
208 let raw = &active.live.revision;
209 if changes
210 .keys()
211 .any(|id| id.guid == self.guid && raw.objects.contains_key(id))
212 {
213 return Err(invalid(
214 "An insertion identity is already present; reconcile the existing edit",
215 ));
216 }
217 Ok(changes)
218 }
219
220 /// `changes` creating the outline or paragraph `object`, the paragraph `paragraph` and its
221 /// text `text` under those identities.
222 pub(crate) fn changes_as(
223 &self,
224 active: &ActivePage<'_>,
225 object: ExGuid,
226 paragraph: ExGuid,
227 text: ExGuid,
228 ) -> Result<Changes, Error> {
198 let [page] = active.pages.as_slice() else {229 let [page] = active.pages.as_slice() else {
199 return Err(invalid("Insertion requires a single active page"));230 return Err(invalid("Insertion requires a single active page"));
200 };231 };
...@@ -247,42 +278,54 @@ impl Insertion {...@@ -247,42 +278,54 @@ impl Insertion {
247 pending.extend(parents.get(&id).into_iter().flatten().copied());278 pending.extend(parents.get(&id).into_iter().flatten().copied());
248 }279 }
249 let modified = current_timestamps()?.0.to_le_bytes();280 let modified = current_timestamps()?.0.to_le_bytes();
250 let paragraph_n = if matches!(self.placement, Placement::Outline { .. }) {281 let (author, default) = (
251 3282 ExGuid {
252 } else {283 guid: self.guid,
253 1284 n: 4,
254 };285 },
255 let table = Arc::new(BTreeMap::from([(0, self.guid)]));286 ExGuid {
256 let reference = |n: u32| n.to_le_bytes().to_vec();287 guid: self.guid,
288 n: 5,
289 },
290 );
291 let mut table = BTreeMap::from([(0, self.guid)]);
292 for id in [object, paragraph, text] {
293 if !table.values().any(|guid| *guid == id.guid) {
294 table.insert(table.len() as u32, id.guid);
295 }
296 }
297 let table = Arc::new(table);
298 let reference =
299 |id: ExGuid| crate::write::compact(id, &table).map(|compact| compact.to_vec());
257 let mut new = BTreeMap::new();300 let mut new = BTreeMap::new();
258 for (n, jcid, values) in [301 for (id, jcid, values) in [
259 (302 (
260 paragraph_n,303 paragraph,
261 0x6000d,304 0x6000d,
262 vec![305 vec![
263 (0x14001d7a, modified.to_vec()),306 (0x14001d7a, modified.to_vec()),
264 (0x14001d09, self.created.to_le_bytes().to_vec()),307 (0x14001d09, self.created.to_le_bytes().to_vec()),
265 (0x0c001c03, vec![1]),308 (0x0c001c03, vec![1]),
266 (0x24001c1f, reference(2)),309 (0x24001c1f, reference(text)?),
267 (0x20001d78, reference(4)),310 (0x20001d78, reference(author)?),
268 (0x20001d79, reference(4)),311 (0x20001d79, reference(author)?),
269 ],312 ],
270 ),313 ),
271 (314 (
272 2,315 text,
273 0x6000e,316 0x6000e,
274 vec![317 vec![
275 (0x14001d7a, modified.to_vec()),318 (0x14001d7a, modified.to_vec()),
276 (0x1c001c22, string(&self.text)),319 (0x1c001c22, string(&self.text)),
277 (0x24001e13, reference(5)),320 (0x24001e13, reference(default)?),
278 (0x10001cfe, 0x409_u16.to_le_bytes().to_vec()),321 (0x10001cfe, 0x409_u16.to_le_bytes().to_vec()),
279 ],322 ],
280 ),323 ),
281 (4, 0x120001, vec![(0x1c001d75, string(&self.author))]),324 (author, 0x120001, vec![(0x1c001d75, string(&self.author))]),
282 (5, 0x12004d, default_text_style()),325 (default, 0x12004d, default_text_style()),
283 ] {326 ] {
284 new.insert(327 new.insert(
285 ExGuid { guid: self.guid, n },328 id,
286 PropertyObject {329 PropertyObject {
287 jcid,330 jcid,
288 bytes: properties(&values)?,331 bytes: properties(&values)?,
...@@ -291,10 +334,6 @@ impl Insertion {...@@ -291,10 +334,6 @@ impl Insertion {
291 );334 );
292 }335 }
293 if !self.formats.is_empty() {336 if !self.formats.is_empty() {
294 let default = ExGuid {
295 guid: self.guid,
296 n: 5,
297 };
298 let mut segments = Vec::new();337 let mut segments = Vec::new();
299 let mut end = 0;338 let mut end = 0;
300 for span in &self.formats {339 for span in &self.formats {
...@@ -329,7 +368,7 @@ impl Insertion {...@@ -329,7 +368,7 @@ impl Insertion {
329 if !segments.iter().any(|(_, id)| *id == default) {368 if !segments.iter().any(|(_, id)| *id == default) {
330 new.remove(&default);369 new.remove(&default);
331 }370 }
332 let target = new.get_mut(&self.text_object()).unwrap();371 let target = new.get_mut(&text).unwrap();
333 let mut references = Vec::new();372 let mut references = Vec::new();
334 let mut ends = Vec::new();373 let mut ends = Vec::new();
335 for (end, id) in segments {374 for (end, id) in segments {
...@@ -340,16 +379,17 @@ impl Insertion {...@@ -340,16 +379,17 @@ impl Insertion {
340 target.set(&[(0x24001e13, &references), (0x1c001e12, &ends)])?;379 target.set(&[(0x24001e13, &references), (0x1c001e12, &ends)])?;
341 }380 }
342 if let Placement::Outline { x, y } = self.placement {381 if let Placement::Outline { x, y } = self.placement {
382 let children = reference(paragraph)?;
343 new.insert(383 new.insert(
344 self.object(),384 object,
345 PropertyObject {385 PropertyObject {
346 jcid: 0x6000c,386 jcid: 0x6000c,
347 global_ids: table,387 global_ids: table,
348 bytes: properties(&[388 bytes: properties(&[
349 (0x14001d7a, modified.to_vec()),389 (0x14001d7a, modified.to_vec()),
350 (0x24001c20, reference(3)),390 (0x24001c20, children),
351 (0x0c001c03, vec![1]),391 (0x0c001c03, vec![1]),
352 (0x1c001c12, vec![1, 0, 0, 0, 0, 0, 0, 0]),392 (0x1c001c12, measurement_bytes(&NATIVE_INDENTS, 4)?),
353 (0x14001c14, (x / 36.0).to_le_bytes().to_vec()),393 (0x14001c14, (x / 36.0).to_le_bytes().to_vec()),
354 (0x14001c15, (y / 36.0).to_le_bytes().to_vec()),394 (0x14001c15, (y / 36.0).to_le_bytes().to_vec()),
355 (0x14001c1b, 13_f32.to_le_bytes().to_vec()),395 (0x14001c1b, 13_f32.to_le_bytes().to_vec()),
...@@ -359,16 +399,11 @@ impl Insertion {...@@ -359,16 +399,11 @@ impl Insertion {
359 );399 );
360 }400 }
361 let raw = &active.live.revision;401 let raw = &active.live.revision;
362 if new.keys().any(|id| raw.objects.contains_key(id)) {
363 return Err(invalid(
364 "An insertion identity is already present; reconcile the existing edit",
365 ));
366 }
367 // Drop the overlay before moving the property bytes it borrows.402 // Drop the overlay before moving the property bytes it borrows.
368 let title = {403 let title = {
369 let mut overlay = BTreeMap::new();404 let mut overlay = BTreeMap::new();
370 let mut parent = view.nodes[&self.parent].clone();405 let mut parent = view.nodes[&self.parent].clone();
371 parent.children.insert(position, self.object());406 parent.children.insert(position, object);
372 overlay.insert(self.parent, parent);407 overlay.insert(self.parent, parent);
373 for (id, object) in &new {408 for (id, object) in &new {
374 overlay.insert(409 overlay.insert(
...@@ -397,7 +432,7 @@ impl Insertion {...@@ -397,7 +432,7 @@ impl Insertion {
397 None => Vec::new(),432 None => Vec::new(),
398 _ => return Err(invalid("The parent has an invalid child list")),433 _ => return Err(invalid("The parent has an invalid child list")),
399 };434 };
400 let child = parent.reference(self.object())?;435 let child = parent.reference(object)?;
401 if position > ids.len() / 4 {436 if position > ids.len() / 4 {
402 return Err(invalid("The parent has an invalid child list"));437 return Err(invalid("The parent has an invalid child list"));
403 }438 }
crates/onestore/src/lib.rs+6-3
...@@ -12,6 +12,7 @@ mod flush;...@@ -12,6 +12,7 @@ mod flush;
12mod formatting;12mod formatting;
13mod insertion;13mod insertion;
14mod objects;14mod objects;
15pub mod op;
15mod outline;16mod outline;
16pub mod page;17pub mod page;
17mod pages;18mod pages;
...@@ -20,6 +21,7 @@ mod properties;...@@ -20,6 +21,7 @@ mod properties;
20#[cfg(feature = "protected")]21#[cfg(feature = "protected")]
21pub mod protected;22pub mod protected;
22mod revisions;23mod revisions;
24mod section;
23mod snapshot;25mod snapshot;
24mod store;26mod store;
25mod toc;27mod toc;
...@@ -27,8 +29,8 @@ mod tree;...@@ -27,8 +29,8 @@ mod tree;
27mod write;29mod write;
2830
29pub use commit::{31pub use commit::{
30 CommitError, CommitIo, CommitState, PreparedEdit, commit_property_bytes, commit_text,32 CommitError, CommitIo, CommitState, PreparedEdit, Stamp, Transaction, commit_property_bytes,
31 confirm_snapshot, place,33 commit_text, confirm_snapshot, place,
32};34};
33#[cfg(any(unix, windows))]35#[cfg(any(unix, windows))]
34pub use commit::{36pub use commit::{
...@@ -38,7 +40,7 @@ pub use commit::{...@@ -38,7 +40,7 @@ pub use commit::{
38pub use create::{create_section, create_table_of_contents};40pub use create::{create_section, create_table_of_contents};
39pub use edit::replace_text;41pub use edit::replace_text;
40pub use files::FileDataReference;42pub use files::FileDataReference;
41pub use formatting::TextAttribute;43pub use formatting::{FONT_SIZES, TextAttribute};
42pub use insertion::Insertion;44pub use insertion::Insertion;
43pub use objects::{Object, ObjectData, ObjectReferences, ResolvedRevision};45pub use objects::{Object, ObjectData, ObjectReferences, ResolvedRevision};
44pub use outline::OutlineEdit;46pub use outline::OutlineEdit;
...@@ -46,6 +48,7 @@ pub use pages::{PageCreation, PageEdit, PagePosition};...@@ -46,6 +48,7 @@ pub use pages::{PageCreation, PageEdit, PagePosition};
46pub use paragraph::{ParagraphJoin, ParagraphSplit};48pub use paragraph::{ParagraphJoin, ParagraphSplit};
47pub use properties::{IdStream, Property, PropertySets, Value};49pub use properties::{IdStream, Property, PropertySets, Value};
48pub use revisions::{ExGuid, ObjectSpace, Revision, RevisionIndex};50pub use revisions::{ExGuid, ObjectSpace, Revision, RevisionIndex};
51pub use section::{Arena, Section};
49pub use snapshot::{read_snapshot, read_storage_snapshot};52pub use snapshot::{read_snapshot, read_storage_snapshot};
50pub use store::{Chunk, Error, FileType, Header, Node, NodeList, Reference, Store};53pub use store::{Chunk, Error, FileType, Header, Node, NodeList, Reference, Store};
51pub use toc::TocEdit;54pub use toc::TocEdit;
crates/onestore/src/objects.rs+2-2
...@@ -19,7 +19,7 @@ pub enum ObjectData<'a> {...@@ -19,7 +19,7 @@ pub enum ObjectData<'a> {
19 },19 },
20}20}
2121
22#[derive(Debug)]22#[derive(Debug, Clone)]
23pub struct Object<'a> {23pub struct Object<'a> {
24 pub jcid: u32,24 pub jcid: u32,
25 pub reference_count: u32,25 pub reference_count: u32,
...@@ -27,7 +27,7 @@ pub struct Object<'a> {...@@ -27,7 +27,7 @@ pub struct Object<'a> {
27 pub global_ids: Arc<GlobalIds>,27 pub global_ids: Arc<GlobalIds>,
28}28}
2929
30#[derive(Debug)]30#[derive(Debug, Clone)]
31pub struct ResolvedRevision<'a> {31pub struct ResolvedRevision<'a> {
32 pub roots: BTreeMap<u32, ExGuid>,32 pub roots: BTreeMap<u32, ExGuid>,
33 pub objects: BTreeMap<ExGuid, Object<'a>>,33 pub objects: BTreeMap<ExGuid, Object<'a>>,
crates/onestore/src/op/apply.rs created+1188
...@@ -0,0 +1,1188 @@
1//! `Section::apply`: each op checked against the page it names, then written through the
2//! typed writers onto the section's copy of that page.
3
4use super::{
5 Edit, Op, OpError, PageOp, SectionOp, TableEdit, TextProperty,
6 content::{self, AttachmentIds},
7 levels,
8 lower::{ParagraphFields, format_edits, paragraph_fields},
9 properties,
10 table::{self, Structure},
11};
12use crate::{
13 Error, ExGuid, Insertion, OutlineEdit, ParagraphJoin, ParagraphSplit, Section, TextAttribute,
14 TreeEdit,
15 active::{ActivePage, Changes},
16 document::Kind,
17 page::{
18 Attachment, Image, Ink, Page, PageObject, PageParagraph, Paragraph, ParagraphContent,
19 TableCell, TableColumn,
20 },
21};
22use std::{
23 collections::{BTreeMap, BTreeSet},
24 ops::Range,
25};
26
27fn invalid(message: &'static str) -> Error {
28 Error { offset: 0, message }
29}
30
31impl<'a> Section<'a> {
32 /// Applies an edit whole or not at all, `author` naming who made it. Its modification
33 /// times are `edit.at`.
34 pub fn apply(&mut self, author: &str, edit: &Edit) -> Result<(), OpError> {
35 if author.contains('\0') {
36 return Err(OpError::Unsupported("Choose an author name without NUL"));
37 }
38 let undo = edit.ops.len() > 1 || edit.ops.iter().any(stores_more_than_once);
39 crate::create::at(edit.at, || {
40 self.atomically(undo, |section| {
41 for op in &edit.ops {
42 match op {
43 Op::Page { space, op } => Writer {
44 section: &mut *section,
45 space: *space,
46 author,
47 }
48 .apply(op)?,
49 Op::Section(op) => section.apply_section(author, op)?,
50 }
51 }
52 Ok(())
53 })
54 })
55 .map_err(|error| self.classify(error))
56 }
57
58 /// Lowers `after` against the page `space` holds and applies the result as one edit;
59 /// returns the ops, which reach `after` where the writers can store it.
60 pub fn apply_page(
61 &mut self,
62 author: &str,
63 at: u64,
64 space: ExGuid,
65 after: &Page,
66 ) -> Result<Vec<PageOp>, OpError> {
67 let before = self.page(space).map_err(|error| self.classify(Failure::Rejected(error)))?;
68 let ops = super::lower_page(&before, after)
69 .map_err(|error| OpError::Unsupported(error.message))?;
70 let edit = Edit {
71 at,
72 ops: ops
73 .iter()
74 .map(|op| Op::Page {
75 space,
76 op: op.clone(),
77 })
78 .collect(),
79 };
80 self.apply(author, &edit)?;
81 Ok(ops)
82 }
83
84 fn classify(&self, failure: Failure) -> OpError {
85 match failure {
86 Failure::Refused(error) => error,
87 Failure::Rejected(error) if self.broken() => OpError::Failed(error),
88 Failure::Rejected(error) => OpError::Unsupported(error.message),
89 }
90 }
91
92 fn apply_section(&mut self, author: &str, op: &SectionOp) -> Result<(), Failure> {
93 match op {
94 SectionOp::Create(creation) => {
95 let (root, roots, objects) = creation.changes(self)?;
96 self.apply_changes(self.root(), &[], root)?;
97 self.create_space(creation.space(), roots, objects)?;
98 }
99 SectionOp::Import { creation, page } => {
100 self.apply_section(author, &SectionOp::Create(creation.clone()))?;
101 let space = creation.space();
102 let before = self.page(space)?;
103 let mut after = page.clone();
104 // The created page's title stays; the imported page brings the rest.
105 after.objects.retain(|object| !matches!(object, PageObject::Title(_)));
106 for object in before.objects.iter().rev() {
107 if let PageObject::Title(_) = object {
108 after.objects.insert(0, object.clone());
109 }
110 }
111 after.title = before.title.clone();
112 after.identity = before.identity;
113 after.created = before.created;
114 after.margin_origin = before.margin_origin;
115 for op in super::lower_page(&before, &after)? {
116 Writer {
117 section: self,
118 space,
119 author,
120 }
121 .apply(&op)?;
122 }
123 }
124 SectionOp::Pages(edits) => {
125 for (space, changes) in crate::pages::section_changes(self, edits, &[])? {
126 self.apply_changes(space, &[], changes)?;
127 }
128 }
129 SectionOp::Delete(pages) => {
130 for (space, changes) in crate::pages::section_changes(self, &[], pages)? {
131 self.apply_changes(space, &[], changes)?;
132 }
133 }
134 }
135 Ok(())
136 }
137}
138
139/// Whether an op may store more than one revision of a page, so its failure part way needs
140/// the page as it was.
141fn stores_more_than_once(op: &Op) -> bool {
142 match op {
143 Op::Section(_) => true,
144 Op::Page { op, .. } => matches!(
145 op,
146 PageOp::Link { .. }
147 | PageOp::Insert { .. }
148 | PageOp::Move { .. }
149 | PageOp::Add { .. }
150 | PageOp::Table {
151 edit: TableEdit::Rows { .. } | TableEdit::Column { .. },
152 ..
153 }
154 ),
155 }
156}
157
158/// Why an op was not applied: a precondition it names, or what a writer refused.
159enum Failure {
160 Refused(OpError),
161 Rejected(Error),
162}
163
164impl From<Error> for Failure {
165 fn from(error: Error) -> Self {
166 Self::Rejected(error)
167 }
168}
169
170impl From<OpError> for Failure {
171 fn from(error: OpError) -> Self {
172 Self::Refused(error)
173 }
174}
175
176/// Writes ops onto one page of a section.
177struct Writer<'s, 'a> {
178 section: &'s mut Section<'a>,
179 space: ExGuid,
180 author: &'s str,
181}
182
183impl<'a> Writer<'_, 'a> {
184 fn page(&mut self) -> Result<&ActivePage<'a>, Error> {
185 self.section.active(self.space)
186 }
187
188 fn write(
189 &mut self,
190 changes: impl FnOnce(&ActivePage<'a>) -> Result<Changes, Error>,
191 ) -> Result<(), Failure> {
192 self.write_with(&[], changes)
193 }
194
195 fn write_with(
196 &mut self,
197 payloads: &[([u8; 16], &[u8])],
198 changes: impl FnOnce(&ActivePage<'a>) -> Result<Changes, Error>,
199 ) -> Result<(), Failure> {
200 let changes = changes(self.page()?)?;
201 self.section.apply_changes(self.space, payloads, changes)?;
202 Ok(())
203 }
204
205 /// Requires `id` reachable on the page.
206 fn target(&mut self, id: ExGuid) -> Result<(), Failure> {
207 let page = self.page()?;
208 if page.live.is_reachable(id) && page.view.nodes.contains_key(&id) {
209 Ok(())
210 } else {
211 Err(OpError::TargetUnavailable(id).into())
212 }
213 }
214
215 /// Requires `id` free for a new object: not reachable on the page.
216 fn free(&mut self, id: ExGuid) -> Result<(), Failure> {
217 if id.guid == [0; 16] || self.page()?.live.is_reachable(id) {
218 Err(OpError::DuplicateIdentity(id).into())
219 } else {
220 Ok(())
221 }
222 }
223
224 /// The only parent of `id` on the page.
225 fn parent(&mut self, id: ExGuid) -> Result<ExGuid, Failure> {
226 match self.page()?.parents.get(&id).map(Vec::as_slice) {
227 Some([parent]) => Ok(*parent),
228 _ => Err(OpError::StructureChanged("Page content must have one parent").into()),
229 }
230 }
231
232 /// The container `id` lies in, outline groups passed over.
233 fn container(&mut self, id: ExGuid) -> Result<ExGuid, Failure> {
234 let mut at = self.parent(id)?;
235 while matches!(self.page()?.view.nodes[&at].kind, Kind::OutlineGroup) {
236 at = self.parent(at)?;
237 }
238 Ok(at)
239 }
240
241 /// The text object a paragraph holds.
242 fn text_of(&mut self, paragraph: ExGuid) -> Result<ExGuid, Failure> {
243 let page = self.page()?;
244 let node = &page.view.nodes[&paragraph];
245 match node.content.as_slice() {
246 [text] if matches!(page.view.nodes[text].kind, Kind::RichText { .. }) => Ok(*text),
247 _ => Err(OpError::Unsupported("Select a paragraph holding text").into()),
248 }
249 }
250
251 /// The absolute outline level of paragraph `id`; zero for an outline or cell.
252 fn level(&mut self, id: ExGuid) -> Result<u32, Failure> {
253 if !matches!(self.page()?.view.nodes[&id].kind, Kind::Paragraph { .. }) {
254 return Ok(0);
255 }
256 let container = self.container(id)?;
257 let depth = self.depth(container, id)?;
258 Ok(self.level(container)? + depth)
259 }
260
261 fn depth(&mut self, container: ExGuid, id: ExGuid) -> Result<u32, Failure> {
262 levels::stored_depths(&self.page()?.view, container)
263 .into_iter()
264 .find(|(child, _)| *child == id)
265 .map(|(_, depth)| depth)
266 .ok_or_else(|| OpError::StructureChanged("A paragraph left its container").into())
267 }
268
269 /// Stores `container`'s children at the depths they have, `targets` overriding some.
270 fn regroup(&mut self, container: ExGuid, targets: &[(ExGuid, u32)]) -> Result<(), Failure> {
271 let page = self.page()?;
272 let current = levels::stored_depths(&page.view, container);
273 let is_cell = matches!(page.view.nodes[&container].kind, Kind::Cell { .. });
274 let (children, depths): (Vec<ExGuid>, Vec<u32>) = current
275 .iter()
276 .map(|(child, depth)| {
277 let target = targets
278 .iter()
279 .find(|(id, _)| id == child)
280 .map_or(*depth, |(_, depth)| *depth);
281 (*child, target)
282 })
283 .unzip();
284 if current.iter().map(|(_, depth)| *depth).eq(depths.iter().copied()) {
285 return Ok(());
286 }
287 let (level, runs) = levels::runs(&children, &depths, is_cell)?;
288 self.write(|page| levels::regroup_changes(page, container, level, &runs))
289 }
290
291 fn apply(&mut self, op: &PageOp) -> Result<(), Failure> {
292 match op {
293 PageOp::Text { text, range, with } => {
294 self.target(*text)?;
295 if with.contains(['\0', '\n', '\u{fffc}']) {
296 return Err(OpError::Unsupported("Use ordinary paragraph text").into());
297 }
298 self.write(|page| crate::edit::text_changes(page, *text, range.clone(), with))
299 }
300 PageOp::Format {
301 text,
302 range,
303 set,
304 clear,
305 } => {
306 self.target(*text)?;
307 self.write(|page| {
308 crate::formatting::format_changes(page, *text, range.clone(), set, clear)
309 })
310 }
311 PageOp::Link {
312 text,
313 range,
314 target,
315 } => {
316 self.target(*text)?;
317 let page = self.page()?;
318 let current = crate::page::text_of(&page.view, &page.parents, *text)?;
319 for op in link_ops(&current, *text, range.clone(), target.as_deref())? {
320 self.apply(&op)?;
321 }
322 Ok(())
323 }
324 PageOp::Equation { text, math } => {
325 self.target(*text)?;
326 self.write(|page| content::equation_changes(page, *text, math))
327 }
328 PageOp::Insert {
329 container,
330 before,
331 paragraphs,
332 } => {
333 self.target(*container)?;
334 self.insert(*container, *before, paragraphs)
335 }
336 PageOp::Split {
337 text,
338 at,
339 paragraph,
340 right,
341 lists,
342 } => {
343 self.target(*text)?;
344 for id in [*paragraph, *right].iter().chain(lists) {
345 self.free(*id)?;
346 }
347 let split = ParagraphSplit::new(*text, *at, self.author)?;
348 self.write(|page| split.changes_as(page, *paragraph, *right, lists))
349 }
350 PageOp::Join { left, right } => {
351 self.target(*left)?;
352 self.target(*right)?;
353 let join = ParagraphJoin::new(*left, *right, self.author)?;
354 self.write(|page| join.changes(page))
355 }
356 PageOp::Move {
357 object,
358 parent,
359 before,
360 } => {
361 self.target(*object)?;
362 self.move_to(*object, *parent, *before)
363 }
364 PageOp::Delete { object } => {
365 self.target(*object)?;
366 let page = self.page()?;
367 if matches!(page.view.nodes[object].kind, Kind::Paragraph { .. }) {
368 let container = self.container(*object)?;
369 if matches!(self.page()?.view.nodes[&container].kind, Kind::Cell { .. })
370 && levels::flat_children(&self.page()?.view, container) == [*object]
371 {
372 return Err(OpError::Unsupported(
373 "A table cell keeps a paragraph; insert its replacement first",
374 )
375 .into());
376 }
377 }
378 let edit = TreeEdit::delete(*object, self.author)?;
379 self.write(|page| edit.changes(page))
380 }
381 PageOp::Level { paragraph, level } => {
382 self.target(*paragraph)?;
383 let container = self.container(*paragraph)?;
384 let base = self.level(container)?;
385 let depth = level
386 .checked_sub(base)
387 .filter(|depth| *depth > 0)
388 .ok_or(OpError::Unsupported(
389 "A paragraph lies deeper than its parent",
390 ))?;
391 self.regroup(container, &[(*paragraph, depth)])
392 }
393 PageOp::Outline { object, edit } => {
394 self.target(*object)?;
395 self.write(|page| edit.changes(page, *object))
396 }
397 PageOp::Paragraph {
398 paragraph,
399 alignment,
400 rtl,
401 space_before,
402 space_after,
403 line_spacing,
404 language,
405 } => {
406 self.target(*paragraph)?;
407 let text = self.text_of(*paragraph)?;
408 let values = ParagraphFields {
409 alignment: *alignment,
410 rtl: *rtl,
411 space_before: *space_before,
412 space_after: *space_after,
413 line_spacing: *line_spacing,
414 language: *language,
415 }
416 .values()?;
417 if values.is_empty() {
418 return Ok(());
419 }
420 self.write(|page| properties::paragraph_format_changes(page, text, &values))
421 }
422 PageOp::Style {
423 paragraph,
424 style,
425 definition,
426 } => {
427 self.target(*paragraph)?;
428 let text = self.text_of(*paragraph)?;
429 self.write(|page| properties::style_changes(page, text, *style, Some(definition)))
430 }
431 PageOp::List { paragraph, lists } => {
432 self.target(*paragraph)?;
433 let page = self.page()?;
434 for (id, _) in lists {
435 if page
436 .parents
437 .get(id)
438 .is_some_and(|owners| owners.iter().any(|owner| owner != paragraph))
439 {
440 return Err(OpError::Unsupported("A list node belongs to one paragraph").into());
441 }
442 }
443 let nodes = lists
444 .iter()
445 .map(|(id, definition)| Ok((*id, properties::list_values(definition)?)))
446 .collect::<Result<Vec<_>, Error>>()?;
447 self.write(|page| properties::list_changes(page, *paragraph, &nodes))
448 }
449 PageOp::Tags {
450 target,
451 tags,
452 definitions,
453 } => {
454 self.target(*target)?;
455 let mut entries = Vec::new();
456 for tag in tags {
457 let definition = tag
458 .definition
459 .ok_or(OpError::Unsupported("A note tag names its definition"))?;
460 let model = definitions
461 .iter()
462 .find(|(id, _)| *id == definition)
463 .map(|(_, definition)| definition);
464 entries.push((definition, tag, model));
465 }
466 self.write(|page| properties::tag_changes(page, *target, &entries))
467 }
468 PageOp::Add { object, before } => self.add(object, *before),
469 PageOp::Picture {
470 picture,
471 layout,
472 alt,
473 } => {
474 self.target(*picture)?;
475 let page = self.page()?;
476 let node = &page.view.nodes[picture];
477 if !matches!(node.kind, Kind::Image { .. }) {
478 return Err(OpError::Unsupported("Select a picture").into());
479 }
480 let stored = Image::read(&page.view, *picture, node)?;
481 self.write(|page| {
482 content::picture_edit_changes(
483 page,
484 *picture,
485 (&stored.layout, &stored.alt),
486 layout,
487 alt,
488 )
489 })
490 }
491 PageOp::Attachment {
492 attachment,
493 filename,
494 source_path,
495 size,
496 } => {
497 self.target(*attachment)?;
498 let page = self.page()?;
499 let node = &page.view.nodes[attachment];
500 if !matches!(node.kind, Kind::Attachment { .. }) {
501 return Err(OpError::Unsupported("Select an attachment").into());
502 }
503 let stored = Attachment::read(&page.view, *attachment, node)?;
504 let edited = Attachment {
505 filename: filename.clone(),
506 source_path: source_path.clone(),
507 size: *size,
508 ..stored.clone()
509 };
510 self.write(|page| content::attachment_edit_changes(page, &stored, &edited))
511 }
512 PageOp::Strokes { ink, add, remove } => {
513 self.target(*ink)?;
514 let page = self.page()?;
515 let node = &page.view.nodes[ink];
516 if !matches!(node.kind, Kind::Ink { .. }) {
517 return Err(OpError::Unsupported("Select ink").into());
518 }
519 let stored = Ink::read(&page.view, *ink, node)?;
520 if !stored.groups.is_empty() {
521 return Err(OpError::Unsupported("Grouped ink keeps its strokes").into());
522 }
523 for id in remove {
524 if !stored.strokes.iter().any(|stroke| stroke.id == *id) {
525 return Err(OpError::TargetUnavailable(*id).into());
526 }
527 }
528 for stroke in add {
529 self.free(stroke.id)?;
530 }
531 let kept: Vec<ExGuid> = stored
532 .strokes
533 .iter()
534 .map(|stroke| stroke.id)
535 .filter(|id| !remove.contains(id))
536 .collect();
537 let added: Vec<_> = add.iter().map(|stroke| (stroke.id, stroke)).collect();
538 self.write(|page| content::strokes_changes(page, *ink, &kept, &added))
539 }
540 PageOp::Table { table, edit } => {
541 self.target(*table)?;
542 self.table(*table, edit)
543 }
544 }
545 }
546
547 /// Moves `object` before `before` in `parent`, or among the page's children.
548 fn move_to(
549 &mut self,
550 object: ExGuid,
551 parent: Option<ExGuid>,
552 before: Option<ExGuid>,
553 ) -> Result<(), Failure> {
554 let Some(parent) = parent else {
555 let [page] = self.page()?.pages[..] else {
556 return Err(OpError::Unsupported("Choose a page space holding one page").into());
557 };
558 if let Some(before) = before
559 && self.parent(before)? != page
560 {
561 return Err(OpError::StructureChanged("The anchor is not on the page").into());
562 }
563 let edit = TreeEdit::move_to(object, page, before, self.author)?;
564 return self.write(|page| edit.changes(page));
565 };
566 self.target(parent)?;
567 let mut at = parent;
568 loop {
569 if at == object {
570 return Err(OpError::StructureChanged("A subtree cannot move inside itself").into());
571 }
572 match self.page()?.parents.get(&at).map(Vec::as_slice) {
573 Some([up]) => at = *up,
574 _ => break,
575 }
576 }
577 let holder = match before {
578 Some(before) => {
579 self.target(before)?;
580 if self.container(before)? != parent {
581 return Err(
582 OpError::StructureChanged("The anchor is no child of the container").into(),
583 );
584 }
585 self.parent(before)?
586 }
587 None => parent,
588 };
589 let level = self.level(object)?;
590 let edit = TreeEdit::move_to(object, holder, before, self.author)?;
591 self.write(|page| edit.changes(page))?;
592 if matches!(self.page()?.view.nodes[&parent].kind, Kind::Cell { .. }) {
593 return Ok(());
594 }
595 let base = self.level(parent)?;
596 let depth = if level > base { level - base } else { 1 };
597 self.regroup(parent, &[(object, depth)])
598 }
599
600 /// Inserts paragraphs into `container` before `before` as `PageOp::Insert` does.
601 fn insert(
602 &mut self,
603 container: ExGuid,
604 before: Option<ExGuid>,
605 paragraphs: &[PageParagraph],
606 ) -> Result<(), Failure> {
607 if paragraphs.is_empty() {
608 return Err(OpError::Unsupported("Insert at least one paragraph").into());
609 }
610 let holder = match before {
611 Some(before) => {
612 self.target(before)?;
613 if self.container(before)? != container {
614 return Err(
615 OpError::StructureChanged("The anchor is no child of the container").into(),
616 );
617 }
618 self.parent(before)?
619 }
620 None => container,
621 };
622 let mut seen = BTreeSet::new();
623 for paragraph in paragraphs {
624 bare(paragraph)?;
625 for id in super::model::identities(std::slice::from_ref(paragraph)) {
626 if !seen.insert(id) {
627 return Err(OpError::DuplicateIdentity(id).into());
628 }
629 self.free(id)?;
630 }
631 if let Some(parent) = paragraph.parent
632 && parent != container
633 && !paragraphs.iter().any(|p| p.id == parent)
634 {
635 return Err(OpError::StructureChanged(
636 "An inserted paragraph's parent precedes it among them",
637 )
638 .into());
639 }
640 }
641 let base = self.level(container)?;
642 let mut targets: BTreeMap<ExGuid, Vec<(ExGuid, u32)>> = BTreeMap::new();
643 for paragraph in paragraphs {
644 let (parent, anchor, holder) = match paragraph.parent.filter(|p| *p != container) {
645 Some(parent) => (parent, None, parent),
646 None => (container, before, holder),
647 };
648 let parent_level = if parent == container {
649 base
650 } else {
651 paragraphs.iter().find(|p| p.id == parent).unwrap().level
652 };
653 let depth = paragraph
654 .level
655 .checked_sub(parent_level)
656 .filter(|depth| *depth > 0)
657 .ok_or(OpError::Unsupported("A paragraph lies deeper than its parent"))?;
658 targets.entry(parent).or_default().push((paragraph.id, depth));
659 self.paragraph(holder, anchor, paragraph, None)?;
660 }
661 for (parent, targets) in targets {
662 if !matches!(self.page()?.view.nodes[&parent].kind, Kind::Cell { .. }) {
663 self.regroup(parent, &targets)?;
664 }
665 }
666 Ok(())
667 }
668
669 /// Creates one paragraph with its content in `holder` before `anchor`; with `outline`,
670 /// as the first paragraph of that new outline at its position.
671 fn paragraph(
672 &mut self,
673 holder: ExGuid,
674 anchor: Option<ExGuid>,
675 paragraph: &PageParagraph,
676 outline: Option<(ExGuid, f32, f32)>,
677 ) -> Result<(), Failure> {
678 let (text, text_id) = match &paragraph.content {
679 ParagraphContent::Text(text) => (text.text.text(), text.id),
680 _ => (
681 "",
682 ExGuid {
683 guid: crate::write::fresh_guid()?,
684 n: 1,
685 },
686 ),
687 };
688 let equation = paragraph
689 .text()
690 .is_some_and(|text| crate::page::Math::is_equation(&text.text));
691 let insertion = match outline {
692 Some((_, x, y)) => {
693 let page = self.page()?.pages.first().copied().unwrap_or_default();
694 Insertion::outline(page, x, y, text, self.author)?
695 }
696 None => Insertion::paragraph(holder, anchor, text, self.author)?,
697 };
698 let object = outline.map_or(paragraph.id, |(id, _, _)| id);
699 self.write(|page| insertion.changes_as(page, object, paragraph.id, text_id))?;
700 match &paragraph.content {
701 ParagraphContent::Text(target) => {
702 if equation {
703 return self.write(|page| content::equation_changes(page, text_id, &target.text));
704 }
705 let page = self.page()?;
706 let stored = crate::page::text_of(&page.view, &page.parents, text_id)?;
707 let values = paragraph_fields(&stored, &target.text)?.values()?;
708 if !values.is_empty() {
709 self.write(|page| properties::paragraph_format_changes(page, text_id, &values))?;
710 }
711 let page = self.page()?;
712 let stored = crate::page::text_of(&page.view, &page.parents, text_id)?;
713 for (range, set, clear) in format_edits(&stored, &target.text, true)? {
714 self.write(|page| {
715 crate::formatting::format_changes(page, text_id, range, &set, &clear)
716 })?;
717 }
718 Ok(())
719 }
720 ParagraphContent::Table(table) => {
721 super::table::validate_table(table)?;
722 let structure = Structure {
723 rows: table
724 .rows
725 .iter()
726 .map(|row| (row.id, row.cells.iter().map(|cell| cell.id).collect()))
727 .collect(),
728 new_rows: table.rows.iter().map(|row| row.id).collect(),
729 new_cells: table
730 .rows
731 .iter()
732 .flat_map(|row| &row.cells)
733 .map(|cell| (cell.id, (table::cell_indents(&cell.indents, None), cell.shading)))
734 .collect(),
735 columns: &table.columns,
736 borders: table.borders,
737 };
738 self.write(|page| table::table_changes(page, table.id, Some(paragraph.id), &structure))?;
739 for cell in table.rows.iter().flat_map(|row| &row.cells) {
740 self.cell(cell)?;
741 }
742 Ok(())
743 }
744 ParagraphContent::Image(image) => {
745 if image.layout.x.is_some() || image.layout.y.is_some() {
746 return Err(OpError::Unsupported("A paragraph picture has no position of its own").into());
747 }
748 self.picture(image, Some(paragraph.id))
749 }
750 ParagraphContent::Attachment(attachment) => {
751 let Some(bytes) = &attachment.bytes else {
752 return Err(OpError::Unsupported("A new attachment needs its payload").into());
753 };
754 let ids = AttachmentIds {
755 object: attachment.id,
756 file: fresh()?,
757 payload: crate::write::fresh_guid()?,
758 preview: match &attachment.preview {
759 Some(_) => Some((crate::write::fresh_guid()?, fresh()?)),
760 None => None,
761 },
762 };
763 let mut payloads: Vec<([u8; 16], &[u8])> = vec![(ids.payload, bytes)];
764 if let (Some((payload, _)), Some(icon)) = (ids.preview, &attachment.preview) {
765 payloads.push((payload, icon));
766 }
767 self.write_with(&payloads, |page| {
768 content::attachment_changes(page, attachment, &ids, paragraph.id)
769 })
770 }
771 ParagraphContent::Ink(ink) => self.ink(ink, Some(paragraph.id)),
772 ParagraphContent::Unsupported(_) => {
773 Err(OpError::Unsupported("Unsupported content cannot be inserted").into())
774 }
775 }
776 }
777
778 /// Fills a new cell with its paragraphs.
779 fn cell(&mut self, cell: &TableCell) -> Result<(), Failure> {
780 if cell.paragraphs.is_empty() {
781 return Err(OpError::Unsupported("A new table cell needs a paragraph").into());
782 }
783 self.insert(cell.id, None, &cell.paragraphs)
784 }
785
786 fn picture(&mut self, image: &Image, holder: Option<ExGuid>) -> Result<(), Failure> {
787 let Some(bytes) = &image.bytes else {
788 return Err(OpError::Unsupported("A new picture needs its payload").into());
789 };
790 let (file, payload) = (fresh()?, crate::write::fresh_guid()?);
791 let bytes: &[u8] = bytes;
792 self.write_with(&[(payload, bytes)], |page| {
793 content::picture_changes(page, image, image.id, file, payload, holder)
794 })
795 }
796
797 fn ink(&mut self, ink: &Ink, holder: Option<ExGuid>) -> Result<(), Failure> {
798 for stroke in &ink.strokes {
799 self.free(stroke.id)?;
800 }
801 let data = fresh()?;
802 let strokes: Vec<_> = ink.strokes.iter().map(|stroke| (stroke.id, stroke)).collect();
803 self.write(|page| content::ink_changes(page, ink, ink.id, data, &strokes, holder))
804 }
805
806 /// Adds a page object on top, then moves it before `before`.
807 fn add(&mut self, object: &PageObject, before: Option<ExGuid>) -> Result<(), Failure> {
808 self.free(object.id())?;
809 match object {
810 PageObject::Outline(outline) => {
811 let (Some(x), Some(y)) = (outline.layout.x, outline.layout.y) else {
812 return Err(OpError::Unsupported("A new outline needs a position").into());
813 };
814 let [first, rest @ ..] = outline.paragraphs.as_slice() else {
815 return Err(OpError::Unsupported("A new outline needs a paragraph").into());
816 };
817 for paragraph in &outline.paragraphs {
818 bare(paragraph)?;
819 for id in super::model::identities(std::slice::from_ref(paragraph)) {
820 self.free(id)?;
821 }
822 }
823 if first.parent.is_some() || first.level != 1 {
824 return Err(OpError::Unsupported("An outline starts at its first level").into());
825 }
826 self.paragraph(outline.id, None, first, Some((outline.id, x, y)))?;
827 // Later paragraphs follow the first, their levels as the outline gives them.
828 if !rest.is_empty() {
829 let mut targets = vec![(first.id, 1)];
830 for paragraph in rest {
831 let parent = paragraph.parent.unwrap_or(outline.id);
832 let parent_level = rest
833 .iter()
834 .chain([first])
835 .find(|p| p.id == parent)
836 .map_or(0, |p| p.level);
837 let depth = paragraph
838 .level
839 .checked_sub(parent_level)
840 .filter(|depth| *depth > 0)
841 .ok_or(OpError::Unsupported("A paragraph lies deeper than its parent"))?;
842 self.paragraph(parent, None, paragraph, None)?;
843 if parent == outline.id {
844 targets.push((paragraph.id, depth));
845 } else {
846 self.regroup(parent, &[(paragraph.id, depth)])?;
847 }
848 }
849 self.regroup(outline.id, &targets)?;
850 }
851 // A new outline is 468 points wide unless it says otherwise.
852 if let Some(points) = outline.layout.max_width
853 && (points, outline.layout.width_set_by_user) != (468.0, None)
854 {
855 let edit = OutlineEdit::Width {
856 points,
857 user_set: outline.layout.width_set_by_user == Some(true),
858 };
859 self.write(|page| edit.changes(page, outline.id))?;
860 }
861 if !outline.indents.is_empty() && outline.indents != content::NATIVE_INDENTS {
862 let indents = content::measurement_bytes(&outline.indents, 4)?;
863 let id = outline.id;
864 self.write(|page| {
865 let mut node =
866 crate::write::PropertyObject::from_object(&page.live.revision.objects[&id])?;
867 node.set(&[(0x1c001c12, &indents)])?;
868 Ok(BTreeMap::from([(id, node)]))
869 })?;
870 }
871 }
872 PageObject::Image(image) => {
873 if image.layout.x.is_none() || image.layout.y.is_none() {
874 return Err(OpError::Unsupported("A new page-level picture needs a position").into());
875 }
876 self.picture(image, None)?;
877 }
878 PageObject::Ink(ink) => self.ink(ink, None)?,
879 PageObject::Title(_) | PageObject::Unsupported(_) => {
880 return Err(OpError::Unsupported("Titles and unsupported objects cannot be added").into());
881 }
882 }
883 if before.is_some() {
884 self.move_to(object.id(), None, before)?;
885 }
886 Ok(())
887 }
888
889 fn table(&mut self, id: ExGuid, edit: &TableEdit) -> Result<(), Failure> {
890 let page = self.page()?;
891 let node = &page.view.nodes[&id];
892 let Kind::Table {
893 widths,
894 locked,
895 borders,
896 ..
897 } = &node.kind
898 else {
899 return Err(OpError::Unsupported("Select a table").into());
900 };
901 let mut columns: Vec<TableColumn> = widths
902 .iter()
903 .enumerate()
904 .map(|(i, width)| TableColumn {
905 width: *width,
906 locked: locked.get(i).copied().unwrap_or(false),
907 })
908 .collect();
909 let mut rows: Vec<(ExGuid, Vec<ExGuid>)> = node
910 .children
911 .iter()
912 .map(|row| (*row, page.view.nodes[row].children.clone()))
913 .collect();
914 let mut borders = *borders;
915 let template = rows
916 .first()
917 .and_then(|(_, cells)| cells.first())
918 .and_then(|cell| match &page.view.nodes[cell].kind {
919 Kind::Cell { indents, .. } => Some(indents.clone()),
920 _ => None,
921 });
922 let mut new_rows = BTreeSet::new();
923 let mut new_cells: BTreeMap<ExGuid, (Vec<f32>, Option<u32>)> = BTreeMap::new();
924 let mut filled: Vec<&TableCell> = Vec::new();
925 let new_cell = |cell: &TableCell| {
926 (
927 table::cell_indents(&cell.indents, template.as_deref()).to_vec(),
928 cell.shading,
929 )
930 };
931 match edit {
932 TableEdit::Rows { before, rows: added } => {
933 let at = match before {
934 Some(before) => rows
935 .iter()
936 .position(|(row, _)| row == before)
937 .ok_or(OpError::StructureChanged("The anchor row is not in the table"))?,
938 None => rows.len(),
939 };
940 for row in added {
941 if row.cells.len() != columns.len() {
942 return Err(OpError::Unsupported("Every table row has one cell per column").into());
943 }
944 new_rows.insert(row.id);
945 for cell in &row.cells {
946 new_cells.insert(cell.id, new_cell(cell));
947 filled.push(cell);
948 }
949 }
950 rows.splice(
951 at..at,
952 added
953 .iter()
954 .map(|row| (row.id, row.cells.iter().map(|cell| cell.id).collect())),
955 );
956 }
957 TableEdit::Column { at, width, cells } => {
958 let at = *at as usize;
959 if at > columns.len() || cells.len() != rows.len() {
960 return Err(OpError::Unsupported("A new column has one cell per row").into());
961 }
962 for ((_, row), cell) in rows.iter_mut().zip(cells) {
963 row.insert(at, cell.id);
964 new_cells.insert(cell.id, new_cell(cell));
965 filled.push(cell);
966 }
967 columns.insert(
968 at,
969 TableColumn {
970 width: *width,
971 locked: false,
972 },
973 );
974 }
975 TableEdit::DeleteRow(row) => {
976 let before = rows.len();
977 rows.retain(|(id, _)| id != row);
978 if rows.len() == before {
979 return Err(OpError::TargetUnavailable(*row).into());
980 }
981 if rows.is_empty() {
982 return Err(OpError::Unsupported("A table keeps a row; delete the table instead").into());
983 }
984 }
985 TableEdit::DeleteColumn(at) => {
986 let at = *at as usize;
987 if at >= columns.len() || columns.len() == 1 {
988 return Err(OpError::Unsupported("Choose a column of a table with several").into());
989 }
990 columns.remove(at);
991 for (_, cells) in &mut rows {
992 cells.remove(at);
993 }
994 }
995 TableEdit::Columns(widths) => {
996 if widths.len() != columns.len() {
997 return Err(OpError::Unsupported("Give every column its width").into());
998 }
999 columns = widths.clone();
1000 }
1001 TableEdit::Borders(value) => borders = Some(*value),
1002 TableEdit::Cell {
1003 cell,
1004 shading,
1005 indents,
1006 } => {
1007 if !rows.iter().any(|(_, cells)| cells.contains(cell)) {
1008 return Err(OpError::TargetUnavailable(*cell).into());
1009 }
1010 let Kind::Cell {
1011 shading: stored_shading,
1012 indents: stored_indents,
1013 } = &page.view.nodes[cell].kind
1014 else {
1015 return Err(OpError::Unsupported("Select a table cell").into());
1016 };
1017 let stored = (*stored_shading, stored_indents.clone());
1018 return self.write(|page| {
1019 table::cell_changes(page, *cell, (stored.0, &stored.1), *shading, indents)
1020 });
1021 }
1022 }
1023 for cell in &filled {
1024 for id in std::iter::once(cell.id).chain(super::model::identities(&cell.paragraphs)) {
1025 self.free(id)?;
1026 }
1027 }
1028 for row in &new_rows {
1029 self.free(*row)?;
1030 }
1031 if columns.iter().any(|c| !c.width.is_finite() || c.width < 36.0) {
1032 return Err(OpError::Unsupported("Table columns are at least 36 points wide").into());
1033 }
1034 let structure = Structure {
1035 rows,
1036 new_rows,
1037 new_cells: new_cells
1038 .iter()
1039 .map(|(id, (indents, shading))| (*id, (indents.as_slice(), *shading)))
1040 .collect(),
1041 columns: &columns,
1042 borders,
1043 };
1044 self.write(|page| table::table_changes(page, id, None, &structure))?;
1045 for cell in filled {
1046 self.cell(cell)?;
1047 }
1048 Ok(())
1049 }
1050}
1051
1052fn fresh() -> Result<ExGuid, Error> {
1053 Ok(ExGuid {
1054 guid: crate::write::fresh_guid()?,
1055 n: 1,
1056 })
1057}
1058
1059/// Requires what `Insert` leaves to other ops unset.
1060fn bare(paragraph: &PageParagraph) -> Result<(), OpError> {
1061 let text_tags = paragraph.text().is_some_and(|text| !text.tags.is_empty());
1062 if paragraph.style.is_some()
1063 || !paragraph.lists.is_empty()
1064 || !paragraph.tags.is_empty()
1065 || text_tags
1066 || paragraph.collapsed
1067 {
1068 return Err(OpError::Unsupported(
1069 "Set styles, lists, tags and collapse state with their own ops",
1070 ));
1071 }
1072 if paragraph.media != Default::default() {
1073 return Err(OpError::Unsupported(
1074 "Recording annotations are made by OneNote while it records",
1075 ));
1076 }
1077 match &paragraph.content {
1078 ParagraphContent::Text(text) if text.date_field.is_some() => Err(OpError::Unsupported(
1079 "New paragraphs contain plain text without fields",
1080 )),
1081 ParagraphContent::Table(table) => {
1082 if !table.tags.is_empty() {
1083 return Err(OpError::Unsupported("Set a table's tags with their own op"));
1084 }
1085 for paragraph in table.rows.iter().flat_map(|row| &row.cells).flat_map(|cell| &cell.paragraphs)
1086 {
1087 bare(paragraph)?;
1088 }
1089 Ok(())
1090 }
1091 ParagraphContent::Attachment(attachment) if attachment.recording.is_some() => Err(
1092 OpError::Unsupported("Recordings are captured by OneNote, not inserted"),
1093 ),
1094 _ => Ok(()),
1095 }
1096}
1097
1098/// The field code OneNote stores before a hyperlink's label.
1099fn field_code(target: &str) -> Result<String, Error> {
1100 if target.is_empty() || target.contains(['"', '\0', '\r', '\n', '\u{fddf}']) {
1101 return Err(invalid("Choose a link target without quotes or line breaks"));
1102 }
1103 Ok(format!("\u{fddf}HYPERLINK \"{target}\""))
1104}
1105
1106/// The text edits making `range` of `text` a hyperlink to `target`: the field code
1107/// inserted hidden before the label, both flagged as link runs as OneNote stores them. With
1108/// `None` or an existing link, the field code before the label is removed first.
1109pub(crate) fn link_ops(
1110 text: &Paragraph,
1111 id: ExGuid,
1112 range: Range<u32>,
1113 target: Option<&str>,
1114) -> Result<Vec<PageOp>, Error> {
1115 if range.is_empty() {
1116 return Err(invalid("A link needs a label"));
1117 }
1118 let at = |offset: u32| super::lower::format_in(text, offset);
1119 let mut ops = Vec::new();
1120 let mut label = range.clone();
1121 // A field code is a hidden link run ending where the label starts.
1122 let start = text.byte_offset(range.start)?;
1123 let spans = text.spans();
1124 let code = spans.iter().enumerate().find_map(|(i, span)| {
1125 let from = if i == 0 { 0 } else { spans[i - 1].end };
1126 (span.end == start
1127 && span.format.hidden == Some(true)
1128 && span.format.hyperlink == Some(true)
1129 && text.text()[from..span.end].starts_with('\u{fddf}'))
1130 .then_some(from..span.end)
1131 });
1132 if let Some(code) = code {
1133 let code = text.utf16_offset(code.start)?..text.utf16_offset(code.end)?;
1134 let length = code.end - code.start;
1135 ops.push(PageOp::Text {
1136 text: id,
1137 range: code.clone(),
1138 with: String::new(),
1139 });
1140 label = label.start - length..label.end - length;
1141 } else if target.is_none() {
1142 return Err(invalid("The range is no link"));
1143 }
1144 let base = at(range.start)?.clone();
1145 match target {
1146 Some(target) => {
1147 let code = field_code(target)?;
1148 let length = code.encode_utf16().count() as u32;
1149 ops.push(PageOp::Text {
1150 text: id,
1151 range: label.start..label.start,
1152 with: code,
1153 });
1154 let mut set = vec![
1155 TextAttribute::Hyperlink(true),
1156 TextAttribute::HyperlinkLabel(true),
1157 ];
1158 if base.hidden != Some(true) {
1159 ops.push(PageOp::Format {
1160 text: id,
1161 range: label.start..label.start + length,
1162 set: [set.clone(), vec![TextAttribute::Hidden(true)]].concat(),
1163 clear: Vec::new(),
1164 });
1165 }
1166 set.retain(|attribute| match attribute {
1167 TextAttribute::Hyperlink(_) => base.hyperlink != Some(true),
1168 _ => base.hyperlink_label != Some(true),
1169 });
1170 if !set.is_empty() || base.hidden == Some(true) {
1171 ops.push(PageOp::Format {
1172 text: id,
1173 range: label.start + length..label.end + length,
1174 set,
1175 clear: Vec::new(),
1176 });
1177 }
1178 }
1179 None => ops.push(PageOp::Format {
1180 text: id,
1181 range: label,
1182 set: Vec::new(),
1183 clear: vec![TextProperty::Hyperlink, TextProperty::HyperlinkLabel],
1184 }),
1185 }
1186 ops.retain(|op| !matches!(op, PageOp::Format { set, clear, .. } if set.is_empty() && clear.is_empty()));
1187 Ok(ops)
1188}
crates/onestore/src/op/content.rs created+746
...@@ -0,0 +1,746 @@
1//! Paragraph and page content other than text: pictures, attachments, ink and equations,
2//! written as OneNote 2010 stores them.
3
4use super::Values;
5use crate::{
6 Error, ExGuid,
7 active::{ActivePage, Changes},
8 document::{Format, Layout},
9 page::{Attachment, Image, Ink, InkStroke, Paragraph},
10 write::PropertyObject,
11};
12use std::collections::BTreeMap;
13
14fn invalid(message: &'static str) -> Error {
15 Error { offset: 0, message }
16}
17
18/// The `<ifndf>{GUID}` form a file-data declaration uses to name a payload in the file-data store.
19fn payload_reference(guid: [u8; 16]) -> String {
20 let id = ExGuid { guid, n: 0 }.to_string();
21 format!("<ifndf>{}", id.split(',').next().unwrap())
22}
23
24/// The compact child references a page object holds.
25fn page_children(object: &PropertyObject) -> Result<Vec<u8>, Error> {
26 let properties = crate::PropertySets::parse(&object.bytes)?;
27 match properties.sets[0]
28 .iter()
29 .find(|p| p.id == 0x24001c20)
30 .map(|p| &p.value)
31 {
32 Some(crate::Value::References { compact_ids, .. }) => Ok(compact_ids.to_vec()),
33 None => Ok(Vec::new()),
34 _ => Err(invalid("The page has an invalid child list")),
35 }
36}
37
38/// Makes `object` the content of paragraph `holder`, or a child of the page after its others.
39fn hold(
40 active: &ActivePage<'_>,
41 changed: &mut Changes,
42 object: ExGuid,
43 holder: Option<ExGuid>,
44 modified: &[u8; 4],
45) -> Result<(), Error> {
46 let raw = &active.live.revision;
47 match holder {
48 Some(holder) => {
49 let mut paragraph = PropertyObject::from_object(&raw.objects[&holder])?;
50 let content = paragraph.reference(object)?;
51 paragraph.set(&[(0x24001c1f, &content), (0x14001d7a, modified)])?;
52 changed.insert(holder, paragraph);
53 }
54 None => {
55 let [page] = active.pages[..] else {
56 return Err(invalid("Choose an object space containing one active page"));
57 };
58 let mut node = PropertyObject::from_object(&raw.objects[&page])?;
59 let mut children = page_children(&node)?;
60 children.extend(node.reference(object)?);
61 node.set(&[(0x24001c20, &children), (0x14001d7a, modified)])?;
62 changed.insert(page, node);
63 }
64 }
65 Ok(())
66}
67
68/// The character-style properties a span format sets; paragraph-level fields stay on the
69/// paragraph style.
70pub(crate) fn style_values(format: &Format) -> Values {
71 let mut values = Values::new();
72 for (id, flag) in [
73 (0x08001c04, format.bold),
74 (0x08001c05, format.italic),
75 (0x08001c06, format.underline),
76 (0x08001c07, format.strike),
77 (0x08001c08, format.superscript),
78 (0x08001c09, format.subscript),
79 (0x08001e16, format.hidden),
80 (0x08001e14, format.hyperlink),
81 (0x08001e19, format.hyperlink_label),
82 (0x08003401, format.math),
83 (0x08001e22, format.embedded_object),
84 ] {
85 if let Some(flag) = flag {
86 values.push((id | (u32::from(flag) << 31), Vec::new()));
87 }
88 }
89 if let Some(font) = &format.font {
90 values.push((0x1c001c0a, crate::create::string(font)));
91 }
92 if let Some(size) = format.font_size {
93 values.push((
94 0x10001c0b,
95 ((size * 2.0).round() as u16).to_le_bytes().to_vec(),
96 ));
97 }
98 if let Some(color) = format.color {
99 values.push((0x14001c0c, color.to_le_bytes().to_vec()));
100 }
101 if let Some(highlight) = format.highlight {
102 values.push((0x14001c0d, highlight.to_le_bytes().to_vec()));
103 }
104 if let Some(language) = format.language {
105 values.push((0x14001c3b, language.to_le_bytes().to_vec()));
106 }
107 values
108}
109
110/// The indentation table, in points, OneNote gives each outline and table cell it creates.
111pub(crate) const NATIVE_INDENTS: [f32; 4] = [18.0, 0.0, 27.0, 27.0];
112
113/// A native measurement array: a count byte, `header - 1` reserved bytes, then each value
114/// in half-inch units.
115pub(crate) fn measurement_bytes(values: &[f32], header: usize) -> Result<Vec<u8>, Error> {
116 let count = u8::try_from(values.len())
117 .map_err(|_| invalid("A measurement array exceeds the document range"))?;
118 let mut bytes = vec![0u8; header];
119 bytes[0] = count;
120 for value in values {
121 if !value.is_finite() {
122 return Err(invalid("A measurement must be finite"));
123 }
124 bytes.extend_from_slice(&(value / 36.0).to_le_bytes());
125 }
126 Ok(bytes)
127}
128
129/// A stored picture keeps what `Picture` cannot change.
130pub(crate) fn picture_fixed_fields(stored: &Image, image: &Image) -> Result<(), Error> {
131 if stored.id != image.id
132 || stored.bytes != image.bytes
133 || stored.size != image.size
134 || stored.background != image.background
135 {
136 return Err(invalid(
137 "A stored picture keeps its payload, intrinsic size and background state",
138 ));
139 }
140 Ok(())
141}
142
143/// Position and displayed-size properties that take `layout` from `stored`, and the ones
144/// to remove.
145fn layout_values(stored: &Layout, layout: &Layout) -> Result<(Values, Vec<u32>), Error> {
146 let mut values = Values::new();
147 let mut removed = Vec::new();
148 if (layout.x, layout.y) != (stored.x, stored.y) {
149 let (Some(x), Some(y)) = (layout.x, layout.y) else {
150 return Err(invalid("A picture position needs both coordinates"));
151 };
152 if !(x.is_finite() && y.is_finite()) {
153 return Err(invalid("A picture position must be finite"));
154 }
155 values.push((0x14001c14, (x / 36.0).to_le_bytes().to_vec()));
156 values.push((0x14001c15, (y / 36.0).to_le_bytes().to_vec()));
157 }
158 if (
159 layout.max_width,
160 layout.max_height,
161 layout.width_set_by_user,
162 ) != (
163 stored.max_width,
164 stored.max_height,
165 stored.width_set_by_user,
166 ) {
167 match (layout.max_width, layout.max_height) {
168 (Some(width), Some(height)) => {
169 if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) {
170 return Err(invalid("Picture size must be positive"));
171 }
172 values.push((0x14001c1b, (width / 36.0).to_le_bytes().to_vec()));
173 values.push((0x14001c1c, (height / 36.0).to_le_bytes().to_vec()));
174 let user_set = u32::from(layout.width_set_by_user == Some(true));
175 values.push((0x08001cbd | (user_set << 31), Vec::new()));
176 }
177 (None, None) => removed.extend([0x14001c1b, 0x14001c1c, 0x08001cbd]),
178 _ => return Err(invalid("A picture size needs both dimensions")),
179 }
180 }
181 if layout.reserved_width != stored.reserved_width {
182 return Err(invalid("A picture has no reserved width"));
183 }
184 Ok((values, removed))
185}
186
187fn icon_size(values: &mut Values, size: Option<[f32; 2]>, message: &'static str) -> Result<(), Error> {
188 if let Some([width, height]) = size {
189 if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) {
190 return Err(invalid(message));
191 }
192 values.push((0x140034cd, (width / 36.0).to_le_bytes().to_vec()));
193 values.push((0x140034ce, (height / 36.0).to_le_bytes().to_vec()));
194 }
195 Ok(())
196}
197
198/// What OneNote stores for an inserted picture: a file-data object declaring payload
199/// `payload` by identity and extension, and picture object `id` that paragraph `holder`
200/// holds as content or the page lists as a child. The caller embeds the payload.
201pub(crate) fn picture_changes(
202 active: &ActivePage<'_>,
203 image: &Image,
204 id: ExGuid,
205 file: ExGuid,
206 payload: [u8; 16],
207 holder: Option<ExGuid>,
208) -> Result<Changes, Error> {
209 let Some(bytes) = &image.bytes else {
210 return Err(invalid("A new picture needs its payload"));
211 };
212 let extension = match bytes.as_ref() {
213 [0x89, b'P', b'N', b'G', ..] => ".png",
214 [0xff, 0xd8, 0xff, ..] => ".jpg",
215 [b'G', b'I', b'F', b'8', ..] => ".gif",
216 [b'B', b'M', ..] => ".bmp",
217 _ => return Err(invalid("Choose a PNG, JPEG, GIF or BMP picture")),
218 };
219 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
220 let mut values: Values = vec![(0x14001d7a, modified.to_vec())];
221 if let Some([width, height]) = image.size {
222 if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) {
223 return Err(invalid("Picture size must be positive"));
224 }
225 values.push((0x140034cd, (width / 36.0).to_le_bytes().to_vec()));
226 values.push((0x140034ce, (height / 36.0).to_le_bytes().to_vec()));
227 }
228 values.extend(layout_values(&Default::default(), &image.layout)?.0);
229 if let Some(alt) = &image.alt {
230 values.push((0x1c001e58, crate::create::string(alt)));
231 }
232 if image.background {
233 values.push((0x08001d13 | (1 << 31), Vec::new()));
234 }
235 values.push((0x14001c3b, 0x409_u32.to_le_bytes().to_vec()));
236 values.push((0x08001d85, Vec::new()));
237 let mut changed = BTreeMap::new();
238 changed.insert(
239 file,
240 PropertyObject::file(file, &payload_reference(payload), extension)?,
241 );
242 let mut picture = PropertyObject {
243 jcid: 0x60011,
244 bytes: crate::create::properties(&values)?,
245 global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])),
246 };
247 picture.reference(id)?;
248 let container = picture.reference(file)?;
249 picture.set(&[(0x20001c3f, &container)])?;
250 changed.insert(id, picture);
251 hold(active, &mut changed, id, holder, &modified)?;
252 Ok(changed)
253}
254
255/// A moved, resized or described picture as OneNote stores one: the position, the layout
256/// width and height with the user flag and the description on the picture object, leaving
257/// the intrinsic size alone.
258pub(crate) fn picture_edit_changes(
259 active: &ActivePage<'_>,
260 object: ExGuid,
261 stored: (&Layout, &Option<String>),
262 layout: &Layout,
263 alt: &Option<String>,
264) -> Result<Changes, Error> {
265 let mut values: Values = vec![(
266 0x14001d7a,
267 crate::create::current_timestamps()?
268 .0
269 .to_le_bytes()
270 .to_vec(),
271 )];
272 let (layout, mut removed) = layout_values(stored.0, layout)?;
273 values.extend(layout);
274 if alt != stored.1 {
275 match alt {
276 Some(alt) => values.push((0x1c001e58, crate::create::string(alt))),
277 None => removed.push(0x1c001e58),
278 }
279 }
280 let raw = &active.live.revision;
281 let mut picture = PropertyObject::from_object(&raw.objects[&object])?;
282 picture.remove(&removed)?;
283 let values: Vec<(u32, &[u8])> = values
284 .iter()
285 .map(|(id, bytes)| (*id, bytes.as_slice()))
286 .collect();
287 picture.set(&values)?;
288 Ok(BTreeMap::from([(object, picture)]))
289}
290
291/// Identities of a new attachment's objects and payloads.
292pub(crate) struct AttachmentIds {
293 pub object: ExGuid,
294 pub file: ExGuid,
295 pub payload: [u8; 16],
296 /// The preview icon's payload and file-data object, when it has one.
297 pub preview: Option<([u8; 16], ExGuid)>,
298}
299
300/// What OneNote stores for an inserted file: an embedded-file container declaring the
301/// payload and an attachment object naming the file, which paragraph `holder` holds as
302/// content. The caller embeds the payloads.
303pub(crate) fn attachment_changes(
304 active: &ActivePage<'_>,
305 attachment: &Attachment,
306 ids: &AttachmentIds,
307 holder: ExGuid,
308) -> Result<Changes, Error> {
309 let name = attachment.filename.as_str();
310 if name.is_empty() || name.contains(['\0', '/', '\\']) {
311 return Err(invalid(
312 "An attachment needs a file name without path separators",
313 ));
314 }
315 if attachment.recording.is_some() {
316 return Err(invalid("Recordings are captured by OneNote, not inserted"));
317 }
318 if attachment.bytes.is_none() {
319 return Err(invalid("A new attachment needs its payload"));
320 }
321 if attachment
322 .preview
323 .as_ref()
324 .is_some_and(|icon| !icon.starts_with(&[0x89, b'P', b'N', b'G']))
325 {
326 return Err(invalid("An attachment preview is a PNG icon"));
327 }
328 let extension = name
329 .rfind('.')
330 .filter(|dot| *dot > 0)
331 .map(|dot| &name[dot..])
332 .unwrap_or("");
333 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
334 let mut values: Values = vec![(0x14001d7a, modified.to_vec())];
335 icon_size(&mut values, attachment.size, "Attachment icon size must be positive")?;
336 values.push((0x14001c3b, 0x409_u32.to_le_bytes().to_vec()));
337 values.push((0x10001cfe, 0x409_u16.to_le_bytes().to_vec()));
338 values.push((0x1c001dcf, vec![0; 32]));
339 values.push((
340 0x1c001d61,
341 [16u32, 1, 0, 0, 0]
342 .iter()
343 .flat_map(|v| v.to_le_bytes())
344 .collect(),
345 ));
346 values.push((0x14001c3e, 1u32.to_le_bytes().to_vec()));
347 values.push((0x14001c84, 1u32.to_le_bytes().to_vec()));
348 values.push((0x1c001c22, crate::create::string(name)));
349 values.push((0x1c001d9c, crate::create::string(name)));
350 if let Some(path) = &attachment.source_path {
351 values.push((0x1c001d9d, crate::create::string(path)));
352 }
353 let mut changed = BTreeMap::new();
354 let mut file = PropertyObject::file(ids.file, &payload_reference(ids.payload), extension)?;
355 file.jcid = crate::write::EMBEDDED_FILE_JCID;
356 changed.insert(ids.file, file);
357 let mut node = PropertyObject {
358 jcid: 0x60035,
359 bytes: crate::create::properties(&values)?,
360 global_ids: std::sync::Arc::new(BTreeMap::from([(0, ids.object.guid)])),
361 };
362 node.reference(ids.object)?;
363 let container = node.reference(ids.file)?;
364 node.set(&[(0x20001d9b, &container)])?;
365 if let Some((payload, icon)) = ids.preview {
366 changed.insert(
367 icon,
368 PropertyObject::file(icon, &payload_reference(payload), ".png")?,
369 );
370 let reference = node.reference(icon)?;
371 node.set(&[(0x20001c3f, &reference)])?;
372 }
373 changed.insert(ids.object, node);
374 hold(active, &mut changed, ids.object, Some(holder), &modified)?;
375 Ok(changed)
376}
377
378/// A stored attachment keeps its payload and preview; its shown name, recorded source
379/// path and icon size change in place, as OneNote's rename does.
380pub(crate) fn attachment_edit_changes(
381 active: &ActivePage<'_>,
382 stored: &Attachment,
383 attachment: &Attachment,
384) -> Result<Changes, Error> {
385 let mut values: Values = vec![(
386 0x14001d7a,
387 crate::create::current_timestamps()?
388 .0
389 .to_le_bytes()
390 .to_vec(),
391 )];
392 if attachment.recording != stored.recording {
393 return Err(invalid("A recording stays the recording OneNote captured"));
394 }
395 let name = attachment.filename.as_str();
396 if name.is_empty() || name.contains(['\0', '/', '\\']) {
397 return Err(invalid(
398 "An attachment needs a file name without path separators",
399 ));
400 }
401 let mut removed = Vec::new();
402 if attachment.filename != stored.filename {
403 let name = crate::create::string(&attachment.filename);
404 values.push((0x1c001c22, name.clone()));
405 values.push((0x1c001d9c, name));
406 }
407 if attachment.source_path != stored.source_path {
408 match &attachment.source_path {
409 Some(path) => values.push((0x1c001d9d, crate::create::string(path))),
410 None => removed.push(0x1c001d9d),
411 }
412 }
413 if attachment.size != stored.size {
414 match attachment.size {
415 Some(_) => icon_size(&mut values, attachment.size, "Attachment icon size must be positive")?,
416 None => removed.extend([0x140034cd, 0x140034ce]),
417 }
418 }
419 let object = stored.id;
420 let raw = &active.live.revision;
421 let mut node = PropertyObject::from_object(&raw.objects[&object])?;
422 node.remove(&removed)?;
423 let values: Vec<(u32, &[u8])> = values
424 .iter()
425 .map(|(id, bytes)| (*id, bytes.as_slice()))
426 .collect();
427 node.set(&values)?;
428 Ok(BTreeMap::from([(object, node)]))
429}
430
431/// Creates stroke objects (numbered from `first`) and one drawing-attribute object per
432/// distinct pen; returns the compact references for the data node's stroke list.
433fn write_strokes(
434 changed: &mut Changes,
435 data_object: &mut PropertyObject,
436 strokes: &[(ExGuid, &InkStroke)],
437 first: usize,
438 filetime: u64,
439) -> Result<Vec<u8>, Error> {
440 let mut styles: Vec<(InkPen, ExGuid)> = Vec::new();
441 let mut references = Vec::new();
442 for (offset, (stroke_id, stroke)) in strokes.iter().enumerate() {
443 let pen = InkPen::of(stroke);
444 let style = match styles.iter().find(|(known, _)| *known == pen) {
445 Some((_, id)) => *id,
446 None => {
447 let id = ExGuid {
448 guid: crate::write::fresh_guid()?,
449 n: 1,
450 };
451 let mut style = PropertyObject {
452 jcid: 0x120048,
453 bytes: crate::create::properties(&pen.values())?,
454 global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])),
455 };
456 style.reference(id)?;
457 changed.insert(id, style);
458 styles.push((pen, id));
459 id
460 }
461 };
462 let mut object = PropertyObject {
463 jcid: 0x20047,
464 bytes: crate::create::properties(&stroke_values(
465 stroke,
466 (first + offset) as u32 + 1,
467 filetime,
468 )?)?,
469 global_ids: std::sync::Arc::new(BTreeMap::from([(0, stroke_id.guid)])),
470 };
471 object.reference(*stroke_id)?;
472 let style_reference = object.reference(style)?;
473 object.set(&[(0x20003409, &style_reference)])?;
474 changed.insert(*stroke_id, object);
475 references.extend(data_object.reference(*stroke_id)?);
476 }
477 Ok(references)
478}
479
480/// The drawing attributes OneNote shares between strokes drawn with the same pen.
481#[derive(PartialEq)]
482struct InkPen {
483 width: u32,
484 height: u32,
485 color: Option<u32>,
486 transparency: Option<u8>,
487 pen_tip: Option<u8>,
488}
489
490impl InkPen {
491 fn of(stroke: &InkStroke) -> Self {
492 Self {
493 width: (stroke.width * 2540.0 / 72.0).to_bits(),
494 height: (stroke.height * 2540.0 / 72.0).to_bits(),
495 color: stroke.color,
496 transparency: stroke.transparency,
497 pen_tip: stroke.pen_tip,
498 }
499 }
500
501 fn values(&self) -> Values {
502 let mut values: Values = vec![
503 (0x1c00340a, crate::page::ink::DIMENSIONS.to_vec()),
504 (0x1400340c, self.height.to_le_bytes().to_vec()),
505 (0x1400340d, self.width.to_le_bytes().to_vec()),
506 ];
507 if let Some(color) = self.color {
508 values.push((0x1400340f, color.to_le_bytes().to_vec()));
509 }
510 if let Some(transparency) = self.transparency {
511 values.push((0x0c003414, vec![transparency]));
512 }
513 if let Some(tip) = self.pen_tip {
514 values.push((0x0c003412, vec![tip]));
515 }
516 values
517 }
518}
519
520fn stroke_values(stroke: &InkStroke, index: u32, filetime: u64) -> Result<Values, Error> {
521 if stroke.points.is_empty() {
522 return Err(invalid("A stroke needs at least one point"));
523 }
524 if stroke
525 .points
526 .iter()
527 .any(|p| !p[0].is_finite() || !p[1].is_finite())
528 || !(stroke.width.is_finite() && stroke.height.is_finite())
529 || stroke.width <= 0.0
530 || stroke.height <= 0.0
531 {
532 return Err(invalid(
533 "Stroke points and pen size must be finite and positive",
534 ));
535 }
536 let left = stroke
537 .points
538 .iter()
539 .map(|p| p[0])
540 .fold(f32::INFINITY, f32::min);
541 let top = stroke
542 .points
543 .iter()
544 .map(|p| p[1])
545 .fold(f32::INFINITY, f32::min);
546 let mut origin = ((left - stroke.width / 2.0) / 36.0).to_le_bytes().to_vec();
547 origin.extend_from_slice(&((top - stroke.height / 2.0) / 36.0).to_le_bytes());
548 Ok(vec![
549 (0x1c00340b, stroke.packet()),
550 (0x14003419, index.to_le_bytes().to_vec()),
551 (0x1000341b, 0x409_u16.to_le_bytes().to_vec()),
552 (0x0c00341c, vec![0]),
553 (0x1c00341a, crate::write::fresh_guid()?.to_vec()),
554 (0x1c00341d, filetime.to_le_bytes().to_vec()),
555 (0x1c00345b, origin),
556 ])
557}
558
559/// New ink as OneNote 2010 stores a drawing: container `id` that the page lists as a child
560/// (or paragraph `holder` holds as content), its data node `data` listing the strokes, each
561/// stroke's packet and half-inch origin, and one drawing-attribute object per distinct pen.
562pub(crate) fn ink_changes(
563 active: &ActivePage<'_>,
564 ink: &Ink,
565 id: ExGuid,
566 data: ExGuid,
567 strokes: &[(ExGuid, &InkStroke)],
568 holder: Option<ExGuid>,
569) -> Result<Changes, Error> {
570 if !ink.groups.is_empty() {
571 return Err(invalid("New ink holds strokes, not nested groups"));
572 }
573 if ink.strokes.is_empty() {
574 return Err(invalid("New ink needs at least one stroke"));
575 }
576 if ink.layout != Default::default() {
577 return Err(invalid("Ink positions come from its strokes"));
578 }
579 let (modified, filetime) = crate::create::current_timestamps()?;
580 let modified = modified.to_le_bytes();
581 let mut changed = BTreeMap::new();
582 let mut data_object = PropertyObject {
583 jcid: 0x2003b,
584 bytes: crate::create::properties(&[])?,
585 global_ids: std::sync::Arc::new(BTreeMap::from([(0, data.guid)])),
586 };
587 data_object.reference(data)?;
588 let references = write_strokes(&mut changed, &mut data_object, strokes, 0, filetime)?;
589 data_object.set(&[(0x24003416, &references)])?;
590 changed.insert(data, data_object);
591 let mut object = PropertyObject {
592 jcid: 0x60014,
593 bytes: crate::create::properties(&[
594 (0x14001d7a, modified.to_vec()),
595 (0x14001d4e, 1u32.to_le_bytes().to_vec()),
596 ])?,
597 global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])),
598 };
599 object.reference(id)?;
600 let data_reference = object.reference(data)?;
601 object.set(&[(0x20003415, &data_reference)])?;
602 changed.insert(id, object);
603 hold(active, &mut changed, id, holder, &modified)?;
604 Ok(changed)
605}
606
607/// Rewrites a drawing's stroke list: `kept` strokes stay as they are (OneNote erases whole
608/// strokes rather than editing them), the rest leave the list, `added` ones are created.
609pub(crate) fn strokes_changes(
610 active: &ActivePage<'_>,
611 container: ExGuid,
612 kept: &[ExGuid],
613 added: &[(ExGuid, &InkStroke)],
614) -> Result<Changes, Error> {
615 let data = match active
616 .view
617 .nodes
618 .get(&container)
619 .map(|node| &node.kind)
620 {
621 Some(crate::document::Kind::Ink {
622 data: Some(data), ..
623 }) => *data,
624 _ => return Err(invalid("Stored ink has no stroke data to rewrite")),
625 };
626 let (modified, filetime) = crate::create::current_timestamps()?;
627 let modified = modified.to_le_bytes();
628 let raw = &active.live.revision;
629 let mut changed = BTreeMap::new();
630 let mut data_object = PropertyObject::from_object(&raw.objects[&data])?;
631 let mut references = Vec::new();
632 for id in kept {
633 references.extend(data_object.reference(*id)?);
634 }
635 references.extend(write_strokes(
636 &mut changed,
637 &mut data_object,
638 added,
639 kept.len(),
640 filetime,
641 )?);
642 data_object.set(&[(0x24003416, &references)])?;
643 changed.insert(data, data_object);
644 let mut object = PropertyObject::from_object(&raw.objects[&container])?;
645 object.set(&[(0x14001d7a, &modified)])?;
646 changed.insert(container, object);
647 Ok(changed)
648}
649
650/// An equation as OneNote stores one: the linear text, one run per span with a style
651/// carrying the span's format, the run-data array naming each run's inline object, and
652/// the math language marker on the text object.
653pub(crate) fn equation_changes(
654 active: &ActivePage<'_>,
655 object: ExGuid,
656 text: &Paragraph,
657) -> Result<Changes, Error> {
658 if text.text().contains('\u{fffc}') {
659 return Err(invalid("Equations cannot hold embedded objects"));
660 }
661 let encoded: Vec<u8> = text
662 .text()
663 .encode_utf16()
664 .chain([0])
665 .flat_map(u16::to_le_bytes)
666 .collect();
667 let mut ends = Vec::new();
668 let mut styles: Vec<Values> = Vec::new();
669 let mut sets = Vec::new();
670 let all_math = text.spans().iter().all(|s| s.format.math == Some(true));
671 for span in text.spans() {
672 ends.extend(text.utf16_offset(span.end)?.to_le_bytes());
673 styles.push(style_values(&span.format));
674 sets.push(match &span.format.math_object {
675 Some(object) => {
676 let mut set = vec![(0x1400344f, object.kind.to_le_bytes().to_vec())];
677 if let Some(count) = object.arguments {
678 set.push((0x14003450, count.to_le_bytes().to_vec()));
679 }
680 if let Some(columns) = object.columns {
681 set.push((0x0c003451, vec![columns]));
682 }
683 for (id, symbol) in [0x10003453, 0x10003454, 0x10003455]
684 .into_iter()
685 .zip(&object.symbols)
686 {
687 let unit = u16::try_from(u32::from(*symbol))
688 .map_err(|_| invalid("Math symbols are single UTF-16 units"))?;
689 set.push((id, unit.to_le_bytes().to_vec()));
690 }
691 set
692 }
693 None => Vec::new(),
694 });
695 }
696 ends.truncate(ends.len() - 4);
697 let has_objects = sets.iter().any(|set| !set.is_empty());
698 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
699 let raw = &active.live.revision;
700 let mut changed = BTreeMap::new();
701 let mut target = PropertyObject::from_object(&raw.objects[&object])?;
702 let mut references = Vec::new();
703 let mut created: Vec<(Values, ExGuid)> = Vec::new();
704 for values in &styles {
705 let id = match created.iter().find(|(known, _)| known == values) {
706 Some((_, id)) => *id,
707 None => {
708 let id = ExGuid {
709 guid: crate::write::fresh_guid()?,
710 n: 1,
711 };
712 let mut style = PropertyObject {
713 jcid: 0x12004d,
714 bytes: crate::create::properties(values)?,
715 global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])),
716 };
717 style.reference(id)?;
718 changed.insert(id, style);
719 created.push((values.clone(), id));
720 id
721 }
722 };
723 references.extend(target.reference(id)?);
724 }
725 target.remove(&[0x1c003498, 0x40003499])?;
726 target.set(&[
727 (0x1c001c22, &encoded),
728 (0x1c001e12, &ends),
729 (0x24001e13, &references),
730 (0x14001d7a, &modified),
731 ])?;
732 if has_objects {
733 target.set_sets(0x40003499, 0x44000811, &sets)?;
734 }
735 if all_math {
736 // The flags and language marker OneNote's equation editor leaves on every equation
737 // text object.
738 target.set(&[
739 (0x10001cfe, &0x7f_u16.to_le_bytes()),
740 (0x14001c3e, &1u32.to_le_bytes()),
741 (0x14001c84, &1u32.to_le_bytes()),
742 ])?;
743 }
744 changed.insert(object, target);
745 Ok(changed)
746}
crates/onestore/src/op/levels.rs created+143
...@@ -0,0 +1,143 @@
1//! Outline levels: OneNote stores a container's children at its child level and nests each
2//! run of deeper paragraphs in an outline group adding the rest
3//! (`evidence/structural-edits/xml/c6-first-tab.xml`, MS-ONE 2.2.22).
4
5use crate::{
6 Error, ExGuid,
7 active::{ActivePage, Changes},
8 document::{Kind, Revision},
9 write::PropertyObject,
10};
11use std::collections::BTreeMap;
12
13fn invalid(message: &'static str) -> Error {
14 Error { offset: 0, message }
15}
16
17/// The children of `container` with its outline groups lifted into it, in order.
18pub(crate) fn flat_children(view: &Revision<'_>, container: ExGuid) -> Vec<ExGuid> {
19 fn flat(view: &Revision<'_>, id: ExGuid, out: &mut Vec<ExGuid>) {
20 for child in &view.nodes[&id].children {
21 match view.nodes[child].kind {
22 Kind::OutlineGroup => flat(view, *child, out),
23 _ => out.push(*child),
24 }
25 }
26 }
27 let mut children = Vec::new();
28 flat(view, container, &mut children);
29 children
30}
31
32/// How much deeper than `container` each of its children lies, groups lifted.
33pub(crate) fn stored_depths(view: &Revision<'_>, container: ExGuid) -> Vec<(ExGuid, u32)> {
34 fn walk(view: &Revision<'_>, id: ExGuid, base: u32, out: &mut Vec<(ExGuid, u32)>) {
35 let node = &view.nodes[&id];
36 let depth = base + u32::from(node.child_level.unwrap_or(0));
37 for child in &node.children {
38 match view.nodes[child].kind {
39 Kind::OutlineGroup => walk(view, *child, depth, out),
40 _ => out.push((*child, depth)),
41 }
42 }
43 }
44 let mut out = Vec::new();
45 walk(view, container, 0, &mut out);
46 out
47}
48
49/// Lifts the children of `container`'s outline groups into it.
50pub(crate) fn ungroup_changes(active: &ActivePage<'_>, container: ExGuid) -> Result<Changes, Error> {
51 let children = flat_children(&active.view, container);
52 let parents = active.editable_parents(container)?;
53 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
54 let raw = &active.live.revision;
55 let mut holder = PropertyObject::from_object(&raw.objects[&container])?;
56 let mut references = Vec::new();
57 for child in children {
58 references.extend(holder.reference(child)?);
59 }
60 holder.set(&[(0x24001c20, &references), (0x14001d7a, &modified)])?;
61 let mut changed = BTreeMap::from([(container, holder)]);
62 crate::formatting::touch_ancestors(raw, parents, container, &modified, &mut changed)?;
63 Ok(changed)
64}
65
66/// Children in runs: one child at the container's child level, or a run sharing a deeper
67/// depth.
68pub(crate) type Runs = Vec<(u32, Vec<ExGuid>)>;
69
70/// The child level of a container whose children lie `depths` deeper than it, and its
71/// children in runs.
72pub(crate) fn runs(children: &[ExGuid], depths: &[u32], is_cell: bool) -> Result<(u32, Runs), Error> {
73 let level = *depths
74 .iter()
75 .min()
76 .ok_or_else(|| invalid("A container needs a child"))?;
77 if !(1..=31).contains(&level) || depths.iter().any(|depth| *depth > level + 31) {
78 return Err(invalid(
79 "Outline levels lie 1 to 31 deeper than their parent",
80 ));
81 }
82 if is_cell && depths.iter().any(|depth| *depth != level) {
83 return Err(invalid("Table cells hold no outline groups"));
84 }
85 let mut runs: Runs = Vec::new();
86 for (id, depth) in children.iter().zip(depths) {
87 match runs.last_mut() {
88 Some((last, ids)) if *last == *depth && *depth != level => ids.push(*id),
89 _ => runs.push((*depth, vec![*id])),
90 }
91 }
92 Ok((level, runs))
93}
94
95/// Stores `container`'s children as `runs` below child level `level`, each deeper run in a
96/// new outline group.
97pub(crate) fn regroup_changes(
98 active: &ActivePage<'_>,
99 container: ExGuid,
100 level: u32,
101 runs: &[(u32, Vec<ExGuid>)],
102) -> Result<Changes, Error> {
103 let parents = active.editable_parents(container)?;
104 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
105 let raw = &active.live.revision;
106 let mut holder = PropertyObject::from_object(&raw.objects[&container])?;
107 let mut changed = BTreeMap::new();
108 let mut references = Vec::new();
109 for (depth, ids) in runs {
110 if *depth == level {
111 references.extend(holder.reference(ids[0])?);
112 continue;
113 }
114 let id = ExGuid {
115 guid: crate::write::fresh_guid()?,
116 n: 1,
117 };
118 let mut group = PropertyObject {
119 jcid: 0x60019,
120 bytes: crate::create::properties(&[
121 (0x14001d7a, modified.to_vec()),
122 (0x0c001c03, vec![(depth - level) as u8]),
123 ])?,
124 global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])),
125 };
126 let mut members = Vec::new();
127 for member in ids {
128 members.extend(group.reference(*member)?);
129 }
130 group.set(&[(0x24001c20, &members)])?;
131 group.reference(id)?;
132 changed.insert(id, group);
133 references.extend(holder.reference(id)?);
134 }
135 holder.set(&[
136 (0x24001c20, &references),
137 (0x0c001c03, &[level as u8]),
138 (0x14001d7a, &modified),
139 ])?;
140 changed.insert(container, holder);
141 crate::formatting::touch_ancestors(raw, parents, container, &modified, &mut changed)?;
142 Ok(changed)
143}
crates/onestore/src/op/lower.rs created+1778
...@@ -0,0 +1,1778 @@
1//! The ops that turn one page model into another, computed from the models alone: each op
2//! is interpreted on a prediction of the page (`model::apply`) so later ones see what earlier
3//! ones leave, as the typed writers would store it.
4
5use super::{PageOp, TableEdit, TextProperty, Values, model};
6use crate::{
7 Error, ExGuid, TextAttribute,
8 document::{Format, Kind, Tag},
9 page::{
10 Definition, Outline, Page, PageObject, PageParagraph, Paragraph, ParagraphContent, Table,
11 TableCell, TextObject, text::new_id,
12 },
13};
14use std::{
15 collections::{BTreeMap, BTreeSet},
16 ops::Range,
17};
18
19fn invalid(message: &'static str) -> Error {
20 Error { offset: 0, message }
21}
22
23/// The ops that turn `before`, a page as stored, into `after`.
24pub fn lower_page(before: &Page, after: &Page) -> Result<Vec<PageOp>, Error> {
25 let old = View::new(before, false)?;
26 let new = View::new(after, false)?;
27 Lowering::new(before.clone(), None).run(before, after, &old, &new)
28}
29
30/// The ops that replace `before`, consecutive paragraphs of outline or table cell
31/// `container` with their parents listed first or outside the range, by `after`. `next`
32/// is the paragraph following the range; `definitions` holds the lists, tags and styles
33/// the paragraphs reference. Only the paragraphs given are read.
34pub fn lower(
35 container: ExGuid,
36 before: &[PageParagraph],
37 after: &[PageParagraph],
38 next: Option<&PageParagraph>,
39 definitions: &BTreeMap<ExGuid, Definition>,
40) -> Result<Vec<PageOp>, Error> {
41 let page = |paragraphs: &[PageParagraph]| Page {
42 title: String::new(),
43 identity: None,
44 created: None,
45 margin_origin: [0.0; 2],
46 objects: vec![PageObject::Outline(Outline {
47 id: container,
48 title: false,
49 min_width: None,
50 layout: Default::default(),
51 indents: Vec::new(),
52 paragraphs: paragraphs.to_vec(),
53 unsupported: Vec::new(),
54 })],
55 definitions: definitions.clone(),
56 };
57 let (before, after) = (page(before), page(after));
58 let old = View::new(&before, true)?;
59 let new = View::new(&after, true)?;
60 let tail = next.map(|next| (next.parent.unwrap_or(container), next.id));
61 Lowering::new(before.clone(), tail).run(&before, &after, &old, &new)
62}
63
64/// Direct children of every container, in model order, plus lookups by identity.
65pub(crate) struct View<'a> {
66 pub page: &'a Page,
67 pub outlines: BTreeMap<ExGuid, &'a Outline>,
68 /// Outlines owned by a title object rather than the page.
69 pub title_outlines: BTreeSet<ExGuid>,
70 pub paragraphs: BTreeMap<ExGuid, &'a PageParagraph>,
71 pub children: BTreeMap<ExGuid, Vec<ExGuid>>,
72 pub container: BTreeMap<ExGuid, ExGuid>,
73 pub page_children: Vec<ExGuid>,
74 /// Containers a partial model names as parents without holding them, in order.
75 pub external: Vec<ExGuid>,
76 partial: bool,
77}
78
79impl<'a> View<'a> {
80 /// `partial` accepts parents outside the model, as a range of paragraphs has.
81 pub(crate) fn new(page: &'a Page, partial: bool) -> Result<Self, Error> {
82 let mut view = Self {
83 page,
84 outlines: BTreeMap::new(),
85 title_outlines: BTreeSet::new(),
86 paragraphs: BTreeMap::new(),
87 children: BTreeMap::new(),
88 container: BTreeMap::new(),
89 page_children: Vec::new(),
90 external: Vec::new(),
91 partial,
92 };
93 for object in &page.objects {
94 view.page_children.push(object.id());
95 match object {
96 PageObject::Outline(outline) => view.outline(outline)?,
97 PageObject::Title(title) => {
98 for outline in &title.outlines {
99 view.title_outlines.insert(outline.id);
100 view.outline(outline)?;
101 }
102 }
103 PageObject::Image(_) | PageObject::Ink(_) | PageObject::Unsupported(_) => {}
104 }
105 }
106 Ok(view)
107 }
108
109 fn outline(&mut self, outline: &'a Outline) -> Result<(), Error> {
110 if self.outlines.insert(outline.id, outline).is_some() {
111 return Err(invalid("The page model repeats an outline identity"));
112 }
113 self.children.entry(outline.id).or_default();
114 self.paragraphs_of(outline.id, &outline.paragraphs)
115 }
116
117 fn paragraphs_of(&mut self, root: ExGuid, list: &'a [PageParagraph]) -> Result<(), Error> {
118 let listed: BTreeSet<ExGuid> = if self.partial {
119 list.iter().map(|p| p.id).collect()
120 } else {
121 BTreeSet::new()
122 };
123 for paragraph in list {
124 let container = paragraph.parent.unwrap_or(root);
125 if let Some(parent) = paragraph.parent
126 && !self.paragraphs.contains_key(&parent)
127 {
128 if !self.partial || listed.contains(&parent) {
129 return Err(invalid(
130 "A paragraph's parent must precede it in its container",
131 ));
132 }
133 if !self.external.contains(&parent) {
134 self.external.push(parent);
135 }
136 }
137 if self.paragraphs.insert(paragraph.id, paragraph).is_some() {
138 return Err(invalid("The page model repeats a paragraph identity"));
139 }
140 self.children
141 .entry(container)
142 .or_default()
143 .push(paragraph.id);
144 self.children.entry(paragraph.id).or_default();
145 self.container.insert(paragraph.id, container);
146 if let ParagraphContent::Table(table) = &paragraph.content {
147 for row in &table.rows {
148 for cell in &row.cells {
149 if self.children.contains_key(&cell.id) {
150 return Err(invalid("The page model repeats a cell identity"));
151 }
152 self.children.entry(cell.id).or_default();
153 self.paragraphs_of(cell.id, &cell.paragraphs)?;
154 }
155 }
156 }
157 }
158 Ok(())
159 }
160
161 pub(crate) fn text(&self, paragraph: ExGuid) -> Option<&'a TextObject> {
162 self.paragraphs.get(&paragraph).and_then(|p| p.text())
163 }
164
165 /// How much deeper than `container` each of its children lies: their outline levels
166 /// less the paragraph's, or less nothing in an outline or cell.
167 pub(crate) fn depths(&self, container: ExGuid, children: &[ExGuid]) -> Option<Vec<u32>> {
168 let base = self.paragraphs.get(&container).map_or(0, |p| p.level);
169 children
170 .iter()
171 .map(|id| self.paragraphs.get(id)?.level.checked_sub(base))
172 .collect()
173 }
174
175 /// Each outline's paragraphs in document order, a table's cells following its paragraph.
176 pub(crate) fn documents(&self) -> impl Iterator<Item = Vec<&'a PageParagraph>> + '_ {
177 fn walk<'a>(list: &'a [PageParagraph], out: &mut Vec<&'a PageParagraph>) {
178 for paragraph in list {
179 out.push(paragraph);
180 if let ParagraphContent::Table(table) = &paragraph.content {
181 for cell in table.rows.iter().flat_map(|row| &row.cells) {
182 walk(&cell.paragraphs, out);
183 }
184 }
185 }
186 }
187 self.outlines.values().map(|outline| {
188 let mut out = Vec::new();
189 walk(&outline.paragraphs, &mut out);
190 out
191 })
192 }
193
194 /// Every container in document order: outlines, external parents, paragraphs and cells.
195 fn containers(&self) -> Vec<ExGuid> {
196 let mut containers = Vec::new();
197 for object in &self.page.objects {
198 let outlines: Vec<&Outline> = match object {
199 PageObject::Outline(outline) => vec![outline],
200 PageObject::Title(title) => title.outlines.iter().collect(),
201 PageObject::Image(_) | PageObject::Ink(_) | PageObject::Unsupported(_) => {
202 Vec::new()
203 }
204 };
205 for outline in outlines {
206 containers.push(outline.id);
207 containers.extend(&self.external);
208 collect_containers(&outline.paragraphs, &mut containers);
209 }
210 }
211 containers
212 }
213}
214
215pub(crate) fn collect_containers(list: &[PageParagraph], out: &mut Vec<ExGuid>) {
216 for paragraph in list {
217 out.push(paragraph.id);
218 if let ParagraphContent::Table(table) = &paragraph.content {
219 for row in &table.rows {
220 for cell in &row.cells {
221 out.push(cell.id);
222 collect_containers(&cell.paragraphs, out);
223 }
224 }
225 }
226 }
227}
228
229/// What the writers keep as stored, checked before any op: titles, unsupported objects,
230/// definitions other than lists and tags, outline roles, paragraph styles and formats,
231/// recording links, fields and content types.
232pub(crate) fn validate(
233 before: &Page,
234 after: &Page,
235 old: &View<'_>,
236 new: &View<'_>,
237) -> Result<(), Error> {
238 if after.created != before.created || after.margin_origin != before.margin_origin {
239 return Err(invalid("Page creation time and margins cannot be edited"));
240 }
241 for (id, definition) in &after.definitions {
242 if before
243 .definitions
244 .get(id)
245 .is_some_and(|stored| stored != definition)
246 && !matches!(
247 definition.kind,
248 Kind::List { .. } | Kind::TagDefinition { .. }
249 )
250 {
251 return Err(invalid("Style definitions cannot be edited"));
252 }
253 }
254 for paragraph in new.paragraphs.values() {
255 if let ParagraphContent::Table(table) = &paragraph.content {
256 super::table::validate_table(table)?;
257 }
258 }
259 let fixed = |page: &Page| -> Vec<String> {
260 let mut fixed: Vec<String> = page
261 .objects
262 .iter()
263 .filter_map(|object| match object {
264 PageObject::Unsupported(unsupported) => Some(format!("{unsupported:?}")),
265 PageObject::Title(title) => Some(format!(
266 "{:?} {:?} {:?} {:?}",
267 title.id,
268 title.date,
269 title.layout,
270 title.outlines.iter().map(|o| o.id).collect::<Vec<_>>()
271 )),
272 PageObject::Outline(_) | PageObject::Image(_) | PageObject::Ink(_) => None,
273 })
274 .collect();
275 fixed.sort();
276 fixed
277 };
278 if fixed(before) != fixed(after) {
279 return Err(invalid(
280 "Titles and unsupported objects cannot be edited through the page model",
281 ));
282 }
283 for (id, outline) in &new.outlines {
284 if outline.paragraphs.is_empty() {
285 return Err(invalid(
286 "An outline needs a paragraph; remove the outline instead",
287 ));
288 }
289 let Some(previous) = old.outlines.get(id) else {
290 continue;
291 };
292 let same = outline.title == previous.title
293 && outline.min_width == previous.min_width
294 && outline.indents == previous.indents
295 && outline.unsupported == previous.unsupported
296 && (!new.title_outlines.contains(id) || outline.layout == previous.layout);
297 if !same {
298 return Err(invalid(
299 "Outline roles, indentation tables and title geometry cannot be edited",
300 ));
301 }
302 }
303 let owners: BTreeMap<ExGuid, &PageParagraph> = old
304 .paragraphs
305 .values()
306 .filter_map(|owner| Some((owner.text()?.id, *owner)))
307 .collect();
308 for (id, paragraph) in &new.paragraphs {
309 let Some(previous) = old.paragraphs.get(id) else {
310 continue;
311 };
312 // A join moves a text object, with its style and recording link, into an emptied
313 // paragraph; one new to the page starts as a copy without the link.
314 let owner = paragraph.text().map(|text| owners.get(&text.id));
315 let (style, media) = match owner {
316 Some(Some(owner)) => (owner.style, owner.media.clone()),
317 Some(None) => (paragraph.style, Default::default()),
318 None => (previous.style, previous.media.clone()),
319 };
320 if paragraph.media != media {
321 return Err(invalid("Recording annotations cannot be edited"));
322 }
323 if paragraph.style != style || paragraph.format != previous.format {
324 return Err(invalid(
325 "Paragraph styles and paragraph formatting cannot be edited",
326 ));
327 }
328 match (&paragraph.content, &previous.content) {
329 (ParagraphContent::Text(text), ParagraphContent::Text(previous)) => {
330 if text.date_field != previous.date_field {
331 return Err(invalid("Text fields cannot be edited"));
332 }
333 }
334 (ParagraphContent::Table(table), ParagraphContent::Table(previous)) => {
335 if table.id != previous.id
336 || table.layout != previous.layout
337 || table.tags != previous.tags
338 {
339 return Err(invalid("Table identity, layout and tags cannot be edited"));
340 }
341 let cells: BTreeMap<ExGuid, &TableCell> = previous
342 .rows
343 .iter()
344 .flat_map(|row| &row.cells)
345 .map(|cell| (cell.id, cell))
346 .collect();
347 let unchanged_cells = table.rows.iter().flat_map(|row| &row.cells).all(|cell| {
348 cells.get(&cell.id).is_none_or(|before| {
349 cell.layout == before.layout && cell.unsupported == before.unsupported
350 })
351 });
352 if !unchanged_cells {
353 return Err(invalid("Cell layout cannot be edited"));
354 }
355 }
356 (ParagraphContent::Unsupported(a), ParagraphContent::Unsupported(b)) if a == b => {}
357 (ParagraphContent::Ink(a), ParagraphContent::Ink(b)) => {
358 if a.id != b.id {
359 return Err(invalid("Ink identity cannot change"));
360 }
361 }
362 (ParagraphContent::Image(a), ParagraphContent::Image(b)) => {
363 super::content::picture_fixed_fields(a, b)?;
364 if (a.layout.x, a.layout.y) != (b.layout.x, b.layout.y) {
365 return Err(invalid("A paragraph picture has no position of its own"));
366 }
367 }
368 (ParagraphContent::Attachment(a), ParagraphContent::Attachment(b)) => {
369 if a.id != b.id {
370 return Err(invalid("Attachment identity cannot change"));
371 }
372 }
373 _ => return Err(invalid("Paragraph content type cannot change")),
374 }
375 }
376 Ok(())
377}
378
379/// Identities that keep their stored position: a longest increasing run of survivors,
380/// always including immovable ones. `after` may contain identities absent from `before`.
381pub(crate) fn kept_set(
382 before: &[ExGuid],
383 after: &[ExGuid],
384 movable: impl Fn(ExGuid) -> bool,
385) -> Result<BTreeSet<ExGuid>, Error> {
386 let position: BTreeMap<ExGuid, usize> =
387 after.iter().enumerate().map(|(i, id)| (*id, i)).collect();
388 let sequence: Vec<(ExGuid, usize)> = before
389 .iter()
390 .filter_map(|id| position.get(id).map(|at| (*id, *at)))
391 .collect();
392 let fixed: Vec<usize> = sequence
393 .iter()
394 .filter(|(id, _)| !movable(*id))
395 .map(|(_, at)| *at)
396 .collect();
397 if fixed.windows(2).any(|w| w[0] > w[1]) {
398 return Err(invalid(
399 "Images and unsupported objects cannot be reordered",
400 ));
401 }
402 if sequence.windows(2).all(|pair| pair[0].1 < pair[1].1) {
403 return Ok(sequence.into_iter().map(|(id, _)| id).collect());
404 }
405 // Longest increasing subsequence over `after` positions, forced through immovable items.
406 let n = sequence.len();
407 let mut best = vec![1usize; n];
408 let mut previous = vec![usize::MAX; n];
409 for i in 0..n {
410 let (id, at) = sequence[i];
411 let mandatory_before = sequence[..i]
412 .iter()
413 .filter(|(other, _)| !movable(*other))
414 .map(|(_, at)| *at)
415 .max();
416 if movable(id) && mandatory_before.is_some_and(|m| m > at) {
417 best[i] = 0;
418 continue;
419 }
420 let mandatory_after = sequence[i + 1..]
421 .iter()
422 .filter(|(other, _)| !movable(*other))
423 .map(|(_, at)| *at)
424 .min();
425 if movable(id) && mandatory_after.is_some_and(|m| m < at) {
426 best[i] = 0;
427 continue;
428 }
429 for j in 0..i {
430 if best[j] > 0 && sequence[j].1 < at && best[j] + 1 > best[i] {
431 best[i] = best[j] + 1;
432 previous[i] = j;
433 }
434 }
435 }
436 let mut kept = BTreeSet::new();
437 if let Some((mut i, _)) = best
438 .iter()
439 .enumerate()
440 .max_by_key(|(i, b)| (**b, usize::MAX - i))
441 && best[i] > 0
442 {
443 loop {
444 kept.insert(sequence[i].0);
445 if previous[i] == usize::MAX {
446 break;
447 }
448 i = previous[i];
449 }
450 }
451 for (id, _) in &sequence {
452 if !movable(*id) && !kept.contains(id) {
453 return Err(invalid(
454 "Images and unsupported objects cannot be reordered",
455 ));
456 }
457 }
458 Ok(kept)
459}
460
461/// The format of the span containing the UTF-16 position `at` (the following span at a
462/// boundary, the last at the end), which text inserted there takes.
463pub(crate) fn format_in(paragraph: &Paragraph, at: u32) -> Result<&Format, Error> {
464 let byte = paragraph.byte_offset(at)?;
465 let spans = paragraph.spans();
466 let index = spans
467 .partition_point(|span| span.end <= byte)
468 .min(spans.len() - 1);
469 Ok(&spans[index].format)
470}
471
472/// The smallest UTF-16 range whose replacement turns `before` into `after`.
473pub(crate) fn text_edit(before: &str, after: &str) -> Result<(Range<u32>, String), Error> {
474 let prefix = before
475 .char_indices()
476 .zip(after.chars())
477 .take_while(|((_, a), b)| a == b)
478 .map(|((i, a), _)| i + a.len_utf8())
479 .last()
480 .unwrap_or(0);
481 let suffix = before[prefix..]
482 .chars()
483 .rev()
484 .zip(after[prefix..].chars().rev())
485 .take_while(|(a, b)| a == b)
486 .map(|(a, _)| a.len_utf8())
487 .sum::<usize>();
488 let units = |s: &str| -> Result<u32, Error> {
489 u32::try_from(s.encode_utf16().count())
490 .map_err(|_| invalid("Text exceeds UTF-16 offset range"))
491 };
492 let start = units(&before[..prefix])?;
493 let end = start + units(&before[prefix..before.len() - suffix])?;
494 Ok((start..end, after[prefix..after.len() - suffix].to_owned()))
495}
496
497/// A character-format change over a range: attributes to set and properties to clear.
498pub(crate) type FormatEdit = (Range<u32>, Vec<TextAttribute>, Vec<TextProperty>);
499
500/// The format edits turning `stored`'s spans into `target`'s, which has the same text. A
501/// `fresh` text object was created by this edit, so an unset language keeps the writer's.
502pub(crate) fn format_edits(
503 stored: &Paragraph,
504 target: &Paragraph,
505 fresh: bool,
506) -> Result<Vec<FormatEdit>, Error> {
507 let mut boundaries = BTreeSet::new();
508 for paragraph in [stored, target] {
509 for span in paragraph.spans() {
510 boundaries.insert(paragraph.utf16_offset(span.end)?);
511 }
512 }
513 boundaries.insert(0);
514 let boundaries: Vec<u32> = boundaries.into_iter().collect();
515 let mut pending: Option<FormatEdit> = None;
516 let mut edits = Vec::new();
517 for window in boundaries.windows(2) {
518 let (start, end) = (window[0], window[1]);
519 if start == end {
520 continue;
521 }
522 let (set, clear) = attributes(format_in(stored, start)?, format_in(target, start)?, fresh)?;
523 match &mut pending {
524 Some((range, previous_set, previous_clear))
525 if *previous_set == set && *previous_clear == clear && range.end == start =>
526 {
527 range.end = end;
528 }
529 _ => {
530 if let Some(edit) = pending.take() {
531 edits.push(edit);
532 }
533 pending = Some((start..end, set, clear));
534 }
535 }
536 }
537 edits.extend(pending);
538 if target.text().is_empty() {
539 let (set, clear) = attributes(format_in(stored, 0)?, format_in(target, 0)?, fresh)?;
540 edits.push((0..0, set, clear));
541 }
542 edits.retain(|(_, set, clear)| !set.is_empty() || !clear.is_empty());
543 Ok(edits)
544}
545
546/// The change turning `current` into `target`; unsupported differences are errors. A value the
547/// target leaves unset is cleared from the run, as the model's formats are complete.
548fn attributes(
549 current: &Format,
550 target: &Format,
551 fresh: bool,
552) -> Result<(Vec<TextAttribute>, Vec<TextProperty>), Error> {
553 let mut out = Vec::new();
554 let mut cleared = Vec::new();
555 macro_rules! boolean {
556 ($field:ident, $variant:ident) => {
557 match target.$field {
558 Some(value) if current.$field != Some(value) => {
559 out.push(TextAttribute::$variant(value))
560 }
561 None if current.$field.is_some() => cleared.push(TextProperty::$variant),
562 _ => {}
563 }
564 };
565 }
566 boolean!(bold, Bold);
567 boolean!(italic, Italic);
568 boolean!(underline, Underline);
569 boolean!(strike, Strike);
570 boolean!(superscript, Superscript);
571 boolean!(subscript, Subscript);
572 boolean!(hidden, Hidden);
573 boolean!(hyperlink, Hyperlink);
574 boolean!(hyperlink_label, HyperlinkLabel);
575 boolean!(math, Math);
576 macro_rules! value {
577 ($field:ident, $property:ident, $attribute:expr) => {
578 if current.$field != target.$field {
579 match &target.$field {
580 Some(value) => out.push($attribute(value)),
581 None => cleared.push(TextProperty::$property),
582 }
583 }
584 };
585 }
586 let color =
587 |value: u32| (value != 0xff000000).then(|| value.to_le_bytes()[..3].try_into().unwrap());
588 value!(font, Font, |font: &String| TextAttribute::Font(font.clone()));
589 value!(font_size, FontSize, |size: &f32| TextAttribute::FontSize(
590 *size
591 ));
592 value!(color, Color, |value: &u32| TextAttribute::Color(color(
593 *value
594 )));
595 value!(highlight, Highlight, |value: &u32| {
596 TextAttribute::Highlight(color(*value))
597 });
598 if current.language != target.language {
599 match target.language {
600 Some(language) => out.push(TextAttribute::Language(language)),
601 None if fresh => {}
602 None => {
603 return Err(invalid("Inherited character formatting cannot be restored"));
604 }
605 }
606 }
607 // An absent value and its stored default are the same formatting.
608 let flag = |a: Option<bool>, b: Option<bool>| a.unwrap_or(false) == b.unwrap_or(false);
609 let points = |a: Option<f32>, b: Option<f32>| a.unwrap_or(0.0) == b.unwrap_or(0.0);
610 let same_rest = flag(current.embedded_object, target.embedded_object)
611 && current.alignment.unwrap_or(0) == target.alignment.unwrap_or(0)
612 && flag(current.rtl, target.rtl)
613 && points(current.space_before, target.space_before)
614 && points(current.space_after, target.space_after)
615 && points(current.line_spacing, target.line_spacing)
616 && points(current.list_spacing, target.list_spacing);
617 if !same_rest {
618 return Err(invalid(
619 "Fields and paragraph spacing cannot be edited through the page model",
620 ));
621 }
622 Ok((out, cleared))
623}
624
625/// Paragraph formatting a text object stores: alignment, direction, spacing and language.
626#[derive(Clone, Copy, Debug, Default, PartialEq)]
627pub(crate) struct ParagraphFields {
628 pub alignment: Option<u8>,
629 pub rtl: Option<bool>,
630 pub space_before: Option<f32>,
631 pub space_after: Option<f32>,
632 pub line_spacing: Option<f32>,
633 pub language: Option<u32>,
634}
635
636impl ParagraphFields {
637 pub(crate) fn values(&self) -> Result<Values, Error> {
638 super::properties::paragraph_values(
639 self.alignment,
640 self.rtl,
641 self.space_before,
642 self.space_after,
643 self.line_spacing,
644 self.language,
645 )
646 }
647}
648
649/// The paragraph formatting `target` sets throughout that `stored` lacks somewhere.
650pub(crate) fn paragraph_fields(
651 stored: &Paragraph,
652 target: &Paragraph,
653) -> Result<ParagraphFields, Error> {
654 let first = format_in(target, 0)?;
655 macro_rules! field {
656 ($field:ident) => {{
657 let value = first.$field.unwrap_or_default();
658 (target
659 .spans()
660 .iter()
661 .all(|span| span.format.$field.unwrap_or_default() == value)
662 && stored
663 .spans()
664 .iter()
665 .any(|span| span.format.$field.unwrap_or_default() != value))
666 .then_some(value)
667 }};
668 }
669 Ok(ParagraphFields {
670 alignment: field!(alignment),
671 rtl: field!(rtl),
672 space_before: field!(space_before),
673 space_after: field!(space_after),
674 line_spacing: field!(line_spacing),
675 language: field!(language),
676 })
677}
678
679/// `paragraph_fields` encoded, as the page writer stores them.
680pub(crate) fn paragraph_change(stored: &Paragraph, target: &Paragraph) -> Result<Values, Error> {
681 paragraph_fields(stored, target)?.values()
682}
683
684/// Whether two tag lists store the same tags.
685pub(crate) fn same_tags(a: &[Tag], b: &[Tag]) -> bool {
686 let key = |t: &Tag| {
687 (
688 t.definition,
689 t.action_type,
690 t.status,
691 t.created,
692 t.completed,
693 t.start,
694 t.due,
695 t.task_id,
696 )
697 };
698 a.len() == b.len() && a.iter().zip(b).all(|(x, y)| key(x) == key(y))
699}
700
701/// Whether the writers can split or join this text: ordinary text without generated fields,
702/// equations or embedded objects.
703fn ordinary(text: &TextObject) -> bool {
704 text.date_field.is_none()
705 && !text.text.text().contains('\u{fffc}')
706 && text.text.spans().iter().all(|span| {
707 span.format.math != Some(true) && span.format.embedded_object != Some(true)
708 })
709}
710
711/// Whether a split at `offset` would divide a hyperlink, which the writer refuses.
712fn divides_link(text: &Paragraph, offset: u32) -> Result<bool, Error> {
713 let byte = text.byte_offset(offset)?;
714 let link = |span: &crate::page::text::Span| span.format.hyperlink == Some(true);
715 let spans = text.spans();
716 let mut start = 0;
717 for (i, span) in spans.iter().enumerate() {
718 if start < byte && byte < span.end {
719 return Ok(link(span));
720 }
721 if byte == span.end && byte > 0 && i + 1 < spans.len() {
722 let next = &spans[i + 1];
723 return Ok(link(span) && link(next) && !text.text()[byte..].starts_with('\u{fddf}'));
724 }
725 start = span.end;
726 }
727 Ok(false)
728}
729
730/// A paragraph as `Insert` and `Add` create it: structure and content, without the
731/// properties their own ops set.
732fn bare(paragraph: &PageParagraph) -> PageParagraph {
733 let mut stripped = paragraph.clone();
734 stripped.style = None;
735 stripped.lists.clear();
736 stripped.tags.clear();
737 stripped.collapsed = false;
738 match &mut stripped.content {
739 ParagraphContent::Text(text) => text.tags.clear(),
740 ParagraphContent::Table(table) => {
741 for cell in table.rows.iter_mut().flat_map(|row| &mut row.cells) {
742 cell.paragraphs = cell.paragraphs.iter().map(bare).collect();
743 }
744 }
745 _ => {}
746 }
747 stripped
748}
749
750fn bare_cells(cells: &[TableCell]) -> Vec<TableCell> {
751 cells
752 .iter()
753 .map(|cell| TableCell {
754 paragraphs: cell.paragraphs.iter().map(bare).collect(),
755 ..cell.clone()
756 })
757 .collect()
758}
759
760struct Lowering {
761 ops: Vec<PageOp>,
762 /// The page as the ops so far leave it.
763 current: Page,
764 /// The paragraph after a partial model's range: its container and identity.
765 tail: Option<(ExGuid, ExGuid)>,
766 /// Paragraphs and text objects the ops so far created.
767 created: BTreeSet<ExGuid>,
768}
769
770impl Lowering {
771 fn new(current: Page, tail: Option<(ExGuid, ExGuid)>) -> Self {
772 Self {
773 ops: Vec::new(),
774 current,
775 tail,
776 created: BTreeSet::new(),
777 }
778 }
779
780 fn emit(&mut self, op: PageOp) -> Result<(), Error> {
781 model::apply(&mut self.current, &op)?;
782 self.ops.push(op);
783 Ok(())
784 }
785
786 /// Whether the page, as the ops so far leave it, holds `id` as a paragraph, text,
787 /// list node or table part.
788 fn holds(&self, id: ExGuid) -> bool {
789 model::holds(&self.current, id)
790 }
791
792 fn run(
793 mut self,
794 before: &Page,
795 after: &Page,
796 old: &View<'_>,
797 new: &View<'_>,
798 ) -> Result<Vec<PageOp>, Error> {
799 validate(before, after, old, new)?;
800 let mut placed: BTreeMap<ExGuid, Vec<ExGuid>> = old.children.clone();
801 let mut consumed = BTreeSet::new();
802 self.split_and_join(old, new, &mut placed, &mut consumed)?;
803 self.place_page(old, new)?;
804 self.place(old, new, &placed)?;
805 self.delete(old, new, &consumed)?;
806 self.levels(new)?;
807 self.equations(new)?;
808 self.text(new)?;
809 self.styles(after, new)?;
810 self.lists(after, new)?;
811 self.tags(after, new)?;
812 self.paragraph_formatting(new)?;
813 self.formatting(old, new)?;
814 self.layout(old, new)?;
815 Ok(self.ops)
816 }
817
818 /// Splits and joins stored paragraphs as OneNote's Enter, Backspace and Delete do.
819 fn split_and_join(
820 &mut self,
821 old: &View<'_>,
822 new: &View<'_>,
823 placed: &mut BTreeMap<ExGuid, Vec<ExGuid>>,
824 consumed: &mut BTreeSet<ExGuid>,
825 ) -> Result<(), Error> {
826 // A new paragraph may be split from the stored paragraph nearest before it, whatever
827 // placement or pasted paragraphs the same edit adds between them.
828 let mut splits = Vec::new();
829 for document in new.documents() {
830 let mut left = None;
831 for paragraph in document {
832 if old.paragraphs.contains_key(&paragraph.id) {
833 left = paragraph.text().is_some().then_some(paragraph.id);
834 } else if let Some(left) = left {
835 let container = new.container[&paragraph.id];
836 let at = new.children[&container]
837 .iter()
838 .position(|id| *id == paragraph.id);
839 splits.push(((container, at), left, paragraph.id));
840 }
841 }
842 }
843 splits.sort();
844 let mut split = BTreeSet::new();
845 for (_, left, right) in splits {
846 let (Some(previous), Some(after), Some(next)) =
847 (old.text(left), new.text(left), new.text(right))
848 else {
849 continue;
850 };
851 if split.contains(&left)
852 || after.id != previous.id
853 || new.paragraphs[&right].style != old.paragraphs[&left].style
854 || !ordinary(previous)
855 || old.title_outlines.contains(&root(old, left))
856 {
857 continue;
858 }
859 let Ok(offset) = after.text.utf16_offset(after.text.text().len()) else {
860 continue;
861 };
862 let length = previous.text.utf16_offset(previous.text.text().len())?;
863 // Splitting at the end only differs from appending a paragraph by the copied
864 // paragraph style; without one, an appended empty paragraph is an insertion.
865 if offset == length && new.paragraphs[&right].style.is_none() {
866 continue;
867 }
868 let (Ok(head), Ok(tail)) = (
869 previous.text.slice(0..offset),
870 previous.text.slice(offset..length),
871 ) else {
872 continue;
873 };
874 // An emptied side takes the writer's insertion style, so only its text must agree.
875 let same = |expected: &Paragraph, actual: &Paragraph| {
876 expected.text() == actual.text()
877 && (expected.text().is_empty() || expected == actual)
878 };
879 if !same(&head, &after.text)
880 || !same(&tail, &next.text)
881 || divides_link(&previous.text, offset)?
882 || old.paragraphs[&left].lists.len() > 251
883 {
884 continue;
885 }
886 // A text object a join moved into the paragraph keeps its identity there.
887 let right_text = if self.holds(next.id) {
888 new_id().map_err(|_| invalid("System random source failed"))?
889 } else {
890 next.id
891 };
892 // The split copies the left paragraph's list nodes in order; a node the model
893 // shares with a stored paragraph stays that paragraph's.
894 let stored_lists = old.paragraphs[&left].lists.len();
895 let model_lists = &new.paragraphs[&right].lists;
896 let mut lists = Vec::new();
897 for i in 0..stored_lists {
898 lists.push(match model_lists.get(i) {
899 Some(list) if model_lists.len() == stored_lists && !self.holds(*list) => *list,
900 _ => new_id().map_err(|_| invalid("System random source failed"))?,
901 });
902 }
903 self.emit(PageOp::Split {
904 text: previous.id,
905 at: offset,
906 paragraph: right,
907 right: right_text,
908 lists,
909 })?;
910 self.created.extend([right, right_text]);
911 split.insert(left);
912 // An earlier split may have carried the left paragraph to its tail.
913 let list = placed
914 .values_mut()
915 .find(|list| list.contains(&left))
916 .unwrap();
917 let at = list.iter().position(|id| *id == left).unwrap();
918 list.insert(at + 1, right);
919 placed.insert(right, placed[&left].clone());
920 placed.insert(left, Vec::new());
921 }
922 for (container, children) in &old.children {
923 if !new.children.contains_key(container) {
924 continue;
925 }
926 for pair in children.windows(2) {
927 let [left, right] = [pair[0], pair[1]];
928 let (Some(previous), Some(after), Some(removed)) =
929 (old.text(left), new.text(left), old.text(right))
930 else {
931 continue;
932 };
933 if new.paragraphs.contains_key(&right)
934 || !old.children[&left].is_empty()
935 || !old.children[&right].is_empty()
936 || removed.text.text().is_empty()
937 || !ordinary(previous)
938 || !ordinary(removed)
939 || old.title_outlines.contains(&root(old, left))
940 {
941 continue;
942 }
943 let mut joined = previous.text.clone();
944 if joined.append(removed.text.clone()).is_err() || joined != after.text {
945 continue;
946 }
947 let adopts_right = previous.text.text().is_empty();
948 if after.id != if adopts_right { removed.id } else { previous.id } {
949 continue;
950 }
951 self.emit(PageOp::Join {
952 left: previous.id,
953 right: removed.id,
954 })?;
955 consumed.insert(right);
956 placed.get_mut(container).unwrap().retain(|id| *id != right);
957 placed.remove(&right);
958 }
959 }
960 Ok(())
961 }
962
963 /// Adds, changes and orders what the page holds directly: outlines, pictures and ink.
964 fn place_page(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> {
965 let pictures = |page: &Page| -> BTreeMap<ExGuid, crate::page::Image> {
966 page.objects
967 .iter()
968 .filter_map(|object| match object {
969 PageObject::Image(image) => Some((image.id, image.clone())),
970 _ => None,
971 })
972 .collect()
973 };
974 let stored = pictures(old.page);
975 for (id, image) in pictures(new.page) {
976 if let Some(previous) = stored.get(&id)
977 && *previous != image
978 {
979 super::content::picture_fixed_fields(previous, &image)?;
980 self.emit(PageOp::Picture {
981 picture: id,
982 layout: image.layout.clone(),
983 alt: image.alt.clone(),
984 })?;
985 }
986 }
987 let inks = |page: &Page| -> BTreeMap<ExGuid, crate::page::Ink> {
988 page.objects
989 .iter()
990 .filter_map(|object| match object {
991 PageObject::Ink(ink) => Some((ink.id, ink.clone())),
992 _ => None,
993 })
994 .collect()
995 };
996 let stored = inks(old.page);
997 for (id, ink) in inks(new.page) {
998 if let Some(previous) = stored.get(&id)
999 && *previous != ink
1000 {
1001 self.strokes(previous, &ink)?;
1002 }
1003 }
1004 let title = |page: &Page, id: ExGuid| {
1005 page.objects
1006 .iter()
1007 .any(|object| matches!(object, PageObject::Title(title) if title.id == id))
1008 };
1009 let survivors: Vec<ExGuid> = old
1010 .page_children
1011 .iter()
1012 .copied()
1013 .filter(|id| {
1014 !title(old.page, *id) && new.page.objects.iter().any(|object| object.id() == *id)
1015 })
1016 .collect();
1017 let after: Vec<&PageObject> = new
1018 .page
1019 .objects
1020 .iter()
1021 .filter(|object| !title(new.page, object.id()))
1022 .collect();
1023 let order: Vec<ExGuid> = after.iter().map(|object| object.id()).collect();
1024 let kept = kept_set(&survivors, &order, |id| {
1025 new.outlines.contains_key(&id) && !new.title_outlines.contains(&id)
1026 })?;
1027 let mut next = None;
1028 for object in after.iter().rev() {
1029 let id = object.id();
1030 let stored = old.page_children.contains(&id);
1031 if !stored {
1032 if new.title_outlines.contains(&id) {
1033 return Err(invalid("Title outlines cannot be added"));
1034 }
1035 let object = match object {
1036 PageObject::Outline(outline) => {
1037 if outline.paragraphs.iter().any(|p| old.paragraphs.contains_key(&p.id)) {
1038 return Err(invalid(
1039 "A new outline holds new paragraphs; move stored ones after",
1040 ));
1041 }
1042 self.created.extend(model::identities(&outline.paragraphs));
1043 PageObject::Outline(Outline {
1044 paragraphs: outline.paragraphs.iter().map(bare).collect(),
1045 ..(*outline).clone()
1046 })
1047 }
1048 PageObject::Image(_) | PageObject::Ink(_) => (*object).clone(),
1049 PageObject::Title(_) | PageObject::Unsupported(_) => {
1050 return Err(invalid(
1051 "Titles and unsupported objects cannot be edited through the page model",
1052 ));
1053 }
1054 };
1055 self.emit(PageOp::Add {
1056 object,
1057 before: next,
1058 })?;
1059 } else if !kept.contains(&id) {
1060 self.emit(PageOp::Move {
1061 object: id,
1062 parent: None,
1063 before: next,
1064 })?;
1065 }
1066 next = Some(id);
1067 }
1068 Ok(())
1069 }
1070
1071 /// Inserts new paragraphs and moves stored ones to the containers and order the model
1072 /// gives them, then edits the stored tables and paragraph content.
1073 fn place(
1074 &mut self,
1075 old: &View<'_>,
1076 new: &View<'_>,
1077 placed: &BTreeMap<ExGuid, Vec<ExGuid>>,
1078 ) -> Result<(), Error> {
1079 let mut inserted: BTreeSet<ExGuid> = BTreeSet::new();
1080 // New outlines and cells, whose paragraphs arrive with them.
1081 let arriving: BTreeSet<ExGuid> = new
1082 .children
1083 .keys()
1084 .filter(|id| {
1085 !old.children.contains_key(id)
1086 && !new.paragraphs.contains_key(id)
1087 && !new.external.contains(id)
1088 })
1089 .copied()
1090 .collect();
1091 for container in new.containers() {
1092 if arriving.contains(&root(new, container)) {
1093 continue;
1094 }
1095 let Some(children) = new.children.get(&container) else {
1096 continue;
1097 };
1098 let current: Vec<ExGuid> = placed
1099 .get(&container)
1100 .map(|list| {
1101 list.iter()
1102 .copied()
1103 .filter(|id| new.paragraphs.contains_key(id))
1104 .collect()
1105 })
1106 .unwrap_or_default();
1107 let kept = kept_set(&current, children, |_| true)?;
1108 let tail = self
1109 .tail
1110 .filter(|(parent, _)| *parent == container)
1111 .map(|(_, id)| id);
1112 let mut next = tail;
1113 let mut run: Vec<ExGuid> = Vec::new();
1114 for id in children.iter().rev() {
1115 let fresh = !old.paragraphs.contains_key(id) && !self.created.contains(id);
1116 if fresh && !inserted.contains(id) {
1117 run.push(*id);
1118 continue;
1119 }
1120 self.insert(container, next, &mut run, new, &mut inserted)?;
1121 if !kept.contains(id) && !inserted.contains(id) {
1122 self.emit(PageOp::Move {
1123 object: *id,
1124 parent: Some(container),
1125 before: next,
1126 })?;
1127 }
1128 next = Some(*id);
1129 }
1130 self.insert(container, next, &mut run, new, &mut inserted)?;
1131 }
1132 self.tables(old, new)?;
1133 self.content(old, new)?;
1134 Ok(())
1135 }
1136
1137 /// Inserts `run`, new siblings in reverse order, with their new descendants.
1138 fn insert(
1139 &mut self,
1140 container: ExGuid,
1141 before: Option<ExGuid>,
1142 run: &mut Vec<ExGuid>,
1143 new: &View<'_>,
1144 inserted: &mut BTreeSet<ExGuid>,
1145 ) -> Result<(), Error> {
1146 if run.is_empty() {
1147 return Ok(());
1148 }
1149 run.reverse();
1150 let mut paragraphs = Vec::new();
1151 let mut pending: Vec<ExGuid> = run.drain(..).rev().collect();
1152 while let Some(id) = pending.pop() {
1153 let paragraph = new.paragraphs[&id];
1154 paragraphs.push(bare(paragraph));
1155 inserted.insert(id);
1156 self.created.insert(id);
1157 if let Some(text) = paragraph.text() {
1158 self.created.insert(text.id);
1159 }
1160 if let ParagraphContent::Table(table) = &paragraph.content {
1161 for cell in table.rows.iter().flat_map(|row| &row.cells) {
1162 self.created.extend(model::identities(&cell.paragraphs));
1163 }
1164 }
1165 // New children follow their parent; stored ones move there later.
1166 pending.extend(
1167 new.children[&id]
1168 .iter()
1169 .rev()
1170 .filter(|child| !self.holds(**child) && !inserted.contains(child)),
1171 );
1172 }
1173 self.emit(PageOp::Insert {
1174 container,
1175 before,
1176 paragraphs,
1177 })
1178 }
1179
1180 /// Row, column, width, border and cell edits of stored tables.
1181 fn tables(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> {
1182 let stored: BTreeMap<ExGuid, &Table> = old
1183 .paragraphs
1184 .values()
1185 .filter_map(|p| match &p.content {
1186 ParagraphContent::Table(table) => Some((table.id, table)),
1187 _ => None,
1188 })
1189 .collect();
1190 for paragraph in new.paragraphs.values() {
1191 let ParagraphContent::Table(table) = &paragraph.content else {
1192 continue;
1193 };
1194 let Some(previous) = stored.get(&table.id) else {
1195 continue;
1196 };
1197 self.table(previous, table)?;
1198 let cells: BTreeMap<ExGuid, &TableCell> = previous
1199 .rows
1200 .iter()
1201 .flat_map(|row| &row.cells)
1202 .map(|cell| (cell.id, cell))
1203 .collect();
1204 for cell in table.rows.iter().flat_map(|row| &row.cells) {
1205 let Some(before) = cells.get(&cell.id) else {
1206 continue;
1207 };
1208 if (cell.shading, &cell.indents) != (before.shading, &before.indents) {
1209 self.emit(PageOp::Table {
1210 table: table.id,
1211 edit: TableEdit::Cell {
1212 cell: cell.id,
1213 shading: cell.shading,
1214 indents: cell.indents.clone(),
1215 },
1216 })?;
1217 }
1218 }
1219 }
1220 Ok(())
1221 }
1222
1223 /// The row and column edits turning stored table `before` into `after`.
1224 fn table(&mut self, before: &Table, after: &Table) -> Result<(), Error> {
1225 let id = after.id;
1226 let unsupported = || invalid("Table rows and cells keep their order; add or remove them");
1227 let old_rows: Vec<ExGuid> = before.rows.iter().map(|row| row.id).collect();
1228 let new_rows: Vec<ExGuid> = after.rows.iter().map(|row| row.id).collect();
1229 let kept_rows: Vec<ExGuid> = old_rows
1230 .iter()
1231 .copied()
1232 .filter(|row| new_rows.contains(row))
1233 .collect();
1234 if new_rows
1235 .iter()
1236 .filter(|row| old_rows.contains(row))
1237 .ne(kept_rows.iter())
1238 {
1239 return Err(unsupported());
1240 }
1241 // The column pattern every kept row shares: which stored cells stay, which are new.
1242 let mut deleted: Option<Vec<u32>> = None;
1243 let mut added: Option<Vec<u32>> = None;
1244 for row in &kept_rows {
1245 let old_cells: Vec<ExGuid> = before.rows.iter().find(|r| r.id == *row).unwrap().cells
1246 .iter()
1247 .map(|cell| cell.id)
1248 .collect();
1249 let new_cells: Vec<ExGuid> = after.rows.iter().find(|r| r.id == *row).unwrap().cells
1250 .iter()
1251 .map(|cell| cell.id)
1252 .collect();
1253 let gone: Vec<u32> = (0..old_cells.len() as u32)
1254 .filter(|i| !new_cells.contains(&old_cells[*i as usize]))
1255 .collect();
1256 let fresh: Vec<u32> = (0..new_cells.len() as u32)
1257 .filter(|i| !old_cells.contains(&new_cells[*i as usize]))
1258 .collect();
1259 if old_cells
1260 .iter()
1261 .filter(|cell| new_cells.contains(cell))
1262 .ne(new_cells.iter().filter(|cell| old_cells.contains(cell)))
1263 || deleted.as_ref().is_some_and(|known| *known != gone)
1264 || added.as_ref().is_some_and(|known| *known != fresh)
1265 {
1266 return Err(unsupported());
1267 }
1268 deleted = Some(gone);
1269 added = Some(fresh);
1270 }
1271 let (deleted, added) = (deleted.unwrap_or_default(), added.unwrap_or_default());
1272 if kept_rows.is_empty() && before.columns.len() != after.columns.len() {
1273 return Err(unsupported());
1274 }
1275 for row in &old_rows {
1276 if !new_rows.contains(row) && !kept_rows.is_empty() {
1277 self.emit(PageOp::Table {
1278 table: id,
1279 edit: TableEdit::DeleteRow(*row),
1280 })?;
1281 }
1282 }
1283 for at in deleted.iter().rev() {
1284 self.emit(PageOp::Table {
1285 table: id,
1286 edit: TableEdit::DeleteColumn(*at),
1287 })?;
1288 }
1289 for at in &added {
1290 let cells = kept_rows
1291 .iter()
1292 .map(|row| {
1293 let row = after.rows.iter().find(|r| r.id == *row).unwrap();
1294 bare_cells(&row.cells[*at as usize..=*at as usize]).remove(0)
1295 })
1296 .collect::<Vec<_>>();
1297 for cell in &cells {
1298 self.created.extend(model::identities(&cell.paragraphs));
1299 }
1300 self.emit(PageOp::Table {
1301 table: id,
1302 edit: TableEdit::Column {
1303 at: *at,
1304 width: after.columns[*at as usize].width,
1305 cells,
1306 },
1307 })?;
1308 }
1309 // New rows, each run before the stored row that follows it.
1310 let mut run = Vec::new();
1311 let mut next = None;
1312 for row in after.rows.iter().rev() {
1313 if old_rows.contains(&row.id) {
1314 self.rows(id, next, &mut run)?;
1315 next = Some(row.id);
1316 } else {
1317 run.push(row.clone());
1318 }
1319 }
1320 self.rows(id, next, &mut run)?;
1321 if kept_rows.is_empty() {
1322 for row in &old_rows {
1323 self.emit(PageOp::Table {
1324 table: id,
1325 edit: TableEdit::DeleteRow(*row),
1326 })?;
1327 }
1328 }
1329 let current = model::table(&self.current, id)
1330 .ok_or_else(|| invalid("A table is missing after row edits"))?;
1331 if current.columns != after.columns {
1332 self.emit(PageOp::Table {
1333 table: id,
1334 edit: TableEdit::Columns(after.columns.clone()),
1335 })?;
1336 }
1337 let current = model::table(&self.current, id).unwrap();
1338 if current.borders != after.borders
1339 && let Some(borders) = after.borders
1340 {
1341 self.emit(PageOp::Table {
1342 table: id,
1343 edit: TableEdit::Borders(borders),
1344 })?;
1345 }
1346 Ok(())
1347 }
1348
1349 fn rows(
1350 &mut self,
1351 table: ExGuid,
1352 before: Option<ExGuid>,
1353 run: &mut Vec<crate::page::TableRow>,
1354 ) -> Result<(), Error> {
1355 if run.is_empty() {
1356 return Ok(());
1357 }
1358 run.reverse();
1359 let rows: Vec<crate::page::TableRow> = run
1360 .drain(..)
1361 .map(|row| crate::page::TableRow {
1362 id: row.id,
1363 cells: bare_cells(&row.cells),
1364 })
1365 .collect();
1366 for cell in rows.iter().flat_map(|row| &row.cells) {
1367 self.created.extend(model::identities(&cell.paragraphs));
1368 }
1369 self.emit(PageOp::Table {
1370 table,
1371 edit: TableEdit::Rows { before, rows },
1372 })
1373 }
1374
1375 /// Edits of stored pictures, ink and attachments that paragraphs hold.
1376 fn content(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> {
1377 for (id, paragraph) in &new.paragraphs {
1378 let Some(previous) = old.paragraphs.get(id) else {
1379 continue;
1380 };
1381 match (&previous.content, &paragraph.content) {
1382 (ParagraphContent::Image(stored), ParagraphContent::Image(image))
1383 if stored != image =>
1384 {
1385 self.emit(PageOp::Picture {
1386 picture: image.id,
1387 layout: image.layout.clone(),
1388 alt: image.alt.clone(),
1389 })?;
1390 }
1391 (ParagraphContent::Ink(stored), ParagraphContent::Ink(ink)) if stored != ink => {
1392 self.strokes(stored, ink)?;
1393 }
1394 (ParagraphContent::Attachment(stored), ParagraphContent::Attachment(attachment))
1395 if stored != attachment =>
1396 {
1397 self.emit(PageOp::Attachment {
1398 attachment: attachment.id,
1399 filename: attachment.filename.clone(),
1400 source_path: attachment.source_path.clone(),
1401 size: attachment.size,
1402 })?;
1403 }
1404 _ => {}
1405 }
1406 }
1407 Ok(())
1408 }
1409
1410 fn strokes(&mut self, stored: &crate::page::Ink, ink: &crate::page::Ink) -> Result<(), Error> {
1411 if stored.layout != ink.layout || stored.groups != ink.groups {
1412 return Err(invalid("Ink position and groups stay as stored"));
1413 }
1414 let mut add = Vec::new();
1415 for stroke in &ink.strokes {
1416 match stored.strokes.iter().find(|s| s.id == stroke.id) {
1417 Some(previous) if previous == stroke => {}
1418 Some(_) => return Err(invalid("A stored stroke keeps its path and pen")),
1419 None => add.push(stroke.clone()),
1420 }
1421 }
1422 let remove: Vec<ExGuid> = stored
1423 .strokes
1424 .iter()
1425 .filter(|stroke| !ink.strokes.iter().any(|s| s.id == stroke.id))
1426 .map(|stroke| stroke.id)
1427 .collect();
1428 if ink.strokes.iter().map(|s| s.id).ne(stored
1429 .strokes
1430 .iter()
1431 .filter(|s| !remove.contains(&s.id))
1432 .map(|s| s.id)
1433 .chain(add.iter().map(|s| s.id)))
1434 {
1435 return Err(invalid("Stored strokes keep their order; new ones follow"));
1436 }
1437 self.emit(PageOp::Strokes {
1438 ink: ink.id,
1439 add,
1440 remove,
1441 })
1442 }
1443
1444 fn delete(
1445 &mut self,
1446 old: &View<'_>,
1447 new: &View<'_>,
1448 consumed: &BTreeSet<ExGuid>,
1449 ) -> Result<(), Error> {
1450 let removed_outline =
1451 |id: ExGuid| old.outlines.contains_key(&id) && !new.outlines.contains_key(&id);
1452 for id in old.paragraphs.keys() {
1453 if new.paragraphs.contains_key(id) || consumed.contains(id) {
1454 continue;
1455 }
1456 let mut ancestor = old.container[id];
1457 let mut covered = false;
1458 // A surviving ancestor is placed where the model says, carrying this paragraph along.
1459 while !new.paragraphs.contains_key(&ancestor) {
1460 if removed_outline(ancestor)
1461 || (old.paragraphs.contains_key(&ancestor) && !consumed.contains(&ancestor))
1462 || (old.children.contains_key(&ancestor)
1463 && !old.paragraphs.contains_key(&ancestor)
1464 && !old.outlines.contains_key(&ancestor)
1465 && !new.children.contains_key(&ancestor)
1466 && !old.external.contains(&ancestor))
1467 {
1468 covered = true;
1469 break;
1470 }
1471 match old.container.get(&ancestor) {
1472 Some(parent) => ancestor = *parent,
1473 None => break,
1474 }
1475 }
1476 if !covered {
1477 self.emit(PageOp::Delete { object: *id })?;
1478 }
1479 }
1480 let kept: BTreeSet<ExGuid> = new.page.objects.iter().map(PageObject::id).collect();
1481 for object in &old.page.objects {
1482 if matches!(object, PageObject::Image(_) | PageObject::Ink(_))
1483 && !kept.contains(&object.id())
1484 {
1485 self.emit(PageOp::Delete {
1486 object: object.id(),
1487 })?;
1488 }
1489 }
1490 for id in old.outlines.keys() {
1491 if removed_outline(*id) {
1492 if old.title_outlines.contains(id) {
1493 return Err(invalid("Title outlines cannot be removed"));
1494 }
1495 self.emit(PageOp::Delete { object: *id })?;
1496 }
1497 }
1498 Ok(())
1499 }
1500
1501 /// Gives paragraphs the levels the model does, parents first.
1502 fn levels(&mut self, new: &View<'_>) -> Result<(), Error> {
1503 for document in new.documents() {
1504 for paragraph in document {
1505 let current = model::paragraph(&self.current, paragraph.id)
1506 .ok_or_else(|| invalid("A paragraph is missing after structural edits"))?;
1507 if current.level != paragraph.level {
1508 self.emit(PageOp::Level {
1509 paragraph: paragraph.id,
1510 level: paragraph.level,
1511 })?;
1512 }
1513 }
1514 }
1515 Ok(())
1516 }
1517
1518 fn equations(&mut self, new: &View<'_>) -> Result<(), Error> {
1519 for (id, paragraph) in &new.paragraphs {
1520 let Some(text) = paragraph.text() else {
1521 continue;
1522 };
1523 let stored = model::paragraph(&self.current, *id).and_then(|p| p.text().cloned());
1524 // Deleting an equation leaves no math in the model, but its stored runs are still math.
1525 if !crate::page::Math::is_equation(&text.text)
1526 && !stored
1527 .as_ref()
1528 .is_some_and(|stored| crate::page::Math::is_equation(&stored.text))
1529 {
1530 continue;
1531 }
1532 let stored =
1533 stored.ok_or_else(|| invalid("An equation paragraph is missing after placement"))?;
1534 if stored.text != text.text {
1535 self.emit(PageOp::Equation {
1536 text: stored.id,
1537 math: text.text.clone(),
1538 })?;
1539 }
1540 }
1541 Ok(())
1542 }
1543
1544 fn text(&mut self, new: &View<'_>) -> Result<(), Error> {
1545 for (id, paragraph) in &new.paragraphs {
1546 let Some(text) = paragraph.text() else {
1547 continue;
1548 };
1549 let Some(stored) = model::paragraph(&self.current, *id).and_then(|p| p.text().cloned())
1550 else {
1551 return Err(invalid("A paragraph is missing after structural edits"));
1552 };
1553 if stored.id != text.id {
1554 return Err(invalid("Text object identities cannot change"));
1555 }
1556 if stored.text.text() == text.text.text() {
1557 continue;
1558 }
1559 let (range, with) = text_edit(stored.text.text(), text.text.text())?;
1560 self.emit(PageOp::Text {
1561 text: stored.id,
1562 range,
1563 with,
1564 })?;
1565 }
1566 Ok(())
1567 }
1568
1569 fn styles(&mut self, after: &Page, new: &View<'_>) -> Result<(), Error> {
1570 for (id, paragraph) in &new.paragraphs {
1571 let Some(style) = paragraph.style else {
1572 continue;
1573 };
1574 let current = model::paragraph(&self.current, *id)
1575 .ok_or_else(|| invalid("A paragraph is missing after text edits"))?;
1576 if current.style == Some(style) {
1577 continue;
1578 }
1579 if paragraph.text().is_none() {
1580 return Err(invalid("Only text paragraphs take a paragraph style"));
1581 }
1582 let definition = after
1583 .definitions
1584 .get(&style)
1585 .ok_or_else(|| invalid("A paragraph references a missing style definition"))?;
1586 self.emit(PageOp::Style {
1587 paragraph: *id,
1588 style,
1589 definition: definition.clone(),
1590 })?;
1591 }
1592 Ok(())
1593 }
1594
1595 /// Gives each paragraph the list nodes its model references; one another paragraph
1596 /// owns is copied, as native list nodes belong to one paragraph.
1597 fn lists(&mut self, after: &Page, new: &View<'_>) -> Result<(), Error> {
1598 for (id, paragraph) in &new.paragraphs {
1599 let current = model::paragraph(&self.current, *id)
1600 .ok_or_else(|| invalid("A paragraph is missing after text edits"))?;
1601 let same = paragraph.lists.len() == current.lists.len()
1602 && paragraph.lists.iter().zip(&current.lists).all(|(model, node)| {
1603 model == node
1604 && after.definitions.get(model) == self.current.definitions.get(node)
1605 });
1606 if same {
1607 continue;
1608 }
1609 let own = current.lists.clone();
1610 let mut lists = Vec::new();
1611 for list in &paragraph.lists {
1612 let definition = after
1613 .definitions
1614 .get(list)
1615 .ok_or_else(|| invalid("A paragraph references a missing list definition"))?;
1616 let node = if own.contains(list) || !model::owns_list(&self.current, *list) {
1617 *list
1618 } else {
1619 new_id().map_err(|_| invalid("System random source failed"))?
1620 };
1621 lists.push((node, definition.clone()));
1622 }
1623 self.emit(PageOp::List {
1624 paragraph: *id,
1625 lists,
1626 })?;
1627 }
1628 Ok(())
1629 }
1630
1631 fn tags(&mut self, after: &Page, new: &View<'_>) -> Result<(), Error> {
1632 for (id, paragraph) in &new.paragraphs {
1633 let current = model::paragraph(&self.current, *id)
1634 .ok_or_else(|| invalid("A paragraph is missing after text edits"))?
1635 .clone();
1636 let mut targets = vec![(*id, &paragraph.tags, current.tags.clone())];
1637 if let (Some(text), Some(stored)) = (paragraph.text(), current.text()) {
1638 targets.push((text.id, &text.tags, stored.tags.clone()));
1639 }
1640 for (target, tags, stored) in targets {
1641 if same_tags(tags, &stored) {
1642 continue;
1643 }
1644 let mut definitions = Vec::new();
1645 for tag in tags {
1646 let definition = tag
1647 .definition
1648 .ok_or_else(|| invalid("A note tag names its definition"))?;
1649 if let Some(model) = after.definitions.get(&definition)
1650 && !definitions.iter().any(|(id, _)| *id == definition)
1651 {
1652 definitions.push((definition, model.clone()));
1653 }
1654 }
1655 self.emit(PageOp::Tags {
1656 target,
1657 tags: tags.clone(),
1658 definitions,
1659 })?;
1660 }
1661 }
1662 Ok(())
1663 }
1664
1665 fn paragraph_formatting(&mut self, new: &View<'_>) -> Result<(), Error> {
1666 for (id, paragraph) in &new.paragraphs {
1667 let Some(text) = paragraph.text() else {
1668 continue;
1669 };
1670 let stored = model::paragraph(&self.current, *id)
1671 .and_then(|p| p.text().cloned())
1672 .ok_or_else(|| invalid("A paragraph is missing after text edits"))?;
1673 let fields = paragraph_fields(&stored.text, &text.text)?;
1674 if fields == ParagraphFields::default() {
1675 continue;
1676 }
1677 if text.date_field.is_some() {
1678 return Err(invalid(
1679 "Generated title fields cannot be formatted as ordinary text",
1680 ));
1681 }
1682 self.emit(PageOp::Paragraph {
1683 paragraph: *id,
1684 alignment: fields.alignment,
1685 rtl: fields.rtl,
1686 space_before: fields.space_before,
1687 space_after: fields.space_after,
1688 line_spacing: fields.line_spacing,
1689 language: fields.language,
1690 })?;
1691 }
1692 Ok(())
1693 }
1694
1695 fn formatting(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> {
1696 let stored_texts: BTreeSet<ExGuid> = old
1697 .paragraphs
1698 .values()
1699 .filter_map(|p| Some(p.text()?.id))
1700 .collect();
1701 for (id, paragraph) in &new.paragraphs {
1702 let Some(text) = paragraph.text() else {
1703 continue;
1704 };
1705 let stored = model::paragraph(&self.current, *id)
1706 .and_then(|p| p.text().cloned())
1707 .ok_or_else(|| invalid("A paragraph is missing after text edits"))?;
1708 if stored.text.text() != text.text.text() {
1709 return Err(invalid("Text edits did not converge on the model"));
1710 }
1711 let fresh = !stored_texts.contains(&text.id);
1712 for (range, set, clear) in format_edits(&stored.text, &text.text, fresh)? {
1713 self.emit(PageOp::Format {
1714 text: stored.id,
1715 range,
1716 set,
1717 clear,
1718 })?;
1719 }
1720 }
1721 Ok(())
1722 }
1723
1724 fn layout(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> {
1725 for (id, paragraph) in &new.paragraphs {
1726 let current = model::paragraph(&self.current, *id)
1727 .ok_or_else(|| invalid("A paragraph is missing after text edits"))?;
1728 if current.collapsed != paragraph.collapsed {
1729 self.emit(PageOp::Outline {
1730 object: *id,
1731 edit: crate::OutlineEdit::Collapsed(paragraph.collapsed),
1732 })?;
1733 }
1734 }
1735 for (id, outline) in &new.outlines {
1736 if new.title_outlines.contains(id) || !old.outlines.contains_key(id) {
1737 continue;
1738 }
1739 let stored = model::outline(&self.current, *id)
1740 .ok_or_else(|| invalid("An outline is missing after text edits"))?;
1741 let (stored_position, stored_width) = (
1742 (stored.layout.x, stored.layout.y),
1743 (stored.layout.max_width, stored.layout.width_set_by_user),
1744 );
1745 if (outline.layout.x, outline.layout.y) != stored_position {
1746 let (Some(x), Some(y)) = (outline.layout.x, outline.layout.y) else {
1747 return Err(invalid("An outline position needs both coordinates"));
1748 };
1749 self.emit(PageOp::Outline {
1750 object: *id,
1751 edit: crate::OutlineEdit::Position { x, y },
1752 })?;
1753 }
1754 if (outline.layout.max_width, outline.layout.width_set_by_user) != stored_width {
1755 let Some(points) = outline.layout.max_width else {
1756 return Err(invalid("An outline width cannot be removed"));
1757 };
1758 self.emit(PageOp::Outline {
1759 object: *id,
1760 edit: crate::OutlineEdit::Width {
1761 points,
1762 user_set: outline.layout.width_set_by_user == Some(true),
1763 },
1764 })?;
1765 }
1766 }
1767 Ok(())
1768 }
1769}
1770
1771/// The outline or cell a paragraph of `view` lies in.
1772fn root(view: &View<'_>, paragraph: ExGuid) -> ExGuid {
1773 let mut at = paragraph;
1774 while let Some(parent) = view.container.get(&at) {
1775 at = *parent;
1776 }
1777 at
1778}
crates/onestore/src/op/mod.rs created+265
...@@ -0,0 +1,265 @@
1//! Object-level edits of a section: what an editor emits, a queue stores and
2//! `Section::apply` writes. Identities of new objects are the emitter's, so applying an edit
3//! to another image of the section creates the same objects; UTF-16 code units measure text.
4
5use crate::{
6 ExGuid, OutlineEdit, PageCreation, PageEdit, TextAttribute,
7 document::{Layout, Tag},
8 page::{Definition, InkStroke, Page, PageObject, PageParagraph, Paragraph, TableCell,
9 TableColumn, TableRow},
10};
11use serde::{Deserialize, Serialize};
12use std::ops::Range;
13
14mod apply;
15pub(crate) mod content;
16pub(crate) mod levels;
17pub(crate) mod lower;
18pub(crate) mod model;
19pub(crate) mod properties;
20pub(crate) mod table;
21#[cfg(test)]
22mod tests;
23
24pub use lower::{lower, lower_page};
25
26/// Property identifiers with their encoded values.
27pub(crate) type Values = Vec<(u32, Vec<u8>)>;
28
29/// One user action, applied whole or not at all.
30#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
31pub struct Edit {
32 /// FILETIME when the action happened; the modification time of what it changes.
33 pub at: u64,
34 pub ops: Vec<Op>,
35}
36
37#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
38pub enum Op {
39 Page { space: ExGuid, op: PageOp },
40 Section(SectionOp),
41}
42
43/// A change to the page an object space holds. Targets are stored identities; paragraph
44/// properties name the paragraph, text edits its text object.
45#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
46pub enum PageOp {
47 /// Replaces a range; inserted text takes the format of the run it lands in, the
48 /// following run at a boundary except at the end.
49 Text {
50 text: ExGuid,
51 range: Range<u32>,
52 with: String,
53 },
54 /// Sets and clears character formatting over a range; an empty text's `0..0` is its
55 /// insertion format. Cleared properties are inherited again.
56 Format {
57 text: ExGuid,
58 range: Range<u32>,
59 set: Vec<TextAttribute>,
60 clear: Vec<TextProperty>,
61 },
62 /// Makes the range a hyperlink to `target`, its field code stored hidden before the
63 /// label, or with `None` removes the link around it, leaving the label as plain text.
64 Link {
65 text: ExGuid,
66 range: Range<u32>,
67 target: Option<String>,
68 },
69 /// Rewrites an equation's linear text and spans whole.
70 Equation { text: ExGuid, math: Paragraph },
71 /// Inserts paragraphs before a direct child of `container`, or last. Children follow
72 /// their parents in `paragraphs`; levels are absolute. Text keeps its spans' formats;
73 /// lists, tags, styles and collapse state are set by their own ops.
74 Insert {
75 container: ExGuid,
76 before: Option<ExGuid>,
77 paragraphs: Vec<PageParagraph>,
78 },
79 /// Enter at `at`: the text left of it stays, the rest moves to a new paragraph `paragraph`
80 /// with text object `right` and a copy of each list node under `lists`, in order.
81 Split {
82 text: ExGuid,
83 at: u32,
84 paragraph: ExGuid,
85 right: ExGuid,
86 lists: Vec<ExGuid>,
87 },
88 /// Appends the right text to the left one and removes the right paragraph; an empty
89 /// left text is replaced by the right text object.
90 Join { left: ExGuid, right: ExGuid },
91 /// Moves a subtree before a direct child of `parent`, or last; `None` names the page,
92 /// whose children are outlines, pictures and ink. A paragraph keeps its level where it
93 /// lies deeper than its new parent.
94 Move {
95 object: ExGuid,
96 parent: Option<ExGuid>,
97 before: Option<ExGuid>,
98 },
99 /// Removes a subtree; an emptied table cell receives an empty paragraph.
100 Delete { object: ExGuid },
101 /// Sets a paragraph's absolute outline level, regrouping its container.
102 Level { paragraph: ExGuid, level: u32 },
103 /// Outline position and width, or a paragraph's saved expansion state.
104 Outline { object: ExGuid, edit: OutlineEdit },
105 /// Paragraph formatting stored on a paragraph's text; `None` leaves a value as it is.
106 Paragraph {
107 paragraph: ExGuid,
108 alignment: Option<u8>,
109 rtl: Option<bool>,
110 space_before: Option<f32>,
111 space_after: Option<f32>,
112 line_spacing: Option<f32>,
113 language: Option<u32>,
114 },
115 /// References a paragraph style, created from its definition where the page lacks it.
116 Style {
117 paragraph: ExGuid,
118 style: ExGuid,
119 definition: Definition,
120 },
121 /// Replaces a paragraph's list nodes; a node another paragraph owns is copied.
122 List {
123 paragraph: ExGuid,
124 lists: Vec<(ExGuid, Definition)>,
125 },
126 /// Replaces the note tags of a paragraph, its text or a table; tag definitions the page
127 /// lacks are created from `definitions`.
128 Tags {
129 target: ExGuid,
130 tags: Vec<Tag>,
131 definitions: Vec<(ExGuid, Definition)>,
132 },
133 /// Adds an outline with its paragraphs, a picture with its payload, or ink, before a
134 /// page child or on top.
135 Add {
136 object: PageObject,
137 before: Option<ExGuid>,
138 },
139 /// A stored picture's position, displayed size and description.
140 Picture {
141 picture: ExGuid,
142 layout: Layout,
143 alt: Option<String>,
144 },
145 /// A stored attachment's shown name, recorded source path and icon size.
146 Attachment {
147 attachment: ExGuid,
148 filename: String,
149 source_path: Option<String>,
150 size: Option<[f32; 2]>,
151 },
152 /// Adds and erases whole strokes of stored ink.
153 Strokes {
154 ink: ExGuid,
155 add: Vec<InkStroke>,
156 remove: Vec<ExGuid>,
157 },
158 Table { table: ExGuid, edit: TableEdit },
159}
160
161#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
162pub enum TableEdit {
163 /// New rows before a row, or last, each with one cell per column.
164 Rows {
165 before: Option<ExGuid>,
166 rows: Vec<TableRow>,
167 },
168 /// A new column at `at`: one cell per row, in row order.
169 Column {
170 at: u32,
171 width: f32,
172 cells: Vec<TableCell>,
173 },
174 DeleteRow(ExGuid),
175 DeleteColumn(u32),
176 /// Every column's width and lock.
177 Columns(Vec<TableColumn>),
178 Borders(bool),
179 /// A cell's shading (`None` clears it) and indentation table.
180 Cell {
181 cell: ExGuid,
182 shading: Option<u32>,
183 indents: Vec<f32>,
184 },
185}
186
187#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
188pub enum SectionOp {
189 Create(PageCreation),
190 /// A page created with `creation` holding `page`'s content under the identities it
191 /// carries (`Page::copy` gives fresh ones); the created title stays.
192 Import { creation: PageCreation, page: Page },
193 /// Page moves and indentation, in order.
194 Pages(Vec<PageEdit>),
195 /// Removes pages permanently; their revisions stay stored.
196 Delete(Vec<ExGuid>),
197}
198
199/// A character property that `PageOp::Format` can clear so the text inherits it.
200#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
201pub enum TextProperty {
202 Bold,
203 Italic,
204 Underline,
205 Strike,
206 Superscript,
207 Subscript,
208 Hidden,
209 Hyperlink,
210 HyperlinkLabel,
211 Math,
212 Font,
213 FontSize,
214 Color,
215 Highlight,
216}
217
218impl TextProperty {
219 pub(crate) fn id(self) -> u32 {
220 match self {
221 Self::Bold => 0x08001c04,
222 Self::Italic => 0x08001c05,
223 Self::Underline => 0x08001c06,
224 Self::Strike => 0x08001c07,
225 Self::Superscript => 0x08001c08,
226 Self::Subscript => 0x08001c09,
227 Self::Hidden => 0x08001e16,
228 Self::Hyperlink => 0x08001e14,
229 Self::HyperlinkLabel => 0x08001e19,
230 Self::Math => 0x08003401,
231 Self::Font => 0x1c001c0a,
232 Self::FontSize => 0x10001c0b,
233 Self::Color => 0x14001c0c,
234 Self::Highlight => 0x14001c0d,
235 }
236 }
237}
238
239/// Why `Section::apply` refused an edit; the section is as it was before it.
240#[derive(Clone, Debug, PartialEq)]
241pub enum OpError {
242 /// An object an op names is not reachable in its space's active revision.
243 TargetUnavailable(ExGuid),
244 /// A new object's identity is already reachable.
245 DuplicateIdentity(ExGuid),
246 /// An anchor is no direct child of its container, or a move would make a cycle.
247 StructureChanged(&'static str),
248 /// The writers cannot make this change to this content.
249 Unsupported(&'static str),
250 /// Writing failed after the section began changing; it must be reopened.
251 Failed(crate::Error),
252}
253
254impl std::fmt::Display for OpError {
255 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 match self {
257 Self::TargetUnavailable(id) => write!(f, "{id} is not on the page"),
258 Self::DuplicateIdentity(id) => write!(f, "{id} already exists"),
259 Self::StructureChanged(message) | Self::Unsupported(message) => f.write_str(message),
260 Self::Failed(error) => write!(f, "{error}"),
261 }
262 }
263}
264
265impl std::error::Error for OpError {}
crates/onestore/src/op/model.rs created+1245
...@@ -0,0 +1,1245 @@
1//! Ops interpreted on a page model: what reading the page back after `Section::apply`
2//! yields, for the lowering's prediction and as the tests' oracle.
3
4use super::{PageOp, TableEdit, TextProperty, content::NATIVE_INDENTS, lower::format_in};
5use crate::{
6 Error, ExGuid, OutlineEdit, TextAttribute,
7 document::{Format, Kind},
8 page::{
9 Outline, Page, PageObject, PageParagraph, Paragraph, ParagraphContent, Table, TextObject,
10 },
11};
12use std::collections::BTreeSet;
13
14fn invalid(message: &'static str) -> Error {
15 Error { offset: 0, message }
16}
17
18/// A length in points as the store keeps it, in half inches.
19fn stored(points: f32) -> f32 {
20 points / 36.0 * 36.0
21}
22
23/// Where a flat paragraph list lies: a page object, a title outline, then the table cells
24/// on the way down, each as (paragraph index, row, cell).
25#[derive(Clone, Debug)]
26pub(crate) struct Path {
27 object: usize,
28 title_outline: Option<usize>,
29 cells: Vec<(usize, usize, usize)>,
30}
31
32/// The lists of a page, each with its path and owner (outline or cell identity).
33pub(crate) fn lists(page: &Page) -> Vec<(Path, ExGuid, &Vec<PageParagraph>)> {
34 fn cells<'p>(
35 path: &Path,
36 list: &'p [PageParagraph],
37 out: &mut Vec<(Path, ExGuid, &'p Vec<PageParagraph>)>,
38 ) {
39 for (index, paragraph) in list.iter().enumerate() {
40 if let ParagraphContent::Table(table) = &paragraph.content {
41 for (r, row) in table.rows.iter().enumerate() {
42 for (c, cell) in row.cells.iter().enumerate() {
43 let mut path = path.clone();
44 path.cells.push((index, r, c));
45 out.push((path.clone(), cell.id, &cell.paragraphs));
46 cells(&path, &cell.paragraphs, out);
47 }
48 }
49 }
50 }
51 }
52 let mut out = Vec::new();
53 for (object, value) in page.objects.iter().enumerate() {
54 let outlines: Vec<(Option<usize>, &Outline)> = match value {
55 PageObject::Outline(outline) => vec![(None, outline)],
56 PageObject::Title(title) => title.outlines.iter().enumerate().map(|(i, o)| (Some(i), o)).collect(),
57 _ => Vec::new(),
58 };
59 for (title_outline, outline) in outlines {
60 let path = Path {
61 object,
62 title_outline,
63 cells: Vec::new(),
64 };
65 out.push((path.clone(), outline.id, &outline.paragraphs));
66 cells(&path, &outline.paragraphs, &mut out);
67 }
68 }
69 out
70}
71
72pub(crate) fn list_at<'p>(page: &'p mut Page, path: &Path) -> &'p mut Vec<PageParagraph> {
73 let outline = match (&mut page.objects[path.object], path.title_outline) {
74 (PageObject::Outline(outline), None) => outline,
75 (PageObject::Title(title), Some(i)) => &mut title.outlines[i],
76 _ => unreachable!(),
77 };
78 let mut list = &mut outline.paragraphs;
79 for (index, r, c) in &path.cells {
80 let ParagraphContent::Table(table) = &mut list[*index].content else {
81 unreachable!()
82 };
83 list = &mut table.rows[*r].cells[*c].paragraphs;
84 }
85 list
86}
87
88/// The list holding paragraph `id`, and its index there.
89fn find(page: &Page, id: ExGuid) -> Option<(Path, usize)> {
90 lists(page).into_iter().find_map(|(path, _, list)| {
91 list.iter().position(|p| p.id == id).map(|index| (path, index))
92 })
93}
94
95/// The list a container's children lie in: its own for an outline or cell, the one
96/// holding it for a paragraph.
97fn container(page: &Page, id: ExGuid) -> Option<(Path, Option<usize>)> {
98 lists(page)
99 .into_iter()
100 .find_map(|(path, owner, list)| {
101 if owner == id {
102 Some((path, None))
103 } else {
104 list.iter()
105 .position(|p| p.id == id)
106 .map(|index| (path, Some(index)))
107 }
108 })
109}
110
111/// The entries of `list` in the subtree of the paragraph at `index`.
112fn subtree(list: &[PageParagraph], index: usize) -> std::ops::Range<usize> {
113 let mut members = BTreeSet::from([list[index].id]);
114 let mut end = index + 1;
115 while end < list.len() && list[end].parent.is_some_and(|p| members.contains(&p)) {
116 members.insert(list[end].id);
117 end += 1;
118 }
119 index..end
120}
121
122pub(crate) fn paragraph(page: &Page, id: ExGuid) -> Option<&PageParagraph> {
123 lists(page)
124 .into_iter()
125 .find_map(|(_, _, list)| list.iter().find(|p| p.id == id))
126}
127
128pub(crate) fn outline(page: &Page, id: ExGuid) -> Option<&Outline> {
129 page.objects.iter().find_map(|object| match object {
130 PageObject::Outline(outline) if outline.id == id => Some(outline),
131 _ => None,
132 })
133}
134
135pub(crate) fn table(page: &Page, id: ExGuid) -> Option<&Table> {
136 lists(page).into_iter().find_map(|(_, _, list)| {
137 list.iter().find_map(|p| match &p.content {
138 ParagraphContent::Table(table) if table.id == id => Some(table),
139 _ => None,
140 })
141 })
142}
143
144/// Whether a paragraph of the page lists `list` among its list nodes.
145pub(crate) fn owns_list(page: &Page, list: ExGuid) -> bool {
146 lists(page)
147 .into_iter()
148 .any(|(_, _, paragraphs)| paragraphs.iter().any(|p| p.lists.contains(&list)))
149}
150
151/// Identities of paragraphs and what they hold, tables included.
152pub(crate) fn identities(paragraphs: &[PageParagraph]) -> Vec<ExGuid> {
153 let mut out = Vec::new();
154 for paragraph in paragraphs {
155 out.push(paragraph.id);
156 match &paragraph.content {
157 ParagraphContent::Text(text) => out.push(text.id),
158 ParagraphContent::Table(table) => {
159 out.push(table.id);
160 for row in &table.rows {
161 out.push(row.id);
162 for cell in &row.cells {
163 out.push(cell.id);
164 out.extend(identities(&cell.paragraphs));
165 }
166 }
167 }
168 ParagraphContent::Image(image) => out.push(image.id),
169 ParagraphContent::Attachment(attachment) => out.push(attachment.id),
170 ParagraphContent::Ink(ink) => {
171 out.push(ink.id);
172 out.extend(ink.strokes.iter().map(|s| s.id));
173 }
174 ParagraphContent::Unsupported(unsupported) => out.push(unsupported.id),
175 }
176 out.extend(paragraph.lists.iter().copied());
177 }
178 out
179}
180
181/// Whether the page holds `id` as an object of the model.
182pub(crate) fn holds(page: &Page, id: ExGuid) -> bool {
183 page.objects.iter().any(|object| {
184 object.id() == id
185 || match object {
186 PageObject::Outline(outline) => identities(&outline.paragraphs).contains(&id),
187 PageObject::Title(title) => title
188 .outlines
189 .iter()
190 .any(|o| o.id == id || identities(&o.paragraphs).contains(&id)),
191 PageObject::Ink(ink) => ink.strokes.iter().any(|s| s.id == id),
192 PageObject::Image(_) | PageObject::Unsupported(_) => false,
193 }
194 })
195}
196
197/// The paragraph holding text object `text`: its list and index.
198fn text_holder(page: &Page, text: ExGuid) -> Option<(Path, usize)> {
199 lists(page).into_iter().find_map(|(path, _, list)| {
200 list.iter()
201 .position(|p| p.text().is_some_and(|t| t.id == text))
202 .map(|index| (path, index))
203 })
204}
205
206fn text_mut(page: &mut Page, text: ExGuid) -> Result<&mut TextObject, Error> {
207 let (path, index) = text_holder(page, text).ok_or_else(|| invalid("Text is not on the page"))?;
208 Ok(list_at(page, &path)[index].text_mut().unwrap())
209}
210
211fn paragraph_mut(page: &mut Page, id: ExGuid) -> Result<&mut PageParagraph, Error> {
212 let (path, index) = find(page, id).ok_or_else(|| invalid("Paragraph is not on the page"))?;
213 Ok(&mut list_at(page, &path)[index])
214}
215
216fn table_mut(page: &mut Page, id: ExGuid) -> Result<&mut Table, Error> {
217 let (path, index) = lists(page)
218 .into_iter()
219 .find_map(|(path, _, list)| {
220 list.iter()
221 .position(|p| matches!(&p.content, ParagraphContent::Table(t) if t.id == id))
222 .map(|index| (path, index))
223 })
224 .ok_or_else(|| invalid("Table is not on the page"))?;
225 let ParagraphContent::Table(table) = &mut list_at(page, &path)[index].content else {
226 unreachable!()
227 };
228 Ok(table)
229}
230
231/// `text` with a range replaced as the text writer does: inserted text takes the format of
232/// the run it lands in, and emptied text keeps its last run's.
233pub(crate) fn replace_text(
234 text: &Paragraph,
235 range: std::ops::Range<u32>,
236 with: &str,
237) -> Result<Paragraph, Error> {
238 let length = text.utf16_offset(text.text().len())?;
239 if range.start > range.end || range.end > length {
240 return Err(invalid("The edit range exceeds the text"));
241 }
242 // Replacing text with itself leaves its formatting as it is.
243 if text.text()[text.byte_offset(range.start)?..text.byte_offset(range.end)?] == *with {
244 return Ok(text.clone());
245 }
246 let format = format_in(text, range.start)?.clone();
247 let removed = range.end - range.start;
248 if length - removed == 0 && with.is_empty() {
249 return Ok(Paragraph::new(String::new(), text.spans().last().unwrap().format.clone()));
250 }
251 let mut result = text.clone();
252 result.apply(crate::page::text::Edit {
253 range,
254 replacement: Paragraph::new(with.to_owned(), format),
255 })?;
256 Ok(result)
257}
258
259/// `text` with every span taking `from`'s paragraph formatting, which the text object
260/// stores for all its runs.
261fn paragraph_formatted(text: &Paragraph, from: &Format) -> Paragraph {
262 let mut previous = 0;
263 Paragraph::from_runs(text.spans().iter().map(|span| {
264 let mut format = span.format.clone();
265 format.alignment = from.alignment;
266 format.rtl = from.rtl;
267 format.space_before = from.space_before;
268 format.space_after = from.space_after;
269 format.line_spacing = from.line_spacing;
270 let run = (text.text()[previous..span.end].to_owned(), format);
271 previous = span.end;
272 run
273 }))
274}
275
276fn set(format: &mut Format, attribute: &TextAttribute) {
277 let color = |value: &Option<[u8; 3]>| {
278 Some(value.map_or(0xff000000, |[r, g, b]| u32::from_le_bytes([r, g, b, 0])))
279 };
280 match attribute {
281 TextAttribute::Bold(v) => format.bold = Some(*v),
282 TextAttribute::Italic(v) => format.italic = Some(*v),
283 TextAttribute::Underline(v) => format.underline = Some(*v),
284 TextAttribute::Strike(v) => format.strike = Some(*v),
285 TextAttribute::Superscript(v) => {
286 format.superscript = Some(*v);
287 if *v {
288 format.subscript = Some(false);
289 }
290 }
291 TextAttribute::Subscript(v) => {
292 format.subscript = Some(*v);
293 if *v {
294 format.superscript = Some(false);
295 }
296 }
297 TextAttribute::Font(font) => format.font = Some(font.clone()),
298 TextAttribute::FontSize(size) => format.font_size = Some(*size),
299 TextAttribute::Color(value) => format.color = color(value),
300 TextAttribute::Highlight(value) => format.highlight = color(value),
301 TextAttribute::Language(language) => format.language = Some(*language),
302 TextAttribute::Hidden(v) => format.hidden = Some(*v),
303 TextAttribute::Hyperlink(v) => format.hyperlink = Some(*v),
304 TextAttribute::HyperlinkLabel(v) => format.hyperlink_label = Some(*v),
305 TextAttribute::Math(v) => format.math = Some(*v),
306 }
307}
308
309fn clear(format: &mut Format, property: TextProperty) {
310 match property {
311 TextProperty::Bold => format.bold = None,
312 TextProperty::Italic => format.italic = None,
313 TextProperty::Underline => format.underline = None,
314 TextProperty::Strike => format.strike = None,
315 TextProperty::Superscript => format.superscript = None,
316 TextProperty::Subscript => format.subscript = None,
317 TextProperty::Hidden => format.hidden = None,
318 TextProperty::Hyperlink => format.hyperlink = None,
319 TextProperty::HyperlinkLabel => format.hyperlink_label = None,
320 TextProperty::Math => format.math = None,
321 TextProperty::Font => format.font = None,
322 TextProperty::FontSize => format.font_size = None,
323 TextProperty::Color => format.color = None,
324 TextProperty::Highlight => format.highlight = None,
325 }
326}
327
328/// `text` with a range reformatted as the format writer does; a reformatted run states its
329/// language.
330pub(crate) fn format_text(
331 text: &Paragraph,
332 range: std::ops::Range<u32>,
333 attributes: &[TextAttribute],
334 cleared: &[TextProperty],
335 style: &Format,
336) -> Result<Paragraph, Error> {
337 let apply = |format: &Format| {
338 let mut format = format.clone();
339 for attribute in attributes {
340 set(&mut format, attribute);
341 }
342 for property in cleared {
343 clear(&mut format, *property);
344 }
345 // A cleared property shows the paragraph style's value.
346 format = format.inherit(style);
347 if format.language.is_none() {
348 format.language = Some(0x409);
349 }
350 format
351 };
352 if text.text().is_empty() {
353 return Ok(Paragraph::new(String::new(), apply(&text.spans()[0].format)));
354 }
355 let (start, end) = (text.byte_offset(range.start)?, text.byte_offset(range.end)?);
356 let mut runs = Vec::new();
357 let mut previous = 0;
358 for span in text.spans() {
359 for (from, to, selected) in [
360 (previous, start.clamp(previous, span.end), false),
361 (start.clamp(previous, span.end), end.clamp(previous, span.end), true),
362 (end.clamp(previous, span.end), span.end, false),
363 ] {
364 if from < to {
365 let format = if selected { apply(&span.format) } else { span.format.clone() };
366 runs.push((text.text()[from..to].to_owned(), format));
367 }
368 }
369 previous = span.end;
370 }
371 Ok(Paragraph::from_runs(runs))
372}
373
374/// A text as `Insert` stores it: an unset language is the writer's insertion language, and
375/// paragraph formatting is stored only where every span shares a value other than the default.
376fn inserted(text: &Paragraph) -> Paragraph {
377 let spans = text.spans();
378 macro_rules! shared {
379 ($field:ident, $type:ty, $store:expr) => {{
380 let first = spans[0].format.$field.unwrap_or_default();
381 (spans.iter().all(|s| s.format.$field.unwrap_or_default() == first)
382 && first != <$type>::default())
383 .then(|| $store(first))
384 }};
385 }
386 let alignment = shared!(alignment, u8, |v| v);
387 let rtl = shared!(rtl, bool, |v| v);
388 let space_before = shared!(space_before, f32, stored);
389 let space_after = shared!(space_after, f32, stored);
390 let line_spacing = shared!(line_spacing, f32, stored);
391 let mut previous = 0;
392 Paragraph::from_runs(spans.iter().map(|span| {
393 let mut format = span.format.clone();
394 format.language.get_or_insert(0x409);
395 format.alignment = alignment;
396 format.rtl = rtl;
397 format.space_before = space_before;
398 format.space_after = space_after;
399 format.line_spacing = line_spacing;
400 let run = (text.text()[previous..span.end].to_owned(), format);
401 previous = span.end;
402 run
403 }))
404}
405
406/// A stroke as the store keeps it: points on the HIMETRIC grid, pen size in HIMETRIC.
407fn stroked(stroke: &crate::page::InkStroke) -> crate::page::InkStroke {
408 let himetric = |points: f32| points * 2540.0 / 72.0 * 72.0 / 2540.0;
409 crate::page::InkStroke {
410 points: stroke
411 .points
412 .iter()
413 .map(|[x, y]| [crate::page::ink::snap(*x), crate::page::ink::snap(*y)])
414 .collect(),
415 width: himetric(stroke.width),
416 height: himetric(stroke.height),
417 ..stroke.clone()
418 }
419}
420
421fn inked(ink: &crate::page::Ink) -> crate::page::Ink {
422 crate::page::Ink {
423 strokes: ink.strokes.iter().map(stroked).collect(),
424 ..ink.clone()
425 }
426}
427
428/// Paragraphs as `Insert` stores them.
429fn insertion(paragraphs: &[PageParagraph]) -> Vec<PageParagraph> {
430 paragraphs
431 .iter()
432 .map(|paragraph| {
433 let mut paragraph = paragraph.clone();
434 match &mut paragraph.content {
435 ParagraphContent::Text(text) if !crate::page::Math::is_equation(&text.text) => {
436 text.text = inserted(&text.text);
437 }
438 // An equation is written whole, without paragraph formatting.
439 ParagraphContent::Text(text) => {
440 text.text = paragraph_formatted(&text.text, &Format::default());
441 }
442 ParagraphContent::Table(table) => {
443 for cell in table.rows.iter_mut().flat_map(|row| &mut row.cells) {
444 if cell.indents.is_empty() {
445 cell.indents = NATIVE_INDENTS.to_vec();
446 }
447 cell.paragraphs = insertion(&cell.paragraphs);
448 }
449 }
450 ParagraphContent::Ink(ink) => *ink = inked(ink),
451 _ => {}
452 }
453 paragraph
454 })
455 .collect()
456}
457
458/// Inserts `paragraphs` (roots then descendants) into `container` before `before`.
459fn insert(
460 page: &mut Page,
461 container: ExGuid,
462 before: Option<ExGuid>,
463 paragraphs: Vec<PageParagraph>,
464) -> Result<(), Error> {
465 let (path, owner) =
466 self::container(page, container).ok_or_else(|| invalid("The container is not on the page"))?;
467 let list = list_at(page, &path);
468 let at = match before {
469 Some(before) => list
470 .iter()
471 .position(|p| p.id == before)
472 .ok_or_else(|| invalid("The anchor is not on the page"))?,
473 None => match owner {
474 Some(index) => subtree(list, index).end,
475 None => list.len(),
476 },
477 };
478 list.splice(at..at, paragraphs);
479 Ok(())
480}
481
482/// The absolute level of the children of `container`.
483fn child_level(page: &Page, container: ExGuid) -> u32 {
484 paragraph(page, container).map_or(0, |p| p.level)
485}
486
487/// Shifts the levels of the subtree at `index` so its root lies at `level`.
488fn relevel(list: &mut [PageParagraph], index: usize, level: u32) {
489 let range = subtree(list, index);
490 let delta = i64::from(level) - i64::from(list[index].level);
491 for paragraph in &mut list[range] {
492 paragraph.level = (i64::from(paragraph.level) + delta) as u32;
493 }
494}
495
496/// The title a page's writers store after changing it: the title text's first line when
497/// it has one, which only an edit of that text changes, else the first line of body text.
498fn retitle(page: &mut Page, edited: Option<ExGuid>) {
499 let line = |text: &str| text.trim_start().split('\r').next().unwrap_or_default().to_owned();
500 match find_title_text(page) {
501 Some(title) => {
502 let text = text_of(page, title);
503 if line(&text).is_empty() {
504 automatic(page);
505 } else if edited == Some(title) {
506 page.title = line(&text);
507 }
508 }
509 None => automatic(page),
510 }
511}
512
513fn automatic(page: &mut Page) {
514 let mut roots: Vec<&PageObject> = page
515 .objects
516 .iter()
517 .filter(|object| !matches!(object, PageObject::Title(_)))
518 .collect();
519 roots.sort_by(|a, b| {
520 let (a, b) = (a.layout(), b.layout());
521 a.y.unwrap_or(0.0)
522 .total_cmp(&b.y.unwrap_or(0.0))
523 .then_with(|| a.x.unwrap_or(0.0).total_cmp(&b.x.unwrap_or(0.0)))
524 });
525 fn first(list: &[PageParagraph]) -> Option<&str> {
526 list.iter().find_map(|paragraph| match &paragraph.content {
527 ParagraphContent::Text(text) => {
528 Some(crate::edit::automatic_title(text.text.text())).filter(|t| !t.is_empty())
529 }
530 ParagraphContent::Table(table) => table
531 .rows
532 .iter()
533 .flat_map(|row| &row.cells)
534 .find_map(|cell| first(&cell.paragraphs)),
535 _ => None,
536 })
537 }
538 let title = roots
539 .into_iter()
540 .find_map(|object| match object {
541 PageObject::Outline(outline) => first(&outline.paragraphs),
542 _ => None,
543 })
544 .unwrap_or_default()
545 .to_owned();
546 page.title = title;
547}
548
549/// Applies `op` to `page` as reading the page back after `Section::apply` would show it.
550pub(crate) fn apply(page: &mut Page, op: &PageOp) -> Result<(), Error> {
551 interpret(page, op)?;
552 // A page lists the definitions its paragraphs reference.
553 let mut referenced = BTreeSet::new();
554 for (_, _, list) in lists(page) {
555 for paragraph in list {
556 referenced.extend(paragraph.lists.iter().copied());
557 referenced.extend(paragraph.style);
558 let tags = paragraph.tags.iter().chain(match &paragraph.content {
559 ParagraphContent::Text(text) => text.tags.as_slice(),
560 ParagraphContent::Table(table) => table.tags.as_slice(),
561 _ => &[],
562 });
563 referenced.extend(tags.filter_map(|tag| tag.definition));
564 }
565 }
566 page.definitions.retain(|id, _| referenced.contains(id));
567 Ok(())
568}
569
570fn interpret(page: &mut Page, op: &PageOp) -> Result<(), Error> {
571 match op {
572 PageOp::Text { text, range, with } => {
573 let object = text_mut(page, *text)?;
574 object.text = replace_text(&object.text, range.clone(), with)?;
575 retitle(page, Some(*text));
576 }
577 PageOp::Format {
578 text,
579 range,
580 set,
581 clear,
582 } => {
583 let (path, index) =
584 text_holder(page, *text).ok_or_else(|| invalid("Text is not on the page"))?;
585 let style = list_at(page, &path)[index]
586 .style
587 .and_then(|style| page.definitions.get(&style))
588 .map(|definition| definition.format.clone())
589 .unwrap_or_default();
590 let object = text_mut(page, *text)?;
591 object.text = format_text(&object.text, range.clone(), set, clear, &style)?;
592 }
593 PageOp::Link {
594 text,
595 range,
596 target,
597 } => {
598 let current = text_mut(page, *text)?.text.clone();
599 for op in super::apply::link_ops(&current, *text, range.clone(), target.as_deref())? {
600 interpret(page, &op)?;
601 }
602 }
603 PageOp::Equation { text, math } => {
604 let object = text_mut(page, *text)?;
605 let previous = object.text.spans()[0].format.clone();
606 object.text = paragraph_formatted(math, &previous);
607 }
608 PageOp::Insert {
609 container,
610 before,
611 paragraphs,
612 } => {
613 insert(page, *container, *before, insertion(paragraphs))?;
614 retitle(page, None);
615 }
616 PageOp::Split {
617 text,
618 at,
619 paragraph,
620 right,
621 lists,
622 } => {
623 let (path, index) = text_holder(page, *text).ok_or_else(|| invalid("Text is not on the page"))?;
624 let definitions: Vec<_> = {
625 let list = list_at(page, &path);
626 list[index].lists.clone()
627 };
628 let copies: Vec<_> = definitions
629 .iter()
630 .zip(lists)
631 .map(|(old, new)| {
632 let mut definition = page.definitions.get(old).cloned();
633 if let Some(crate::page::Definition {
634 kind: Kind::List { restart, .. },
635 ..
636 }) = &mut definition
637 {
638 *restart = None;
639 }
640 (*new, definition)
641 })
642 .collect();
643 for (id, definition) in copies {
644 if let Some(definition) = definition {
645 page.definitions.insert(id, definition);
646 }
647 }
648 let list = list_at(page, &path);
649 let left = &mut list[index];
650 let source = left.text().unwrap().text.clone();
651 let length = source.utf16_offset(source.text().len())?;
652 let (head, tail) = (source.slice(0..*at)?, source.slice(*at..length)?);
653 let mut new = left.clone();
654 left.text_mut().unwrap().text = head;
655 new.id = *paragraph;
656 new.lists = lists.clone();
657 new.media = Default::default();
658 new.content = ParagraphContent::Text(TextObject {
659 id: *right,
660 date_field: None,
661 text: tail,
662 tags: Vec::new(),
663 });
664 let left_id = list[index].id;
665 for child in &mut list[index + 1..] {
666 if child.parent == Some(left_id) {
667 child.parent = Some(*paragraph);
668 }
669 }
670 list.insert(index + 1, new);
671 retitle(page, None);
672 }
673 PageOp::Join { left, right } => {
674 let (path, index) = text_holder(page, *right).ok_or_else(|| invalid("Text is not on the page"))?;
675 let (left_path, left_index) =
676 text_holder(page, *left).ok_or_else(|| invalid("Text is not on the page"))?;
677 let list = list_at(page, &path);
678 let removed = list[index].clone();
679 let right_text = removed.text().unwrap().clone();
680 let definitions = page.definitions.clone();
681 let left_list = list_at(page, &left_path);
682 let left_style = left_list[left_index].style;
683 let adopts = left_list[left_index].text().unwrap().text.text().is_empty();
684 if adopts {
685 // The text object brings its paragraph style and recording link.
686 left_list[left_index].style = removed.style;
687 left_list[left_index].media = removed.media.clone();
688 }
689 let upper = left_list[left_index].text_mut().unwrap();
690 if adopts {
691 upper.id = right_text.id;
692 upper.text = right_text.text;
693 } else {
694 // The lower text keeps its look under the upper paragraph's style: a flag or
695 // colour only that style states becomes false or automatic.
696 let differs = left_style != removed.style;
697 let left = left_style
698 .and_then(|style| definitions.get(&style))
699 .map(|definition| definition.format.clone())
700 .unwrap_or_default();
701 let mut previous = 0;
702 let lower = Paragraph::from_runs(right_text.text.spans().iter().map(|span| {
703 let mut format = span.format.clone();
704 if differs {
705 for (field, base) in [
706 (&mut format.bold, left.bold),
707 (&mut format.italic, left.italic),
708 (&mut format.underline, left.underline),
709 (&mut format.strike, left.strike),
710 (&mut format.superscript, left.superscript),
711 (&mut format.subscript, left.subscript),
712 (&mut format.hidden, left.hidden),
713 (&mut format.hyperlink, left.hyperlink),
714 (&mut format.hyperlink_label, left.hyperlink_label),
715 (&mut format.math, left.math),
716 ] {
717 if field.is_none() && base.is_some() {
718 *field = Some(false);
719 }
720 }
721 for (field, base) in [
722 (&mut format.color, left.color),
723 (&mut format.highlight, left.highlight),
724 ] {
725 if field.is_none() && base.is_some() {
726 *field = Some(0xff000000);
727 }
728 }
729 }
730 let run = (right_text.text.text()[previous..span.end].to_owned(), format);
731 previous = span.end;
732 run
733 }));
734 let paragraph = upper.text.spans()[0].format.clone();
735 upper.text.append(lower)?;
736 upper.text = paragraph_formatted(&upper.text, &paragraph);
737 }
738 let left_id = left_list[left_index].id;
739 let list = list_at(page, &path);
740 // The right paragraph's children join the left paragraph's ancestor beside it,
741 // which is the outline group holding it where it lies deeper.
742 let mut stem = left_id;
743 while let Some(parent) = list.iter().find(|p| p.id == stem).and_then(|p| p.parent) {
744 if Some(parent) == removed.parent {
745 break;
746 }
747 stem = parent;
748 }
749 let range = subtree(list, index);
750 let stem_at = list.iter().position(|p| p.id == stem).unwrap();
751 let stem_level = list[stem_at].level;
752 if range.len() > 1 && stem_level > removed.level {
753 // The group takes the right paragraph's child level for all its members.
754 let level = list[range.start + 1].level;
755 let mut member = stem_at;
756 loop {
757 relevel(list, member, level);
758 let Some(previous) = list[..member]
759 .iter()
760 .rposition(|p| p.parent == removed.parent)
761 .filter(|at| list[*at].level == stem_level)
762 else {
763 break;
764 };
765 member = previous;
766 }
767 for child in &mut list[range.start + 1..range.end] {
768 if child.parent == Some(removed.id) {
769 child.parent = removed.parent;
770 }
771 }
772 } else {
773 let delta = i64::from(stem_level) - i64::from(removed.level);
774 for child in &mut list[range.start + 1..range.end] {
775 child.level = (i64::from(child.level) + delta) as u32;
776 if child.parent == Some(removed.id) {
777 child.parent = Some(stem);
778 }
779 }
780 }
781 list.remove(index);
782 retitle(page, None);
783 }
784 PageOp::Move {
785 object,
786 parent,
787 before,
788 } => {
789 let Some(parent) = parent else {
790 let from = page
791 .objects
792 .iter()
793 .position(|o| o.id() == *object)
794 .ok_or_else(|| invalid("The object is not on the page"))?;
795 let moved = page.objects.remove(from);
796 let at = page_position(page, *before)?;
797 page.objects.insert(at, moved);
798 retitle(page, None);
799 return Ok(());
800 };
801 let (path, index) = find(page, *object).ok_or_else(|| invalid("The paragraph is not on the page"))?;
802 let list = list_at(page, &path);
803 let mut moved: Vec<PageParagraph> = list.drain(subtree(list, index)).collect();
804 let into_cell = table_cells(page).contains(parent);
805 let base = child_level(page, *parent);
806 let level = if into_cell {
807 let (path, _) = self::container(page, *parent).unwrap();
808 list_at(page, &path)
809 .iter()
810 .find(|p| p.parent.is_none())
811 .map_or(1, |p| p.level)
812 } else if moved[0].level > base {
813 moved[0].level
814 } else {
815 base + 1
816 };
817 moved[0].parent = paragraph(page, *parent).map(|_| *parent);
818 relevel(&mut moved, 0, level);
819 insert(page, *parent, *before, moved)?;
820 drop_emptied(page);
821 retitle(page, None);
822 }
823 PageOp::Delete { object } => {
824 if let Some(at) = page.objects.iter().position(|o| o.id() == *object) {
825 page.objects.remove(at);
826 } else {
827 let (path, index) =
828 find(page, *object).ok_or_else(|| invalid("The object is not on the page"))?;
829 let list = list_at(page, &path);
830 let range = subtree(list, index);
831 if !path.cells.is_empty() && range.len() == list.len() {
832 return Err(invalid(
833 "A table cell keeps a paragraph; insert its replacement first",
834 ));
835 }
836 list.drain(range);
837 }
838 drop_emptied(page);
839 retitle(page, None);
840 }
841 PageOp::Level { paragraph, level } => {
842 let (path, index) = find(page, *paragraph).ok_or_else(|| invalid("The paragraph is not on the page"))?;
843 relevel(list_at(page, &path), index, *level);
844 }
845 PageOp::Outline { object, edit } => match edit {
846 OutlineEdit::Collapsed(value) => paragraph_mut(page, *object)?.collapsed = *value,
847 OutlineEdit::Position { x, y } => {
848 let outline = outline_mut(page, *object)?;
849 outline.layout.x = Some(stored(*x));
850 outline.layout.y = Some(stored(*y));
851 retitle(page, None);
852 }
853 OutlineEdit::Width { points, user_set } => {
854 let outline = outline_mut(page, *object)?;
855 outline.layout.max_width = Some(stored(*points));
856 outline.layout.width_set_by_user = Some(*user_set);
857 outline.layout.reserved_width = None;
858 }
859 },
860 PageOp::Paragraph {
861 paragraph,
862 alignment,
863 rtl,
864 space_before,
865 space_after,
866 line_spacing,
867 language,
868 } => {
869 let text = &mut paragraph_mut(page, *paragraph)?
870 .text_mut()
871 .ok_or_else(|| invalid("Paragraph formatting belongs to text"))?
872 .text;
873 let mut previous = 0;
874 let runs: Vec<(String, Format)> = text
875 .spans()
876 .iter()
877 .map(|span| {
878 let mut format = span.format.clone();
879 format.alignment = alignment.or(format.alignment);
880 format.rtl = rtl.or(format.rtl);
881 format.space_before = space_before.map(stored).or(format.space_before);
882 format.space_after = space_after.map(stored).or(format.space_after);
883 format.line_spacing = line_spacing.map(stored).or(format.line_spacing);
884 // A run's own language outranks the text's.
885 format.language = format.language.or(*language);
886 let run = (text.text()[previous..span.end].to_owned(), format);
887 previous = span.end;
888 run
889 })
890 .collect();
891 *text = Paragraph::from_runs(runs);
892 }
893 PageOp::Style {
894 paragraph,
895 style,
896 definition,
897 } => {
898 let old = paragraph_mut(page, *paragraph)?
899 .style
900 .and_then(|style| page.definitions.get(&style))
901 .map(|definition| definition.format.clone())
902 .unwrap_or_default();
903 let style = define(page, *style, definition);
904 let new = page.definitions[&style].format.clone();
905 let paragraph = paragraph_mut(page, *paragraph)?;
906 paragraph.style = Some(style);
907 if let Some(text) = paragraph.text_mut() {
908 let mut previous = 0;
909 let runs: Vec<(String, Format)> = text
910 .text
911 .spans()
912 .iter()
913 .map(|span| {
914 // What the old style gave, the new one gives instead.
915 let mut format = span.format.clone();
916 macro_rules! restyle {
917 ($($field:ident),*) => {$(
918 if old.$field.is_some() && format.$field == old.$field {
919 format.$field = new.$field.clone();
920 } else if format.$field.is_none() {
921 format.$field = new.$field.clone();
922 }
923 )*};
924 }
925 restyle!(bold, italic, underline, strike, superscript, subscript, hidden,
926 hyperlink, hyperlink_label, math, embedded_object, font, font_size,
927 color, highlight, language, alignment, rtl, space_before, space_after,
928 line_spacing, list_spacing, math_object);
929 let run = (text.text.text()[previous..span.end].to_owned(), format);
930 previous = span.end;
931 run
932 })
933 .collect();
934 text.text = Paragraph::from_runs(runs);
935 }
936 }
937 PageOp::List { paragraph, lists } => {
938 paragraph_mut(page, *paragraph)?.lists = lists.iter().map(|(id, _)| *id).collect();
939 for (id, definition) in lists {
940 page.definitions.insert(*id, definition.clone());
941 }
942 }
943 PageOp::Tags {
944 target,
945 tags,
946 definitions,
947 } => {
948 let mut tags = tags.clone();
949 for tag in &mut tags {
950 if let Some(id) = &mut tag.definition
951 && let Some((_, definition)) = definitions.iter().find(|(d, _)| d == id)
952 {
953 *id = define(page, *id, definition);
954 }
955 }
956 if let Ok(paragraph) = paragraph_mut(page, *target) {
957 paragraph.tags = tags;
958 } else if let Ok(text) = text_mut(page, *target) {
959 text.tags = tags;
960 } else {
961 table_mut(page, *target)?.tags = tags;
962 }
963 }
964 PageOp::Add { object, before } => {
965 let object = match object {
966 PageObject::Outline(outline) => {
967 let mut outline = outline.clone();
968 outline.paragraphs = insertion(&outline.paragraphs);
969 if outline.indents.is_empty() {
970 outline.indents = NATIVE_INDENTS.to_vec();
971 }
972 let layout = &mut outline.layout;
973 layout.x = layout.x.map(stored);
974 layout.y = layout.y.map(stored);
975 match layout.max_width {
976 Some(points) if (points, layout.width_set_by_user) != (468.0, None) => {
977 layout.max_width = Some(stored(points));
978 layout.width_set_by_user = Some(layout.width_set_by_user == Some(true));
979 }
980 _ => {
981 layout.max_width = Some(468.0);
982 layout.width_set_by_user = None;
983 }
984 }
985 layout.reserved_width = None;
986 layout.max_height = Some(0.6_f32 * 36.0);
987 PageObject::Outline(outline)
988 }
989 PageObject::Ink(ink) => PageObject::Ink(inked(ink)),
990 object => object.clone(),
991 };
992 let outline = matches!(object, PageObject::Outline(_));
993 let at = page_position(page, *before)?;
994 page.objects.insert(at, object);
995 if outline {
996 retitle(page, None);
997 }
998 }
999 PageOp::Picture {
1000 picture,
1001 layout,
1002 alt,
1003 } => {
1004 let image = image_mut(page, *picture)?;
1005 image.layout = crate::document::Layout {
1006 x: layout.x.map(stored),
1007 y: layout.y.map(stored),
1008 max_width: layout.max_width.map(stored),
1009 max_height: layout.max_height.map(stored),
1010 width_set_by_user: layout.max_width.map(|_| layout.width_set_by_user == Some(true)),
1011 reserved_width: image.layout.reserved_width,
1012 };
1013 image.alt = alt.clone();
1014 }
1015 PageOp::Attachment {
1016 attachment,
1017 filename,
1018 source_path,
1019 size,
1020 } => {
1021 let (path, index) = lists(page)
1022 .into_iter()
1023 .find_map(|(path, _, list)| {
1024 list.iter()
1025 .position(|p| matches!(&p.content, ParagraphContent::Attachment(a) if a.id == *attachment))
1026 .map(|index| (path, index))
1027 })
1028 .ok_or_else(|| invalid("The attachment is not on the page"))?;
1029 let ParagraphContent::Attachment(stored_attachment) = &mut list_at(page, &path)[index].content
1030 else {
1031 unreachable!()
1032 };
1033 stored_attachment.filename = filename.clone();
1034 stored_attachment.source_path = source_path.clone();
1035 stored_attachment.size = size.map(|[w, h]| [stored(w), stored(h)]);
1036 }
1037 PageOp::Strokes { ink, add, remove } => {
1038 let ink = ink_mut(page, *ink)?;
1039 ink.strokes.retain(|stroke| !remove.contains(&stroke.id));
1040 ink.strokes.extend(add.iter().map(stroked));
1041 }
1042 PageOp::Table { table, edit } => {
1043 let table = table_mut(page, *table)?;
1044 match edit {
1045 TableEdit::Rows { before, rows } => {
1046 let at = match before {
1047 Some(before) => table
1048 .rows
1049 .iter()
1050 .position(|r| r.id == *before)
1051 .ok_or_else(|| invalid("The anchor row is not in the table"))?,
1052 None => table.rows.len(),
1053 };
1054 let mut rows = rows.clone();
1055 for cell in rows.iter_mut().flat_map(|row| &mut row.cells) {
1056 if cell.indents.is_empty() {
1057 cell.indents = table
1058 .rows
1059 .first()
1060 .and_then(|r| r.cells.first())
1061 .map_or(NATIVE_INDENTS.to_vec(), |c| c.indents.clone());
1062 }
1063 cell.paragraphs = insertion(&cell.paragraphs);
1064 }
1065 table.rows.splice(at..at, rows);
1066 }
1067 TableEdit::Column { at, width, cells } => {
1068 let template = table
1069 .rows
1070 .first()
1071 .and_then(|r| r.cells.first())
1072 .map(|c| c.indents.clone());
1073 for (row, cell) in table.rows.iter_mut().zip(cells) {
1074 let mut cell = cell.clone();
1075 if cell.indents.is_empty() {
1076 cell.indents = template.clone().unwrap_or(NATIVE_INDENTS.to_vec());
1077 }
1078 cell.paragraphs = insertion(&cell.paragraphs);
1079 row.cells.insert(*at as usize, cell);
1080 }
1081 table.columns.insert(
1082 *at as usize,
1083 crate::page::TableColumn {
1084 width: stored(*width),
1085 locked: false,
1086 },
1087 );
1088 }
1089 TableEdit::DeleteRow(row) => table.rows.retain(|r| r.id != *row),
1090 TableEdit::DeleteColumn(at) => {
1091 table.columns.remove(*at as usize);
1092 for row in &mut table.rows {
1093 row.cells.remove(*at as usize);
1094 }
1095 }
1096 TableEdit::Columns(columns) => {
1097 table.columns = columns
1098 .iter()
1099 .map(|c| crate::page::TableColumn {
1100 width: stored(c.width),
1101 locked: c.locked,
1102 })
1103 .collect();
1104 }
1105 TableEdit::Borders(borders) => table.borders = Some(*borders),
1106 TableEdit::Cell {
1107 cell,
1108 shading,
1109 indents,
1110 } => {
1111 let cell = table
1112 .rows
1113 .iter_mut()
1114 .flat_map(|row| &mut row.cells)
1115 .find(|c| c.id == *cell)
1116 .ok_or_else(|| invalid("The cell is not in the table"))?;
1117 cell.shading = *shading;
1118 cell.indents = indents.iter().map(|v| stored(*v)).collect();
1119 }
1120 }
1121 }
1122 }
1123 Ok(())
1124}
1125
1126/// Removes outlines a tree edit left without paragraphs, as the tree writer does.
1127fn drop_emptied(page: &mut Page) {
1128 page.objects
1129 .retain(|object| !matches!(object, PageObject::Outline(outline) if outline.paragraphs.is_empty()));
1130}
1131
1132/// The identity a read-only definition is stored under: an equal one the page holds, as
1133/// the writers share identical read-only objects, else `id`.
1134fn define(page: &mut Page, id: ExGuid, definition: &crate::page::Definition) -> ExGuid {
1135 if page.definitions.contains_key(&id) {
1136 return id;
1137 }
1138 if let Some((existing, _)) = page.definitions.iter().find(|(_, d)| *d == definition) {
1139 return *existing;
1140 }
1141 page.definitions.insert(id, definition.clone());
1142 id
1143}
1144
1145/// Where a page object goes before `before`, or after the other children; titles, which
1146/// the page lists apart, read after them.
1147fn page_position(page: &Page, before: Option<ExGuid>) -> Result<usize, Error> {
1148 match before {
1149 Some(before) => page
1150 .objects
1151 .iter()
1152 .position(|o| o.id() == before)
1153 .ok_or_else(|| invalid("The anchor is not on the page")),
1154 None => Ok(page
1155 .objects
1156 .iter()
1157 .position(|o| matches!(o, PageObject::Title(_)))
1158 .unwrap_or(page.objects.len())),
1159 }
1160}
1161
1162fn table_cells(page: &Page) -> BTreeSet<ExGuid> {
1163 lists(page)
1164 .into_iter()
1165 .filter(|(path, _, _)| !path.cells.is_empty())
1166 .map(|(_, owner, _)| owner)
1167 .collect()
1168}
1169
1170fn outline_mut(page: &mut Page, id: ExGuid) -> Result<&mut Outline, Error> {
1171 page.objects
1172 .iter_mut()
1173 .find_map(|object| match object {
1174 PageObject::Outline(outline) if outline.id == id => Some(outline),
1175 _ => None,
1176 })
1177 .ok_or_else(|| invalid("The outline is not on the page"))
1178}
1179
1180fn image_mut(page: &mut Page, id: ExGuid) -> Result<&mut crate::page::Image, Error> {
1181 if let Some(at) = page.objects.iter().position(|o| o.id() == id) {
1182 let PageObject::Image(image) = &mut page.objects[at] else {
1183 return Err(invalid("Select a picture"));
1184 };
1185 return Ok(image);
1186 }
1187 let (path, index) = lists(page)
1188 .into_iter()
1189 .find_map(|(path, _, list)| {
1190 list.iter()
1191 .position(|p| matches!(&p.content, ParagraphContent::Image(i) if i.id == id))
1192 .map(|index| (path, index))
1193 })
1194 .ok_or_else(|| invalid("The picture is not on the page"))?;
1195 let ParagraphContent::Image(image) = &mut list_at(page, &path)[index].content else {
1196 unreachable!()
1197 };
1198 Ok(image)
1199}
1200
1201fn ink_mut(page: &mut Page, id: ExGuid) -> Result<&mut crate::page::Ink, Error> {
1202 if let Some(at) = page.objects.iter().position(|o| o.id() == id) {
1203 let PageObject::Ink(ink) = &mut page.objects[at] else {
1204 return Err(invalid("Select ink"));
1205 };
1206 return Ok(ink);
1207 }
1208 let (path, index) = lists(page)
1209 .into_iter()
1210 .find_map(|(path, _, list)| {
1211 list.iter()
1212 .position(|p| matches!(&p.content, ParagraphContent::Ink(i) if i.id == id))
1213 .map(|index| (path, index))
1214 })
1215 .ok_or_else(|| invalid("The ink is not on the page"))?;
1216 let ParagraphContent::Ink(ink) = &mut list_at(page, &path)[index].content else {
1217 unreachable!()
1218 };
1219 Ok(ink)
1220}
1221
1222/// The text object of the page title, when it has one.
1223fn find_title_text(page: &Page) -> Option<ExGuid> {
1224 page.objects
1225 .iter()
1226 .filter_map(|object| match object {
1227 PageObject::Title(title) => Some(title),
1228 _ => None,
1229 })
1230 .flat_map(|title| title.outlines.iter().filter(|o| Some(o.id) != title.date))
1231 .flat_map(|outline| &outline.paragraphs)
1232 .filter_map(|p| p.text().filter(|t| t.date_field.is_none()))
1233 .map(|t| t.id)
1234 .next_back()
1235}
1236
1237fn text_of(page: &Page, text: ExGuid) -> String {
1238 lists(page)
1239 .into_iter()
1240 .find_map(|(_, _, list)| {
1241 list.iter()
1242 .find_map(|p| p.text().filter(|t| t.id == text).map(|t| t.text.text().to_owned()))
1243 })
1244 .unwrap_or_default()
1245}
crates/onestore/src/op/properties.rs created+376
...@@ -0,0 +1,376 @@
1//! Paragraph properties: list nodes, note tags, paragraph styles and paragraph formatting.
2
3use super::Values;
4use crate::{
5 Error, ExGuid, PropertySets, Value,
6 active::{ActivePage, Changes},
7 document::{Kind, Tag},
8 page::Definition,
9 write::PropertyObject,
10};
11use std::collections::{BTreeMap, BTreeSet};
12
13fn invalid(message: &'static str) -> Error {
14 Error { offset: 0, message }
15}
16
17/// The properties of a list node carrying `definition`.
18pub(crate) fn list_values(definition: &Definition) -> Result<Values, Error> {
19 let Kind::List {
20 font,
21 format,
22 restart,
23 bullet,
24 } = &definition.kind
25 else {
26 return Err(invalid("A paragraph list must reference a list definition"));
27 };
28 if bullet.is_some() && (format.is_none() || font.is_none()) {
29 return Err(invalid("A bullet definition names its glyph and font"));
30 }
31 let mut values: Values = Vec::new();
32 if let Some(format) = format {
33 let units: Vec<u16> = format.encode_utf16().collect();
34 let count = u16::try_from(units.len())
35 .map_err(|_| invalid("List format exceeds the document range"))?;
36 let mut bytes = count.to_le_bytes().to_vec();
37 bytes.extend(units.iter().flat_map(|unit| unit.to_le_bytes()));
38 values.push((0x1c001c1a, bytes));
39 }
40 if let Some(font) = font {
41 values.push((0x1c001c52, crate::create::string(font)));
42 }
43 if let Some(restart) = restart {
44 values.push((0x14001cb7, restart.to_le_bytes().to_vec()));
45 }
46 if let Some(bullet) = bullet {
47 values.push((0x10001d0e, bullet.to_le_bytes().to_vec()));
48 // Every native bullet node carries this cleared flag alongside its index.
49 values.push((0x0c001cc0, vec![0]));
50 }
51 let style = &definition.format;
52 if let Some(font) = &style.font {
53 values.push((0x1c001c0a, crate::create::string(font)));
54 }
55 if let Some(size) = style.font_size {
56 let half = (size * 2.0).round();
57 if !(0.0..=f32::from(u16::MAX)).contains(&half) {
58 return Err(invalid("List font size is outside the document range"));
59 }
60 values.push((0x10001c0b, (half as u16).to_le_bytes().to_vec()));
61 }
62 if let Some(color) = style.color {
63 values.push((0x14001c0c, color.to_le_bytes().to_vec()));
64 }
65 if let Some(language) = style.language {
66 values.push((0x14001c3b, language.to_le_bytes().to_vec()));
67 }
68 for (flag, id) in [(style.bold, 0x08001c04), (style.italic, 0x08001c05)] {
69 if let Some(flag) = flag {
70 values.push((id | (u32::from(flag) << 31), Vec::new()));
71 }
72 }
73 Ok(values)
74}
75
76/// Gives `paragraph` the list nodes `nodes` names, rewriting stored ones to their values
77/// and creating the rest.
78pub(crate) fn list_changes(
79 active: &ActivePage<'_>,
80 paragraph: ExGuid,
81 nodes: &[(ExGuid, Values)],
82) -> Result<Changes, Error> {
83 let parents = active.editable_parents(paragraph)?;
84 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
85 let raw = &active.live.revision;
86 let mut changed = BTreeMap::new();
87 let mut target = PropertyObject::from_object(&raw.objects[&paragraph])?;
88 let mut references = Vec::new();
89 for (list, values) in nodes {
90 let mut node = match raw.objects.get(list) {
91 Some(existing) => {
92 if existing.jcid != 0x60012 {
93 return Err(invalid(
94 "A list definition identity belongs to another object",
95 ));
96 }
97 let mut node = PropertyObject::from_object(existing)?;
98 node.remove(&[
99 0x1c001c1a, 0x1c001c52, 0x14001cb7, 0x10001d0e, 0x0c001cc0, 0x1c001c0a,
100 0x10001c0b, 0x14001c0c, 0x14001c3b, 0x08001c04, 0x08001c05,
101 ])?;
102 node
103 }
104 None => PropertyObject {
105 jcid: 0x60012,
106 bytes: crate::create::properties(&[])?,
107 global_ids: std::sync::Arc::new(BTreeMap::from([(0, list.guid)])),
108 },
109 };
110 node.set(
111 &values
112 .iter()
113 .map(|(id, bytes)| (*id, bytes.as_slice()))
114 .collect::<Vec<_>>(),
115 )?;
116 node.set(&[(0x14001d7a, &modified)])?;
117 node.reference(*list)?;
118 references.extend_from_slice(&target.reference(*list)?);
119 changed.insert(*list, node);
120 }
121 if references.is_empty() {
122 target.remove(&[0x24001c26])?;
123 } else {
124 target.set(&[(0x24001c26, &references)])?;
125 }
126 target.set(&[(0x14001d7a, &modified)])?;
127 changed.insert(paragraph, target);
128 crate::formatting::touch_ancestors(raw, parents, paragraph, &modified, &mut changed)?;
129 Ok(changed)
130}
131
132/// The properties of a new tag definition object.
133fn tag_definition_values(definition: &Definition) -> Result<Values, Error> {
134 let Kind::TagDefinition {
135 label,
136 action_type,
137 shape,
138 color,
139 highlight,
140 } = &definition.kind
141 else {
142 return Err(invalid("A note tag must reference a tag definition"));
143 };
144 let mut values: Values = vec![
145 (0x0c003473, vec![0]),
146 (0x10003463, action_type.unwrap_or(0).to_le_bytes().to_vec()),
147 (0x10003464, shape.unwrap_or(0).to_le_bytes().to_vec()),
148 (0x14003467, 0u32.to_le_bytes().to_vec()),
149 ];
150 if let Some(label) = label {
151 values.push((0x1c003468, crate::create::string(label)));
152 }
153 if let Some(color) = color {
154 values.push((0x14003466, color.to_le_bytes().to_vec()));
155 }
156 if let Some(highlight) = highlight {
157 values.push((0x14003465, highlight.to_le_bytes().to_vec()));
158 }
159 Ok(values)
160}
161
162/// Replaces the note tags of `object`. Each entry names the stored identity of its tag's
163/// definition and, where the tag does not carry its action type or the page lacks the
164/// definition, the definition itself.
165pub(crate) fn tag_changes(
166 active: &ActivePage<'_>,
167 object: ExGuid,
168 tags: &[(ExGuid, &Tag, Option<&Definition>)],
169) -> Result<Changes, Error> {
170 let raw = &active.live.revision;
171 let mut definitions: Vec<(ExGuid, Option<Values>)> = Vec::new();
172 let mut sets: Vec<(usize, Values)> = Vec::new();
173 let mut action_types = BTreeSet::new();
174 for (written, tag, definition) in tags {
175 let action_type = if tag.status & 4 != 0 {
176 tag.action_type
177 } else {
178 match definition.map(|d| &d.kind) {
179 Some(Kind::TagDefinition { action_type, .. }) => *action_type,
180 _ => return Err(invalid("A note tag must reference a tag definition")),
181 }
182 };
183 if !action_types.insert(action_type.unwrap_or(0)) {
184 return Err(invalid("An element holds one note tag per action type"));
185 }
186 let index = match definitions.iter().position(|(id, _)| id == written) {
187 Some(index) => index,
188 None => {
189 let values = if raw.objects.contains_key(written) {
190 None
191 } else {
192 let definition = definition
193 .ok_or_else(|| invalid("A note tag references a missing tag definition"))?;
194 Some(tag_definition_values(definition)?)
195 };
196 definitions.push((*written, values));
197 definitions.len() - 1
198 }
199 };
200 let mut fields: Values = Vec::new();
201 if let Some(action_type) = tag.action_type {
202 fields.push((0x10003463, action_type.to_le_bytes().to_vec()));
203 }
204 for (id, value) in [
205 (0x1400346e, tag.created),
206 (0x1400346f, tag.completed),
207 (0x1400346a, tag.start),
208 (0x1400346b, tag.due),
209 ] {
210 if let Some(value) = value {
211 fields.push((id, value.to_le_bytes().to_vec()));
212 }
213 }
214 fields.push((0x10003470, tag.status.to_le_bytes().to_vec()));
215 if let Some(task) = tag.task_id {
216 fields.push((0x1c003469, task.to_vec()));
217 }
218 sets.push((index, fields));
219 }
220 let parents = active.editable_parents(object)?;
221 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
222 let mut changed = BTreeMap::new();
223 for (id, values) in &definitions {
224 let Some(values) = values else {
225 continue;
226 };
227 let mut node = PropertyObject {
228 jcid: 0x120043,
229 bytes: crate::create::properties(values)?,
230 global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])),
231 };
232 node.reference(*id)?;
233 changed.insert(*id, node);
234 }
235 let mut target = PropertyObject::from_object(&raw.objects[&object])?;
236 let mut encoded = Vec::new();
237 for (index, fields) in &sets {
238 let reference = target.reference(definitions[*index].0)?;
239 let mut set = vec![(0x20003488, reference.to_vec())];
240 set.extend(fields.iter().cloned());
241 encoded.push(set);
242 }
243 target.set_sets(0x40003489, 0x44000811, &encoded)?;
244 target.set(&[(0x14001d7a, &modified)])?;
245 changed.insert(object, target);
246 crate::formatting::touch_ancestors(raw, parents, object, &modified, &mut changed)?;
247 Ok(changed)
248}
249
250/// Makes `text` reference paragraph style `style`, created from `definition` where the
251/// page does not store it.
252pub(crate) fn style_changes(
253 active: &ActivePage<'_>,
254 text: ExGuid,
255 style: ExGuid,
256 definition: Option<&Definition>,
257) -> Result<Changes, Error> {
258 let raw = &active.live.revision;
259 let mut changed = BTreeMap::new();
260 if !raw.objects.contains_key(&style) {
261 let definition =
262 definition.ok_or_else(|| invalid("A paragraph references a missing style definition"))?;
263 let Kind::Style { name } = &definition.kind else {
264 return Err(invalid("A paragraph style must be a style definition"));
265 };
266 let format = &definition.format;
267 let mut values = super::content::style_values(format);
268 if let Some(name) = name {
269 values.push((0x1c00345a, crate::create::string(name)));
270 }
271 if let Some(alignment) = format.alignment {
272 values.push((0x0c003477, vec![alignment]));
273 }
274 for (property, value) in [
275 (0x1400342e, format.space_before),
276 (0x1400342f, format.space_after),
277 (0x14003430, format.line_spacing),
278 ] {
279 if let Some(points) = value {
280 values.push((property, (points / 36.0).to_le_bytes().to_vec()));
281 }
282 }
283 let mut node = PropertyObject {
284 jcid: 0x12004d,
285 bytes: crate::create::properties(&values)?,
286 global_ids: std::sync::Arc::new(BTreeMap::from([(0, style.guid)])),
287 };
288 node.reference(style)?;
289 changed.insert(style, node);
290 }
291 let mut target = PropertyObject::from_object(&raw.objects[&text])?;
292 let reference = target.reference(style)?;
293 target.set(&[(0x2000342c, &reference)])?;
294 changed.insert(text, target);
295 Ok(changed)
296}
297
298/// The text-object properties of paragraph formatting, in the order they are written.
299pub(crate) fn paragraph_values(
300 alignment: Option<u8>,
301 rtl: Option<bool>,
302 space_before: Option<f32>,
303 space_after: Option<f32>,
304 line_spacing: Option<f32>,
305 language: Option<u32>,
306) -> Result<Values, Error> {
307 let mut values = Values::new();
308 if let Some(alignment) = alignment {
309 if alignment > 2 {
310 return Err(invalid("Paragraph alignment must be left, center or right"));
311 }
312 values.push((0x0c003477, vec![alignment]));
313 }
314 if let Some(rtl) = rtl {
315 values.push((0x08003476 | (u32::from(rtl) << 31), Vec::new()));
316 }
317 for (property, value) in [
318 (0x1400342e, space_before),
319 (0x1400342f, space_after),
320 (0x14003430, line_spacing),
321 ] {
322 if let Some(points) = value {
323 let stored = points / 36.0;
324 if !stored.is_finite() || !(0.0..=27777.777).contains(&stored) {
325 return Err(invalid("Paragraph spacing is outside the document range"));
326 }
327 values.push((property, stored.to_le_bytes().to_vec()));
328 }
329 }
330 if let Some(language) = language {
331 values.push((0x14001c3b, language.to_le_bytes().to_vec()));
332 }
333 Ok(values)
334}
335
336/// Writes paragraph formatting `values` on text object `object`; alignment also sets the
337/// layout alignment fields OneNote keeps beside it.
338pub(crate) fn paragraph_format_changes(
339 active: &ActivePage<'_>,
340 object: ExGuid,
341 values: &Values,
342) -> Result<Changes, Error> {
343 let parents = active.editable_parents(object)?;
344 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
345 let raw = &active.live.revision;
346 let mut target = PropertyObject::from_object(&raw.objects[&object])?;
347 target.set(
348 &values
349 .iter()
350 .map(|(id, bytes)| (*id, bytes.as_slice()))
351 .collect::<Vec<_>>(),
352 )?;
353 if let Some((_, alignment)) = values.iter().find(|(id, _)| *id == 0x0c003477) {
354 for property in [0x14001c3e, 0x14001c84] {
355 let fields = PropertySets::parse(&target.bytes)?;
356 let previous = fields.sets[0]
357 .iter()
358 .find(|field| field.id == property)
359 .map(|field| match field.value {
360 Value::Bytes(bytes) => bytes
361 .try_into()
362 .map(u32::from_le_bytes)
363 .map_err(|_| invalid("Invalid paragraph layout alignment")),
364 _ => Err(invalid("Invalid paragraph layout alignment")),
365 })
366 .transpose()?
367 .unwrap_or(0);
368 let value = (previous & !7) | (u32::from(alignment[0]) + 1);
369 target.set(&[(property, &value.to_le_bytes())])?;
370 }
371 }
372 target.set(&[(0x14001d7a, &modified)])?;
373 let mut changed = BTreeMap::from([(object, target)]);
374 crate::formatting::touch_ancestors(raw, parents, object, &modified, &mut changed)?;
375 Ok(changed)
376}
crates/onestore/src/op/table.rs created+214
...@@ -0,0 +1,214 @@
1//! Tables as OneNote stores them: rows and cells are plain containers, a cell carries its
2//! indentation table and the flags every native cell has, the table its column widths,
3//! locks and border flag.
4
5use super::{
6 Values,
7 content::{NATIVE_INDENTS, measurement_bytes},
8};
9use crate::{
10 Error, ExGuid,
11 active::{ActivePage, Changes},
12 page::{Table, TableColumn},
13 write::PropertyObject,
14};
15use std::collections::{BTreeMap, BTreeSet};
16
17fn invalid(message: &'static str) -> Error {
18 Error { offset: 0, message }
19}
20
21/// Every row carries one cell per column and widths are usable.
22pub(crate) fn validate_table(table: &Table) -> Result<(), Error> {
23 if table.rows.is_empty() || table.columns.is_empty() {
24 return Err(invalid("A table needs at least one row and one column"));
25 }
26 if table
27 .columns
28 .iter()
29 .any(|c| !c.width.is_finite() || c.width < 36.0)
30 {
31 return Err(invalid("Table columns are at least 36 points wide"));
32 }
33 for row in &table.rows {
34 if row.cells.len() != table.columns.len() {
35 return Err(invalid("Every table row has one cell per column"));
36 }
37 for cell in &row.cells {
38 // An emptied cell keeps a replacement paragraph, as the tree writer provides.
39 for paragraph in &cell.paragraphs {
40 if let crate::page::ParagraphContent::Table(nested) = &paragraph.content {
41 validate_table(nested)?;
42 }
43 }
44 }
45 }
46 Ok(())
47}
48
49/// A table's rows and cells by stored identity, in order, and what new ones carry.
50pub(crate) struct Structure<'t> {
51 pub rows: Vec<(ExGuid, Vec<ExGuid>)>,
52 pub new_rows: BTreeSet<ExGuid>,
53 /// The indentation table and shading of each new cell.
54 pub new_cells: BTreeMap<ExGuid, (&'t [f32], Option<u32>)>,
55 pub columns: &'t [TableColumn],
56 pub borders: Option<bool>,
57}
58
59/// The indentation table a new cell takes: its own, else the table's first cell's, else
60/// the one OneNote gives new cells.
61pub(crate) fn cell_indents<'t>(own: &'t [f32], template: Option<&'t [f32]>) -> &'t [f32] {
62 [own, template.unwrap_or_default(), &NATIVE_INDENTS]
63 .into_iter()
64 .find(|indents| !indents.is_empty())
65 .unwrap()
66}
67
68/// Writes table `table` as `structure`; with `holder`, creates it as that paragraph's content.
69pub(crate) fn table_changes(
70 active: &ActivePage<'_>,
71 table: ExGuid,
72 holder: Option<ExGuid>,
73 structure: &Structure<'_>,
74) -> Result<Changes, Error> {
75 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
76 let mut created: Vec<(ExGuid, u32, Values)> = Vec::new();
77 for (row, cells) in &structure.rows {
78 for cell in cells {
79 if let Some((indents, shading)) = structure.new_cells.get(cell) {
80 let mut values: Values = vec![
81 (0x14001d7a, modified.to_vec()),
82 (0x0c001c13, vec![0]),
83 (0x0c001c03, vec![1]),
84 (0x88001c91, Vec::new()),
85 (0x1c001c12, measurement_bytes(indents, 4)?),
86 ];
87 if let Some(shading) = shading {
88 values.push((0x14001e26, shading.to_le_bytes().to_vec()));
89 }
90 created.push((*cell, 0x60024, values));
91 }
92 }
93 if structure.new_rows.contains(row) {
94 created.push((*row, 0x60023, vec![(0x14001d7a, modified.to_vec())]));
95 }
96 }
97 let columns = structure.columns;
98 let mut table_values: Values = vec![
99 (0x14001d7a, modified.to_vec()),
100 (
101 0x14001d57,
102 (structure.rows.len() as u32).to_le_bytes().to_vec(),
103 ),
104 (0x14001d58, (columns.len() as u32).to_le_bytes().to_vec()),
105 (
106 0x1c001d66,
107 measurement_bytes(&columns.iter().map(|c| c.width).collect::<Vec<_>>(), 1)?,
108 ),
109 ];
110 let mut locks = vec![columns.len() as u8];
111 locks.extend(vec![0u8; columns.len().div_ceil(8)]);
112 for (i, column) in columns.iter().enumerate() {
113 if column.locked {
114 locks[1 + i / 8] |= 1 << (i % 8);
115 }
116 }
117 table_values.push((0x1c001d7d, locks));
118 table_values.push((
119 0x08001d5e | (u32::from(structure.borders.unwrap_or(true)) << 31),
120 Vec::new(),
121 ));
122 if holder.is_some() {
123 table_values.push((0x14001c3e, 1u32.to_le_bytes().to_vec()));
124 table_values.push((0x14001c84, 1u32.to_le_bytes().to_vec()));
125 created.push((table, 0x60022, Vec::new()));
126 }
127 let raw = &active.live.revision;
128 let mut changed: Changes = BTreeMap::new();
129 for (id, jcid, values) in &created {
130 let mut node = PropertyObject {
131 jcid: *jcid,
132 bytes: crate::create::properties(values)?,
133 global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])),
134 };
135 node.reference(*id)?;
136 changed.insert(*id, node);
137 }
138 let mut row_refs = Vec::new();
139 for (row_id, cells) in &structure.rows {
140 let mut row = match changed.remove(row_id) {
141 Some(row) => row,
142 None => PropertyObject::from_object(&raw.objects[row_id])?,
143 };
144 let mut cell_refs = Vec::new();
145 for cell in cells {
146 cell_refs.extend_from_slice(&row.reference(*cell)?);
147 }
148 row.set(&[(0x24001c20, &cell_refs), (0x14001d7a, &modified)])?;
149 changed.insert(*row_id, row);
150 }
151 let mut table_object = match changed.remove(&table) {
152 Some(object) => object,
153 None => PropertyObject::from_object(&raw.objects[&table])?,
154 };
155 for (row_id, _) in &structure.rows {
156 row_refs.extend_from_slice(&table_object.reference(*row_id)?);
157 }
158 table_object.remove(&[0x1c001d7d, 0x08001d5e])?;
159 table_object.set(
160 &table_values
161 .iter()
162 .map(|(id, bytes)| (*id, bytes.as_slice()))
163 .collect::<Vec<_>>(),
164 )?;
165 table_object.set(&[(0x24001c20, &row_refs)])?;
166 changed.insert(table, table_object);
167 if let Some(holder) = holder {
168 let mut object = PropertyObject::from_object(&raw.objects[&holder])?;
169 let reference = object.reference(table)?;
170 object.set(&[(0x24001c1f, &reference), (0x14001d7a, &modified)])?;
171 changed.insert(holder, object);
172 }
173 Ok(changed)
174}
175
176/// Shading (the documented `CellShadingColor`, which OneNote 2010 stores but neither
177/// renders nor accepts through its COM schema) and indents change in place on a cell.
178pub(crate) fn cell_changes(
179 active: &ActivePage<'_>,
180 cell: ExGuid,
181 stored: (Option<u32>, &[f32]),
182 shading: Option<u32>,
183 indents: &[f32],
184) -> Result<Changes, Error> {
185 let mut values: Values = vec![(
186 0x14001d7a,
187 crate::create::current_timestamps()?
188 .0
189 .to_le_bytes()
190 .to_vec(),
191 )];
192 let mut removed = Vec::new();
193 if shading != stored.0 {
194 match shading {
195 Some(shading) => values.push((0x14001e26, shading.to_le_bytes().to_vec())),
196 None => removed.push(0x14001e26),
197 }
198 }
199 if indents != stored.1 {
200 if indents.is_empty() {
201 return Err(invalid("A table cell needs an indentation table"));
202 }
203 values.push((0x1c001c12, measurement_bytes(indents, 4)?));
204 }
205 let raw = &active.live.revision;
206 let mut object = PropertyObject::from_object(&raw.objects[&cell])?;
207 object.remove(&removed)?;
208 let values: Vec<(u32, &[u8])> = values
209 .iter()
210 .map(|(id, bytes)| (*id, bytes.as_slice()))
211 .collect();
212 object.set(&values)?;
213 Ok(BTreeMap::from([(cell, object)]))
214}
crates/onestore/src/op/tests.rs created+2150
...@@ -0,0 +1,2150 @@
1//! Ops against the page-model writer: for random edits of corpus pages, lowering the edit
2//! and applying the ops through a `Section` stores what `PreparedEdit::page` stores.
3
4use super::*;
5use crate::{
6 Arena, RevisionIndex, Section, Store,
7 document::{Document, Format, Kind, Layout},
8 page::{
9 Attachment, Definition, Image, Ink, InkStroke, Outline, PageObject, PageParagraph,
10 ParagraphContent, Table, TableCell, TableColumn, TableRow, TextObject, text::new_id,
11 },
12 write::GUIDS,
13};
14use std::collections::BTreeMap;
15
16const SOURCES: &[(&str, &[u8])] = &[
17 ("outline-edit", include_bytes!("../../../../corpus/outline-edit/before/notebook/synthetic.one")),
18 ("paragraph-edit", include_bytes!("../../../../corpus/paragraph-edit/before/notebook/synthetic.one")),
19 ("outline-tree", include_bytes!("../../../../corpus/outline-edit/tree/before/notebook/synthetic.one")),
20 ("features", include_bytes!("../../../../corpus/m6/native-features-01/notebook/Features.one")),
21 ("tables", include_bytes!("../../../../corpus/table-edit/nested/cold/notebook/synthetic.one")),
22 ("table-controls", include_bytes!("../../../../corpus/m6/native-table-controls-01/notebook/synthetic.one")),
23 ("lists", include_bytes!("../../../../corpus/list-edit/cold/notebook/lists.one")),
24 ("tags", include_bytes!("../../../../corpus/tag-edit/cold/notebook/tags.one")),
25 ("pictures", include_bytes!("../../../../corpus/picture-edit/native-page-level/notebook/pictures.one")),
26 ("attachments", include_bytes!("../../../../corpus/attachment-edit/plain/cold/notebook/files.one")),
27 ("ink", include_bytes!("../../../../corpus/ink-edit/drawing/cold/notebook/ink.one")),
28 ("math", include_bytes!("../../../../corpus/math-edit/native-editor/notebook/links.one")),
29 ("links", include_bytes!("../../../../corpus/link-edit/native-links/notebook/links.one")),
30 ("paragraph-format", include_bytes!("../../../../corpus/paragraph-format/cold/notebook/synthetic.one")),
31 ("enter-probe", include_bytes!("../../../../evidence/structural-edits/probe-section/probe.one")),
32];
33
34/// FILETIME both writers take as the edit's time.
35const AT: u64 = 133_700_000_000_000_000;
36
37pub(super) struct Rng(pub(super) u64);
38
39impl Rng {
40 fn next(&mut self) -> u64 {
41 self.0 = self
42 .0
43 .wrapping_mul(6364136223846793005)
44 .wrapping_add(1442695040888963407);
45 self.0 >> 33
46 }
47
48 fn coin(&mut self) -> bool {
49 self.next().is_multiple_of(2)
50 }
51
52 fn pick(&mut self, count: usize) -> Option<usize> {
53 (count > 0).then(|| self.next() as usize % count)
54 }
55
56 fn text(&mut self) -> String {
57 let words = ["", "a", "Two words", "東京 🦀", "é\u{301}", "longer text here", " "];
58 words[self.next() as usize % words.len()].to_owned()
59 }
60}
61
62pub(super) fn seeded<T>(seed: u64, f: impl FnOnce() -> T) -> T {
63 let outer = GUIDS.replace(Some(seed));
64 let result = f();
65 GUIDS.set(outer);
66 result
67}
68
69fn pages(image: &[u8]) -> Vec<ExGuid> {
70 let store = Store::parse(image).unwrap();
71 let index = RevisionIndex::parse(&store).unwrap();
72 let document = Document::parse(&index).unwrap();
73 document
74 .pages()
75 .unwrap()
76 .into_iter()
77 .map(|(space, _)| space)
78 .collect()
79}
80
81fn read(image: &[u8], space: ExGuid) -> Page {
82 let store = Store::parse(image).unwrap();
83 let index = RevisionIndex::parse(&store).unwrap();
84 Page::from_space(&Document::parse(&index).unwrap(), space).unwrap()
85}
86
87/// `page` with writer-chosen identities replaced in document order: list nodes, which a
88/// copy for a second owner gets, and tag field indices.
89pub(super) fn normalize(page: &Page) -> Page {
90 let mut page = page.clone();
91 let mut lists = BTreeMap::new();
92 let mut paths: Vec<_> = model::lists(&page).into_iter().map(|(path, _, _)| path).collect();
93 paths.sort_by_key(|path| format!("{path:?}"));
94 for path in &paths {
95 for paragraph in model::list_at(&mut page, path).iter_mut() {
96 for list in &mut paragraph.lists {
97 let next = ExGuid {
98 guid: [0xee; 16],
99 n: 1 + lists.len() as u32,
100 };
101 *list = *lists.entry(*list).or_insert(next);
102 }
103 for tag in paragraph.tags.iter_mut() {
104 tag.extra_set = 0;
105 }
106 if let Some(text) = paragraph.text_mut() {
107 for tag in &mut text.tags {
108 tag.extra_set = 0;
109 }
110 // An absent paragraph value and its default are the same formatting.
111 let mut previous = 0;
112 let runs: Vec<(String, Format)> = text
113 .text
114 .spans()
115 .iter()
116 .map(|span| {
117 let mut format = span.format.clone();
118 format.alignment = format.alignment.filter(|v| *v != 0);
119 format.rtl = format.rtl.filter(|v| *v);
120 format.space_before = format.space_before.filter(|v| *v != 0.0);
121 format.space_after = format.space_after.filter(|v| *v != 0.0);
122 format.line_spacing = format.line_spacing.filter(|v| *v != 0.0);
123 let run = (text.text.text()[previous..span.end].to_owned(), format);
124 previous = span.end;
125 run
126 })
127 .collect();
128 text.text = Paragraph::from_runs(runs);
129 }
130 }
131 }
132 page.definitions = std::mem::take(&mut page.definitions)
133 .into_iter()
134 .filter_map(|(id, definition)| match definition.kind {
135 Kind::List { .. } => lists.get(&id).map(|id| (*id, definition)),
136 _ => Some((id, definition)),
137 })
138 .collect();
139 page
140}
141
142#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
143pub(super) enum Family {
144 Text,
145 Format,
146 Insert,
147 Paste,
148 Delete,
149 Move,
150 Collapse,
151 Position,
152 Width,
153 NewOutline,
154 List,
155 Tag,
156 Split,
157 Join,
158 Indent,
159 Outdent,
160 Alignment,
161 Style,
162 TableRow,
163 TableColumn,
164 TableDelete,
165 TableWidth,
166 TableShading,
167 NewTable,
168 PagePicture,
169 PictureEdit,
170 ParagraphPicture,
171 Attachment,
172 AttachmentRename,
173 NewInk,
174 InkStrokes,
175 Link,
176 Equation,
177}
178
179pub(super) const FAMILIES: [Family; 33] = [
180 Family::Text,
181 Family::Format,
182 Family::Insert,
183 Family::Paste,
184 Family::Delete,
185 Family::Move,
186 Family::Collapse,
187 Family::Position,
188 Family::Width,
189 Family::NewOutline,
190 Family::List,
191 Family::Tag,
192 Family::Split,
193 Family::Join,
194 Family::Indent,
195 Family::Outdent,
196 Family::Alignment,
197 Family::Style,
198 Family::TableRow,
199 Family::TableColumn,
200 Family::TableDelete,
201 Family::TableWidth,
202 Family::TableShading,
203 Family::NewTable,
204 Family::PagePicture,
205 Family::PictureEdit,
206 Family::ParagraphPicture,
207 Family::Attachment,
208 Family::AttachmentRename,
209 Family::NewInk,
210 Family::InkStrokes,
211 Family::Link,
212 Family::Equation,
213];
214
215const PNG: &[u8] = &[
216 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x48, 0x44,
217 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06, 0x00, 0x00, 0x00, 0x1f,
218 0x15, 0xc4, 0x89, 0x00, 0x00, 0x00, 0x0d, 0x49, 0x44, 0x41, 0x54, 0x78, 0x9c, 0x63, 0xf8,
219 0xcf, 0xc0, 0xf0, 0x1f, 0x00, 0x05, 0x00, 0x01, 0xff, 0x89, 0x99, 0x3d, 0x1d, 0x00, 0x00,
220 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
221];
222
223fn id() -> ExGuid {
224 new_id().unwrap()
225}
226
227fn text_paragraph(text: &str, format: Format) -> PageParagraph {
228 PageParagraph {
229 id: id(),
230 parent: None,
231 level: 1,
232 style: None,
233 format: Format::default(),
234 content: ParagraphContent::Text(TextObject {
235 id: id(),
236 date_field: None,
237 text: Paragraph::new(text.to_owned(), format),
238 tags: Vec::new(),
239 }),
240 lists: Vec::new(),
241 tags: Vec::new(),
242 media: Default::default(),
243 collapsed: false,
244 }
245}
246
247/// Paths of the page's editable lists: ordinary outlines and their cells.
248fn editable(page: &Page) -> Vec<model::Path> {
249 let titles: Vec<ExGuid> = page
250 .objects
251 .iter()
252 .filter_map(|o| match o {
253 PageObject::Title(title) => Some(title.outlines.iter().map(|o| o.id).collect::<Vec<_>>()),
254 _ => None,
255 })
256 .flatten()
257 .collect();
258 model::lists(page)
259 .into_iter()
260 .filter(|(_, owner, list)| !titles.contains(owner) && !list.is_empty())
261 .map(|(path, _, _)| path)
262 .collect()
263}
264
265/// A text paragraph of an editable list: its list and index.
266fn pick_text(page: &Page, rng: &mut Rng) -> Option<(model::Path, usize)> {
267 let candidates: Vec<(model::Path, usize)> = editable(page)
268 .into_iter()
269 .flat_map(|path| {
270 let list = model::lists(page)
271 .into_iter()
272 .find(|(p, _, _)| format!("{p:?}") == format!("{path:?}"))
273 .unwrap()
274 .2
275 .clone();
276 list.iter()
277 .enumerate()
278 .filter(|(_, p)| {
279 p.text().is_some_and(|t| {
280 t.date_field.is_none() && !crate::page::Math::is_equation(&t.text)
281 })
282 })
283 .map(|(i, _)| (path.clone(), i))
284 .collect::<Vec<_>>()
285 })
286 .collect();
287 candidates.get(rng.pick(candidates.len())?).cloned()
288}
289
290fn outlines(page: &mut Page) -> Vec<&mut Outline> {
291 page.objects
292 .iter_mut()
293 .filter_map(|o| match o {
294 PageObject::Outline(outline) => Some(outline),
295 _ => None,
296 })
297 .collect()
298}
299
300fn subtree_end(list: &[PageParagraph], index: usize) -> usize {
301 let mut members = vec![list[index].id];
302 let mut end = index + 1;
303 while end < list.len() && list[end].parent.is_some_and(|p| members.contains(&p)) {
304 members.push(list[end].id);
305 end += 1;
306 }
307 end
308}
309
310fn tables(page: &mut Page) -> Vec<&mut Table> {
311 let mut out = Vec::new();
312 for outline in outlines(page) {
313 for paragraph in &mut outline.paragraphs {
314 if let ParagraphContent::Table(table) = &mut paragraph.content {
315 out.push(table);
316 }
317 }
318 }
319 out
320}
321
322fn new_cell(indents: &[f32]) -> TableCell {
323 TableCell {
324 id: id(),
325 layout: Layout::default(),
326 indents: indents.to_vec(),
327 shading: None,
328 paragraphs: vec![text_paragraph("cell", Format::default())],
329 unsupported: Vec::new(),
330 }
331}
332
333/// Applies one random edit of `family` to `page`; false when the page offers none.
334pub(super) fn mutate(page: &mut Page, rng: &mut Rng, family: Family) -> bool {
335 match family {
336 Family::Text | Family::Format | Family::Link => {
337 let Some((path, index)) = pick_text(page, rng) else {
338 return false;
339 };
340 let text = &mut model::list_at(page, &path)[index].text_mut().unwrap().text;
341 let end = text.utf16_offset(text.text().len()).unwrap();
342 let chars: Vec<u32> = text
343 .text()
344 .char_indices()
345 .map(|(i, _)| text.utf16_offset(i).unwrap())
346 .chain([end])
347 .collect();
348 let a = chars[rng.pick(chars.len()).unwrap()];
349 let b = chars[rng.pick(chars.len()).unwrap()];
350 let range = a.min(b)..a.max(b);
351 match family {
352 Family::Text => {
353 let format = text.format_at(range.start).unwrap().clone();
354 text.apply(crate::page::text::Edit {
355 range,
356 replacement: Paragraph::new(rng.text(), format),
357 })
358 .is_ok()
359 }
360 Family::Format => {
361 if range.is_empty() {
362 return false;
363 }
364 let slice = text.slice(range.clone()).unwrap();
365 let attribute = rng.next() % 6;
366 let mut from = 0;
367 let runs: Vec<(String, Format)> = slice
368 .spans()
369 .iter()
370 .map(|span| {
371 let mut format = span.format.clone();
372 match attribute {
373 0 => format.bold = Some(!format.bold.unwrap_or(false)),
374 1 => format.italic = Some(!format.italic.unwrap_or(false)),
375 2 => format.underline = Some(!format.underline.unwrap_or(false)),
376 3 => format.font_size = Some(if format.font_size == Some(14.0) { 11.0 } else { 14.0 }),
377 4 => format.color = Some(0x00ff),
378 _ => format.font = Some("Consolas".into()),
379 }
380 let run = (slice.text()[from..span.end].to_owned(), format);
381 from = span.end;
382 run
383 })
384 .collect();
385 text.apply(crate::page::text::Edit {
386 range,
387 replacement: Paragraph::from_runs(runs),
388 })
389 .is_ok()
390 }
391 _ => {
392 if range.is_empty()
393 || text.spans().iter().any(|s| s.format.hyperlink == Some(true))
394 {
395 return false;
396 }
397 let base = text.format_at(range.start).unwrap().clone();
398 let label = text.slice(range.clone()).unwrap();
399 let mut code = base.clone();
400 code.hyperlink = Some(true);
401 code.hyperlink_label = Some(true);
402 code.hidden = Some(true);
403 let mut from = 0;
404 let mut runs = vec![(
405 "\u{fddf}HYPERLINK \"https://example.invalid/op\"".to_owned(),
406 code,
407 )];
408 for span in label.spans() {
409 let mut format = span.format.clone();
410 format.hyperlink = Some(true);
411 format.hyperlink_label = Some(true);
412 runs.push((label.text()[from..span.end].to_owned(), format));
413 from = span.end;
414 }
415 text.apply(crate::page::text::Edit {
416 range,
417 replacement: Paragraph::from_runs(runs),
418 })
419 .is_ok()
420 }
421 }
422 }
423 Family::Insert | Family::Paste | Family::Style => {
424 let paths = editable(page);
425 let Some(path) = paths.get(rng.pick(paths.len()).unwrap_or(0)).cloned() else {
426 return false;
427 };
428 let style = (family == Family::Style).then(|| {
429 let existing = page.definitions.iter().find_map(|(id, d)| {
430 matches!(&d.kind, Kind::Style { name: Some(name) } if name == "Heading 2")
431 .then_some(*id)
432 });
433 existing.unwrap_or_else(|| {
434 let id = id();
435 page.definitions.insert(
436 id,
437 Definition {
438 kind: Kind::Style {
439 name: Some("Heading 2".into()),
440 },
441 format: Format {
442 font: Some("Calibri".into()),
443 font_size: Some(13.0),
444 bold: Some(true),
445 color: Some(0x7d4f1e),
446 ..Default::default()
447 },
448 },
449 );
450 id
451 })
452 });
453 let list = model::list_at(page, &path);
454 let template = list
455 .iter()
456 .find_map(|p| p.text().map(|t| t.text.format_at(0).unwrap().clone()))
457 .unwrap_or_default();
458 let boundaries: Vec<usize> = (0..=list.len())
459 .filter(|i| list.get(*i).is_none_or(|p| p.parent.is_none()))
460 .collect();
461 let at = boundaries[rng.pick(boundaries.len()).unwrap()];
462 let count = if family == Family::Paste { 2 + rng.next() as usize % 3 } else { 1 };
463 let mut inserted = Vec::new();
464 for i in 0..count {
465 let mut paragraph = text_paragraph(&rng.text(), template.clone());
466 if let Some(style) = style {
467 paragraph.style = Some(style);
468 }
469 if i > 0 && rng.coin() {
470 let parent: &PageParagraph = &inserted[0];
471 paragraph.parent = Some(parent.id);
472 paragraph.level = parent.level + 1;
473 }
474 inserted.push(paragraph);
475 }
476 // Children follow their parent; keep them after the first inserted paragraph.
477 inserted.sort_by_key(|p| p.parent.is_some());
478 let cell_level = list.first().map_or(1, |p| p.level);
479 for paragraph in &mut inserted {
480 if paragraph.parent.is_none() {
481 paragraph.level = cell_level;
482 }
483 }
484 list.splice(at..at, inserted);
485 true
486 }
487 Family::Delete | Family::Move | Family::Collapse => {
488 let paths = editable(page);
489 let Some(path) = paths.get(rng.pick(paths.len()).unwrap_or(0)).cloned() else {
490 return false;
491 };
492 let list = model::list_at(page, &path);
493 let top: Vec<usize> = (0..list.len()).filter(|i| list[*i].parent.is_none()).collect();
494 match family {
495 Family::Delete => {
496 if top.len() < 2 {
497 return false;
498 }
499 let at = top[rng.pick(top.len()).unwrap()];
500 let end = subtree_end(list, at);
501 list.drain(at..end);
502 true
503 }
504 Family::Move => {
505 if top.len() < 2 {
506 return false;
507 }
508 let from = top[rng.pick(top.len()).unwrap()];
509 let end = subtree_end(list, from);
510 let block: Vec<_> = list.drain(from..end).collect();
511 let boundaries: Vec<usize> = (0..=list.len())
512 .filter(|i| list.get(*i).is_none_or(|p| p.parent.is_none()))
513 .collect();
514 let to = boundaries[rng.pick(boundaries.len()).unwrap()];
515 list.splice(to..to, block);
516 true
517 }
518 _ => {
519 let at = rng.pick(list.len()).unwrap();
520 list[at].collapsed ^= true;
521 true
522 }
523 }
524 }
525 Family::Position | Family::Width => {
526 let mut outlines: Vec<&mut Outline> = outlines(page).into_iter().filter(|o| !o.title).collect();
527 let Some(at) = rng.pick(outlines.len()) else {
528 return false;
529 };
530 let outline = &mut outlines[at];
531 if family == Family::Position {
532 outline.layout.x = Some((rng.next() % 200) as f32 * 1.5);
533 outline.layout.y = Some((rng.next() % 200) as f32 * 2.25);
534 } else {
535 outline.layout.max_width = Some(36.0 + (rng.next() % 200) as f32 * 2.0);
536 outline.layout.width_set_by_user = Some(rng.coin());
537 }
538 true
539 }
540 Family::NewOutline => {
541 let at = page
542 .objects
543 .iter()
544 .position(|o| matches!(o, PageObject::Title(_)))
545 .unwrap_or(page.objects.len());
546 let at = rng.pick(at + 1).unwrap();
547 page.objects.insert(
548 at,
549 PageObject::Outline(Outline {
550 id: id(),
551 title: false,
552 min_width: None,
553 layout: Layout {
554 x: Some((rng.next() % 100) as f32 * 1.5),
555 y: Some((rng.next() % 100) as f32 * 2.25),
556 ..Default::default()
557 },
558 indents: Vec::new(),
559 paragraphs: vec![text_paragraph(&rng.text(), Format::default())],
560 unsupported: Vec::new(),
561 }),
562 );
563 true
564 }
565 Family::List | Family::Tag | Family::Alignment => {
566 let Some((path, index)) = pick_text(page, rng) else {
567 return false;
568 };
569 match family {
570 Family::List => {
571 let paragraph = &model::list_at(page, &path)[index];
572 if paragraph.lists.is_empty() {
573 let list = id();
574 page.definitions.insert(
575 list,
576 Definition {
577 kind: Kind::List {
578 font: Some("Courier New".into()),
579 format: Some("\u{25cb}".into()),
580 restart: None,
581 bullet: Some(4),
582 },
583 format: Format {
584 font_size: Some(11.0),
585 color: Some(0xff000000),
586 ..Default::default()
587 },
588 },
589 );
590 model::list_at(page, &path)[index].lists = vec![list];
591 } else {
592 model::list_at(page, &path)[index].lists.clear();
593 }
594 }
595 Family::Tag => {
596 let definition = page
597 .definitions
598 .iter()
599 .find(|(_, d)| matches!(&d.kind, Kind::TagDefinition { label: Some(l), .. } if l == "Op task"))
600 .map(|(id, _)| *id)
601 .unwrap_or_else(|| {
602 let id = id();
603 page.definitions.insert(
604 id,
605 Definition {
606 kind: Kind::TagDefinition {
607 label: Some("Op task".into()),
608 action_type: Some(0),
609 shape: Some(3),
610 color: None,
611 highlight: None,
612 },
613 format: Default::default(),
614 },
615 );
616 id
617 });
618 let status = (rng.next() % 2) as u16;
619 let text = model::list_at(page, &path)[index].text_mut().unwrap();
620 if text.tags.is_empty() {
621 text.tags.push(crate::document::Tag {
622 definition: Some(definition),
623 action_type: None,
624 status,
625 created: Some(1_262_401_445),
626 completed: (status == 1).then_some(1_262_402_000),
627 start: None,
628 due: None,
629 task_id: None,
630 extra_set: 0,
631 });
632 } else {
633 text.tags.clear();
634 }
635 }
636 _ => {
637 let alignment = (rng.next() % 3) as u8;
638 let text = &mut model::list_at(page, &path)[index].text_mut().unwrap().text;
639 let mut from = 0;
640 let runs: Vec<(String, Format)> = text
641 .spans()
642 .iter()
643 .map(|span| {
644 let mut format = span.format.clone();
645 format.alignment = Some(alignment);
646 let run = (text.text()[from..span.end].to_owned(), format);
647 from = span.end;
648 run
649 })
650 .collect();
651 *text = Paragraph::from_runs(runs);
652 }
653 }
654 true
655 }
656 Family::Split | Family::Join => {
657 let Some((path, index)) = pick_text(page, rng) else {
658 return false;
659 };
660 let list = model::list_at(page, &path);
661 if family == Family::Split {
662 let paragraph = &list[index];
663 let text = &paragraph.text().unwrap().text;
664 let end = text.utf16_offset(text.text().len()).unwrap();
665 if text.spans().iter().any(|s| s.format.hyperlink == Some(true)) {
666 return false;
667 }
668 let offsets: Vec<u32> = text
669 .text()
670 .char_indices()
671 .map(|(i, _)| text.utf16_offset(i).unwrap())
672 .chain([end])
673 .collect();
674 let at = offsets[rng.pick(offsets.len()).unwrap()];
675 let (head, tail) = (text.slice(0..at).unwrap(), text.slice(at..end).unwrap());
676 let mut right = paragraph.clone();
677 right.id = id();
678 right.tags.clear();
679 right.media = Default::default();
680 right.content = ParagraphContent::Text(TextObject {
681 id: id(),
682 date_field: None,
683 text: tail,
684 tags: Vec::new(),
685 });
686 let left_id = paragraph.id;
687 list[index].text_mut().unwrap().text = head;
688 for child in &mut list[index + 1..] {
689 if child.parent == Some(left_id) {
690 child.parent = Some(right.id);
691 }
692 }
693 list.insert(index + 1, right);
694 true
695 } else {
696 let next = index + 1;
697 let Some(lower) = list.get(next) else {
698 return false;
699 };
700 let upper = &list[index];
701 if lower.parent != upper.parent
702 || lower.level != upper.level
703 || list.iter().any(|p| p.parent == Some(upper.id) || p.parent == Some(lower.id))
704 {
705 return false;
706 }
707 let Some(lower_text) = lower.text().cloned() else {
708 return false;
709 };
710 if lower_text.text.text().is_empty() || crate::page::Math::is_equation(&lower_text.text) {
711 return false;
712 }
713 let upper = list[index].text_mut().unwrap();
714 if upper.text.text().is_empty() {
715 upper.id = lower_text.id;
716 upper.text = lower_text.text;
717 } else {
718 upper.text.append(lower_text.text).unwrap();
719 }
720 list.remove(next);
721 true
722 }
723 }
724 Family::Indent | Family::Outdent => {
725 let paths = editable(page);
726 let Some(path) = paths.get(rng.pick(paths.len()).unwrap_or(0)).cloned() else {
727 return false;
728 };
729 if !format!("{path:?}").contains("cells: []") {
730 return false;
731 }
732 let list = model::list_at(page, &path);
733 let Some(index) = rng.pick(list.len()) else {
734 return false;
735 };
736 let end = subtree_end(list, index);
737 if family == Family::Indent {
738 // Tab: the paragraph becomes the last child of its previous sibling.
739 let parent = list[index].parent;
740 let Some(sibling) = list[..index].iter().rev().find(|p| p.parent == parent).map(|p| (p.id, p.level)) else {
741 return false;
742 };
743 list[index].parent = Some(sibling.0);
744 let delta = sibling.1 + 1 - list[index].level;
745 if delta > 1 || list[index].level + 1 > 31 {
746 return false;
747 }
748 for paragraph in &mut list[index..end] {
749 paragraph.level += 1;
750 }
751 list[index].level = list[index].level.max(sibling.1 + 1);
752 } else {
753 let Some(parent) = list[index].parent else {
754 return false;
755 };
756 // Shift-Tab of a last child: it follows its parent.
757 if list[end..].iter().any(|p| p.parent == Some(parent)) {
758 return false;
759 }
760 let grandparent = list.iter().find(|p| p.id == parent).unwrap().parent;
761 list[index].parent = grandparent;
762 for paragraph in &mut list[index..end] {
763 paragraph.level -= 1;
764 }
765 if list[index].level == 0 {
766 return false;
767 }
768 }
769 true
770 }
771 Family::TableRow
772 | Family::TableColumn
773 | Family::TableDelete
774 | Family::TableWidth
775 | Family::TableShading => {
776 let mut tables = tables(page);
777 let Some(at) = rng.pick(tables.len()) else {
778 return false;
779 };
780 let table = &mut tables[at];
781 let indents = table.rows[0].cells[0].indents.clone();
782 match family {
783 Family::TableRow => {
784 let row = TableRow {
785 id: id(),
786 cells: table.columns.iter().map(|_| new_cell(&indents)).collect(),
787 };
788 let at = rng.pick(table.rows.len() + 1).unwrap();
789 table.rows.insert(at, row);
790 }
791 Family::TableColumn => {
792 let at = rng.pick(table.columns.len() + 1).unwrap();
793 table.columns.insert(at, TableColumn { width: 72.0, locked: false });
794 for row in &mut table.rows {
795 row.cells.insert(at, new_cell(&indents));
796 }
797 }
798 Family::TableDelete => {
799 if rng.coin() && table.rows.len() > 1 {
800 let at = rng.pick(table.rows.len()).unwrap();
801 table.rows.remove(at);
802 } else if table.columns.len() > 1 {
803 let at = rng.pick(table.columns.len()).unwrap();
804 table.columns.remove(at);
805 for row in &mut table.rows {
806 row.cells.remove(at);
807 }
808 } else {
809 return false;
810 }
811 }
812 Family::TableWidth => {
813 let at = rng.pick(table.columns.len()).unwrap();
814 table.columns[at].width = 40.0 + (rng.next() % 100) as f32;
815 table.columns[at].locked = rng.coin();
816 }
817 _ => {
818 let cells: Vec<_> = table.rows.iter_mut().flat_map(|r| &mut r.cells).collect();
819 let len = cells.len();
820 let cell = cells.into_iter().nth(rng.pick(len).unwrap()).unwrap();
821 cell.shading = if cell.shading.is_some() { None } else { Some(0x00ccff) };
822 }
823 }
824 true
825 }
826 Family::NewTable | Family::ParagraphPicture | Family::Attachment | Family::Equation => {
827 let equation = page.objects.iter().find_map(|o| match o {
828 PageObject::Outline(outline) => outline.paragraphs.iter().find_map(|p| {
829 p.text().filter(|t| crate::page::Math::is_equation(&t.text)).map(|t| t.text.clone())
830 }),
831 _ => None,
832 });
833 let Some(outline) = outlines(page).into_iter().find(|o| !o.title) else {
834 return false;
835 };
836 let mut paragraph = text_paragraph("", Format::default());
837 paragraph.content = match family {
838 Family::NewTable => ParagraphContent::Table(Table {
839 id: id(),
840 columns: vec![TableColumn { width: 72.0, locked: false }; 2],
841 rows: (0..2)
842 .map(|_| TableRow {
843 id: id(),
844 cells: (0..2).map(|_| new_cell(&[])).collect(),
845 })
846 .collect(),
847 borders: Some(true),
848 layout: Layout::default(),
849 tags: Vec::new(),
850 }),
851 Family::ParagraphPicture => ParagraphContent::Image(Image {
852 id: id(),
853 layout: Layout::default(),
854 size: Some([0.75, 0.75]),
855 bytes: Some(PNG.into()),
856 alt: Some("dot".into()),
857 background: false,
858 }),
859 Family::Attachment => ParagraphContent::Attachment(Attachment {
860 id: id(),
861 filename: "notes.txt".into(),
862 source_path: Some(r"C:\notes.txt".into()),
863 size: None,
864 bytes: Some(b"attached bytes".as_slice().into()),
865 preview: None,
866 recording: None,
867 }),
868 _ => {
869 let Some(equation) = equation else {
870 return false;
871 };
872 ParagraphContent::Text(TextObject {
873 id: id(),
874 date_field: None,
875 text: equation,
876 tags: Vec::new(),
877 })
878 }
879 };
880 let boundaries: Vec<usize> = (0..=outline.paragraphs.len())
881 .filter(|i| outline.paragraphs.get(*i).is_none_or(|p| p.parent.is_none()))
882 .collect();
883 let at = boundaries[rng.pick(boundaries.len()).unwrap()];
884 outline.paragraphs.insert(at, paragraph);
885 true
886 }
887 Family::PagePicture | Family::NewInk => {
888 let at = page
889 .objects
890 .iter()
891 .position(|o| matches!(o, PageObject::Title(_)))
892 .unwrap_or(page.objects.len());
893 let object = if family == Family::PagePicture {
894 PageObject::Image(Image {
895 id: id(),
896 layout: Layout {
897 x: Some(100.0),
898 y: Some(200.0),
899 ..Default::default()
900 },
901 size: Some([0.75, 0.75]),
902 bytes: Some(PNG.into()),
903 alt: None,
904 background: false,
905 })
906 } else {
907 PageObject::Ink(Ink {
908 id: id(),
909 layout: Layout::default(),
910 strokes: vec![stroke(rng)],
911 groups: Vec::new(),
912 })
913 };
914 page.objects.insert(at, object);
915 true
916 }
917 Family::PictureEdit => {
918 let images: Vec<&mut Image> = page
919 .objects
920 .iter_mut()
921 .filter_map(|o| match o {
922 PageObject::Image(image) => Some(image),
923 _ => None,
924 })
925 .collect();
926 let len = images.len();
927 let Some(image) = images.into_iter().nth(rng.pick(len).unwrap_or(0)) else {
928 return false;
929 };
930 match rng.next() % 3 {
931 0 => {
932 image.layout.x = Some((rng.next() % 300) as f32);
933 image.layout.y = Some((rng.next() % 300) as f32);
934 }
935 1 => {
936 image.layout.max_width = Some(20.0 + (rng.next() % 100) as f32);
937 image.layout.max_height = Some(20.0 + (rng.next() % 100) as f32);
938 image.layout.width_set_by_user = Some(true);
939 }
940 _ => image.alt = Some(rng.text()),
941 }
942 true
943 }
944 Family::AttachmentRename => {
945 let mut found = false;
946 for outline in outlines(page) {
947 for paragraph in &mut outline.paragraphs {
948 if let ParagraphContent::Attachment(attachment) = &mut paragraph.content
949 && !found
950 && attachment.recording.is_none()
951 {
952 attachment.filename = format!("renamed {}.txt", rng.next() % 100);
953 found = true;
954 }
955 }
956 }
957 found
958 }
959 Family::InkStrokes => {
960 let inks: Vec<&mut Ink> = page
961 .objects
962 .iter_mut()
963 .filter_map(|o| match o {
964 PageObject::Ink(ink) if ink.groups.is_empty() => Some(ink),
965 _ => None,
966 })
967 .collect();
968 let len = inks.len();
969 let Some(ink) = inks.into_iter().nth(rng.pick(len).unwrap_or(0)) else {
970 return false;
971 };
972 if rng.coin() || ink.strokes.len() < 2 {
973 ink.strokes.push(stroke(rng));
974 } else {
975 let at = rng.pick(ink.strokes.len()).unwrap();
976 ink.strokes.remove(at);
977 }
978 true
979 }
980 }
981}
982
983fn stroke(rng: &mut Rng) -> InkStroke {
984 let x = (rng.next() % 300) as f32;
985 InkStroke {
986 id: id(),
987 points: vec![[x, 100.0], [x + 10.0, 110.0], [x + 20.0, 105.0]],
988 width: 1.5,
989 height: 1.5,
990 color: Some(0x0000ff),
991 transparency: None,
992 pen_tip: None,
993 }
994}
995
996/// How one differential case came out.
997#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
998pub(super) enum Outcome {
999 /// The edit made no change the writers store.
1000 Unchanged,
1001 /// Both refused the edit.
1002 Refused,
1003 /// The page writer refused; the ops were written.
1004 OnlyOps,
1005 /// Identical bytes.
1006 Bytes,
1007 /// Equal pages, both images valid, the op revision no larger.
1008 Model,
1009 /// The op revision declares more than the page writer's.
1010 Larger,
1011 Mismatch(String),
1012}
1013
1014/// Objects the space's active revision declares in `image`.
1015pub(super) fn declared(image: &[u8], space: ExGuid) -> usize {
1016 let store = Store::parse(image).unwrap();
1017 let index = RevisionIndex::parse(&store).unwrap();
1018 let rid = index.active(space).unwrap();
1019 index.spaces[&space].revisions[&rid]
1020 .nodes
1021 .iter()
1022 .filter_map(|node| match node.reference {
1023 Some(crate::Reference::NodeList(chunk)) if node.id == 0xb0 => Some(chunk),
1024 _ => None,
1025 })
1026 .map(|chunk| {
1027 store
1028 .lists
1029 .values()
1030 .find(|list| list.fragments.first().is_some_and(|f| f.offset == chunk.offset))
1031 .map_or(0, |list| {
1032 list.nodes
1033 .iter()
1034 .filter(|n| {
1035 matches!(n.id, 0x2d | 0x2e | 0x41 | 0x42 | 0xa4 | 0xa5 | 0xc4 | 0xc5 | 0x72 | 0x73)
1036 })
1037 .count()
1038 })
1039 })
1040 .sum()
1041}
1042
1043/// Writes `after` over page `space` of `source` both ways and compares the images.
1044pub(super) fn differential(source: &[u8], space: ExGuid, after: &Page, seed: u64) -> Outcome {
1045 let before = read(source, space);
1046 let oracle = seeded(seed, || {
1047 crate::create::at(AT, || crate::PreparedEdit::page(source, space, after, "Author"))
1048 });
1049 let arena = Arena::default();
1050 let mut section = Section::open(&arena, source.to_vec()).unwrap();
1051 let ops = lower_page(&before, after);
1052 let written = ops.as_ref().ok().map(|ops| {
1053 seeded(seed, || {
1054 let edit = Edit {
1055 at: AT,
1056 ops: ops
1057 .iter()
1058 .map(|op| Op::Page {
1059 space,
1060 op: op.clone(),
1061 })
1062 .collect(),
1063 };
1064 section.apply("Author", &edit).map(|()| {
1065 let predicted = {
1066 let mut page = before.clone();
1067 for op in ops {
1068 model::apply(&mut page, op).unwrap();
1069 }
1070 page
1071 };
1072 (section.seal().unwrap(), predicted)
1073 })
1074 })
1075 });
1076 let image = section.image();
1077 match (oracle, written) {
1078 (Err(_), None | Some(Err(_))) => Outcome::Refused,
1079 (Err(_), Some(Ok(_))) => Outcome::OnlyOps,
1080 (Ok(_), None) => Outcome::Mismatch(format!("lowering refused: {:?}", ops.unwrap_err())),
1081 (Ok(_), Some(Err(error))) => Outcome::Mismatch(format!("apply refused: {error:?} for {:?}", ops.unwrap())),
1082 (Ok(edit), Some(Ok((transaction, predicted)))) => {
1083 let stored = normalize(&read(&image, space));
1084 if normalize(&predicted) != stored {
1085 return Outcome::Mismatch(format!(
1086 "the model predicts otherwise: {}",
1087 first_difference(&stored, &normalize(&predicted))
1088 ));
1089 }
1090 let expected = edit.as_bytes();
1091 if expected == source && transaction.is_none() {
1092 return Outcome::Unchanged;
1093 }
1094 if expected == image.as_slice() {
1095 return Outcome::Bytes;
1096 }
1097 let store = Store::parse(&image).unwrap();
1098 let index = RevisionIndex::parse(&store).unwrap();
1099 if let Err(error) = index.validate_current() {
1100 return Outcome::Mismatch(format!("invalid image: {error:?}"));
1101 }
1102 let (ours, theirs) = (normalize(&read(&image, space)), normalize(&read(expected, space)));
1103 if ours != theirs {
1104 return Outcome::Mismatch(first_difference(&theirs, &ours));
1105 }
1106 if expected == source {
1107 return Outcome::Model;
1108 }
1109 if declared(&image, space) > declared(expected, space) {
1110 return Outcome::Larger;
1111 }
1112 Outcome::Model
1113 }
1114 }
1115}
1116
1117fn spans(text: &Paragraph) -> String {
1118 let mut out = format!("{:?}:", text.text());
1119 for span in text.spans() {
1120 let f = &span.format;
1121 out.push_str(&format!(
1122 " [{} b{:?} h{:?} l{:?}/{:?} c{:?} a{:?} s{:?} lang{:?} f{:?}]",
1123 span.end, f.bold, f.hidden, f.hyperlink, f.hyperlink_label, f.color, f.alignment, f.space_before, f.language, f.font
1124 ));
1125 }
1126 out
1127}
1128
1129pub(super) fn first_difference(expected: &Page, actual: &Page) -> String {
1130 let texts = |page: &Page| -> Vec<(ExGuid, Paragraph)> {
1131 model::lists(page)
1132 .into_iter()
1133 .flat_map(|(_, _, list)| list.iter())
1134 .filter_map(|p| p.text().map(|t| (p.id, t.text.clone())))
1135 .collect()
1136 };
1137 for ((a_id, a), (b_id, b)) in texts(expected).iter().zip(texts(actual).iter()) {
1138 if a_id == b_id && a != b {
1139 return format!("\n expected {}\n actual {}", spans(a), spans(b));
1140 }
1141 }
1142 let (a, b) = (format!("{expected:?}"), format!("{actual:?}"));
1143 let at = a
1144 .bytes()
1145 .zip(b.bytes())
1146 .position(|(x, y)| x != y)
1147 .unwrap_or(a.len().min(b.len()));
1148 let window = |s: &str| {
1149 let start = s.floor_char_boundary(at.saturating_sub(300));
1150 let end = s.floor_char_boundary((at + 200).min(s.len()));
1151 s[start..end].to_owned()
1152 };
1153 format!("\n expected …{}\n actual …{}", window(&a), window(&b))
1154}
1155
1156/// Every family on every corpus page it applies to, several seeds each.
1157#[test]
1158fn ops_store_what_the_page_writer_stores() {
1159 let mut tally: BTreeMap<(Family, String), usize> = BTreeMap::new();
1160 let mut mismatches = Vec::new();
1161 let seeds = std::env::var("OP_SEEDS").map_or(3, |s| s.parse().unwrap());
1162 for (name, source) in SOURCES {
1163 for (p, space) in pages(source).into_iter().enumerate().take(4) {
1164 for family in FAMILIES {
1165 for seed in 0..seeds {
1166 let mut rng = Rng(seed * 7919 + p as u64 * 131 + family as u64);
1167 let mut after = read(source, space);
1168 GUIDS.set(Some(1 << 60 | seed << 32 | (family as u64) << 20));
1169 if !mutate(&mut after, &mut rng, family) {
1170 continue;
1171 }
1172 GUIDS.set(None);
1173 let outcome = differential(source, space, &after, 1 << 40 | seed);
1174 let key = match &outcome {
1175 Outcome::Mismatch(_) => "Mismatch".to_owned(),
1176 other => format!("{other:?}"),
1177 };
1178 *tally.entry((family, key)).or_default() += 1;
1179 if let Outcome::Mismatch(detail) = outcome {
1180 mismatches.push(format!("{name} page {p} {family:?} seed {seed}: {detail}"));
1181 }
1182 }
1183 }
1184 }
1185 }
1186 let mut report = String::new();
1187 for ((family, outcome), count) in &tally {
1188 report.push_str(&format!("{family:?} {outcome} {count}\n"));
1189 }
1190 println!("{report}");
1191 for mismatch in mismatches.iter().take(8) {
1192 println!("{mismatch}\n");
1193 }
1194 assert!(mismatches.is_empty(), "{} mismatches", mismatches.len());
1195 assert!(!tally.keys().any(|(_, outcome)| outcome == "Larger"));
1196}
1197
1198#[test]
1199#[ignore]
1200fn debug_case() {
1201 let (_, source) = SOURCES[5];
1202 let space = pages(source)[1];
1203 let family = Family::NewOutline;
1204 let (seed, p) = (1u64, 1u64);
1205 let mut rng = Rng(seed * 7919 + p * 131 + family as u64);
1206 let mut after = read(source, space);
1207 GUIDS.set(Some(1 << 60 | seed << 32 | (family as u64) << 20));
1208 assert!(mutate(&mut after, &mut rng, family));
1209 GUIDS.set(None);
1210 let before = read(source, space);
1211 let ops = lower_page(&before, &after).unwrap();
1212 let mut page = before.clone();
1213 for op in &ops {
1214 model::apply(&mut page, op).unwrap();
1215 }
1216 for object in &page.objects {
1217 println!("{:?} {:?} {:?}", object.id(), object.layout(), match object { PageObject::Outline(o) => o.paragraphs.iter().filter_map(|p| p.text().map(|t| t.text.text().to_owned())).collect::<Vec<_>>(), PageObject::Title(t) => t.outlines.iter().flat_map(|o| &o.paragraphs).filter_map(|p| p.text().map(|t| format!("title {:?}", t.text.text()))).collect(), _ => vec![] });
1218 }
1219 println!("{:?} -> {:?}", before.title, page.title);
1220}
1221
1222/// Random ops drawn from a page model: targets the page holds, new identities fresh.
1223fn random_op(page: &Page, rng: &mut Rng) -> Option<PageOp> {
1224 let texts: Vec<(ExGuid, ExGuid, Paragraph)> = model::lists(page)
1225 .into_iter()
1226 .flat_map(|(_, _, list)| list.iter())
1227 .filter_map(|p| {
1228 let text = p.text()?;
1229 (text.date_field.is_none() && !crate::page::Math::is_equation(&text.text))
1230 .then(|| (p.id, text.id, text.text.clone()))
1231 })
1232 .collect();
1233 let (paragraph, text, content) = texts.get(rng.pick(texts.len())?)?.clone();
1234 let length = content.utf16_offset(content.text().len()).unwrap();
1235 let offsets: Vec<u32> = content
1236 .text()
1237 .char_indices()
1238 .map(|(i, _)| content.utf16_offset(i).unwrap())
1239 .chain([length])
1240 .collect();
1241 let a = offsets[rng.pick(offsets.len()).unwrap()];
1242 let b = offsets[rng.pick(offsets.len()).unwrap()];
1243 let range = a.min(b)..a.max(b);
1244 let holder = model::paragraph(page, paragraph).unwrap();
1245 let container = holder.parent.or_else(|| {
1246 model::lists(page)
1247 .into_iter()
1248 .find(|(_, _, list)| list.iter().any(|p| p.id == paragraph))
1249 .map(|(_, owner, _)| owner)
1250 })?;
1251 let siblings: Vec<ExGuid> = model::lists(page)
1252 .into_iter()
1253 .flat_map(|(_, _, list)| list.iter())
1254 .filter(|p| p.parent == holder.parent && p.id != paragraph)
1255 .map(|p| p.id)
1256 .collect();
1257 if rng.next().is_multiple_of(4)
1258 && let Some(op) = random_object_op(page, rng)
1259 {
1260 return Some(op);
1261 }
1262 Some(match rng.next() % 16 {
1263 0 | 1 => PageOp::Text {
1264 text,
1265 range,
1266 with: rng.text(),
1267 },
1268 2 => PageOp::Format {
1269 text,
1270 range,
1271 set: vec![TextAttribute::Bold(rng.coin())],
1272 clear: if rng.next().is_multiple_of(3) { vec![TextProperty::Italic] } else { Vec::new() },
1273 },
1274 3 => PageOp::Split {
1275 text,
1276 at: a,
1277 paragraph: id(),
1278 right: id(),
1279 lists: holder.lists.iter().map(|_| id()).collect(),
1280 },
1281 4 => {
1282 let mut new = text_paragraph(&rng.text(), content.format_at(0).unwrap().clone());
1283 new.level = holder.level;
1284 new.parent = holder.parent;
1285 PageOp::Insert {
1286 container,
1287 before: (rng.coin()).then_some(paragraph),
1288 paragraphs: vec![new],
1289 }
1290 }
1291 5 => PageOp::Delete { object: paragraph },
1292 6 => PageOp::Move {
1293 object: paragraph,
1294 parent: Some(container),
1295 before: siblings.get(rng.pick(siblings.len().max(1)).unwrap_or(0)).copied(),
1296 },
1297 7 => PageOp::Level {
1298 paragraph,
1299 level: (holder.level + 1).clamp(1, 4) - (rng.next() % 2) as u32,
1300 },
1301 8 => PageOp::Outline {
1302 object: paragraph,
1303 edit: crate::OutlineEdit::Collapsed(rng.coin()),
1304 },
1305 9 => PageOp::Paragraph {
1306 paragraph,
1307 alignment: Some((rng.next() % 3) as u8),
1308 rtl: None,
1309 space_before: Some(4.5),
1310 space_after: None,
1311 line_spacing: None,
1312 language: None,
1313 },
1314 10 => {
1315 let right = texts.iter().skip_while(|(p, _, _)| *p != paragraph).nth(1)?;
1316 PageOp::Join {
1317 left: text,
1318 right: right.1,
1319 }
1320 }
1321 11 => PageOp::List {
1322 paragraph,
1323 lists: vec![(
1324 id(),
1325 Definition {
1326 kind: Kind::List {
1327 font: Some("Calibri".into()),
1328 format: Some("\u{fffd}1.".into()),
1329 restart: None,
1330 bullet: None,
1331 },
1332 format: Format::default(),
1333 },
1334 )],
1335 },
1336 12 => {
1337 let definition = id();
1338 PageOp::Tags {
1339 target: if rng.coin() { paragraph } else { text },
1340 tags: vec![crate::document::Tag {
1341 definition: Some(definition),
1342 action_type: None,
1343 status: 0,
1344 created: Some(1_262_401_445),
1345 completed: None,
1346 start: None,
1347 due: None,
1348 task_id: None,
1349 extra_set: 0,
1350 }],
1351 definitions: vec![(
1352 definition,
1353 Definition {
1354 kind: Kind::TagDefinition {
1355 label: Some("Random".into()),
1356 action_type: Some(0),
1357 shape: Some(3),
1358 color: None,
1359 highlight: None,
1360 },
1361 format: Format::default(),
1362 },
1363 )],
1364 }
1365 }
1366 13 => PageOp::Link {
1367 text,
1368 range,
1369 target: rng.coin().then(|| "https://example.invalid/random".into()),
1370 },
1371 14 => PageOp::Add {
1372 object: PageObject::Outline(Outline {
1373 id: id(),
1374 title: false,
1375 min_width: None,
1376 layout: Layout {
1377 x: Some(36.0),
1378 y: Some(300.0 + (rng.next() % 100) as f32),
1379 ..Default::default()
1380 },
1381 indents: Vec::new(),
1382 paragraphs: vec![text_paragraph(&rng.text(), Format::default())],
1383 unsupported: Vec::new(),
1384 }),
1385 before: None,
1386 },
1387 _ => PageOp::Add {
1388 object: PageObject::Ink(Ink {
1389 id: id(),
1390 layout: Layout::default(),
1391 strokes: vec![stroke(rng)],
1392 groups: Vec::new(),
1393 }),
1394 before: None,
1395 },
1396 })
1397}
1398
1399/// Random edits of tables, pictures, ink, outlines and styles the page holds.
1400fn random_object_op(page: &Page, rng: &mut Rng) -> Option<PageOp> {
1401 let tables: Vec<Table> = model::lists(page)
1402 .into_iter()
1403 .flat_map(|(_, _, list)| list.iter())
1404 .filter_map(|p| match &p.content {
1405 ParagraphContent::Table(table) => Some(table.clone()),
1406 _ => None,
1407 })
1408 .collect();
1409 let outlines: Vec<&Outline> = page
1410 .objects
1411 .iter()
1412 .filter_map(|o| match o {
1413 PageObject::Outline(outline) if !outline.title => Some(outline),
1414 _ => None,
1415 })
1416 .collect();
1417 let pictures: Vec<&Image> = page
1418 .objects
1419 .iter()
1420 .filter_map(|o| match o {
1421 PageObject::Image(image) => Some(image),
1422 _ => None,
1423 })
1424 .collect();
1425 let inks: Vec<&Ink> = page
1426 .objects
1427 .iter()
1428 .filter_map(|o| match o {
1429 PageObject::Ink(ink) if ink.groups.is_empty() => Some(ink),
1430 _ => None,
1431 })
1432 .collect();
1433 match rng.next() % 6 {
1434 0 | 1 => {
1435 let table = tables.get(rng.pick(tables.len())?)?;
1436 let indents = table.rows[0].cells[0].indents.clone();
1437 let cell = || TableCell {
1438 paragraphs: vec![text_paragraph("cell", Format::default())],
1439 ..new_cell(&indents)
1440 };
1441 let edit = match rng.next() % 7 {
1442 0 => TableEdit::Rows {
1443 before: table.rows.get(rng.pick(table.rows.len() + 1)?).map(|r| r.id),
1444 rows: vec![TableRow {
1445 id: id(),
1446 cells: table.columns.iter().map(|_| cell()).collect(),
1447 }],
1448 },
1449 1 => TableEdit::Column {
1450 at: rng.pick(table.columns.len() + 1)? as u32,
1451 width: 60.0,
1452 cells: table.rows.iter().map(|_| cell()).collect(),
1453 },
1454 2 => TableEdit::DeleteRow(table.rows[rng.pick(table.rows.len())?].id),
1455 3 => TableEdit::DeleteColumn(rng.pick(table.columns.len())? as u32),
1456 4 => TableEdit::Columns(
1457 table
1458 .columns
1459 .iter()
1460 .map(|c| TableColumn {
1461 width: c.width + 9.0,
1462 locked: !c.locked,
1463 })
1464 .collect(),
1465 ),
1466 5 => TableEdit::Borders(rng.coin()),
1467 _ => {
1468 let cells: Vec<&TableCell> = table.rows.iter().flat_map(|r| &r.cells).collect();
1469 let cell = cells[rng.pick(cells.len())?];
1470 TableEdit::Cell {
1471 cell: cell.id,
1472 shading: Some(0x00ccff),
1473 indents: vec![18.0, 0.0, 27.0, 27.0],
1474 }
1475 }
1476 };
1477 Some(PageOp::Table {
1478 table: table.id,
1479 edit,
1480 })
1481 }
1482 2 => {
1483 let outline = outlines.get(rng.pick(outlines.len())?)?;
1484 Some(PageOp::Outline {
1485 object: outline.id,
1486 edit: if rng.coin() {
1487 crate::OutlineEdit::Position {
1488 x: (rng.next() % 300) as f32,
1489 y: (rng.next() % 300) as f32,
1490 }
1491 } else {
1492 crate::OutlineEdit::Width {
1493 points: 100.0 + (rng.next() % 300) as f32,
1494 user_set: rng.coin(),
1495 }
1496 },
1497 })
1498 }
1499 3 => {
1500 let picture = pictures.get(rng.pick(pictures.len())?)?;
1501 Some(PageOp::Picture {
1502 picture: picture.id,
1503 layout: Layout {
1504 x: Some((rng.next() % 300) as f32),
1505 y: Some((rng.next() % 300) as f32),
1506 max_width: Some(40.0),
1507 max_height: Some(30.0),
1508 width_set_by_user: Some(true),
1509 reserved_width: picture.layout.reserved_width,
1510 },
1511 alt: Some(rng.text()),
1512 })
1513 }
1514 4 => {
1515 let ink = inks.get(rng.pick(inks.len())?)?;
1516 let remove = if ink.strokes.len() > 1 && rng.coin() {
1517 vec![ink.strokes[rng.pick(ink.strokes.len())?].id]
1518 } else {
1519 Vec::new()
1520 };
1521 Some(PageOp::Strokes {
1522 ink: ink.id,
1523 add: vec![stroke(rng)],
1524 remove,
1525 })
1526 }
1527 _ => {
1528 let paragraphs: Vec<&PageParagraph> = model::lists(page)
1529 .into_iter()
1530 .flat_map(|(_, _, list)| list.iter())
1531 .filter(|p| p.text().is_some())
1532 .collect();
1533 let paragraph = paragraphs.get(rng.pick(paragraphs.len())?)?;
1534 Some(PageOp::Style {
1535 paragraph: paragraph.id,
1536 style: id(),
1537 definition: Definition {
1538 kind: Kind::Style {
1539 name: Some("Heading 3".into()),
1540 },
1541 format: Format {
1542 bold: Some(true),
1543 font_size: Some(12.0),
1544 color: Some(0x7d4f1e),
1545 ..Default::default()
1546 },
1547 },
1548 })
1549 }
1550 }
1551}
1552
1553/// Random edits through a section, sealed now and then: each applied edit reads back as the
1554/// model predicts, a refused one changes nothing, and reopening every sealed image gives
1555/// the same pages.
1556#[test]
1557fn random_ops_read_back_as_the_model_predicts() {
1558 let steps = std::env::var("OP_STEPS").map_or(60, |s| s.parse().unwrap());
1559 let (mut applied, mut refused, mut unseen) = (0, 0, 0);
1560 let mut mismatches: Vec<String> = Vec::new();
1561 for (name, source) in SOURCES {
1562 let spaces = pages(source);
1563 for seed in 0..2u64 {
1564 let arena = Arena::default();
1565 let mut section = Section::open(&arena, source.to_vec()).unwrap();
1566 let mut rng = Rng(seed * 104_729 + name.len() as u64);
1567 for step in 0..steps {
1568 let space = spaces[step % spaces.len().min(3)];
1569 let before = section.page(space).unwrap();
1570 let ops: Vec<Op> = (0..1 + rng.next() % 2)
1571 .filter_map(|_| random_op(&before, &mut rng))
1572 .map(|op| Op::Page { space, op })
1573 .collect();
1574 if ops.is_empty() {
1575 continue;
1576 }
1577 let edit = Edit { at: AT + step as u64, ops };
1578 // Text whose emptied final run keeps an insertion style the model cannot show.
1579 let hidden: Vec<ExGuid> = section
1580 .active(space)
1581 .unwrap()
1582 .view
1583 .nodes
1584 .iter()
1585 .filter(|(_, node)| {
1586 matches!(&node.kind, Kind::RichText { text, runs, .. }
1587 if !text.is_empty() && runs.len() > 1 && runs.last().is_some_and(|r| r.start == r.end))
1588 })
1589 .map(|(id, _)| *id)
1590 .collect();
1591 match section.apply("Author", &edit) {
1592 Ok(()) => {
1593 applied += 1;
1594 let mut predicted = before.clone();
1595 for op in &edit.ops {
1596 let Op::Page { op, .. } = op else { unreachable!() };
1597 model::apply(&mut predicted, op).unwrap();
1598 }
1599 let stored = section.page(space).unwrap();
1600 let touches_hidden = edit.ops.iter().any(|op| {
1601 matches!(op, Op::Page { op: PageOp::Text { text, .. } | PageOp::Link { text, .. } | PageOp::Split { text, .. }, .. } if hidden.contains(text))
1602 });
1603 // A restyled run may state what its old style did, which the model
1604 // cannot tell from inheriting it.
1605 let restyles = edit.ops.iter().any(|op| {
1606 matches!(op, Op::Page { op: PageOp::Style { paragraph, .. }, .. }
1607 if model::paragraph(&before, *paragraph).is_some_and(|p| p.style.is_some()))
1608 });
1609 if (touches_hidden || restyles) && normalize(&predicted) != normalize(&stored) {
1610 unseen += 1;
1611 continue;
1612 }
1613 if normalize(&predicted) != normalize(&stored) {
1614 let kinds: Vec<String> = edit
1615 .ops
1616 .iter()
1617 .map(|op| match op {
1618 Op::Page { op, .. } => format!("{op:?}").split([' ', '{']).next().unwrap().to_owned(),
1619 Op::Section(_) => "Section".into(),
1620 })
1621 .collect();
1622 if std::env::var("OP_DEBUG").is_ok() {
1623 let tree = |page: &Page| -> String {
1624 model::lists(page)
1625 .into_iter()
1626 .map(|(_, owner, list)| {
1627 format!(
1628 "{}: {}",
1629 owner.n,
1630 list.iter()
1631 .map(|p| format!("{}<{:?}>@{}", p.id.n, p.parent.map(|p| p.n), p.level))
1632 .collect::<Vec<_>>()
1633 .join(" ")
1634 )
1635 })
1636 .collect::<Vec<_>>()
1637 .join("\n")
1638 };
1639 println!("ops {:?}\nbefore\n{}\nstored\n{}\npredicted\n{}", edit.ops, tree(&before), tree(&stored), tree(&predicted));
1640 }
1641 if mismatches.len() < 6 {
1642 println!(
1643 "{name} seed {seed} step {step} {kinds:?}: {}",
1644 first_difference(&normalize(&stored), &normalize(&predicted))
1645 );
1646 }
1647 mismatches.push(kinds.join("+"));
1648 }
1649 }
1650 Err(OpError::Failed(error)) => panic!("{name} step {step}: {error:?}"),
1651 Err(_) => {
1652 refused += 1;
1653 assert!(section.page(space).unwrap() == before, "{name} step {step}: a refused edit changed the page");
1654 }
1655 }
1656 if step % 7 == 6 || step + 1 == steps {
1657 section.seal().unwrap();
1658 let image = section.image();
1659 let reopened = Section::open(&arena, image).unwrap();
1660 for space in &spaces {
1661 assert!(reopened.page(*space).unwrap() == section.page(*space).unwrap(), "{name} step {step}: reopening changed a page");
1662 }
1663 }
1664 }
1665 }
1666 }
1667 println!("{applied} edits applied, {refused} refused, {unseen} restyled or typed into a hidden insertion style");
1668 let mut kinds = BTreeMap::new();
1669 for kind in &mismatches {
1670 *kinds.entry(kind.clone()).or_insert(0) += 1;
1671 }
1672 println!("model mispredictions: {kinds:?}");
1673 assert!(mismatches.is_empty());
1674 assert!(applied > refused);
1675}
1676
1677/// Page creation, moves, indentation and removal through a section store what the image
1678/// writers store, and the section lists the pages the document does.
1679#[test]
1680fn section_ops_store_what_the_image_writers_store() {
1681 let sources: Vec<Vec<u8>> = vec![
1682 crate::create_section("pages.one", "First", "Author").unwrap(),
1683 include_bytes!("../../../../corpus/page-lifecycle/04-nested/notebook/Lifecycle.one").to_vec(),
1684 SOURCES[3].1.to_vec(),
1685 ];
1686 let (mut bytes, mut total) = (0, 0);
1687 for (n, source) in sources.iter().enumerate() {
1688 let arena = Arena::default();
1689 let mut section = Section::open(&arena, source.to_vec()).unwrap();
1690 let mut image = source.clone();
1691 let mut rng = Rng(n as u64 + 17);
1692 for step in 0..12u64 {
1693 let spaces = pages(&image);
1694 let op = match step % 4 {
1695 0 => SectionOp::Create(
1696 seeded(1 << 50 | step << 20, || {
1697 crate::PageCreation::new(
1698 spaces.get(rng.pick(spaces.len()).unwrap_or(0)).copied().filter(|_| rng.coin()),
1699 Some("Created"),
1700 "Author",
1701 )
1702 })
1703 .unwrap(),
1704 ),
1705 1 => {
1706 let Some(space) = spaces.get(rng.pick(spaces.len()).unwrap_or(0)).copied() else { continue };
1707 let level = if spaces[0] == space { 1 } else { 1 + (rng.next() % 2) as u32 };
1708 SectionOp::Pages(vec![seeded(1 << 51 | step << 20, || crate::PageEdit::set_level(space, level)).unwrap()])
1709 }
1710 2 => {
1711 let Some(space) = spaces.get(rng.pick(spaces.len()).unwrap_or(0)).copied() else { continue };
1712 SectionOp::Pages(vec![seeded(1 << 52 | step << 20, || crate::PageEdit::move_to(space, None, 1)).unwrap()])
1713 }
1714 _ => {
1715 if spaces.len() < 2 {
1716 continue;
1717 }
1718 SectionOp::Delete(vec![spaces[rng.pick(spaces.len()).unwrap()]])
1719 }
1720 };
1721 let expected = seeded(1 << 40 | step << 16, || {
1722 crate::create::at(AT, || match &op {
1723 SectionOp::Create(creation) => crate::PreparedEdit::create_page(&image, creation),
1724 SectionOp::Pages(edits) => crate::PreparedEdit::pages(&image, edits),
1725 SectionOp::Delete(spaces) => crate::PreparedEdit::delete_pages_permanently(&image, spaces),
1726 SectionOp::Import { .. } => unreachable!(),
1727 })
1728 .map(|edit| edit.as_bytes().to_vec())
1729 });
1730 let applied = seeded(1 << 40 | step << 16, || {
1731 section
1732 .apply("Author", &Edit { at: AT, ops: vec![Op::Section(op.clone())] })
1733 .map(|()| section.seal().unwrap())
1734 });
1735 match (expected, applied) {
1736 (Err(_), Err(_)) => continue,
1737 (Ok(expected), Ok(_)) => {
1738 total += 1;
1739 let written = section.image();
1740 if written == expected {
1741 bytes += 1;
1742 } else {
1743 let store = Store::parse(&written).unwrap();
1744 RevisionIndex::parse(&store).unwrap().validate_current().unwrap();
1745 let spaces = pages(&expected);
1746 assert_eq!(pages(&written), spaces, "{n} step {step} {op:?}");
1747 for space in spaces {
1748 assert_eq!(read(&written, space), read(&expected, space), "{n} step {step}");
1749 }
1750 }
1751 image = expected;
1752 let store = Store::parse(&image).unwrap();
1753 let index = RevisionIndex::parse(&store).unwrap();
1754 let document = Document::parse(&index).unwrap();
1755 let listed: Vec<(ExGuid, String, u32)> = document
1756 .pages()
1757 .unwrap()
1758 .into_iter()
1759 .map(|(space, page)| {
1760 let (title, level) = Page::heading(document.active(space).unwrap(), page);
1761 (space, title, level)
1762 })
1763 .collect();
1764 assert_eq!(section.pages().unwrap(), listed, "{n} step {step}");
1765 }
1766 (expected, applied) => panic!("{n} step {step} {op:?}: {:?} vs {:?}", expected.err(), applied.err()),
1767 }
1768 }
1769 }
1770 println!("{bytes} of {total} section edits byte-identical");
1771 assert!(total > 12);
1772}
1773
1774/// Importing a page stores what creating it and writing its copy through the page writer
1775/// does.
1776#[test]
1777fn an_imported_page_reads_as_the_page_writer_leaves_it() {
1778 let target = crate::create_section("import.one", "First", "Author").unwrap();
1779 let mut imported = 0;
1780 for (name, source) in SOURCES {
1781 for space in pages(source).into_iter().take(2) {
1782 let Ok(copy) = read(source, space).copy() else {
1783 continue;
1784 };
1785 let creation = crate::PageCreation::new(None, Some(&copy.title), "Author").unwrap();
1786 let created = crate::PreparedEdit::create_page(&target, &creation).unwrap();
1787 let created = created.as_bytes();
1788 let mut after = read(created, creation.space());
1789 after.objects.retain(|object| matches!(object, PageObject::Title(_)));
1790 after.objects.extend(
1791 copy.objects
1792 .iter()
1793 .filter(|object| !matches!(object, PageObject::Title(_)))
1794 .cloned(),
1795 );
1796 after.definitions = copy.definitions.clone();
1797 let expected = crate::create::at(AT, || {
1798 crate::PreparedEdit::page(created, creation.space(), &after, "Author")
1799 });
1800 let arena = Arena::default();
1801 let mut section = Section::open(&arena, target.clone()).unwrap();
1802 let applied = section.apply(
1803 "Author",
1804 &Edit {
1805 at: AT,
1806 ops: vec![Op::Section(SectionOp::Import {
1807 creation: creation.clone(),
1808 page: copy.clone(),
1809 })],
1810 },
1811 );
1812 match (expected, applied) {
1813 (Err(_), Err(_)) => continue,
1814 (Ok(expected), Ok(())) => {
1815 section.seal().unwrap();
1816 let image = section.image();
1817 let store = Store::parse(&image).unwrap();
1818 RevisionIndex::parse(&store).unwrap().validate_current().unwrap();
1819 let (ours, theirs) = (
1820 normalize(&read(&image, creation.space())),
1821 normalize(&read(expected.as_bytes(), creation.space())),
1822 );
1823 assert!(ours == theirs, "{name}: {}", first_difference(&theirs, &ours));
1824 imported += 1;
1825 }
1826 (expected, applied) => panic!("{name}: {:?} vs {:?}", expected.err(), applied.err()),
1827 }
1828 }
1829 }
1830 assert!(imported > 10, "{imported} pages imported");
1831}
1832
1833/// The ops for the changed paragraphs of one outline, lowered from them alone.
1834fn lower_range(before: &Page, after: &Page) -> Option<Result<Vec<PageOp>, crate::Error>> {
1835 if before.objects.len() != after.objects.len() {
1836 return None;
1837 }
1838 let mut changed = None;
1839 for (a, b) in before.objects.iter().zip(&after.objects) {
1840 match (a, b) {
1841 (PageObject::Outline(a), PageObject::Outline(b)) if a.id == b.id => {
1842 if a.paragraphs != b.paragraphs {
1843 if changed.is_some() || (Outline { paragraphs: Vec::new(), ..a.clone() }) != (Outline { paragraphs: Vec::new(), ..b.clone() }) {
1844 return None;
1845 }
1846 changed = Some((a, b));
1847 }
1848 }
1849 (a, b) if a == b => {}
1850 _ => return None,
1851 }
1852 }
1853 let (a, b) = changed?;
1854 let (a, b) = (&a.paragraphs, &b.paragraphs);
1855 let prefix = a.iter().zip(b).take_while(|(x, y)| x == y).count();
1856 let suffix = a[prefix..]
1857 .iter()
1858 .rev()
1859 .zip(b[prefix..].iter().rev())
1860 .take_while(|(x, y)| x == y)
1861 .count();
1862 let (mut end_a, mut end_b) = (a.len() - suffix, b.len() - suffix);
1863 // A range holds the children of what it changes.
1864 while end_a < a.len() {
1865 let ids: Vec<ExGuid> = a[prefix..end_a].iter().chain(&b[prefix..end_b]).map(|p| p.id).collect();
1866 if a[end_a].parent.is_some_and(|parent| ids.contains(&parent)) {
1867 end_a += 1;
1868 end_b += 1;
1869 } else {
1870 break;
1871 }
1872 }
1873 let outline = match before.objects.iter().find(|o| matches!(o, PageObject::Outline(o) if o.paragraphs == *a)) {
1874 Some(PageObject::Outline(outline)) => outline.id,
1875 _ => return None,
1876 };
1877 let mut definitions = before.definitions.clone();
1878 definitions.extend(after.definitions.clone());
1879 Some(lower(outline, &a[prefix..end_a], &b[prefix..end_b], b.get(end_b), &definitions))
1880}
1881
1882/// Lowering the changed range of an outline stores what lowering the whole page does.
1883#[test]
1884fn a_range_lowers_as_its_page_does() {
1885 let families = [
1886 Family::Text, Family::Format, Family::Insert, Family::Paste, Family::Delete, Family::Move,
1887 Family::Collapse, Family::List, Family::Tag, Family::Split, Family::Join, Family::Indent,
1888 Family::Outdent, Family::Alignment, Family::Style, Family::Link, Family::NewTable,
1889 Family::ParagraphPicture, Family::Attachment, Family::Equation,
1890 ];
1891 let (mut same, mut total, mut smaller) = (0, 0, 0);
1892 let seeds = std::env::var("OP_SEEDS").map_or(3, |s| s.parse().unwrap());
1893 for (name, source) in SOURCES {
1894 for (p, space) in pages(source).into_iter().enumerate().take(4) {
1895 for family in families {
1896 for seed in 0..seeds {
1897 let mut rng = Rng(seed * 7919 + p as u64 * 131 + family as u64);
1898 let before = read(source, space);
1899 let mut after = before.clone();
1900 if !mutate(&mut after, &mut rng, family) {
1901 continue;
1902 }
1903 let Some(ranged) = lower_range(&before, &after) else {
1904 continue;
1905 };
1906 let whole = lower_page(&before, &after);
1907 let (Ok(ranged), Ok(whole)) = (ranged, whole) else {
1908 continue;
1909 };
1910 total += 1;
1911 let write = |ops: &[PageOp]| {
1912 let arena = Arena::default();
1913 let mut section = Section::open(&arena, source.to_vec()).unwrap();
1914 let edit = Edit {
1915 at: AT,
1916 ops: ops.iter().map(|op| Op::Page { space, op: op.clone() }).collect(),
1917 };
1918 section.apply("Author", &edit).map(|()| normalize(&section.page(space).unwrap()))
1919 };
1920 let (ours, theirs) = (write(&ranged), write(&whole));
1921 match (ours, theirs) {
1922 (Ok(ours), Ok(theirs)) => {
1923 assert!(ours == theirs, "{name} page {p} {family:?} seed {seed}: {}", first_difference(&theirs, &ours));
1924 same += 1;
1925 }
1926 (Err(_), Err(_)) => same += 1,
1927 (ours, theirs) => panic!("{name} page {p} {family:?} seed {seed}: {ours:?} vs {theirs:?}"),
1928 }
1929 let length = |ops: &[PageOp]| format!("{ops:?}").len();
1930 smaller += usize::from(length(&ranged) <= length(&whole));
1931 }
1932 }
1933 }
1934 }
1935 println!("{same} of {total} ranges store what their pages do; {smaller} lowered no larger");
1936 assert_eq!(same, total);
1937 assert!(total > 100);
1938}
1939
1940/// One keystroke as ops on the 3000-paragraph probe page:
1941/// `SECTION_PROBE=path cargo test --release -p onestore --lib keystroke_ops -- --ignored --nocapture`.
1942#[test]
1943#[ignore]
1944fn keystroke_ops() {
1945 use std::time::{Duration, Instant};
1946 fn median(mut samples: Vec<Duration>) -> Duration {
1947 samples.sort();
1948 samples[samples.len() / 2]
1949 }
1950 let path = std::env::var("SECTION_PROBE").unwrap_or("/tmp/probe3000.one".into());
1951 let image = std::fs::read(&path).unwrap();
1952 let space = pages(&image)
1953 .into_iter()
1954 .max_by_key(|space| read(&image, *space).objects.len() + format!("{:?}", read(&image, *space)).len())
1955 .unwrap();
1956 let page = read(&image, space);
1957 let outline = page
1958 .objects
1959 .iter()
1960 .filter_map(|o| match o {
1961 PageObject::Outline(outline) => Some(outline),
1962 _ => None,
1963 })
1964 .max_by_key(|o| o.paragraphs.len())
1965 .unwrap();
1966 let middle = outline.paragraphs.len() / 2;
1967 let paragraph = &outline.paragraphs[middle];
1968 let text = paragraph.text().unwrap().id;
1969 println!("{path}: {} bytes, {} paragraphs in the outline", image.len(), outline.paragraphs.len());
1970
1971 // Emitting: a keystroke is one op; a generic range replacement lowers its paragraphs.
1972 let mut edited = paragraph.clone();
1973 let format = edited.text().unwrap().text.format_at(0).unwrap().clone();
1974 edited
1975 .text_mut()
1976 .unwrap()
1977 .text
1978 .apply(crate::page::text::Edit {
1979 range: 0..0,
1980 replacement: Paragraph::new("x".into(), format),
1981 })
1982 .unwrap();
1983 let mut samples = Vec::new();
1984 for _ in 0..200 {
1985 let start = Instant::now();
1986 let ops = lower(
1987 outline.id,
1988 std::slice::from_ref(paragraph),
1989 std::slice::from_ref(&edited),
1990 outline.paragraphs.get(middle + 1),
1991 &page.definitions,
1992 )
1993 .unwrap();
1994 samples.push(start.elapsed());
1995 assert_eq!(ops.len(), 1);
1996 }
1997 println!("lower one paragraph: {:?}", median(samples));
1998
1999 let arena = Arena::default();
2000 let start = Instant::now();
2001 let mut section = Section::open(&arena, image.clone()).unwrap();
2002 println!("Section::open: {:?}", start.elapsed());
2003 let start = Instant::now();
2004 section.active(space).unwrap();
2005 println!("first edit opens the page: {:?}", start.elapsed());
2006 // Five keystrokes a second, each sealed: the outline's modification time ticks once a
2007 // second, which re-declares its child list.
2008 let (mut applies, mut seals, mut sizes) = (Vec::new(), Vec::new(), Vec::new());
2009 for keystroke in 0..300u32 {
2010 let edit = Edit {
2011 at: AT + u64::from(keystroke) * 2_000_000,
2012 ops: vec![Op::Page {
2013 space,
2014 op: PageOp::Text {
2015 text,
2016 range: keystroke..keystroke,
2017 with: "x".into(),
2018 },
2019 }],
2020 };
2021 let start = Instant::now();
2022 section.apply("Probe", &edit).unwrap();
2023 applies.push(start.elapsed());
2024 let start = Instant::now();
2025 let transaction = section.seal().unwrap().unwrap();
2026 seals.push(start.elapsed());
2027 sizes.push((
2028 transaction.append.len(),
2029 transaction.patches.iter().map(|(_, bytes)| bytes.len()).sum::<usize>(),
2030 ));
2031 }
2032 sizes.sort();
2033 println!(
2034 "Section::apply(Text) {:?}, seal {:?}; appended {} bytes (max {}), patched {} bytes (median of 300)",
2035 median(applies),
2036 median(seals),
2037 sizes[sizes.len() / 2].0,
2038 sizes.last().unwrap().0,
2039 sizes[sizes.len() / 2].1,
2040 );
2041 let start = Instant::now();
2042 let reopened = Section::open(&arena, section.image()).unwrap();
2043 println!("reopen after 300 seals: {:?}", start.elapsed());
2044 assert_eq!(reopened.page(space).unwrap(), section.page(space).unwrap());
2045}
2046
2047/// Writes a notebook for the native cold-open gate of the op path to `OP_GATE_EXPORT`:
2048/// corpus pages imported as ops, then edited by lowered page edits of every family, new
2049/// pages, an outline move on a page without a margin origin, page moves and a removal.
2050#[test]
2051#[ignore]
2052fn op_gate_candidate() {
2053 let directory = std::path::PathBuf::from(std::env::var("OP_GATE_EXPORT").unwrap());
2054 std::fs::create_dir_all(&directory).unwrap();
2055 let source = crate::create_section("ops.one", "Ops gate", "Author").unwrap();
2056 let arena = Arena::default();
2057 let mut section = Section::open(&arena, source).unwrap();
2058 let at = std::cell::Cell::new(AT);
2059 let next = || {
2060 at.set(at.get() + 20_000_000);
2061 at.get()
2062 };
2063 let apply = |section: &mut Section<'_>, op: Op| {
2064 let result = section.apply("Gate author", &Edit { at: next(), ops: vec![op] });
2065 section.seal().unwrap();
2066 result
2067 };
2068 let mut imported = Vec::new();
2069 for (name, bytes) in SOURCES.iter().filter(|(name, _)| *name != "enter-probe") {
2070 let Some(space) = pages(bytes).first().copied() else { continue };
2071 let page = read(bytes, space);
2072 if page.objects.iter().any(|o| matches!(o, PageObject::Unsupported(_))) {
2073 continue;
2074 }
2075 let Ok(copy) = page.copy() else { continue };
2076 let creation = crate::PageCreation::new(None, Some(&format!("Imported {name}")), "Gate author").unwrap();
2077 if apply(&mut section, Op::Section(SectionOp::Import { creation: creation.clone(), page: copy })).is_ok() {
2078 imported.push(creation.space());
2079 }
2080 }
2081 let mut edits = 0;
2082 for (n, space) in imported.iter().enumerate() {
2083 for (k, family) in FAMILIES.iter().enumerate() {
2084 if (k + n) % 4 != 0 {
2085 continue;
2086 }
2087 let before = section.page(*space).unwrap();
2088 let mut after = before.clone();
2089 let mut rng = Rng((n * 100 + k) as u64);
2090 if !mutate(&mut after, &mut rng, *family) {
2091 continue;
2092 }
2093 // Edits inside a field code leave a field OneNote and this reader resolve apart,
2094 // whichever writer stores them.
2095 let fields = |page: &Page| -> Vec<(ExGuid, String)> {
2096 model::lists(page)
2097 .into_iter()
2098 .flat_map(|(_, _, list)| list.iter())
2099 .filter_map(|p| p.text())
2100 .filter(|t| t.text.text().contains('\u{fddf}'))
2101 .map(|t| (t.id, t.text.text().to_owned()))
2102 .collect()
2103 };
2104 let edited = fields(&before);
2105 if fields(&after).iter().any(|(id, text)| edited.iter().any(|(e, old)| e == id && old != text)) {
2106 continue;
2107 }
2108 if section.apply_page("Gate author", next(), *space, &after).is_ok() {
2109 section.seal().unwrap();
2110 edits += 1;
2111 }
2112 }
2113 }
2114 // A created page: margins, an outline, and its first move adding the margin origin.
2115 let creation = crate::PageCreation::new(None, Some("Created by ops"), "Gate author").unwrap();
2116 apply(&mut section, Op::Section(SectionOp::Create(creation.clone()))).unwrap();
2117 let outline = Outline {
2118 id: id(),
2119 title: false,
2120 min_width: None,
2121 layout: Layout { x: Some(36.0), y: Some(86.4), ..Default::default() },
2122 indents: Vec::new(),
2123 paragraphs: vec![text_paragraph("Typed through ops", Format::default())],
2124 unsupported: Vec::new(),
2125 };
2126 let outline_id = outline.id;
2127 let text = outline.paragraphs[0].text().unwrap().id;
2128 let space = creation.space();
2129 let page = |op| Op::Page { space, op };
2130 apply(&mut section, page(PageOp::Add { object: PageObject::Outline(outline), before: None })).unwrap();
2131 apply(&mut section, page(PageOp::Outline { object: outline_id, edit: crate::OutlineEdit::Position { x: 72.0, y: 108.0 } })).unwrap();
2132 apply(&mut section, page(PageOp::Text { text, range: 5..5, with: " and sealed".into() })).unwrap();
2133 apply(&mut section, page(PageOp::Link { text, range: 0..5, target: Some("https://example.invalid/ops".into()) })).unwrap();
2134 let spaces: Vec<ExGuid> = section.pages().unwrap().into_iter().map(|(s, _, _)| s).collect();
2135 apply(&mut section, Op::Section(SectionOp::Pages(vec![crate::PageEdit::move_to(space, Some(spaces[1]), 1).unwrap()]))).unwrap();
2136 apply(&mut section, Op::Section(SectionOp::Pages(vec![crate::PageEdit::set_level(spaces[2], 2).unwrap()]))).unwrap();
2137 apply(&mut section, Op::Section(SectionOp::Delete(vec![*spaces.last().unwrap()]))).unwrap();
2138 let image = section.image();
2139 let store = Store::parse(&image).unwrap();
2140 RevisionIndex::parse(&store).unwrap().validate_current().unwrap();
2141 let count = section.pages().unwrap().len();
2142 std::fs::write(directory.join("ops.one"), &image).unwrap();
2143 std::fs::write(
2144 directory.join("Open Notebook.onetoc2"),
2145 crate::create_table_of_contents("Open Notebook.onetoc2", &[("ops.one", store.header.file_id)]).unwrap(),
2146 )
2147 .unwrap();
2148 std::fs::write(directory.join("expected-count.txt"), count.to_string()).unwrap();
2149 println!("{} imported pages, {edits} lowered edits, {count} pages", imported.len());
2150}
crates/onestore/src/outline.rs+11
...@@ -126,6 +126,17 @@ impl OutlineEdit {...@@ -126,6 +126,17 @@ impl OutlineEdit {
126 }126 }
127 }127 }
128 if matches!(self, Self::Position { .. }) {128 if matches!(self, Self::Position { .. }) {
129 // OneNote gives a page its margin origin when an outline on it first moves.
130 let page = changed.get_mut(page).unwrap();
131 if !crate::PropertySets::parse(&page.bytes)?.sets[0]
132 .iter()
133 .any(|field| field.id == 0x14001d0f)
134 {
135 page.set(&[
136 (0x14001d0f, &1.0_f32.to_le_bytes()),
137 (0x14001d10, &0.4_f32.to_le_bytes()),
138 ])?;
139 }
129 update_title(active, view.clone(), &mut changed)?;140 update_title(active, view.clone(), &mut changed)?;
130 }141 }
131 Ok(changed)142 Ok(changed)
crates/onestore/src/page/ink.rs+1-1
...@@ -50,7 +50,7 @@ pub struct InkStroke {...@@ -50,7 +50,7 @@ pub struct InkStroke {
50}50}
5151
52impl Ink {52impl Ink {
53 pub(super) fn read(53 pub(crate) fn read(
54 revision: &Revision<'_>,54 revision: &Revision<'_>,
55 id: ExGuid,55 id: ExGuid,
56 node: &crate::document::Element<'_>,56 node: &crate::document::Element<'_>,
crates/onestore/src/page/mod.rs+96-6
...@@ -209,7 +209,7 @@ impl PartialEq for Attachment {...@@ -209,7 +209,7 @@ impl PartialEq for Attachment {
209}209}
210210
211impl Attachment {211impl Attachment {
212 fn read(212 pub(crate) fn read(
213 revision: &Revision<'_>,213 revision: &Revision<'_>,
214 id: ExGuid,214 id: ExGuid,
215 node: &crate::document::Element<'_>,215 node: &crate::document::Element<'_>,
...@@ -320,7 +320,7 @@ impl PartialEq for Image {...@@ -320,7 +320,7 @@ impl PartialEq for Image {
320}320}
321321
322impl Image {322impl Image {
323 fn read(323 pub(crate) fn read(
324 revision: &Revision<'_>,324 revision: &Revision<'_>,
325 id: ExGuid,325 id: ExGuid,
326 node: &crate::document::Element<'_>,326 node: &crate::document::Element<'_>,
...@@ -556,9 +556,11 @@ impl Page {...@@ -556,9 +556,11 @@ impl Page {
556 })?))556 })?))
557 })557 })
558 .transpose()?,558 .transpose()?,
559 // OneNote 2010 snaps to, and on the first move writes, (36, 14.4) when unset: 1.0
560 // and 0.4 half inches, read as stored ones are.
559 margin_origin: [561 margin_origin: [
560 margin_origin_x.unwrap_or(0.0),562 margin_origin_x.unwrap_or(1.0_f32 * 36.0),
561 margin_origin_y.unwrap_or(0.0),563 margin_origin_y.unwrap_or(0.4_f32 * 36.0),
562 ],564 ],
563 objects: Vec::new(),565 objects: Vec::new(),
564 definitions: BTreeMap::new(),566 definitions: BTreeMap::new(),
...@@ -633,8 +635,8 @@ impl Page {...@@ -633,8 +635,8 @@ impl Page {
633 };635 };
634 let mut outline = Outline {636 let mut outline = Outline {
635 id,637 id,
636 // OneNote flags the outline and its paragraph; `PageCreation` flags only638 // OneNote flags the outline and its paragraph, and still shows an outline
637 // the paragraph, and OneNote still shows that outline as the title.639 // whose paragraph alone is flagged as the title.
638 title: title_text(node)640 title: title_text(node)
639 || node641 || node
640 .children642 .children
...@@ -682,6 +684,94 @@ impl Page {...@@ -682,6 +684,94 @@ impl Page {
682 }684 }
683}685}
684686
687/// The format `container` passes to its children, as reading its page inherits it.
688fn inherited_by(
689 revision: &Revision<'_>,
690 parents: &BTreeMap<ExGuid, Vec<ExGuid>>,
691 container: ExGuid,
692) -> Result<Format, Error> {
693 let invalid = |message| Error { offset: 0, message };
694 let node = revision
695 .nodes
696 .get(&container)
697 .ok_or_else(|| invalid("Missing canvas outline object"))?;
698 let parent = || -> Result<ExGuid, Error> {
699 match parents.get(&container).map(Vec::as_slice) {
700 Some([parent]) => Ok(*parent),
701 _ => Err(invalid("Page content must have one parent")),
702 }
703 };
704 Ok(match &node.kind {
705 Kind::Outline { .. } => node.format.clone(),
706 Kind::OutlineGroup => node
707 .format
708 .inherit(&inherited_by(revision, parents, parent()?)?),
709 Kind::Paragraph {
710 paragraph_style, ..
711 } => {
712 let style = match paragraph_style {
713 Some(id) => revision.nodes.get(id).map(|n| n.format.clone()).unwrap_or_default(),
714 None => Format::default(),
715 };
716 node.format
717 .inherit(&style)
718 .inherit(&inherited_by(revision, parents, parent()?)?)
719 }
720 Kind::Cell { .. } => {
721 let row = parent()?;
722 let [table] = parents.get(&row).map(Vec::as_slice).unwrap_or_default() else {
723 return Err(invalid("A table row must have one table"));
724 };
725 let [holder] = parents.get(table).map(Vec::as_slice).unwrap_or_default() else {
726 return Err(invalid("A table must have one paragraph"));
727 };
728 node.format.inherit(
729 &revision.nodes[table]
730 .format
731 .inherit(&inherited_by(revision, parents, *holder)?),
732 )
733 }
734 _ => Format::default(),
735 })
736}
737
738/// The text of rich-text object `text` as its page model shows it, read from its ancestors
739/// alone.
740pub(crate) fn text_of(
741 revision: &Revision<'_>,
742 parents: &BTreeMap<ExGuid, Vec<ExGuid>>,
743 text: ExGuid,
744) -> Result<Paragraph, Error> {
745 let invalid = |message| Error { offset: 0, message };
746 let [paragraph] = parents.get(&text).map(Vec::as_slice).unwrap_or_default() else {
747 return Err(invalid("Select text belonging to one paragraph"));
748 };
749 let format = inherited_by(revision, parents, *paragraph)?;
750 let node = revision
751 .nodes
752 .get(&text)
753 .ok_or_else(|| invalid("Missing text object"))?;
754 let Kind::RichText {
755 paragraph_style, ..
756 } = &node.kind
757 else {
758 return Err(invalid("Select a rich-text object"));
759 };
760 let runs = revision.text_runs(text)?;
761 Ok(if runs.is_empty() {
762 let style = match paragraph_style {
763 Some(id) => revision.nodes.get(id).map(|n| n.format.clone()).unwrap_or_default(),
764 None => Format::default(),
765 };
766 Paragraph::new(String::new(), node.format.inherit(&style).inherit(&format))
767 } else {
768 Paragraph::from_runs(
769 runs.into_iter()
770 .map(|run| (run.text.to_owned(), run.format.inherit(&format))),
771 )
772 })
773}
774
685fn read_paragraphs(775fn read_paragraphs(
686 revision: &Revision<'_>,776 revision: &Revision<'_>,
687 container: &Element<'_>,777 container: &Element<'_>,
crates/onestore/src/page/write.rs+368-1803
...@@ -1,38 +1,29 @@...@@ -1,38 +1,29 @@
1//! Publishes an edited page model by lowering the difference from the stored page onto1//! Publishes an edited page model by lowering the difference from the stored page onto
2//! the typed writers, whose revisions accumulate in memory, then squashing them into one2//! the typed writers, whose revisions accumulate in memory, then squashing them into one
3//! revision per space.3//! revision per space. `PreparedEdit::page` keeps it until the op path replaces it.
44
5use super::{5use super::{Image, Ink, Outline, Page, PageObject, PageParagraph, ParagraphContent};
6 Attachment, Image, Ink, Math, Outline, Page, PageObject, PageParagraph, ParagraphContent, Table,
7};
8use crate::{6use crate::{
9 Error, ExGuid, Insertion, ObjectData, OutlineEdit, ParagraphJoin, ParagraphSplit, PropertySets,7 Error, ExGuid, Insertion, ObjectData, OutlineEdit, ParagraphJoin, ParagraphSplit, PropertySets,
10 RevisionIndex, Store, TextAttribute, TreeEdit, Value,8 RevisionIndex, Store, TreeEdit, Value,
11 active::{ActivePage, Changes},9 active::{ActivePage, Changes},
12 document::{Format, Kind, Tag},10 document::Kind,
11 op::{
12 content::{self, AttachmentIds},
13 levels,
14 lower::{View, collect_containers, kept_set, text_edit, validate},
15 properties,
16 table::{self, Structure},
17 },
13 write::{PropertyObject, RevisionEdit},18 write::{PropertyObject, RevisionEdit},
14};19};
15use bumpalo::Bump;20use bumpalo::Bump;
16use std::{21use std::collections::{BTreeMap, BTreeSet};
17 collections::{BTreeMap, BTreeSet},
18 ops::Range,
19};
20
21/// Property identifiers with their encoded values.
22type Values = Vec<(u32, Vec<u8>)>;
2322
24fn invalid(message: &'static str) -> Error {23fn invalid(message: &'static str) -> Error {
25 Error { offset: 0, message }24 Error { offset: 0, message }
26}25}
2726
28/// A native measurement array: a count byte, `header - 1` reserved bytes, then each value
29/// in inches.
30/// The `<ifndf>{GUID}` form a file-data declaration uses to name a payload in the file-data store.
31fn payload_reference(guid: [u8; 16]) -> String {
32 let id = ExGuid { guid, n: 0 }.to_string();
33 format!("<ifndf>{}", id.split(',').next().unwrap())
34}
35
36fn page_images(page: &Page) -> impl Iterator<Item = (ExGuid, &Image)> {27fn page_images(page: &Page) -> impl Iterator<Item = (ExGuid, &Image)> {
37 page.objects.iter().filter_map(|object| match object {28 page.objects.iter().filter_map(|object| match object {
38 PageObject::Image(image) => Some((image.id, image)),29 PageObject::Image(image) => Some((image.id, image)),
...@@ -47,292 +38,6 @@ fn page_ink(page: &Page) -> impl Iterator<Item = (ExGuid, &Ink)> {...@@ -47,292 +38,6 @@ fn page_ink(page: &Page) -> impl Iterator<Item = (ExGuid, &Ink)> {
47 })38 })
48}39}
4940
50/// The compact child references a page object holds.
51fn page_children(object: &PropertyObject) -> Result<Vec<u8>, Error> {
52 let properties = crate::PropertySets::parse(&object.bytes)?;
53 match properties.sets[0]
54 .iter()
55 .find(|p| p.id == 0x24001c20)
56 .map(|p| &p.value)
57 {
58 Some(crate::Value::References { compact_ids, .. }) => Ok(compact_ids.to_vec()),
59 None => Ok(Vec::new()),
60 _ => Err(invalid("The page has an invalid child list")),
61 }
62}
63
64/// Creates stroke objects (numbered from `first`) and one drawing-attribute object per
65/// distinct pen; returns the compact references for the data node's stroke list.
66fn write_strokes(
67 changed: &mut BTreeMap<ExGuid, PropertyObject>,
68 data_object: &mut PropertyObject,
69 strokes: &[(ExGuid, &super::InkStroke)],
70 first: usize,
71 filetime: u64,
72) -> Result<Vec<u8>, Error> {
73 let mut styles: Vec<(InkPen, ExGuid)> = Vec::new();
74 let mut references = Vec::new();
75 for (offset, (stroke_id, stroke)) in strokes.iter().enumerate() {
76 let pen = InkPen::of(stroke);
77 let style = match styles.iter().find(|(known, _)| *known == pen) {
78 Some((_, id)) => *id,
79 None => {
80 let id = ExGuid {
81 guid: crate::write::fresh_guid()?,
82 n: 1,
83 };
84 let mut style = PropertyObject {
85 jcid: 0x120048,
86 bytes: crate::create::properties(&pen.values())?,
87 global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])),
88 };
89 style.reference(id)?;
90 changed.insert(id, style);
91 styles.push((pen, id));
92 id
93 }
94 };
95 let mut object = PropertyObject {
96 jcid: 0x20047,
97 bytes: crate::create::properties(&stroke_values(
98 stroke,
99 (first + offset) as u32 + 1,
100 filetime,
101 )?)?,
102 global_ids: std::sync::Arc::new(BTreeMap::from([(0, stroke_id.guid)])),
103 };
104 object.reference(*stroke_id)?;
105 let style_reference = object.reference(style)?;
106 object.set(&[(0x20003409, &style_reference)])?;
107 changed.insert(*stroke_id, object);
108 references.extend(data_object.reference(*stroke_id)?);
109 }
110 Ok(references)
111}
112
113/// The drawing attributes OneNote shares between strokes drawn with the same pen.
114#[derive(PartialEq)]
115struct InkPen {
116 width: u32,
117 height: u32,
118 color: Option<u32>,
119 transparency: Option<u8>,
120 pen_tip: Option<u8>,
121}
122
123impl InkPen {
124 fn of(stroke: &super::InkStroke) -> Self {
125 Self {
126 width: (stroke.width * 2540.0 / 72.0).to_bits(),
127 height: (stroke.height * 2540.0 / 72.0).to_bits(),
128 color: stroke.color,
129 transparency: stroke.transparency,
130 pen_tip: stroke.pen_tip,
131 }
132 }
133
134 fn values(&self) -> Values {
135 let mut values: Values = vec![
136 (0x1c00340a, super::ink::DIMENSIONS.to_vec()),
137 (0x1400340c, self.height.to_le_bytes().to_vec()),
138 (0x1400340d, self.width.to_le_bytes().to_vec()),
139 ];
140 if let Some(color) = self.color {
141 values.push((0x1400340f, color.to_le_bytes().to_vec()));
142 }
143 if let Some(transparency) = self.transparency {
144 values.push((0x0c003414, vec![transparency]));
145 }
146 if let Some(tip) = self.pen_tip {
147 values.push((0x0c003412, vec![tip]));
148 }
149 values
150 }
151}
152
153fn stroke_values(stroke: &super::InkStroke, index: u32, filetime: u64) -> Result<Values, Error> {
154 if stroke.points.is_empty() {
155 return Err(invalid("A stroke needs at least one point"));
156 }
157 if stroke
158 .points
159 .iter()
160 .any(|p| !p[0].is_finite() || !p[1].is_finite())
161 || !(stroke.width.is_finite() && stroke.height.is_finite())
162 || stroke.width <= 0.0
163 || stroke.height <= 0.0
164 {
165 return Err(invalid(
166 "Stroke points and pen size must be finite and positive",
167 ));
168 }
169 let left = stroke
170 .points
171 .iter()
172 .map(|p| p[0])
173 .fold(f32::INFINITY, f32::min);
174 let top = stroke
175 .points
176 .iter()
177 .map(|p| p[1])
178 .fold(f32::INFINITY, f32::min);
179 let mut origin = ((left - stroke.width / 2.0) / 36.0).to_le_bytes().to_vec();
180 origin.extend_from_slice(&((top - stroke.height / 2.0) / 36.0).to_le_bytes());
181 Ok(vec![
182 (0x1c00340b, stroke.packet()),
183 (0x14003419, index.to_le_bytes().to_vec()),
184 (0x1000341b, 0x409_u16.to_le_bytes().to_vec()),
185 (0x0c00341c, vec![0]),
186 (0x1c00341a, crate::write::fresh_guid()?.to_vec()),
187 (0x1c00341d, filetime.to_le_bytes().to_vec()),
188 (0x1c00345b, origin),
189 ])
190}
191
192/// The character-style properties a span format sets; paragraph-level fields stay on the
193/// paragraph style.
194fn style_values(format: &crate::document::Format) -> Values {
195 let mut values = Values::new();
196 for (id, flag) in [
197 (0x08001c04, format.bold),
198 (0x08001c05, format.italic),
199 (0x08001c06, format.underline),
200 (0x08001c07, format.strike),
201 (0x08001c08, format.superscript),
202 (0x08001c09, format.subscript),
203 (0x08001e16, format.hidden),
204 (0x08001e14, format.hyperlink),
205 (0x08001e19, format.hyperlink_label),
206 (0x08003401, format.math),
207 (0x08001e22, format.embedded_object),
208 ] {
209 if let Some(flag) = flag {
210 values.push((id | (u32::from(flag) << 31), Vec::new()));
211 }
212 }
213 if let Some(font) = &format.font {
214 values.push((0x1c001c0a, crate::create::string(font)));
215 }
216 if let Some(size) = format.font_size {
217 values.push((
218 0x10001c0b,
219 ((size * 2.0).round() as u16).to_le_bytes().to_vec(),
220 ));
221 }
222 if let Some(color) = format.color {
223 values.push((0x14001c0c, color.to_le_bytes().to_vec()));
224 }
225 if let Some(highlight) = format.highlight {
226 values.push((0x14001c0d, highlight.to_le_bytes().to_vec()));
227 }
228 if let Some(language) = format.language {
229 values.push((0x14001c3b, language.to_le_bytes().to_vec()));
230 }
231 values
232}
233
234fn picture_fixed_fields(stored: &Image, image: &Image) -> Result<(), Error> {
235 if stored.id != image.id
236 || stored.bytes != image.bytes
237 || stored.size != image.size
238 || stored.background != image.background
239 {
240 return Err(invalid(
241 "A stored picture keeps its payload, intrinsic size and background state",
242 ));
243 }
244 Ok(())
245}
246
247/// Position and displayed-size properties that take `layout` from `stored`, and the ones
248/// to remove.
249fn layout_values(
250 stored: &crate::document::Layout,
251 layout: &crate::document::Layout,
252) -> Result<(Values, Vec<u32>), Error> {
253 let mut values = Values::new();
254 let mut removed = Vec::new();
255 if (layout.x, layout.y) != (stored.x, stored.y) {
256 let (Some(x), Some(y)) = (layout.x, layout.y) else {
257 return Err(invalid("A picture position needs both coordinates"));
258 };
259 if !(x.is_finite() && y.is_finite()) {
260 return Err(invalid("A picture position must be finite"));
261 }
262 values.push((0x14001c14, (x / 36.0).to_le_bytes().to_vec()));
263 values.push((0x14001c15, (y / 36.0).to_le_bytes().to_vec()));
264 }
265 if (
266 layout.max_width,
267 layout.max_height,
268 layout.width_set_by_user,
269 ) != (
270 stored.max_width,
271 stored.max_height,
272 stored.width_set_by_user,
273 ) {
274 match (layout.max_width, layout.max_height) {
275 (Some(width), Some(height)) => {
276 if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) {
277 return Err(invalid("Picture size must be positive"));
278 }
279 values.push((0x14001c1b, (width / 36.0).to_le_bytes().to_vec()));
280 values.push((0x14001c1c, (height / 36.0).to_le_bytes().to_vec()));
281 let user_set = u32::from(layout.width_set_by_user == Some(true));
282 values.push((0x08001cbd | (user_set << 31), Vec::new()));
283 }
284 (None, None) => removed.extend([0x14001c1b, 0x14001c1c, 0x08001cbd]),
285 _ => return Err(invalid("A picture size needs both dimensions")),
286 }
287 }
288 if layout.reserved_width != stored.reserved_width {
289 return Err(invalid("A picture has no reserved width"));
290 }
291 Ok((values, removed))
292}
293
294fn measurement_bytes(values: &[f32], header: usize) -> Result<Vec<u8>, Error> {
295 let count = u8::try_from(values.len())
296 .map_err(|_| invalid("A measurement array exceeds the document range"))?;
297 let mut bytes = vec![0u8; header];
298 bytes[0] = count;
299 for value in values {
300 if !value.is_finite() {
301 return Err(invalid("A measurement must be finite"));
302 }
303 bytes.extend_from_slice(&(value / 36.0).to_le_bytes());
304 }
305 Ok(bytes)
306}
307
308/// Every row carries one cell per column and widths are usable.
309fn validate_table(table: &Table) -> Result<(), Error> {
310 if table.rows.is_empty() || table.columns.is_empty() {
311 return Err(invalid("A table needs at least one row and one column"));
312 }
313 if table
314 .columns
315 .iter()
316 .any(|c| !c.width.is_finite() || c.width < 36.0)
317 {
318 return Err(invalid("Table columns are at least 36 points wide"));
319 }
320 for row in &table.rows {
321 if row.cells.len() != table.columns.len() {
322 return Err(invalid("Every table row has one cell per column"));
323 }
324 for cell in &row.cells {
325 // An emptied cell keeps a replacement paragraph, as the tree writer provides.
326 for paragraph in &cell.paragraphs {
327 if let ParagraphContent::Table(nested) = &paragraph.content {
328 validate_table(nested)?;
329 }
330 }
331 }
332 }
333 Ok(())
334}
335
336pub(crate) fn write_page(41pub(crate) fn write_page(
337 source: &[u8],42 source: &[u8],
338 space: ExGuid,43 space: ExGuid,
...@@ -374,93 +79,6 @@ pub(crate) fn write_page(...@@ -374,93 +79,6 @@ pub(crate) fn write_page(
374 )79 )
375}80}
37681
377/// Direct children of every container, in model order, plus lookups by identity.
378struct View<'a> {
379 page: &'a Page,
380 outlines: BTreeMap<ExGuid, &'a Outline>,
381 /// Outlines owned by a title object rather than the page.
382 title_outlines: BTreeSet<ExGuid>,
383 paragraphs: BTreeMap<ExGuid, &'a PageParagraph>,
384 children: BTreeMap<ExGuid, Vec<ExGuid>>,
385 container: BTreeMap<ExGuid, ExGuid>,
386 page_children: Vec<ExGuid>,
387}
388
389impl<'a> View<'a> {
390 fn new(page: &'a Page) -> Result<Self, Error> {
391 let mut view = Self {
392 page,
393 outlines: BTreeMap::new(),
394 title_outlines: BTreeSet::new(),
395 paragraphs: BTreeMap::new(),
396 children: BTreeMap::new(),
397 container: BTreeMap::new(),
398 page_children: Vec::new(),
399 };
400 for object in &page.objects {
401 view.page_children.push(object.id());
402 match object {
403 PageObject::Outline(outline) => view.outline(outline)?,
404 PageObject::Title(title) => {
405 for outline in &title.outlines {
406 view.title_outlines.insert(outline.id);
407 view.outline(outline)?;
408 }
409 }
410 PageObject::Image(_) | PageObject::Ink(_) | PageObject::Unsupported(_) => {}
411 }
412 }
413 Ok(view)
414 }
415
416 fn outline(&mut self, outline: &'a Outline) -> Result<(), Error> {
417 if self.outlines.insert(outline.id, outline).is_some() {
418 return Err(invalid("The page model repeats an outline identity"));
419 }
420 self.children.entry(outline.id).or_default();
421 self.paragraphs_of(outline.id, &outline.paragraphs)
422 }
423
424 fn paragraphs_of(&mut self, root: ExGuid, list: &'a [PageParagraph]) -> Result<(), Error> {
425 for paragraph in list {
426 let container = paragraph.parent.unwrap_or(root);
427 if paragraph
428 .parent
429 .is_some_and(|parent| !self.paragraphs.contains_key(&parent))
430 {
431 return Err(invalid(
432 "A paragraph's parent must precede it in its container",
433 ));
434 }
435 if self.paragraphs.insert(paragraph.id, paragraph).is_some() {
436 return Err(invalid("The page model repeats a paragraph identity"));
437 }
438 self.children
439 .entry(container)
440 .or_default()
441 .push(paragraph.id);
442 self.children.entry(paragraph.id).or_default();
443 self.container.insert(paragraph.id, container);
444 if let ParagraphContent::Table(table) = &paragraph.content {
445 for row in &table.rows {
446 for cell in &row.cells {
447 if self.children.contains_key(&cell.id) {
448 return Err(invalid("The page model repeats a cell identity"));
449 }
450 self.children.entry(cell.id).or_default();
451 self.paragraphs_of(cell.id, &cell.paragraphs)?;
452 }
453 }
454 }
455 }
456 Ok(())
457 }
458
459 fn text(&self, paragraph: ExGuid) -> Option<&'a super::TextObject> {
460 self.paragraphs.get(&paragraph).and_then(|p| p.text())
461 }
462}
463
464struct Lowering<'a> {82struct Lowering<'a> {
465 active: ActivePage<'a>,83 active: ActivePage<'a>,
466 /// Holds the objects the typed writers store for as long as `active` reads them.84 /// Holds the objects the typed writers store for as long as `active` reads them.
...@@ -521,30 +139,9 @@ impl<'a> Lowering<'a> {...@@ -521,30 +139,9 @@ impl<'a> Lowering<'a> {
521 }139 }
522140
523 fn run(&mut self, before: &Page, after: &Page) -> Result<(), Error> {141 fn run(&mut self, before: &Page, after: &Page) -> Result<(), Error> {
524 if after.created != before.created || after.margin_origin != before.margin_origin {142 let old = View::new(before, false)?;
525 return Err(invalid("Page creation time and margins cannot be edited"));143 let new = View::new(after, false)?;
526 }144 validate(before, after, &old, &new)?;
527 for (id, definition) in &after.definitions {
528 if before
529 .definitions
530 .get(id)
531 .is_some_and(|stored| stored != definition)
532 && !matches!(
533 definition.kind,
534 Kind::List { .. } | Kind::TagDefinition { .. }
535 )
536 {
537 return Err(invalid("Style definitions cannot be edited"));
538 }
539 }
540 let old = View::new(before)?;
541 let new = View::new(after)?;
542 for paragraph in new.paragraphs.values() {
543 if let ParagraphContent::Table(table) = &paragraph.content {
544 validate_table(table)?;
545 }
546 }
547 self.check_fixed_objects(before, after, &old, &new)?;
548 for id in new.outlines.keys().chain(new.paragraphs.keys()) {145 for id in new.outlines.keys().chain(new.paragraphs.keys()) {
549 if !old.outlines.contains_key(id)146 if !old.outlines.contains_key(id)
550 && !old.paragraphs.contains_key(id)147 && !old.paragraphs.contains_key(id)
...@@ -567,9 +164,12 @@ impl<'a> Lowering<'a> {...@@ -567,9 +164,12 @@ impl<'a> Lowering<'a> {
567 .collect();164 .collect();
568 self.insert_outlines(&old, &new, &mut placed, &mut page_order)?;165 self.insert_outlines(&old, &new, &mut placed, &mut page_order)?;
569 let mut consumed = BTreeSet::new();166 let mut consumed = BTreeSet::new();
167 self.ungroup(&old, &new)?;
570 self.split_and_join(&old, &new, &mut placed, &mut consumed)?;168 self.split_and_join(&old, &new, &mut placed, &mut consumed)?;
571 self.place(&old, &new, &placed, &page_order)?;169 self.place(&old, &new, &placed, &page_order)?;
572 self.delete(&old, &new, &consumed)?;170 self.delete(&old, &new, &consumed)?;
171 self.edit_levels(&new)?;
172 self.edit_text_identities(&new)?;
573 self.edit_equations(&new)?;173 self.edit_equations(&new)?;
574 self.edit_text(&new)?;174 self.edit_text(&new)?;
575 self.edit_paragraph_styles(&old, &new, after)?;175 self.edit_paragraph_styles(&old, &new, after)?;
...@@ -581,123 +181,6 @@ impl<'a> Lowering<'a> {...@@ -581,123 +181,6 @@ impl<'a> Lowering<'a> {
581 Ok(())181 Ok(())
582 }182 }
583183
584 fn check_fixed_objects(
585 &self,
586 before: &Page,
587 after: &Page,
588 old: &View<'_>,
589 new: &View<'_>,
590 ) -> Result<(), Error> {
591 let fixed = |page: &Page| -> Vec<String> {
592 page.objects
593 .iter()
594 .filter_map(|object| match object {
595 PageObject::Unsupported(unsupported) => Some(format!("{unsupported:?}")),
596 PageObject::Title(title) => Some(format!(
597 "{:?} {:?} {:?} {:?}",
598 title.id,
599 title.date,
600 title.layout,
601 title.outlines.iter().map(|o| o.id).collect::<Vec<_>>()
602 )),
603 PageObject::Outline(_) | PageObject::Image(_) | PageObject::Ink(_) => None,
604 })
605 .collect()
606 };
607 let mut before_fixed = fixed(before);
608 let mut after_fixed = fixed(after);
609 before_fixed.sort();
610 after_fixed.sort();
611 if before_fixed != after_fixed {
612 return Err(invalid(
613 "Titles and unsupported objects cannot be edited through the page model",
614 ));
615 }
616 for (id, outline) in &new.outlines {
617 if outline.paragraphs.is_empty() {
618 return Err(invalid(
619 "An outline needs a paragraph; remove the outline instead",
620 ));
621 }
622 let Some(previous) = old.outlines.get(id) else {
623 continue;
624 };
625 let same = outline.title == previous.title
626 && outline.min_width == previous.min_width
627 && outline.indents == previous.indents
628 && outline.unsupported == previous.unsupported
629 && (!new.title_outlines.contains(id) || outline.layout == previous.layout);
630 if !same {
631 return Err(invalid(
632 "Outline roles, indentation tables and title geometry cannot be edited",
633 ));
634 }
635 }
636 for (id, paragraph) in &new.paragraphs {
637 let Some(previous) = old.paragraphs.get(id) else {
638 continue;
639 };
640 if paragraph.media != previous.media {
641 return Err(invalid("Recording annotations cannot be edited"));
642 }
643 let same = paragraph.style == previous.style && paragraph.format == previous.format;
644 if !same {
645 return Err(invalid(
646 "Paragraph styles and paragraph formatting cannot be edited",
647 ));
648 }
649 match (&paragraph.content, &previous.content) {
650 (ParagraphContent::Text(text), ParagraphContent::Text(previous)) => {
651 if text.date_field != previous.date_field {
652 return Err(invalid("Text fields cannot be edited"));
653 }
654 }
655 (ParagraphContent::Table(table), ParagraphContent::Table(previous)) => {
656 if table.id != previous.id
657 || table.layout != previous.layout
658 || table.tags != previous.tags
659 {
660 return Err(invalid("Table identity, layout and tags cannot be edited"));
661 }
662 let unchanged_cells =
663 table.rows.iter().flat_map(|row| &row.cells).all(|cell| {
664 previous
665 .rows
666 .iter()
667 .flat_map(|row| &row.cells)
668 .find(|before| before.id == cell.id)
669 .is_none_or(|before| {
670 cell.layout == before.layout
671 && cell.unsupported == before.unsupported
672 })
673 });
674 if !unchanged_cells {
675 return Err(invalid("Cell layout cannot be edited"));
676 }
677 }
678 (ParagraphContent::Unsupported(a), ParagraphContent::Unsupported(b)) if a == b => {}
679 (ParagraphContent::Ink(a), ParagraphContent::Ink(b)) => {
680 if a.id != b.id {
681 return Err(invalid("Ink identity cannot change"));
682 }
683 }
684 (ParagraphContent::Image(a), ParagraphContent::Image(b)) => {
685 picture_fixed_fields(a, b)?;
686 if (a.layout.x, a.layout.y) != (b.layout.x, b.layout.y) {
687 return Err(invalid("A paragraph picture has no position of its own"));
688 }
689 }
690 (ParagraphContent::Attachment(a), ParagraphContent::Attachment(b)) => {
691 if a.id != b.id {
692 return Err(invalid("Attachment identity cannot change"));
693 }
694 }
695 _ => return Err(invalid("Paragraph content type cannot change")),
696 }
697 }
698 Ok(())
699 }
700
701 fn insert_outlines(184 fn insert_outlines(
702 &mut self,185 &mut self,
703 old: &View<'_>,186 old: &View<'_>,
...@@ -758,6 +241,10 @@ impl<'a> Lowering<'a> {...@@ -758,6 +241,10 @@ impl<'a> Lowering<'a> {
758 Ok(())241 Ok(())
759 }242 }
760243
244 /// Splits and joins stored paragraphs as OneNote's Enter, Backspace and Delete do. A new
245 /// styled paragraph that is no exact split starts as Enter at the end of the nearest stored
246 /// paragraph that allows one, taking its properties as OneNote's new paragraphs do; later
247 /// passes place it and give it its text.
761 fn split_and_join(248 fn split_and_join(
762 &mut self,249 &mut self,
763 old: &View<'_>,250 old: &View<'_>,
...@@ -765,65 +252,98 @@ impl<'a> Lowering<'a> {...@@ -765,65 +252,98 @@ impl<'a> Lowering<'a> {
765 placed: &mut BTreeMap<ExGuid, Vec<ExGuid>>,252 placed: &mut BTreeMap<ExGuid, Vec<ExGuid>>,
766 consumed: &mut BTreeSet<ExGuid>,253 consumed: &mut BTreeSet<ExGuid>,
767 ) -> Result<(), Error> {254 ) -> Result<(), Error> {
768 for (container, children) in &new.children {255 // A new paragraph may be split from the stored paragraph nearest before it, whatever
769 if !old.children.contains_key(container) {256 // placement or pasted paragraphs the same save adds between them.
257 let mut splits = Vec::new();
258 for document in new.documents() {
259 let mut left = None;
260 for paragraph in document {
261 if old.paragraphs.contains_key(&paragraph.id) {
262 left = paragraph.text().is_some().then_some(paragraph.id);
263 } else if let Some(left) = left {
264 let container = new.container[&paragraph.id];
265 let at = new.children[&container]
266 .iter()
267 .position(|id| *id == paragraph.id);
268 splits.push(((container, at), left, paragraph.id));
269 }
270 }
271 }
272 splits.sort();
273 let mut split = BTreeSet::new();
274 for (_, left, right) in splits {
275 let (Some(previous), Some(after), Some(next)) =
276 (old.text(left), new.text(left), new.text(right))
277 else {
278 continue;
279 };
280 if split.contains(&left)
281 || after.id != previous.id
282 || new.paragraphs[&right].style != old.paragraphs[&left].style
283 {
770 continue;284 continue;
771 }285 }
772 for pair in children.windows(2) {286 let Ok(offset) = after.text.utf16_offset(after.text.text().len()) else {
773 let [left, right] = [pair[0], pair[1]];287 continue;
774 let (Some(previous), Some(after), Some(next)) =288 };
775 (old.text(left), new.text(left), new.text(right))289 // Splitting at the end only differs from appending a paragraph by the copied
776 else {290 // paragraph style; without one, an appended empty paragraph is an insertion.
777 continue;291 if offset == previous.text.utf16_offset(previous.text.text().len())?
778 };292 && new.paragraphs[&right].style.is_none()
779 if old.paragraphs.contains_key(&right)293 {
780 || old.container.get(&left) != Some(container)294 continue;
781 || !new.children[&left].is_empty()295 }
782 || new.children[&right] != old.children[&left]296 let Ok(head) = previous.text.slice(0..offset) else {
783 || after.id != previous.id297 continue;
784 || new.paragraphs[&right].style != old.paragraphs[&left].style298 };
299 let Ok(tail) = previous.text.slice(
300 offset
301 ..previous
302 .text
303 .utf16_offset(previous.text.text().len())
304 .unwrap_or(0),
305 ) else {
306 continue;
307 };
308 // An emptied side takes the writer's insertion style, so only its text must agree.
309 let same = |expected: &super::Paragraph, actual: &super::Paragraph| {
310 expected.text() == actual.text()
311 && (expected.text().is_empty() || expected == actual)
312 };
313 if !same(&head, &after.text) || !same(&tail, &next.text) {
314 continue;
315 }
316 let edit = ParagraphSplit::new(previous.id, offset, self.author)?;
317 if self.split(&edit, left, right, old, new, placed)? {
318 split.insert(left);
319 }
320 }
321 for document in new.documents() {
322 for (at, paragraph) in document.iter().enumerate() {
323 if old.paragraphs.contains_key(&paragraph.id)
324 || self.alias.contains_key(&paragraph.id)
325 || paragraph.style.is_none()
326 || paragraph.text().is_none()
785 {327 {
786 continue;328 continue;
787 }329 }
788 let Ok(offset) = after.text.utf16_offset(after.text.text().len()) else {330 let stored = |candidate: &&&PageParagraph| {
789 continue;331 old.paragraphs.contains_key(&candidate.id) && candidate.text().is_some()
790 };332 };
791 // Splitting at the end only differs from appending a paragraph by the copied333 let before = document[..at].iter().rev().filter(stored);
792 // paragraph style; without one, an appended empty paragraph is an insertion.334 for template in before.chain(document[at + 1..].iter().filter(stored)) {
793 if offset == previous.text.utf16_offset(previous.text.text().len())?335 let text = self.active.view.nodes[&template.id].content[0];
794 && new.paragraphs[&right].style.is_none()336 let Kind::RichText { text: content, .. } = &self.active.view.nodes[&text].kind
795 {337 else {
796 continue;338 continue;
797 }339 };
798 let Ok(head) = previous.text.slice(0..offset) else {340 let end = u32::try_from(content.encode_utf16().count())
799 continue;341 .map_err(|_| invalid("Paragraph exceeds the UTF-16 offset range"))?;
800 };342 let edit = ParagraphSplit::new(text, end, self.author)?;
801 let Ok(tail) = previous.text.slice(343 if self.split(&edit, template.id, paragraph.id, old, new, placed)? {
802 offset344 break;
803 ..previous345 }
804 .text
805 .utf16_offset(previous.text.text().len())
806 .unwrap_or(0),
807 ) else {
808 continue;
809 };
810 // An emptied side takes the writer's insertion style, so only its text must agree.
811 let same = |expected: &super::Paragraph, actual: &super::Paragraph| {
812 expected.text() == actual.text()
813 && (expected.text().is_empty() || expected == actual)
814 };
815 if !same(&head, &after.text) || !same(&tail, &next.text) {
816 continue;
817 }346 }
818 let split = ParagraphSplit::new(previous.id, offset, self.author)?;
819 self.write(|active| split.changes(active))?;
820 self.alias.insert(right, split.object());
821 self.alias.insert(next.id, split.text_object());
822 let list = placed.get_mut(container).unwrap();
823 let at = list.iter().position(|id| *id == left).unwrap();
824 list.insert(at + 1, right);
825 placed.insert(right, placed[&left].clone());
826 placed.insert(left, Vec::new());
827 }347 }
828 }348 }
829 for (container, children) in &old.children {349 for (container, children) in &old.children {
...@@ -856,12 +376,13 @@ impl<'a> Lowering<'a> {...@@ -856,12 +376,13 @@ impl<'a> Lowering<'a> {
856 previous.id376 previous.id
857 }377 }
858 {378 {
859 return Err(invalid(379 continue;
860 "A joined paragraph keeps the left text identity unless the left text was empty",
861 ));
862 }380 }
863 let join = ParagraphJoin::new(previous.id, removed.id, self.author)?;381 let join = ParagraphJoin::new(previous.id, removed.id, self.author)?;
864 self.write(|active| join.changes(active))?;382 let Ok(changes) = join.changes(&self.active) else {
383 continue;
384 };
385 self.write(|_| Ok(changes))?;
865 consumed.insert(right);386 consumed.insert(right);
866 placed.get_mut(container).unwrap().retain(|id| *id != right);387 placed.get_mut(container).unwrap().retain(|id| *id != right);
867 placed.remove(&right);388 placed.remove(&right);
...@@ -870,6 +391,50 @@ impl<'a> Lowering<'a> {...@@ -870,6 +391,50 @@ impl<'a> Lowering<'a> {
870 Ok(())391 Ok(())
871 }392 }
872393
394 /// Stores `edit` of model paragraph `left` as model paragraph `right`, unless the split
395 /// cannot be made there.
396 fn split(
397 &mut self,
398 edit: &ParagraphSplit,
399 left: ExGuid,
400 right: ExGuid,
401 old: &View<'_>,
402 new: &View<'_>,
403 placed: &mut BTreeMap<ExGuid, Vec<ExGuid>>,
404 ) -> Result<bool, Error> {
405 let Ok(changes) = edit.changes(&self.active) else {
406 return Ok(false);
407 };
408 self.write(|_| Ok(changes))?;
409 self.alias.insert(right, edit.object());
410 // A text object a join moved into the paragraph is taken over after placement.
411 if let Some(text) = new.text(right)
412 && !self.active.live.revision.objects.contains_key(&text.id)
413 {
414 self.alias.insert(text.id, edit.text_object());
415 }
416 // The split copies the left paragraph's list nodes in order; a list the model shares
417 // with a stored paragraph stays that paragraph's.
418 if new.paragraphs[&right].lists.len() == old.paragraphs[&left].lists.len() {
419 for (n, list) in (5..).zip(&new.paragraphs[&right].lists) {
420 if !self.active.live.revision.objects.contains_key(list) {
421 let guid = edit.object().guid;
422 self.alias.insert(*list, ExGuid { guid, n });
423 }
424 }
425 }
426 // An earlier split may have carried the left paragraph to its tail.
427 let list = placed
428 .values_mut()
429 .find(|list| list.contains(&left))
430 .unwrap();
431 let at = list.iter().position(|id| *id == left).unwrap();
432 list.insert(at + 1, right);
433 placed.insert(right, placed[&left].clone());
434 placed.insert(left, Vec::new());
435 Ok(true)
436 }
437
873 fn place(438 fn place(
874 &mut self,439 &mut self,
875 old: &View<'_>,440 old: &View<'_>,
...@@ -957,9 +522,10 @@ impl<'a> Lowering<'a> {...@@ -957,9 +522,10 @@ impl<'a> Lowering<'a> {
957 if pending.is_empty() {522 if pending.is_empty() {
958 break;523 break;
959 }524 }
960 let (ready, waiting): (Vec<ExGuid>, Vec<ExGuid>) = pending525 let (ready, waiting): (Vec<ExGuid>, Vec<ExGuid>) =
961 .into_iter()526 pending.into_iter().partition(|container| {
962 .partition(|container| self.alias.contains_key(container));527 self.alias.contains_key(container) || old.children.contains_key(container)
528 });
963 if ready.is_empty() {529 if ready.is_empty() {
964 return Err(invalid("Table content has no table to hold it"));530 return Err(invalid("Table content has no table to hold it"));
965 }531 }
...@@ -981,18 +547,12 @@ impl<'a> Lowering<'a> {...@@ -981,18 +547,12 @@ impl<'a> Lowering<'a> {
981 let ParagraphContent::Attachment(attachment) = &paragraph.content else {547 let ParagraphContent::Attachment(attachment) = &paragraph.content else {
982 continue;548 continue;
983 };549 };
984 let name = attachment.filename.as_str();
985 if name.is_empty() || name.contains(['\0', '/', '\\']) {
986 return Err(invalid(
987 "An attachment needs a file name without path separators",
988 ));
989 }
990 if let Some(previous) = old.paragraphs.get(paragraph_id) {550 if let Some(previous) = old.paragraphs.get(paragraph_id) {
991 let ParagraphContent::Attachment(stored) = &previous.content else {551 let ParagraphContent::Attachment(stored) = &previous.content else {
992 return Err(invalid("Paragraph content type cannot change"));552 return Err(invalid("Paragraph content type cannot change"));
993 };553 };
994 if stored != attachment {554 if stored != attachment {
995 self.edit_attachment(stored, attachment)?;555 self.write(|active| content::attachment_edit_changes(active, stored, attachment))?;
996 }556 }
997 continue;557 continue;
998 }558 }
...@@ -1002,89 +562,32 @@ impl<'a> Lowering<'a> {...@@ -1002,89 +562,32 @@ impl<'a> Lowering<'a> {
1002 let Some(bytes) = &attachment.bytes else {562 let Some(bytes) = &attachment.bytes else {
1003 return Err(invalid("A new attachment needs its payload"));563 return Err(invalid("A new attachment needs its payload"));
1004 };564 };
1005 let extension = name565 let ids = AttachmentIds {
1006 .rfind('.')566 object: self.allocate(attachment.id)?,
1007 .filter(|dot| *dot > 0)567 file: ExGuid {
1008 .map(|dot| &name[dot..])568 guid: crate::write::fresh_guid()?,
1009 .unwrap_or("");569 n: 1,
1010 let attachment_id = self.allocate(attachment.id)?;570 },
1011 let file_id = ExGuid {571 payload: crate::write::fresh_guid()?,
1012 guid: crate::write::fresh_guid()?,572 preview: match &attachment.preview {
1013 n: 1,573 Some(_) => {
574 let payload = crate::write::fresh_guid()?;
575 let icon = ExGuid {
576 guid: crate::write::fresh_guid()?,
577 n: 1,
578 };
579 Some((payload, icon))
580 }
581 None => None,
582 },
1014 };583 };
1015 let payload_guid = crate::write::fresh_guid()?;584 let mut payloads: Vec<([u8; 16], &[u8])> = vec![(ids.payload, bytes)];
1016 let reference = payload_reference(payload_guid);585 if let (Some((payload, _)), Some(icon)) = (ids.preview, &attachment.preview) {
1017 let modified = crate::create::current_timestamps()?.0.to_le_bytes();586 payloads.push((payload, icon));
1018 let mut values: Values = vec![(0x14001d7a, modified.to_vec())];
1019 if let Some([width, height]) = attachment.size {
1020 if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) {
1021 return Err(invalid("Attachment icon size must be positive"));
1022 }
1023 values.push((0x140034cd, (width / 36.0).to_le_bytes().to_vec()));
1024 values.push((0x140034ce, (height / 36.0).to_le_bytes().to_vec()));
1025 }
1026 values.push((0x14001c3b, 0x409_u32.to_le_bytes().to_vec()));
1027 values.push((0x10001cfe, 0x409_u16.to_le_bytes().to_vec()));
1028 values.push((0x1c001dcf, vec![0; 32]));
1029 values.push((
1030 0x1c001d61,
1031 [16u32, 1, 0, 0, 0]
1032 .iter()
1033 .flat_map(|v| v.to_le_bytes())
1034 .collect(),
1035 ));
1036 values.push((0x14001c3e, 1u32.to_le_bytes().to_vec()));
1037 values.push((0x14001c84, 1u32.to_le_bytes().to_vec()));
1038 values.push((0x1c001c22, crate::create::string(name)));
1039 values.push((0x1c001d9c, crate::create::string(name)));
1040 if let Some(path) = &attachment.source_path {
1041 values.push((0x1c001d9d, crate::create::string(path)));
1042 }587 }
1043 let holder = self.id(*paragraph_id);588 let holder = self.id(*paragraph_id);
1044 let mut payloads: Vec<([u8; 16], &[u8])> = vec![(payload_guid, bytes)];
1045 let mut preview = None;
1046 if let Some(icon) = &attachment.preview {
1047 if !icon.starts_with(&[0x89, b'P', b'N', b'G']) {
1048 return Err(invalid("An attachment preview is a PNG icon"));
1049 }
1050 let guid = crate::write::fresh_guid()?;
1051 payloads.push((guid, icon));
1052 preview = Some((
1053 ExGuid {
1054 guid: crate::write::fresh_guid()?,
1055 n: 1,
1056 },
1057 payload_reference(guid),
1058 ));
1059 }
1060 self.write_with(&payloads, |active| {589 self.write_with(&payloads, |active| {
1061 let raw = &active.live.revision;590 content::attachment_changes(active, attachment, &ids, holder)
1062 let mut changed = BTreeMap::new();
1063 let mut file = PropertyObject::file(file_id, &reference, extension)?;
1064 file.jcid = 0x80036;
1065 changed.insert(file_id, file);
1066 let mut node = PropertyObject {
1067 jcid: 0x60035,
1068 bytes: crate::create::properties(&values)?,
1069 global_ids: std::sync::Arc::new(BTreeMap::from([(0, attachment_id.guid)])),
1070 };
1071 node.reference(attachment_id)?;
1072 let container = node.reference(file_id)?;
1073 node.set(&[(0x20001d9b, &container)])?;
1074 if let Some((icon_id, icon_reference)) = &preview {
1075 changed.insert(
1076 *icon_id,
1077 PropertyObject::file(*icon_id, icon_reference, ".png")?,
1078 );
1079 let icon = node.reference(*icon_id)?;
1080 node.set(&[(0x20001c3f, &icon)])?;
1081 }
1082 changed.insert(attachment_id, node);
1083 let mut object = PropertyObject::from_object(&raw.objects[&holder])?;
1084 let content = object.reference(attachment_id)?;
1085 object.set(&[(0x24001c1f, &content), (0x14001d7a, &modified)])?;
1086 changed.insert(holder, object);
1087 Ok(changed)
1088 })?;591 })?;
1089 }592 }
1090 Ok(())593 Ok(())
...@@ -1183,15 +686,6 @@ impl<'a> Lowering<'a> {...@@ -1183,15 +686,6 @@ impl<'a> Lowering<'a> {
1183 /// child (or a paragraph holds as content), its data node listing stroke objects, each686 /// child (or a paragraph holds as content), its data node listing stroke objects, each
1184 /// stroke's packet and half-inch origin, and one drawing-attribute object per distinct pen.687 /// stroke's packet and half-inch origin, and one drawing-attribute object per distinct pen.
1185 fn insert_ink(&mut self, ink: &Ink, holder: Option<ExGuid>) -> Result<(), Error> {688 fn insert_ink(&mut self, ink: &Ink, holder: Option<ExGuid>) -> Result<(), Error> {
1186 if !ink.groups.is_empty() {
1187 return Err(invalid("New ink holds strokes, not nested groups"));
1188 }
1189 if ink.strokes.is_empty() {
1190 return Err(invalid("New ink needs at least one stroke"));
1191 }
1192 if ink.layout != Default::default() {
1193 return Err(invalid("Ink positions come from its strokes"));
1194 }
1195 let container = self.allocate(ink.id)?;689 let container = self.allocate(ink.id)?;
1196 let data = ExGuid {690 let data = ExGuid {
1197 guid: crate::write::fresh_guid()?,691 guid: crate::write::fresh_guid()?,
...@@ -1201,50 +695,7 @@ impl<'a> Lowering<'a> {...@@ -1201,50 +695,7 @@ impl<'a> Lowering<'a> {
1201 for stroke in &ink.strokes {695 for stroke in &ink.strokes {
1202 strokes.push((self.allocate(stroke.id)?, stroke));696 strokes.push((self.allocate(stroke.id)?, stroke));
1203 }697 }
1204 let (modified, filetime) = crate::create::current_timestamps()?;698 self.write(|active| content::ink_changes(active, ink, container, data, &strokes, holder))
1205 let modified = modified.to_le_bytes();
1206 let page = self.page;
1207 self.write(|active| {
1208 let raw = &active.live.revision;
1209 let mut changed = BTreeMap::new();
1210 let mut data_object = PropertyObject {
1211 jcid: 0x2003b,
1212 bytes: crate::create::properties(&[])?,
1213 global_ids: std::sync::Arc::new(BTreeMap::from([(0, data.guid)])),
1214 };
1215 data_object.reference(data)?;
1216 let references = write_strokes(&mut changed, &mut data_object, &strokes, 0, filetime)?;
1217 data_object.set(&[(0x24003416, &references)])?;
1218 changed.insert(data, data_object);
1219 let mut object = PropertyObject {
1220 jcid: 0x60014,
1221 bytes: crate::create::properties(&[
1222 (0x14001d7a, modified.to_vec()),
1223 (0x14001d4e, 1u32.to_le_bytes().to_vec()),
1224 ])?,
1225 global_ids: std::sync::Arc::new(BTreeMap::from([(0, container.guid)])),
1226 };
1227 object.reference(container)?;
1228 let data_reference = object.reference(data)?;
1229 object.set(&[(0x20003415, &data_reference)])?;
1230 changed.insert(container, object);
1231 match holder {
1232 Some(holder) => {
1233 let mut object = PropertyObject::from_object(&raw.objects[&holder])?;
1234 let content = object.reference(container)?;
1235 object.set(&[(0x24001c1f, &content), (0x14001d7a, &modified)])?;
1236 changed.insert(holder, object);
1237 }
1238 None => {
1239 let mut object = PropertyObject::from_object(&raw.objects[&page])?;
1240 let mut children = page_children(&object)?;
1241 children.extend(object.reference(container)?);
1242 object.set(&[(0x24001c20, &children), (0x14001d7a, &modified)])?;
1243 changed.insert(page, object);
1244 }
1245 }
1246 Ok(changed)
1247 })
1248 }699 }
1249700
1250 /// Rewrites a drawing's stroke list: stored strokes stay as they are (OneNote erases whole701 /// Rewrites a drawing's stroke list: stored strokes stay as they are (OneNote erases whole
...@@ -1266,204 +717,40 @@ impl<'a> Lowering<'a> {...@@ -1266,204 +717,40 @@ impl<'a> Lowering<'a> {
1266 }717 }
1267 }718 }
1268 let container = self.id(ink.id);719 let container = self.id(ink.id);
1269 let data = match self720 self.write(|active| content::strokes_changes(active, container, &kept, &added))
1270 .active
1271 .view
1272 .nodes
1273 .get(&container)
1274 .map(|node| &node.kind)
1275 {
1276 Some(crate::document::Kind::Ink {
1277 data: Some(data), ..
1278 }) => *data,
1279 _ => return Err(invalid("Stored ink has no stroke data to rewrite")),
1280 };
1281 let (modified, filetime) = crate::create::current_timestamps()?;
1282 let modified = modified.to_le_bytes();
1283 self.write(|active| {
1284 let raw = &active.live.revision;
1285 let mut changed = BTreeMap::new();
1286 let mut data_object = PropertyObject::from_object(&raw.objects[&data])?;
1287 let mut references = Vec::new();
1288 for id in &kept {
1289 references.extend(data_object.reference(*id)?);
1290 }
1291 references.extend(write_strokes(
1292 &mut changed,
1293 &mut data_object,
1294 &added,
1295 kept.len(),
1296 filetime,
1297 )?);
1298 data_object.set(&[(0x24003416, &references)])?;
1299 changed.insert(data, data_object);
1300 let mut object = PropertyObject::from_object(&raw.objects[&container])?;
1301 object.set(&[(0x14001d7a, &modified)])?;
1302 changed.insert(container, object);
1303 Ok(changed)
1304 })
1305 }
1306
1307 /// Gives a new picture what OneNote stores for an inserted one: the payload embedded
1308 /// in the section's file-data store, a file-data object declaring it by identity and
1309 /// extension, and a picture object that a paragraph holds as content or the page
1310 /// lists as a child.
1311 fn insert_image(&mut self, image: &Image, holder: Option<ExGuid>) -> Result<(), Error> {
1312 let Some(bytes) = &image.bytes else {
1313 return Err(invalid("A new picture needs its payload"));
1314 };
1315 let extension = match bytes.as_ref() {
1316 [0x89, b'P', b'N', b'G', ..] => ".png",
1317 [0xff, 0xd8, 0xff, ..] => ".jpg",
1318 [b'G', b'I', b'F', b'8', ..] => ".gif",
1319 [b'B', b'M', ..] => ".bmp",
1320 _ => return Err(invalid("Choose a PNG, JPEG, GIF or BMP picture")),
1321 };
1322 let image_id = self.allocate(image.id)?;
1323 let file_id = ExGuid {
1324 guid: crate::write::fresh_guid()?,
1325 n: 1,
1326 };
1327 let payload_guid = crate::write::fresh_guid()?;
1328 let reference = payload_reference(payload_guid);
1329 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
1330 let mut values: Values = vec![(0x14001d7a, modified.to_vec())];
1331 if let Some([width, height]) = image.size {
1332 if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) {
1333 return Err(invalid("Picture size must be positive"));
1334 }
1335 values.push((0x140034cd, (width / 36.0).to_le_bytes().to_vec()));
1336 values.push((0x140034ce, (height / 36.0).to_le_bytes().to_vec()));
1337 }
1338 values.extend(layout_values(&Default::default(), &image.layout)?.0);
1339 if let Some(alt) = &image.alt {
1340 values.push((0x1c001e58, crate::create::string(alt)));
1341 }
1342 if image.background {
1343 values.push((0x08001d13 | (1 << 31), Vec::new()));
1344 }
1345 values.push((0x08001d85, Vec::new()));
1346 let page = self.page;
1347 let payload: &[u8] = bytes;
1348 self.write_with(&[(payload_guid, payload)], |active| {
1349 let raw = &active.live.revision;
1350 let mut changed = BTreeMap::new();
1351 let file = PropertyObject::file(file_id, &reference, extension)?;
1352 changed.insert(file_id, file);
1353 let mut picture = PropertyObject {
1354 jcid: 0x60011,
1355 bytes: crate::create::properties(&values)?,
1356 global_ids: std::sync::Arc::new(BTreeMap::from([(0, image_id.guid)])),
1357 };
1358 picture.reference(image_id)?;
1359 let container = picture.reference(file_id)?;
1360 picture.set(&[(0x20001c3f, &container)])?;
1361 changed.insert(image_id, picture);
1362 match holder {
1363 Some(holder) => {
1364 let mut object = PropertyObject::from_object(&raw.objects[&holder])?;
1365 let content = object.reference(image_id)?;
1366 object.set(&[(0x24001c1f, &content), (0x14001d7a, &modified)])?;
1367 changed.insert(holder, object);
1368 }
1369 None => {
1370 let mut object = PropertyObject::from_object(&raw.objects[&page])?;
1371 let mut children = page_children(&object)?;
1372 children.extend(object.reference(image_id)?);
1373 object.set(&[(0x24001c20, &children), (0x14001d7a, &modified)])?;
1374 changed.insert(page, object);
1375 }
1376 }
1377 Ok(changed)
1378 })
1379 }721 }
1380722
1381 /// Writes a moved, resized or described picture the way OneNote stores one: the723 /// Gives a new picture what OneNote stores for an inserted one: the payload embedded
1382 /// position, the layout width and height with the user flag and the description on724 /// in the section's file-data store, a file-data object declaring it by identity and
1383 /// the picture object, leaving the intrinsic size alone.725 /// extension, and a picture object that a paragraph holds as content or the page
1384 /// A stored attachment keeps its payload and preview; its shown name, recorded source726 /// lists as a child.
1385 /// path and icon size change in place, as OneNote's rename does.727 fn insert_image(&mut self, image: &Image, holder: Option<ExGuid>) -> Result<(), Error> {
1386 fn edit_attachment(728 let Some(bytes) = &image.bytes else {
1387 &mut self,729 return Err(invalid("A new picture needs its payload"));
1388 stored: &Attachment,730 };
1389 attachment: &Attachment,731 let image_id = self.allocate(image.id)?;
1390 ) -> Result<(), Error> {732 let file_id = ExGuid {
1391 let mut values: Values = vec![(733 guid: crate::write::fresh_guid()?,
1392 0x14001d7a,734 n: 1,
1393 crate::create::current_timestamps()?735 };
1394 .0736 let payload_guid = crate::write::fresh_guid()?;
1395 .to_le_bytes()737 let payload: &[u8] = bytes;
1396 .to_vec(),738 self.write_with(&[(payload_guid, payload)], |active| {
1397 )];739 content::picture_changes(active, image, image_id, file_id, payload_guid, holder)
1398 if attachment.recording != stored.recording {
1399 return Err(invalid("A recording stays the recording OneNote captured"));
1400 }
1401 let mut removed = Vec::new();
1402 if attachment.filename != stored.filename {
1403 let name = crate::create::string(&attachment.filename);
1404 values.push((0x1c001c22, name.clone()));
1405 values.push((0x1c001d9c, name));
1406 }
1407 if attachment.source_path != stored.source_path {
1408 match &attachment.source_path {
1409 Some(path) => values.push((0x1c001d9d, crate::create::string(path))),
1410 None => removed.push(0x1c001d9d),
1411 }
1412 }
1413 if attachment.size != stored.size {
1414 match attachment.size {
1415 Some([width, height]) => {
1416 if !(width.is_finite() && height.is_finite() && width > 0.0 && height > 0.0) {
1417 return Err(invalid("Attachment icon size must be positive"));
1418 }
1419 values.push((0x140034cd, (width / 36.0).to_le_bytes().to_vec()));
1420 values.push((0x140034ce, (height / 36.0).to_le_bytes().to_vec()));
1421 }
1422 None => removed.extend([0x140034cd, 0x140034ce]),
1423 }
1424 }
1425 let object = self.id(attachment.id);
1426 self.write(|active| {
1427 let raw = &active.live.revision;
1428 let mut node = PropertyObject::from_object(&raw.objects[&object])?;
1429 node.remove(&removed)?;
1430 let values: Vec<(u32, &[u8])> = values
1431 .iter()
1432 .map(|(id, bytes)| (*id, bytes.as_slice()))
1433 .collect();
1434 node.set(&values)?;
1435 Ok(BTreeMap::from([(object, node)]))
1436 })740 })
1437 }741 }
1438742
1439 fn edit_image(&mut self, stored: &Image, image: &Image) -> Result<(), Error> {743 fn edit_image(&mut self, stored: &Image, image: &Image) -> Result<(), Error> {
1440 picture_fixed_fields(stored, image)?;744 content::picture_fixed_fields(stored, image)?;
1441 let mut values: Values = vec![(
1442 0x14001d7a,
1443 crate::create::current_timestamps()?
1444 .0
1445 .to_le_bytes()
1446 .to_vec(),
1447 )];
1448 let (layout, mut removed) = layout_values(&stored.layout, &image.layout)?;
1449 values.extend(layout);
1450 if image.alt != stored.alt {
1451 match &image.alt {
1452 Some(alt) => values.push((0x1c001e58, crate::create::string(alt))),
1453 None => removed.push(0x1c001e58),
1454 }
1455 }
1456 let object = self.id(image.id);745 let object = self.id(image.id);
1457 self.write(|active| {746 self.write(|active| {
1458 let raw = &active.live.revision;747 content::picture_edit_changes(
1459 let mut picture = PropertyObject::from_object(&raw.objects[&object])?;748 active,
1460 picture.remove(&removed)?;749 object,
1461 let values: Vec<(u32, &[u8])> = values750 (&stored.layout, &stored.alt),
1462 .iter()751 &image.layout,
1463 .map(|(id, bytes)| (*id, bytes.as_slice()))752 &image.alt,
1464 .collect();753 )
1465 picture.set(&values)?;
1466 Ok(BTreeMap::from([(object, picture)]))
1467 })754 })
1468 }755 }
1469756
...@@ -1528,7 +815,9 @@ impl<'a> Lowering<'a> {...@@ -1528,7 +815,9 @@ impl<'a> Lowering<'a> {
1528 Insertion::paragraph(self.id(*container), anchor, text, self.author)?;815 Insertion::paragraph(self.id(*container), anchor, text, self.author)?;
1529 self.write(|active| insertion.changes(active))?;816 self.write(|active| insertion.changes(active))?;
1530 self.alias.insert(*id, insertion.object());817 self.alias.insert(*id, insertion.object());
1531 if let Some(text_id) = text_id {818 if let Some(text_id) =
819 text_id.filter(|id| !self.active.live.revision.objects.contains_key(id))
820 {
1532 self.alias.insert(text_id, insertion.text_object());821 self.alias.insert(text_id, insertion.text_object());
1533 }822 }
1534 } else if !kept.contains(id) {823 } else if !kept.contains(id) {
...@@ -1542,12 +831,7 @@ impl<'a> Lowering<'a> {...@@ -1542,12 +831,7 @@ impl<'a> Lowering<'a> {
1542 Ok(())831 Ok(())
1543 }832 }
1544833
1545 /// Creates tables, rows and cells the model added, rebuilds every table's row and cell834 /// Changes the shading and indents of stored cells in place.
1546 /// order to the model's, and writes the column widths, locks and border flag. Native
1547 /// rows and cells are plain containers; a cell carries its indent array and the flags
1548 /// every native cell has.
1549 /// Shading (the documented `CellShadingColor`, which OneNote 2010 stores but neither
1550 /// renders nor accepts through its COM schema) and indents change in place on a cell.
1551 fn edit_cells(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> {835 fn edit_cells(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> {
1552 let stored: BTreeMap<ExGuid, &super::TableCell> = old836 let stored: BTreeMap<ExGuid, &super::TableCell> = old
1553 .paragraphs837 .paragraphs
...@@ -1570,46 +854,25 @@ impl<'a> Lowering<'a> {...@@ -1570,46 +854,25 @@ impl<'a> Lowering<'a> {
1570 if (cell.shading, &cell.indents) == (previous.shading, &previous.indents) {854 if (cell.shading, &cell.indents) == (previous.shading, &previous.indents) {
1571 continue;855 continue;
1572 }856 }
1573 let mut values: Values = vec![(
1574 0x14001d7a,
1575 crate::create::current_timestamps()?
1576 .0
1577 .to_le_bytes()
1578 .to_vec(),
1579 )];
1580 let mut removed = Vec::new();
1581 if cell.shading != previous.shading {
1582 match cell.shading {
1583 Some(shading) => values.push((0x14001e26, shading.to_le_bytes().to_vec())),
1584 None => removed.push(0x14001e26),
1585 }
1586 }
1587 if cell.indents != previous.indents {
1588 if cell.indents.is_empty() {
1589 removed.push(0x1c001c12);
1590 } else {
1591 values.push((0x1c001c12, measurement_bytes(&cell.indents, 4)?));
1592 }
1593 }
1594 let object = self.id(cell.id);857 let object = self.id(cell.id);
1595 self.write(|active| {858 self.write(|active| {
1596 let raw = &active.live.revision;859 table::cell_changes(
1597 let mut object_properties = PropertyObject::from_object(&raw.objects[&object])?;860 active,
1598 object_properties.remove(&removed)?;861 object,
1599 let values: Vec<(u32, &[u8])> = values862 (previous.shading, &previous.indents),
1600 .iter()863 cell.shading,
1601 .map(|(id, bytes)| (*id, bytes.as_slice()))864 &cell.indents,
1602 .collect();865 )
1603 object_properties.set(&values)?;
1604 Ok(BTreeMap::from([(object, object_properties)]))
1605 })?;866 })?;
1606 }867 }
1607 }868 }
1608 Ok(())869 Ok(())
1609 }870 }
1610871
872 /// Creates tables, rows and cells the model added, rebuilds every table's row and cell
873 /// order to the model's, and writes the column widths, locks and border flag.
1611 fn edit_table_structure(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> {874 fn edit_table_structure(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> {
1612 let old_tables: BTreeMap<ExGuid, &Table> = old875 let old_tables: BTreeMap<ExGuid, &super::Table> = old
1613 .paragraphs876 .paragraphs
1614 .values()877 .values()
1615 .filter_map(|p| match &p.content {878 .filter_map(|p| match &p.content {
...@@ -1642,8 +905,6 @@ impl<'a> Lowering<'a> {...@@ -1642,8 +905,6 @@ impl<'a> Lowering<'a> {
1642 if unchanged {905 if unchanged {
1643 continue;906 continue;
1644 }907 }
1645 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
1646 let mut created: Vec<(ExGuid, u32, Values)> = Vec::new();
1647 let table_id = if previous.is_some() {908 let table_id = if previous.is_some() {
1648 self.id(table.id)909 self.id(table.id)
1649 } else {910 } else {
...@@ -1662,13 +923,22 @@ impl<'a> Lowering<'a> {...@@ -1662,13 +923,22 @@ impl<'a> Lowering<'a> {
1662 .unwrap_or_default();923 .unwrap_or_default();
1663 let template = previous924 let template = previous
1664 .and_then(|p| p.rows.first())925 .and_then(|p| p.rows.first())
1665 .and_then(|r| r.cells.first());926 .and_then(|r| r.cells.first())
1666 let mut rows = Vec::new();927 .map(|cell| cell.indents.as_slice());
928 let mut structure = Structure {
929 rows: Vec::new(),
930 new_rows: BTreeSet::new(),
931 new_cells: BTreeMap::new(),
932 columns: &table.columns,
933 borders: table.borders,
934 };
1667 for row in &table.rows {935 for row in &table.rows {
1668 let row_id = if existing_rows.contains(&row.id) {936 let row_id = if existing_rows.contains(&row.id) {
1669 self.id(row.id)937 self.id(row.id)
1670 } else {938 } else {
1671 self.allocate(row.id)?939 let written = self.allocate(row.id)?;
940 structure.new_rows.insert(written);
941 written
1672 };942 };
1673 let mut cells = Vec::new();943 let mut cells = Vec::new();
1674 for cell in &row.cells {944 for cell in &row.cells {
...@@ -1676,116 +946,18 @@ impl<'a> Lowering<'a> {...@@ -1676,116 +946,18 @@ impl<'a> Lowering<'a> {
1676 self.id(cell.id)946 self.id(cell.id)
1677 } else {947 } else {
1678 let written = self.allocate(cell.id)?;948 let written = self.allocate(cell.id)?;
1679 let indents = if cell.indents.is_empty() {949 structure.new_cells.insert(
1680 template.map(|t| t.indents.clone()).unwrap_or_default()950 written,
1681 } else {951 (table::cell_indents(&cell.indents, template), cell.shading),
1682 cell.indents.clone()952 );
1683 };
1684 let mut values: Values = vec![
1685 (0x14001d7a, modified.to_vec()),
1686 (0x0c001c13, vec![0]),
1687 (0x0c001c03, vec![1]),
1688 (0x88001c91, Vec::new()),
1689 ];
1690 if !indents.is_empty() {
1691 values.push((0x1c001c12, measurement_bytes(&indents, 4)?));
1692 }
1693 if let Some(shading) = cell.shading {
1694 values.push((0x14001e26, shading.to_le_bytes().to_vec()));
1695 }
1696 created.push((written, 0x60024, values));
1697 written953 written
1698 };954 };
1699 cells.push(cell_id);955 cells.push(cell_id);
1700 }956 }
1701 if !existing_rows.contains(&row.id) {957 structure.rows.push((row_id, cells));
1702 created.push((row_id, 0x60023, vec![(0x14001d7a, modified.to_vec())]));
1703 }
1704 rows.push((row_id, cells));
1705 }
1706 let mut table_values: Values = vec![
1707 (0x14001d7a, modified.to_vec()),
1708 (0x14001d57, (table.rows.len() as u32).to_le_bytes().to_vec()),
1709 (
1710 0x14001d58,
1711 (table.columns.len() as u32).to_le_bytes().to_vec(),
1712 ),
1713 (
1714 0x1c001d66,
1715 measurement_bytes(
1716 &table.columns.iter().map(|c| c.width).collect::<Vec<_>>(),
1717 1,
1718 )?,
1719 ),
1720 ];
1721 let mut locks = vec![table.columns.len() as u8];
1722 locks.extend(vec![0u8; table.columns.len().div_ceil(8)]);
1723 for (i, column) in table.columns.iter().enumerate() {
1724 if column.locked {
1725 locks[1 + i / 8] |= 1 << (i % 8);
1726 }
1727 }
1728 table_values.push((0x1c001d7d, locks));
1729 table_values.push((
1730 0x08001d5e | (u32::from(table.borders.unwrap_or(true)) << 31),
1731 Vec::new(),
1732 ));
1733 if previous.is_none() {
1734 table_values.push((0x14001c3e, 1u32.to_le_bytes().to_vec()));
1735 table_values.push((0x14001c84, 1u32.to_le_bytes().to_vec()));
1736 created.push((table_id, 0x60022, Vec::new()));
1737 }958 }
1738 let holder = self.id(*paragraph_id);959 let holder = previous.is_none().then(|| self.id(*paragraph_id));
1739 let is_new = previous.is_none();960 self.write(|active| table::table_changes(active, table_id, holder, &structure))?;
1740 self.write(|active| {
1741 let raw = &active.live.revision;
1742 let mut changed: BTreeMap<ExGuid, PropertyObject> = BTreeMap::new();
1743 for (id, jcid, values) in &created {
1744 let mut node = PropertyObject {
1745 jcid: *jcid,
1746 bytes: crate::create::properties(values)?,
1747 global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])),
1748 };
1749 node.reference(*id)?;
1750 changed.insert(*id, node);
1751 }
1752 let mut row_refs = Vec::new();
1753 for (row_id, cells) in &rows {
1754 let mut row = match changed.remove(row_id) {
1755 Some(row) => row,
1756 None => PropertyObject::from_object(&raw.objects[row_id])?,
1757 };
1758 let mut cell_refs = Vec::new();
1759 for cell in cells {
1760 cell_refs.extend_from_slice(&row.reference(*cell)?);
1761 }
1762 row.set(&[(0x24001c20, &cell_refs), (0x14001d7a, &modified)])?;
1763 changed.insert(*row_id, row);
1764 }
1765 let mut table_object = match changed.remove(&table_id) {
1766 Some(object) => object,
1767 None => PropertyObject::from_object(&raw.objects[&table_id])?,
1768 };
1769 for (row_id, _) in &rows {
1770 row_refs.extend_from_slice(&table_object.reference(*row_id)?);
1771 }
1772 table_object.remove(&[0x1c001d7d, 0x08001d5e])?;
1773 table_object.set(
1774 &table_values
1775 .iter()
1776 .map(|(id, bytes)| (*id, bytes.as_slice()))
1777 .collect::<Vec<_>>(),
1778 )?;
1779 table_object.set(&[(0x24001c20, &row_refs)])?;
1780 changed.insert(table_id, table_object);
1781 if is_new {
1782 let mut object = PropertyObject::from_object(&raw.objects[&holder])?;
1783 let reference = object.reference(table_id)?;
1784 object.set(&[(0x24001c1f, &reference), (0x14001d7a, &modified)])?;
1785 changed.insert(holder, object);
1786 }
1787 Ok(changed)
1788 })?;
1789 }961 }
1790 Ok(())962 Ok(())
1791 }963 }
...@@ -1804,11 +976,10 @@ impl<'a> Lowering<'a> {...@@ -1804,11 +976,10 @@ impl<'a> Lowering<'a> {
1804 }976 }
1805 let mut ancestor = old.container[id];977 let mut ancestor = old.container[id];
1806 let mut covered = false;978 let mut covered = false;
1807 loop {979 // A surviving ancestor is placed where the model says, carrying this paragraph along.
980 while !new.paragraphs.contains_key(&ancestor) {
1808 if removed_outline(ancestor)981 if removed_outline(ancestor)
1809 || (old.paragraphs.contains_key(&ancestor)982 || (old.paragraphs.contains_key(&ancestor) && !consumed.contains(&ancestor))
1810 && !new.paragraphs.contains_key(&ancestor)
1811 && !consumed.contains(&ancestor))
1812 || (old.children.contains_key(&ancestor)983 || (old.children.contains_key(&ancestor)
1813 && !old.paragraphs.contains_key(&ancestor)984 && !old.paragraphs.contains_key(&ancestor)
1814 && !old.outlines.contains_key(&ancestor)985 && !old.outlines.contains_key(&ancestor)
...@@ -1853,120 +1024,116 @@ impl<'a> Lowering<'a> {...@@ -1853,120 +1024,116 @@ impl<'a> Lowering<'a> {
1853 Ok(())1024 Ok(())
1854 }1025 }
18551026
1856 /// Rewrites every equation paragraph whose stored text object differs from the model, the1027 /// Rewrites every equation paragraph whose stored text object differs from the model.
1857 /// way OneNote stores an equation: the linear text, one run per span with a style
1858 /// carrying the span's format, the run-data array naming each run's inline object, and
1859 /// the math language marker on the text object.
1860 fn edit_equations(&mut self, new: &View<'_>) -> Result<(), Error> {1028 fn edit_equations(&mut self, new: &View<'_>) -> Result<(), Error> {
1861 let current = self.current()?;1029 let current = self.current()?;
1862 let current = View::new(&current)?;1030 let current = View::new(&current, false)?;
1863 for (id, paragraph) in &new.paragraphs {1031 for (id, paragraph) in &new.paragraphs {
1864 let Some(text) = paragraph.text() else {1032 let Some(text) = paragraph.text() else {
1865 continue;1033 continue;
1866 };1034 };
1867 if !Math::is_equation(&text.text) {1035 let stored = current.text(self.id(*id));
1036 // Deleting an equation leaves no math in the model, but its stored runs are still math.
1037 if !super::Math::is_equation(&text.text)
1038 && !stored.is_some_and(|stored| super::Math::is_equation(&stored.text))
1039 {
1868 continue;1040 continue;
1869 }1041 }
1870 let stored = current1042 let stored = stored
1871 .text(self.id(*id))
1872 .ok_or_else(|| invalid("An equation paragraph is missing after placement"))?;1043 .ok_or_else(|| invalid("An equation paragraph is missing after placement"))?;
1873 if stored.text == text.text {1044 if stored.text == text.text {
1874 continue;1045 continue;
1875 }1046 }
1876 let object = stored.id;1047 let object = stored.id;
1877 if text.text.text().contains('\u{fffc}') {1048 self.write(|active| content::equation_changes(active, object, &text.text))?;
1878 return Err(invalid("Equations cannot hold embedded objects"));1049 }
1879 }1050 Ok(())
1880 let encoded: Vec<u8> = text1051 }
1881 .text1052
1882 .text()1053 /// Lifts the paragraphs of outline groups into their container wherever this save changes
1883 .encode_utf16()1054 /// the container's children or their depths, for placement to see them; `edit_levels`
1884 .chain([0])1055 /// groups them again.
1885 .flat_map(u16::to_le_bytes)1056 fn ungroup(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> {
1886 .collect();1057 for (container, children) in &old.children {
1887 let mut ends = Vec::new();1058 let Some(after) = new.children.get(container) else {
1888 let mut styles: Vec<Values> = Vec::new();1059 continue;
1889 let mut sets = Vec::new();1060 };
1890 let all_math = text1061 let grouped = self.active.view.nodes[container]
1891 .text1062 .children
1892 .spans()
1893 .iter()1063 .iter()
1894 .all(|s| s.format.math == Some(true));1064 .any(|id| matches!(self.active.view.nodes[id].kind, Kind::OutlineGroup));
1895 for span in text.text.spans() {1065 if !grouped
1896 ends.extend(text.text.utf16_offset(span.end)?.to_le_bytes());1066 || (after == children
1897 styles.push(style_values(&span.format));1067 && old.depths(*container, children) == new.depths(*container, after))
1898 sets.push(match &span.format.math_object {1068 {
1899 Some(object) => {1069 continue;
1900 let mut set = vec![(0x1400344f, object.kind.to_le_bytes().to_vec())];1070 }
1901 if let Some(count) = object.arguments {1071 let object = *container;
1902 set.push((0x14003450, count.to_le_bytes().to_vec()));1072 self.write(|active| levels::ungroup_changes(active, object))?;
1903 }1073 }
1904 if let Some(columns) = object.columns {1074 Ok(())
1905 set.push((0x0c003451, vec![columns]));1075 }
1906 }1076
1907 for (id, symbol) in [0x10003453, 0x10003454, 0x10003455]1077 /// Indents each container's children to the depths the model gives them.
1908 .into_iter()1078 fn edit_levels(&mut self, new: &View<'_>) -> Result<(), Error> {
1909 .zip(&object.symbols)1079 let current = self.current()?;
1910 {1080 let stored = View::new(&current, false)?;
1911 let unit = u16::try_from(u32::from(*symbol))1081 for (container, children) in &new.children {
1912 .map_err(|_| invalid("Math symbols are single UTF-16 units"))?;1082 let depths = new
1913 set.push((id, unit.to_le_bytes().to_vec()));1083 .depths(*container, children)
1914 }1084 .ok_or_else(|| invalid("A paragraph lies no deeper than its parent"))?;
1915 set1085 let object = self.id(*container);
1916 }1086 let written: Vec<ExGuid> = children.iter().map(|id| self.id(*id)).collect();
1917 None => Vec::new(),1087 if children.is_empty() || stored.depths(object, &written) == Some(depths.clone()) {
1918 });1088 continue;
1089 }
1090 let is_cell =
1091 !new.paragraphs.contains_key(container) && !new.outlines.contains_key(container);
1092 let (level, runs) = levels::runs(&written, &depths, is_cell)?;
1093 self.write(|active| levels::regroup_changes(active, object, level, &runs))?;
1094 }
1095 Ok(())
1096 }
1097
1098 /// Gives a paragraph the text object its model names when a split and a join in one save
1099 /// moved text between paragraphs: a stored object is taken over, as OneNote moves a lower
1100 /// paragraph's text into an emptied upper one; a new one starts as a copy of the text
1101 /// the paragraph holds.
1102 fn edit_text_identities(&mut self, new: &View<'_>) -> Result<(), Error> {
1103 for (id, paragraph) in &new.paragraphs {
1104 let Some(text) = paragraph.text() else {
1105 continue;
1106 };
1107 let object = self.id(*id);
1108 let stored = self.active.view.nodes[&object].content[0];
1109 if stored == self.id(text.id) {
1110 continue;
1919 }1111 }
1920 ends.truncate(ends.len() - 4);1112 let target = if self.active.live.revision.objects.contains_key(&text.id) {
1921 let has_objects = sets.iter().any(|set| !set.is_empty());1113 text.id
1922 let modified = crate::create::current_timestamps()?.0.to_le_bytes();1114 } else {
1115 self.allocate(text.id)?
1116 };
1117 let unstyled = paragraph.style.is_none();
1923 self.write(|active| {1118 self.write(|active| {
1119 let parents = active.editable_parents(object)?;
1120 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
1924 let raw = &active.live.revision;1121 let raw = &active.live.revision;
1925 let mut changed = BTreeMap::new();1122 let mut changed = BTreeMap::new();
1926 let mut target = PropertyObject::from_object(&raw.objects[&object])?;1123 if !raw.objects.contains_key(&target) {
1927 let mut references = Vec::new();1124 let mut copy = PropertyObject::from_object(&raw.objects[&stored])?;
1928 let mut created: Vec<(Values, ExGuid)> = Vec::new();1125 copy.remove(&[0x1c001c98, 0x14001c99])?;
1929 for values in &styles {1126 if unstyled {
1930 let id = match created.iter().find(|(known, _)| known == values) {1127 copy.remove(&[0x2000342c])?;
1931 Some((_, id)) => *id,1128 }
1932 None => {1129 copy.reference(target)?;
1933 let id = ExGuid {1130 changed.insert(target, copy);
1934 guid: crate::write::fresh_guid()?,
1935 n: 1,
1936 };
1937 let mut style = PropertyObject {
1938 jcid: 0x12004d,
1939 bytes: crate::create::properties(values)?,
1940 global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])),
1941 };
1942 style.reference(id)?;
1943 changed.insert(id, style);
1944 created.push((values.clone(), id));
1945 id
1946 }
1947 };
1948 references.extend(target.reference(id)?);
1949 }
1950 target.remove(&[0x1c003498, 0x40003499])?;
1951 target.set(&[
1952 (0x1c001c22, &encoded),
1953 (0x1c001e12, &ends),
1954 (0x24001e13, &references),
1955 (0x14001d7a, &modified),
1956 ])?;
1957 if has_objects {
1958 target.set_sets(0x40003499, 0x44000811, &sets)?;
1959 }
1960 if all_math {
1961 // The flags and language marker OneNote's equation editor leaves on
1962 // every equation text object.
1963 target.set(&[
1964 (0x10001cfe, &0x7f_u16.to_le_bytes()),
1965 (0x14001c3e, &1u32.to_le_bytes()),
1966 (0x14001c84, &1u32.to_le_bytes()),
1967 ])?;
1968 }1131 }
1969 changed.insert(object, target);1132 let mut holder = PropertyObject::from_object(&raw.objects[&object])?;
1133 let reference = holder.reference(target)?;
1134 holder.set(&[(0x24001c1f, &reference), (0x14001d7a, &modified)])?;
1135 changed.insert(object, holder);
1136 crate::formatting::touch_ancestors(raw, parents, object, &modified, &mut changed)?;
1970 Ok(changed)1137 Ok(changed)
1971 })?;1138 })?;
1972 }1139 }
...@@ -1975,7 +1142,7 @@ impl<'a> Lowering<'a> {...@@ -1975,7 +1142,7 @@ impl<'a> Lowering<'a> {
19751142
1976 fn edit_text(&mut self, new: &View<'_>) -> Result<(), Error> {1143 fn edit_text(&mut self, new: &View<'_>) -> Result<(), Error> {
1977 let current = self.current()?;1144 let current = self.current()?;
1978 let current = View::new(&current)?;1145 let current = View::new(&current, false)?;
1979 for (id, paragraph) in &new.paragraphs {1146 for (id, paragraph) in &new.paragraphs {
1980 let Some(text) = paragraph.text() else {1147 let Some(text) = paragraph.text() else {
1981 continue;1148 continue;
...@@ -2003,7 +1170,7 @@ impl<'a> Lowering<'a> {...@@ -2003,7 +1170,7 @@ impl<'a> Lowering<'a> {
2003 /// nodes belong to one paragraph, and a dropped reference leaves the node unreferenced.1170 /// nodes belong to one paragraph, and a dropped reference leaves the node unreferenced.
2004 fn edit_lists(&mut self, after: &Page, new: &View<'_>) -> Result<(), Error> {1171 fn edit_lists(&mut self, after: &Page, new: &View<'_>) -> Result<(), Error> {
2005 let current = self.current()?;1172 let current = self.current()?;
2006 let stored = View::new(&current)?;1173 let stored = View::new(&current, false)?;
2007 let mut owners: BTreeMap<ExGuid, ExGuid> = BTreeMap::new();1174 let mut owners: BTreeMap<ExGuid, ExGuid> = BTreeMap::new();
2008 for (id, paragraph) in &stored.paragraphs {1175 for (id, paragraph) in &stored.paragraphs {
2009 for list in &paragraph.lists {1176 for list in &paragraph.lists {
...@@ -2022,7 +1189,8 @@ impl<'a> Lowering<'a> {...@@ -2022,7 +1189,8 @@ impl<'a> Lowering<'a> {
2022 .iter()1189 .iter()
2023 .zip(&previous.lists)1190 .zip(&previous.lists)
2024 .all(|(model, node)| {1191 .all(|(model, node)| {
2025 after.definitions.get(model) == current.definitions.get(node)1192 self.id(*model) == *node
1193 && after.definitions.get(model) == current.definitions.get(node)
2026 });1194 });
2027 if same {1195 if same {
2028 continue;1196 continue;
...@@ -2032,12 +1200,14 @@ impl<'a> Lowering<'a> {...@@ -2032,12 +1200,14 @@ impl<'a> Lowering<'a> {
2032 // Writer-allocated identities keep a nonzero sequence number so the node's1200 // Writer-allocated identities keep a nonzero sequence number so the node's
2033 // compact identity is never the null identity; squash renames them to the1201 // compact identity is never the null identity; squash renames them to the
2034 // model's. A copy for a second owner keeps its allocated identity.1202 // model's. A copy for a second owner keeps its allocated identity.
2035 let node_id = match owners.get(list) {1203 let node_id = match owners.get(&self.id(*list)) {
2036 Some(owner) if *owner == image_id => *list,1204 Some(owner) if *owner == image_id => self.id(*list),
2037 Some(_) => ExGuid {1205 Some(_) => ExGuid {
2038 guid: crate::write::fresh_guid()?,1206 guid: crate::write::fresh_guid()?,
2039 n: 1,1207 n: 1,
2040 },1208 },
1209 // A node its paragraph let go of in this save serves the model again.
1210 None if self.active.live.revision.objects.contains_key(list) => *list,
2041 None => {1211 None => {
2042 let written = ExGuid {1212 let written = ExGuid {
2043 guid: crate::write::fresh_guid()?,1213 guid: crate::write::fresh_guid()?,
...@@ -2051,113 +1221,10 @@ impl<'a> Lowering<'a> {...@@ -2051,113 +1221,10 @@ impl<'a> Lowering<'a> {
2051 .definitions1221 .definitions
2052 .get(list)1222 .get(list)
2053 .ok_or_else(|| invalid("A paragraph references a missing list definition"))?;1223 .ok_or_else(|| invalid("A paragraph references a missing list definition"))?;
2054 let Kind::List {1224 nodes.push((node_id, properties::list_values(definition)?));
2055 font,
2056 format,
2057 restart,
2058 bullet,
2059 } = &definition.kind
2060 else {
2061 return Err(invalid("A paragraph list must reference a list definition"));
2062 };
2063 if bullet.is_some() && (format.is_none() || font.is_none()) {
2064 return Err(invalid("A bullet definition names its glyph and font"));
2065 }
2066 let mut values: Values = Vec::new();
2067 if let Some(format) = format {
2068 let units: Vec<u16> = format.encode_utf16().collect();
2069 let count = u16::try_from(units.len())
2070 .map_err(|_| invalid("List format exceeds the document range"))?;
2071 let mut bytes = count.to_le_bytes().to_vec();
2072 bytes.extend(units.iter().flat_map(|unit| unit.to_le_bytes()));
2073 values.push((0x1c001c1a, bytes));
2074 }
2075 if let Some(font) = font {
2076 values.push((0x1c001c52, crate::create::string(font)));
2077 }
2078 if let Some(restart) = restart {
2079 values.push((0x14001cb7, restart.to_le_bytes().to_vec()));
2080 }
2081 if let Some(bullet) = bullet {
2082 values.push((0x10001d0e, bullet.to_le_bytes().to_vec()));
2083 // Every native bullet node carries this cleared flag alongside its index.
2084 values.push((0x0c001cc0, vec![0]));
2085 }
2086 let style = &definition.format;
2087 if let Some(font) = &style.font {
2088 values.push((0x1c001c0a, crate::create::string(font)));
2089 }
2090 if let Some(size) = style.font_size {
2091 let half = (size * 2.0).round();
2092 if !(0.0..=f32::from(u16::MAX)).contains(&half) {
2093 return Err(invalid("List font size is outside the document range"));
2094 }
2095 values.push((0x10001c0b, (half as u16).to_le_bytes().to_vec()));
2096 }
2097 if let Some(color) = style.color {
2098 values.push((0x14001c0c, color.to_le_bytes().to_vec()));
2099 }
2100 if let Some(language) = style.language {
2101 values.push((0x14001c3b, language.to_le_bytes().to_vec()));
2102 }
2103 for (flag, id) in [(style.bold, 0x08001c04), (style.italic, 0x08001c05)] {
2104 if let Some(flag) = flag {
2105 values.push((id | (u32::from(flag) << 31), Vec::new()));
2106 }
2107 }
2108 nodes.push((node_id, values));
2109 }1225 }
2110 let object = self.id(*id);1226 let object = self.id(*id);
2111 self.write(|active| {1227 self.write(|active| properties::list_changes(active, object, &nodes))?;
2112 let parents = active.editable_parents(object)?;
2113 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
2114 let raw = &active.live.revision;
2115 let mut changed = BTreeMap::new();
2116 let mut target = PropertyObject::from_object(&raw.objects[&object])?;
2117 let mut references = Vec::new();
2118 for (list, values) in &nodes {
2119 let mut node = match raw.objects.get(list) {
2120 Some(existing) => {
2121 if existing.jcid != 0x60012 {
2122 return Err(invalid(
2123 "A list definition identity belongs to another object",
2124 ));
2125 }
2126 let mut node = PropertyObject::from_object(existing)?;
2127 node.remove(&[
2128 0x1c001c1a, 0x1c001c52, 0x14001cb7, 0x10001d0e, 0x0c001cc0,
2129 0x1c001c0a, 0x10001c0b, 0x14001c0c, 0x14001c3b, 0x08001c04,
2130 0x08001c05,
2131 ])?;
2132 node
2133 }
2134 None => PropertyObject {
2135 jcid: 0x60012,
2136 bytes: crate::create::properties(&[])?,
2137 global_ids: std::sync::Arc::new(BTreeMap::from([(0, list.guid)])),
2138 },
2139 };
2140 node.set(
2141 &values
2142 .iter()
2143 .map(|(id, bytes)| (*id, bytes.as_slice()))
2144 .collect::<Vec<_>>(),
2145 )?;
2146 node.set(&[(0x14001d7a, &modified)])?;
2147 node.reference(*list)?;
2148 references.extend_from_slice(&target.reference(*list)?);
2149 changed.insert(*list, node);
2150 }
2151 if references.is_empty() {
2152 target.remove(&[0x24001c26])?;
2153 } else {
2154 target.set(&[(0x24001c26, &references)])?;
2155 }
2156 target.set(&[(0x14001d7a, &modified)])?;
2157 changed.insert(object, target);
2158 crate::formatting::touch_ancestors(raw, parents, object, &modified, &mut changed)?;
2159 Ok(changed)
2160 })?;
2161 }1228 }
2162 Ok(())1229 Ok(())
2163 }1230 }
...@@ -2166,23 +1233,8 @@ impl<'a> Lowering<'a> {...@@ -2166,23 +1233,8 @@ impl<'a> Lowering<'a> {
2166 /// the stored ones. A tag definition new to the section becomes a definition object1233 /// the stored ones. A tag definition new to the section becomes a definition object
2167 /// carrying the model's identity after squash.1234 /// carrying the model's identity after squash.
2168 fn edit_tags(&mut self, after: &Page, new: &View<'_>) -> Result<(), Error> {1235 fn edit_tags(&mut self, after: &Page, new: &View<'_>) -> Result<(), Error> {
2169 fn same(a: &[Tag], b: &[Tag]) -> bool {
2170 let key = |t: &Tag| {
2171 (
2172 t.definition,
2173 t.action_type,
2174 t.status,
2175 t.created,
2176 t.completed,
2177 t.start,
2178 t.due,
2179 t.task_id,
2180 )
2181 };
2182 a.len() == b.len() && a.iter().zip(b).all(|(x, y)| key(x) == key(y))
2183 }
2184 let current = self.current()?;1236 let current = self.current()?;
2185 let stored = View::new(&current)?;1237 let stored = View::new(&current, false)?;
2186 for (id, paragraph) in &new.paragraphs {1238 for (id, paragraph) in &new.paragraphs {
2187 let image_id = self.id(*id);1239 let image_id = self.id(*id);
2188 let previous = stored1240 let previous = stored
...@@ -2194,28 +1246,15 @@ impl<'a> Lowering<'a> {...@@ -2194,28 +1246,15 @@ impl<'a> Lowering<'a> {
2194 targets.push((self.id(text.id), &text.tags, &before.tags));1246 targets.push((self.id(text.id), &text.tags, &before.tags));
2195 }1247 }
2196 for (object, tags, stored_tags) in targets {1248 for (object, tags, stored_tags) in targets {
2197 if same(tags, stored_tags) {1249 if crate::op::lower::same_tags(tags, stored_tags) {
2198 continue;1250 continue;
2199 }1251 }
2200 let mut definitions: Vec<(ExGuid, Option<Values>)> = Vec::new();1252 let mut entries = Vec::new();
2201 let mut sets: Vec<(usize, Values)> = Vec::new();
2202 let mut action_types = BTreeSet::new();
2203 for tag in tags {1253 for tag in tags {
2204 let definition = tag1254 let definition = tag
2205 .definition1255 .definition
2206 .ok_or_else(|| invalid("A note tag names its definition"))?;1256 .ok_or_else(|| invalid("A note tag names its definition"))?;
2207 let action_type = if tag.status & 4 != 0 {1257 let known = self.active.live.revision.objects.contains_key(&definition)
2208 tag.action_type
2209 } else {
2210 match after.definitions.get(&definition).map(|d| &d.kind) {
2211 Some(Kind::TagDefinition { action_type, .. }) => *action_type,
2212 _ => return Err(invalid("A note tag must reference a tag definition")),
2213 }
2214 };
2215 if !action_types.insert(action_type.unwrap_or(0)) {
2216 return Err(invalid("An element holds one note tag per action type"));
2217 }
2218 let known = current.definitions.contains_key(&definition)
2219 || self.alias.contains_key(&definition);1258 || self.alias.contains_key(&definition);
2220 let written = if known {1259 let written = if known {
2221 self.id(definition)1260 self.id(definition)
...@@ -2227,107 +1266,9 @@ impl<'a> Lowering<'a> {...@@ -2227,107 +1266,9 @@ impl<'a> Lowering<'a> {
2227 self.alias.insert(definition, allocated);1266 self.alias.insert(definition, allocated);
2228 allocated1267 allocated
2229 };1268 };
2230 let index = match definitions.iter().position(|(id, _)| *id == written) {1269 entries.push((written, tag, after.definitions.get(&definition)));
2231 Some(index) => index,
2232 None => {
2233 let values = if known {
2234 None
2235 } else {
2236 let Some(model) = after.definitions.get(&definition) else {
2237 return Err(invalid(
2238 "A note tag references a missing tag definition",
2239 ));
2240 };
2241 let Kind::TagDefinition {
2242 label,
2243 action_type,
2244 shape,
2245 color,
2246 highlight,
2247 } = &model.kind
2248 else {
2249 return Err(invalid(
2250 "A note tag must reference a tag definition",
2251 ));
2252 };
2253 let mut values: Values = vec![
2254 (0x0c003473, vec![0]),
2255 (0x10003463, action_type.unwrap_or(0).to_le_bytes().to_vec()),
2256 (0x10003464, shape.unwrap_or(0).to_le_bytes().to_vec()),
2257 (0x14003467, 0u32.to_le_bytes().to_vec()),
2258 ];
2259 if let Some(label) = label {
2260 values.push((0x1c003468, crate::create::string(label)));
2261 }
2262 if let Some(color) = color {
2263 values.push((0x14003466, color.to_le_bytes().to_vec()));
2264 }
2265 if let Some(highlight) = highlight {
2266 values.push((0x14003465, highlight.to_le_bytes().to_vec()));
2267 }
2268 Some(values)
2269 };
2270 definitions.push((written, values));
2271 definitions.len() - 1
2272 }
2273 };
2274 let mut fields: Values = Vec::new();
2275 if let Some(action_type) = tag.action_type {
2276 fields.push((0x10003463, action_type.to_le_bytes().to_vec()));
2277 }
2278 for (id, value) in [
2279 (0x1400346e, tag.created),
2280 (0x1400346f, tag.completed),
2281 (0x1400346a, tag.start),
2282 (0x1400346b, tag.due),
2283 ] {
2284 if let Some(value) = value {
2285 fields.push((id, value.to_le_bytes().to_vec()));
2286 }
2287 }
2288 fields.push((0x10003470, tag.status.to_le_bytes().to_vec()));
2289 if let Some(task) = tag.task_id {
2290 fields.push((0x1c003469, task.to_vec()));
2291 }
2292 sets.push((index, fields));
2293 }1270 }
2294 self.write(|active| {1271 self.write(|active| properties::tag_changes(active, object, &entries))?;
2295 let parents = active.editable_parents(object)?;
2296 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
2297 let raw = &active.live.revision;
2298 let mut changed = BTreeMap::new();
2299 for (id, values) in &definitions {
2300 let Some(values) = values else {
2301 continue;
2302 };
2303 let mut node = PropertyObject {
2304 jcid: 0x120043,
2305 bytes: crate::create::properties(values)?,
2306 global_ids: std::sync::Arc::new(BTreeMap::from([(0, id.guid)])),
2307 };
2308 node.reference(*id)?;
2309 changed.insert(*id, node);
2310 }
2311 let mut target = PropertyObject::from_object(&raw.objects[&object])?;
2312 let mut encoded = Vec::new();
2313 for (index, fields) in &sets {
2314 let reference = target.reference(definitions[*index].0)?;
2315 let mut set = vec![(0x20003488, reference.to_vec())];
2316 set.extend(fields.iter().cloned());
2317 encoded.push(set);
2318 }
2319 target.set_sets(0x40003489, 0x44000811, &encoded)?;
2320 target.set(&[(0x14001d7a, &modified)])?;
2321 changed.insert(object, target);
2322 crate::formatting::touch_ancestors(
2323 raw,
2324 parents,
2325 object,
2326 &modified,
2327 &mut changed,
2328 )?;
2329 Ok(changed)
2330 })?;
2331 }1272 }
2332 }1273 }
2333 Ok(())1274 Ok(())
...@@ -2342,7 +1283,6 @@ impl<'a> Lowering<'a> {...@@ -2342,7 +1283,6 @@ impl<'a> Lowering<'a> {
2342 new: &View<'_>,1283 new: &View<'_>,
2343 after: &Page,1284 after: &Page,
2344 ) -> Result<(), Error> {1285 ) -> Result<(), Error> {
2345 let current = self.current()?;
2346 for (id, paragraph) in &new.paragraphs {1286 for (id, paragraph) in &new.paragraphs {
2347 let Some(definition) = paragraph.style else {1287 let Some(definition) = paragraph.style else {
2348 continue;1288 continue;
...@@ -2357,7 +1297,7 @@ impl<'a> Lowering<'a> {...@@ -2357,7 +1297,7 @@ impl<'a> Lowering<'a> {
2357 let Some(text) = paragraph.text() else {1297 let Some(text) = paragraph.text() else {
2358 return Err(invalid("Only text paragraphs take a paragraph style"));1298 return Err(invalid("Only text paragraphs take a paragraph style"));
2359 };1299 };
2360 let known = current.definitions.contains_key(&definition)1300 let known = self.active.live.revision.objects.contains_key(&definition)
2361 || self.alias.contains_key(&definition);1301 || self.alias.contains_key(&definition);
2362 let style_id = if known {1302 let style_id = if known {
2363 self.id(definition)1303 self.id(definition)
...@@ -2369,59 +1309,16 @@ impl<'a> Lowering<'a> {...@@ -2369,59 +1309,16 @@ impl<'a> Lowering<'a> {
2369 self.alias.insert(definition, allocated);1309 self.alias.insert(definition, allocated);
2370 allocated1310 allocated
2371 };1311 };
2372 let values = if known {
2373 None
2374 } else {
2375 let Some(model) = after.definitions.get(&definition) else {
2376 return Err(invalid("A paragraph references a missing style definition"));
2377 };
2378 let Kind::Style { name } = &model.kind else {
2379 return Err(invalid("A paragraph style must be a style definition"));
2380 };
2381 let mut values = style_values(&model.format);
2382 if let Some(name) = name {
2383 values.push((0x1c00345a, crate::create::string(name)));
2384 }
2385 if let Some(alignment) = model.format.alignment {
2386 values.push((0x0c003477, vec![alignment]));
2387 }
2388 for (property, value) in [
2389 (0x1400342e, model.format.space_before),
2390 (0x1400342f, model.format.space_after),
2391 (0x14003430, model.format.line_spacing),
2392 ] {
2393 if let Some(points) = value {
2394 values.push((property, (points / 36.0).to_le_bytes().to_vec()));
2395 }
2396 }
2397 Some(values)
2398 };
2399 let object = self.id(text.id);1312 let object = self.id(text.id);
2400 self.write(|active| {1313 let definition = after.definitions.get(&definition);
2401 let raw = &active.live.revision;1314 self.write(|active| properties::style_changes(active, object, style_id, definition))?;
2402 let mut changed = BTreeMap::new();
2403 if let Some(values) = &values {
2404 let mut node = PropertyObject {
2405 jcid: 0x12004d,
2406 bytes: crate::create::properties(values)?,
2407 global_ids: std::sync::Arc::new(BTreeMap::from([(0, style_id.guid)])),
2408 };
2409 node.reference(style_id)?;
2410 changed.insert(style_id, node);
2411 }
2412 let mut target = PropertyObject::from_object(&raw.objects[&object])?;
2413 let reference = target.reference(style_id)?;
2414 target.set(&[(0x2000342c, &reference)])?;
2415 changed.insert(object, target);
2416 Ok(changed)
2417 })?;
2418 }1315 }
2419 Ok(())1316 Ok(())
2420 }1317 }
24211318
2422 fn edit_paragraph_formatting(&mut self, new: &View<'_>) -> Result<(), Error> {1319 fn edit_paragraph_formatting(&mut self, new: &View<'_>) -> Result<(), Error> {
2423 let current = self.current()?;1320 let current = self.current()?;
2424 let current = View::new(&current)?;1321 let current = View::new(&current, false)?;
2425 for (id, paragraph) in &new.paragraphs {1322 for (id, paragraph) in &new.paragraphs {
2426 let Some(text) = paragraph.text() else {1323 let Some(text) = paragraph.text() else {
2427 continue;1324 continue;
...@@ -2429,51 +1326,7 @@ impl<'a> Lowering<'a> {...@@ -2429,51 +1326,7 @@ impl<'a> Lowering<'a> {
2429 let stored = current1326 let stored = current
2430 .text(self.id(*id))1327 .text(self.id(*id))
2431 .ok_or_else(|| invalid("A paragraph is missing after text edits"))?;1328 .ok_or_else(|| invalid("A paragraph is missing after text edits"))?;
2432 let mut values: Vec<(u32, Vec<u8>)> = Vec::new();1329 let values = crate::op::lower::paragraph_change(&stored.text, &text.text)?;
2433 macro_rules! field {
2434 ($field:ident, $value:ident, $encode:block) => {
2435 let $value = format_in(&text.text, 0)?.$field.unwrap_or_default();
2436 if text
2437 .text
2438 .spans()
2439 .iter()
2440 .all(|span| span.format.$field.unwrap_or_default() == $value)
2441 && stored
2442 .text
2443 .spans()
2444 .iter()
2445 .any(|span| span.format.$field.unwrap_or_default() != $value)
2446 {
2447 values.push($encode);
2448 }
2449 };
2450 }
2451 field!(alignment, value, {
2452 if value > 2 {
2453 return Err(invalid("Paragraph alignment must be left, center or right"));
2454 }
2455 (0x0c003477, vec![value])
2456 });
2457 field!(rtl, value, {
2458 (0x08003476 | (u32::from(value) << 31), Vec::new())
2459 });
2460 macro_rules! spacing {
2461 ($field:ident, $property:expr) => {
2462 field!($field, value, {
2463 let stored = value / 36.0;
2464 if !stored.is_finite() || !(0.0..=27777.777).contains(&stored) {
2465 return Err(invalid("Paragraph spacing is outside the document range"));
2466 }
2467 ($property, stored.to_le_bytes().to_vec())
2468 });
2469 };
2470 }
2471 spacing!(space_before, 0x1400342e);
2472 spacing!(space_after, 0x1400342f);
2473 spacing!(line_spacing, 0x14003430);
2474 field!(language, value, {
2475 (0x14001c3b, value.to_le_bytes().to_vec())
2476 });
2477 if values.is_empty() {1330 if values.is_empty() {
2478 continue;1331 continue;
2479 }1332 }
...@@ -2483,48 +1336,14 @@ impl<'a> Lowering<'a> {...@@ -2483,48 +1336,14 @@ impl<'a> Lowering<'a> {
2483 ));1336 ));
2484 }1337 }
2485 let object = stored.id;1338 let object = stored.id;
2486 self.write(|active| {1339 self.write(|active| properties::paragraph_format_changes(active, object, &values))?;
2487 let parents = active.editable_parents(object)?;
2488 let modified = crate::create::current_timestamps()?.0.to_le_bytes();
2489 let raw = &active.live.revision;
2490 let mut target = PropertyObject::from_object(&raw.objects[&object])?;
2491 target.set(
2492 &values
2493 .iter()
2494 .map(|(id, bytes)| (*id, bytes.as_slice()))
2495 .collect::<Vec<_>>(),
2496 )?;
2497 if let Some((_, alignment)) = values.iter().find(|(id, _)| *id == 0x0c003477) {
2498 for property in [0x14001c3e, 0x14001c84] {
2499 let fields = PropertySets::parse(&target.bytes)?;
2500 let previous = fields.sets[0]
2501 .iter()
2502 .find(|field| field.id == property)
2503 .map(|field| match field.value {
2504 Value::Bytes(bytes) => bytes
2505 .try_into()
2506 .map(u32::from_le_bytes)
2507 .map_err(|_| invalid("Invalid paragraph layout alignment")),
2508 _ => Err(invalid("Invalid paragraph layout alignment")),
2509 })
2510 .transpose()?
2511 .unwrap_or(0);
2512 let value = (previous & !7) | (u32::from(alignment[0]) + 1);
2513 target.set(&[(property, &value.to_le_bytes())])?;
2514 }
2515 }
2516 target.set(&[(0x14001d7a, &modified)])?;
2517 let mut changed = BTreeMap::from([(object, target)]);
2518 crate::formatting::touch_ancestors(raw, parents, object, &modified, &mut changed)?;
2519 Ok(changed)
2520 })?;
2521 }1340 }
2522 Ok(())1341 Ok(())
2523 }1342 }
25241343
2525 fn edit_formatting(&mut self, new: &View<'_>) -> Result<(), Error> {1344 fn edit_formatting(&mut self, new: &View<'_>) -> Result<(), Error> {
2526 let current = self.current()?;1345 let current = self.current()?;
2527 let current = View::new(&current)?;1346 let current = View::new(&current, false)?;
2528 for (id, paragraph) in &new.paragraphs {1347 for (id, paragraph) in &new.paragraphs {
2529 let Some(text) = paragraph.text() else {1348 let Some(text) = paragraph.text() else {
2530 continue;1349 continue;
...@@ -2536,56 +1355,10 @@ impl<'a> Lowering<'a> {...@@ -2536,56 +1355,10 @@ impl<'a> Lowering<'a> {
2536 return Err(invalid("Text edits did not converge on the model"));1355 return Err(invalid("Text edits did not converge on the model"));
2537 }1356 }
2538 let fresh = self.alias.contains_key(&text.id);1357 let fresh = self.alias.contains_key(&text.id);
2539 let mut boundaries = BTreeSet::new();1358 for (range, set, clear) in crate::op::lower::format_edits(&stored.text, &text.text, fresh)? {
2540 for paragraph in [&stored.text, &text.text] {
2541 for span in paragraph.spans() {
2542 boundaries.insert(paragraph.utf16_offset(span.end)?);
2543 }
2544 }
2545 boundaries.insert(0);
2546 let boundaries: Vec<u32> = boundaries.into_iter().collect();
2547 let mut pending: Option<(Range<u32>, Vec<TextAttribute>)> = None;
2548 let mut edits = Vec::new();
2549 for window in boundaries.windows(2) {
2550 let (start, end) = (window[0], window[1]);
2551 if start == end {
2552 continue;
2553 }
2554 let attributes = attributes(
2555 format_in(&stored.text, start)?,
2556 format_in(&text.text, start)?,
2557 fresh,
2558 )?;
2559 match &mut pending {
2560 Some((range, previous)) if *previous == attributes && range.end == start => {
2561 range.end = end;
2562 }
2563 _ => {
2564 if let Some(edit) = pending.take() {
2565 edits.push(edit);
2566 }
2567 pending = Some((start..end, attributes));
2568 }
2569 }
2570 }
2571 edits.extend(pending);
2572 if text.text.text().is_empty() {
2573 let attributes = attributes(
2574 format_in(&stored.text, 0)?,
2575 format_in(&text.text, 0)?,
2576 fresh,
2577 )?;
2578 if !attributes.is_empty() {
2579 edits.push((0..0, attributes));
2580 }
2581 }
2582 for (range, attributes) in edits {
2583 if attributes.is_empty() {
2584 continue;
2585 }
2586 let object = stored.id;1359 let object = stored.id;
2587 self.write(|active| {1360 self.write(|active| {
2588 crate::formatting::format_changes(active, object, range, &attributes)1361 crate::formatting::format_changes(active, object, range, &set, &clear)
2589 })?;1362 })?;
2590 }1363 }
2591 }1364 }
...@@ -2594,7 +1367,7 @@ impl<'a> Lowering<'a> {...@@ -2594,7 +1367,7 @@ impl<'a> Lowering<'a> {
25941367
2595 fn edit_layout(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> {1368 fn edit_layout(&mut self, old: &View<'_>, new: &View<'_>) -> Result<(), Error> {
2596 let current = self.current()?;1369 let current = self.current()?;
2597 let current = View::new(&current)?;1370 let current = View::new(&current, false)?;
2598 for (id, paragraph) in &new.paragraphs {1371 for (id, paragraph) in &new.paragraphs {
2599 let stored = current1372 let stored = current
2600 .paragraphs1373 .paragraphs
...@@ -2621,7 +1394,7 @@ impl<'a> Lowering<'a> {...@@ -2621,7 +1394,7 @@ impl<'a> Lowering<'a> {
2621 && !outline.indents.is_empty()1394 && !outline.indents.is_empty()
2622 && outline.indents != stored.indents1395 && outline.indents != stored.indents
2623 {1396 {
2624 let indents = measurement_bytes(&outline.indents, 4)?;1397 let indents = content::measurement_bytes(&outline.indents, 4)?;
2625 self.write(|active| {1398 self.write(|active| {
2626 let raw = &active.live.revision;1399 let raw = &active.live.revision;
2627 let mut node = PropertyObject::from_object(&raw.objects[&object])?;1400 let mut node = PropertyObject::from_object(&raw.objects[&object])?;
...@@ -2654,217 +1427,6 @@ impl<'a> Lowering<'a> {...@@ -2654,217 +1427,6 @@ impl<'a> Lowering<'a> {
2654 }1427 }
2655}1428}
26561429
2657fn collect_containers(list: &[PageParagraph], out: &mut Vec<ExGuid>) {
2658 for paragraph in list {
2659 out.push(paragraph.id);
2660 if let ParagraphContent::Table(table) = &paragraph.content {
2661 for row in &table.rows {
2662 for cell in &row.cells {
2663 out.push(cell.id);
2664 collect_containers(&cell.paragraphs, out);
2665 }
2666 }
2667 }
2668 }
2669}
2670
2671/// Identities that keep their stored position: a longest increasing run of survivors,
2672/// always including immovable ones. `after` may contain identities absent from `before`.
2673fn kept_set(
2674 before: &[ExGuid],
2675 after: &[ExGuid],
2676 movable: impl Fn(ExGuid) -> bool,
2677) -> Result<BTreeSet<ExGuid>, Error> {
2678 let position: BTreeMap<ExGuid, usize> =
2679 after.iter().enumerate().map(|(i, id)| (*id, i)).collect();
2680 let sequence: Vec<(ExGuid, usize)> = before
2681 .iter()
2682 .filter_map(|id| position.get(id).map(|at| (*id, *at)))
2683 .collect();
2684 let fixed: Vec<usize> = sequence
2685 .iter()
2686 .filter(|(id, _)| !movable(*id))
2687 .map(|(_, at)| *at)
2688 .collect();
2689 if fixed.windows(2).any(|w| w[0] > w[1]) {
2690 return Err(invalid(
2691 "Images and unsupported objects cannot be reordered",
2692 ));
2693 }
2694 if sequence.windows(2).all(|pair| pair[0].1 < pair[1].1) {
2695 return Ok(sequence.into_iter().map(|(id, _)| id).collect());
2696 }
2697 // Longest increasing subsequence over `after` positions, forced through immovable items.
2698 let n = sequence.len();
2699 let mut best = vec![1usize; n];
2700 let mut previous = vec![usize::MAX; n];
2701 for i in 0..n {
2702 let (id, at) = sequence[i];
2703 let mandatory_before = sequence[..i]
2704 .iter()
2705 .filter(|(other, _)| !movable(*other))
2706 .map(|(_, at)| *at)
2707 .max();
2708 if movable(id) && mandatory_before.is_some_and(|m| m > at) {
2709 best[i] = 0;
2710 continue;
2711 }
2712 let mandatory_after = sequence[i + 1..]
2713 .iter()
2714 .filter(|(other, _)| !movable(*other))
2715 .map(|(_, at)| *at)
2716 .min();
2717 if movable(id) && mandatory_after.is_some_and(|m| m < at) {
2718 best[i] = 0;
2719 continue;
2720 }
2721 for j in 0..i {
2722 if best[j] > 0 && sequence[j].1 < at && best[j] + 1 > best[i] {
2723 best[i] = best[j] + 1;
2724 previous[i] = j;
2725 }
2726 }
2727 }
2728 let mut kept = BTreeSet::new();
2729 if let Some((mut i, _)) = best
2730 .iter()
2731 .enumerate()
2732 .max_by_key(|(i, b)| (**b, usize::MAX - i))
2733 && best[i] > 0
2734 {
2735 loop {
2736 kept.insert(sequence[i].0);
2737 if previous[i] == usize::MAX {
2738 break;
2739 }
2740 i = previous[i];
2741 }
2742 }
2743 for (id, _) in &sequence {
2744 if !movable(*id) && !kept.contains(id) {
2745 return Err(invalid(
2746 "Images and unsupported objects cannot be reordered",
2747 ));
2748 }
2749 }
2750 Ok(kept)
2751}
2752
2753/// The format of the span containing the UTF-16 position `at` (the last span at the end).
2754fn format_in(paragraph: &super::Paragraph, at: u32) -> Result<&Format, Error> {
2755 let byte = paragraph.byte_offset(at)?;
2756 let spans = paragraph.spans();
2757 let index = spans
2758 .partition_point(|span| span.end <= byte)
2759 .min(spans.len() - 1);
2760 Ok(&spans[index].format)
2761}
2762
2763/// The smallest UTF-16 range whose replacement turns `before` into `after`.
2764fn text_edit(before: &str, after: &str) -> Result<(Range<u32>, String), Error> {
2765 let prefix = before
2766 .char_indices()
2767 .zip(after.chars())
2768 .take_while(|((_, a), b)| a == b)
2769 .map(|((i, a), _)| i + a.len_utf8())
2770 .last()
2771 .unwrap_or(0);
2772 let suffix = before[prefix..]
2773 .chars()
2774 .rev()
2775 .zip(after[prefix..].chars().rev())
2776 .take_while(|(a, b)| a == b)
2777 .map(|(a, _)| a.len_utf8())
2778 .sum::<usize>();
2779 let units = |s: &str| -> Result<u32, Error> {
2780 u32::try_from(s.encode_utf16().count())
2781 .map_err(|_| invalid("Text exceeds UTF-16 offset range"))
2782 };
2783 let start = units(&before[..prefix])?;
2784 let end = start + units(&before[prefix..before.len() - suffix])?;
2785 Ok((start..end, after[prefix..after.len() - suffix].to_owned()))
2786}
2787
2788/// Explicit attributes turning `current` into `target`; unsupported differences are errors.
2789/// A `fresh` text object was inserted by this edit, so an unspecified target value keeps
2790/// the insertion's default instead of demanding an inherited value the image cannot restore.
2791/// Language tags are retained as stored because the model has no way to author them.
2792fn attributes(current: &Format, target: &Format, fresh: bool) -> Result<Vec<TextAttribute>, Error> {
2793 let mut out = Vec::new();
2794 let inherited = || invalid("Inherited character formatting cannot be restored");
2795 // An absent flag and an explicit false are the same formatting.
2796 macro_rules! boolean {
2797 ($field:ident, $variant:ident) => {
2798 if current.$field.unwrap_or(false) != target.$field.unwrap_or(false) {
2799 out.push(TextAttribute::$variant(target.$field.unwrap_or(false)));
2800 }
2801 };
2802 }
2803 boolean!(bold, Bold);
2804 boolean!(italic, Italic);
2805 boolean!(underline, Underline);
2806 boolean!(strike, Strike);
2807 boolean!(superscript, Superscript);
2808 boolean!(subscript, Subscript);
2809 boolean!(hidden, Hidden);
2810 boolean!(hyperlink, Hyperlink);
2811 boolean!(hyperlink_label, HyperlinkLabel);
2812 if current.font != target.font {
2813 match &target.font {
2814 Some(font) => out.push(TextAttribute::Font(font.clone())),
2815 None if fresh => {}
2816 None => return Err(inherited()),
2817 }
2818 }
2819 if current.font_size != target.font_size {
2820 match target.font_size {
2821 Some(size) => out.push(TextAttribute::FontSize(size)),
2822 None if fresh => {}
2823 None => return Err(inherited()),
2824 }
2825 }
2826 let color =
2827 |value: u32| (value != 0xff000000).then(|| value.to_le_bytes()[..3].try_into().unwrap());
2828 if current.color != target.color {
2829 match target.color {
2830 Some(value) => out.push(TextAttribute::Color(color(value))),
2831 None if fresh => {}
2832 None => return Err(inherited()),
2833 }
2834 }
2835 if current.highlight != target.highlight {
2836 match target.highlight {
2837 Some(value) => out.push(TextAttribute::Highlight(color(value))),
2838 None if fresh => {}
2839 None => return Err(inherited()),
2840 }
2841 }
2842 if current.language != target.language {
2843 match target.language {
2844 Some(language) => out.push(TextAttribute::Language(language)),
2845 None if fresh => {}
2846 None => return Err(inherited()),
2847 }
2848 }
2849 // An absent value and its stored default are the same formatting.
2850 let flag = |a: Option<bool>, b: Option<bool>| a.unwrap_or(false) == b.unwrap_or(false);
2851 let points = |a: Option<f32>, b: Option<f32>| a.unwrap_or(0.0) == b.unwrap_or(0.0);
2852 let same_rest = flag(current.math, target.math)
2853 && flag(current.embedded_object, target.embedded_object)
2854 && current.alignment.unwrap_or(0) == target.alignment.unwrap_or(0)
2855 && flag(current.rtl, target.rtl)
2856 && points(current.space_before, target.space_before)
2857 && points(current.space_after, target.space_after)
2858 && points(current.line_spacing, target.line_spacing)
2859 && points(current.list_spacing, target.list_spacing);
2860 if !same_rest {
2861 return Err(invalid(
2862 "Fields and paragraph spacing cannot be edited through the page model",
2863 ));
2864 }
2865 Ok(out)
2866}
2867
2868/// Payload identities the file-data store of `store` declares, in order.1430/// Payload identities the file-data store of `store` declares, in order.
2869pub(crate) fn declared_payloads(store: &Store<'_>) -> Vec<[u8; 16]> {1431pub(crate) fn declared_payloads(store: &Store<'_>) -> Vec<[u8; 16]> {
2870 store1432 store
...@@ -3002,7 +1564,10 @@ fn remap(object: &mut PropertyObject, rename: &BTreeMap<ExGuid, ExGuid>) -> Resu...@@ -3002,7 +1564,10 @@ fn remap(object: &mut PropertyObject, rename: &BTreeMap<ExGuid, ExGuid>) -> Resu
3002#[cfg(test)]1564#[cfg(test)]
3003mod tests {1565mod tests {
3004 use super::*;1566 use super::*;
3005 use crate::page::{PageParagraph, Paragraph, TextObject, text::new_id};1567 use crate::{
1568 document::Format,
1569 page::{PageParagraph, Paragraph, TextObject, text::new_id},
1570 };
30061571
3007 /// A new page in a new section and the page with `count` paragraphs added in an outline.1572 /// A new page in a new section and the page with `count` paragraphs added in an outline.
3008 fn generated(count: usize) -> (Vec<u8>, ExGuid, Page) {1573 fn generated(count: usize) -> (Vec<u8>, ExGuid, Page) {
crates/onestore/src/pages.rs+367-244
...@@ -2,6 +2,7 @@ use crate::{...@@ -2,6 +2,7 @@ use crate::{
2 Error, ExGuid,2 Error, ExGuid,
3 create::{current_timestamps, properties, string},3 create::{current_timestamps, properties, string},
4 document::{Document, FieldValue, Kind},4 document::{Document, FieldValue, Kind},
5 op::content::{NATIVE_INDENTS, measurement_bytes},
5 write::{PropertyObject, RevisionEdit, fresh_guid, write_revisions},6 write::{PropertyObject, RevisionEdit, fresh_guid, write_revisions},
6};7};
7use serde::{Deserialize, Serialize};8use serde::{Deserialize, Serialize};
...@@ -129,11 +130,101 @@ impl PageEdit {...@@ -129,11 +130,101 @@ impl PageEdit {
129 }130 }
130}131}
131132
133/// What page edits read of one page space: its metadata element and the stored manifest
134/// and metadata objects.
135pub(crate) struct PageParts<'a> {
136 pub metadata: crate::document::Element<'a>,
137 pub manifest: (ExGuid, PropertyObject),
138 pub stored_metadata: (ExGuid, PropertyObject),
139}
140
132pub(crate) fn edit_pages(141pub(crate) fn edit_pages(
133 source: &[u8],142 source: &[u8],
134 edits: &[PageEdit],143 edits: &[PageEdit],
135 removals: &[ExGuid],144 removals: &[ExGuid],
136) -> Result<Vec<u8>, Error> {145) -> Result<Vec<u8>, Error> {
146 write_revisions(source, |index| {
147 let document = Document::parse(index)?;
148 let pages: Vec<ExGuid> = document.pages()?.into_iter().map(|(sid, _)| sid).collect();
149 let root = document.active(document.root)?;
150 let raw = index.resolve_active(document.root)?;
151 let parts = |sid: ExGuid| -> Result<PageParts<'_>, Error> {
152 let page = document.active(sid)?;
153 let revision = index.resolve_active(sid)?;
154 let metadata = page
155 .roots
156 .get(&2)
157 .and_then(|id| page.nodes.get(id))
158 .ok_or_else(|| invalid("Page metadata is unavailable"))?;
159 let (manifest, stored) = (revision.roots[&1], revision.roots[&2]);
160 Ok(PageParts {
161 metadata: metadata.clone(),
162 manifest: (manifest, PropertyObject::from_object(&revision.objects[&manifest])?),
163 stored_metadata: (stored, PropertyObject::from_object(&revision.objects[&stored])?),
164 })
165 };
166 let Some(changes) = page_changes(document.root, root, &raw, &pages, parts, edits, removals)? else {
167 return Ok(BTreeMap::new());
168 };
169 Ok(changes
170 .into_iter()
171 .map(|(space, changes)| (space, RevisionEdit::Update(changes)))
172 .collect())
173 })
174}
175
176/// `edit_pages` on a section kept open: the objects each changed space stores.
177pub(crate) fn section_changes(
178 section: &mut crate::Section<'_>,
179 edits: &[PageEdit],
180 removals: &[ExGuid],
181) -> Result<Vec<(ExGuid, crate::active::Changes)>, Error> {
182 let root = section.root();
183 let (view, raw) = {
184 let page = section.active(root)?;
185 (page.view.clone(), page.live.revision.clone())
186 };
187 let section_id = *view
188 .roots
189 .get(&1)
190 .ok_or_else(|| invalid("Section root is unavailable"))?;
191 let pages: Vec<ExGuid> = view.nodes[&section_id]
192 .children
193 .iter()
194 .flat_map(|series| view.nodes[series].spaces.clone())
195 .collect();
196 let section = &*section;
197 let parts = |sid: ExGuid| -> Result<PageParts<'_>, Error> {
198 let revision = section.revision(sid)?;
199 let (manifest, stored) = (revision.roots[&1], revision.roots[&2]);
200 Ok(PageParts {
201 metadata: crate::document::Element::parse_with(
202 &revision.objects[&stored],
203 crate::FileType::Section,
204 &mut |_| Err(invalid("Page metadata holds no payload")),
205 )?,
206 manifest: (manifest, PropertyObject::from_object(&revision.objects[&manifest])?),
207 stored_metadata: (stored, PropertyObject::from_object(&revision.objects[&stored])?),
208 })
209 };
210 Ok(page_changes(root, &view, &raw, &pages, parts, edits, removals)?
211 .unwrap_or_default()
212 .into_iter()
213 .filter(|(_, changes)| !changes.is_empty())
214 .collect())
215}
216
217/// The objects page moves, indentation and removals change in each space; none when the
218/// order and levels stay.
219fn page_changes<'a>(
220 root: ExGuid,
221 view: &crate::document::Revision<'_>,
222 raw: &crate::ResolvedRevision<'_>,
223 pages: &[ExGuid],
224 parts: impl Fn(ExGuid) -> Result<PageParts<'a>, Error>,
225 edits: &[PageEdit],
226 removals: &[ExGuid],
227) -> Result<Option<BTreeMap<ExGuid, crate::active::Changes>>, Error> {
137 if !edits.is_empty() && !removals.is_empty() {228 if !edits.is_empty() && !removals.is_empty() {
138 return Err(invalid(229 return Err(invalid(
139 "Choose page placement or page removal for one operation",230 "Choose page placement or page removal for one operation",
...@@ -153,252 +244,242 @@ pub(crate) fn edit_pages(...@@ -153,252 +244,242 @@ pub(crate) fn edit_pages(
153 ));244 ));
154 }245 }
155 }246 }
156 write_revisions(source, |index| {247 let section_id = view.roots[&1];
157 let document = Document::parse(index)?;248 let section_node = &view.nodes[&section_id];
158 let pages = document.pages()?;249 if !matches!(section_node.kind, Kind::Section { .. })
159 let view = document.active(document.root)?;250 || section_node.extra[0]
160 let section_id = view.roots[&1];251 .iter()
161 let section_node = &view.nodes[&section_id];252 .any(|field| field.id == 0x88001cde)
162 if !matches!(section_node.kind, Kind::Section { .. })253 {
163 || section_node.extra[0]254 return Err(invalid("Choose an editable section for page editing"));
255 }
256 let mut order = Vec::new();
257 let mut levels = BTreeMap::new();
258 let mut page_guids = BTreeMap::new();
259 let mut stored = BTreeMap::new();
260 for sid in pages {
261 let page = parts(*sid)?;
262 let Kind::Metadata { level, .. } = page.metadata.kind else {
263 return Err(invalid("Choose ordinary pages with page metadata"));
264 };
265 let level = level.unwrap_or(1);
266 if !(1..=3).contains(&level) || levels.insert(*sid, level).is_some() {
267 return Err(invalid(
268 "Each page space must contain one page with a valid level",
269 ));
270 }
271 if page_guids.insert(metadata_guid(&page.metadata)?, *sid).is_some() {
272 return Err(invalid("Pages must have distinct metadata identifiers"));
273 }
274 order.push(*sid);
275 stored.insert(*sid, page);
276 }
277 let original_order = order.clone();
278 let original_levels = levels.clone();
279 let mut series_by_head = BTreeMap::new();
280 let mut copies = BTreeMap::new();
281 let mut copy_ids = BTreeSet::new();
282 for oid in &section_node.children {
283 let series = &view.nodes[oid];
284 let Some(head) = series.spaces.first() else {
285 return Err(invalid("A page series must contain at least one page"));
286 };
287 if levels.get(head) != Some(&1)
288 || series.spaces[1..]
164 .iter()289 .iter()
165 .any(|field| field.id == 0x88001cde)290 .any(|sid| levels.get(sid).is_none_or(|level| *level == 1))
166 {291 {
167 return Err(invalid("Choose an editable section for page editing"));292 return Err(invalid(
293 "A page series must start with its only top-level page",
294 ));
168 }295 }
169 let mut order = Vec::new();296 if let Some(retained) = series.spaces.iter().find(|sid| !removed.contains(sid)) {
170 let mut levels = BTreeMap::new();297 series_by_head.insert(*retained, *oid);
171 let mut page_guids = BTreeMap::new();
172 for (sid, _) in &pages {
173 let page = document.active(*sid)?;
174 let Some(metadata) = page.roots.get(&2).and_then(|id| page.nodes.get(id)) else {
175 return Err(invalid("Page metadata is unavailable"));
176 };
177 let Kind::Metadata { level, .. } = metadata.kind else {
178 return Err(invalid("Choose ordinary pages with page metadata"));
179 };
180 let level = level.unwrap_or(1);
181 if !(1..=3).contains(&level) || levels.insert(*sid, level).is_some() {
182 return Err(invalid(
183 "Each page space must contain one page with a valid level",
184 ));
185 }
186 if page_guids.insert(metadata_guid(metadata)?, *sid).is_some() {
187 return Err(invalid("Pages must have distinct metadata identifiers"));
188 }
189 order.push(*sid);
190 }298 }
191 let original_order = order.clone();299 if let Some(field) = series.extra[0].iter().find(|field| field.id == 0x24003442) {
192 let original_levels = levels.clone();300 let FieldValue::Objects(ids) = &field.value else {
193 let mut series_by_head = BTreeMap::new();301 return Err(invalid("Page metadata copies must be object references"));
194 let mut copies = BTreeMap::new();
195 let mut copy_ids = BTreeSet::new();
196 for oid in &section_node.children {
197 let series = &view.nodes[oid];
198 let Some(head) = series.spaces.first() else {
199 return Err(invalid("A page series must contain at least one page"));
200 };302 };
201 if levels.get(head) != Some(&1)303 if ids.len() != series.spaces.len() {
202 || series.spaces[1..]304 return Err(invalid("Page metadata copies must match their series"));
203 .iter()
204 .any(|sid| levels.get(sid).is_none_or(|level| *level == 1))
205 {
206 return Err(invalid(
207 "A page series must start with its only top-level page",
208 ));
209 }
210 if let Some(retained) = series.spaces.iter().find(|sid| !removed.contains(sid)) {
211 series_by_head.insert(*retained, *oid);
212 }305 }
213 if let Some(field) = series.extra[0].iter().find(|field| field.id == 0x24003442) {306 for id in ids {
214 let FieldValue::Objects(ids) = &field.value else {307 if !copy_ids.insert(*id)
215 return Err(invalid("Page metadata copies must be object references"));308 || !matches!(
216 };309 view.nodes.get(id).map(|n| &n.kind),
217 if ids.len() != series.spaces.len() {310 Some(Kind::Metadata { .. })
218 return Err(invalid("Page metadata copies must match their series"));311 )
312 {
313 return Err(invalid("Each page needs its own ordinary metadata copy"));
219 }314 }
220 for id in ids {315 let sid = page_guids
221 if !copy_ids.insert(*id)316 .get(&metadata_guid(&view.nodes[id])?)
222 || !matches!(317 .filter(|sid| series.spaces.contains(sid))
223 view.nodes.get(id).map(|n| &n.kind),318 .ok_or_else(|| {
224 Some(Kind::Metadata { .. })319 invalid("Metadata copy must identify a page in its series")
225 )320 })?;
226 {321 if copies.insert(*sid, *id).is_some() {
227 return Err(invalid("Each page needs its own ordinary metadata copy"));322 return Err(invalid("Each page needs its own ordinary metadata copy"));
228 }
229 let sid = page_guids
230 .get(&metadata_guid(&view.nodes[id])?)
231 .filter(|sid| series.spaces.contains(sid))
232 .ok_or_else(|| {
233 invalid("Metadata copy must identify a page in its series")
234 })?;
235 if copies.insert(*sid, *id).is_some() {
236 return Err(invalid("Each page needs its own ordinary metadata copy"));
237 }
238 }323 }
239 }324 }
240 }325 }
241 for sid in &removed {326 }
242 if !levels.contains_key(sid) {327 for sid in &removed {
243 return Err(invalid("The selected page is no longer in the section"));328 if !levels.contains_key(sid) {
244 }329 return Err(invalid("The selected page is no longer in the section"));
245 }
246 order.retain(|sid| !removed.contains(sid));
247 if !removed.is_empty()
248 && let Some(first) = order.first()
249 {
250 levels.insert(*first, 1);
251 }330 }
252 for edit in edits {331 }
253 let Some(at) = order.iter().position(|sid| *sid == edit.space) else {332 order.retain(|sid| !removed.contains(sid));
254 return Err(invalid("The selected page is no longer in the section"));333 if !removed.is_empty()
334 && let Some(first) = order.first()
335 {
336 levels.insert(*first, 1);
337 }
338 for edit in edits {
339 let Some(at) = order.iter().position(|sid| *sid == edit.space) else {
340 return Err(invalid("The selected page is no longer in the section"));
341 };
342 levels.insert(edit.space, edit.level);
343 if let PagePosition::Before(before) = edit.position {
344 order.remove(at);
345 let position = match before {
346 None => order.len(),
347 Some(before) => {
348 order.iter().position(|sid| *sid == before).ok_or_else(|| {
349 invalid("The movement anchor is no longer in the section")
350 })?
351 }
255 };352 };
256 levels.insert(edit.space, edit.level);353 order.insert(position, edit.space);
257 if let PagePosition::Before(before) = edit.position {
258 order.remove(at);
259 let position = match before {
260 None => order.len(),
261 Some(before) => {
262 order.iter().position(|sid| *sid == before).ok_or_else(|| {
263 invalid("The movement anchor is no longer in the section")
264 })?
265 }
266 };
267 order.insert(position, edit.space);
268 }
269 }354 }
270 if order.first().is_some_and(|sid| levels[sid] != 1) {355 }
271 return Err(invalid(356 if order.first().is_some_and(|sid| levels[sid] != 1) {
272 "The section's first page must have indentation level 1",357 return Err(invalid(
273 ));358 "The section's first page must have indentation level 1",
359 ));
360 }
361 if order == original_order && levels == original_levels {
362 return Ok(None);
363 }
364 let mut groups: Vec<Vec<ExGuid>> = Vec::new();
365 for sid in order {
366 if levels[&sid] == 1 {
367 groups.push(Vec::new());
274 }368 }
275 if order == original_order && levels == original_levels {369 groups.last_mut().unwrap().push(sid);
276 return Ok(BTreeMap::new());370 }
371 let mut changes = BTreeMap::new();
372 for sid in &removed {
373 let page = &stored[sid];
374 let (manifest_id, mut manifest) = page.manifest.clone();
375 if manifest.jcid != 0x60037 {
376 return Err(invalid("Choose ordinary pages for removal"));
277 }377 }
278 let raw = index.resolve_active(document.root)?;378 manifest.bytes = properties(&[])?;
279 let mut groups: Vec<Vec<ExGuid>> = Vec::new();379 let (metadata_id, mut metadata) = page.stored_metadata.clone();
280 for sid in order {380 metadata.set(&[(0x88001de9, &[])])?;
281 if levels[&sid] == 1 {381 changes.insert(
282 groups.push(Vec::new());382 *sid,
383 BTreeMap::from([(manifest_id, manifest), (metadata_id, metadata)]),
384 );
385 }
386 let mut replacements = BTreeMap::new();
387 let mut children = Vec::new();
388 for spaces in groups {
389 let head = spaces[0];
390 let old_id = series_by_head.get(&head).copied();
391 let (id, mut series) = if let Some(id) = old_id {
392 (id, PropertyObject::from_object(&raw.objects[&id])?)
393 } else {
394 let edit = selected
395 .get(&head)
396 .ok_or_else(|| invalid("A new series must start at an edited page"))?;
397 let id = ExGuid {
398 guid: edit.guid,
399 n: 1,
400 };
401 if raw.objects.contains_key(&id) {
402 return Err(invalid("The new page-series identity already exists"));
283 }403 }
284 groups.last_mut().unwrap().push(sid);404 (
405 id,
406 PropertyObject {
407 jcid: 0x60008,
408 bytes: properties(&[(0x1c001c30, edit.guid.to_vec())])?,
409 global_ids: Arc::new(BTreeMap::from([(0, edit.guid)])),
410 },
411 )
412 };
413 children.push(id);
414 let membership_changed = old_id.is_none_or(|old| view.nodes[&old].spaces != spaces);
415 if !membership_changed
416 && !spaces.iter().any(|sid| {
417 levels[sid] != original_levels[sid]
418 || copies.get(sid).is_some_and(|id| {
419 matches!(view.nodes[id].kind, Kind::Metadata { level, .. }
420 if level.unwrap_or(1) != levels[sid])
421 })
422 })
423 {
424 continue;
285 }425 }
286 let mut changes = BTreeMap::new();426 let mut metadata_ids = Vec::new();
287 for sid in &removed {427 for sid in &spaces {
288 let page = index.resolve_active(*sid)?;428 let (page_id, original) = stored[sid].stored_metadata.clone();
289 let manifest_id = page.roots[&1];429 if old_id.is_none() && *sid == head {
290 let mut manifest = PropertyObject::from_object(&page.objects[&manifest_id])?;430 series.copy_property(&original, 0x18001c65)?;
291 if manifest.jcid != 0x60037 {
292 return Err(invalid("Choose ordinary pages for removal"));
293 }431 }
294 manifest.bytes = properties(&[])?;432 if levels[sid] != original_levels[sid] {
295 let metadata_id = page.roots[&2];433 let mut metadata = original.clone();
296 let mut metadata = PropertyObject::from_object(&page.objects[&metadata_id])?;434 metadata.set(&[(0x14001dff, &levels[sid].to_le_bytes())])?;
297 metadata.set(&[(0x88001de9, &[])])?;435 changes.insert(*sid, BTreeMap::from([(page_id, metadata)]));
298 changes.insert(
299 *sid,
300 RevisionEdit::Update(BTreeMap::from([
301 (manifest_id, manifest),
302 (metadata_id, metadata),
303 ])),
304 );
305 }
306 let mut replacements = BTreeMap::new();
307 let mut children = Vec::new();
308 for spaces in groups {
309 let head = spaces[0];
310 let old_id = series_by_head.get(&head).copied();
311 let (id, mut series) = if let Some(id) = old_id {
312 (id, PropertyObject::from_object(&raw.objects[&id])?)
313 } else {
314 let edit = selected
315 .get(&head)
316 .ok_or_else(|| invalid("A new series must start at an edited page"))?;
317 let id = ExGuid {
318 guid: edit.guid,
319 n: 1,
320 };
321 if raw.objects.contains_key(&id) {
322 return Err(invalid("The new page-series identity already exists"));
323 }
324 (
325 id,
326 PropertyObject {
327 jcid: 0x60008,
328 bytes: properties(&[(0x1c001c30, edit.guid.to_vec())])?,
329 global_ids: Arc::new(BTreeMap::from([(0, edit.guid)])),
330 },
331 )
332 };
333 children.push(id);
334 let membership_changed = old_id.is_none_or(|old| view.nodes[&old].spaces != spaces);
335 if !membership_changed
336 && !spaces.iter().any(|sid| {
337 levels[sid] != original_levels[sid]
338 || copies.get(sid).is_some_and(|id| {
339 matches!(view.nodes[id].kind, Kind::Metadata { level, .. }
340 if level.unwrap_or(1) != levels[sid])
341 })
342 })
343 {
344 continue;
345 }436 }
346 let mut metadata_ids = Vec::new();437 let copy_id = copies
347 for sid in &spaces {438 .get(sid)
348 let page = index.resolve_active(*sid)?;439 .copied()
349 let page_id = page.roots[&2];440 .unwrap_or_else(|| metadata_id(*sid));
350 let original = PropertyObject::from_object(&page.objects[&page_id])?;441 let mut copy = match raw.objects.get(&copy_id) {
351 if old_id.is_none() && *sid == head {442 Some(object) => {
352 series.copy_property(&original, 0x18001c65)?;443 if object.jcid != 0x20030
353 }444 || (!copies.contains_key(sid) && copy_ids.contains(&copy_id))
354 if levels[sid] != original_levels[sid] {445 {
355 let mut metadata = PropertyObject::from_object(&page.objects[&page_id])?;446 return Err(invalid("Page metadata identities overlap"));
356 metadata.set(&[(0x14001dff, &levels[sid].to_le_bytes())])?;
357 changes.insert(
358 *sid,
359 RevisionEdit::Update(BTreeMap::from([(page_id, metadata)])),
360 );
361 }
362 let copy_id = copies
363 .get(sid)
364 .copied()
365 .unwrap_or_else(|| metadata_id(*sid));
366 let mut copy = match raw.objects.get(&copy_id) {
367 Some(object) => {
368 if object.jcid != 0x20030
369 || (!copies.contains_key(sid) && copy_ids.contains(&copy_id))
370 {
371 return Err(invalid("Page metadata identities overlap"));
372 }
373 PropertyObject::from_object(object)?
374 }447 }
375 None => original,448 PropertyObject::from_object(object)?
376 };
377 copy.set(&[(0x14001dff, &levels[sid].to_le_bytes())])?;
378 copy.reference(copy_id)?;
379 if replacements.insert(copy_id, copy).is_some() {
380 return Err(invalid("Page metadata identities overlap"));
381 }
382 metadata_ids.push(copy_id);
383 }
384 if membership_changed || spaces.iter().any(|sid| !copies.contains_key(sid)) {
385 set_references(&mut series, 0x2c001d63, &spaces)?;
386 set_references(&mut series, 0x24003442, &metadata_ids)?;
387 if replacements.insert(id, series).is_some() {
388 return Err(invalid("Page-series and metadata identities overlap"));
389 }449 }
450 None => original,
451 };
452 copy.set(&[(0x14001dff, &levels[sid].to_le_bytes())])?;
453 copy.reference(copy_id)?;
454 if replacements.insert(copy_id, copy).is_some() {
455 return Err(invalid("Page metadata identities overlap"));
390 }456 }
457 metadata_ids.push(copy_id);
391 }458 }
392 if children != section_node.children {459 if membership_changed || spaces.iter().any(|sid| !copies.contains_key(sid)) {
393 let mut parent = PropertyObject::from_object(&raw.objects[&section_id])?;460 set_references(&mut series, 0x2c001d63, &spaces)?;
394 set_references(&mut parent, 0x24001c20, &children)?;461 set_references(&mut series, 0x24003442, &metadata_ids)?;
395 replacements.insert(section_id, parent);462 if replacements.insert(id, series).is_some() {
463 return Err(invalid("Page-series and metadata identities overlap"));
464 }
396 }465 }
397 changes.insert(document.root, RevisionEdit::Update(replacements));466 }
398 Ok(changes)467 if children != section_node.children {
399 })468 let mut parent = PropertyObject::from_object(&raw.objects[&section_id])?;
469 set_references(&mut parent, 0x24001c20, &children)?;
470 replacements.insert(section_id, parent);
471 }
472 changes.insert(root, replacements);
473 Ok(Some(changes))
400}474}
401475
476/// A page creation's changes to the root space, and the new space's roots and objects.
477pub(crate) type Creation = (
478 crate::active::Changes,
479 BTreeMap<u32, ExGuid>,
480 crate::active::Changes,
481);
482
402/// An empty top-level page with stable identities and creation time.483/// An empty top-level page with stable identities and creation time.
403/// Retain the intent across retries; an existing page identity rejects duplicate creation.484/// Retain the intent across retries; an existing page identity rejects duplicate creation.
404#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]485#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
...@@ -487,6 +568,38 @@ impl PageCreation {...@@ -487,6 +568,38 @@ impl PageCreation {
487 let document = Document::parse(index)?;568 let document = Document::parse(index)?;
488 document.pages()?;569 document.pages()?;
489 let view = document.active(document.root)?;570 let view = document.active(document.root)?;
571 let raw = index.resolve_active(document.root)?;
572 let (root, roots, objects) = self.creation(view, &raw)?;
573 Ok(BTreeMap::from([
574 (document.root, RevisionEdit::Update(root)),
575 (self.space(), RevisionEdit::Create { roots, objects }),
576 ]))
577 })
578 }
579
580 /// `apply` on a section kept open: the root space's changes, and the new space's roots
581 /// and objects.
582 pub(crate) fn changes(
583 &self,
584 section: &mut crate::Section<'_>,
585 ) -> Result<Creation, Error> {
586 self.validate()?;
587 if section.revision(self.space()).is_ok() {
588 return Err(invalid(
589 "This page identity already exists; reconcile the original creation",
590 ));
591 }
592 let root = section.root();
593 let page = section.active(root)?;
594 self.creation(&page.view, &page.live.revision)
595 }
596
597 fn creation(
598 &self,
599 view: &crate::document::Revision<'_>,
600 raw: &crate::ResolvedRevision<'_>,
601 ) -> Result<Creation, Error> {
602 {
490 let section_id = view.roots[&1];603 let section_id = view.roots[&1];
491 let section_node = &view.nodes[&section_id];604 let section_node = &view.nodes[&section_id];
492 if !matches!(section_node.kind, Kind::Section { .. })605 if !matches!(section_node.kind, Kind::Section { .. })
...@@ -506,7 +619,6 @@ impl PageCreation {...@@ -506,7 +619,6 @@ impl PageCreation {
506 invalid("Insert before the first page of an existing series, or append")619 invalid("Insert before the first page of an existing series, or append")
507 })?,620 })?,
508 };621 };
509 let raw = index.resolve_active(document.root)?;
510 let id = |n| ExGuid { guid: self.guid, n };622 let id = |n| ExGuid { guid: self.guid, n };
511 let reference = |n: u32| n.to_le_bytes().to_vec();623 let reference = |n: u32| n.to_le_bytes().to_vec();
512 let timestamp = ((u64::from(self.created) + 315532800 + 11644473600) * 10000000)624 let timestamp = ((u64::from(self.created) + 315532800 + 11644473600) * 10000000)
...@@ -533,11 +645,15 @@ impl PageCreation {...@@ -533,11 +645,15 @@ impl PageCreation {
533 (0x14001dff, reference(1)),645 (0x14001dff, reference(1)),
534 (0x18001c65, timestamp.clone()),646 (0x18001c65, timestamp.clone()),
535 ];647 ];
536 let mut page = vec![648 let mut page = [
537 modified(),649 vec![
538 (0x1c001d75, string(&self.author)),650 modified(),
539 (0x1c001d3c, string("")),651 (0x1c001d75, string(&self.author)),
540 ];652 (0x1c001d3c, string("")),
653 ],
654 crate::create::page_margins(),
655 ]
656 .concat();
541 if self.title.is_some() {657 if self.title.is_some() {
542 page.push((0x24001d5f, reference(13)));658 page.push((0x24001d5f, reference(13)));
543 }659 }
...@@ -556,6 +672,9 @@ impl PageCreation {...@@ -556,6 +672,9 @@ impl PageCreation {
556 (0x24001c20, reference(14)),672 (0x24001c20, reference(14)),
557 (0x14001c14, 0_f32.to_le_bytes().to_vec()),673 (0x14001c14, 0_f32.to_le_bytes().to_vec()),
558 (0x14001c15, 0_f32.to_le_bytes().to_vec()),674 (0x14001c15, 0_f32.to_le_bytes().to_vec()),
675 (0x14001c3e, reference(0x0009_000c)),
676 (0x14001c84, reference(0)),
677 (0x14001cf1, reference(0)),
559 ],678 ],
560 ),679 ),
561 (680 (
...@@ -564,7 +683,18 @@ impl PageCreation {...@@ -564,7 +683,18 @@ impl PageCreation {
564 vec![683 vec![
565 modified(),684 modified(),
566 (0x24001c20, reference(15)),685 (0x24001c20, reference(15)),
686 (0x1c001c12, measurement_bytes(&NATIVE_INDENTS, 4)?),
687 (0x0c001c13, vec![0]),
567 (0x0c001c03, vec![1]),688 (0x0c001c03, vec![1]),
689 (0x14001c1c, 0.6_f32.to_le_bytes().to_vec()),
690 (0x88001cf9, vec![]),
691 (0x88001cb2, vec![]),
692 (0x88001cb4, vec![]),
693 (0x88001c91, vec![]),
694 (0x14001cec, 4.5_f32.to_le_bytes().to_vec()),
695 (0x88001cff, vec![]),
696 (0x14001c3e, reference(0)),
697 (0x14001c84, reference(0xc)),
568 ],698 ],
569 ),699 ),
570 (700 (
...@@ -577,6 +707,7 @@ impl PageCreation {...@@ -577,6 +707,7 @@ impl PageCreation {
577 (0x20001d78, reference(17)),707 (0x20001d78, reference(17)),
578 (0x20001d79, reference(17)),708 (0x20001d79, reference(17)),
579 (0x14001d09, self.created.to_le_bytes().to_vec()),709 (0x14001d09, self.created.to_le_bytes().to_vec()),
710 (0x88001cb2, vec![]),
580 (0x88001cb4, vec![]),711 (0x88001cb4, vec![]),
581 ],712 ],
582 ),713 ),
...@@ -627,24 +758,16 @@ impl PageCreation {...@@ -627,24 +758,16 @@ impl PageCreation {
627 references.extend_from_slice(&parent.reference(child)?);758 references.extend_from_slice(&parent.reference(child)?);
628 }759 }
629 parent.set(&[(0x24001c20, &references)])?;760 parent.set(&[(0x24001c20, &references)])?;
630 Ok(BTreeMap::from([761 Ok((
631 (762 BTreeMap::from([
632 document.root,763 (section_id, parent),
633 RevisionEdit::Update(BTreeMap::from([764 (id(2), series),
634 (section_id, parent),765 (metadata_id, object(0x20030, metadata)?),
635 (id(2), series),766 ]),
636 (metadata_id, object(0x20030, metadata)?),767 BTreeMap::from([(1, id(10)), (2, id(11))]),
637 ])),768 objects,
638 ),769 ))
639 (770 }
640 self.space(),
641 RevisionEdit::Create {
642 roots: BTreeMap::from([(1, id(10)), (2, id(11))]),
643 objects,
644 },
645 ),
646 ]))
647 })
648 }771 }
649}772}
650773
crates/onestore/src/paragraph.rs+81-34
...@@ -85,6 +85,34 @@ impl ParagraphSplit {...@@ -85,6 +85,34 @@ impl ParagraphSplit {
85 }85 }
8686
87 pub(crate) fn changes(&self, active: &ActivePage<'_>) -> Result<Changes, Error> {87 pub(crate) fn changes(&self, active: &ActivePage<'_>) -> Result<Changes, Error> {
88 let lists = (5..256)
89 .map(|n| ExGuid {
90 guid: self.guid,
91 n,
92 })
93 .collect::<Vec<_>>();
94 let changes = self.changes_as(active, self.object(), self.text_object(), &lists)?;
95 let raw = &active.live.revision;
96 if changes
97 .keys()
98 .any(|id| id.guid == self.guid && raw.objects.contains_key(id))
99 {
100 return Err(invalid(
101 "A split identity already exists; reconcile the existing edit",
102 ));
103 }
104 Ok(changes)
105 }
106
107 /// `changes` creating the right paragraph `paragraph` with text `right`, and a copy of
108 /// the left paragraph's list nodes under `lists`, in order.
109 pub(crate) fn changes_as(
110 &self,
111 active: &ActivePage<'_>,
112 paragraph_id: ExGuid,
113 right_text: ExGuid,
114 list_ids: &[ExGuid],
115 ) -> Result<Changes, Error> {
88 let [_] = active.pages.as_slice() else {116 let [_] = active.pages.as_slice() else {
89 return Err(invalid("Splitting requires a single active page"));117 return Err(invalid("Splitting requires a single active page"));
90 };118 };
...@@ -136,10 +164,32 @@ impl ParagraphSplit {...@@ -136,10 +164,32 @@ impl ParagraphSplit {
136 }164 }
137 let raw = &active.live.revision;165 let raw = &active.live.revision;
138 let (text, runs) = ordinary_text(view, raw, self.text)?;166 let (text, runs) = ordinary_text(view, raw, self.text)?;
167 let resolved = view.text_runs(self.text)?;
168 let link = |at: usize| {
169 resolved
170 .get(at)
171 .is_some_and(|run| run.format.hyperlink == Some(true))
172 };
173 if let Some(after) = runs
174 .iter()
175 .position(|run| run.start < self.offset && self.offset < run.end)
176 {
177 if link(after) {
178 return Err(invalid("A split cannot divide a hyperlink"));
179 }
180 } else if let Some(after) = runs
181 .iter()
182 .position(|run| run.start == self.offset && self.offset > 0)
183 && link(after - 1)
184 && link(after)
185 && !resolved[after].text.starts_with('\u{fddf}')
186 {
187 return Err(invalid("A split cannot divide a hyperlink"));
188 }
139 let node = &view.nodes[&self.text];189 let node = &view.nodes[&self.text];
140 let length = u32::try_from(text.encode_utf16().count())190 let length = u32::try_from(text.encode_utf16().count())
141 .map_err(|_| invalid("Paragraph exceeds the UTF-16 offset range"))?;191 .map_err(|_| invalid("Paragraph exceeds the UTF-16 offset range"))?;
142 if self.offset > length || lists.len() > 251 {192 if self.offset > length || lists.len() > 251 || lists.len() > list_ids.len() {
143 return Err(invalid(193 return Err(invalid(
144 "Choose a position within the paragraph and at most 251 list levels",194 "Choose a position within the paragraph and at most 251 list levels",
145 ));195 ));
...@@ -153,9 +203,14 @@ impl ParagraphSplit {...@@ -153,9 +203,14 @@ impl ParagraphSplit {
153 n: 4,203 n: 4,
154 };204 };
155 let typing = runs.last().and_then(|run| run.format).unwrap_or(normal_id);205 let typing = runs.last().and_then(|run| run.format).unwrap_or(normal_id);
206 // Enter at the start leaves the emptied upper paragraph the first run's style, as
207 // OneNote does (`evidence/structural-edits/xml/c2s-*`).
208 let leading = runs.first().and_then(|run| run.format);
156 let modified = current_timestamps()?.0.to_le_bytes();209 let modified = current_timestamps()?.0.to_le_bytes();
157 let mut changed = BTreeMap::new();210 let mut changed = BTreeMap::new();
158 if (self.offset == 0 && !text.is_empty()) || runs.iter().any(|run| run.format.is_none()) {211 if (self.offset == 0 && !text.is_empty() && leading.is_none())
212 || runs.iter().any(|run| run.format.is_none())
213 {
159 changed.insert(214 changed.insert(
160 normal_id,215 normal_id,
161 PropertyObject {216 PropertyObject {
...@@ -169,15 +224,22 @@ impl ParagraphSplit {...@@ -169,15 +224,22 @@ impl ParagraphSplit {
169 &raw.objects[&self.text],224 &raw.objects[&self.text],
170 node,225 node,
171 0..self.offset,226 0..self.offset,
172 if text.is_empty() { typing } else { normal_id },227 if text.is_empty() {
228 typing
229 } else if self.offset == 0 {
230 leading.unwrap_or(normal_id)
231 } else {
232 normal_id
233 },
173 )?;234 )?;
174 let mut suffix = fragment(&raw.objects[&self.text], node, self.offset..length, typing)?;235 let mut suffix = fragment(&raw.objects[&self.text], node, self.offset..length, typing)?;
175 prefix.set(&[(0x14001d7a, &modified)])?;236 prefix.set(&[(0x14001d7a, &modified)])?;
176 suffix.remove(&[0x40003489])?;237 // Note tags and the recording link stay with the original text.
238 suffix.remove(&[0x40003489, 0x1c001c98, 0x14001c99])?;
177 suffix.set(&[(0x14001d7a, &modified)])?;239 suffix.set(&[(0x14001d7a, &modified)])?;
178 suffix.reference(self.text_object())?;240 suffix.reference(right_text)?;
179 changed.insert(self.text, prefix);241 changed.insert(self.text, prefix);
180 changed.insert(self.text_object(), suffix);242 changed.insert(right_text, suffix);
181 changed.insert(243 changed.insert(
182 author_id,244 author_id,
183 PropertyObject {245 PropertyObject {
...@@ -187,7 +249,8 @@ impl ParagraphSplit {...@@ -187,7 +249,8 @@ impl ParagraphSplit {
187 },249 },
188 );250 );
189 let mut right = PropertyObject::from_object(&raw.objects[paragraph])?;251 let mut right = PropertyObject::from_object(&raw.objects[paragraph])?;
190 let content = right.reference(self.text_object())?;252 right.reference(paragraph_id)?;
253 let content = right.reference(right_text)?;
191 let author = right.reference(author_id)?;254 let author = right.reference(author_id)?;
192 right.set(&[255 right.set(&[
193 (0x24001c1f, &content),256 (0x24001c1f, &content),
...@@ -202,10 +265,7 @@ impl ParagraphSplit {...@@ -202,10 +265,7 @@ impl ParagraphSplit {
202 if !matches!(view.nodes[old].kind, Kind::List { .. }) {265 if !matches!(view.nodes[old].kind, Kind::List { .. }) {
203 return Err(invalid("The paragraph list is unavailable"));266 return Err(invalid("The paragraph list is unavailable"));
204 }267 }
205 let id = ExGuid {268 let id = list_ids[i];
206 guid: self.guid,
207 n: 5 + u32::try_from(i).unwrap(),
208 };
209 let mut list = PropertyObject::from_object(&raw.objects[old])?;269 let mut list = PropertyObject::from_object(&raw.objects[old])?;
210 list.remove(&[0x14001cb7])?;270 list.remove(&[0x14001cb7])?;
211 list.reference(id)?;271 list.reference(id)?;
...@@ -214,7 +274,7 @@ impl ParagraphSplit {...@@ -214,7 +274,7 @@ impl ParagraphSplit {
214 }274 }
215 right.set(&[(0x24001c26, &references)])?;275 right.set(&[(0x24001c26, &references)])?;
216 }276 }
217 changed.insert(self.object(), right);277 changed.insert(paragraph_id, right);
218 let mut original = PropertyObject::from_object(&raw.objects[paragraph])?;278 let mut original = PropertyObject::from_object(&raw.objects[paragraph])?;
219 original.remove(&[0x24001c20])?;279 original.remove(&[0x24001c20])?;
220 let author = original.reference(author_id)?;280 let author = original.reference(author_id)?;
...@@ -225,19 +285,11 @@ impl ParagraphSplit {...@@ -225,19 +285,11 @@ impl ParagraphSplit {
225 for child in &view.nodes[parent].children {285 for child in &view.nodes[parent].children {
226 children.extend_from_slice(&parent_object.reference(*child)?);286 children.extend_from_slice(&parent_object.reference(*child)?);
227 if child == paragraph {287 if child == paragraph {
228 children.extend_from_slice(&parent_object.reference(self.object())?);288 children.extend_from_slice(&parent_object.reference(paragraph_id)?);
229 }289 }
230 }290 }
231 parent_object.set(&[(0x24001c20, &children)])?;291 parent_object.set(&[(0x24001c20, &children)])?;
232 changed.insert(*parent, parent_object);292 changed.insert(*parent, parent_object);
233 if changed
234 .keys()
235 .any(|id| id.guid == self.guid && raw.objects.contains_key(id))
236 {
237 return Err(invalid(
238 "A split identity already exists; reconcile the existing edit",
239 ));
240 }
241 for id in ancestors {293 for id in ancestors {
242 let object = match changed.entry(id) {294 let object = match changed.entry(id) {
243 std::collections::btree_map::Entry::Occupied(entry) => entry.into_mut(),295 std::collections::btree_map::Entry::Occupied(entry) => entry.into_mut(),
...@@ -311,6 +363,9 @@ fn fragment(...@@ -311,6 +363,9 @@ fn fragment(
311 Ok(object)363 Ok(object)
312}364}
313365
366/// A text object's text and runs, unless it holds what a split or join cannot divide or
367/// merge: generated title text, equations, embedded objects or per-run data. Hyperlink
368/// fields are ordinary runs; a recording link belongs to the text object.
314fn ordinary_text<'a>(369fn ordinary_text<'a>(
315 view: &'a crate::document::Revision<'_>,370 view: &'a crate::document::Revision<'_>,
316 raw: &crate::ResolvedRevision<'_>,371 raw: &crate::ResolvedRevision<'_>,
...@@ -326,23 +381,15 @@ fn ordinary_text<'a>(...@@ -326,23 +381,15 @@ fn ordinary_text<'a>(
326 else {381 else {
327 return Err(invalid("Select ordinary paragraph text"));382 return Err(invalid("Select ordinary paragraph text"));
328 };383 };
329 if *boilerplate || !node.media_ids.is_empty() || node.media_time_ms.is_some() {384 if *boilerplate {
330 return Err(invalid(385 return Err(invalid("Generated text cannot be split or joined"));
331 "Generated or recording-linked text cannot be split or joined",
332 ));
333 }386 }
334 for run in view.text_runs(id)? {387 for run in view.text_runs(id)? {
335 if [388 if [run.format.math, run.format.embedded_object].contains(&Some(true))
336 run.format.hidden,389 || run.text.contains('\u{fffc}')
337 run.format.hyperlink,
338 run.format.math,
339 run.format.embedded_object,
340 ]
341 .contains(&Some(true))
342 || run.text.contains(['\u{fffc}', '\u{fddf}'])
343 {390 {
344 return Err(invalid(391 return Err(invalid(
345 "This paragraph contains a field or embedded object that cannot be split or joined",392 "This paragraph contains an equation or embedded object that cannot be split or joined",
346 ));393 ));
347 }394 }
348 }395 }
crates/onestore/src/section.rs created+1235
...@@ -0,0 +1,1235 @@
1//! A section file kept parsed across edits. Edits apply to its object spaces in memory and
2//! seal into one transaction appending one revision per changed space.
3
4use crate::{
5 Chunk, Error, ExGuid, FileType, ObjectData, Reference, ResolvedRevision, RevisionIndex, Stamp,
6 Store, Transaction,
7 active::{ActivePage, Changes, Files},
8 document::Revision,
9 page::Page,
10 store::StoreState,
11 write::{
12 Appending, LiveRevision, Sealing, Written, chain_depth, check_transaction, declared,
13 replacement, unchanged,
14 },
15};
16use bumpalo::Bump;
17use std::collections::{BTreeMap, BTreeSet};
18
19type Result<T> = std::result::Result<T, Error>;
20
21/// Holds the bytes a `Section` reads: the image it opened and each transaction it sealed.
22#[derive(Default)]
23pub struct Arena(Bump);
24
25/// A section file as its image and the transactions sealed on it leave it.
26pub struct Section<'a> {
27 arena: &'a Arena,
28 /// The opened image, then the bytes each sealed transaction appended, at their offsets.
29 segments: Vec<(u64, &'a [u8])>,
30 /// The in-place writes of the sealed transactions, in order.
31 patches: Vec<(u64, Vec<u8>)>,
32 state: StoreState,
33 /// Payloads the opened image embeds, by identity.
34 files: &'a [([u8; 16], Chunk)],
35 /// Payload identities the file-data store declares.
36 declared: BTreeSet<[u8; 16]>,
37 spaces: BTreeMap<ExGuid, Space<'a>>,
38 /// Payloads applied changes embed, in order, until sealed.
39 payloads: Vec<([u8; 16], &'a [u8])>,
40 /// Whether an operation failed after it began changing state; the section must be
41 /// reopened.
42 broken: bool,
43 /// The root object space, which lists the pages.
44 root: ExGuid,
45 /// While an edit applies, the spaces it changed as they were before it.
46 undo: Option<Undo<'a>>,
47}
48
49#[derive(Default)]
50struct Undo<'a> {
51 /// `None` for a space the edit created.
52 spaces: BTreeMap<ExGuid, Option<Space<'a>>>,
53 payloads: usize,
54}
55
56#[derive(Clone)]
57struct Space<'a> {
58 /// The active revision the file stores; none before a new space's first seal.
59 rid: Option<ExGuid>,
60 state: SpaceState<'a>,
61}
62
63#[derive(Clone)]
64enum SpaceState<'a> {
65 /// As the opened image stores it, until first edited.
66 Stored {
67 revision: ResolvedRevision<'a>,
68 depth: usize,
69 },
70 Open(Box<Open<'a>>),
71}
72
73#[derive(Clone)]
74struct Open<'a> {
75 /// The revision the file stores.
76 file: LiveRevision<'a>,
77 /// `file` with the changes applied since the last seal.
78 page: ActivePage<'a>,
79 /// Objects whose content, reachability or count changed since the last seal.
80 pending: BTreeSet<ExGuid>,
81}
82
83impl<'a> Section<'a> {
84 /// Parses and fully validates a section image.
85 pub fn open(arena: &'a Arena, image: Vec<u8>) -> Result<Self> {
86 let bytes: &'a [u8] = arena.0.alloc_slice_copy(&image);
87 drop(image);
88 let store = Store::parse(bytes)?;
89 if let Some(offset) = store.checksum_mismatches.first() {
90 return Err(Error {
91 offset: *offset,
92 message: "Cannot write a file with transaction checksum damage",
93 });
94 }
95 if store.header.file_type != FileType::Section {
96 return Err(Error {
97 offset: 0,
98 message: "Choose a section file",
99 });
100 }
101 let index = RevisionIndex::parse(&store)?;
102 index.validate_current()?;
103 let mut spaces = BTreeMap::new();
104 for (id, space) in &index.spaces {
105 let Some(rid) = space.labels.get(&(ExGuid::default(), 1)).copied() else {
106 continue;
107 };
108 let resolved = index.resolve(*id, rid)?;
109 let revision = ResolvedRevision {
110 roots: resolved.roots,
111 objects: resolved
112 .objects
113 .into_iter()
114 .map(|(id, object)| {
115 // The parse borrows `bytes` only as long as `store`; its slices lie
116 // in `bytes`, which the arena keeps.
117 let data = match object.data {
118 ObjectData::Properties(data) => {
119 ObjectData::Properties(within(bytes, data))
120 }
121 ObjectData::Encrypted(data) => {
122 ObjectData::Encrypted(within(bytes, data))
123 }
124 ObjectData::File {
125 reference,
126 extension,
127 } => ObjectData::File {
128 reference: within(bytes, reference),
129 extension: within(bytes, extension),
130 },
131 };
132 let object = crate::Object {
133 jcid: object.jcid,
134 reference_count: object.reference_count,
135 data,
136 global_ids: object.global_ids,
137 };
138 (id, object)
139 })
140 .collect(),
141 };
142 spaces.insert(
143 *id,
144 Space {
145 rid: Some(rid),
146 state: SpaceState::Stored {
147 revision,
148 depth: chain_depth(&index, *id, rid),
149 },
150 },
151 );
152 }
153 let mut files = Vec::new();
154 for node in store.lists.values().flat_map(|list| &list.nodes) {
155 if node.id != 0x94 {
156 continue;
157 }
158 let (Some(guid), Some(Reference::Data(chunk))) =
159 (node.payload.first_chunk(), node.reference)
160 else {
161 return Err(Error {
162 offset: node.offset,
163 message: "File-data object lacks a data reference",
164 });
165 };
166 files.push((*guid, chunk));
167 }
168 files.sort_unstable_by_key(|(guid, _)| *guid);
169 Ok(Self {
170 arena,
171 segments: vec![(0, bytes)],
172 patches: Vec::new(),
173 state: store.state()?,
174 declared: files.iter().map(|(guid, _)| *guid).collect(),
175 files: arena.0.alloc_slice_copy(&files),
176 spaces,
177 payloads: Vec::new(),
178 broken: false,
179 root: index.root,
180 undo: None,
181 })
182 }
183
184 /// The header and length of the image this section's sealed transactions leave.
185 pub fn stamp(&self) -> &Stamp {
186 &self.state.stamp
187 }
188
189 /// The image this section's sealed transactions leave; for tests and seeding caches.
190 pub fn image(&self) -> Vec<u8> {
191 let mut image = self
192 .segments
193 .iter()
194 .flat_map(|(_, bytes)| *bytes)
195 .copied()
196 .collect::<Vec<_>>();
197 for (offset, bytes) in &self.patches {
198 let offset = *offset as usize;
199 image[offset..offset + bytes.len()].copy_from_slice(bytes);
200 }
201 image[..1024].copy_from_slice(&self.state.stamp.header);
202 image
203 }
204
205 /// The page an object space holds, with the changes applied since the last seal.
206 pub fn page(&self, space: ExGuid) -> Result<Page> {
207 let stored = self.spaces.get(&space).ok_or(Error {
208 offset: 0,
209 message: "Object space has no active default revision",
210 })?;
211 match &stored.state {
212 SpaceState::Open(open) => one_page(&open.page.view, &open.page.pages),
213 SpaceState::Stored { revision, .. } => {
214 let files = self.files();
215 let (view, _) =
216 Revision::parse(space, revision, FileType::Section, &mut |guid| files(guid))?;
217 one_page(&view, &crate::active::manifest_pages(&view))
218 }
219 }
220 }
221
222 /// Page spaces in section order with the title and outline level (1 at the top) the
223 /// page list shows, read from each page's metadata alone.
224 pub fn pages(&mut self) -> Result<Vec<(ExGuid, String, u32)>> {
225 let root = self.root;
226 let view = &self.active(root)?.view;
227 let section = view.roots.get(&1).and_then(|id| view.nodes.get(id)).ok_or(Error {
228 offset: 0,
229 message: "Section root is unavailable",
230 })?;
231 let spaces: Vec<ExGuid> = section
232 .children
233 .iter()
234 .filter_map(|series| view.nodes.get(series))
235 .flat_map(|series| series.spaces.clone())
236 .collect();
237 let element = |object: &crate::Object<'a>| {
238 crate::document::Element::parse_with(object, FileType::Section, &mut |_| {
239 Err(Error {
240 offset: 0,
241 message: "Page metadata holds no payload",
242 })
243 })
244 };
245 let mut pages = Vec::new();
246 for space in spaces {
247 let revision = self.revision(space)?;
248 let (mut title, mut level) = (None, 1);
249 if let Some(metadata) = revision.roots.get(&2).and_then(|id| revision.objects.get(id))
250 && let crate::document::Kind::Metadata {
251 title: stored,
252 level: stored_level,
253 } = element(metadata)?.kind
254 {
255 title = stored;
256 level = stored_level.unwrap_or(1);
257 }
258 if title.is_none() {
259 let manifest = revision.roots.get(&1).and_then(|id| revision.objects.get(id));
260 if let Some(manifest) = manifest
261 && let Some(page) = element(manifest)?
262 .content
263 .first()
264 .and_then(|id| revision.objects.get(id))
265 && let crate::document::Kind::Page {
266 alternate_title, ..
267 } = element(page)?.kind
268 {
269 title = alternate_title;
270 }
271 }
272 pages.push((space, title.unwrap_or_default(), level));
273 }
274 Ok(pages)
275 }
276
277 /// Reads a payload of the opened image by identity.
278 fn files(&self) -> Files<'a> {
279 let (bytes, files) = (self.segments[0].1, self.files);
280 std::rc::Rc::new(move |guid| {
281 let start = files.partition_point(|(id, _)| *id < guid);
282 match files[start..]
283 .iter()
284 .take_while(|(id, _)| *id == guid)
285 .count()
286 {
287 0 => Err(Error {
288 offset: 0,
289 message: "File-data object is not declared",
290 }),
291 1 => {
292 let chunk = files[start].1;
293 let (offset, length) = (chunk.offset as usize, chunk.length as usize);
294 let blob = bytes.get(offset..offset + length).ok_or(Error {
295 offset,
296 message: "Chunk extends outside the data area",
297 })?;
298 crate::files::payload(blob, offset)
299 }
300 _ => Err(Error {
301 offset: 0,
302 message: "Duplicate file-data identity",
303 }),
304 }
305 })
306 }
307
308 fn usable(&self) -> Result<()> {
309 if self.broken {
310 return Err(Error {
311 offset: 0,
312 message: "A failed edit left the section to be reopened",
313 });
314 }
315 Ok(())
316 }
317
318 /// The editable state of an object space, opened on first use.
319 pub(crate) fn active(&mut self, space: ExGuid) -> Result<&ActivePage<'a>> {
320 Ok(&self.editable(space)?.page)
321 }
322
323 fn editable(&mut self, id: ExGuid) -> Result<&mut Open<'a>> {
324 let files = self.files();
325 let space = self.spaces.get_mut(&id).ok_or(Error {
326 offset: 0,
327 message: "Object space has no active default revision",
328 })?;
329 if let SpaceState::Stored { revision, depth } = &space.state {
330 let file = LiveRevision::new(revision.clone(), *depth)?;
331 let page = ActivePage::open(id, file.clone(), FileType::Section, files)?;
332 space.state = SpaceState::Open(Box::new(Open {
333 file,
334 page,
335 pending: BTreeSet::new(),
336 }));
337 }
338 let SpaceState::Open(open) = &mut space.state else {
339 unreachable!()
340 };
341 Ok(open)
342 }
343
344 /// Stores a writer's objects and payloads in a space until the next seal; false when
345 /// it stores nothing. An error leaves the section to be reopened.
346 pub(crate) fn apply_changes(
347 &mut self,
348 space: ExGuid,
349 payloads: &[([u8; 16], &[u8])],
350 changes: Changes,
351 ) -> Result<bool> {
352 self.usable()?;
353 if let Some(undo) = &mut self.undo
354 && !undo.spaces.contains_key(&space)
355 {
356 undo.spaces.insert(space, self.spaces.get(&space).cloned());
357 }
358 let arena = &self.arena.0;
359 self.broken = true;
360 let open = self.editable(space)?;
361 let embedded = open.page.payloads.len();
362 let Some(touched) = open.page.store(arena, payloads, changes)? else {
363 self.broken = false;
364 return Ok(false);
365 };
366 open.pending.extend(touched);
367 let embedded = open.page.payloads[embedded..].to_vec();
368 self.payloads.extend(embedded);
369 self.broken = false;
370 Ok(true)
371 }
372
373 /// Creates an object space holding `changes` under `roots` until the next seal, which
374 /// declares it. An error after validation leaves the section to be reopened.
375 pub(crate) fn create_space(
376 &mut self,
377 space: ExGuid,
378 roots: BTreeMap<u32, ExGuid>,
379 changes: Changes,
380 ) -> Result<()> {
381 self.usable()?;
382 if space.guid == [0; 16] || self.spaces.contains_key(&space) {
383 return Err(Error {
384 offset: 0,
385 message: "Choose a new object-space identity in a section file",
386 });
387 }
388 if roots.is_empty() || changes.is_empty() {
389 return Err(Error {
390 offset: 0,
391 message: "New object space needs roots and objects",
392 });
393 }
394 let file = LiveRevision::new(
395 ResolvedRevision {
396 roots,
397 objects: BTreeMap::new(),
398 },
399 0,
400 )?;
401 let mut work = file.clone();
402 let changes = work.prepare(changes, true)?;
403 let mut objects = Vec::new();
404 for (id, change) in changes {
405 let bytes = self.arena.0.alloc_slice_copy(&change.bytes);
406 objects.push((id, declared(change.jcid, bytes, change.global_ids)?));
407 }
408 if let Some(undo) = &mut self.undo {
409 undo.spaces.entry(space).or_insert(None);
410 }
411 self.broken = true;
412 let pending = work.commit(objects)?.touched;
413 let all: Vec<ExGuid> = work.revision.objects.keys().copied().collect();
414 work.settle(all)?;
415 let page = ActivePage::open(space, work, FileType::Section, self.files())?;
416 self.spaces.insert(
417 space,
418 Space {
419 rid: None,
420 state: SpaceState::Open(Box::new(Open {
421 file,
422 page,
423 pending,
424 })),
425 },
426 );
427 self.broken = false;
428 Ok(())
429 }
430
431 /// The root object space, which lists the pages.
432 pub(crate) fn root(&self) -> ExGuid {
433 self.root
434 }
435
436 /// The stored revision of a space, as edits since the last seal leave it.
437 pub(crate) fn revision(&self, space: ExGuid) -> Result<&ResolvedRevision<'a>> {
438 match self.spaces.get(&space).map(|space| &space.state) {
439 Some(SpaceState::Stored { revision, .. }) => Ok(revision),
440 Some(SpaceState::Open(open)) => Ok(&open.page.live.revision),
441 None => Err(Error {
442 offset: 0,
443 message: "Object space has no active default revision",
444 }),
445 }
446 }
447
448 /// Runs `f`, returning the section to its state before it when `f` fails; `undo`
449 /// saves each space before `f` first changes it, which costs a copy of that space.
450 pub(crate) fn atomically<T, E>(
451 &mut self,
452 undo: bool,
453 f: impl FnOnce(&mut Self) -> std::result::Result<T, E>,
454 ) -> std::result::Result<T, E> {
455 if undo {
456 self.undo = Some(Undo {
457 spaces: BTreeMap::new(),
458 payloads: self.payloads.len(),
459 });
460 }
461 let result = f(self);
462 if let Some(undo) = self.undo.take()
463 && result.is_err()
464 {
465 for (id, space) in undo.spaces {
466 match space {
467 Some(space) => self.spaces.insert(id, space),
468 None => self.spaces.remove(&id),
469 };
470 }
471 self.payloads.truncate(undo.payloads);
472 self.broken = false;
473 }
474 result
475 }
476
477 /// Whether a failed operation left the section to be reopened.
478 pub(crate) fn broken(&self) -> bool {
479 self.broken
480 }
481
482 /// Appends one revision per space changed since the last seal, and the payloads they
483 /// embed, as one transaction on `stamp`; none when nothing changed. An error leaves the
484 /// section to be reopened.
485 pub fn seal(&mut self) -> Result<Option<Transaction>> {
486 self.usable()?;
487 self.broken = true;
488 let arena = &self.arena.0;
489 let mut appending = Appending::new(self.state.clone());
490 let mut sealed = Vec::new();
491 for (id, space) in &mut self.spaces {
492 let SpaceState::Open(open) = &mut space.state else {
493 continue;
494 };
495 if open.pending.is_empty() {
496 continue;
497 }
498 let (work, file) = (&open.page.live, &mut open.file);
499 let mut replacements = BTreeMap::new();
500 for object_id in &open.pending {
501 if !work.is_reachable(*object_id) {
502 continue;
503 }
504 let object = &work.revision.objects[object_id];
505 if let Some(stored) = file.revision.objects.get(object_id)
506 && unchanged(stored, object)?
507 {
508 continue;
509 }
510 replacements.insert(*object_id, replacement(*object_id, object)?);
511 }
512 let replacements = file.prepare(replacements, true)?;
513 if replacements.is_empty() {
514 sealed.push((*id, None));
515 continue;
516 }
517 let replaced: BTreeSet<ExGuid> = replacements.keys().copied().collect();
518 let created = replaced
519 .iter()
520 .filter(|id| !file.revision.objects.contains_key(id))
521 .copied()
522 .collect();
523 let mut objects = Vec::new();
524 for (object_id, change) in replacements {
525 // The page holds these bytes already unless preparing remapped them.
526 let bytes = match work.revision.objects.get(&object_id).map(|o| o.data) {
527 Some(ObjectData::Properties(bytes)) if bytes == change.bytes => bytes,
528 _ => arena.alloc_slice_copy(&change.bytes),
529 };
530 objects.push((object_id, declared(change.jcid, bytes, change.global_ids)?));
531 }
532 let commit = file.commit(objects)?;
533 let (rid, stored) = appending.revision(
534 &Sealing {
535 space: *id,
536 previous: space.rid,
537 new_space: space.rid.is_none(),
538 label: (ExGuid::default(), 1),
539 live: file,
540 commit: &commit,
541 replaced: &replaced,
542 created: &created,
543 },
544 &self.segments,
545 None,
546 None,
547 )?;
548 if commit.checkpoint {
549 let all: Vec<ExGuid> = file.revision.objects.keys().copied().collect();
550 file.settle(all)?;
551 } else {
552 file.settle(commit.changed.iter().copied())?;
553 }
554 sealed.push((*id, Some((rid, stored, commit))));
555 }
556 let payloads: Vec<_> = self
557 .payloads
558 .iter()
559 .filter(|(guid, _)| !self.declared.contains(guid))
560 .copied()
561 .collect();
562 appending.payloads(&payloads, None)?;
563 #[cfg_attr(not(test), expect(unused_mut))]
564 let mut transaction = appending.finish()?;
565 #[cfg(test)]
566 if let (Some(tamper), Some((transaction, _))) = (tests::TAMPER.get(), &mut transaction) {
567 tamper(transaction);
568 }
569 if let Some((transaction, state)) = &transaction {
570 let mut written = Vec::new();
571 for (id, revision) in &sealed {
572 let (Some((rid, _, commit)), Some(space)) = (revision, self.spaces.get(id)) else {
573 continue;
574 };
575 let SpaceState::Open(open) = &space.state else {
576 unreachable!()
577 };
578 written.push(Written {
579 space: *id,
580 rid: *rid,
581 previous: space.rid,
582 live: &open.file,
583 commit,
584 });
585 }
586 check_transaction(
587 &self.state,
588 state,
589 transaction,
590 &self.segments,
591 &written,
592 &payloads,
593 )?;
594 let base = transaction.base.length;
595 let segment: &'a [u8] = arena.alloc_slice_copy(&transaction.append);
596 self.segments.push((base, segment));
597 self.patches.extend(transaction.patches.iter().cloned());
598 self.state = state.clone();
599 self.declared.extend(payloads.iter().map(|(guid, _)| *guid));
600 for (id, revision) in &sealed {
601 let Some((rid, stored, _)) = revision else {
602 continue;
603 };
604 let space = self.spaces.get_mut(id).unwrap();
605 space.rid = Some(*rid);
606 let SpaceState::Open(open) = &mut space.state else {
607 unreachable!()
608 };
609 for (object_id, chunk) in stored {
610 let start = (chunk.offset - base) as usize;
611 open.file.revision.objects.get_mut(object_id).unwrap().data =
612 ObjectData::Properties(&segment[start..start + chunk.length as usize]);
613 }
614 }
615 }
616 for (id, revision) in sealed {
617 let SpaceState::Open(open) = &mut self.spaces.get_mut(&id).unwrap().state else {
618 unreachable!()
619 };
620 let mut compare = std::mem::take(&mut open.pending);
621 if let Some((_, _, commit)) = revision {
622 if commit.checkpoint {
623 compare.extend(open.file.revision.objects.keys());
624 } else {
625 compare.extend(commit.touched);
626 }
627 }
628 open.page.adopt(&open.file, compare)?;
629 }
630 self.payloads.clear();
631 self.broken = false;
632 Ok(transaction.map(|(transaction, _)| transaction))
633 }
634
635 /// Applies a transaction sealed on this section's stamp, as reopening the image it
636 /// leaves would; nothing may be applied since the last seal.
637 pub fn replay(&mut self, transaction: &Transaction) -> Result<()> {
638 self.usable()?;
639 if !self.payloads.is_empty()
640 || self.spaces.values().any(
641 |space| matches!(&space.state, SpaceState::Open(open) if !open.pending.is_empty()),
642 )
643 {
644 return Err(Error {
645 offset: 0,
646 message: "Seal applied changes before replaying a transaction",
647 });
648 }
649 let mut image = self.image();
650 transaction.apply(&mut image)?;
651 *self = Self::open(self.arena, image)?;
652 Ok(())
653 }
654}
655
656/// `part`, a slice of `image`, with the image's lifetime.
657fn within<'a>(image: &'a [u8], part: &[u8]) -> &'a [u8] {
658 let start = part.as_ptr().addr() - image.as_ptr().addr();
659 &image[start..start + part.len()]
660}
661
662fn one_page(view: &Revision<'_>, pages: &[ExGuid]) -> Result<Page> {
663 let [page] = pages else {
664 return Err(Error {
665 offset: 0,
666 message: "Choose an object space containing one active page",
667 });
668 };
669 Page::from_revision(view, *page)
670}
671
672#[cfg(test)]
673mod tests {
674 use super::*;
675 use crate::{Insertion, active::tests::Writes, document::Document, write::GUIDS};
676
677 thread_local! {
678 /// Changes a transaction after it is built and before it is checked.
679 pub(super) static TAMPER: std::cell::Cell<Option<fn(&mut Transaction)>> =
680 const { std::cell::Cell::new(None) };
681 }
682
683 /// `f` with `fresh_guid` counting from `seed`.
684 fn seeded<T>(seed: u64, f: impl FnOnce() -> T) -> T {
685 let outer = GUIDS.replace(Some(seed));
686 let result = f();
687 GUIDS.set(outer);
688 result
689 }
690
691 /// The page spaces of an image, fullest first.
692 fn page_spaces(image: &[u8]) -> Vec<ExGuid> {
693 let store = Store::parse(image).unwrap();
694 let index = RevisionIndex::parse(&store).unwrap();
695 let document = Document::parse(&index).unwrap();
696 let mut spaces: Vec<_> = document
697 .pages()
698 .unwrap()
699 .into_iter()
700 .map(|(space, _)| (document.active(space).unwrap().nodes.len(), space))
701 .collect();
702 spaces.sort_by(|a, b| b.cmp(a));
703 spaces.into_iter().map(|(_, space)| space).collect()
704 }
705
706 /// Applies seeded batches of typed-writer changes to the pages of `source` through a
707 /// `Section` and through the writer that parses the image and squashes a batch into
708 /// one revision, requiring equal bytes after every seal.
709 fn differential(
710 source: &[u8],
711 seed: u64,
712 batches: usize,
713 largest: usize,
714 pages: usize,
715 ) -> usize {
716 let arena = Arena::default();
717 let mut section = Section::open(&arena, source.to_vec()).unwrap();
718 let spaces = page_spaces(source);
719 let mut image = source.to_vec();
720 let mut writes = Writes(seed);
721 // New objects count from far above the identities builds draw.
722 GUIDS.set(Some(1 << 62 | seed << 40));
723 let (mut sealed, mut checkpoints) = (0, 0);
724 for batch in 0..batches {
725 let space = spaces[batch % spaces.len().min(pages)];
726 let store = Store::parse(&image).unwrap();
727 let index = RevisionIndex::parse(&store).unwrap();
728 let bump = Bump::new();
729 let mut legacy = ActivePage::parse(&index, space).unwrap();
730 for _ in 0..1 + (writes.0 as usize >> 40) % largest {
731 let Ok(changes) = writes.next(section.active(space).unwrap()) else {
732 continue;
733 };
734 let stored = legacy.write(&bump, &[], changes.clone()).unwrap();
735 assert_eq!(
736 section.apply_changes(space, &[], changes).unwrap(),
737 stored,
738 "batch {batch}"
739 );
740 }
741 let guids = (batch as u64 + 1) << 32;
742 let expected = seeded(guids, || {
743 crate::page::write::squash(
744 &index,
745 &[(space, &legacy.live.revision)],
746 &legacy.payloads,
747 &BTreeMap::new(),
748 None,
749 )
750 .unwrap()
751 });
752 let before = section.stamp().clone();
753 let chains = depths(&section);
754 let transaction = seeded(guids, || section.seal().unwrap());
755 checkpoints += depths(&section)
756 .iter()
757 .filter(|(id, depth)| chains.get(id).is_some_and(|before| before > depth))
758 .count();
759 let mut written = image.clone();
760 if let Some(transaction) = &transaction {
761 assert_eq!(transaction.base, before);
762 transaction.apply(&mut written).unwrap();
763 sealed += 1;
764 }
765 assert!(written == expected, "batch {batch}");
766 assert_eq!(Stamp::of(&written).unwrap(), *section.stamp());
767 drop(legacy);
768 drop(index);
769 drop(store);
770 image = expected;
771 if batch % 25 == 0 || batch + 1 == batches {
772 assert!(section.image() == image);
773 let store = Store::parse(&image).unwrap();
774 let index = RevisionIndex::parse(&store).unwrap();
775 index.validate_current().unwrap();
776 let document = Document::parse(&index).unwrap();
777 for space in &spaces {
778 assert_eq!(
779 section.page(*space).unwrap(),
780 Page::from_space(&document, *space).unwrap()
781 );
782 }
783 }
784 }
785 GUIDS.set(None);
786 assert!(sealed * 3 > batches, "{sealed} of {batches} batches sealed");
787 checkpoints
788 }
789
790 /// Chain depths of the spaces a section has opened.
791 fn depths(section: &Section<'_>) -> BTreeMap<ExGuid, usize> {
792 section
793 .spaces
794 .iter()
795 .filter_map(|(id, space)| match &space.state {
796 SpaceState::Open(open) => Some((*id, open.file.depth)),
797 SpaceState::Stored { .. } => None,
798 })
799 .collect()
800 }
801
802 #[test]
803 fn seals_append_the_bytes_the_image_writer_does() {
804 let corpus = [
805 include_bytes!(
806 "../../../corpus/native/20260905-05/snapshots/02-text/notebook/synthetic.one"
807 )
808 .as_slice(),
809 include_bytes!("../../../corpus/m6/native-features-01/notebook/Features.one"),
810 include_bytes!("../../../corpus/outline-edit/tree/before/notebook/synthetic.one"),
811 include_bytes!("../../../corpus/paragraph-edit/before/notebook/synthetic.one"),
812 ];
813 for (seed, source) in corpus.iter().enumerate() {
814 differential(source, seed as u64, 60, 1, 2);
815 differential(source, seed as u64 + 100, 60, 5, 2);
816 }
817 let created = crate::create_section("model.one", "First", "Author").unwrap();
818 differential(&created, 7, 60, 3, 1);
819 }
820
821 #[test]
822 fn wide_batches_regroup_and_alias_as_the_image_writer_does() {
823 // These seeds make a seal alias read-only objects or group tables across writes
824 // differently from the writes themselves, so the pages adopt the sealed form.
825 let corpus = [
826 include_bytes!("../../../corpus/m6/native-features-01/notebook/Features.one")
827 .as_slice(),
828 include_bytes!("../../../corpus/outline-edit/tree/before/notebook/synthetic.one"),
829 include_bytes!("../../../corpus/paragraph-edit/before/notebook/synthetic.one"),
830 ];
831 for seed in 4..=8 {
832 for source in corpus {
833 differential(source, seed, 30, 12, 3);
834 }
835 }
836 }
837
838 #[test]
839 fn a_long_chain_checkpoints_as_the_image_writer_does() {
840 // Past 512 revisions of one space, so the chain checkpoints once.
841 let checkpoints = differential(
842 include_bytes!(
843 "../../../corpus/native/20260905-05/snapshots/02-text/notebook/synthetic.one"
844 ),
845 11,
846 560,
847 2,
848 1,
849 );
850 assert_eq!(checkpoints, 1);
851 }
852
853 #[test]
854 fn a_created_space_seals_as_the_image_writer_creates_it() {
855 let source = &crate::create_section("model.one", "First", "Author").unwrap();
856 let store = Store::parse(source).unwrap();
857 let index = RevisionIndex::parse(&store).unwrap();
858 // A page without history, whose objects name no other space or context.
859 let (copied, revision, reachable) = page_spaces(source)
860 .into_iter()
861 .map(|space| {
862 let revision = index.resolve_active(space).unwrap();
863 let reachable = revision.reachable().unwrap();
864 (space, revision, reachable)
865 })
866 .find(|(_, revision, reachable)| {
867 reachable.iter().all(|id| {
868 let references = revision.objects[id].references().unwrap();
869 references.object_spaces.is_empty() && references.contexts.is_empty()
870 })
871 })
872 .unwrap();
873 let objects: Changes = reachable
874 .iter()
875 .map(|id| (*id, replacement(*id, &revision.objects[id]).unwrap()))
876 .collect();
877 let space = ExGuid {
878 guid: [7; 16],
879 n: 0,
880 };
881 let expected = seeded(1, || {
882 crate::write::write_revisions(source, |_| {
883 Ok(BTreeMap::from([(
884 space,
885 crate::write::RevisionEdit::Create {
886 roots: revision.roots.clone(),
887 objects: objects.clone(),
888 },
889 )]))
890 })
891 .unwrap()
892 });
893 let arena = Arena::default();
894 let mut section = Section::open(&arena, source.to_vec()).unwrap();
895 section
896 .create_space(space, revision.roots.clone(), objects)
897 .unwrap();
898 assert_eq!(section.page(space).unwrap(), section.page(copied).unwrap());
899 seeded(1, || section.seal().unwrap()).unwrap();
900 assert!(section.image() == expected);
901 // The space's next revision depends on the one creating it.
902 let mut writes = Writes(5);
903 let mut image = expected;
904 let mut written = 0;
905 for batch in 0..6 {
906 let Ok(changes) = writes.next(section.active(space).unwrap()) else {
907 continue;
908 };
909 written += 1;
910 section.apply_changes(space, &[], changes.clone()).unwrap();
911 let guids = (batch + 2) << 32;
912 let store = Store::parse(&image).unwrap();
913 let index = RevisionIndex::parse(&store).unwrap();
914 let bump = Bump::new();
915 // The root lists no page in this space, so no document reaches it.
916 let rid = index.active(space).unwrap();
917 let live = LiveRevision::new(
918 index.resolve(space, rid).unwrap(),
919 chain_depth(&index, space, rid),
920 )
921 .unwrap();
922 let store = &store;
923 let files: Files<'_> = std::rc::Rc::new(|guid| store.file_data(guid));
924 let mut legacy = ActivePage::open(space, live, FileType::Section, files).unwrap();
925 legacy.write(&bump, &[], changes).unwrap();
926 let expected = seeded(guids, || {
927 let revision = &legacy.live.revision;
928 crate::page::write::squash(
929 &index,
930 &[(space, revision)],
931 &[],
932 &BTreeMap::new(),
933 None,
934 )
935 .unwrap()
936 });
937 drop(legacy);
938 drop(index);
939 image = expected;
940 seeded(guids, || section.seal().unwrap());
941 assert!(section.image() == image, "batch {batch}");
942 }
943 assert!(written > 2);
944 let reopened = Section::open(&arena, image).unwrap();
945 assert_eq!(reopened.page(space).unwrap(), section.page(space).unwrap());
946 }
947
948 #[test]
949 fn payloads_embed_once_as_the_image_writer_embeds_them() {
950 let source = include_bytes!("../../../corpus/m6/native-features-01/notebook/Features.one");
951 let space = page_spaces(source)[0];
952 let payload = ([9; 16], b"payload bytes".as_slice());
953 let arena = Arena::default();
954 let mut section = Section::open(&arena, source.to_vec()).unwrap();
955 let mut image = source.to_vec();
956 for seed in [1_u64 << 32, 2 << 32] {
957 assert!(
958 section
959 .apply_changes(space, &[payload], Changes::new())
960 .unwrap()
961 );
962 let store = Store::parse(&image).unwrap();
963 let index = RevisionIndex::parse(&store).unwrap();
964 let legacy = ActivePage::parse(&index, space).unwrap();
965 let expected = seeded(seed, || {
966 let revision = &legacy.live.revision;
967 crate::page::write::squash(
968 &index,
969 &[(space, revision)],
970 &[payload],
971 &BTreeMap::new(),
972 None,
973 )
974 .unwrap()
975 });
976 let transaction = seeded(seed, || section.seal().unwrap());
977 assert_eq!(transaction.is_some(), expected != image);
978 assert!(section.image() == expected);
979 drop(legacy);
980 drop(index);
981 drop(store);
982 image = expected;
983 }
984 let store = Store::parse(&image).unwrap();
985 assert_eq!(store.file_data(payload.0).unwrap(), payload.1);
986 }
987
988 #[test]
989 fn a_seal_checks_the_bytes_it_appends() {
990 fn utf16(text: &str) -> Vec<u8> {
991 text.encode_utf16().flat_map(u16::to_le_bytes).collect()
992 }
993 fn object_bytes(transaction: &mut Transaction) {
994 let text = utf16("Tampered text");
995 let at = transaction
996 .append
997 .windows(text.len())
998 .position(|w| w == text)
999 .unwrap();
1000 transaction.append[at] ^= 1;
1001 }
1002 fn link(transaction: &mut Transaction) {
1003 let (_, bytes) = transaction
1004 .patches
1005 .iter_mut()
1006 .find(|(_, b)| b.len() == 12)
1007 .unwrap();
1008 bytes[0] ^= 8;
1009 }
1010 fn sentinel(transaction: &mut Transaction) {
1011 let (_, bytes) = transaction.patches.last_mut().unwrap();
1012 *bytes.last_mut().unwrap() ^= 1;
1013 }
1014 fn header(transaction: &mut Transaction) {
1015 transaction.header[96] ^= 2;
1016 }
1017 let source = include_bytes!("../../../corpus/m6/native-features-01/notebook/Features.one");
1018 let space = page_spaces(source)[0];
1019 let seal = |tamper: Option<fn(&mut Transaction)>| {
1020 let arena = Arena::default();
1021 let mut section = Section::open(&arena, source.to_vec()).unwrap();
1022 let page = section.active(space).unwrap();
1023 let changes = page
1024 .view
1025 .nodes
1026 .iter()
1027 .filter(|(id, node)| {
1028 matches!(node.kind, crate::document::Kind::Outline { .. })
1029 && page.parents.contains_key(id)
1030 })
1031 .find_map(|(outline, _)| {
1032 Insertion::paragraph(*outline, None, "Tampered text", "Author")
1033 .and_then(|insertion| insertion.changes(page))
1034 .ok()
1035 })
1036 .unwrap();
1037 section.apply_changes(space, &[], changes).unwrap();
1038 TAMPER.set(tamper);
1039 let sealed = section.seal();
1040 TAMPER.set(None);
1041 let error = sealed.as_ref().err().map(|error| error.message);
1042 assert_eq!(section.seal().is_err(), error.is_some());
1043 error
1044 };
1045 assert_eq!(seal(None), None);
1046 for tamper in [object_bytes, link, sentinel, header] {
1047 assert!(seal(Some(tamper)).is_some());
1048 }
1049 }
1050
1051 #[test]
1052 fn a_replayed_transaction_leaves_the_sealing_section() {
1053 let source = include_bytes!("../../../corpus/m6/native-features-01/notebook/Features.one");
1054 let space = page_spaces(source)[0];
1055 let arena = Arena::default();
1056 let mut sealing = Section::open(&arena, source.to_vec()).unwrap();
1057 let mut replaying = Section::open(&arena, source.to_vec()).unwrap();
1058 let mut writes = Writes(3);
1059 for _ in 0..8 {
1060 for _ in 0..3 {
1061 if let Ok(changes) = writes.next(sealing.active(space).unwrap()) {
1062 sealing.apply_changes(space, &[], changes).unwrap();
1063 }
1064 }
1065 let Some(transaction) = sealing.seal().unwrap() else {
1066 continue;
1067 };
1068 let queued: Transaction =
1069 serde_json::from_str(&serde_json::to_string(&transaction).unwrap()).unwrap();
1070 assert_eq!(queued, transaction);
1071 replaying.replay(&queued).unwrap();
1072 assert_eq!(replaying.stamp(), sealing.stamp());
1073 assert!(replaying.image() == sealing.image());
1074 assert_eq!(replaying.page(space).unwrap(), sealing.page(space).unwrap());
1075 }
1076 }
1077}
1078
1079#[cfg(test)]
1080mod probe {
1081 use super::*;
1082 use crate::{document::Kind, edit::text_changes};
1083 use std::time::{Duration, Instant};
1084
1085 fn median(mut samples: Vec<Duration>) -> Duration {
1086 samples.sort();
1087 samples[samples.len() / 2]
1088 }
1089
1090 /// The fullest page space of `image` and a rich-text object in its middle.
1091 fn target(image: &[u8]) -> (ExGuid, ExGuid, usize) {
1092 let store = Store::parse(image).unwrap();
1093 let index = RevisionIndex::parse(&store).unwrap();
1094 let document = crate::document::Document::parse(&index).unwrap();
1095 let texts = |space: ExGuid| -> Vec<ExGuid> {
1096 document
1097 .active(space)
1098 .unwrap()
1099 .nodes
1100 .iter()
1101 .filter(|(_, node)| {
1102 matches!(
1103 node.kind,
1104 Kind::RichText {
1105 boilerplate: false,
1106 ..
1107 }
1108 )
1109 })
1110 .map(|(id, _)| *id)
1111 .collect()
1112 };
1113 let (space, _) = document
1114 .pages()
1115 .unwrap()
1116 .into_iter()
1117 .max_by_key(|(space, _)| texts(*space).len())
1118 .unwrap();
1119 let texts = texts(space);
1120 (space, texts[texts.len() / 2], texts.len())
1121 }
1122
1123 /// One-character edits of a large page through the image writer and through a
1124 /// `Section`: `SECTION_PROBE=path cargo test --release -p onestore --lib probe -- --ignored --nocapture`.
1125 #[test]
1126 #[ignore]
1127 fn keystrokes() {
1128 let path = std::env::var("SECTION_PROBE").unwrap_or("/tmp/probe3000.one".into());
1129 let image = std::fs::read(&path).unwrap();
1130 let (space, text, count) = target(&image);
1131 println!(
1132 "{path}: {} bytes, {count} text objects on the page",
1133 image.len()
1134 );
1135
1136 let mut samples = Vec::new();
1137 let mut written = 0;
1138 for _ in 0..9 {
1139 let start = Instant::now();
1140 let edit = crate::PreparedEdit::text(&image, space, text, 0..0, "x").unwrap();
1141 samples.push(start.elapsed());
1142 let transaction = edit.transaction();
1143 written = transaction.append.len()
1144 + transaction
1145 .patches
1146 .iter()
1147 .map(|(_, bytes)| bytes.len())
1148 .sum::<usize>();
1149 }
1150 println!(
1151 "image writer, one keystroke: {:?}, {written} bytes written",
1152 median(samples)
1153 );
1154
1155 let before = {
1156 let store = Store::parse(&image).unwrap();
1157 let index = RevisionIndex::parse(&store).unwrap();
1158 Page::from_space(&crate::document::Document::parse(&index).unwrap(), space).unwrap()
1159 };
1160 let mut after = before.clone();
1161 let edited = after
1162 .objects
1163 .iter_mut()
1164 .filter_map(|object| match object {
1165 crate::page::PageObject::Outline(outline) => Some(outline),
1166 _ => None,
1167 })
1168 .flat_map(|outline| &mut outline.paragraphs)
1169 .find_map(|paragraph| paragraph.text_mut().filter(|object| object.id == text))
1170 .unwrap();
1171 let format = edited.text.format_at(0).unwrap().clone();
1172 edited
1173 .text
1174 .apply(crate::page::text::Edit {
1175 range: 0..0,
1176 replacement: crate::page::Paragraph::new("x".into(), format),
1177 })
1178 .unwrap();
1179 let mut samples = Vec::new();
1180 for _ in 0..9 {
1181 let start = Instant::now();
1182 crate::PreparedEdit::page(&image, space, &after, "Probe").unwrap();
1183 samples.push(start.elapsed());
1184 }
1185 println!("page-model writer, one keystroke: {:?}", median(samples));
1186
1187 let arena = Arena::default();
1188 let start = Instant::now();
1189 let mut section = Section::open(&arena, image.clone()).unwrap();
1190 println!("Section::open: {:?}", start.elapsed());
1191 let start = Instant::now();
1192 section.active(space).unwrap();
1193 println!("first edit opens the page: {:?}", start.elapsed());
1194 let (mut applies, mut seals, mut sizes) = (Vec::new(), Vec::new(), Vec::new());
1195 for keystroke in 0..200u32 {
1196 let start = Instant::now();
1197 let changes = text_changes(
1198 section.active(space).unwrap(),
1199 text,
1200 keystroke..keystroke,
1201 "x",
1202 )
1203 .unwrap();
1204 section.apply_changes(space, &[], changes).unwrap();
1205 applies.push(start.elapsed());
1206 let start = Instant::now();
1207 let transaction = section.seal().unwrap().unwrap();
1208 seals.push(start.elapsed());
1209 sizes.push((
1210 transaction.append.len(),
1211 transaction
1212 .patches
1213 .iter()
1214 .map(|(_, bytes)| bytes.len())
1215 .sum::<usize>(),
1216 ));
1217 }
1218 println!(
1219 "first seal: appended {} bytes, patched {} bytes",
1220 sizes[0].0, sizes[0].1
1221 );
1222 sizes.sort();
1223 println!(
1224 "Section, one keystroke: apply {:?}, seal {:?}; appended {:?} bytes, patched {:?} bytes (median)",
1225 median(applies),
1226 median(seals),
1227 sizes[sizes.len() / 2].0,
1228 sizes[sizes.len() / 2].1,
1229 );
1230 let start = Instant::now();
1231 let reopened = Section::open(&arena, section.image()).unwrap();
1232 println!("reopen after 200 seals: {:?}", start.elapsed());
1233 assert_eq!(reopened.page(space).unwrap(), section.page(space).unwrap());
1234 }
1235}
crates/onestore/src/snapshot.rs+1-1
...@@ -1,7 +1,7 @@...@@ -1,7 +1,7 @@
1use crate::{RevisionIndex, Store};1use crate::{RevisionIndex, Store};
2use std::io;2use std::io;
33
4fn read_exact(4pub(crate) fn read_exact(
5 read: &mut impl FnMut(u64, &mut [u8]) -> io::Result<usize>,5 read: &mut impl FnMut(u64, &mut [u8]) -> io::Result<usize>,
6 mut offset: u64,6 mut offset: u64,
7 mut output: &mut [u8],7 mut output: &mut [u8],
crates/onestore/src/store.rs+264-114
...@@ -219,6 +219,176 @@ pub struct NodeList<'a> {...@@ -219,6 +219,176 @@ pub struct NodeList<'a> {
219 pub nodes: Vec<Node<'a>>,219 pub nodes: Vec<Node<'a>>,
220}220}
221221
222/// A file-node list fragment (MS-ONESTORE 2.4.1) before its nodes are decoded.
223pub(crate) struct Fragment<'a> {
224 pub id: u32,
225 pub sequence: u32,
226 pub next: Chunk,
227 body: Cursor<'a>,
228}
229
230impl<'a> Fragment<'a> {
231 /// The fragment `bytes`, which start at file offset `offset`.
232 pub(crate) fn parse(bytes: &'a [u8], offset: usize) -> Result<Self> {
233 if bytes.len() < 36 {
234 return Err(Error {
235 offset,
236 message: "Truncated file-node fragment",
237 });
238 }
239 let mut c = Cursor { bytes, offset };
240 if u64::from_le_bytes(c.read()?) != 0xa4567ab1f5f7f4c4 {
241 return Err(Error {
242 offset,
243 message: "Incorrect file-node fragment signature",
244 });
245 }
246 let id = u32::from_le_bytes(c.read()?);
247 let sequence = u32::from_le_bytes(c.read()?);
248 let end = offset + bytes.len();
249 let mut tail = Cursor {
250 bytes: &bytes[bytes.len() - 20..],
251 offset: end - 20,
252 };
253 let next = tail.chunk()?;
254 if u64::from_le_bytes(tail.read()?) != 0x8bc215c38233ba4b {
255 return Err(Error {
256 offset: end - 8,
257 message: "Incorrect file-node fragment footer",
258 });
259 }
260 Ok(Self {
261 id,
262 sequence,
263 next,
264 body: Cursor {
265 bytes: &bytes[16..bytes.len() - 20],
266 offset: offset + 16,
267 },
268 })
269 }
270
271 /// Decodes nodes into `nodes` until it holds `required` or the fragment ends; `data`
272 /// checks each data reference.
273 pub(crate) fn nodes(
274 self,
275 required: usize,
276 nodes: &mut Vec<Node<'a>>,
277 data: impl Fn(Chunk) -> Result<()>,
278 ) -> Result<()> {
279 let mut c = self.body;
280 while nodes.len() < required && c.bytes.len() >= 4 {
281 let offset = c.offset;
282 let raw = u32::from_le_bytes(c.read()?);
283 let size = usize::try_from((raw >> 10) & 0x1fff).unwrap();
284 if size < 4 {
285 return Err(Error {
286 offset,
287 message: "File node is shorter than its header",
288 });
289 }
290 let id = u16::try_from(raw & 0x3ff).unwrap();
291 let body = c.take(size - 4)?;
292 if id == 0xff {
293 break;
294 }
295 let mut fields = Cursor {
296 bytes: body,
297 offset: offset + 4,
298 };
299 let reference = match (raw >> 27) & 0xf {
300 0 => None,
301 base @ (1 | 2) => {
302 let (stp, nil, shift) = match (raw >> 23) & 3 {
303 0 => (u64::from_le_bytes(fields.read()?), u64::MAX, 0),
304 1 => (
305 u64::from(u32::from_le_bytes(fields.read()?)),
306 u64::from(u32::MAX),
307 0,
308 ),
309 2 => (
310 u64::from(u16::from_le_bytes(fields.read()?)),
311 u64::from(u16::MAX),
312 3,
313 ),
314 3 => (
315 u64::from(u32::from_le_bytes(fields.read()?)),
316 u64::from(u32::MAX),
317 3,
318 ),
319 _ => unreachable!(),
320 };
321 let cb = match (raw >> 25) & 3 {
322 0 => u64::from(u32::from_le_bytes(fields.read()?)),
323 1 => u64::from_le_bytes(fields.read()?),
324 2 => u64::from(fields.read::<1>()?[0]) * 8,
325 3 => u64::from(u16::from_le_bytes(fields.read()?)) * 8,
326 _ => unreachable!(),
327 };
328 let reference = Chunk {
329 offset: if cb == 0 && stp == nil {
330 u64::MAX
331 } else {
332 stp << shift
333 },
334 length: cb,
335 };
336 if base == 1 {
337 if !reference.absent() {
338 data(reference)?;
339 }
340 Some(Reference::Data(reference))
341 } else {
342 Some(Reference::NodeList(reference))
343 }
344 }
345 _ => {
346 return Err(Error {
347 offset,
348 message: "Unsupported file-node base type",
349 });
350 }
351 };
352 nodes.push(Node {
353 id,
354 offset,
355 payload: fields.bytes,
356 reference,
357 });
358 }
359 Ok(())
360 }
361}
362
363/// Where a store's next transaction appends, as its committed transactions leave it.
364#[derive(Clone, Debug)]
365pub(crate) struct StoreState {
366 pub stamp: crate::Stamp,
367 pub file_type: FileType,
368 /// The last transaction-log fragment and the entry bytes it holds.
369 pub log: (Chunk, usize),
370 /// The log's checksum through its last entry.
371 pub log_crc: u32,
372 /// The highest file-node list identity the log names.
373 pub max_list: u32,
374 pub root: ListTail,
375 /// The file-data store list, once one exists.
376 pub files: Option<ListTail>,
377 /// Each object space's revision manifest list.
378 pub spaces: BTreeMap<crate::ExGuid, ListTail>,
379}
380
381/// The last fragment of a file-node list, where appending continues.
382#[derive(Clone, Copy, Debug)]
383pub(crate) struct ListTail {
384 pub id: u32,
385 pub fragments: u32,
386 pub last: Chunk,
387 pub nodes: usize,
388 /// The file offset past the list's last node.
389 pub end: u64,
390}
391
222#[derive(Debug)]392#[derive(Debug)]
223pub(crate) struct TransactionFragment<'a> {393pub(crate) struct TransactionFragment<'a> {
224 pub chunk: Chunk,394 pub chunk: Chunk,
...@@ -357,26 +527,9 @@ impl<'a> Store<'a> {...@@ -357,26 +527,9 @@ impl<'a> Store<'a> {
357 loop {527 loop {
358 let range = chunk.range(data)?;528 let range = chunk.range(data)?;
359 claim(&mut occupied, range.clone())?;529 claim(&mut occupied, range.clone())?;
360 if range.len() < 36 {530 let fragment = Fragment::parse(&data[range.clone()], range.start)?;
361 return Err(Error {531 if usize::try_from(fragment.sequence).ok() != Some(fragments.len())
362 offset: range.start,532 || list_id.is_some_and(|previous| fragment.id != previous)
363 message: "Truncated file-node fragment",
364 });
365 }
366 let mut c = Cursor {
367 bytes: &data[range.clone()],
368 offset: range.start,
369 };
370 if u64::from_le_bytes(c.read()?) != 0xa4567ab1f5f7f4c4 {
371 return Err(Error {
372 offset: range.start,
373 message: "Incorrect file-node fragment signature",
374 });
375 }
376 let id = u32::from_le_bytes(c.read()?);
377 let sequence = u32::from_le_bytes(c.read()?);
378 if usize::try_from(sequence).ok() != Some(fragments.len())
379 || list_id.is_some_and(|previous| id != previous)
380 {533 {
381 return Err(Error {534 return Err(Error {
382 offset: range.start + 8,535 offset: range.start + 8,
...@@ -384,13 +537,13 @@ impl<'a> Store<'a> {...@@ -384,13 +537,13 @@ impl<'a> Store<'a> {
384 });537 });
385 }538 }
386 if list_id.is_none() {539 if list_id.is_none() {
387 if !list_ids.insert(id) {540 if !list_ids.insert(fragment.id) {
388 return Err(Error {541 return Err(Error {
389 offset: range.start + 8,542 offset: range.start + 8,
390 message: "Duplicate file-node list identity",543 message: "Duplicate file-node list identity",
391 });544 });
392 }545 }
393 required = usize::try_from(*counts.get(&id).ok_or(Error {546 required = usize::try_from(*counts.get(&fragment.id).ok_or(Error {
394 offset: range.start + 8,547 offset: range.start + 8,
395 message: "File-node list is absent from the transaction log",548 message: "File-node list is absent from the transaction log",
396 })?)549 })?)
...@@ -398,99 +551,12 @@ impl<'a> Store<'a> {...@@ -398,99 +551,12 @@ impl<'a> Store<'a> {
398 offset: range.start + 8,551 offset: range.start + 8,
399 message: "File-node count exceeds address space",552 message: "File-node count exceeds address space",
400 })?;553 })?;
401 list_id = Some(id);554 list_id = Some(fragment.id);
402 }
403 let mut tail = Cursor {
404 bytes: &data[range.end - 20..range.end],
405 offset: range.end - 20,
406 };
407 let next = tail.chunk()?;
408 if u64::from_le_bytes(tail.read()?) != 0x8bc215c38233ba4b {
409 return Err(Error {
410 offset: range.end - 8,
411 message: "Incorrect file-node fragment footer",
412 });
413 }
414 c.bytes = &data[range.start + 16..range.end - 20];
415 while nodes.len() < required && c.bytes.len() >= 4 {
416 let offset = c.offset;
417 let raw = u32::from_le_bytes(c.read()?);
418 let size = usize::try_from((raw >> 10) & 0x1fff).unwrap();
419 if size < 4 {
420 return Err(Error {
421 offset,
422 message: "File node is shorter than its header",
423 });
424 }
425 let id = u16::try_from(raw & 0x3ff).unwrap();
426 let body = c.take(size - 4)?;
427 if id == 0xff {
428 break;
429 }
430 let mut fields = Cursor {
431 bytes: body,
432 offset: offset + 4,
433 };
434 let reference = match (raw >> 27) & 0xf {
435 0 => None,
436 base @ (1 | 2) => {
437 let (stp, nil, shift) = match (raw >> 23) & 3 {
438 0 => (u64::from_le_bytes(fields.read()?), u64::MAX, 0),
439 1 => (
440 u64::from(u32::from_le_bytes(fields.read()?)),
441 u64::from(u32::MAX),
442 0,
443 ),
444 2 => (
445 u64::from(u16::from_le_bytes(fields.read()?)),
446 u64::from(u16::MAX),
447 3,
448 ),
449 3 => (
450 u64::from(u32::from_le_bytes(fields.read()?)),
451 u64::from(u32::MAX),
452 3,
453 ),
454 _ => unreachable!(),
455 };
456 let cb = match (raw >> 25) & 3 {
457 0 => u64::from(u32::from_le_bytes(fields.read()?)),
458 1 => u64::from_le_bytes(fields.read()?),
459 2 => u64::from(fields.read::<1>()?[0]) * 8,
460 3 => u64::from(u16::from_le_bytes(fields.read()?)) * 8,
461 _ => unreachable!(),
462 };
463 let reference = Chunk {
464 offset: if cb == 0 && stp == nil {
465 u64::MAX
466 } else {
467 stp << shift
468 },
469 length: cb,
470 };
471 if base == 1 {
472 if !reference.absent() {
473 reference.range(data)?;
474 }
475 Some(Reference::Data(reference))
476 } else {
477 Some(Reference::NodeList(reference))
478 }
479 }
480 _ => {
481 return Err(Error {
482 offset,
483 message: "Unsupported file-node base type",
484 });
485 }
486 };
487 nodes.push(Node {
488 id,
489 offset,
490 payload: fields.bytes,
491 reference,
492 });
493 }555 }
556 let next = fragment.next;
557 fragment.nodes(required, &mut nodes, |reference| {
558 reference.range(data).map(drop)
559 })?;
494 fragments.push(chunk);560 fragments.push(chunk);
495 if nodes.len() == required {561 if nodes.len() == required {
496 break;562 break;
...@@ -517,6 +583,90 @@ impl<'a> Store<'a> {...@@ -517,6 +583,90 @@ impl<'a> Store<'a> {
517 })583 })
518 }584 }
519585
586 /// Where the next transaction appends.
587 pub(crate) fn state(&self) -> Result<StoreState> {
588 let file_type = self.header.file_type;
589 let mut log_crc = if file_type == FileType::Section {
590 u32::MAX
591 } else {
592 0
593 };
594 for fragment in &self.transaction_fragments {
595 log_crc = transaction_crc(
596 log_crc,
597 fragment.entries,
598 file_type,
599 fragment.entries.len() + 20 > fragment.chunk.length as usize,
600 );
601 }
602 let last = self.transaction_fragments.last().unwrap();
603 let root = self.list(self.header.root)?;
604 let files = match root.nodes.iter().find(|node| node.id == 0x90) {
605 Some(node) => {
606 let Some(Reference::NodeList(chunk)) = node.reference else {
607 return Err(Error {
608 offset: node.offset,
609 message: "File-data store reference lacks a list",
610 });
611 };
612 Some(self.tail(chunk)?)
613 }
614 None => None,
615 };
616 let mut spaces = BTreeMap::new();
617 for node in root.nodes.iter().filter(|node| node.id == 8) {
618 let revisions = node
619 .referenced_list(self)?
620 .iter()
621 .rfind(|node| node.id == 0x10);
622 if let Some(Node {
623 reference: Some(Reference::NodeList(chunk)),
624 ..
625 }) = revisions
626 {
627 spaces.insert(node.fields(self).exguid()?, self.tail(*chunk)?);
628 }
629 }
630 Ok(StoreState {
631 stamp: crate::Stamp::of(self.data)?,
632 file_type,
633 log: (last.chunk, last.entries.len()),
634 log_crc,
635 max_list: self
636 .transaction_fragments
637 .iter()
638 .flat_map(|fragment| fragment.entries.chunks_exact(8))
639 .map(|entry| u32::from_le_bytes(entry[..4].try_into().unwrap()))
640 .max()
641 .unwrap(),
642 root: self.tail(self.header.root)?,
643 files,
644 spaces,
645 })
646 }
647
648 fn tail(&self, chunk: Chunk) -> Result<ListTail> {
649 let list = self.list(chunk)?;
650 let last = *list.fragments.last().unwrap();
651 let start = usize::try_from(last.offset).unwrap();
652 let node = list.nodes.last().ok_or(Error {
653 offset: start,
654 message: "Cannot append to an empty file-node list",
655 })?;
656 let header =
657 u32::from_le_bytes(self.data[node.offset..node.offset + 4].try_into().unwrap());
658 Ok(ListTail {
659 id: u32::from_le_bytes(self.data[start + 8..start + 12].try_into().unwrap()),
660 fragments: u32::try_from(list.fragments.len()).map_err(|_| Error {
661 offset: start,
662 message: "File-node fragment sequences are exhausted",
663 })?,
664 last,
665 nodes: list.nodes.len(),
666 end: (node.offset + usize::try_from((header >> 10) & 0x1fff).unwrap()) as u64,
667 })
668 }
669
520 pub fn chunk_data(&self, chunk: Chunk) -> Result<&'a [u8]> {670 pub fn chunk_data(&self, chunk: Chunk) -> Result<&'a [u8]> {
521 Ok(&self.data[chunk.range(self.data)?])671 Ok(&self.data[chunk.range(self.data)?])
522 }672 }
crates/onestore/src/write.rs+879-291
...@@ -1,7 +1,8 @@...@@ -1,7 +1,8 @@
1use crate::{1use crate::{
2 Chunk, Error, ExGuid, FileType, ObjectData, PropertySets, Reference, RevisionIndex, Store,2 Chunk, Error, ExGuid, FileType, Node, ObjectData, PropertySets, Reference, RevisionIndex,
3 Value,3 Store, Value,
4 store::{crc, transaction_crc},4 bytes::Cursor,
5 store::{Fragment, ListTail, StoreState, crc, transaction_crc},
5};6};
67
7use std::{8use std::{
...@@ -14,12 +15,25 @@ mod tests;...@@ -14,12 +15,25 @@ mod tests;
1415
15type Result<T> = std::result::Result<T, Error>;16type Result<T> = std::result::Result<T, Error>;
1617
18#[cfg(test)]
19thread_local! {
20 /// When set, `fresh_guid` counts from it instead of drawing randomness, so tests can
21 /// compare builds byte for byte.
22 pub(crate) static GUIDS: std::cell::Cell<Option<u64>> = const { std::cell::Cell::new(None) };
23}
24
17pub(crate) fn fresh_guid() -> Result<[u8; 16]> {25pub(crate) fn fresh_guid() -> Result<[u8; 16]> {
18 let mut guid = [0; 16];26 let mut guid = [0; 16];
19 getrandom::fill(&mut guid).map_err(|_| Error {27 getrandom::fill(&mut guid).map_err(|_| Error {
20 offset: 0,28 offset: 0,
21 message: "System random source failed",29 message: "System random source failed",
22 })?;30 })?;
31 #[cfg(test)]
32 if let Some(next) = GUIDS.get() {
33 GUIDS.set(Some(next + 1));
34 guid[..8].copy_from_slice(&next.to_le_bytes());
35 guid[8..].copy_from_slice(&0x5eed_u64.to_le_bytes());
36 }
23 guid[7] = (guid[7] & 0x0f) | 0x40;37 guid[7] = (guid[7] & 0x0f) | 0x40;
24 guid[8] = (guid[8] & 0x3f) | 0x80;38 guid[8] = (guid[8] & 0x3f) | 0x80;
25 Ok(guid)39 Ok(guid)
...@@ -74,9 +88,14 @@ pub(crate) fn append(data: &mut Vec<u8>, bytes: &[u8]) -> Result<Chunk> {...@@ -74,9 +88,14 @@ pub(crate) fn append(data: &mut Vec<u8>, bytes: &[u8]) -> Result<Chunk> {
74}88}
7589
76pub(crate) fn append_list(data: &mut Vec<u8>, id: u32, nodes: &[Vec<u8>]) -> Result<Chunk> {90pub(crate) fn append_list(data: &mut Vec<u8>, id: u32, nodes: &[Vec<u8>]) -> Result<Chunk> {
91 append(data, &fragment(id, 0, nodes))
92}
93
94/// The file-node list fragment `sequence` of list `id`, holding `nodes` and no successor.
95fn fragment(id: u32, sequence: u32, nodes: &[Vec<u8>]) -> Vec<u8> {
77 let mut bytes = 0xa4567ab1f5f7f4c4_u64.to_le_bytes().to_vec();96 let mut bytes = 0xa4567ab1f5f7f4c4_u64.to_le_bytes().to_vec();
78 bytes.extend_from_slice(&id.to_le_bytes());97 bytes.extend_from_slice(&id.to_le_bytes());
79 bytes.extend_from_slice(&0_u32.to_le_bytes());98 bytes.extend_from_slice(&sequence.to_le_bytes());
80 for node in nodes {99 for node in nodes {
81 bytes.extend_from_slice(node);100 bytes.extend_from_slice(node);
82 }101 }
...@@ -84,10 +103,10 @@ pub(crate) fn append_list(data: &mut Vec<u8>, id: u32, nodes: &[Vec<u8>]) -> Res...@@ -84,10 +103,10 @@ pub(crate) fn append_list(data: &mut Vec<u8>, id: u32, nodes: &[Vec<u8>]) -> Res
84 bytes.extend_from_slice(&u64::MAX.to_le_bytes());103 bytes.extend_from_slice(&u64::MAX.to_le_bytes());
85 bytes.extend_from_slice(&0_u32.to_le_bytes());104 bytes.extend_from_slice(&0_u32.to_le_bytes());
86 bytes.extend_from_slice(&0x8bc215c38233ba4b_u64.to_le_bytes());105 bytes.extend_from_slice(&0x8bc215c38233ba4b_u64.to_le_bytes());
87 append(data, &bytes)106 bytes
88}107}
89108
90fn compact(id: ExGuid, table: &BTreeMap<u32, [u8; 16]>) -> Result<[u8; 4]> {109pub(crate) fn compact(id: ExGuid, table: &BTreeMap<u32, [u8; 16]>) -> Result<[u8; 4]> {
91 let index = table110 let index = table
92 .iter()111 .iter()
93 .find_map(|(index, guid)| (*guid == id.guid).then_some(*index))112 .find_map(|(index, guid)| (*guid == id.guid).then_some(*index))
...@@ -378,6 +397,56 @@ pub(crate) fn patched(...@@ -378,6 +397,56 @@ pub(crate) fn patched(
378 Ok(changed)397 Ok(changed)
379}398}
380399
400/// Whether `after` stores as `before` does: the same type and bytes, naming the same
401/// identities. Stored tables keep only the entries an object names, so those decide.
402pub(crate) fn unchanged(before: &crate::Object<'_>, after: &crate::Object<'_>) -> Result<bool> {
403 if before.jcid != after.jcid || before.data != after.data {
404 return Ok(false);
405 }
406 let entries = match after.data {
407 ObjectData::Properties(bytes) => table_entries(bytes)?,
408 _ => Vec::new(),
409 };
410 Ok(entries
411 .iter()
412 .all(|entry| before.global_ids.get(entry) == after.global_ids.get(entry)))
413}
414
415/// `object` as a replacement a revision stores under `id`.
416pub(crate) fn replacement(id: ExGuid, object: &crate::Object<'_>) -> Result<PropertyObject> {
417 let mut replacement = match object.data {
418 ObjectData::Properties(_) => PropertyObject::from_object(object)?,
419 ObjectData::File {
420 reference,
421 extension,
422 } => {
423 let text = |bytes: &[u8]| {
424 String::from_utf16(
425 &bytes
426 .chunks_exact(2)
427 .map(|pair| u16::from_le_bytes([pair[0], pair[1]]))
428 .collect::<Vec<_>>(),
429 )
430 .map_err(|_| Error {
431 offset: 0,
432 message: "Invalid UTF-16 file-data declaration",
433 })
434 };
435 let mut replacement = PropertyObject::file(id, &text(reference)?, &text(extension)?)?;
436 replacement.jcid = object.jcid;
437 replacement
438 }
439 ObjectData::Encrypted(_) => {
440 return Err(Error {
441 offset: 0,
442 message: "Page edits only produce property objects",
443 });
444 }
445 };
446 replacement.reference(id)?;
447 Ok(replacement)
448}
449
381/// The object type OneNote gives embedded picture payload declarations; embedded files450/// The object type OneNote gives embedded picture payload declarations; embedded files
382/// use `EMBEDDED_FILE_JCID` with the same declaration shape.451/// use `EMBEDDED_FILE_JCID` with the same declaration shape.
383pub(crate) const FILE_DATA_JCID: u32 = 0x80039;452pub(crate) const FILE_DATA_JCID: u32 = 0x80039;
...@@ -784,6 +853,7 @@ fn used_entries<'o>(...@@ -784,6 +853,7 @@ fn used_entries<'o>(
784}853}
785854
786/// A space's revision as appending a revision and reading it back leaves it.855/// A space's revision as appending a revision and reading it back leaves it.
856#[derive(Clone)]
787pub(crate) struct LiveRevision<'a> {857pub(crate) struct LiveRevision<'a> {
788 pub revision: crate::ResolvedRevision<'a>,858 pub revision: crate::ResolvedRevision<'a>,
789 /// Incoming references of each object reachable from the roots; a root counts once.859 /// Incoming references of each object reachable from the roots; a root counts once.
...@@ -792,7 +862,7 @@ pub(crate) struct LiveRevision<'a> {...@@ -792,7 +862,7 @@ pub(crate) struct LiveRevision<'a> {
792 /// objects alias.862 /// objects alias.
793 readonly: BTreeMap<u32, BTreeMap<&'a [u8], BTreeSet<ExGuid>>>,863 readonly: BTreeMap<u32, BTreeMap<&'a [u8], BTreeSet<ExGuid>>>,
794 /// Revisions in the dependency chain, counted to 512; zero before the first.864 /// Revisions in the dependency chain, counted to 512; zero before the first.
795 depth: usize,865 pub depth: usize,
796}866}
797867
798impl<'a> LiveRevision<'a> {868impl<'a> LiveRevision<'a> {
...@@ -1230,39 +1300,6 @@ fn update(...@@ -1230,39 +1300,6 @@ fn update(
1230 }1300 }
1231}1301}
12321302
1233fn append_fragment(
1234 source: &[u8],
1235 output: &mut Vec<u8>,
1236 list: &crate::NodeList,
1237 nodes: &[Vec<u8>],
1238) -> Result<(u32, usize)> {
1239 let last_fragment = *list.fragments.last().unwrap();
1240 let list_start = usize::try_from(last_fragment.offset).unwrap();
1241 let list_id = u32::from_le_bytes(source[list_start + 8..list_start + 12].try_into().unwrap());
1242 let chunk = append_list(output, list_id, nodes)?;
1243 let start = usize::try_from(chunk.offset).unwrap();
1244 let sequence = u32::try_from(list.fragments.len()).map_err(|_| Error {
1245 offset: list_start,
1246 message: "File-node fragment sequences are exhausted",
1247 })?;
1248 output[start + 12..start + 16].copy_from_slice(&sequence.to_le_bytes());
1249 let last_node = list.nodes.last().unwrap();
1250 let node_header = u32::from_le_bytes(
1251 source[last_node.offset..last_node.offset + 4]
1252 .try_into()
1253 .unwrap(),
1254 );
1255 let nodes_end = last_node.offset + usize::try_from((node_header >> 10) & 0x1fff).unwrap();
1256 let tail = list_start + usize::try_from(last_fragment.length).unwrap() - 20;
1257 if tail - nodes_end >= 4 {
1258 output[nodes_end..nodes_end + 4].copy_from_slice(&node(0xff, None, &[])?);
1259 }
1260 output[tail..tail + 8].copy_from_slice(&chunk.offset.to_le_bytes());
1261 output[tail + 8..tail + 12].copy_from_slice(&(chunk.length as u32).to_le_bytes());
1262
1263 Ok((list_id, list.nodes.len() + nodes.len()))
1264}
1265
1266pub(crate) fn write_revisions(1303pub(crate) fn write_revisions(
1267 source: &[u8],1304 source: &[u8],
1268 edit: impl FnOnce(&RevisionIndex<'_>) -> Result<BTreeMap<ExGuid, RevisionEdit>>,1305 edit: impl FnOnce(&RevisionIndex<'_>) -> Result<BTreeMap<ExGuid, RevisionEdit>>,
...@@ -1372,36 +1409,7 @@ pub(crate) fn build_on(...@@ -1372,36 +1409,7 @@ pub(crate) fn build_on(
1372 None => index.resolve(space, rid),1409 None => index.resolve(space, rid),
1373 };1410 };
1374 let changes = edit(index)?;1411 let changes = edit(index)?;
1375 // Room for the revision, so appending does not double the image.1412 let mut appending = Appending::new(store.state()?);
1376 let mut output = Vec::with_capacity(source.len() + source.len() / 16 + (1 << 16));
1377 output.extend_from_slice(source);
1378 // Native files reserve 1 KiB per transaction-log fragment; a fragment that ends the file
1379 // keeps that room before the first new chunk.
1380 let tail = store.transaction_fragments.last().unwrap().chunk;
1381 let reserved = usize::try_from(tail.offset + tail.length.max(1024) + 32).unwrap();
1382 if usize::try_from(tail.offset + tail.length)
1383 .unwrap()
1384 .next_multiple_of(8)
1385 >= output.len()
1386 {
1387 output.resize(reserved, 0);
1388 }
1389 let mut maximum = store
1390 .transaction_fragments
1391 .iter()
1392 .flat_map(|fragment| fragment.entries.chunks_exact(8))
1393 .map(|entry| u32::from_le_bytes(entry[..4].try_into().unwrap()))
1394 .max()
1395 .unwrap();
1396 let mut allocate_list = || {
1397 maximum = maximum.checked_add(1).ok_or(Error {
1398 offset: 0,
1399 message: "File-node list identities are exhausted",
1400 })?;
1401 Ok::<_, Error>(maximum)
1402 };
1403 let mut counts = Vec::new();
1404 let mut root_nodes = Vec::new();
1405 for (space, change) in changes {1413 for (space, change) in changes {
1406 let new_space = matches!(change, RevisionEdit::Create { .. });1414 let new_space = matches!(change, RevisionEdit::Create { .. });
1407 let current = (ExGuid::default(), 1_u32);1415 let current = (ExGuid::default(), 1_u32);
...@@ -1457,18 +1465,19 @@ pub(crate) fn build_on(...@@ -1457,18 +1465,19 @@ pub(crate) fn build_on(
1457 )1465 )
1458 }1466 }
1459 };1467 };
1460 let existing: BTreeSet<ExGuid> = revision.objects.keys().copied().collect();
1461 let depth = rid.map_or(0, |rid| chain_depth(index, space, rid));1468 let depth = rid.map_or(0, |rid| chain_depth(index, space, rid));
1462 let mut live = LiveRevision::new(revision, depth)?;1469 let mut live = LiveRevision::new(revision, depth)?;
1463 let replacements = live.prepare(replacements, is_section)?;1470 let replacements = live.prepare(replacements, is_section)?;
1464 if replacements.is_empty() {1471 if replacements.is_empty() {
1465 continue;1472 continue;
1466 }1473 }
1467 let Commit {1474 let replaced: BTreeSet<ExGuid> = replacements.keys().copied().collect();
1468 checkpoint,1475 let created = replaced
1469 changed,1476 .iter()
1470 ..1477 .filter(|id| !live.revision.objects.contains_key(id))
1471 } = live.commit(1478 .copied()
1479 .collect();
1480 let commit = live.commit(
1472 replacements1481 replacements
1473 .iter()1482 .iter()
1474 .map(|(id, replacement)| {1483 .map(|(id, replacement)| {
...@@ -1480,12 +1489,224 @@ pub(crate) fn build_on(...@@ -1480,12 +1489,224 @@ pub(crate) fn build_on(
1480 })1489 })
1481 .collect::<Result<Vec<_>>>()?,1490 .collect::<Result<Vec<_>>>()?,
1482 )?;1491 )?;
1492 // A dependent revision inherits its key, as OneNote writes it.
1493 let key = if protection.is_some() && commit.checkpoint {
1494 Some(
1495 index
1496 .spaces
1497 .get(&space)
1498 .and_then(|space| {
1499 space.revisions.values().find_map(|revision| {
1500 revision.nodes.first().filter(|node| node.id == 0x7c)
1501 })
1502 })
1503 .ok_or(Error {
1504 offset: 0,
1505 message: "Protected revisions continue a protected object space",
1506 })?,
1507 )
1508 } else {
1509 None
1510 };
1511 appending.revision(
1512 &Sealing {
1513 space,
1514 previous: rid,
1515 new_space,
1516 label,
1517 live: &live,
1518 commit: &commit,
1519 replaced: &replaced,
1520 created: &created,
1521 },
1522 &[(0, source)],
1523 protection,
1524 key,
1525 )?;
1526 }
1527 appending.payloads(payloads, protection)?;
1528 Ok(match appending.finish()? {
1529 Some((transaction, _)) => {
1530 let mut output = source.to_vec();
1531 transaction.apply(&mut output)?;
1532 output
1533 }
1534 None => source.to_vec(),
1535 })
1536}
1537
1538/// A space's revision as `LiveRevision::commit` left it, ready to append.
1539pub(crate) struct Sealing<'r, 'a> {
1540 pub space: ExGuid,
1541 /// The revision it depends on unless it is a checkpoint; none for a new space or label.
1542 pub previous: Option<ExGuid>,
1543 pub new_space: bool,
1544 /// The context and role it is current under.
1545 pub label: (ExGuid, u32),
1546 pub live: &'r LiveRevision<'a>,
1547 pub commit: &'r Commit,
1548 /// Objects whose bytes the revision stores rather than referencing stored bytes.
1549 pub replaced: &'r BTreeSet<ExGuid>,
1550 /// Replaced objects the space did not hold before.
1551 pub created: &'r BTreeSet<ExGuid>,
1552}
1553
1554/// The chunk `bytes` occupies in a store whose bytes `segments` hold at their offsets.
1555fn located(segments: &[(u64, &[u8])], bytes: &[u8]) -> Result<Chunk> {
1556 let address = bytes.as_ptr().addr();
1557 segments
1558 .iter()
1559 .find_map(|(offset, segment)| {
1560 let start = address.checked_sub(segment.as_ptr().addr())?;
1561 (start + bytes.len() <= segment.len()).then(|| Chunk {
1562 offset: offset + start as u64,
1563 length: bytes.len() as u64,
1564 })
1565 })
1566 .ok_or(Error {
1567 offset: 0,
1568 message: "Stored object data lies outside the store",
1569 })
1570}
1571
1572/// A transaction under construction: bytes appended at the end of a store and patches
1573/// inside it, advancing `state` as its lists grow.
1574pub(crate) struct Appending {
1575 pub state: StoreState,
1576 base: crate::Stamp,
1577 append: Vec<u8>,
1578 patches: Vec<(u64, Vec<u8>)>,
1579 /// File-node counts of the lists this transaction writes, as log entries.
1580 counts: Vec<(u32, usize)>,
1581 /// Nodes the root list gains: new object spaces and the file-data store.
1582 root_nodes: Vec<Vec<u8>>,
1583}
1584
1585impl Appending {
1586 pub(crate) fn new(state: StoreState) -> Self {
1587 let base = state.stamp.clone();
1588 let mut append = Vec::with_capacity(1 << 16);
1589 // Native files reserve 1 KiB per transaction-log fragment; a fragment that ends the
1590 // file keeps that room before the first new chunk.
1591 let (tail, _) = state.log;
1592 if (tail.offset + tail.length).next_multiple_of(8) >= base.length {
1593 let reserved = tail.offset + tail.length.max(1024) + 32;
1594 append.resize(reserved.saturating_sub(base.length) as usize, 0);
1595 }
1596 Self {
1597 state,
1598 base,
1599 append,
1600 patches: Vec::new(),
1601 counts: Vec::new(),
1602 root_nodes: Vec::new(),
1603 }
1604 }
1605
1606 fn end(&self) -> u64 {
1607 self.base.length + self.append.len() as u64
1608 }
1609
1610 fn append(&mut self, bytes: &[u8]) -> Result<Chunk> {
1611 let length = u64::from(u32::try_from(bytes.len()).map_err(|_| Error {
1612 offset: 0,
1613 message: "Chunk exceeds the encoded length limit",
1614 })?);
1615 let padding = self.end().next_multiple_of(8) - self.end();
1616 self.append.resize(self.append.len() + padding as usize, 0);
1617 let offset = self.end();
1618 self.append.extend_from_slice(bytes);
1619 Ok(Chunk { offset, length })
1620 }
1621
1622 /// Writes `bytes` at a file offset: into the appended bytes, or as a patch.
1623 fn write(&mut self, offset: u64, bytes: &[u8]) {
1624 match offset.checked_sub(self.base.length) {
1625 Some(at) => {
1626 let at = at as usize;
1627 self.append[at..at + bytes.len()].copy_from_slice(bytes);
1628 }
1629 None => self.patches.push((offset, bytes.to_vec())),
1630 }
1631 }
1632
1633 fn allocate_list(&mut self) -> Result<u32> {
1634 self.state.max_list = self.state.max_list.checked_add(1).ok_or(Error {
1635 offset: 0,
1636 message: "File-node list identities are exhausted",
1637 })?;
1638 Ok(self.state.max_list)
1639 }
1640
1641 /// Appends `nodes` as a new list.
1642 fn list(&mut self, nodes: &[Vec<u8>]) -> Result<(Chunk, ListTail)> {
1643 let id = self.allocate_list()?;
1644 let chunk = self.append(&fragment(id, 0, nodes))?;
1645 self.counts.push((id, nodes.len()));
1646 Ok((
1647 chunk,
1648 ListTail {
1649 id,
1650 fragments: 1,
1651 last: chunk,
1652 nodes: nodes.len(),
1653 end: chunk.offset + 16 + nodes.iter().map(Vec::len).sum::<usize>() as u64,
1654 },
1655 ))
1656 }
1657
1658 /// Appends `nodes` to the list ending at `tail` as its next fragment.
1659 fn extend(&mut self, tail: &mut ListTail, nodes: &[Vec<u8>]) -> Result<()> {
1660 let chunk = self.append(&fragment(tail.id, tail.fragments, nodes))?;
1661 let next = tail.last.offset + tail.last.length - 20;
1662 if next - tail.end >= 4 {
1663 self.write(tail.end, &node(0xff, None, &[])?);
1664 }
1665 let mut reference = chunk.offset.to_le_bytes().to_vec();
1666 reference.extend_from_slice(&(chunk.length as u32).to_le_bytes());
1667 self.write(next, &reference);
1668 *tail = ListTail {
1669 id: tail.id,
1670 fragments: tail.fragments.checked_add(1).ok_or(Error {
1671 offset: 0,
1672 message: "File-node fragment sequences are exhausted",
1673 })?,
1674 last: chunk,
1675 nodes: tail.nodes + nodes.len(),
1676 end: chunk.offset + 16 + nodes.iter().map(Vec::len).sum::<usize>() as u64,
1677 };
1678 self.counts.push((tail.id, tail.nodes));
1679 Ok(())
1680 }
1681
1682 /// Appends a space's next revision: its manifest, object groups and new data. Returns the
1683 /// revision's identity and where it stored each replaced object's bytes.
1684 pub(crate) fn revision(
1685 &mut self,
1686 sealing: &Sealing<'_, '_>,
1687 segments: &[(u64, &[u8])],
1688 protection: Option<&dyn Protection>,
1689 key: Option<&crate::Node<'_>>,
1690 ) -> Result<(ExGuid, Vec<(ExGuid, Chunk)>)> {
1691 let is_section = self.state.file_type == FileType::Section;
1692 let Sealing {
1693 space,
1694 label,
1695 live,
1696 commit,
1697 ..
1698 } = *sealing;
1483 let revision = &live.revision;1699 let revision = &live.revision;
1484 let selected: Vec<_> = revision1700 let checkpoint = commit.checkpoint;
1485 .objects1701 let selected: Vec<(&ExGuid, &crate::Object<'_>)> = if checkpoint {
1486 .iter()1702 revision.objects.iter().collect()
1487 .filter(|(id, _)| checkpoint || changed.contains(id))1703 } else {
1488 .collect();1704 commit
1705 .changed
1706 .iter()
1707 .map(|id| (id, &revision.objects[id]))
1708 .collect()
1709 };
1489 // A table-of-contents manifest has one global id table (sections group objects,1710 // A table-of-contents manifest has one global id table (sections group objects,
1490 // each group with its own table); OneNote resolves every node against it.1711 // each group with its own table); OneNote resolves every node against it.
1491 let toc_table = if is_section {1712 let toc_table = if is_section {
...@@ -1535,7 +1756,10 @@ pub(crate) fn build_on(...@@ -1535,7 +1756,10 @@ pub(crate) fn build_on(
1535 if checkpoint {1756 if checkpoint {
1536 ExGuid::default()1757 ExGuid::default()
1537 } else {1758 } else {
1538 rid.unwrap()1759 sealing.previous.ok_or(Error {
1760 offset: 0,
1761 message: "A dependent revision needs the revision it follows",
1762 })?
1539 }1763 }
1540 .encode(&mut start);1764 .encode(&mut start);
1541 if !is_section {1765 if !is_section {
...@@ -1548,7 +1772,7 @@ pub(crate) fn build_on(...@@ -1548,7 +1772,7 @@ pub(crate) fn build_on(
1548 label.0.encode(&mut start);1772 label.0.encode(&mut start);
1549 }1773 }
1550 let mut manifest = Vec::new();1774 let mut manifest = Vec::new();
1551 if new_space {1775 if sealing.new_space {
1552 let mut payload = Vec::new();1776 let mut payload = Vec::new();
1553 space.encode(&mut payload);1777 space.encode(&mut payload);
1554 payload.extend_from_slice(&0_u32.to_le_bytes());1778 payload.extend_from_slice(&0_u32.to_le_bytes());
...@@ -1563,23 +1787,14 @@ pub(crate) fn build_on(...@@ -1563,23 +1787,14 @@ pub(crate) fn build_on(
1563 None,1787 None,
1564 &start,1788 &start,
1565 )?);1789 )?);
1566 // A dependent revision inherits its key, as OneNote writes it.
1567 if protection.is_some() && checkpoint {1790 if protection.is_some() && checkpoint {
1568 let key = index1791 let key = key.ok_or(Error {
1569 .spaces1792 offset: 0,
1570 .get(&space)1793 message: "Protected revisions continue a protected object space",
1571 .and_then(|space| {1794 })?;
1572 space
1573 .revisions
1574 .values()
1575 .find_map(|revision| revision.nodes.first().filter(|node| node.id == 0x7c))
1576 })
1577 .ok_or(Error {
1578 offset: 0,
1579 message: "Protected revisions continue a protected object space",
1580 })?;
1581 manifest.push(node(0x7c, key.reference, key.payload)?);1795 manifest.push(node(0x7c, key.reference, key.payload)?);
1582 }1796 }
1797 let mut stored = Vec::new();
1583 for (table, objects) in groups {1798 for (table, objects) in groups {
1584 let mut payload = Vec::new();1799 let mut payload = Vec::new();
1585 let mut group = if is_section {1800 let mut group = if is_section {
...@@ -1651,33 +1866,31 @@ pub(crate) fn build_on(...@@ -1651,33 +1866,31 @@ pub(crate) fn build_on(
1651 }1866 }
1652 }1867 }
1653 }1868 }
1654 append(&mut output, &mapped)?1869 self.append(&mapped)?
1655 } else if replacements.contains_key(&id) {1870 } else if sealing.replaced.contains(&id) {
1656 match protection {1871 let chunk = match protection {
1657 Some(protection) => {1872 Some(protection) => {
1658 append(&mut output, &protection.seal_property(bytes)?)?1873 self.append(&protection.seal_property(bytes)?)?
1659 }1874 }
1660 None => append(&mut output, bytes)?,1875 None => self.append(bytes)?,
1661 }
1662 } else {
1663 let stored = match protection {
1664 Some(protection) => protection.stored(bytes).ok_or(Error {
1665 offset: 0,
1666 message: "Protected object has no stored form",
1667 })?,
1668 None => bytes,
1669 };1876 };
1670 Chunk {1877 stored.push((id, chunk));
1671 offset: u64::try_from(1878 chunk
1672 stored.as_ptr().addr() - source.as_ptr().addr(),1879 } else {
1673 )1880 located(
1674 .unwrap(),1881 segments,
1675 length: u64::try_from(stored.len()).unwrap(),1882 match protection {
1676 }1883 Some(protection) => protection.stored(bytes).ok_or(Error {
1884 offset: 0,
1885 message: "Protected object has no stored form",
1886 })?,
1887 None => bytes,
1888 },
1889 )?
1677 };1890 };
1678 // A table-of-contents object is declared when the revision is a1891 // A table-of-contents object is declared when the revision is a
1679 // checkpoint or the object is new, and revised otherwise.1892 // checkpoint or the object is new, and revised otherwise.
1680 let declared = checkpoint || !existing.contains(&id);1893 let declared = checkpoint || sealing.created.contains(&id);
1681 if is_section {1894 if is_section {
1682 declaration.extend_from_slice(&object.jcid.to_le_bytes());1895 declaration.extend_from_slice(&object.jcid.to_le_bytes());
1683 declaration.push(flags);1896 declaration.push(flags);
...@@ -1720,9 +1933,7 @@ pub(crate) fn build_on(...@@ -1720,9 +1933,7 @@ pub(crate) fn build_on(
1720 }1933 }
1721 if is_section {1934 if is_section {
1722 group.push(node(0xb8, None, &[])?);1935 group.push(node(0xb8, None, &[])?);
1723 let group_id = allocate_list()?;1936 let (chunk, _) = self.list(&group)?;
1724 let chunk = append_list(&mut output, group_id, &group)?;
1725 counts.push((group_id, group.len()));
1726 manifest.push(node(0xb0, Some(Reference::NodeList(chunk)), &payload)?);1937 manifest.push(node(0xb0, Some(Reference::NodeList(chunk)), &payload)?);
1727 let mut overrides = vec![0; 8];1938 let mut overrides = vec![0; 8];
1728 overrides.extend_from_slice(&(!override_crc).to_le_bytes());1939 overrides.extend_from_slice(&(!override_crc).to_le_bytes());
...@@ -1751,198 +1962,575 @@ pub(crate) fn build_on(...@@ -1751,198 +1962,575 @@ pub(crate) fn build_on(
1751 }1962 }
1752 }1963 }
1753 manifest.push(node(0x1c, None, &[])?);1964 manifest.push(node(0x1c, None, &[])?);
1754 if new_space {1965 if sealing.new_space {
1755 let list_id = allocate_list()?;1966 let (chunk, tail) = self.list(&manifest)?;
1756 let chunk = append_list(&mut output, list_id, &manifest)?;
1757 counts.push((list_id, manifest.len()));
1758 let mut payload = Vec::new();1967 let mut payload = Vec::new();
1759 space.encode(&mut payload);1968 space.encode(&mut payload);
1760 let nodes = vec![1969 let (chunk, _) = self.list(&[
1761 node(0xc, None, &payload)?,1970 node(0xc, None, &payload)?,
1762 node(0x10, Some(Reference::NodeList(chunk)), &[])?,1971 node(0x10, Some(Reference::NodeList(chunk)), &[])?,
1763 ];1972 ])?;
1764 let list_id = allocate_list()?;1973 self.root_nodes
1765 let chunk = append_list(&mut output, list_id, &nodes)?;1974 .push(node(8, Some(Reference::NodeList(chunk)), &payload)?);
1766 counts.push((list_id, nodes.len()));1975 self.state.spaces.insert(space, tail);
1767 root_nodes.push(node(8, Some(Reference::NodeList(chunk)), &payload)?);
1768 } else {1976 } else {
1769 let root = store.list(store.header.root)?;1977 let mut tail = *self.state.spaces.get(&space).ok_or(Error {
1770 let space_node = root
1771 .nodes
1772 .iter()
1773 .find(|node| node.id == 8 && node.fields(store).exguid() == Ok(space))
1774 .ok_or(Error {
1775 offset: 0,
1776 message: "Object space is absent from the root list",
1777 })?;
1778 let space_list = space_node.referenced_list(store)?;
1779 let revision_node = space_list.iter().rfind(|node| node.id == 0x10).unwrap();
1780 let Some(Reference::NodeList(manifest_reference)) = revision_node.reference else {
1781 unreachable!()
1782 };
1783 let revision_list = store.list(manifest_reference)?;
1784 counts.push(append_fragment(
1785 source,
1786 &mut output,
1787 revision_list,
1788 &manifest,
1789 )?);
1790 }
1791 }
1792 let mut data_nodes = Vec::new();
1793 for (guid, payload) in payloads {
1794 if !is_section {
1795 return Err(Error {
1796 offset: 0,1978 offset: 0,
1797 message: "Embedded payloads require a section file",1979 message: "Object space is absent from the root list",
1798 });1980 })?;
1981 self.extend(&mut tail, &manifest)?;
1982 self.state.spaces.insert(space, tail);
1799 }1983 }
1800 let mut blob = vec![1984 Ok((new_rid, stored))
1801 0xe7, 0x16, 0xe3, 0xbd, 0x65, 0x26, 0x11, 0x45, 0xa4, 0xc4, 0x8d, 0x4d, 0x0b, 0x7a,
1802 0x9e, 0xac,
1803 ];
1804 let sealed = protection.map(|protection| protection.seal_file(payload));
1805 let payload = sealed.as_deref().unwrap_or(*payload);
1806 blob.extend_from_slice(&(payload.len() as u64).to_le_bytes());
1807 blob.extend_from_slice(&[0; 12]);
1808 blob.extend_from_slice(payload);
1809 blob.resize(blob.len().next_multiple_of(8), 0);
1810 blob.extend_from_slice(&[
1811 0x22, 0xa7, 0xfb, 0x71, 0x79, 0x0f, 0x0b, 0x4a, 0xbb, 0x13, 0x89, 0x92, 0x56, 0x42,
1812 0x6b, 0x24,
1813 ]);
1814 let chunk = append(&mut output, &blob)?;
1815 data_nodes.push(node(0x94, Some(Reference::Data(chunk)), guid)?);
1816 }1985 }
1817 if !data_nodes.is_empty() {1986
1987 /// Embeds payloads as file-data store objects referenced from the root file node list
1988 /// under their identities, as OneNote embeds pictures and attachments.
1989 pub(crate) fn payloads(
1990 &mut self,
1991 payloads: &[([u8; 16], &[u8])],
1992 protection: Option<&dyn Protection>,
1993 ) -> Result<()> {
1994 let mut data_nodes = Vec::new();
1995 for (guid, payload) in payloads {
1996 if self.state.file_type != FileType::Section {
1997 return Err(Error {
1998 offset: 0,
1999 message: "Embedded payloads require a section file",
2000 });
2001 }
2002 let mut blob = vec![
2003 0xe7, 0x16, 0xe3, 0xbd, 0x65, 0x26, 0x11, 0x45, 0xa4, 0xc4, 0x8d, 0x4d, 0x0b, 0x7a,
2004 0x9e, 0xac,
2005 ];
2006 let sealed = protection.map(|protection| protection.seal_file(payload));
2007 let payload = sealed.as_deref().unwrap_or(*payload);
2008 blob.extend_from_slice(&(payload.len() as u64).to_le_bytes());
2009 blob.extend_from_slice(&[0; 12]);
2010 blob.extend_from_slice(payload);
2011 blob.resize(blob.len().next_multiple_of(8), 0);
2012 blob.extend_from_slice(&[
2013 0x22, 0xa7, 0xfb, 0x71, 0x79, 0x0f, 0x0b, 0x4a, 0xbb, 0x13, 0x89, 0x92, 0x56, 0x42,
2014 0x6b, 0x24,
2015 ]);
2016 let chunk = self.append(&blob)?;
2017 data_nodes.push(node(0x94, Some(Reference::Data(chunk)), guid)?);
2018 }
2019 if data_nodes.is_empty() {
2020 return Ok(());
2021 }
1818 // Payload declarations live in the file-data store list the root list references.2022 // Payload declarations live in the file-data store list the root list references.
1819 let root = store.list(store.header.root)?;2023 match self.state.files {
1820 match root.nodes.iter().find(|node| node.id == 0x90) {2024 Some(mut tail) => {
1821 Some(reference) => {2025 self.extend(&mut tail, &data_nodes)?;
1822 let Some(Reference::NodeList(chunk)) = reference.reference else {2026 self.state.files = Some(tail);
1823 return Err(Error {
1824 offset: reference.offset,
1825 message: "File-data store reference lacks a list",
1826 });
1827 };
1828 counts.push(append_fragment(
1829 source,
1830 &mut output,
1831 store.list(chunk)?,
1832 &data_nodes,
1833 )?);
1834 }2027 }
1835 None => {2028 None => {
1836 let list_id = allocate_list()?;2029 let (chunk, tail) = self.list(&data_nodes)?;
1837 let chunk = append_list(&mut output, list_id, &data_nodes)?;2030 self.state.files = Some(tail);
1838 counts.push((list_id, data_nodes.len()));2031 self.root_nodes
1839 root_nodes.push(node(0x90, Some(Reference::NodeList(chunk)), &[])?);2032 .push(node(0x90, Some(Reference::NodeList(chunk)), &[])?);
1840 }2033 }
1841 }2034 }
2035 Ok(())
1842 }2036 }
1843 if !root_nodes.is_empty() {2037
1844 counts.push(append_fragment(2038 /// The transaction committing what was appended, and the state it leaves; none when
1845 source,2039 /// nothing was.
1846 &mut output,2040 pub(crate) fn finish(mut self) -> Result<Option<(crate::Transaction, StoreState)>> {
1847 store.list(store.header.root)?,2041 if !self.root_nodes.is_empty() {
1848 &root_nodes,2042 let nodes = std::mem::take(&mut self.root_nodes);
1849 )?);2043 let mut root = self.state.root;
2044 self.extend(&mut root, &nodes)?;
2045 self.state.root = root;
2046 }
2047 if self.counts.is_empty() {
2048 return Ok(None);
2049 }
2050 let file_type = self.state.file_type;
2051 let is_section = file_type == FileType::Section;
2052 let mut header = self.base.header;
2053 let count = u32::from_le_bytes(header[96..100].try_into().unwrap());
2054 let transactions = count.checked_add(1).ok_or(Error {
2055 offset: 96,
2056 message: "Transaction counter is exhausted",
2057 })?;
2058 let changed_bytes = count ^ transactions;
2059 let commit_byte = (31 - changed_bytes.leading_zeros()) / 8;
2060 let ceiling = transactions | ((1_u32 << (commit_byte * 8)) - 1);
2061 let mut entries = Vec::new();
2062 for (id, count) in std::mem::take(&mut self.counts) {
2063 entries.extend_from_slice(&id.to_le_bytes());
2064 entries.extend_from_slice(
2065 &u32::try_from(count)
2066 .map_err(|_| Error {
2067 offset: 0,
2068 message: "File-node count exceeds the format limit",
2069 })?
2070 .to_le_bytes(),
2071 );
2072 }
2073 let (mut log_chunk, mut used) = self.state.log;
2074 let mut checksum = self.state.log_crc;
2075 let mut crc_used = used;
2076 let mut crc_capacity = (log_chunk.length as usize - 12) & !7;
2077 let mut advance_crc = |checksum, entry: &[u8]| {
2078 if crc_used == crc_capacity {
2079 crc_used = 0;
2080 crc_capacity = 1008;
2081 }
2082 crc_used += 8;
2083 transaction_crc(checksum, entry, file_type, crc_used == crc_capacity)
2084 };
2085 for entry in entries.chunks_exact(8) {
2086 checksum = advance_crc(checksum, entry);
2087 }
2088 // Past a counter carry, sentinels up to the ceiling follow the one that commits; the
2089 // next transaction's entries overwrite them, as a reader counting sentinels expects.
2090 let committed = entries.len() + 8;
2091 let mut committed_crc = None;
2092 for _ in transactions..=ceiling {
2093 let sentinel_crc = if is_section { !checksum } else { checksum };
2094 entries.extend_from_slice(&1_u32.to_le_bytes());
2095 entries.extend_from_slice(&sentinel_crc.to_le_bytes());
2096 checksum = advance_crc(checksum, &entries[entries.len() - 8..]);
2097 committed_crc.get_or_insert(checksum);
2098 }
2099 let mut log = self.state.log;
2100 let mut remaining = entries.as_slice();
2101 while !remaining.is_empty() {
2102 let capacity = usize::try_from(log_chunk.length)
2103 .unwrap()
2104 .checked_sub(12)
2105 .map(|size| size & !7)
2106 .filter(|capacity| *capacity >= used)
2107 .ok_or(Error {
2108 offset: usize::try_from(log_chunk.offset).unwrap(),
2109 message: "Transaction fragment has no room for continuation",
2110 })?;
2111 let size = remaining.len().min(capacity - used);
2112 self.write(log_chunk.offset + used as u64, &remaining[..size]);
2113 let written = entries.len() - remaining.len();
2114 if (written + 1..=written + size).contains(&committed) {
2115 log = (log_chunk, used + committed - written);
2116 }
2117 used += size;
2118 remaining = &remaining[size..];
2119 if !remaining.is_empty() {
2120 let mut fragment = vec![0; 1024];
2121 fragment[1008..1016].copy_from_slice(&u64::MAX.to_le_bytes());
2122 let next = self.append(&fragment)?;
2123 let mut reference = next.offset.to_le_bytes().to_vec();
2124 reference.extend_from_slice(&(next.length as u32).to_le_bytes());
2125 self.write(log_chunk.offset + capacity as u64, &reference);
2126 log_chunk = next;
2127 used = 0;
2128 }
2129 }
2130 let length = self.end();
2131 header[96..100].copy_from_slice(&transactions.to_le_bytes());
2132 header[196..204].copy_from_slice(&length.to_le_bytes());
2133 header[212..228].copy_from_slice(&fresh_guid()?);
2134 header[236..252].copy_from_slice(&fresh_guid()?);
2135 let generation = u64::from_le_bytes(header[228..236].try_into().unwrap())
2136 .checked_add(1)
2137 .ok_or(Error {
2138 offset: 228,
2139 message: "File generation counter is exhausted",
2140 })?;
2141 header[228..236].copy_from_slice(&generation.to_le_bytes());
2142 self.state.stamp = crate::Stamp { header, length };
2143 self.state.log = log;
2144 self.state.log_crc = committed_crc.unwrap();
2145 Ok(Some((
2146 crate::Transaction {
2147 base: self.base,
2148 append: self.append,
2149 patches: self.patches,
2150 header,
2151 },
2152 self.state,
2153 )))
1850 }2154 }
1851 if counts.is_empty() {2155}
1852 return Ok(source.to_vec());2156
2157/// The `required` nodes of fragment `sequence` of list `id`, which `bytes` at `at` hold and
2158/// which ends its list.
2159fn nodes(
2160 bytes: &[u8],
2161 at: Chunk,
2162 id: u32,
2163 sequence: u32,
2164 required: usize,
2165) -> Result<Vec<Node<'_>>> {
2166 let fragment = Fragment::parse(bytes, at.offset as usize)?;
2167 if fragment.id != id || fragment.sequence != sequence || !fragment.next.absent() {
2168 return Err(Error {
2169 offset: at.offset as usize,
2170 message: "An appended fragment has the wrong list, sequence or successor",
2171 });
2172 }
2173 let mut nodes = Vec::new();
2174 fragment.nodes(required, &mut nodes, |_| Ok(()))?;
2175 if nodes.len() != required {
2176 return Err(Error {
2177 offset: at.offset as usize,
2178 message: "An appended fragment lacks logged nodes",
2179 });
1853 }2180 }
2181 Ok(nodes)
2182}
18542183
1855 let transactions = store.header.transaction_count.checked_add(1).ok_or(Error {2184/// A revision `Appending::revision` wrote, as `check_transaction` expects to read it back.
1856 offset: 96,2185pub(crate) struct Written<'r, 'a> {
1857 message: "Transaction counter is exhausted",2186 pub space: ExGuid,
1858 })?;2187 pub rid: ExGuid,
1859 let changed_bytes = store.header.transaction_count ^ transactions;2188 pub previous: Option<ExGuid>,
1860 let commit_byte = (31 - changed_bytes.leading_zeros()) / 8;2189 pub live: &'r LiveRevision<'a>,
1861 let ceiling = transactions | ((1_u32 << (commit_byte * 8)) - 1);2190 pub commit: &'r Commit,
1862 let mut entries = Vec::new();2191}
1863 for (id, count) in counts {2192
1864 entries.extend_from_slice(&id.to_le_bytes());2193/// Reads back what `transaction` appends to the section `before` describes, whose bytes
1865 entries.extend_from_slice(2194/// `segments` hold, and checks it stores `revisions` and `payloads` as `after` expects:
1866 &u32::try_from(count)2195/// fragments link and decode to the logged counts under the logged checksum; each declared
1867 .map_err(|_| Error {2196/// object parses, names identities its group's table holds and objects reachable after its
1868 offset: 0,2197/// revision, carries its incremental reference count and, if read-only, its MD5; roots stay
1869 message: "File-node count exceeds the format limit",2198/// unless a space is new. Validates nothing the transaction leaves as it was.
1870 })?2199pub(crate) fn check_transaction(
1871 .to_le_bytes(),2200 before: &StoreState,
1872 );2201 after: &StoreState,
2202 transaction: &crate::Transaction,
2203 segments: &[(u64, &[u8])],
2204 revisions: &[Written<'_, '_>],
2205 payloads: &[([u8; 16], &[u8])],
2206) -> Result<()> {
2207 let wrong = |message| Error { offset: 0, message };
2208 let base = transaction.base.length;
2209 // A chunk's bytes with this transaction's writes: nothing read here is patched earlier.
2210 let read = |chunk: Chunk| -> Result<Vec<u8>> {
2211 let (start, end) = (chunk.offset, chunk.offset + chunk.length);
2212 let mut bytes = if start >= base {
2213 transaction
2214 .append
2215 .get((start - base) as usize..(end - base) as usize)
2216 .ok_or(wrong("An appended chunk lies outside the transaction"))?
2217 .to_vec()
2218 } else {
2219 let (offset, segment) = segments
2220 .iter()
2221 .rfind(|(offset, _)| *offset <= start)
2222 .ok_or(wrong("A chunk lies outside the store"))?;
2223 segment
2224 .get((start - offset) as usize..(end - offset) as usize)
2225 .ok_or(wrong("A chunk lies outside the store"))?
2226 .to_vec()
2227 };
2228 for (offset, patch) in &transaction.patches {
2229 let (from, to) = (start.max(*offset), end.min(offset + patch.len() as u64));
2230 if from < to {
2231 bytes[(from - start) as usize..(to - start) as usize]
2232 .copy_from_slice(&patch[(from - offset) as usize..(to - offset) as usize]);
2233 }
2234 }
2235 Ok(bytes)
2236 };
2237 let header = &transaction.header;
2238 let count = |header: &[u8; 1024]| u32::from_le_bytes(header[96..100].try_into().unwrap());
2239 if *header != after.stamp.header
2240 || count(header) != count(&before.stamp.header) + 1
2241 || u64::from_le_bytes(header[196..204].try_into().unwrap()) != after.stamp.length
2242 || after.stamp.length != base + transaction.append.len() as u64
2243 {
2244 return Err(wrong("The transaction header does not commit its bytes"));
1873 }2245 }
1874 let mut checksum = if is_section { u32::MAX } else { 0 };2246
1875 for fragment in &store.transaction_fragments {2247 // The log: entries from where it ended, through the sentinel that commits them.
1876 checksum = transaction_crc(2248 let mut counts = BTreeMap::new();
1877 checksum,2249 let (mut chunk, mut used) = before.log;
1878 fragment.entries,2250 let mut checksum = before.log_crc;
1879 store.header.file_type,2251 loop {
1880 fragment.entries.len() + 20 > fragment.chunk.length as usize,2252 let capacity = (chunk.length as usize - 12) & !7;
1881 );2253 let bytes = read(chunk)?;
2254 if used == capacity {
2255 let next = u64::from_le_bytes(bytes[capacity..capacity + 8].try_into().unwrap());
2256 let length = u32::from_le_bytes(bytes[capacity + 8..capacity + 12].try_into().unwrap());
2257 (chunk, used) = (
2258 Chunk {
2259 offset: next,
2260 length: u64::from(length),
2261 },
2262 0,
2263 );
2264 continue;
2265 }
2266 let entry = &bytes[used..used + 8];
2267 used += 8;
2268 let id = u32::from_le_bytes(entry[..4].try_into().unwrap());
2269 let value = u32::from_le_bytes(entry[4..].try_into().unwrap());
2270 if id == 1 {
2271 if value != !checksum {
2272 return Err(wrong("The transaction log checksum does not match"));
2273 }
2274 checksum = transaction_crc(checksum, entry, FileType::Section, used == capacity);
2275 break;
2276 }
2277 checksum = transaction_crc(checksum, entry, FileType::Section, used == capacity);
2278 counts.insert(id, value as usize);
1882 }2279 }
1883 let last_log = store.transaction_fragments.last().unwrap();2280 if (chunk, used) != after.log || checksum != after.log_crc {
1884 let mut crc_used = last_log.entries.len();2281 return Err(wrong("The transaction log ends elsewhere than its state"));
1885 let mut crc_capacity = (last_log.chunk.length as usize - 12) & !7;2282 }
1886 let mut advance_crc = |checksum, entry: &[u8]| {2283
1887 if crc_used == crc_capacity {2284 // Nodes a list gained: the fragment `after` ends with, linked from where `before` ended.
1888 crc_used = 0;2285 let gained = |before: Option<&ListTail>, after: &ListTail| -> Result<Vec<u8>> {
1889 crc_capacity = 1008;2286 if counts.get(&after.id) != Some(&after.nodes) {
1890 }2287 return Err(wrong("A list's nodes disagree with the transaction log"));
1891 crc_used += 8;2288 }
1892 transaction_crc(2289 if let Some(before) = before {
1893 checksum,2290 let previous = read(before.last)?;
1894 entry,2291 if Fragment::parse(&previous, before.last.offset as usize)?.next != after.last
1895 store.header.file_type,2292 || after.fragments != before.fragments + 1
1896 crc_used == crc_capacity,2293 || after.id != before.id
1897 )2294 {
2295 return Err(wrong("An appended fragment is not linked to its list"));
2296 }
2297 }
2298 read(after.last)
1898 };2299 };
1899 for entry in entries.chunks_exact(8) {2300 for written in revisions {
1900 checksum = advance_crc(checksum, entry);2301 let Written {
2302 space,
2303 rid,
2304 live,
2305 commit,
2306 ..
2307 } = *written;
2308 let old = before.spaces.get(&space);
2309 let new = after
2310 .spaces
2311 .get(&space)
2312 .ok_or(wrong("A sealed space has no list"))?;
2313 let bytes = gained(old, new)?;
2314 let manifest = nodes(
2315 &bytes,
2316 new.last,
2317 new.id,
2318 old.map_or(0, |old| old.fragments),
2319 new.nodes - old.map_or(0, |old| old.nodes),
2320 )?;
2321 let mut manifest = manifest.iter();
2322 let mut next = || {
2323 manifest
2324 .next()
2325 .ok_or(wrong("A revision manifest is truncated"))
2326 };
2327 let mut node = next()?;
2328 if old.is_none() {
2329 let mut c = Cursor {
2330 bytes: node.payload,
2331 offset: node.offset,
2332 };
2333 if node.id != 0x14 || c.exguid()? != space {
2334 return Err(wrong("A new space's revision list names another space"));
2335 }
2336 node = next()?;
2337 }
2338 let mut c = Cursor {
2339 bytes: node.payload,
2340 offset: node.offset,
2341 };
2342 let dependency = if commit.checkpoint {
2343 ExGuid::default()
2344 } else {
2345 written
2346 .previous
2347 .ok_or(wrong("A dependent revision follows none"))?
2348 };
2349 if node.id != 0x1e
2350 || c.exguid()? != rid
2351 || c.exguid()? != dependency
2352 || c.read::<4>()? != 1_u32.to_le_bytes()
2353 || c.read::<2>()? != [0, 0]
2354 {
2355 return Err(wrong(
2356 "A revision starts with the wrong identity or dependency",
2357 ));
2358 }
2359 let mut declared = BTreeSet::new();
2360 let mut roots = BTreeMap::new();
2361 loop {
2362 let node = next()?;
2363 match node.id {
2364 0xb0 => {
2365 let Some(Reference::NodeList(at)) = node.reference else {
2366 return Err(wrong("An object group lacks its list"));
2367 };
2368 let bytes = read(at)?;
2369 let id = Fragment::parse(&bytes, at.offset as usize)?.id;
2370 let required = *counts
2371 .get(&id)
2372 .ok_or(wrong("An object group is not logged"))?;
2373 let group = nodes(&bytes, at, id, 0, required)?;
2374 let mut table = BTreeMap::new();
2375 let mut override_crc = u32::MAX;
2376 for item in &group {
2377 let mut c = Cursor {
2378 bytes: item.payload,
2379 offset: item.offset,
2380 };
2381 match item.id {
2382 0xb4 | 0x22 | 0x28 | 0xb8 => {}
2383 0x24 => {
2384 table.insert(u32::from_le_bytes(c.read()?), c.read::<16>()?);
2385 }
2386 0xa5 | 0xc5 | 0x73 => {
2387 let id = c.compact(&table)?;
2388 let object = live
2389 .revision
2390 .objects
2391 .get(&id)
2392 .ok_or(wrong("A revision declares an unknown object"))?;
2393 let jcid = u32::from_le_bytes(c.read()?);
2394 let flags = if item.id == 0x73 {
2395 None
2396 } else {
2397 Some(c.read::<1>()?[0])
2398 };
2399 let count = u32::from_le_bytes(c.read()?);
2400 if !declared.insert(id)
2401 || jcid != object.jcid
2402 || count != object.reference_count
2403 {
2404 return Err(wrong(
2405 "A declaration disagrees with its object or its count",
2406 ));
2407 }
2408 override_crc =
2409 crc(override_crc, &count.to_le_bytes(), FileType::Section);
2410 if item.id == 0x73 {
2411 continue;
2412 }
2413 let Some(Reference::Data(at)) = item.reference else {
2414 return Err(wrong("An object declaration lacks its data"));
2415 };
2416 let data = read(at)?;
2417 let stored = crate::Object {
2418 jcid,
2419 reference_count: count,
2420 data: ObjectData::Properties(&data),
2421 global_ids: Arc::new(table.clone()),
2422 };
2423 let references = stored.references()?;
2424 let expected = u8::from(!references.objects.is_empty())
2425 | (u8::from(
2426 !references.object_spaces.is_empty()
2427 || !references.contexts.is_empty(),
2428 ) << 1);
2429 if object.data != ObjectData::Properties(&data)
2430 || flags != Some(expected)
2431 || (item.id == 0xc5) != (jcid & 0x100000 != 0)
2432 || (item.id == 0xc5 && c.read::<16>()? != md5::compute(&data).0)
2433 {
2434 return Err(wrong(
2435 "An object's stored bytes differ from its revision",
2436 ));
2437 }
2438 if live.is_reachable(id)
2439 && !references
2440 .objects
2441 .iter()
2442 .all(|target| live.is_reachable(*target))
2443 {
2444 return Err(wrong(
2445 "An object references one its revision cannot reach",
2446 ));
2447 }
2448 }
2449 _ => return Err(wrong("Unexpected node in an object group")),
2450 }
2451 }
2452 let node = next()?;
2453 let mut c = Cursor {
2454 bytes: node.payload,
2455 offset: node.offset,
2456 };
2457 if node.id != 0x84
2458 || c.read::<8>()? != [0; 8]
2459 || u32::from_le_bytes(c.read()?) != !override_crc
2460 {
2461 return Err(wrong("An object group's count overrides do not match"));
2462 }
2463 }
2464 0x5a => {
2465 let mut c = Cursor {
2466 bytes: node.payload,
2467 offset: node.offset,
2468 };
2469 let id = c.exguid()?;
2470 roots.insert(u32::from_le_bytes(c.read()?), id);
2471 }
2472 0x1c => break,
2473 _ => return Err(wrong("Unexpected node in a revision manifest")),
2474 }
2475 }
2476 let expected: BTreeSet<ExGuid> = if commit.checkpoint {
2477 live.revision.objects.keys().copied().collect()
2478 } else {
2479 commit.changed.clone()
2480 };
2481 if declared != expected
2482 || (commit.checkpoint && roots != live.revision.roots)
2483 || (!commit.checkpoint && !roots.is_empty())
2484 {
2485 return Err(wrong(
2486 "A revision declares other objects or roots than it changed",
2487 ));
2488 }
1901 }2489 }
1902 for _ in transactions..=ceiling {2490
1903 let sentinel_crc = if is_section { !checksum } else { checksum };2491 if after.root.nodes != before.root.nodes {
1904 entries.extend_from_slice(&1_u32.to_le_bytes());2492 let bytes = gained(Some(&before.root), &after.root)?;
1905 entries.extend_from_slice(&sentinel_crc.to_le_bytes());2493 let added = nodes(
1906 checksum = advance_crc(checksum, &entries[entries.len() - 8..]);2494 &bytes,
2495 after.root.last,
2496 after.root.id,
2497 before.root.fragments,
2498 after.root.nodes - before.root.nodes,
2499 )?;
2500 let created = revisions
2501 .iter()
2502 .filter(|written| !before.spaces.contains_key(&written.space))
2503 .count();
2504 let spaces = added.iter().filter(|node| node.id == 8).count();
2505 if spaces != created || added.iter().any(|node| !matches!(node.id, 8 | 0x90)) {
2506 return Err(wrong(
2507 "The root list gains other nodes than new spaces declare",
2508 ));
2509 }
1907 }2510 }
1908 let mut log_chunk = last_log.chunk;2511 if !payloads.is_empty() {
1909 let mut used = last_log.entries.len();2512 let files = after
1910 let mut remaining = entries.as_slice();2513 .files
1911 while !remaining.is_empty() {2514 .as_ref()
1912 let capacity = usize::try_from(log_chunk.length)2515 .ok_or(wrong("Embedded payloads have no store"))?;
1913 .unwrap()2516 let bytes = gained(before.files.as_ref(), files)?;
1914 .checked_sub(12)2517 let declared = nodes(
1915 .map(|size| size & !7)2518 &bytes,
1916 .filter(|capacity| *capacity >= used)2519 files.last,
1917 .ok_or(Error {2520 files.id,
1918 offset: usize::try_from(log_chunk.offset).unwrap(),2521 before.files.map_or(0, |files| files.fragments),
1919 message: "Transaction fragment has no room for continuation",2522 payloads.len(),
1920 })?;2523 )?;
1921 let offset = usize::try_from(log_chunk.offset).unwrap();2524 for ((guid, payload), node) in payloads.iter().zip(&declared) {
1922 let size = remaining.len().min(capacity - used);2525 let Some(Reference::Data(at)) = node.reference else {
1923 output[offset + used..offset + used + size].copy_from_slice(&remaining[..size]);2526 return Err(wrong("A payload declaration lacks its data"));
1924 remaining = &remaining[size..];2527 };
1925 if !remaining.is_empty() {2528 let data = read(at)?;
1926 let mut fragment = vec![0; 1024];2529 let blob = crate::files::payload(&data, at.offset as usize);
1927 fragment[1008..1016].copy_from_slice(&u64::MAX.to_le_bytes());2530 if node.id != 0x94 || node.payload != guid || blob.ok() != Some(*payload) {
1928 let next = append(&mut output, &fragment)?;2531 return Err(wrong("An embedded payload differs from its declaration"));
1929 output[offset + capacity..offset + capacity + 8]2532 }
1930 .copy_from_slice(&next.offset.to_le_bytes());
1931 output[offset + capacity + 8..offset + capacity + 12]
1932 .copy_from_slice(&(next.length as u32).to_le_bytes());
1933 log_chunk = next;
1934 used = 0;
1935 }2533 }
1936 }2534 }
1937 output[96..100].copy_from_slice(&transactions.to_le_bytes());2535 Ok(())
1938 let length = u64::try_from(output.len()).unwrap();
1939 output[196..204].copy_from_slice(&length.to_le_bytes());
1940 output[212..228].copy_from_slice(&fresh_guid()?);
1941 output[236..252].copy_from_slice(&fresh_guid()?);
1942 let generation = store.header.generation.checked_add(1).ok_or(Error {
1943 offset: 228,
1944 message: "File generation counter is exhausted",
1945 })?;
1946 output[228..236].copy_from_slice(&generation.to_le_bytes());
1947 Ok(output)
1948}2536}
crates/onestore/src/write/tests/multi.rs+12-4
...@@ -186,7 +186,9 @@ fn interrupted_multi_space_publication_never_exposes_a_partial_nesting() {...@@ -186,7 +186,9 @@ fn interrupted_multi_space_publication_never_exposes_a_partial_nesting() {
186 write_limit,186 write_limit,
187 random: 1951,187 random: 1951,
188 };188 };
189 crate::commit::commit_bytes(&mut complete, SOURCE, &written).unwrap();189 crate::commit::Transaction::between(SOURCE, &written)
190 .commit(&mut complete)
191 .unwrap();
190 assert_eq!(complete.durable, written);192 assert_eq!(complete.durable, written);
191 for fail_at in 1..=complete.operation {193 for fail_at in 1..=complete.operation {
192 let mut interrupted = disk::Disk {194 let mut interrupted = disk::Disk {
...@@ -197,7 +199,9 @@ fn interrupted_multi_space_publication_never_exposes_a_partial_nesting() {...@@ -197,7 +199,9 @@ fn interrupted_multi_space_publication_never_exposes_a_partial_nesting() {
197 write_limit,199 write_limit,
198 random: 1951 + fail_at as u64,200 random: 1951 + fail_at as u64,
199 };201 };
200 crate::commit::commit_bytes(&mut interrupted, SOURCE, &written).unwrap_err();202 crate::commit::Transaction::between(SOURCE, &written)
203 .commit(&mut interrupted)
204 .unwrap_err();
201 let observed = current::current(&interrupted.durable);205 let observed = current::current(&interrupted.durable);
202 assert!(206 assert!(
203 observed == old || observed == new,207 observed == old || observed == new,
...@@ -286,7 +290,9 @@ fn repeated_multi_space_edits_cross_counter_carries_and_checkpoint_each_space()...@@ -286,7 +290,9 @@ fn repeated_multi_space_edits_cross_counter_carries_and_checkpoint_each_space()
286 write_limit: 4096,290 write_limit: 4096,
287 random: 1952,291 random: 1952,
288 };292 };
289 crate::commit::commit_bytes(&mut complete, &source, &written).unwrap();293 crate::commit::Transaction::between(&source, &written)
294 .commit(&mut complete)
295 .unwrap();
290 for fail_at in 1..=complete.operation {296 for fail_at in 1..=complete.operation {
291 let mut interrupted = disk::Disk {297 let mut interrupted = disk::Disk {
292 visible: source.clone(),298 visible: source.clone(),
...@@ -296,7 +302,9 @@ fn repeated_multi_space_edits_cross_counter_carries_and_checkpoint_each_space()...@@ -296,7 +302,9 @@ fn repeated_multi_space_edits_cross_counter_carries_and_checkpoint_each_space()
296 write_limit: 4096,302 write_limit: 4096,
297 random: 1952 + fail_at as u64,303 random: 1952 + fail_at as u64,
298 };304 };
299 crate::commit::commit_bytes(&mut interrupted, &source, &written).unwrap_err();305 crate::commit::Transaction::between(&source, &written)
306 .commit(&mut interrupted)
307 .unwrap_err();
300 let observed = current::current(&interrupted.durable);308 let observed = current::current(&interrupted.durable);
301 assert!(observed == old || observed == new, "{step}:{fail_at}");309 assert!(observed == old || observed == new, "{step}:{fail_at}");
302 }310 }
crates/onestore/tests/edit.rs+11-2
...@@ -210,7 +210,7 @@ fn confirming_visible_text_requires_flush_without_another_revision() {...@@ -210,7 +210,7 @@ fn confirming_visible_text_requires_flush_without_another_revision() {
210}210}
211211
212#[test]212#[test]
213fn confirmation_rejects_changed_or_truncated_physical_tail_before_any_write() {213fn confirmation_rejects_a_changed_header_or_length_before_any_write() {
214 let (sid, oid) = target(SOURCE);214 let (sid, oid) = target(SOURCE);
215 let mut source = SOURCE.to_vec();215 let mut source = SOURCE.to_vec();
216 source.resize(3 * 1024 * 1024 + 131, 0);216 source.resize(3 * 1024 * 1024 + 131, 0);
...@@ -223,7 +223,7 @@ fn confirmation_rejects_changed_or_truncated_physical_tail_before_any_write() {...@@ -223,7 +223,7 @@ fn confirmation_rejects_changed_or_truncated_physical_tail_before_any_write() {
223 if let trace::Event::Write(offset, bytes) = &disk.events[0] {223 if let trace::Event::Write(offset, bytes) = &disk.events[0] {
224 panic!("Confirmation wrote {} bytes at {offset}", bytes.len());224 panic!("Confirmation wrote {} bytes at {offset}", bytes.len());
225 }225 }
226 for changed in [65535, 65536, 1048575, 1048576, 2097152, source.len() - 1] {226 for changed in [96, 128, 212, 1023] {
227 disk.bytes.clone_from(&source);227 disk.bytes.clone_from(&source);
228 disk.bytes[changed] ^= 1;228 disk.bytes[changed] ^= 1;
229 disk.events.clear();229 disk.events.clear();
...@@ -232,9 +232,18 @@ fn confirmation_rejects_changed_or_truncated_physical_tail_before_any_write() {...@@ -232,9 +232,18 @@ fn confirmation_rejects_changed_or_truncated_physical_tail_before_any_write() {
232 assert_eq!(error.error.kind(), std::io::ErrorKind::ResourceBusy);232 assert_eq!(error.error.kind(), std::io::ErrorKind::ResourceBusy);
233 assert!(disk.events.is_empty());233 assert!(disk.events.is_empty());
234 }234 }
235 // Committed content changes only with the header, so the body is never compared.
236 for changed in [1024, 1048576, source.len() - 1] {
237 disk.bytes.clone_from(&source);
238 disk.bytes[changed] ^= 1;
239 disk.events.clear();
240 onestore::commit_text(&mut disk, &source, sid, oid, 0..0, "").unwrap();
241 assert!(matches!(disk.events[..], [trace::Event::Flush]));
242 }
235 for length in [source.len() - 1, source.len() + 1] {243 for length in [source.len() - 1, source.len() + 1] {
236 disk.bytes.clone_from(&source);244 disk.bytes.clone_from(&source);
237 disk.bytes.resize(length, 0);245 disk.bytes.resize(length, 0);
246 disk.events.clear();
238 let error = onestore::commit_text(&mut disk, &source, sid, oid, 0..0, "").unwrap_err();247 let error = onestore::commit_text(&mut disk, &source, sid, oid, 0..0, "").unwrap_err();
239 assert_eq!(error.state, CommitState::NotCommitted);248 assert_eq!(error.state, CommitState::NotCommitted);
240 assert!(disk.events.is_empty());249 assert!(disk.events.is_empty());
crates/onestore/tests/page.rs+73
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1use onestore::{1use onestore::{
2 ExGuid, Insertion, PageCreation, PreparedEdit, RevisionIndex, Store,2 ExGuid, Insertion, PageCreation, PreparedEdit, RevisionIndex, Store,
3 document::{Document, Kind},3 document::{Document, Kind},
4 page::{Page, PageObject},
4};5};
56
6#[path = "support/current.rs"]7#[path = "support/current.rs"]
...@@ -137,6 +138,78 @@ fn created_pages_support_title_edits_and_body_insertion() {...@@ -137,6 +138,78 @@ fn created_pages_support_title_edits_and_body_insertion() {
137 }138 }
138}139}
139140
141/// OneNote 2010 draws level-1 text 27 pt inside an outline whose table stops at entry 0.
142#[test]
143fn created_body_outlines_carry_the_indentation_table_onenote_writes() {
144 let source = onestore::create_section("pages.one", "Original", "Author").unwrap();
145 let store = Store::parse(&source).unwrap();
146 let index = RevisionIndex::parse(&store).unwrap();
147 let document = Document::parse(&index).unwrap();
148 let (space, page) = document.pages().unwrap()[0];
149 let [first] = document.active(space).unwrap().nodes[&page].children[..] else {
150 panic!()
151 };
152 let insertion = Insertion::outline(page, 72.0, 144.0, "Second", "Author").unwrap();
153 let written = PreparedEdit::insert(&source, space, &insertion).unwrap();
154 let store = Store::parse(written.as_bytes()).unwrap();
155 let index = RevisionIndex::parse(&store).unwrap();
156 let document = Document::parse(&index).unwrap();
157 let view = document.active(space).unwrap();
158 for outline in [first, insertion.object()] {
159 let Kind::Outline { indents } = &view.nodes[&outline].kind else {
160 panic!()
161 };
162 assert_eq!(indents, &[18.0, 0.0, 27.0, 27.0]);
163 }
164}
165
166#[test]
167fn a_created_title_outline_is_stored_and_read_as_onenote_stores_it() {
168 let source = onestore::create_section("pages.one", "Original", "Author").unwrap();
169 let intent = PageCreation::new(None, Some("Title"), "Author").unwrap();
170 let prepared = PreparedEdit::create_page(&source, &intent).unwrap();
171 let store = Store::parse(prepared.as_bytes()).unwrap();
172 let index = RevisionIndex::parse(&store).unwrap();
173 let document = Document::parse(&index).unwrap();
174 let view = document.active(intent.space()).unwrap();
175 let [title] = view.nodes[&intent.object()].structure[..] else {
176 panic!()
177 };
178 let [outline] = view.nodes[&title].children[..] else {
179 panic!()
180 };
181 let [paragraph] = view.nodes[&outline].children[..] else {
182 panic!()
183 };
184 let Kind::Outline { indents } = &view.nodes[&outline].kind else {
185 panic!()
186 };
187 assert_eq!(indents, &[18.0, 0.0, 27.0, 27.0]);
188 let flags = |id| -> Vec<u32> {
189 view.nodes[&id].extra[0]
190 .iter()
191 .map(|field| field.id)
192 .filter(|id| id >> 24 == 0x88)
193 .collect()
194 };
195 assert_eq!(
196 flags(outline),
197 [0x88001cf9, 0x88001cb2, 0x88001cb4, 0x88001c91, 0x88001cff]
198 );
199 assert_eq!(flags(paragraph), [0x88001cb2, 0x88001cb4]);
200 let page = Page::from_space(&document, intent.space()).unwrap();
201 let [PageObject::Title(title)] = &page.objects[..] else {
202 panic!()
203 };
204 let [outline] = &title.outlines[..] else {
205 panic!()
206 };
207 assert!(outline.title);
208 assert_eq!(outline.indents, [18.0, 0.0, 27.0, 27.0]);
209 assert_eq!(outline.min_width, Some(162.0));
210 assert_eq!(outline.layout.max_height, Some(21.6));
211}
212
140#[test]213#[test]
141fn invalid_page_intents_and_nonleading_anchors_reject_before_publication() {214fn invalid_page_intents_and_nonleading_anchors_reject_before_publication() {
142 for title in ["a\nb", "\r", "\0", "\u{fffc}", "\u{fddf}"] {215 for title in ["a\nb", "\r", "\0", "\u{fffc}", "\u{fddf}"] {
crates/onestore/tests/page_math.rs+39
...@@ -328,3 +328,42 @@ fn equations_are_written_and_read_back() {...@@ -328,3 +328,42 @@ fn equations_are_written_and_read_back() {
328 .unwrap();328 .unwrap();
329 }329 }
330}330}
331
332/// Backspacing through inline math leaves plain text in the model, but the stored text object
333/// still holds the math runs, which the ordinary text edit refuses.
334#[test]
335fn a_paragraph_emptied_of_its_inline_math_saves() {
336 let store = Store::parse(NATIVE).unwrap();
337 let index = RevisionIndex::parse(&store).unwrap();
338 let document = Document::parse(&index).unwrap();
339 let (space, before) = document
340 .pages()
341 .unwrap()
342 .into_iter()
343 .map(|(space, _)| (space, Page::from_space(&document, space).unwrap()))
344 .find(|(_, page)| page.title == "Equation controls")
345 .unwrap();
346 let mut after = before.clone();
347 let text = after
348 .objects
349 .iter_mut()
350 .filter_map(|object| match object {
351 PageObject::Outline(outline) => Some(outline),
352 _ => None,
353 })
354 .flat_map(|outline| &mut outline.paragraphs)
355 .filter_map(|paragraph| match &mut paragraph.content {
356 onestore::page::ParagraphContent::Text(text) => Some(text),
357 _ => None,
358 })
359 .find(|text| text.text.text() == "Inline 𝛼+𝛽")
360 .unwrap();
361 let plain = text.text.spans()[0].format.clone();
362 assert_ne!(plain.math, Some(true));
363 text.text = onestore::page::Paragraph::new(String::new(), plain);
364 let written = onestore::PreparedEdit::page(NATIVE, space, &after, "Author").unwrap();
365 let store = Store::parse(written.as_bytes()).unwrap();
366 let index = RevisionIndex::parse(&store).unwrap();
367 let document = Document::parse(&index).unwrap();
368 assert_eq!(Page::from_space(&document, space).unwrap(), after);
369}
crates/onestore/tests/page_tables.rs+20
...@@ -94,6 +94,13 @@ fn referenced(page: &Page) -> std::collections::BTreeSet<ExGuid> {...@@ -94,6 +94,13 @@ fn referenced(page: &Page) -> std::collections::BTreeSet<ExGuid> {
94 out94 out
95}95}
9696
97/// A new cell without its own or a sibling's indentation table takes OneNote's.
98fn with_native_indents(table: &mut Table) {
99 for cell in table.rows.iter_mut().flat_map(|row| &mut row.cells) {
100 cell.indents = vec![18.0, 0.0, 27.0, 27.0];
101 }
102}
103
97fn assert_same(written: &[u8], space: ExGuid, expected: &Page) -> Page {104fn assert_same(written: &[u8], space: ExGuid, expected: &Page) -> Page {
98 let stored = page_in(written, space);105 let stored = page_in(written, space);
99 let mut expected = expected.clone();106 let mut expected = expected.clone();
...@@ -224,6 +231,9 @@ fn cell_shading_and_indents_change_in_place() {...@@ -224,6 +231,9 @@ fn cell_shading_and_indents_change_in_place() {
224 assert_eq!(cell.shading, None);231 assert_eq!(cell.shading, None);
225 cell.shading = Some(0x0000ffff);232 cell.shading = Some(0x0000ffff);
226 let original_indents = cell.indents.clone();233 let original_indents = cell.indents.clone();
234 cell.indents.clear();
235 assert!(PreparedEdit::page(TREES, space, &after, AUTHOR).is_err());
236 let cell = &mut table_mut(&mut after).rows[0].cells[0];
227 cell.indents = vec![18.0, 0.0, 27.0, 27.0];237 cell.indents = vec![18.0, 0.0, 27.0, 27.0];
228 let written = PreparedEdit::page(TREES, space, &after, AUTHOR).unwrap();238 let written = PreparedEdit::page(TREES, space, &after, AUTHOR).unwrap();
229 let stored = assert_same(written.as_bytes(), space, &after);239 let stored = assert_same(written.as_bytes(), space, &after);
...@@ -277,6 +287,15 @@ fn a_table_nests_inside_a_cell_and_is_removed_again() {...@@ -277,6 +287,15 @@ fn a_table_nests_inside_a_cell_and_is_removed_again() {
277 .paragraphs287 .paragraphs
278 .push(holder);288 .push(holder);
279 let written = PreparedEdit::page(TREES, space, &after, AUTHOR).unwrap();289 let written = PreparedEdit::page(TREES, space, &after, AUTHOR).unwrap();
290 let ParagraphContent::Table(nested) = &mut table_mut(&mut after).rows[0].cells[1]
291 .paragraphs
292 .last_mut()
293 .unwrap()
294 .content
295 else {
296 panic!()
297 };
298 with_native_indents(nested);
280 let stored = assert_same(written.as_bytes(), space, &after);299 let stored = assert_same(written.as_bytes(), space, &after);
281 if let Some(directory) = std::env::var_os("ONESTORE_NESTED_TABLE_EXPORT") {300 if let Some(directory) = std::env::var_os("ONESTORE_NESTED_TABLE_EXPORT") {
282 let directory = std::path::PathBuf::from(directory);301 let directory = std::path::PathBuf::from(directory);
...@@ -376,6 +395,7 @@ fn a_new_table_is_created_on_a_fresh_page() {...@@ -376,6 +395,7 @@ fn a_new_table_is_created_on_a_fresh_page() {
376 body_paragraphs(&mut after).push(holder);395 body_paragraphs(&mut after).push(holder);
377 body_paragraphs(&mut after).push(cell_paragraph(&template, "After the table"));396 body_paragraphs(&mut after).push(cell_paragraph(&template, "After the table"));
378 let written = PreparedEdit::page(&source, space, &after, AUTHOR).unwrap();397 let written = PreparedEdit::page(&source, space, &after, AUTHOR).unwrap();
398 with_native_indents(table_mut(&mut after));
379 assert_same(written.as_bytes(), space, &after);399 assert_same(written.as_bytes(), space, &after);
380 if let Some(directory) = std::env::var_os("ONESTORE_TABLE_EXPORT") {400 if let Some(directory) = std::env::var_os("ONESTORE_TABLE_EXPORT") {
381 let directory = std::path::PathBuf::from(directory);401 let directory = std::path::PathBuf::from(directory);
crates/onestore/tests/page_writer.rs+11-2
...@@ -621,8 +621,10 @@ fn unsupported_model_edits_are_rejected_before_writing() {...@@ -621,8 +621,10 @@ fn unsupported_model_edits_are_rejected_before_writing() {
621 assert!(PreparedEdit::page(TREES, space, &page, "a\0b").is_err());621 assert!(PreparedEdit::page(TREES, space, &page, "a\0b").is_err());
622}622}
623623
624/// Unset run values stay unset; only the language, which MS-ONE requires on every run, comes
625/// from the insertion.
624#[test]626#[test]
625fn a_new_paragraph_with_unspecified_formatting_takes_the_insertion_default() {627fn a_new_paragraph_with_unspecified_formatting_rereads_with_only_the_required_language() {
626 let (space, mut page) = page_by_title(OUTLINES, "Move leaf down");628 let (space, mut page) = page_by_title(OUTLINES, "Move leaf down");
627 let text = onestore::page::TextObject {629 let text = onestore::page::TextObject {
628 id: new_id().unwrap(),630 id: new_id().unwrap(),
...@@ -662,7 +664,14 @@ fn a_new_paragraph_with_unspecified_formatting_takes_the_insertion_default() {...@@ -662,7 +664,14 @@ fn a_new_paragraph_with_unspecified_formatting_takes_the_insertion_default() {
662 .unwrap();664 .unwrap();
663 assert_eq!(read_back.text.text(), "Plain 東京");665 assert_eq!(read_back.text.text(), "Plain 東京");
664 let format = read_back.text.format_at(0).unwrap();666 let format = read_back.text.format_at(0).unwrap();
665 assert!(format.font.is_some() && format.font_size.is_some());667 assert!(format.language.is_some());
668 assert_eq!(
669 format,
670 &onestore::document::Format {
671 language: format.language,
672 ..Default::default()
673 }
674 );
666 assert_eq!(675 assert_eq!(
667 PreparedEdit::page(written.as_bytes(), space, &stored, AUTHOR)676 PreparedEdit::page(written.as_bytes(), space, &stored, AUTHOR)
668 .unwrap()677 .unwrap()
crates/onestore/tests/paragraph.rs+34-14
...@@ -117,12 +117,8 @@ fn joins_match_native_graphs_tags_and_inherited_character_styles() {...@@ -117,12 +117,8 @@ fn joins_match_native_graphs_tags_and_inherited_character_styles() {
117 let intent = ParagraphJoin::new(left, right, "Join author").unwrap();117 let intent = ParagraphJoin::new(left, right, "Join author").unwrap();
118 let restored = serde_json::from_value(serde_json::to_value(&intent).unwrap()).unwrap();118 let restored = serde_json::from_value(serde_json::to_value(&intent).unwrap()).unwrap();
119 assert_eq!(intent, restored);119 assert_eq!(intent, restored);
120 let edited = PreparedEdit::join(source, *sid, &restored);120 let edited = PreparedEdit::join(source, *sid, &restored)
121 if name == "Split before hyperlink" {121 .unwrap_or_else(|error| panic!("{name}: {error}"));
122 assert!(edited.is_err());
123 continue;
124 }
125 let edited = edited.unwrap_or_else(|error| panic!("{name}: {error}"));
126 let current_store = Store::parse(edited.as_bytes()).unwrap();122 let current_store = Store::parse(edited.as_bytes()).unwrap();
127 assert_eq!(123 assert_eq!(
128 current_store.header.transaction_count,124 current_store.header.transaction_count,
...@@ -296,6 +292,9 @@ fn splits_partition_native_paragraphs_at_every_scalar_boundary() {...@@ -296,6 +292,9 @@ fn splits_partition_native_paragraphs_at_every_scalar_boundary() {
296 let store = Store::parse(SOURCE).unwrap();292 let store = Store::parse(SOURCE).unwrap();
297 let index = RevisionIndex::parse(&store).unwrap();293 let index = RevisionIndex::parse(&store).unwrap();
298 let document = Document::parse(&index).unwrap();294 let document = Document::parse(&index).unwrap();
295 let native_store = Store::parse(JOIN_FIXTURES[0].0).unwrap();
296 let native_index = RevisionIndex::parse(&native_store).unwrap();
297 let native_document = Document::parse(&native_index).unwrap();
299 for case in manifest["cases"].as_array().unwrap() {298 for case in manifest["cases"].as_array().unwrap() {
300 let text: ExGuid = serde_json::from_value(case["original_text"].clone()).unwrap();299 let text: ExGuid = serde_json::from_value(case["original_text"].clone()).unwrap();
301 let paragraph: ExGuid = serde_json::from_value(case["original_paragraph"].clone()).unwrap();300 let paragraph: ExGuid = serde_json::from_value(case["original_paragraph"].clone()).unwrap();
...@@ -326,8 +325,18 @@ fn splits_partition_native_paragraphs_at_every_scalar_boundary() {...@@ -326,8 +325,18 @@ fn splits_partition_native_paragraphs_at_every_scalar_boundary() {
326 let restored = serde_json::from_value(serde_json::to_value(&intent).unwrap()).unwrap();325 let restored = serde_json::from_value(serde_json::to_value(&intent).unwrap()).unwrap();
327 assert_eq!(intent, restored);326 assert_eq!(intent, restored);
328 let edited = PreparedEdit::split(SOURCE, *sid, &restored);327 let edited = PreparedEdit::split(SOURCE, *sid, &restored);
329 if case["case"] == "Split before hyperlink" {328 // OneNote 2010 splits before or after a hyperlink and ignores Enter inside one.
330 assert!(edited.is_err());329 let link = |at: usize| {
330 expected
331 .get(at)
332 .is_some_and(|(_, style)| style["hyperlink"] == true)
333 };
334 if position > 0
335 && link(position - 1)
336 && link(position)
337 && expected[position].0 != '\u{fddf}'
338 {
339 assert!(edited.is_err(), "{} at {offset}", case["case"]);
331 continue;340 continue;
332 }341 }
333 let edited =342 let edited =
...@@ -380,6 +389,23 @@ fn splits_partition_native_paragraphs_at_every_scalar_boundary() {...@@ -380,6 +389,23 @@ fn splits_partition_native_paragraphs_at_every_scalar_boundary() {
380 "{} suffix at {offset}",389 "{} suffix at {offset}",
381 case["case"]390 case["case"]
382 );391 );
392 if case["offset_utf16"] == *offset {
393 let native = &native_document.spaces[sid];
394 let native = &native.revisions[&native.contexts[&ExGuid::default()]];
395 let new_text = serde_json::from_value(case["new_text"].clone()).unwrap();
396 assert_eq!(
397 characters(after, text),
398 characters(native, text),
399 "{}",
400 case["case"]
401 );
402 assert_eq!(
403 characters(after, intent.text_object()),
404 characters(native, new_text),
405 "{}",
406 case["case"]
407 );
408 }
383 let mut children = parent_node.children.clone();409 let mut children = parent_node.children.clone();
384 children.insert(410 children.insert(
385 children.iter().position(|id| *id == paragraph).unwrap() + 1,411 children.iter().position(|id| *id == paragraph).unwrap() + 1,
...@@ -511,9 +537,6 @@ fn export_native_paragraph_splits() {...@@ -511,9 +537,6 @@ fn export_native_paragraph_splits() {
511 let mut source = SOURCE.to_vec();537 let mut source = SOURCE.to_vec();
512 let mut written = Vec::new();538 let mut written = Vec::new();
513 for case in manifest["cases"].as_array().unwrap() {539 for case in manifest["cases"].as_array().unwrap() {
514 if case["case"] == "Split before hyperlink" {
515 continue;
516 }
517 let text: ExGuid = serde_json::from_value(case["original_text"].clone()).unwrap();540 let text: ExGuid = serde_json::from_value(case["original_text"].clone()).unwrap();
518 let store = Store::parse(&source).unwrap();541 let store = Store::parse(&source).unwrap();
519 let index = RevisionIndex::parse(&store).unwrap();542 let index = RevisionIndex::parse(&store).unwrap();
...@@ -621,9 +644,6 @@ fn export_native_paragraph_joins() {...@@ -621,9 +644,6 @@ fn export_native_paragraph_joins() {
621 let mut source = source.to_vec();644 let mut source = source.to_vec();
622 let mut manifest = Vec::new();645 let mut manifest = Vec::new();
623 for (name, left, right) in cases {646 for (name, left, right) in cases {
624 if name == "Split before hyperlink" {
625 continue;
626 }
627 let sid = *document647 let sid = *document
628 .spaces648 .spaces
629 .iter()649 .iter()
crates/onestore/tests/transaction.rs created+107
...@@ -0,0 +1,107 @@
1use onestore::{
2 CommitIo, CommitState, ExGuid, PreparedEdit, RevisionIndex, Stamp, Store,
3 document::{Document, Kind},
4};
5use std::io;
6
7/// Crosses the 65535 → 65536 counter carry on its next transaction.
8const SOURCE: &[u8] =
9 include_bytes!("../../../corpus/append/round-01/tx-65535/notebook/synthetic.one");
10
11fn text(source: &[u8]) -> (ExGuid, ExGuid) {
12 let store = Store::parse(source).unwrap();
13 let index = RevisionIndex::parse(&store).unwrap();
14 let document = Document::parse(&index).unwrap();
15 for (sid, space) in &document.spaces {
16 let revision = &space.revisions[&space.contexts[&ExGuid::default()]];
17 for (oid, node) in &revision.nodes {
18 if matches!(node.kind, Kind::RichText { .. }) {
19 return (*sid, *oid);
20 }
21 }
22 }
23 panic!("Missing text fixture")
24}
25
26/// A file that records how many bytes a commit reads and writes.
27struct Counted {
28 bytes: Vec<u8>,
29 read: usize,
30 written: usize,
31}
32
33impl CommitIo for Counted {
34 fn read_at(&mut self, offset: u64, output: &mut [u8]) -> io::Result<usize> {
35 let offset = offset as usize;
36 let count = output.len().min(self.bytes.len().saturating_sub(offset));
37 output[..count].copy_from_slice(&self.bytes[offset..offset + count]);
38 self.read += count;
39 Ok(count)
40 }
41 fn write_at(&mut self, offset: u64, bytes: &[u8]) -> io::Result<usize> {
42 let offset = offset as usize;
43 self.bytes
44 .resize(self.bytes.len().max(offset + bytes.len()), 0);
45 self.bytes[offset..offset + bytes.len()].copy_from_slice(bytes);
46 self.written += bytes.len();
47 Ok(bytes.len())
48 }
49 fn flush(&mut self) -> io::Result<()> {
50 Ok(())
51 }
52}
53
54#[test]
55fn a_transaction_reads_the_header_and_writes_only_its_edit() {
56 let (sid, oid) = text(SOURCE);
57 let edit = PreparedEdit::text(SOURCE, sid, oid, 0..0, "é").unwrap();
58 let transaction = edit.transaction();
59 let mut image = SOURCE.to_vec();
60 transaction.apply(&mut image).unwrap();
61 assert_eq!(image, edit.as_bytes());
62 let mut file = Counted {
63 bytes: SOURCE.to_vec(),
64 read: 0,
65 written: 0,
66 };
67 transaction.commit(&mut file).unwrap();
68 assert_eq!(file.bytes, edit.as_bytes());
69 assert!(file.read <= 1024 + 2, "read {} bytes", file.read);
70 let appended = edit.as_bytes().len() - SOURCE.len();
71 assert!(
72 file.written < appended + 2048,
73 "wrote {} bytes for {appended} appended",
74 file.written
75 );
76 // The file has moved past the base: nothing is written, and applying needs the base.
77 let (read, written) = (file.read, file.written);
78 let error = transaction.commit(&mut file).unwrap_err();
79 assert_eq!(error.state, CommitState::NotCommitted);
80 assert_eq!(error.error.kind(), io::ErrorKind::ResourceBusy);
81 assert_eq!(file.written, written);
82 assert!(file.read - read <= 1024);
83 assert!(transaction.apply(&mut image).is_err());
84}
85
86#[test]
87fn stamps_name_the_committed_image() {
88 let (sid, oid) = text(SOURCE);
89 let edit = PreparedEdit::text(SOURCE, sid, oid, 0..0, "x").unwrap();
90 let before = Stamp::of(SOURCE).unwrap();
91 let after = Stamp::of(edit.as_bytes()).unwrap();
92 assert_ne!(before, after);
93 assert_eq!(before.length, SOURCE.len() as u64);
94 // An unpublished tail changes the length, so a commit built without it is refused.
95 let mut tail = SOURCE.to_vec();
96 tail.extend_from_slice(&[0; 8]);
97 assert_ne!(Stamp::of(&tail).unwrap(), before);
98 let mut file = Counted {
99 bytes: tail,
100 read: 0,
101 written: 0,
102 };
103 let error = edit.commit(&mut file).unwrap_err();
104 assert_eq!(error.error.kind(), io::ErrorKind::ResourceBusy);
105 assert_eq!(file.written, 0);
106 assert!(Stamp::of(&SOURCE[..1023]).is_err());
107}
tools/canvas/README.md+4-4
...@@ -50,7 +50,7 @@ snowbound title bar · toolbar · section frame · page tabs · the page as a...@@ -50,7 +50,7 @@ snowbound title bar · toolbar · section frame · page tabs · the page as a
5050
51`--notebook FOLDER` opens a notebook laid out as OneNote lays one out. The title bar shows the application's icon and the notebook's name; below it two rows of toolbar buttons follow the Home ribbon's Basic Text group, the first nine tags and zoom (only zoom acts yet). Readable top-level sections are tabs in the notebook's order, each leaning over the next at 45° with a faint highlight along its top, and the open one on top, casting a soft shadow and framing the page and the page tabs on the right in its colour, which shades slightly down the window; a tab that opens rises to meet the frame as its outline fades in. The frame's top corners and the page's corners are rounded concentric with the window's. The open page's tab is the page's colour and joins it with rounded inside corners; subpages are indented. The interface follows the system's light or dark appearance, and in the dark one the page's paper darkens and text left at OneNote's automatic colour turns light (except on a highlight), resolved when painting so nothing re-lays out; explicit colours, highlights, pictures and ink keep theirs, except that the background art of OneNote 2010's page templates, recognised by the picture's hash, paints from bundled vector recreations drawn for the paper. Sections take their stored colour's hue at a saturation and lightness chosen for each appearance. `--ui-font FONT_FILE_OR_FOLDER` sets the interface's typeface in place of the system's; fonts are not bundled. The section's colour is also the interface accent, easing to the next section's when another opens. The search box, New Page and the page-tab toggle sit above the page tabs. Command-F searches the page titles; Enter opens the first match and Escape clears it. A page that also changed on another computer shows Keep mine / Keep theirs above it. `--section FILE TITLE` opens one section the same way at that page. Section files must be regular files; discovery rejects symbolic links.51`--notebook FOLDER` opens a notebook laid out as OneNote lays one out. The title bar shows the application's icon and the notebook's name; below it two rows of toolbar buttons follow the Home ribbon's Basic Text group, the first nine tags and zoom (only zoom acts yet). Readable top-level sections are tabs in the notebook's order, each leaning over the next at 45° with a faint highlight along its top, and the open one on top, casting a soft shadow and framing the page and the page tabs on the right in its colour, which shades slightly down the window; a tab that opens rises to meet the frame as its outline fades in. The frame's top corners and the page's corners are rounded concentric with the window's. The open page's tab is the page's colour and joins it with rounded inside corners; subpages are indented. The interface follows the system's light or dark appearance, and in the dark one the page's paper darkens and text left at OneNote's automatic colour turns light (except on a highlight), resolved when painting so nothing re-lays out; explicit colours, highlights, pictures and ink keep theirs, except that the background art of OneNote 2010's page templates, recognised by the picture's hash, paints from bundled vector recreations drawn for the paper. Sections take their stored colour's hue at a saturation and lightness chosen for each appearance. `--ui-font FONT_FILE_OR_FOLDER` sets the interface's typeface in place of the system's; fonts are not bundled. The section's colour is also the interface accent, easing to the next section's when another opens. The search box, New Page and the page-tab toggle sit above the page tabs. Command-F searches the page titles; Enter opens the first match and Escape clears it. A page that also changed on another computer shows Keep mine / Keep theirs above it. `--section FILE TITLE` opens one section the same way at that page. Section files must be regular files; discovery rejects symbolic links.
5252
53The `ui` crate builds every frame from the application's state. A cache keyed by stable box ids keeps hover, press, focus, scroll and animation values, and the previous frame's layout routes the frame's input before building, so a frame answers input one layout late and paints with its own. Sizes are solved per axis after building: fixed, label-sized, a fraction of an ancestor, or the sum of children, with overflow shared out by each box's strictness. The page is a custom box: `ui` routes it the pointer, wheel, key and input-method events that land on it, in order, and the host hands them to `PageView` and paints the page in a clipped layer of the same frame. Page scrollbars are `ui` widgets over that box; the canvas reports only its scroll bounds. Text fields edit through `draw::edit`, as the page does, so keys, clicks and drags select and move the same way in both. Work the frame asks for (opening pages or sections, page requests, saving) runs after the frame is painted and requests the frame that shows it.53The `ui` crate builds every frame from the application's state. A cache keyed by stable box ids keeps hover, press, focus, scroll and animation values, and the previous frame's layout routes the frame's input before building, so a frame answers input one layout late and paints with its own. Sizes are solved per axis after building: fixed, label-sized, a fraction of an ancestor, or the sum of children, with overflow shared out by each box's strictness. The page is a custom box: `ui` routes it the pointer, wheel, key and input-method events that land on it, in order, and the host hands them to `PageView` and paints the page in a clipped layer of the same frame. Page scrollbars are `ui` widgets over that box; the canvas reports only its scroll bounds. Text fields edit through `draw::edit`, as the page does, so keys, clicks and drags select and move the same way in both. Work the frame asks for (page requests, saving) runs after the frame is painted and requests the frame that shows it. Opening a section or page reads it on a thread of its own while the current page stays live; the newest read is laid out and replaces the page once the pictures it shows first are drawn, or after 200 ms.
5454
55A covered window receives no redraws, so interaction can be scripted: `SNOWBOUND_REPLAY` names a file of `move X Y`, `press`, `release`, `wheel DX DY`, `key NAME`, `type TEXT`, `modifiers [shift] [command]`, `wait MS`, `snapshot PNG_PATH` and `appearance light|dark` lines in logical pixels, and each step and every 16 ms of a wait draws a frame. Snapshots omit the window's traffic lights, which AppKit draws. Replays edit and save like a user, so point them at a copy of a notebook.55A covered window receives no redraws, so interaction can be scripted: `SNOWBOUND_REPLAY` names a file of `move X Y`, `press`, `release`, `wheel DX DY`, `key NAME`, `type TEXT`, `modifiers [shift] [command]`, `wait MS`, `snapshot PNG_PATH` and `appearance light|dark` lines in logical pixels, and each step and every 16 ms of a wait draws a frame. Snapshots omit the window's traffic lights, which AppKit draws. Replays edit and save like a user, so point them at a copy of a notebook.
5656
...@@ -66,7 +66,7 @@ The `draw` crate owns the renderer so the page and application chrome share one...@@ -66,7 +66,7 @@ The `draw` crate owns the renderer so the page and application chrome share one
6666
67Glyph coverage uses quarter-pixel phases with the vertical sign converted to Swash's Y-up coordinates. The `draw` GPU readback test checks nine successive quarter-pixel translations by their linear-light ink centroids, then requires identical pixels on repaint, cache eviction and renderer reconstruction on the same device. The native caret experiment exposed the previous inverted vertical phase; correcting raster placement does not change line metrics or wrapping.67Glyph coverage uses quarter-pixel phases with the vertical sign converted to Swash's Y-up coordinates. The `draw` GPU readback test checks nine successive quarter-pixel translations by their linear-light ink centroids, then requires identical pixels on repaint, cache eviction and renderer reconstruction on the same device. The native caret experiment exposed the previous inverted vertical phase; correcting raster placement does not change line metrics or wrapping.
6868
69Image textures have a separate 64 MiB / 256-entry cache. Offscreen images do not upload or consume the active-frame budget; a frame whose visible images exceed that budget returns an error. Under pressure, eviction preserves every image needed by the current frame. PNG/JPEG decoding checks encoded size, dimensions and decoded output size, with a best-effort codec allocation limit. These limits do not bound all CPU buffers or driver allocations. Images use linear filtering of premultiplied, linear-light colors; glyphs retain their own sampling/blending policy. Decoded color values are currently interpreted as sRGB; ICC and orientation metadata are not transformed by this experiment.69Image textures have a separate 64 MiB / 256-entry cache. Offscreen images do not upload or consume the active-frame budget; a frame whose visible images exceed that budget returns an error. Under pressure, eviction preserves every image needed by the current frame, and a texture goes when every copy of its image has. PNG/JPEG decoding reads the size from the header first, checks encoded size and dimensions against a 256 MiB decoding limit with a best-effort codec allocation limit, and shrinks the picture to the size asked for, filtering premultiplied values in sRGB encoding. These limits do not bound all CPU buffers or driver allocations. Images use linear filtering of premultiplied, linear-light colors; glyphs retain their own sampling/blending policy. Decoded color values are currently interpreted as sRGB; ICC and orientation metadata are not transformed by this experiment.
7070
71## Note tags71## Note tags
7272
...@@ -138,7 +138,7 @@ Input tracing logs Winit keyboard/IME, pointer, modifier and focus events, Acces...@@ -138,7 +138,7 @@ Input tracing logs Winit keyboard/IME, pointer, modifier and focus events, Acces
138138
139This integration mode keeps imported objects read-only and opens a separate editable canvas to their left. The initial notes width defaults to 240 pt in this mode, and the reference starts 24 pt to its right. Existing text-file and width arguments also work. Typing, outline creation/movement, paste and undo operate on the temporary notes. The close guard protects those notes. Reference text is rendered but is not exposed through the host’s editable accessibility fields.139This integration mode keeps imported objects read-only and opens a separate editable canvas to their left. The initial notes width defaults to 240 pt in this mode, and the reference starts 24 pt to its right. Existing text-file and width arguments also work. Typing, outline creation/movement, paste and undo operate on the temporary notes. The close guard protects those notes. Reference text is rendered but is not exposed through the host’s editable accessibility fields.
140140
141The host and offscreen renderer both use `canvas::gpu::page::PageScene`. The live scene retains decoded image assets and reads current geometry from `CanvasEditor`; the static reference scene owns its imported content. Layout and image decoding happen on construction; drawing reuses those caches. Scene coordinates stay in source document points, while the comparison example applies its captured margin normalization through the viewport. All three shared-scene outputs are pixel-identical to their preceding title-rendering captures. Decoded image residency is capped at 64 MiB per scene, in addition to the renderer’s GPU cache budget; encoded source payloads and temporary decoder allocations are separate.141The host and offscreen renderer both use `canvas::gpu::page::PageScene`. The live scene reads current geometry from `CanvasEditor`; the static reference scene owns its imported content. Layout happens on construction and drawing reuses it. Pictures only read their headers then: before each frame, `update_pictures` decodes those in view, and half a view around it, on the scene's picture thread at their shown size in device pixels (in half-power-of-two zoom steps, 150 ms after the zoom settles), and paints each once it lands; one that fails to decode paints as a placeholder. Pictures in view shrink together to fit 32 MiB, those around it decode with what room is left, and those unseen longest are let go past it, so no number or size of pictures fails a page. `settle` waits for the whole page, for offscreen drawing. The stored bytes stay shared with the page model and save unchanged. Scene coordinates stay in source document points, while the comparison example applies its captured margin normalization through the viewport. All three shared-scene outputs are pixel-identical to their preceding title-rendering captures. Decoded pictures are capped at 32 MiB per scene, in addition to the renderer’s GPU cache budget; encoded source payloads and temporary decoder allocations are separate.
142142
143The host sleeps its caret timer while macOS reports the window occluded and requests a redraw when visibility returns. Input tracing includes draw attempts, surface occlusion/timeouts and submitted presents; submission is not a measurement of display completion. Startup failures propagate a nonzero exit code after the event loop exits.143The host sleeps its caret timer while macOS reports the window occluded and requests a redraw when visibility returns. Input tracing includes draw attempts, surface occlusion/timeouts and submitted presents; submission is not a measurement of display completion. Startup failures propagate a nonzero exit code after the event loop exits.
144144
...@@ -150,7 +150,7 @@ The host sleeps its caret timer while macOS reports the window occluded and requ...@@ -150,7 +150,7 @@ The host sleeps its caret timer while macOS reports the window occluded and requ
150150
151`--page` opens supported body outlines and title text as temporary editable objects in their source positions. It accepts a section copy and exact page title, without the separate text-file/width arguments. Title text supports editing, wrapping, IME and undo, and remains present when empty. Date text follows its height; clicking the date or time opens the macOS date control and commits into the same undo history. Clicking a picture selects it with OneNote's dashed border, tint and eight handles. Dragging it moves it on the page grid (Option places it freely); edge handles stretch one axis and corner handles keep the aspect ratio, anchored at the opposite side. Background pictures pass clicks through. Moves and resizes share the undo history. Character editing, selection, IME and undo use the same outline model as new pages. Existing structured or tagged paragraphs accept in-paragraph edits; their structural split/join remains restricted. Newly created flat outlines support the full structural workflow. The app never writes the section file.151`--page` opens supported body outlines and title text as temporary editable objects in their source positions. It accepts a section copy and exact page title, without the separate text-file/width arguments. Title text supports editing, wrapping, IME and undo, and remains present when empty. Date text follows its height; clicking the date or time opens the macOS date control and commits into the same undo history. Clicking a picture selects it with OneNote's dashed border, tint and eight handles. Dragging it moves it on the page grid (Option places it freely); edge handles stretch one axis and corner handles keep the aspect ratio, anchored at the opposite side. Background pictures pass clicks through. Moves and resizes share the undo history. Character editing, selection, IME and undo use the same outline model as new pages. Existing structured or tagged paragraphs accept in-paragraph edits; their structural split/join remains restricted. Newly created flat outlines support the full structural workflow. The app never writes the section file.
152152
153`PageScene::from_page` constructs the editor-owned page and decodes its image assets. Imported paint order stays with the editor; each text slot invokes the host’s outline painter. Movement reads the current model geometry without decoding images again. New outlines draw after the imported page. The close guard compares all object layouts, editable text and the creation timestamp with the opening state, including displacement that remains after a title is shortened.153`PageScene::from_page` constructs the editor-owned page and reads its pictures' headers. Imported paint order stays with the editor; each text slot invokes the host’s outline painter. Movement reads the current model geometry and keeps each picture's raster. New outlines draw after the imported page. The close guard compares all object layouts, editable text and the creation timestamp with the opening state, including displacement that remains after a title is shortened.
154154
155The app's private integration test exercises the actual outline painter and accessibility consumer for each imported outline, then verifies text restoration through undo:155The app's private integration test exercises the actual outline painter and accessibility consumer for each imported outline, then verifies text restoration through undo:
156156
tools/test_page_creation.py+3-1
...@@ -16,9 +16,11 @@ class PageCreationTest(unittest.TestCase):...@@ -16,9 +16,11 @@ class PageCreationTest(unittest.TestCase):
16 compare(fixture / 'candidate', fixture / 'cold')16 compare(fixture / 'candidate', fixture / 'cold')
1717
18 def test_created_pages_and_both_writers_survive_native_cold_reopens(self):18 def test_created_pages_and_both_writers_survive_native_cold_reopens(self):
19 for source, capture in [('candidate', 'cold'), ('native/notebook', 'native/cold'), ('followup', 'followup/cold')]:19 for source, capture in [('candidate', 'cold'), ('native/notebook', 'native/cold')]:
20 with self.subTest(source=source):20 with self.subTest(source=source):
21 compare(FIXTURE / source, FIXTURE / capture)21 compare(FIXTURE / source, FIXTURE / capture)
22 # OneNote refreshes the renamed titles' stale section metadata copies.
23 self.assertEqual(compare(FIXTURE / 'followup', FIXTURE / 'followup/cold', refresh_metadata=True), 2)
2224
23 def test_duplicate_titles_keep_distinct_pages_through_native_and_rust_edits(self):25 def test_duplicate_titles_keep_distinct_pages_through_native_and_rust_edits(self):
24 with TemporaryDirectory() as temporary:26 with TemporaryDirectory() as temporary:
tools/test_page_movement.py+1-1
...@@ -111,7 +111,7 @@ class PageMovementTest(unittest.TestCase):...@@ -111,7 +111,7 @@ class PageMovementTest(unittest.TestCase):
111 with self.assertRaises(AssertionError):111 with self.assertRaises(AssertionError):
112 compare(fixture / 'notebook', fixture / 'cold')112 compare(fixture / 'notebook', fixture / 'cold')
113 refreshed = compare(fixture / 'notebook', fixture / 'cold',113 refreshed = compare(fixture / 'notebook', fixture / 'cold',
114 refresh_metadata_levels=phase == '02-promoted-parent')114 refresh_metadata=phase == '02-promoted-parent')
115 self.assertEqual(refreshed, int(phase == '02-promoted-parent'))115 self.assertEqual(refreshed, int(phase == '02-promoted-parent'))
116 output = temporary / phase116 output = temporary / phase
117 subprocess.run([EXPORTER, fixture / 'notebook/Lifecycle.one', output], check=True)117 subprocess.run([EXPORTER, fixture / 'notebook/Lifecycle.one', output], check=True)
tools/verify_page_creation.py+7-8
...@@ -14,7 +14,7 @@ from document_model import EXPORTER, ordered_pages, view...@@ -14,7 +14,7 @@ from document_model import EXPORTER, ordered_pages, view
14compare_document = runpy.run_path(str(Path(__file__).with_name('verify-document.py')))['compare']14compare_document = runpy.run_path(str(Path(__file__).with_name('verify-document.py')))['compare']
1515
1616
17def compare(source, capture, *, refresh_metadata_levels=False):17def compare(source, capture, *, refresh_metadata=False):
18 files = list(source.glob('*.one'))18 files = list(source.glob('*.one'))
19 assert len(files) == 119 assert len(files) == 1
20 with TemporaryDirectory() as temporary:20 with TemporaryDirectory() as temporary:
...@@ -53,13 +53,12 @@ def compare(source, capture, *, refresh_metadata_levels=False):...@@ -53,13 +53,12 @@ def compare(source, capture, *, refresh_metadata_levels=False):
53 series = expected[series_id]53 series = expected[series_id]
54 existing = [p for p in series['extra'][0] if p['id'] == 0x24003442]54 existing = [p for p in series['extra'][0] if p['id'] == 0x24003442]
55 added = [p for p in observed['nodes'][series_id]['extra'][0] if p['id'] == 0x24003442]55 added = [p for p in observed['nodes'][series_id]['extra'][0] if p['id'] == 0x24003442]
56 if existing and refresh_metadata_levels:56 if existing and refresh_metadata:
57 for copy in existing[0]['value']['Objects']:57 for copy in existing[0]['value']['Objects']:
58 cached = expected[copy]['kind']58 assert expected[copy]['kind']['type'] == 'Metadata'
59 assert cached['type'] == 'Metadata'59 current = metadata[copy][1]['kind']
60 level = metadata[copy][1]['kind']['level']60 refreshed += expected[copy]['kind'] != current
61 refreshed += cached['level'] != level61 expected[copy]['kind'] = current
62 cached['level'] = level
63 if existing or not added:62 if existing or not added:
64 continue63 continue
65 copies = []64 copies = []
...@@ -75,7 +74,7 @@ def compare(source, capture, *, refresh_metadata_levels=False):...@@ -75,7 +74,7 @@ def compare(source, capture, *, refresh_metadata_levels=False):
75 series['extra'][0].append(added[0])74 series['extra'][0].append(added[0])
76 assert original['roots'] == observed['roots']75 assert original['roots'] == observed['roots']
77 assert expected == observed['nodes'], sid76 assert expected == observed['nodes'], sid
78 print(f'Passed: {len(pages)} ordered pages and preserved active graphs; {filled} missing section metadata copies filled natively; {refreshed} section metadata levels refreshed')77 print(f'Passed: {len(pages)} ordered pages and preserved active graphs; {filled} missing section metadata copies filled natively; {refreshed} section metadata copies refreshed')
79 return refreshed78 return refreshed
8079
8180