authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-13 01:35:55-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-25 20:26:20-07:00
log94b162d4813c318a27b96cc9f1923bd80c21c1d6
tree99056cb9a0b9ca3d42b80f18a50740a5c699dbd5
parent3d72c72fcc4bf5ea8b86377b6117843fea073b61
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

feat: notebook structure over SMB, nested-table page copies, run language

`Notebook` now runs over a `Storage` trait: a mounted directory or an SMB share (`Notebook::open_smb`), so sections and groups are created, renamed, coloured, ordered and deleted on the share too. The native-compatible SMB client gained file creation, directory creation, rename (`FileRenameInformation`), delete-on-close and header placement (`onestore::place` on any `CommitIo`); `smb::tests::live_structure` runs against the Samba lab VM through `tools/test_smb_structure.py`. Page copies cover nested tables and paragraph subtrees inside new cells: the place pass builds tables round by round as their holders appear, and a run's language id is a text attribute the formatting writer sets. Pages whose indent levels only an outline group can carry refuse to copy, since the model holds no outline groups. `corpus/page-copy` grows to seventeen copied pages. Assisted-by: claude-fable-5.1

77 files changed, 865 insertions(+), 191 deletions(-)

corpus/page-copy/README.md+4-1
...@@ -6,7 +6,10 @@...@@ -6,7 +6,10 @@
6`Section::import_page` copied one page from each of eight earlier rows (text6`Section::import_page` copied one page from each of eight earlier rows (text
7with an attachment and its icon, a table, an inserted picture, a page-level7with an attachment and its icon, a table, an inserted picture, a page-level
8ink drawing, note tags, bullets and numbering, equations, paragraph8ink drawing, note tags, bullets and numbering, equations, paragraph
9formatting), each as a page creation and a save queued through the replica9formatting) and the `table-edit/nested` pages whose indent levels the model
10can express (nested tables, cell subtrees, mixed languages; four outdent
11pages need outline groups and refuse), each as a page creation and a save
12queued through the replica
10and published to the file; the last copy (paragraph formatting) was then deleted permanently13and published to the file; the last copy (paragraph formatting) was then deleted permanently
11through `Section::delete_pages` (`expected-count.txt` is the page count14through `Section::delete_pages` (`expected-count.txt` is the page count
12left). Every object carries a fresh identity (`Page::copy`), paragraph15left). Every object carries a fresh identity (`Page::copy`), paragraph
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/candidate/expected-count.txt+1-1
...@@ -1 +1 @@...@@ -1 +1 @@
19
\ No newline at end of file
117
\ No newline at end of file
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-1C0F2CDE -ExpectedPages 9 -KeepOpen", "exit": 0, "stdout": "Read 1 sections and 9 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-33218F72 -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-1c0f2cde", "hostname": "ONE-M6-1C0F2CDE"}1{"name": "m6-33218f72", "hostname": "ONE-M6-33218F72"}
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/notebook/expected-count.txt+1-1
...@@ -1 +1 @@...@@ -1 +1 @@
19
\ No newline at end of file
117
\ No newline at end of file
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-1C0F2CDE", 4 "hostname": "ONE-M6-33218F72",
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="{CA49439A-5FEF-43A6-AB60-E8C051A59087}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-13T07:34:46.000Z" color="#B49EDE"><one:Section name="copies" ID="{6D7B6C59-2E45-0D8F-286E-4FD72999E63A}{1}{B0}" path="C:\one-tests\runs\capture\notebook\copies.one" lastModifiedTime="2026-09-13T07:34:46.000Z" color="#8AA8E4"><one:Page ID="{8BB0D046-3BC7-0CE9-33B2-04F7D5E9C6FD}{1}{B0}" name="Destination" dateTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:34:35.000Z" pageLevel="1"/><one:Page ID="{C12C0ECF-DDF8-0A21-11BE-3074429542E0}{1}{B0}" name="Before the file" dateTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:34:35.000Z" pageLevel="1"/><one:Page ID="{91EAB23E-CBE0-0D18-1A25-23B9CBC39112}{1}{B0}" name="Before the table" dateTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:34:35.000Z" pageLevel="1"/><one:Page ID="{B5A45771-4A39-05F8-1953-4F2F9B2B33A5}{1}{B0}" name="Before the picture" dateTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:34:36.000Z" pageLevel="1"/><one:Page ID="{B28A26BE-5DF2-0214-1827-B1747FE95ECC}{1}{B0}" name="Beside the drawing" dateTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:34:36.000Z" pageLevel="1"/><one:Page ID="{5C003E48-8717-05C2-096E-6762316BC0A0}{1}{B0}" name="Open task" dateTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:34:36.000Z" pageLevel="1"/><one:Page ID="{C5E005CB-753E-07E2-0C53-CD40FBB64A63}{1}{B0}" name="Bullet item" dateTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:34:36.000Z" pageLevel="1"/><one:Page ID="{5A5E0FDA-1644-0F00-0C45-8D72178B2031}{1}{B0}" name="Equations below" dateTime="2026-09-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:34:36.000Z" pageLevel="1"/><one:Page ID="{D4C06049-3C91-0190-0C2E-1BB1EBA49EBA}{1}{B0}" name="A notebook created in Rust." dateTime="2026-09-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:34:36.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="{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>
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": "Equations below - 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-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-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="{5A5E0FDA-1644-0F00-0C45-8D72178B2031}{1}{B0}" name="Equations below" dateTime="2026-09-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:34:36.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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{5A5E0FDA-1644-0F00-0C45-8D72178B2031}{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-13T07:33:15.000Z" objectID="{75AB7537-48D1-0C4C-297D-B4D4CFA87CA2}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{D6CEF286-AFD3-0A03-0655-BA825F09BE5C}{1}{B0}" alignment="left"><one:T><![CDATA[Equations below]]></one:T></one:OE><one:OE creationTime="2026-09-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{111AB493-1B15-0171-1BEC-C517BF808F15}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{E963BDE9-C0E4-02BE-2D54-23989FC58846}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{869A34E1-759D-03DC-1F28-DE83A586BDD6}{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="{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>
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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{877DD34D-81AC-038C-0477-B3E0645DD63E}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{CD4EC150-40D1-074B-1157-F388604EEBE0}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{1B908B9B-F5DF-0E47-3861-D55838C5AB28}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{2A2E6B30-0040-052F-26F1-91BABCAE46B2}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{3110679E-A6C8-08CA-14E7-D38AABF07619}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{58A7316B-7529-03F0-2F54-20F513A79572}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{BFE82679-7A72-09C5-266D-FBEC819E237D}{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>
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": "Open task - 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-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-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="{5C003E48-8717-05C2-096E-6762316BC0A0}{1}{B0}" name="Open task" dateTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:34:36.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-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{5C003E48-8717-05C2-096E-6762316BC0A0}{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-13T07:33:15.000Z" objectID="{EA96F1EA-D912-043B-3585-EF6DB73F7D60}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{2B4A358D-B65A-0B30-167C-A3C772956F2F}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{30380DA0-EF03-0A04-03AD-6F50C3F095CB}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{26502F9C-1811-0C5C-23BE-395D96FFF797}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{B17CBFD9-E3D1-0CC8-0837-B615ED83C6E5}{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="{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
3AAAAAElFTkSuQmCC
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>
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": "Destination - 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-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+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="{8BB0D046-3BC7-0CE9-33B2-04F7D5E9C6FD}{1}{B0}" name="Destination" dateTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:34: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-13T07:33:14.000Z" objectID="{6D7B6C59-2E45-0D8F-286E-4FD72999E63A}{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-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{6D7B6C59-2E45-0D8F-286E-4FD72999E63A}{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="{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
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
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>
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": "Before the table - 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-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+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="{91EAB23E-CBE0-0D18-1A25-23B9CBC39112}{1}{B0}" name="Before the table" dateTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:34:35.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-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{91EAB23E-CBE0-0D18-1A25-23B9CBC39112}{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-13T07:33:14.000Z" objectID="{899FA9AA-6E30-05C6-0039-C1174852FBAA}{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-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{03DB4753-6DD9-0476-0A44-807F1E239611}{1}{B0}" alignment="left"><one:T><![CDATA[Before the table]]></one:T></one:OE><one:OE creationTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{CC36F460-50AA-0CAC-185C-B2DD3405EC23}{1}{B0}" alignment="left"><one:Table bordersVisible="true" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{F406EA96-EABF-0750-358B-13B077B8877C}{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="{B57E3F32-D364-012F-129A-7A8AFA0A8DE1}{1}{B0}" lastModifiedTime="2026-09-13T07:33:14.000Z"><one:Cell lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{8B99B904-69FB-0ABF-25D8-6EDED1F41EED}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{2F0315A5-D396-09FD-0772-968EDD6113D6}{1}{B0}" alignment="left"><one:T><![CDATA[Name]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{58E45041-62CE-0EFA-3F22-C4509482E3BF}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{0F92F8F0-F5F6-0CCA-3E1C-10A217EA11EF}{1}{B0}" alignment="left"><one:T><![CDATA[Value]]></one:T></one:OE></one:OEChildren></one:Cell></one:Row><one:Row objectID="{6D9AA72E-D225-0D46-3D00-5B12D99343C3}{1}{B0}" lastModifiedTime="2026-09-13T07:33:14.000Z"><one:Cell lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{2555B7CF-F12A-013D-0652-4CA9AEF5284C}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{A0E15DEA-0A8D-0F42-12FA-36CC54EA289A}{1}{B0}" alignment="left"><one:T><![CDATA[Rust &#129408;]]></one:T></one:OE></one:OEChildren></one:Cell><one:Cell lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{7ED49F5B-8C93-0208-16B6-8816DB6B4151}{1}{B0}"><one:OEChildren><one:OE creationTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{D60ED3D2-2081-02A2-1EC5-A3D1190448EB}{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-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{F18DD402-3CA3-01AB-32DC-D1D5A8545B7F}{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="{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>
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": "Beside the drawing - 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-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-6
...@@ -1,7 +1,4 @@...@@ -1,7 +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="{B28A26BE-5DF2-0214-1827-B1747FE95ECC}{1}{B0}" name="Beside the drawing" dateTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:34:36.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-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{B28A26BE-5DF2-0214-1827-B1747FE95ECC}{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-13T07:33:14.000Z" objectID="{BA59696B-EAE1-0771-0BCB-5878F45A65F8}{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-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{38FEF76B-56AE-0456-1B54-A2C668C24685}{1}{B0}" alignment="left"><one:T><![CDATA[Beside the drawing]]></one:T></one:OE></one:OEChildren></one:Outline><one:InkDrawing lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{501D4164-CA81-04D3-1BDC-D2CA0006F088}{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="{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
3URK/7HHMzCKxQYkOuxMs7nqhWM9UiuaXxU+PBvi60uGbIsRmbacNslhJmh1OQDQGbBACEQZIEEUj 3style='font-weight:bold'>Target &#129408; é</span> <a
4RiMJSBBE/wFFI0YjBQgLZU9mZ2hkaRkUMggAgB4CIeLiQTMIAPAQAovF4kFkAxVGAEAKXAWH8gln 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>
5jCj7wnvCe8IAh/B8h4Pke8I7wnvCQAUURgBAAAAJBAUURgAAAAASABEgKFNtArpWcU+AsrjC8JNY
6RQo/QCNkARXUNSA7oAMVRgBACj9AIm9GCC70WAqACQEKTAIPUq7GcA5CJYzgBRRGAIAAAAkEBRRG
7AAAAABIAESAJY0kvgp5dSp0WwhnX6NwFCj9AI2QBFdQ1IDugAxVGAEAKP0Aib0YILvRYCoA=</one:Data></one:InkDrawing></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": "Before the picture - 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-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+4-3
...@@ -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="{B5A45771-4A39-05F8-1953-4F2F9B2B33A5}{1}{B0}" name="Before the picture" dateTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:34:36.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-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{B5A45771-4A39-05F8-1953-4F2F9B2B33A5}{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-13T07:33:14.000Z" objectID="{6323EB68-A315-07CD-37C1-E46DAF4BAB42}{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-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{078AE9E1-0253-0D93-182A-750B047E4D60}{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-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{4DBAC23A-2DBC-0548-3811-E4D1B951886F}{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="{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
3AAAAAElFTkSuQmCC 3style='font-weight:bold'>Target &#129408; é</span> <a
4</one:Data></one:Image></one:OE><one:OE creationTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{79D39D0E-8F58-05A6-1C44-0CB6265FFE97}{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: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": "Before the file - 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-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+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="{C12C0ECF-DDF8-0A21-11BE-3074429542E0}{1}{B0}" name="Before the file" dateTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:34:35.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-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{C12C0ECF-DDF8-0A21-11BE-3074429542E0}{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-13T07:33:14.000Z" objectID="{A4D24969-1B13-0F34-3174-834AD8FF8832}{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-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{6D8ADE9E-3EE4-03A9-2EC9-0A80C321B126}{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-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{03AD9394-425B-0B9C-0D27-CA384DFFCEBF}{1}{B0}" alignment="left"><one:InsertedFile pathCache="C:\Users\clover\AppData\Local\Microsoft\OneNote\14.0\OneNoteOfflineCache_Files\6d823ba5-6b7e-4df3-947b-a1047440dfa2.txt" preferredName="notes 🦀.txt"/></one:OE><one:OE creationTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{A6E026B4-3D17-078E-20A3-50C86A85A2AB}{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="{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;
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-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": "Bullet item - 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-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+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="{C5E005CB-753E-07E2-0C53-CD40FBB64A63}{1}{B0}" name="Bullet item" dateTime="2026-09-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:34:36.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-13T07:33:14.000Z" lastModifiedTime="2026-09-13T07:33:14.000Z" objectID="{C5E005CB-753E-07E2-0C53-CD40FBB64A63}{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-13T07:33:15.000Z" objectID="{04550845-DFD8-0D15-199D-14BBB6068FC4}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{FADAD1EB-13D4-0679-1470-A27D6E3C4C82}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{D1386D56-B57F-01DD-3DCE-AE2F7E41CF7B}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{8F5A5D13-ADD9-0514-013E-D1A0CBA1E68F}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{448444D9-663C-0454-150E-544F2D29DAED}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{AB4C6543-D5B5-02F2-18CD-1EA3C75D0B5F}{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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{1E15DFFF-2CEC-03FA-0FEF-75E2303FB336}{1}{B0}" alignment="left"><one:T><![CDATA[Nested plain]]></one:T></one:OE></one:OEChildren></one:OE><one:OE creationTime="2026-09-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{49D667B4-861E-0253-29DB-8BA25CF21B9E}{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="{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>
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": "A notebook created in Rust. - 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-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+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="{D4C06049-3C91-0190-0C2E-1BB1EBA49EBA}{1}{B0}" name="A notebook created in Rust." dateTime="2026-09-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:34:36.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-13T07:33:15.000Z" lastModifiedTime="2026-09-13T07:33:15.000Z" objectID="{D4C06049-3C91-0190-0C2E-1BB1EBA49EBA}{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-13T07:33:16.000Z" objectID="{F0CA7185-BDBF-066E-0D2C-CAE46E036850}{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-13T07:33:16.000Z" lastModifiedTime="2026-09-13T07:33:16.000Z" objectID="{F7D0FEC9-71DC-00E3-30D7-07DB00C2623B}{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="{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
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 created+13
...@@ -0,0 +1,13 @@
1{
2 "exit": 0,
3 "stdout": "",
4 "stderr": "",
5 "w": 800,
6 "h": 600,
7 "error": null,
8 "win": {
9 "title": "Equations below - Microsoft OneNote",
10 "class": "Framework::CFrame",
11 "dialog": false
12 }
13}
\ No newline at end of file
corpus/page-copy/cold/read/page-009.png created
Binary files /dev/null and b/corpus/page-copy/cold/read/page-009.png differ
corpus/page-copy/cold/read/page-009.xml created+3
...@@ -0,0 +1,3 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{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
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>
corpus/page-copy/cold/read/page-010.navigation.json created+13
...@@ -0,0 +1,13 @@
1{
2 "exit": 0,
3 "stdout": "",
4 "stderr": "",
5 "w": 800,
6 "h": 600,
7 "error": null,
8 "win": {
9 "title": "Move numbered subtree down - Microsoft OneNote",
10 "class": "Framework::CFrame",
11 "dialog": false
12 }
13}
\ No newline at end of file
corpus/page-copy/cold/read/page-010.png created
Binary files /dev/null and b/corpus/page-copy/cold/read/page-010.png differ
corpus/page-copy/cold/read/page-010.xml created+5
...@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{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
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
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>
corpus/page-copy/cold/read/page-011.navigation.json created+13
...@@ -0,0 +1,13 @@
1{
2 "exit": 0,
3 "stdout": "",
4 "stderr": "",
5 "w": 800,
6 "h": 600,
7 "error": null,
8 "win": {
9 "title": "Open task - Microsoft OneNote",
10 "class": "Framework::CFrame",
11 "dialog": false
12 }
13}
\ No newline at end of file
corpus/page-copy/cold/read/page-011.png created
Binary files /dev/null and b/corpus/page-copy/cold/read/page-011.png differ
corpus/page-copy/cold/read/page-011.xml created+2
...@@ -0,0 +1,2 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{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>
corpus/page-copy/cold/read/page-012.navigation.json created+13
...@@ -0,0 +1,13 @@
1{
2 "exit": 0,
3 "stdout": "",
4 "stderr": "",
5 "w": 800,
6 "h": 600,
7 "error": null,
8 "win": {
9 "title": "Move cell subtree down - Microsoft OneNote",
10 "class": "Framework::CFrame",
11 "dialog": false
12 }
13}
\ No newline at end of file
corpus/page-copy/cold/read/page-012.png created
Binary files /dev/null and b/corpus/page-copy/cold/read/page-012.png differ
corpus/page-copy/cold/read/page-012.xml created+5
...@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{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
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
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>
corpus/page-copy/cold/read/page-013.navigation.json created+13
...@@ -0,0 +1,13 @@
1{
2 "exit": 0,
3 "stdout": "",
4 "stderr": "",
5 "w": 800,
6 "h": 600,
7 "error": null,
8 "win": {
9 "title": "Delete cell subtree - Microsoft OneNote",
10 "class": "Framework::CFrame",
11 "dialog": false
12 }
13}
\ No newline at end of file
corpus/page-copy/cold/read/page-013.png created
Binary files /dev/null and b/corpus/page-copy/cold/read/page-013.png differ
corpus/page-copy/cold/read/page-013.xml created+5
...@@ -0,0 +1,5 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{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
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="{51E78125-5D88-02B2-1F60-3564BF9AB799}{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="{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>
corpus/page-copy/cold/read/page-014.navigation.json created+13
...@@ -0,0 +1,13 @@
1{
2 "exit": 0,
3 "stdout": "",
4 "stderr": "",
5 "w": 800,
6 "h": 600,
7 "error": null,
8 "win": {
9 "title": "Beside the drawing - Microsoft OneNote",
10 "class": "Framework::CFrame",
11 "dialog": false
12 }
13}
\ No newline at end of file
corpus/page-copy/cold/read/page-014.png created
Binary files /dev/null and b/corpus/page-copy/cold/read/page-014.png differ
corpus/page-copy/cold/read/page-014.xml created+7
...@@ -0,0 +1,7 @@
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
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 created+13
...@@ -0,0 +1,13 @@
1{
2 "exit": 0,
3 "stdout": "",
4 "stderr": "",
5 "w": 800,
6 "h": 600,
7 "error": null,
8 "win": {
9 "title": "A notebook created in Rust. - Microsoft OneNote",
10 "class": "Framework::CFrame",
11 "dialog": false
12 }
13}
\ No newline at end of file
corpus/page-copy/cold/read/page-015.png created
Binary files /dev/null and b/corpus/page-copy/cold/read/page-015.png differ
corpus/page-copy/cold/read/page-015.xml created+2
...@@ -0,0 +1,2 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{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>
corpus/page-copy/cold/read/page-016.navigation.json created+13
...@@ -0,0 +1,13 @@
1{
2 "exit": 0,
3 "stdout": "",
4 "stderr": "",
5 "w": 800,
6 "h": 600,
7 "error": null,
8 "win": {
9 "title": "Before the table - Microsoft OneNote",
10 "class": "Framework::CFrame",
11 "dialog": false
12 }
13}
\ No newline at end of file
corpus/page-copy/cold/read/page-016.png created
Binary files /dev/null and b/corpus/page-copy/cold/read/page-016.png differ
corpus/page-copy/cold/read/page-016.xml created+2
...@@ -0,0 +1,2 @@
1<?xml version="1.0"?>
2<one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{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>
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": "{03AD9394-425B-0B9C-0D27-CA384DFFCEBF}{1}{B0}", 5 "object": "{9E89195D-B7EC-0AC4-34FD-F47D633F0533}{1}{B0}",
6 "kind": "InsertedFile", 6 "kind": "InsertedFile",
7 "page": "{C12C0ECF-DDF8-0A21-11BE-3074429542E0}{1}{B0}", 7 "page": "{62E12C49-D5FE-0850-17D5-B4DC95B736E9}{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,6 +1,6 @@...@@ -1,6 +1,6 @@
1{1{
2 "notebook": "/private/tmp/m4/copy",2 "notebook": "/private/tmp/m4/copy",
3 "expected_pages": 9,3 "expected_pages": 17,
4 "author": null,4 "author": null,
5 "author_timeout_seconds": 600,5 "author_timeout_seconds": 600,
6 "inspect": false,6 "inspect": false,
corpus/page-copy/cold/source.json+8-8
...@@ -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": "bf856a7700054971543fb890257cba327dfd35ef6e5438a41483490c4e06407b",5 "sha256": "acb0747c250f7bebed1e90b18d5dbbf8c32731f9287571b1e732681921fd323a",
6 "mtime_ns": 17892847971915473506 "mtime_ns": 1789287183577857848
7 },7 },
8 {8 {
9 "path": "copies.one",9 "path": "copies.one",
10 "bytes": 91008,10 "bytes": 185400,
11 "sha256": "8f6c9b66379c4bf6730e281d8363737d88fbe1c521a62fd4d054f735b0505cc7",11 "sha256": "dcd7c226d86477adc1ad2f8da8a408ffa2b993b372517ab8d464e5746a971aac",
12 "mtime_ns": 178928479718790625212 "mtime_ns": 1789287183575141938
13 },13 },
14 {14 {
15 "path": "expected-count.txt",15 "path": "expected-count.txt",
16 "bytes": 1,16 "bytes": 2,
17 "sha256": "19581e27de7ced00ff1ce50b2047e7a567c76b1cbaebabe5ef03f7c3017bb5b7",17 "sha256": "4523540f1504cd17100c4835e85b7eefd49911580f8efff0599a8f283be6b9e3",
18 "mtime_ns": 178928479719163318318 "mtime_ns": 1789287183577930514
19 }19 }
20]20]
crates/notebook/README.md+5-2
...@@ -334,8 +334,11 @@ metadata; a deleted section moves into `OneNote_RecycleBin`, a group with its...@@ -334,8 +334,11 @@ metadata; a deleted section moves into `OneNote_RecycleBin`, a group with its
334own TOC. Every created, renamed or moved file is placed with334own TOC. Every created, renamed or moved file is placed with
335`onestore::place_file`, which sets the header's ancestor to the parent TOC's335`onestore::place_file`, which sets the header's ancestor to the parent TOC's
336identity and the name CRC OneNote checks on open; a file without them is336identity and the name CRC OneNote checks on open; a file without them is
337re-identified and listed anew. These operate on the mounted directory;337re-identified and listed anew. They run over `Storage`: a mounted directory
338the SMB transport has no create, rename or delete.338(`Notebook::open`) or an SMB share (`Notebook::open_smb` with a
339`smb::Client`, which gained create, directory creation, rename, delete and
340header placement under native writer coordination); `tools/test_smb_structure.py
341VM OUTPUT` drives them against a disposable Samba lab VM.
339342
340`Notebook::find_page(url)` resolves a stored internal link to a section path343`Notebook::find_page(url)` resolves a stored internal link to a section path
341and page space by identity: the linked section first, then every readable344and page space by identity: the linked section first, then every readable
crates/notebook/src/session.rs+303-114
...@@ -66,9 +66,195 @@ fn entries(folder: &discover::Folder) -> Entries {...@@ -66,9 +66,195 @@ fn entries(folder: &discover::Folder) -> Entries {
66 out66 out
67}67}
6868
69/// A notebook directory and the cache directory holding its section replicas.69/// Where a notebook's files live: a mounted directory or an SMB share. Paths are catalog
70/// paths, `/`-separated and relative to the notebook root.
71pub trait Storage: Send + Sync {
72 fn discover(&self, limits: discover::Limits) -> Result<discover::Folder>;
73 fn exists(&self, path: &str) -> bool;
74 fn read(&self, path: &str) -> Result<Vec<u8>>;
75 /// Creates a file holding `bytes`; an existing file is an error.
76 fn create(&self, path: &str, bytes: &[u8]) -> Result<()>;
77 fn create_directory(&self, path: &str) -> Result<()>;
78 /// Renames or moves a file or directory; an existing target is an error.
79 fn rename(&self, from: &str, to: &str) -> Result<()>;
80 /// Names a section or TOC file for its notebook, as `onestore::place`.
81 fn place(&self, path: &str, ancestor: [u8; 16], name: &str) -> Result<()>;
82 fn commit(&self, path: &str, edit: &PreparedEdit<'_>) -> Result<()>;
83 fn set_property(
84 &self,
85 path: &str,
86 source: &[u8],
87 space: ExGuid,
88 object: ExGuid,
89 property: u32,
90 value: &[u8],
91 ) -> Result<()>;
92}
93
94/// A mounted notebook directory.
95struct Directory(PathBuf);
96
97impl Directory {
98 fn path(&self, relative: &str) -> PathBuf {
99 if relative.is_empty() {
100 self.0.clone()
101 } else {
102 self.0.join(relative)
103 }
104 }
105}
106
107impl Storage for Directory {
108 fn discover(&self, limits: discover::Limits) -> Result<discover::Folder> {
109 Ok(discover::discover(
110 &mut discover::Local::open(&self.0)?,
111 limits,
112 )?)
113 }
114
115 fn exists(&self, path: &str) -> bool {
116 self.path(path).exists()
117 }
118
119 fn read(&self, path: &str) -> Result<Vec<u8>> {
120 Ok(onestore::read_file(self.path(path))?)
121 }
122
123 fn create(&self, path: &str, bytes: &[u8]) -> Result<()> {
124 std::fs::File::create_new(self.path(path))?.write_all(bytes)?;
125 Ok(())
126 }
127
128 fn create_directory(&self, path: &str) -> Result<()> {
129 Ok(std::fs::create_dir(self.path(path))?)
130 }
131
132 fn rename(&self, from: &str, to: &str) -> Result<()> {
133 Ok(std::fs::rename(self.path(from), self.path(to))?)
134 }
135
136 fn place(&self, path: &str, ancestor: [u8; 16], name: &str) -> Result<()> {
137 Ok(onestore::place_file(self.path(path), ancestor, name)?)
138 }
139
140 fn commit(&self, path: &str, edit: &PreparedEdit<'_>) -> Result<()> {
141 Ok(edit.commit_file(self.path(path))?)
142 }
143
144 fn set_property(
145 &self,
146 path: &str,
147 source: &[u8],
148 space: ExGuid,
149 object: ExGuid,
150 property: u32,
151 value: &[u8],
152 ) -> Result<()> {
153 Ok(onestore::commit_file_property(
154 self.path(path),
155 source,
156 space,
157 object,
158 property,
159 value,
160 )?)
161 }
162}
163
164/// A notebook directory on an SMB share, reached through the native-compatible client.
165#[cfg(feature = "smb")]
166pub struct Share {
167 client: Arc<crate::smb::Client>,
168 root: String,
169}
170
171#[cfg(feature = "smb")]
172impl Share {
173 fn path(&self, relative: &str) -> String {
174 match (self.root.is_empty(), relative.is_empty()) {
175 (_, true) => self.root.clone(),
176 (true, false) => relative.to_owned(),
177 (false, false) => format!("{}/{relative}", self.root),
178 }
179 }
180}
181
182#[cfg(feature = "smb")]
183impl Storage for Share {
184 fn discover(&self, limits: discover::Limits) -> Result<discover::Folder> {
185 Ok(discover::discover(
186 &mut discover::Smb::new(&self.client, &self.root)?,
187 limits,
188 )?)
189 }
190
191 fn exists(&self, path: &str) -> bool {
192 let (folder, name) = split(path);
193 self.client
194 .read_dir(&self.path(folder), 100_000)
195 .is_ok_and(|entries| entries.iter().any(|entry| entry.name == name))
196 }
197
198 fn read(&self, path: &str) -> Result<Vec<u8>> {
199 Ok(self
200 .client
201 .read_storage(&self.path(path), 256 * 1024 * 1024)?)
202 }
203
204 fn create(&self, path: &str, bytes: &[u8]) -> Result<()> {
205 Ok(self.client.create(&self.path(path), bytes)?)
206 }
207
208 fn create_directory(&self, path: &str) -> Result<()> {
209 Ok(self.client.create_directory(&self.path(path))?)
210 }
211
212 fn rename(&self, from: &str, to: &str) -> Result<()> {
213 Ok(self.client.rename(&self.path(from), &self.path(to))?)
214 }
215
216 fn place(&self, path: &str, ancestor: [u8; 16], name: &str) -> Result<()> {
217 Ok(self.client.place(&self.path(path), ancestor, name)?)
218 }
219
220 fn commit(&self, path: &str, edit: &PreparedEdit<'_>) -> Result<()> {
221 Ok(self.client.commit_prepared(&self.path(path), edit)?)
222 }
223
224 fn set_property(
225 &self,
226 path: &str,
227 source: &[u8],
228 space: ExGuid,
229 object: ExGuid,
230 property: u32,
231 value: &[u8],
232 ) -> Result<()> {
233 Ok(self.client.commit_property_bytes(
234 &self.path(path),
235 source,
236 space,
237 object,
238 property,
239 value,
240 )?)
241 }
242}
243
244const LIMITS: discover::Limits = discover::Limits {
245 entries: 100_000,
246 bytes_per_file: 256 * 1024 * 1024,
247 depth: 64,
248};
249
250const TOC: &str = "Open Notebook.onetoc2";
251const RECYCLE_BIN: &str = "OneNote_RecycleBin";
252
253/// A notebook's files and the cache directory holding its section replicas.
70pub struct Notebook {254pub struct Notebook {
71 root: PathBuf,255 storage: Box<dyn Storage>,
256 /// The mounted directory, when sections open through local replicas.
257 root: Option<PathBuf>,
72 cache: PathBuf,258 cache: PathBuf,
73 catalog: discover::Folder,259 catalog: discover::Folder,
74}260}
...@@ -76,17 +262,31 @@ pub struct Notebook {...@@ -76,17 +262,31 @@ pub struct Notebook {
76impl Notebook {262impl Notebook {
77 pub fn open(root: impl AsRef<Path>, cache: impl AsRef<Path>) -> Result<Self> {263 pub fn open(root: impl AsRef<Path>, cache: impl AsRef<Path>) -> Result<Self> {
78 let root = root.as_ref().canonicalize()?;264 let root = root.as_ref().canonicalize()?;
265 Self::with(Box::new(Directory(root.clone())), Some(root), cache)
266 }
267
268 /// Opens the notebook at `root` on the share `client` is connected to. Sections open
269 /// through `Section::resume_smb` with the catalog's paths.
270 #[cfg(feature = "smb")]
271 pub fn open_smb(
272 client: Arc<crate::smb::Client>,
273 root: &str,
274 cache: impl AsRef<Path>,
275 ) -> Result<Self> {
276 let root = root.replace('\\', "/");
277 Self::with(Box::new(Share { client, root }), None, cache)
278 }
279
280 fn with(
281 storage: Box<dyn Storage>,
282 root: Option<PathBuf>,
283 cache: impl AsRef<Path>,
284 ) -> Result<Self> {
79 let cache = cache.as_ref().to_path_buf();285 let cache = cache.as_ref().to_path_buf();
80 std::fs::create_dir_all(&cache)?;286 std::fs::create_dir_all(&cache)?;
81 let catalog = discover::discover(287 let catalog = storage.discover(LIMITS)?;
82 &mut discover::Local::open(&root)?,
83 discover::Limits {
84 entries: 100_000,
85 bytes_per_file: 256 * 1024 * 1024,
86 depth: 64,
87 },
88 )?;
89 Ok(Self {288 Ok(Self {
289 storage,
90 root,290 root,
91 cache,291 cache,
92 catalog,292 catalog,
...@@ -97,18 +297,11 @@ impl Notebook {...@@ -97,18 +297,11 @@ impl Notebook {
97 &self.catalog297 &self.catalog
98 }298 }
99299
100 /// Rereads the notebook directory and reports what changed since the last catalog,300 /// Rereads the notebook and reports what changed since the last catalog, keyed by
101 /// keyed by file identity so a renamed or moved section stays the same section. A301 /// file identity so a renamed or moved section stays the same section. A failed read
102 /// failed read keeps the previous catalog: an unreachable notebook is not an empty one.302 /// keeps the previous catalog: an unreachable notebook is not an empty one.
103 pub fn refresh(&mut self) -> Result<Vec<Change>> {303 pub fn refresh(&mut self) -> Result<Vec<Change>> {
104 let catalog = discover::discover(304 let catalog = self.storage.discover(LIMITS)?;
105 &mut discover::Local::open(&self.root)?,
106 discover::Limits {
107 entries: 100_000,
108 bytes_per_file: 256 * 1024 * 1024,
109 depth: 64,
110 },
111 )?;
112 let (before, before_orders) = entries(&self.catalog);305 let (before, before_orders) = entries(&self.catalog);
113 let (after, after_orders) = entries(&catalog);306 let (after, after_orders) = entries(&catalog);
114 let mut changes = Vec::new();307 let mut changes = Vec::new();
...@@ -158,28 +351,27 @@ impl Notebook {...@@ -158,28 +351,27 @@ impl Notebook {
158 Err(io::Error::from(io::ErrorKind::NotFound).into())351 Err(io::Error::from(io::ErrorKind::NotFound).into())
159 }352 }
160353
161 fn directory(&self, path: &str) -> Result<PathBuf> {354 /// A catalog section's path, refusing paths the catalog does not list.
162 let directory = if path.is_empty() {355 fn section_path(&self, path: &str) -> Result<&discover::Section> {
163 self.root.clone()356 let mut folders = vec![&self.catalog];
164 } else {357 while let Some(folder) = folders.pop() {
165 self.root.join(path).canonicalize()?358 if let Some(section) = folder.sections.iter().find(|section| section.path == path) {
166 };359 return Ok(section);
167 if !directory.starts_with(&self.root) || !directory.is_dir() {360 }
168 return Err(io::Error::from(io::ErrorKind::PermissionDenied).into());361 folders.extend(&folder.groups);
169 }362 }
170 Ok(directory)363 Err(io::Error::from(io::ErrorKind::NotFound).into())
171 }364 }
172365
173 /// A folder's TOC path and file identity, creating the TOC when the folder has none366 /// A folder's TOC path and file identity, creating the TOC when the folder has none
174 /// (OneNote names it `Open Notebook.onetoc2`).367 /// (OneNote names it `Open Notebook.onetoc2`).
175 fn toc(&self, folder: &str) -> Result<(PathBuf, [u8; 16])> {368 fn toc(&self, folder: &str) -> Result<(String, [u8; 16])> {
176 let directory = self.directory(folder)?;
177 match &self.folder(folder)?.toc {369 match &self.folder(folder)?.toc {
178 Some(toc) => Ok((directory.join(&toc.filename), toc.file_id)),370 Some(toc) => Ok((catalog_path(folder, &toc.filename), toc.file_id)),
179 None => {371 None => {
180 let path = directory.join("Open Notebook.onetoc2");372 let path = catalog_path(folder, TOC);
181 let bytes = onestore::create_table_of_contents("Open Notebook.onetoc2", &[])?;373 let bytes = onestore::create_table_of_contents(TOC, &[])?;
182 std::fs::File::create_new(&path)?.write_all(&bytes)?;374 self.storage.create(&path, &bytes)?;
183 Ok((path, onestore::Store::parse(&bytes)?.header.file_id))375 Ok((path, onestore::Store::parse(&bytes)?.header.file_id))
184 }376 }
185 }377 }
...@@ -187,23 +379,21 @@ impl Notebook {...@@ -187,23 +379,21 @@ impl Notebook {
187379
188 fn edit_toc(&self, folder: &str, edits: &[onestore::TocEdit]) -> Result<()> {380 fn edit_toc(&self, folder: &str, edits: &[onestore::TocEdit]) -> Result<()> {
189 let (toc, _) = self.toc(folder)?;381 let (toc, _) = self.toc(folder)?;
190 let source = onestore::read_file(&toc)?;382 let source = self.storage.read(&toc)?;
191 PreparedEdit::table_of_contents(&source, edits)?383 self.storage
192 .commit_file(&toc)384 .commit(&toc, &PreparedEdit::table_of_contents(&source, edits)?)
193 .map_err(|error| error.error)?;
194 Ok(())
195 }385 }
196386
197 /// Creates `name.one` in `folder` with one empty page and lists it last in the folder's387 /// Creates `name.one` in `folder` with one empty page and lists it last in the folder's
198 /// TOC, as OneNote creates a section. Returns the new catalog path.388 /// TOC, as OneNote creates a section. Returns the new catalog path.
199 pub fn create_section(&mut self, folder: &str, name: &str, author: &str) -> Result<String> {389 pub fn create_section(&mut self, folder: &str, name: &str, author: &str) -> Result<String> {
200 let filename = format!("{name}.one");390 let filename = format!("{name}.one");
201 let directory = self.directory(folder)?;391 self.folder(folder)?;
202 let (_, ancestor) = self.toc(folder)?;392 let (_, ancestor) = self.toc(folder)?;
203 let bytes = onestore::create_section(&filename, "", author)?;393 let bytes = onestore::create_section(&filename, "", author)?;
204 let path = directory.join(&filename);394 let path = catalog_path(folder, &filename);
205 std::fs::File::create_new(&path)?.write_all(&bytes)?;395 self.storage.create(&path, &bytes)?;
206 onestore::place_file(&path, ancestor, &filename)?;396 self.storage.place(&path, ancestor, &filename)?;
207 let identity = onestore::Store::parse(&bytes)?.header.file_id;397 let identity = onestore::Store::parse(&bytes)?.header.file_id;
208 self.edit_toc(398 self.edit_toc(
209 folder,399 folder,
...@@ -214,22 +404,22 @@ impl Notebook {...@@ -214,22 +404,22 @@ impl Notebook {
214 }],404 }],
215 )?;405 )?;
216 self.refresh()?;406 self.refresh()?;
217 Ok(catalog_path(folder, &filename))407 Ok(path)
218 }408 }
219409
220 /// Creates a section group: a folder with its own TOC, listed last in the parent's TOC.410 /// Creates a section group: a folder with its own TOC, listed last in the parent's TOC.
221 pub fn create_group(&mut self, folder: &str, name: &str) -> Result<String> {411 pub fn create_group(&mut self, folder: &str, name: &str) -> Result<String> {
222 let directory = self.directory(folder)?;412 self.folder(folder)?;
223 if !component(name) {413 if !component(name) {
224 return Err(io::Error::from(io::ErrorKind::InvalidInput).into());414 return Err(io::Error::from(io::ErrorKind::InvalidInput).into());
225 }415 }
226 let group = directory.join(name);
227 let (_, ancestor) = self.toc(folder)?;416 let (_, ancestor) = self.toc(folder)?;
228 std::fs::create_dir(&group)?;417 let group = catalog_path(folder, name);
229 let bytes = onestore::create_table_of_contents("Open Notebook.onetoc2", &[])?;418 self.storage.create_directory(&group)?;
230 let path = group.join("Open Notebook.onetoc2");419 let bytes = onestore::create_table_of_contents(TOC, &[])?;
231 std::fs::File::create_new(&path)?.write_all(&bytes)?;420 let path = catalog_path(&group, TOC);
232 onestore::place_file(&path, ancestor, name)?;421 self.storage.create(&path, &bytes)?;
422 self.storage.place(&path, ancestor, name)?;
233 let identity = onestore::Store::parse(&bytes)?.header.file_id;423 let identity = onestore::Store::parse(&bytes)?.header.file_id;
234 self.edit_toc(424 self.edit_toc(
235 folder,425 folder,
...@@ -240,48 +430,46 @@ impl Notebook {...@@ -240,48 +430,46 @@ impl Notebook {
240 }],430 }],
241 )?;431 )?;
242 self.refresh()?;432 self.refresh()?;
243 Ok(catalog_path(folder, name))433 Ok(group)
244 }434 }
245435
246 /// Renames a section or section group: the file or folder on disk and its TOC entry.436 /// Renames a section or section group: the file or folder and its TOC entry.
247 pub fn rename(&mut self, path: &str, name: &str) -> Result<String> {437 pub fn rename(&mut self, path: &str, name: &str) -> Result<String> {
248 let (folder, entry) = split(path);438 let (folder, entry) = split(path);
249 let directory = self.directory(folder)?;
250 let (filename, identity) = self.entry(folder, entry)?;439 let (filename, identity) = self.entry(folder, entry)?;
251 let target = if filename.to_ascii_lowercase().ends_with(".one") {440 let target = if filename.to_ascii_lowercase().ends_with(".one") {
252 format!("{name}.one")441 format!("{name}.one")
253 } else {442 } else {
254 name.to_owned()443 name.to_owned()
255 };444 };
256 if !component(&target) || directory.join(&target).exists() {445 let renamed = catalog_path(folder, &target);
446 if !component(&target) || self.storage.exists(&renamed) {
257 return Err(io::Error::from(io::ErrorKind::AlreadyExists).into());447 return Err(io::Error::from(io::ErrorKind::AlreadyExists).into());
258 }448 }
259 std::fs::rename(directory.join(&filename), directory.join(&target))?;449 self.storage
450 .rename(&catalog_path(folder, &filename), &renamed)?;
260 let (_, ancestor) = self.toc(folder)?;451 let (_, ancestor) = self.toc(folder)?;
261 let placed = if target.ends_with(".one") {452 let placed = if target.ends_with(".one") {
262 directory.join(&target)453 renamed.clone()
263 } else {454 } else {
264 directory.join(&target).join("Open Notebook.onetoc2")455 catalog_path(&renamed, TOC)
265 };456 };
266 onestore::place_file(placed, ancestor, &target)?;457 self.storage.place(&placed, ancestor, &target)?;
267 self.edit_toc(458 self.edit_toc(
268 folder,459 folder,
269 &[onestore::TocEdit::Rename {460 &[onestore::TocEdit::Rename {
270 identity,461 identity,
271 filename: target.clone(),462 filename: target,
272 }],463 }],
273 )?;464 )?;
274 self.refresh()?;465 self.refresh()?;
275 Ok(catalog_path(folder, &target))466 Ok(renamed)
276 }467 }
277468
278 /// Sets a section's colour (COLORREF) in its own metadata, where OneNote keeps it.469 /// Sets a section's colour (COLORREF) in its own metadata, where OneNote keeps it.
279 pub fn set_section_color(&mut self, path: &str, color: Option<u32>) -> Result<()> {470 pub fn set_section_color(&mut self, path: &str, color: Option<u32>) -> Result<()> {
280 let file = self.root.join(path).canonicalize()?;471 let path = self.section_path(path)?.path.clone();
281 if !file.starts_with(&self.root) {472 let source = self.storage.read(&path)?;
282 return Err(io::Error::from(io::ErrorKind::PermissionDenied).into());
283 }
284 let source = onestore::read_file(&file)?;
285 let store = onestore::Store::parse(&source)?;473 let store = onestore::Store::parse(&source)?;
286 let index = onestore::RevisionIndex::parse(&store)?;474 let index = onestore::RevisionIndex::parse(&store)?;
287 let document = Document::parse(&index)?;475 let document = Document::parse(&index)?;
...@@ -290,15 +478,14 @@ impl Notebook {...@@ -290,15 +478,14 @@ impl Notebook {
290 .roots478 .roots
291 .get(&2)479 .get(&2)
292 .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))?;480 .ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))?;
293 onestore::commit_file_property(481 self.storage.set_property(
294 &file,482 &path,
295 &source,483 &source,
296 document.root,484 document.root,
297 metadata,485 metadata,
298 0x14001cbe,486 0x14001cbe,
299 &color.unwrap_or(0xffff_ffff).to_le_bytes(),487 &color.unwrap_or(0xffff_ffff).to_le_bytes(),
300 )488 )?;
301 .map_err(|error| error.error)?;
302 self.refresh()?;489 self.refresh()?;
303 Ok(())490 Ok(())
304 }491 }
...@@ -322,23 +509,23 @@ impl Notebook {...@@ -322,23 +509,23 @@ impl Notebook {
322 /// the root TOC lists), and its own folder's TOC entry goes.509 /// the root TOC lists), and its own folder's TOC entry goes.
323 pub fn delete(&mut self, path: &str) -> Result<()> {510 pub fn delete(&mut self, path: &str) -> Result<()> {
324 let (folder, entry) = split(path);511 let (folder, entry) = split(path);
325 let directory = self.directory(folder)?;
326 let (filename, identity) = self.entry(folder, entry)?;512 let (filename, identity) = self.entry(folder, entry)?;
327 if !filename.to_ascii_lowercase().ends_with(".one") {513 if !filename.to_ascii_lowercase().ends_with(".one") {
328 return Err(io::Error::from(io::ErrorKind::InvalidInput).into());514 return Err(io::Error::from(io::ErrorKind::InvalidInput).into());
329 }515 }
330 let bin = self.root.join("OneNote_RecycleBin");516 let bin_toc = catalog_path(RECYCLE_BIN, TOC);
331 let bin_toc = bin.join("Open Notebook.onetoc2");517 if !self.storage.exists(&bin_toc) {
332 if !bin_toc.exists() {518 if !self.storage.exists(RECYCLE_BIN) {
333 std::fs::create_dir_all(&bin)?;519 self.storage.create_directory(RECYCLE_BIN)?;
334 let bytes = onestore::create_table_of_contents("Open Notebook.onetoc2", &[])?;520 }
335 std::fs::File::create_new(&bin_toc)?.write_all(&bytes)?;521 let bytes = onestore::create_table_of_contents(TOC, &[])?;
336 onestore::place_file(&bin_toc, self.toc("")?.1, "OneNote_RecycleBin")?;522 self.storage.create(&bin_toc, &bytes)?;
523 self.storage.place(&bin_toc, self.toc("")?.1, RECYCLE_BIN)?;
337 let bin_identity = onestore::Store::parse(&bytes)?.header.file_id;524 let bin_identity = onestore::Store::parse(&bytes)?.header.file_id;
338 self.edit_toc(525 self.edit_toc(
339 "",526 "",
340 &[onestore::TocEdit::Add {527 &[onestore::TocEdit::Add {
341 filename: "OneNote_RecycleBin".into(),528 filename: RECYCLE_BIN.into(),
342 identity: bin_identity,529 identity: bin_identity,
343 group: true,530 group: true,
344 }],531 }],
...@@ -346,28 +533,31 @@ impl Notebook {...@@ -346,28 +533,31 @@ impl Notebook {
346 }533 }
347 let mut target = filename.clone();534 let mut target = filename.clone();
348 let mut attempt = 1;535 let mut attempt = 1;
349 while bin.join(&target).exists() {536 while self.storage.exists(&catalog_path(RECYCLE_BIN, &target)) {
350 attempt += 1;537 attempt += 1;
351 let (stem, extension) = filename.rsplit_once('.').unwrap_or((&filename, ""));538 let (stem, extension) = filename.rsplit_once('.').unwrap_or((&filename, ""));
352 target = format!("{stem} ({attempt}).{extension}");539 target = format!("{stem} ({attempt}).{extension}");
353 }540 }
354 std::fs::rename(directory.join(&filename), bin.join(&target))?;541 let binned = catalog_path(RECYCLE_BIN, &target);
355 let source = onestore::read_file(&bin_toc)?;542 self.storage
356 onestore::place_file(543 .rename(&catalog_path(folder, &filename), &binned)?;
357 bin.join(&target),544 let source = self.storage.read(&bin_toc)?;
545 self.storage.place(
546 &binned,
358 onestore::Store::parse(&source)?.header.file_id,547 onestore::Store::parse(&source)?.header.file_id,
359 &target,548 &target,
360 )?;549 )?;
361 PreparedEdit::table_of_contents(550 self.storage.commit(
362 &source,551 &bin_toc,
363 &[onestore::TocEdit::Add {552 &PreparedEdit::table_of_contents(
364 filename: target,553 &source,
365 identity,554 &[onestore::TocEdit::Add {
366 group: false,555 filename: target,
367 }],556 identity,
368 )?557 group: false,
369 .commit_file(&bin_toc)558 }],
370 .map_err(|error| error.error)?;559 )?,
560 )?;
371 self.edit_toc(folder, &[onestore::TocEdit::Remove { identity }])?;561 self.edit_toc(folder, &[onestore::TocEdit::Remove { identity }])?;
372 self.refresh().map(drop)562 self.refresh().map(drop)
373 }563 }
...@@ -419,7 +609,7 @@ impl Notebook {...@@ -419,7 +609,7 @@ impl Notebook {
419 .map(|section| (section.path.clone(), None)));609 .map(|section| (section.path.clone(), None)));
420 };610 };
421 for section in sections {611 for section in sections {
422 let bytes = onestore::read_file(self.root.join(&section.path))?;612 let bytes = self.storage.read(&section.path)?;
423 let store = Store::parse(&bytes)?;613 let store = Store::parse(&bytes)?;
424 let index = RevisionIndex::parse(&store)?;614 let index = RevisionIndex::parse(&store)?;
425 let document = Document::parse(&index)?;615 let document = Document::parse(&index)?;
...@@ -436,11 +626,8 @@ impl Notebook {...@@ -436,11 +626,8 @@ impl Notebook {
436 /// written, and the decoded buffers go when the pages have been built.626 /// written, and the decoded buffers go when the pages have been built.
437 #[cfg(feature = "protected")]627 #[cfg(feature = "protected")]
438 pub fn unlock(&self, path: &str, password: &str) -> Result<Vec<(ExGuid, Page)>> {628 pub fn unlock(&self, path: &str, password: &str) -> Result<Vec<(ExGuid, Page)>> {
439 let file = self.root.join(path).canonicalize()?;629 let path = self.section_path(path)?.path.clone();
440 if !file.starts_with(&self.root) {630 let bytes = self.storage.read(&path)?;
441 return Err(io::Error::from(io::ErrorKind::PermissionDenied).into());
442 }
443 let bytes = onestore::read_file(&file)?;
444 let store = Store::parse(&bytes)?;631 let store = Store::parse(&bytes)?;
445 let index = RevisionIndex::parse(&store)?;632 let index = RevisionIndex::parse(&store)?;
446 let unlocked = onestore::protected::UnlockedSection::open(633 let unlocked = onestore::protected::UnlockedSection::open(
...@@ -456,20 +643,22 @@ impl Notebook {...@@ -456,20 +643,22 @@ impl Notebook {
456 .collect()643 .collect()
457 }644 }
458645
459 /// Opens a section by its catalog path.646 /// Opens a section of a mounted notebook by its catalog path.
460 pub fn section(&self, path: &str, notify: impl Fn() + Send + 'static) -> Result<Section> {647 pub fn section(&self, path: &str, notify: impl Fn() + Send + 'static) -> Result<Section> {
461 let mut folders = vec![&self.catalog];648 let path = self.section_path(path)?.path.clone();
462 while let Some(folder) = folders.pop() {649 let Some(root) = &self.root else {
463 if folder.sections.iter().any(|section| section.path == path) {650 return Err(io::Error::new(
464 let file = self.root.join(path).canonicalize()?;651 io::ErrorKind::Unsupported,
465 if !file.starts_with(&self.root) {652 "Sections on a share open through Section::resume_smb",
466 return Err(io::Error::from(io::ErrorKind::PermissionDenied).into());653 )
467 }654 .into());
468 return Section::open(file, &self.cache, notify);655 };
469 }656 // A section replaced by a link out of the notebook is not the catalog's section.
470 folders.extend(&folder.groups);657 let file = root.join(path).canonicalize()?;
658 if !file.starts_with(root) {
659 return Err(io::Error::from(io::ErrorKind::PermissionDenied).into());
471 }660 }
472 Err(io::Error::from(io::ErrorKind::NotFound).into())661 Section::open(file, &self.cache, notify)
473 }662 }
474}663}
475664
crates/notebook/src/smb/mod.rs+93-3
...@@ -13,6 +13,7 @@ use smb2::{...@@ -13,6 +13,7 @@ use smb2::{
13 lock::{LockElement, LockRequest, LockResponse},13 lock::{LockElement, LockRequest, LockResponse},
14 query_info::{InfoType, QueryInfoRequest, QueryInfoResponse},14 query_info::{InfoType, QueryInfoRequest, QueryInfoResponse},
15 read::{ReadRequest, ReadResponse},15 read::{ReadRequest, ReadResponse},
16 set_info::{SetInfoRequest, SetInfoResponse},
16 write::{WriteRequest, WriteResponse},17 write::{WriteRequest, WriteResponse},
17 },18 },
18 pack::{Pack, ReadCursor, Unpack},19 pack::{Pack, ReadCursor, Unpack},
...@@ -180,6 +181,23 @@ impl Client {...@@ -180,6 +181,23 @@ impl Client {
180 }181 }
181182
182 fn open_shared(&self, path: &str, write: bool, sharing: u32) -> io::Result<File<'_>> {183 fn open_shared(&self, path: &str, write: bool, sharing: u32) -> io::Result<File<'_>> {
184 self.open_with(
185 path,
186 if write { 0xc0000000 } else { 0x80000000 },
187 sharing,
188 CreateDisposition::FileOpen,
189 0x42,
190 )
191 }
192
193 fn open_with(
194 &self,
195 path: &str,
196 access: u32,
197 sharing: u32,
198 disposition: CreateDisposition,
199 options: u32,
200 ) -> io::Result<File<'_>> {
183 if path.is_empty() || path.contains('\0') || path.encode_utf16().count() > 32767 {201 if path.is_empty() || path.contains('\0') || path.encode_utf16().count() > 32767 {
184 return Err(io::ErrorKind::InvalidInput.into());202 return Err(io::ErrorKind::InvalidInput.into());
185 }203 }
...@@ -188,11 +206,11 @@ impl Client {...@@ -188,11 +206,11 @@ impl Client {
188 CreateRequest {206 CreateRequest {
189 requested_oplock_level: OplockLevel::None,207 requested_oplock_level: OplockLevel::None,
190 impersonation_level: ImpersonationLevel::Impersonation,208 impersonation_level: ImpersonationLevel::Impersonation,
191 desired_access: FileAccessMask::new(if write { 0xc0000000 } else { 0x80000000 }),209 desired_access: FileAccessMask::new(access),
192 file_attributes: 0,210 file_attributes: 0,
193 share_access: ShareAccess(sharing),211 share_access: ShareAccess(sharing),
194 create_disposition: CreateDisposition::FileOpen,212 create_disposition: disposition,
195 create_options: 0x42,213 create_options: options,
196 name: smb2::encode_path(&path.replace('\\', "/")),214 name: smb2::encode_path(&path.replace('\\', "/")),
197 create_contexts: Vec::new(),215 create_contexts: Vec::new(),
198 },216 },
...@@ -203,6 +221,78 @@ impl Client {...@@ -203,6 +221,78 @@ impl Client {
203 })221 })
204 }222 }
205223
224 /// Creates a file holding `bytes`; an existing file is an error.
225 pub fn create(&self, path: &str, bytes: &[u8]) -> io::Result<()> {
226 let mut file = self.open_with(path, 0xc0000000, 0, CreateDisposition::FileCreate, 0x40)?;
227 let mut written = 0;
228 while written < bytes.len() {
229 let count = file.write_at(written as u64, &bytes[written..])?;
230 if count == 0 {
231 return Err(io::ErrorKind::WriteZero.into());
232 }
233 written += count;
234 }
235 file.flush()?;
236 file.close()
237 }
238
239 /// Creates a directory; an existing one is an error.
240 pub fn create_directory(&self, path: &str) -> io::Result<()> {
241 self.open_with(path, 0x80000000, 7, CreateDisposition::FileCreate, 0x1)?
242 .close()
243 }
244
245 /// Renames or moves a file or directory within the share; an existing target is an
246 /// error.
247 pub fn rename(&self, from: &str, to: &str) -> io::Result<()> {
248 if to.is_empty() || to.contains('\0') {
249 return Err(io::ErrorKind::InvalidInput.into());
250 }
251 let file = self.open_with(
252 from,
253 0x00010000 | 0x80000000,
254 7,
255 CreateDisposition::FileOpen,
256 0,
257 )?;
258 let name: Vec<u8> = to
259 .replace('/', "\\")
260 .encode_utf16()
261 .flat_map(u16::to_le_bytes)
262 .collect();
263 let mut buffer = vec![0; 16];
264 buffer.extend_from_slice(&u32::try_from(name.len()).unwrap().to_le_bytes());
265 buffer.extend_from_slice(&name);
266 let _: SetInfoResponse = self.request(
267 Command::SetInfo,
268 SetInfoRequest {
269 info_type: InfoType::File,
270 file_info_class: 10,
271 additional_information: 0,
272 file_id: file.id.ok_or(io::ErrorKind::InvalidInput)?,
273 buffer,
274 },
275 )?;
276 file.close()
277 }
278
279 /// Deletes a file or an empty directory.
280 pub fn delete(&self, path: &str) -> io::Result<()> {
281 self.open_with(path, 0x00010000, 7, CreateDisposition::FileOpen, 0x1000)?
282 .close()
283 }
284
285 /// Names a section or TOC file for its notebook (`onestore::place`) under native
286 /// writer coordination.
287 pub fn place(&self, path: &str, ancestor: [u8; 16], name: &str) -> Result<(), CommitError> {
288 self.commit(path, |file| {
289 onestore::place(file, ancestor, name).map_err(|error| CommitError {
290 state: CommitState::NotCommitted,
291 error,
292 })
293 })
294 }
295
206 /// Reads a bounded external payload while denying concurrent writes and deletion.296 /// Reads a bounded external payload while denying concurrent writes and deletion.
207 /// Empty files succeed; limits, sharing contention and failed close return no payload.297 /// Empty files succeed; limits, sharing contention and failed close return no payload.
208 pub fn read_asset(&self, path: &str, limit: usize) -> io::Result<Vec<u8>> {298 pub fn read_asset(&self, path: &str, limit: usize) -> io::Result<Vec<u8>> {
crates/notebook/src/smb/tests.rs+99
...@@ -505,3 +505,102 @@ fn read_limits_respect_negotiation_and_available_credits() {...@@ -505,3 +505,102 @@ fn read_limits_respect_negotiation_and_available_credits() {
505 }505 }
506 }506 }
507}507}
508
509/// Notebook structure over the share: sections and groups created, renamed, coloured,
510/// ordered and deleted through `Notebook::open_smb`, seen again by a fresh discovery.
511#[test]
512#[ignore = "requires an owned Samba share at ONESTORE_SMB_LAB"]
513fn live_structure() {
514 use crate::session::Notebook;
515 let client = std::sync::Arc::new(client());
516 let root = format!("structure-{}", std::process::id());
517 client.create_directory(&root).unwrap();
518 let first = onestore::create_section("First.one", "First page", "Author").unwrap();
519 client.create(&format!("{root}/First.one"), &first).unwrap();
520 let first_id = onestore::Store::parse(&first).unwrap().header.file_id;
521 let toc =
522 onestore::create_table_of_contents("Open Notebook.onetoc2", &[("First.one", first_id)])
523 .unwrap();
524 client
525 .create(&format!("{root}/Open Notebook.onetoc2"), &toc)
526 .unwrap();
527 let toc_id = onestore::Store::parse(&toc).unwrap().header.file_id;
528 client
529 .place(&format!("{root}/First.one"), toc_id, "First.one")
530 .unwrap();
531 let cache = tempfile::tempdir().unwrap();
532 let mut notebook =
533 Notebook::open_smb(std::sync::Arc::clone(&client), &root, cache.path()).unwrap();
534 assert_eq!(
535 notebook.create_section("", "Second", "Author").unwrap(),
536 "Second.one"
537 );
538 assert_eq!(notebook.create_group("", "Archive").unwrap(), "Archive");
539 assert_eq!(
540 notebook
541 .create_section("Archive", "Inner", "Author")
542 .unwrap(),
543 "Archive/Inner.one"
544 );
545 assert_eq!(
546 notebook.rename("Second.one", "Renamed").unwrap(),
547 "Renamed.one"
548 );
549 assert_eq!(notebook.rename("Archive", "Kept").unwrap(), "Kept");
550 notebook
551 .set_section_color("Renamed.one", Some(0x5ed7ff))
552 .unwrap();
553 notebook.reorder("", &["Kept", "Renamed.one"]).unwrap();
554 notebook.delete("First.one").unwrap();
555 let names = |folder: &crate::discover::Folder| {
556 (
557 folder
558 .sections
559 .iter()
560 .map(|s| s.path.clone())
561 .collect::<Vec<_>>(),
562 folder
563 .groups
564 .iter()
565 .map(|g| g.path.clone())
566 .collect::<Vec<_>>(),
567 )
568 };
569 let fresh = Notebook::open_smb(std::sync::Arc::clone(&client), &root, cache.path()).unwrap();
570 assert_eq!(
571 names(fresh.catalog()),
572 (
573 vec!["Renamed.one".to_owned()],
574 vec!["Kept".to_owned(), "OneNote_RecycleBin".to_owned()]
575 )
576 );
577 assert_eq!(
578 names(&fresh.catalog().groups[0]),
579 (vec!["Kept/Inner.one".to_owned()], vec![])
580 );
581 assert_eq!(
582 names(&fresh.catalog().groups[1]),
583 (vec!["OneNote_RecycleBin/First.one".to_owned()], vec![])
584 );
585 assert!(fresh.catalog().toc.as_ref().unwrap().unresolved.is_empty());
586 let renamed = client
587 .read_storage(&format!("{root}/Renamed.one"), 1 << 20)
588 .unwrap();
589 let header = onestore::Store::parse(&renamed).unwrap().header;
590 assert_eq!(header.ancestor, toc_id);
591 assert_eq!(header.name_crc, 0x6108912a);
592 assert!(fresh.section("Renamed.one", || {}).is_err());
593 for path in [
594 "Renamed.one",
595 "Kept/Inner.one",
596 "Kept/Open Notebook.onetoc2",
597 "Kept",
598 "OneNote_RecycleBin/First.one",
599 "OneNote_RecycleBin/Open Notebook.onetoc2",
600 "OneNote_RecycleBin",
601 "Open Notebook.onetoc2",
602 ] {
603 client.delete(&format!("{root}/{path}")).unwrap();
604 }
605 client.delete(&root).unwrap();
606}
crates/notebook/tests/copy.rs+25-4
...@@ -15,11 +15,12 @@ use std::{...@@ -15,11 +15,12 @@ use std::{
15};15};
1616
17/// Pages the writer authored for other rows: text with an attachment and its icon, a17/// Pages the writer authored for other rows: text with an attachment and its icon, a
18/// table, an inserted picture, a page-level ink drawing, tags, lists, equations18/// table, nested tables with cell subtrees, an inserted picture, a page-level ink drawing, tags, lists, equations
19/// and paragraph formatting.19/// and paragraph formatting.
20const SOURCES: &[&str] = &[20const SOURCES: &[&str] = &[
21 "attachment-edit/icon/candidate/files.one",21 "attachment-edit/icon/candidate/files.one",
22 "table-edit/created/candidate/tables.one",22 "table-edit/created/candidate/tables.one",
23 "table-edit/nested/candidate/synthetic.one",
23 "picture-edit/inserted/candidate/pictures.one",24 "picture-edit/inserted/candidate/pictures.one",
24 "ink-edit/drawing/candidate/ink.one",25 "ink-edit/drawing/candidate/ink.one",
25 "tag-edit/candidate/tags.one",26 "tag-edit/candidate/tags.one",
...@@ -64,8 +65,10 @@ fn shape(page: &Page) -> String {...@@ -64,8 +65,10 @@ fn shape(page: &Page) -> String {
64 serde_json::Value::Object(fields) => {65 serde_json::Value::Object(fields) => {
65 // The writer's canonical formatting: an unset flag is false and unset66 // The writer's canonical formatting: an unset flag is false and unset
66 // spacing is zero.67 // spacing is zero.
67 fields.retain(|_, value| {68 fields.retain(|key, value| {
68 !value.is_null()69 // OneNote lays outlines out again on open.
70 key != "max_height"
71 && !value.is_null()
69 && *value != serde_json::Value::Bool(false)72 && *value != serde_json::Value::Bool(false)
70 && value.as_f64() != Some(0.0)73 && value.as_f64() != Some(0.0)
71 });74 });
...@@ -153,6 +156,7 @@ fn pages_copy_into_another_section_with_their_content_and_fresh_identities() {...@@ -153,6 +156,7 @@ fn pages_copy_into_another_section_with_their_content_and_fresh_identities() {
153 .unwrap();156 .unwrap();
154 let section = Section::open(&file, &cache, || {}).unwrap();157 let section = Section::open(&file, &cache, || {}).unwrap();
155 let mut expected = Vec::new();158 let mut expected = Vec::new();
159 let mut grouped = Vec::new();
156 for source in SOURCES {160 for source in SOURCES {
157 let bytes = std::fs::read(161 let bytes = std::fs::read(
158 Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../corpus")).join(source),162 Path::new(concat!(env!("CARGO_MANIFEST_DIR"), "/../../corpus")).join(source),
...@@ -166,7 +170,14 @@ fn pages_copy_into_another_section_with_their_content_and_fresh_identities() {...@@ -166,7 +170,14 @@ fn pages_copy_into_another_section_with_their_content_and_fresh_identities() {
166 {170 {
167 continue;171 continue;
168 }172 }
169 let copy = page.copy().unwrap();173 let copy = match page.copy() {
174 Ok(copy) => copy,
175 Err(error) if error.message == "Outline groups cannot be copied" => {
176 grouped.push(page.title.clone());
177 continue;
178 }
179 Err(error) => panic!("{source} {}: {error}", page.title),
180 };
170 assert_ne!(copy.identity, page.identity);181 assert_ne!(copy.identity, page.identity);
171 let space = section182 let space = section
172 .import_page(&page, "Copier")183 .import_page(&page, "Copier")
...@@ -175,6 +186,16 @@ fn pages_copy_into_another_section_with_their_content_and_fresh_identities() {...@@ -175,6 +186,16 @@ fn pages_copy_into_another_section_with_their_content_and_fresh_identities() {
175 }186 }
176 }187 }
177 assert!(expected.len() >= 8, "{} pages", expected.len());188 assert!(expected.len() >= 8, "{} pages", expected.len());
189 // Outdenting leaves paragraphs at levels only an outline group carries.
190 assert_eq!(
191 grouped,
192 [
193 "Outdent first group",
194 "Delete only grouped subtree",
195 "Delete unindented sibling after group",
196 "Outdent child across indentation gap"
197 ]
198 );
178 let deadline = Instant::now() + Duration::from_secs(120);199 let deadline = Instant::now() + Duration::from_secs(120);
179 while section200 while section
180 .queue()201 .queue()
crates/onestore/README.md+2-2
...@@ -43,7 +43,7 @@ harness also accepts `--client-profile release`....@@ -43,7 +43,7 @@ harness also accepts `--client-profile release`.
43| `Object::file_reference`, `Store::file_data` | Identify internal/external payloads and read internal payload bytes |43| `Object::file_reference`, `Store::file_data` | Identify internal/external payloads and read internal payload bytes |
44| `document::Document`, `Revision::text_runs` | Interpret document objects and inherited text formatting while retaining unknown properties and revision identities |44| `document::Document`, `Revision::text_runs` | Interpret document objects and inherited text formatting while retaining unknown properties and revision identities |
45| `Document::active`, `Document::pages_in`, `Revision::parents`, `RevisionIndex::active` | Resolve the active revision, the pages of a space and parent links without repeating the lookups |45| `Document::active`, `Document::pages_in`, `Revision::parents`, `RevisionIndex::active` | Resolve the active revision, the pages of a space and parent links without repeating the lookups |
46| `Page::copy` | The page's content under fresh identities for copying into another section, definitions and payloads included |46| `Page::copy` | The page's content under fresh identities for copying into another section, definitions and payloads included; indent levels only an outline group carries refuse |
47| `page::link::internal_link`, `page::link::parse_internal_link` | Build and read the `onenote:#…` URLs OneNote stores for links to sections, pages and paragraphs, by identity |47| `page::link::internal_link`, `page::link::parse_internal_link` | Build and read the `onenote:#…` URLs OneNote stores for links to sections, pages and paragraphs, by identity |
48| `page::Page`, `page::Paragraph`, `page::Ink`, `page::Math` | Build an editable page model (title, outlines, paragraphs with coalesced text spans, tables, images, attachments, ink drawings and handwriting decoded to stroke polylines in page points) with stored identities; equations parse from their linear text and run data into a tree that renders the MathML OneNote exports; content outside the model is retained as `Unsupported` |48| `page::Page`, `page::Paragraph`, `page::Ink`, `page::Math` | Build an editable page model (title, outlines, paragraphs with coalesced text spans, tables, images, attachments, ink drawings and handwriting decoded to stroke polylines in page points) with stored identities; equations parse from their linear text and run data into a tree that renders the MathML OneNote exports; content outside the model is retained as `Unsupported` |
49| `protected::UnlockedSection` (optional feature) | Own decoded buffers for explicit known-password inspection; clear those buffers on drop; derived document strings/exports remain caller-owned |49| `protected::UnlockedSection` (optional feature) | Own decoded buffers for explicit known-password inspection; clear those buffers on drop; derived document strings/exports remain caller-owned |
...@@ -53,7 +53,7 @@ harness also accepts `--client-profile release`....@@ -53,7 +53,7 @@ harness also accepts `--client-profile release`.
53| `PreparedEdit::delete_pages_permanently` | Remove explicit pages and their section references atomically while retaining stored revisions |53| `PreparedEdit::delete_pages_permanently` | Remove explicit pages and their section references atomically while retaining stored revisions |
54| `create_table_of_contents` | Create ordered section entries from filenames and file identities |54| `create_table_of_contents` | Create ordered section entries from filenames and file identities |
55| `TocEdit`, `PreparedEdit::table_of_contents` | Add, rename, colour, order and remove a table of contents' section and group entries as one revision |55| `TocEdit`, `PreparedEdit::table_of_contents` | Add, rename, colour, order and remove a table of contents' section and group entries as one revision |
56| `place_file` | Name a file for its notebook as OneNote does on adoption (parent TOC identity and name CRC in the header), so OneNote keeps its identity |56| `place`, `place_file` | Name a file for its notebook as OneNote does on adoption (parent TOC identity and name CRC in the header), so OneNote keeps its identity, on any `CommitIo` or under the filesystem adapter |
57| `replace_property_bytes` | Append one scalar-property revision; preserve prior revisions and unrelated property values and references |57| `replace_property_bytes` | Append one scalar-property revision; preserve prior revisions and unrelated property values and references |
58| `replace_text`, `commit_text`, `commit_file_text` | Replace a UTF-16 range across ordinary text runs; publish text, run boundaries and modification time together |58| `replace_text`, `commit_text`, `commit_file_text` | Replace a UTF-16 range across ordinary text runs; publish text, run boundaries and modification time together |
59| `Insertion`, `PreparedEdit::insert` | Insert paragraphs into editable containers or positioned outlines into a page, retaining intent identities across rebases |59| `Insertion`, `PreparedEdit::insert` | Insert paragraphs into editable containers or positioned outlines into a page, retaining intent identities across rebases |
crates/onestore/src/commit.rs+18-14
...@@ -74,23 +74,27 @@ impl Drop for FileIo {...@@ -74,23 +74,27 @@ impl Drop for FileIo {
74/// Places a file in its notebook the way OneNote does on adoption: the header's74/// Places a file in its notebook the way OneNote does on adoption: the header's
75/// `guidAncestor` becomes the parent table of contents' file identity and `crcName` the CRC75/// `guidAncestor` becomes the parent table of contents' file identity and `crcName` the CRC
76/// of `name` (a section's file name, a group's folder name). OneNote re-identifies a file76/// of `name` (a section's file name, a group's folder name). OneNote re-identifies a file
77/// whose header disagrees with its location, which orphans its TOC entry.77/// whose header disagrees with its location, which orphans its TOC entry. The caller holds
78/// OneNote-compatible exclusion on `io`.
79pub fn place(io: &mut impl CommitIo, ancestor: [u8; 16], name: &str) -> io::Result<()> {
80 let mut header = [0; 1024];
81 if io.read_at(0, &mut header)? != header.len() {
82 return Err(io::Error::from(ErrorKind::UnexpectedEof));
83 }
84 crate::Header::parse(&header)
85 .map_err(|error| io::Error::new(ErrorKind::InvalidData, error.message))?;
86 let placement = crate::create::placement(ancestor, name);
87 if io.write_at(128, &placement)? != placement.len() {
88 return Err(io::Error::from(ErrorKind::WriteZero));
89 }
90 io.flush()
91}
92
93/// `place` under the conservative filesystem adapter's whole-file exclusion.
78#[cfg(any(unix, windows))]94#[cfg(any(unix, windows))]
79pub fn place_file(path: impl AsRef<Path>, ancestor: [u8; 16], name: &str) -> io::Result<()> {95pub fn place_file(path: impl AsRef<Path>, ancestor: [u8; 16], name: &str) -> io::Result<()> {
80 let mut io = FileIo::open(path, true)?;96 let mut io = FileIo::open(path, true)?;
81 let mut header = [0; 1024];97 let result = place(&mut io, ancestor, name);
82 let result = (|| {
83 if io.read_at(0, &mut header)? != header.len() {
84 return Err(io::Error::from(ErrorKind::UnexpectedEof));
85 }
86 crate::Header::parse(&header)
87 .map_err(|error| io::Error::new(ErrorKind::InvalidData, error.message))?;
88 let placement = crate::create::placement(ancestor, name);
89 if io.write_at(128, &placement)? != placement.len() {
90 return Err(io::Error::from(ErrorKind::WriteZero));
91 }
92 io.flush()
93 })();
94 let released = io.release();98 let released = io.release();
95 result?;99 result?;
96 released100 released
crates/onestore/src/formatting.rs+3
...@@ -35,6 +35,8 @@ pub enum TextAttribute {...@@ -35,6 +35,8 @@ pub enum TextAttribute {
35 Color(Option<[u8; 3]>),35 Color(Option<[u8; 3]>),
36 /// RGB, or None to clear highlighting.36 /// RGB, or None to clear highlighting.
37 Highlight(Option<[u8; 3]>),37 Highlight(Option<[u8; 3]>),
38 /// Windows language id of the run, such as 1033 for US English.
39 Language(u32),
38 /// Hidden text, such as a hyperlink's field code.40 /// Hidden text, such as a hyperlink's field code.
39 Hidden(bool),41 Hidden(bool),
40 /// The run belongs to a hyperlink field.42 /// The run belongs to a hyperlink field.
...@@ -73,6 +75,7 @@ impl TextAttribute {...@@ -73,6 +75,7 @@ impl TextAttribute {
73 }75 }
74 (0x10001c0b, ((*points * 2.0) as u16).to_le_bytes().to_vec())76 (0x10001c0b, ((*points * 2.0) as u16).to_le_bytes().to_vec())
75 }77 }
78 Self::Language(language) => (0x14001c3b, language.to_le_bytes().to_vec()),
76 Self::Color(color) | Self::Highlight(color) => (79 Self::Color(color) | Self::Highlight(color) => (
77 if matches!(self, Self::Color(_)) {80 if matches!(self, Self::Color(_)) {
78 0x14001c0c81 0x14001c0c
crates/onestore/src/lib.rs+1-1
...@@ -27,7 +27,7 @@ mod write;...@@ -27,7 +27,7 @@ mod write;
2727
28pub use commit::{28pub use commit::{
29 CommitError, CommitIo, CommitState, PreparedEdit, commit_property_bytes, commit_text,29 CommitError, CommitIo, CommitState, PreparedEdit, commit_property_bytes, commit_text,
30 confirm_snapshot,30 confirm_snapshot, place,
31};31};
32#[cfg(any(unix, windows))]32#[cfg(any(unix, windows))]
33pub use commit::{33pub use commit::{
crates/onestore/src/page/mod.rs+27
...@@ -421,6 +421,33 @@ impl Page {...@@ -421,6 +421,33 @@ impl Page {
421 let mut copy: Self =421 let mut copy: Self =
422 serde_json::from_value(value).map_err(|_| invalid("Page copy does not deserialize"))?;422 serde_json::from_value(value).map_err(|_| invalid("Page copy does not deserialize"))?;
423 copy.identity = Some(crate::write::fresh_guid()?);423 copy.identity = Some(crate::write::fresh_guid()?);
424 // A paragraph's indent level follows its parent chain; a level beyond that comes
425 // from an outline group, which the model does not hold.
426 fn levels(paragraphs: &[PageParagraph]) -> bool {
427 let by_id: BTreeMap<ExGuid, u32> = paragraphs.iter().map(|p| (p.id, p.level)).collect();
428 paragraphs.iter().all(|paragraph| {
429 let expected = match paragraph.parent {
430 Some(parent) => by_id.get(&parent).map_or(0, |level| level + 1),
431 None => 1,
432 };
433 paragraph.level == expected
434 && match &paragraph.content {
435 ParagraphContent::Table(table) => table
436 .rows
437 .iter()
438 .flat_map(|row| &row.cells)
439 .all(|cell| levels(&cell.paragraphs)),
440 _ => true,
441 }
442 })
443 }
444 let expressible = copy.objects.iter().all(|object| match object {
445 PageObject::Outline(outline) => levels(&outline.paragraphs),
446 _ => true,
447 });
448 if !expressible {
449 return Err(invalid("Outline groups cannot be copied"));
450 }
424 Ok(copy)451 Ok(copy)
425 }452 }
426453
crates/onestore/src/page/write.rs+55-7
...@@ -355,6 +355,7 @@ pub(crate) fn write_page(...@@ -355,6 +355,7 @@ pub(crate) fn write_page(
355 page: *page,355 page: *page,
356 author,356 author,
357 alias: BTreeMap::new(),357 alias: BTreeMap::new(),
358 built: BTreeSet::new(),
358 };359 };
359 lowering.run(&before, after, &raw.objects.keys().copied().collect())?;360 lowering.run(&before, after, &raw.objects.keys().copied().collect())?;
360 if lowering.image == source {361 if lowering.image == source {
...@@ -457,6 +458,8 @@ struct Lowering<'a> {...@@ -457,6 +458,8 @@ struct Lowering<'a> {
457 author: &'a str,458 author: &'a str,
458 /// Model identities of new objects mapped to the identities the typed writers allocated.459 /// Model identities of new objects mapped to the identities the typed writers allocated.
459 alias: BTreeMap<ExGuid, ExGuid>,460 alias: BTreeMap<ExGuid, ExGuid>,
461 /// Tables whose structure this run has written.
462 built: BTreeSet<ExGuid>,
460}463}
461464
462impl Lowering<'_> {465impl Lowering<'_> {
...@@ -900,20 +903,48 @@ impl Lowering<'_> {...@@ -900,20 +903,48 @@ impl Lowering<'_> {
900 collect_containers(&outline.paragraphs, &mut containers);903 collect_containers(&outline.paragraphs, &mut containers);
901 }904 }
902 }905 }
903 // Cells that do not exist yet receive their paragraphs once the table structure does.906 // Cells that do not exist yet, and containers inside them, receive their
907 // paragraphs once the table structure does.
908 let new_cell = |id: &ExGuid| {
909 !(old.children.contains_key(id)
910 || new.paragraphs.contains_key(id)
911 || new.outlines.contains_key(id))
912 };
904 let (existing, deferred): (Vec<ExGuid>, Vec<ExGuid>) =913 let (existing, deferred): (Vec<ExGuid>, Vec<ExGuid>) =
905 containers.into_iter().partition(|container| {914 containers.into_iter().partition(|container| {
906 old.children.contains_key(container)915 let mut at = *container;
907 || new.paragraphs.contains_key(container)916 loop {
908 || new.outlines.contains_key(container)917 if new_cell(&at) {
918 return false;
919 }
920 match new.container.get(&at) {
921 Some(parent) => at = *parent,
922 None => return true,
923 }
924 }
909 });925 });
910 self.place_containers(old, new, placed, &existing)?;926 self.place_containers(old, new, placed, &existing)?;
911 self.edit_table_structure(old, new)?;927 // Each round builds the tables whose holders exist and fills their cells, which
928 // may hold further new tables.
929 let mut pending = deferred;
930 loop {
931 self.edit_table_structure(old, new)?;
932 if pending.is_empty() {
933 break;
934 }
935 let (ready, waiting): (Vec<ExGuid>, Vec<ExGuid>) = pending
936 .into_iter()
937 .partition(|container| self.alias.contains_key(container));
938 if ready.is_empty() {
939 return Err(invalid("Table content has no table to hold it"));
940 }
941 self.place_containers(old, new, placed, &ready)?;
942 pending = waiting;
943 }
912 self.edit_cells(old, new)?;944 self.edit_cells(old, new)?;
913 self.edit_images(old, new)?;945 self.edit_images(old, new)?;
914 self.edit_ink_paragraphs(old, new)?;946 self.edit_ink_paragraphs(old, new)?;
915 self.edit_attachments(old, new)?;947 self.edit_attachments(old, new)?;
916 self.place_containers(old, new, placed, &deferred)?;
917 Ok(())948 Ok(())
918 }949 }
919950
...@@ -1577,6 +1608,13 @@ impl Lowering<'_> {...@@ -1577,6 +1608,13 @@ impl Lowering<'_> {
1577 continue;1608 continue;
1578 };1609 };
1579 let previous = old_tables.get(&table.id).copied();1610 let previous = old_tables.get(&table.id).copied();
1611 // A table is built once its holder is placed, and only once.
1612 if !(old.paragraphs.contains_key(paragraph_id) || self.alias.contains_key(paragraph_id))
1613 || self.built.contains(&table.id)
1614 {
1615 continue;
1616 }
1617 self.built.insert(table.id);
1580 let unchanged = previous.is_some_and(|previous| {1618 let unchanged = previous.is_some_and(|previous| {
1581 previous.columns == table.columns1619 previous.columns == table.columns
1582 && previous.borders == table.borders1620 && previous.borders == table.borders
...@@ -2450,6 +2488,9 @@ impl Lowering<'_> {...@@ -2450,6 +2488,9 @@ impl Lowering<'_> {
2450 spacing!(space_before, 0x1400342e);2488 spacing!(space_before, 0x1400342e);
2451 spacing!(space_after, 0x1400342f);2489 spacing!(space_after, 0x1400342f);
2452 spacing!(line_spacing, 0x14003430);2490 spacing!(line_spacing, 0x14003430);
2491 field!(language, value, {
2492 (0x14001c3b, value.to_le_bytes().to_vec())
2493 });
2453 if values.is_empty() {2494 if values.is_empty() {
2454 continue;2495 continue;
2455 }2496 }
...@@ -2827,6 +2868,13 @@ fn attributes(current: &Format, target: &Format, fresh: bool) -> Result<Vec<Text...@@ -2827,6 +2868,13 @@ fn attributes(current: &Format, target: &Format, fresh: bool) -> Result<Vec<Text
2827 None => return Err(inherited()),2868 None => return Err(inherited()),
2828 }2869 }
2829 }2870 }
2871 if current.language != target.language {
2872 match target.language {
2873 Some(language) => out.push(TextAttribute::Language(language)),
2874 None if fresh => {}
2875 None => return Err(inherited()),
2876 }
2877 }
2830 // An absent value and its stored default are the same formatting.2878 // An absent value and its stored default are the same formatting.
2831 let flag = |a: Option<bool>, b: Option<bool>| a.unwrap_or(false) == b.unwrap_or(false);2879 let flag = |a: Option<bool>, b: Option<bool>| a.unwrap_or(false) == b.unwrap_or(false);
2832 let points = |a: Option<f32>, b: Option<f32>| a.unwrap_or(0.0) == b.unwrap_or(0.0);2880 let points = |a: Option<f32>, b: Option<f32>| a.unwrap_or(0.0) == b.unwrap_or(0.0);
...@@ -2840,7 +2888,7 @@ fn attributes(current: &Format, target: &Format, fresh: bool) -> Result<Vec<Text...@@ -2840,7 +2888,7 @@ fn attributes(current: &Format, target: &Format, fresh: bool) -> Result<Vec<Text
2840 && points(current.list_spacing, target.list_spacing);2888 && points(current.list_spacing, target.list_spacing);
2841 if !same_rest {2889 if !same_rest {
2842 return Err(invalid(2890 return Err(invalid(
2843 "Fields, language and paragraph spacing cannot be edited through the page model",2891 "Fields and paragraph spacing cannot be edited through the page model",
2844 ));2892 ));
2845 }2893 }
2846 Ok(out)2894 Ok(out)
tools/test_smb_structure.py created+29
...@@ -0,0 +1,29 @@
1#!/usr/bin/env python3
2"""Run the notebook-structure lane against a disposable Linux lab VM's Samba share."""
3import argparse
4import os
5from pathlib import Path
6import subprocess
7import sys
8
9sys.path.insert(0, str(Path(__file__).resolve().parent / 'w7'))
10from linux_vm import load_instance
11
12
13def main():
14 parser = argparse.ArgumentParser(description=__doc__)
15 parser.add_argument('vm', help='An already running, caller-owned Linux lab VM')
16 parser.add_argument('output', type=Path)
17 args = parser.parse_args()
18 output = args.output.resolve()
19 output.mkdir(parents=True, exist_ok=False)
20 config = load_instance(args.vm)
21 env = dict(os.environ, ONESTORE_SMB_LAB=f'127.0.0.1:{config["samba_port"]}')
22 with (output / 'structure.log').open('w') as result:
23 subprocess.run(['cargo', 'test', '-p', 'notebook', '--features', 'smb', 'smb::tests::live_structure', '--', '--ignored', '--exact'],
24 env=env, stdout=result, stderr=result, check=True, timeout=600)
25 print('structure: passed', flush=True)
26
27
28if __name__ == '__main__':
29 main()