| author | |
| committer | |
| log | 50cc916d18c0cf6e5bf920d93cbfc83a8d827dec |
| tree | 3030968bc6c42fe70ea6f23f2192387798c78882 |
| parent | cc0cc614db453797dd5ba20e68e57618cb9c323f |
| signature | Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU |
Update page and metadata title caches in the same revision as outline coordinates. Preserve explicit titles, content identities and historical revisions.
Verified against three native OneNote controls and independent cold reopens, interruption and offline lost-reply tests, 3,692 ASan executions, full core/offline/Python suites, Clippy and both iOS targets.
Assisted-by: gpt-6-astra30 files changed, 520 insertions(+), 5 deletions(-)
corpus/outline-edit/automatic-title/README.md created+29| ... | @@ -0,0 +1,29 @@ | ||
| 1 | # Automatic titles after outline movement | ||
| 2 | |||
| 3 | Three native pages distinguish vertical ordering, horizontal ordering and an | ||
| 4 | explicit title. Moving the second outline ahead of the first changes the two | ||
| 5 | automatic titles to `Second 🦋 é`; the explicit title remains unchanged. | ||
| 6 | |||
| 7 | `before` is the native source. `native` records the corresponding COM moves and | ||
| 8 | an independent cold reopen. `candidate` is the Rust edit; `cold` is its independent | ||
| 9 | OneNote reopen. Cold captures preserve exact active graphs. Rust movement retains | ||
| 10 | all text identities, content, formatting and history. Identical artifacts link | ||
| 11 | to one copy; `provenance.json` records the native runs and clone teardown. | ||
| 12 | |||
| 13 | The Rust regression originally left both automatic-title fields unchanged. | ||
| 14 | Position edits now update the existing page and metadata caches within the same | ||
| 15 | revision as the coordinates. The offline regression additionally changes the | ||
| 16 | remote title-producing text, loses the publication reply and reopens the cache | ||
| 17 | before confirmation and a dependent edit. | ||
| 18 | |||
| 19 | Run `tools.test_outline_edit.OutlineEditTest.test_automatic_titles_follow_native_and_rust_outline_movement` | ||
| 20 | through Python unittest after building the workspace examples with all features. | ||
| 21 | The Rust test `native_automatic_and_explicit_titles_follow_outline_movement` can | ||
| 22 | export a new candidate directory through `ONESTORE_OUTLINE_TITLE_OUTPUT`. | ||
| 23 | |||
| 24 | To regenerate the native controls, copy `fixture.json` and the synthetic section | ||
| 25 | from `corpus/native-delete/deletion-05/before` into a new input notebook directory. | ||
| 26 | Capture it with `tools/native_runner.py --author tools/native/pages.ps1 | ||
| 27 | --collect-notebook`. Capture that result's notebook with | ||
| 28 | `--author tools/native/outline-title.ps1 --collect-notebook`, then independently | ||
| 29 | cold-open both the native result and the Rust candidate without an author script. | ||
corpus/outline-edit/automatic-title/before/notebook/TitleControl.one created| Binary files /dev/null and b/corpus/outline-edit/automatic-title/before/notebook/TitleControl.one differ | |||
corpus/outline-edit/automatic-title/before/read/hierarchy.xml created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{DA3C9D47-DCD5-4182-B17E-00B4CB6DBB11}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T03:24:51.000Z" color="#B49EDE"><one:Section name="synthetic" ID="{394348B6-5F2A-0E82-2852-925C1D38F660}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-09T03:24:50.000Z" color="#8AA8E4"><one:Page ID="{150ABB30-D6E1-4D93-AF35-3ACDE100EA7F}{1}{B0}" name="Fictitious: café, 東京, مرحبا" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-05T05:11:41.000Z" pageLevel="1"/><one:Page ID="{A27B78AE-FBA3-478F-A7E6-454169D601B1}{1}{B0}" name="Recoverable deletion marker." dateTime="2026-09-05T05:13:44.000Z" lastModifiedTime="2026-09-09T03:24:50.000Z" pageLevel="1"/></one:Section><one:Section name="TitleControl" ID="{9DD2EE3E-53D3-055D-1343-6D76EC035493}{1}{B0}" path="C:\one-tests\runs\capture\notebook\TitleControl.one" lastModifiedTime="2026-09-09T03:24:51.000Z" color="#91BAAE"><one:Page ID="{C3EACB1F-9059-03CD-183C-7E9CC1836345}{1}{B0}" name="Vertical first" dateTime="2026-09-09T03:23:11.000Z" lastModifiedTime="2026-09-09T03:24:50.000Z" pageLevel="1"/><one:Page ID="{9FEC010A-A83E-0825-0E19-3A97BD2A52B2}{1}{B0}" name="Horizontal first" dateTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:24:51.000Z" pageLevel="1"/><one:Page ID="{AADEDA09-4488-0EA7-0D68-2D736F66B180}{1}{B0}" name="Explicit title" dateTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:24:51.000Z" pageLevel="1"/></one:Section></one:Notebook> | ||
corpus/outline-edit/automatic-title/before/read/page-001.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="{9FEC010A-A83E-0825-0E19-3A97BD2A52B2}{1}{B0}" name="Horizontal first" dateTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:24:51.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.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 lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:23:15.000Z" objectID="{DA96F918-BCB0-00B1-275C-531A3B751F13}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{B34CBB93-66AE-08F0-2FFD-FBC5A1A32040}{10}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="106.3570098876953" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{B34CBB93-66AE-08F0-2FFD-FBC5A1A32040}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Horizontal first]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{B34CBB93-66AE-08F0-2FFD-FBC5A1A32040}{18}{B0}"><one:Position x="288.0" y="108.0" z="1"/><one:Size width="88.31009674072265" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{B34CBB93-66AE-08F0-2FFD-FBC5A1A32040}{19}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Second &#129419; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | ||
corpus/outline-edit/automatic-title/before/read/page-003.xml created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{AADEDA09-4488-0EA7-0D68-2D736F66B180}{1}{B0}" name="Explicit title" dateTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:24:51.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{6067E70A-7183-0DA4-2D96-035E02A9AC94}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Explicit title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{6067E70A-7183-0DA4-2D96-035E02A9AC94}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="91.95165252685547" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{6067E70A-7183-0DA4-2D96-035E02A9AC94}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Explicit title]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{6067E70A-7183-0DA4-2D96-035E02A9AC94}{45}{B0}"><one:Position x="288.0" y="216.0" z="1"/><one:Size width="88.31009674072265" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{6067E70A-7183-0DA4-2D96-035E02A9AC94}{46}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Second &#129419; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | ||
corpus/outline-edit/automatic-title/before/read/page-004.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="{C3EACB1F-9059-03CD-183C-7E9CC1836345}{1}{B0}" name="Vertical first" dateTime="2026-09-09T03:23:11.000Z" lastModifiedTime="2026-09-09T03:24:50.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.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 lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:23:12.000Z" lastModifiedTime="2026-09-09T03:23:12.000Z" objectID="{5FEC959F-51F9-0B93-0279-DCD133119A42}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:23:15.000Z" objectID="{5FEC959F-51F9-0B93-0279-DCD133119A42}{28}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="94.14306640625" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:23:15.000Z" objectID="{5FEC959F-51F9-0B93-0279-DCD133119A42}{29}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Vertical first]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:23:15.000Z" objectID="{5FEC959F-51F9-0B93-0279-DCD133119A42}{34}{B0}"><one:Position x="288.0" y="216.0" z="1"/><one:Size width="88.31009674072265" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:13.000Z" lastModifiedTime="2026-09-09T03:23:15.000Z" objectID="{5FEC959F-51F9-0B93-0279-DCD133119A42}{35}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Second &#129419; é]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | ||
corpus/outline-edit/automatic-title/candidate/TitleControl.one created| Binary files /dev/null and b/corpus/outline-edit/automatic-title/candidate/TitleControl.one differ | |||
corpus/outline-edit/automatic-title/cold/notebook/TitleControl.one created| Binary files /dev/null and b/corpus/outline-edit/automatic-title/cold/notebook/TitleControl.one differ | |||
corpus/outline-edit/automatic-title/cold/read/hierarchy.xml created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{B5E192AF-AAAF-4FBF-96F2-4419A3603E0E}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T03:31:50.000Z" color="#B49EDE"><one:Section name="TitleControl" ID="{477597C2-B969-004D-27A6-92594658FE4F}{1}{B0}" path="C:\one-tests\runs\capture\notebook\TitleControl.one" lastModifiedTime="2026-09-09T03:31:50.000Z" color="#91BAAE"><one:Page ID="{194DB2E3-7AE3-06DD-2CD9-81B36BD8C999}{1}{B0}" name="Second 🦋 é" dateTime="2026-09-09T03:23:11.000Z" lastModifiedTime="2026-09-09T03:31:50.000Z" pageLevel="1"/><one:Page ID="{454B78F6-4284-0D35-3AFC-C5B81771F86E}{1}{B0}" name="Second 🦋 é" dateTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:31:50.000Z" pageLevel="1"/><one:Page ID="{7079A3F5-AE32-0BB7-398D-D25CC53D1B5C}{1}{B0}" name="Explicit title" dateTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:31:50.000Z" pageLevel="1"/></one:Section></one:Notebook> | ||
corpus/outline-edit/automatic-title/cold/read/page-000.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="{194DB2E3-7AE3-06DD-2CD9-81B36BD8C999}{1}{B0}" name="Second 🦋 é" dateTime="2026-09-09T03:23:11.000Z" lastModifiedTime="2026-09-09T03:31:50.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.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 lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:23:12.000Z" lastModifiedTime="2026-09-09T03:23:12.000Z" objectID="{854BEC63-BB43-0E83-369C-23FE994A309E}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:29:37.000Z" objectID="{854BEC63-BB43-0E83-369C-23FE994A309E}{34}{B0}"><one:Position x="36.0" y="72.0" z="1"/><one:Size width="88.3100814819336" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:13.000Z" lastModifiedTime="2026-09-09T03:23:15.000Z" objectID="{854BEC63-BB43-0E83-369C-23FE994A309E}{35}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Second &#129419; é]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:23:15.000Z" objectID="{854BEC63-BB43-0E83-369C-23FE994A309E}{28}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="94.14306640625" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:23:15.000Z" objectID="{854BEC63-BB43-0E83-369C-23FE994A309E}{29}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Vertical first]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | ||
corpus/outline-edit/automatic-title/cold/read/page-001.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="{454B78F6-4284-0D35-3AFC-C5B81771F86E}{1}{B0}" name="Second 🦋 é" dateTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:31:50.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.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 lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:23:15.000Z" objectID="{003180E4-560A-05A1-13B9-AC35912EB5CF}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:29:37.000Z" objectID="{69EBC26F-8C14-0DE0-1B18-04EA0BF88A9C}{18}{B0}"><one:Position x="36.0" y="108.0" z="1"/><one:Size width="88.3100814819336" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{69EBC26F-8C14-0DE0-1B18-04EA0BF88A9C}{19}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Second &#129419; é]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{69EBC26F-8C14-0DE0-1B18-04EA0BF88A9C}{10}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="106.3570098876953" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{69EBC26F-8C14-0DE0-1B18-04EA0BF88A9C}{11}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Horizontal first]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | ||
corpus/outline-edit/automatic-title/cold/read/page-002.xml created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{7079A3F5-AE32-0BB7-398D-D25CC53D1B5C}{1}{B0}" name="Explicit title" dateTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:31:50.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{BAC09EF6-9B39-08B4-1973-FC71A8F20648}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Explicit title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:29:37.000Z" objectID="{BAC09EF6-9B39-08B4-1973-FC71A8F20648}{45}{B0}"><one:Position x="36.0" y="72.0" z="1"/><one:Size width="88.3100814819336" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{BAC09EF6-9B39-08B4-1973-FC71A8F20648}{46}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Second &#129419; é]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{BAC09EF6-9B39-08B4-1973-FC71A8F20648}{39}{B0}"><one:Position x="72.0" y="108.0" z="0"/><one:Size width="91.95165252685547" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{BAC09EF6-9B39-08B4-1973-FC71A8F20648}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Explicit title]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | ||
corpus/outline-edit/automatic-title/fixture.json created+1| ... | @@ -0,0 +1 @@ | ||
| 1 | {"sections": ["TitleControl.one"], "pages": [{"section": "TitleControl.one", "xml": "<one:Page xmlns:one=\"http://schemas.microsoft.com/office/onenote/2010/onenote\"><one:Outline><one:Position x=\"72\" y=\"108\"/><one:OEChildren><one:OE><one:T>Vertical first</one:T></one:OE></one:OEChildren></one:Outline><one:Outline><one:Position x=\"288\" y=\"216\"/><one:OEChildren><one:OE><one:T>Second \ud83e\udd8b e\u0301</one:T></one:OE></one:OEChildren></one:Outline></one:Page>"}, {"section": "TitleControl.one", "xml": "<one:Page xmlns:one=\"http://schemas.microsoft.com/office/onenote/2010/onenote\"><one:Outline><one:Position x=\"72\" y=\"108\"/><one:OEChildren><one:OE><one:T>Horizontal first</one:T></one:OE></one:OEChildren></one:Outline><one:Outline><one:Position x=\"288\" y=\"108\"/><one:OEChildren><one:OE><one:T>Second \ud83e\udd8b e\u0301</one:T></one:OE></one:OEChildren></one:Outline></one:Page>"}, {"section": "TitleControl.one", "xml": "<one:Page xmlns:one=\"http://schemas.microsoft.com/office/onenote/2010/onenote\"><one:Title><one:OE><one:T>Explicit title</one:T></one:OE></one:Title><one:Outline><one:Position x=\"72\" y=\"108\"/><one:OEChildren><one:OE><one:T>Explicit title</one:T></one:OE></one:OEChildren></one:Outline><one:Outline><one:Position x=\"288\" y=\"216\"/><one:OEChildren><one:OE><one:T>Second \ud83e\udd8b e\u0301</one:T></one:OE></one:OEChildren></one:Outline></one:Page>"}]} | ||
| \ No newline at end of file | |||
corpus/outline-edit/automatic-title/native/cold/notebook/TitleControl.one created+1| ... | @@ -0,0 +1 @@ | ||
| 1 | ../../notebook/TitleControl.one | ||
| \ No newline at end of file | |||
corpus/outline-edit/automatic-title/native/cold/read/hierarchy.xml created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{EBC82F4B-9A9F-4889-96E4-F127498CE3C1}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T03:31:49.000Z" color="#B49EDE"><one:Section name="synthetic" ID="{2C8617A7-DFD6-05EF-1476-4BA78025D20F}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-09T03:31:49.000Z" color="#8AA8E4"><one:Page ID="{00CFE421-561D-46FE-9311-E3367C1DCE10}{1}{B0}" name="Fictitious: café, 東京, مرحبا" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-05T05:11:41.000Z" pageLevel="1"/><one:Page ID="{B7BE27BF-7B5F-4CE2-9BC2-9CBAF4CB25DE}{1}{B0}" name="Recoverable deletion marker." dateTime="2026-09-05T05:13:44.000Z" lastModifiedTime="2026-09-09T03:31:49.000Z" pageLevel="1"/></one:Section><one:Section name="TitleControl" ID="{E01F1A8D-2CB0-04DA-2AC0-CFD96110A464}{1}{B0}" path="C:\one-tests\runs\capture\notebook\TitleControl.one" lastModifiedTime="2026-09-09T03:31:49.000Z" color="#91BAAE"><one:Page ID="{BE273FAC-EF3A-024A-21BF-DC334C9093B2}{1}{B0}" name="Second 🦋 é" dateTime="2026-09-09T03:23:11.000Z" lastModifiedTime="2026-09-09T03:31:49.000Z" pageLevel="1"/><one:Page ID="{E221F5B9-D75D-09A2-379A-98383039A245}{1}{B0}" name="Second 🦋 é" dateTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:31:49.000Z" pageLevel="1"/><one:Page ID="{D7132EBA-3BEB-0F20-34EB-8FDCE2754177}{1}{B0}" name="Explicit title" dateTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:31:49.000Z" pageLevel="1"/></one:Section></one:Notebook> | ||
corpus/outline-edit/automatic-title/native/cold/read/page-002.xml created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{BE273FAC-EF3A-024A-21BF-DC334C9093B2}{1}{B0}" name="Second 🦋 é" dateTime="2026-09-09T03:23:11.000Z" lastModifiedTime="2026-09-09T03:31:49.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:QuickStyleDef index="2" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:23:12.000Z" lastModifiedTime="2026-09-09T03:23:12.000Z" objectID="{2221612C-2E9A-0A14-3BFA-7E7EBE026AB5}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:27:22.000Z" objectID="{2221612C-2E9A-0A14-3BFA-7E7EBE026AB5}{34}{B0}"><one:Position x="36.0" y="72.0" z="0"/><one:Size width="88.3100814819336" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:13.000Z" lastModifiedTime="2026-09-09T03:27:22.000Z" objectID="{2221612C-2E9A-0A14-3BFA-7E7EBE026AB5}{35}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Second &#129419; é]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:23:15.000Z" objectID="{2221612C-2E9A-0A14-3BFA-7E7EBE026AB5}{28}{B0}"><one:Position x="72.0" y="108.0" z="1"/><one:Size width="94.14306640625" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:23:15.000Z" objectID="{2221612C-2E9A-0A14-3BFA-7E7EBE026AB5}{29}{B0}" alignment="left" quickStyleIndex="2"><one:T><![CDATA[Vertical first]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | ||
corpus/outline-edit/automatic-title/native/cold/read/page-003.xml created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{D7132EBA-3BEB-0F20-34EB-8FDCE2754177}{1}{B0}" name="Explicit title" dateTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:31:49.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{1DAA13B9-0EE0-0C23-1415-A1F18FBA5C63}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Explicit title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:27:23.000Z" objectID="{1DAA13B9-0EE0-0C23-1415-A1F18FBA5C63}{45}{B0}"><one:Position x="36.0" y="72.0" z="0"/><one:Size width="88.3100814819336" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:27:23.000Z" objectID="{1DAA13B9-0EE0-0C23-1415-A1F18FBA5C63}{46}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Second &#129419; é]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{1DAA13B9-0EE0-0C23-1415-A1F18FBA5C63}{39}{B0}"><one:Position x="72.0" y="108.0" z="1"/><one:Size width="91.95165252685547" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{1DAA13B9-0EE0-0C23-1415-A1F18FBA5C63}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Explicit title]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | ||
corpus/outline-edit/automatic-title/native/cold/read/page-004.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="{E221F5B9-D75D-09A2-379A-98383039A245}{1}{B0}" name="Second 🦋 é" dateTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:31:49.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:QuickStyleDef index="2" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:23:15.000Z" objectID="{A75B0DAB-C3D3-0136-1EDF-F1B5B666EFE4}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:27:23.000Z" objectID="{CE814F20-19CD-0977-167E-596A2CB0D0B7}{18}{B0}"><one:Position x="36.0" y="108.0" z="0"/><one:Size width="88.3100814819336" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:27:23.000Z" objectID="{CE814F20-19CD-0977-167E-596A2CB0D0B7}{19}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Second &#129419; é]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{CE814F20-19CD-0977-167E-596A2CB0D0B7}{10}{B0}"><one:Position x="72.0" y="132.9477233886719" z="1"/><one:Size width="106.3570098876953" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{CE814F20-19CD-0977-167E-596A2CB0D0B7}{11}{B0}" alignment="left" quickStyleIndex="2"><one:T><![CDATA[Horizontal first]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | ||
corpus/outline-edit/automatic-title/native/notebook/TitleControl.one created| Binary files /dev/null and b/corpus/outline-edit/automatic-title/native/notebook/TitleControl.one differ | |||
corpus/outline-edit/automatic-title/native/read/hierarchy.xml created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Notebook xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" name="notebook" nickname="notebook" ID="{A6F0CAE4-ADEC-45E6-A9E1-FA71B8C62FFC}{1}{B0}" path="C:\one-tests\runs\capture\notebook" lastModifiedTime="2026-09-09T03:28:39.000Z" color="#B49EDE"><one:Section name="synthetic" ID="{6D327C19-B637-0A6D-1453-C8CD4044C8E8}{1}{B0}" path="C:\one-tests\runs\capture\notebook\synthetic.one" lastModifiedTime="2026-09-09T03:27:16.000Z" color="#8AA8E4"><one:Page ID="{417B8F9F-3FFC-497C-9334-605CBC7CD4F7}{1}{B0}" name="Fictitious: café, 東京, مرحبا" dateTime="2026-09-05T05:06:54.000Z" lastModifiedTime="2026-09-05T05:11:41.000Z" pageLevel="1"/><one:Page ID="{F60A4C01-12BE-4360-9BE7-1FD034AA3F39}{1}{B0}" name="Recoverable deletion marker." dateTime="2026-09-05T05:13:44.000Z" lastModifiedTime="2026-09-09T03:27:16.000Z" pageLevel="1"/></one:Section><one:Section name="TitleControl" ID="{17257140-1479-079C-3F6C-F5E3FBDC3AE5}{1}{B0}" path="C:\one-tests\runs\capture\notebook\TitleControl.one" lastModifiedTime="2026-09-09T03:28:39.000Z" color="#91BAAE"><one:Page ID="{491D5461-D7F3-010C-3413-E609D65C0D33}{1}{B0}" name="Second 🦋 é" dateTime="2026-09-09T03:23:11.000Z" lastModifiedTime="2026-09-09T03:27:23.000Z" pageLevel="1"/><one:Page ID="{151B9E74-EF94-0AE4-2236-A202AAF53CC4}{1}{B0}" name="Second 🦋 é" dateTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:27:23.000Z" pageLevel="1"/><one:Page ID="{20294577-0322-0C66-2147-B5E678B9DFF6}{1}{B0}" name="Explicit title" dateTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:27:23.000Z" pageLevel="1"/></one:Section></one:Notebook> | ||
corpus/outline-edit/automatic-title/native/read/page-000.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="{151B9E74-EF94-0AE4-2236-A202AAF53CC4}{1}{B0}" name="Second 🦋 é" dateTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:27:23.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:QuickStyleDef index="2" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:23:15.000Z" objectID="{50616666-FB1A-0270-0B73-CB8F2CAA7165}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:27:23.000Z" objectID="{39BB24ED-2104-0A31-03D2-6350B67C4E36}{18}{B0}"><one:Position x="36.0" y="108.0" z="0"/><one:Size width="88.3100814819336" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:27:23.000Z" objectID="{39BB24ED-2104-0A31-03D2-6350B67C4E36}{19}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Second &#129419; é]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{39BB24ED-2104-0A31-03D2-6350B67C4E36}{10}{B0}"><one:Position x="72.0" y="132.9477233886719" z="1"/><one:Size width="106.3570098876953" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{39BB24ED-2104-0A31-03D2-6350B67C4E36}{11}{B0}" alignment="left" quickStyleIndex="2"><one:T><![CDATA[Horizontal first]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | ||
corpus/outline-edit/automatic-title/native/read/page-001.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="{20294577-0322-0C66-2147-B5E678B9DFF6}{1}{B0}" name="Explicit title" dateTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:27:23.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{EA907874-3629-0F65-01B9-9BCB1576C2E2}{33}{B0}" alignment="left" quickStyleIndex="0" style="font-family:Calibri;font-size:17.0pt"><one:T><![CDATA[Explicit title]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:27:23.000Z" objectID="{EA907874-3629-0F65-01B9-9BCB1576C2E2}{45}{B0}"><one:Position x="36.0" y="72.0" z="0"/><one:Size width="88.3100814819336" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:27:23.000Z" objectID="{EA907874-3629-0F65-01B9-9BCB1576C2E2}{46}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Second &#129419; é]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{EA907874-3629-0F65-01B9-9BCB1576C2E2}{39}{B0}"><one:Position x="72.0" y="108.0" z="1"/><one:Size width="91.95165252685547" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:16.000Z" lastModifiedTime="2026-09-09T03:23:16.000Z" objectID="{EA907874-3629-0F65-01B9-9BCB1576C2E2}{40}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[Explicit title]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | ||
corpus/outline-edit/automatic-title/native/read/page-003.xml created+2| ... | @@ -0,0 +1,2 @@ | ||
| 1 | <?xml version="1.0"?> | ||
| 2 | <one:Page xmlns:one="http://schemas.microsoft.com/office/onenote/2010/onenote" ID="{491D5461-D7F3-010C-3413-E609D65C0D33}{1}{B0}" name="Second 🦋 é" dateTime="2026-09-09T03:23:11.000Z" lastModifiedTime="2026-09-09T03:27:23.000Z" pageLevel="1" lang="en-US"><one:QuickStyleDef index="0" name="PageTitle" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="17.0" spaceBefore="0.0" spaceAfter="0.0"/><one:QuickStyleDef index="1" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:QuickStyleDef index="2" name="p" fontColor="automatic" highlightColor="automatic" font="Calibri" fontSize="11.0" spaceBefore="0.0" spaceAfter="0.0"/><one:PageSettings RTL="false" color="automatic"><one:PageSize><one:Automatic/></one:PageSize><one:RuleLines visible="false"/></one:PageSettings><one:Title lang="en-US"><one:OE author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" creationTime="2026-09-09T03:23:12.000Z" lastModifiedTime="2026-09-09T03:23:12.000Z" objectID="{D51B0AE1-1653-0952-2E56-444424CEF434}{15}{B0}" alignment="left" quickStyleIndex="0"><one:T><![CDATA[]]></one:T></one:OE></one:Title><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:27:22.000Z" objectID="{D51B0AE1-1653-0952-2E56-444424CEF434}{34}{B0}"><one:Position x="36.0" y="72.0" z="0"/><one:Size width="88.3100814819336" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:13.000Z" lastModifiedTime="2026-09-09T03:27:22.000Z" objectID="{D51B0AE1-1653-0952-2E56-444424CEF434}{35}{B0}" alignment="left" quickStyleIndex="1"><one:T><![CDATA[Second &#129419; é]]></one:T></one:OE></one:OEChildren></one:Outline><one:Outline author="virtual" authorInitials="V" lastModifiedBy="virtual" lastModifiedByInitials="V" lastModifiedTime="2026-09-09T03:23:15.000Z" objectID="{D51B0AE1-1653-0952-2E56-444424CEF434}{28}{B0}"><one:Position x="72.0" y="108.0" z="1"/><one:Size width="94.14306640625" height="13.42771339416504"/><one:OEChildren><one:OE creationTime="2026-09-09T03:23:15.000Z" lastModifiedTime="2026-09-09T03:23:15.000Z" objectID="{D51B0AE1-1653-0952-2E56-444424CEF434}{29}{B0}" alignment="left" quickStyleIndex="2"><one:T><![CDATA[Vertical first]]></one:T></one:OE></one:OEChildren></one:Outline></one:Page> | ||
corpus/outline-edit/automatic-title/provenance.json created+116| ... | @@ -0,0 +1,116 @@ | ||
| 1 | { | ||
| 2 | "outline-title-native-before": { | ||
| 3 | "capture": "evidence/m10/outline-title-native-before", | ||
| 4 | "machine": { | ||
| 5 | "name": "m6-cfe84d87", | ||
| 6 | "hostname": "ONE-M6-CFE84D87" | ||
| 7 | }, | ||
| 8 | "run": { | ||
| 9 | "notebook": "/Users/clo/dev/one/evidence/m10/outline-title-input", | ||
| 10 | "expected_pages": -1, | ||
| 11 | "author": "tools/native/pages.ps1", | ||
| 12 | "author_timeout_seconds": 600, | ||
| 13 | "inspect": false, | ||
| 14 | "collect_notebook": true, | ||
| 15 | "base": { | ||
| 16 | "file": "win7-office-base.qcow2", | ||
| 17 | "format": "qcow2", | ||
| 18 | "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", | ||
| 19 | "virtual_size": 68719476736 | ||
| 20 | }, | ||
| 21 | "scripts": { | ||
| 22 | "author.ps1": "dfbf0e016aa4c8486ecea9f39768c633de05ac0db8214139052ca2e9c43bbd02", | ||
| 23 | "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", | ||
| 24 | "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" | ||
| 25 | } | ||
| 26 | }, | ||
| 27 | "teardown": { | ||
| 28 | "absent": true | ||
| 29 | } | ||
| 30 | }, | ||
| 31 | "outline-title-native-after": { | ||
| 32 | "capture": "evidence/m10/outline-title-native-after", | ||
| 33 | "machine": { | ||
| 34 | "name": "m6-49c5ad30", | ||
| 35 | "hostname": "ONE-M6-49C5AD30" | ||
| 36 | }, | ||
| 37 | "run": { | ||
| 38 | "notebook": "/Users/clo/dev/one/evidence/m10/outline-title-native-before/notebook", | ||
| 39 | "expected_pages": -1, | ||
| 40 | "author": "evidence/m10/outline-title-move.ps1", | ||
| 41 | "author_timeout_seconds": 600, | ||
| 42 | "inspect": false, | ||
| 43 | "collect_notebook": true, | ||
| 44 | "base": { | ||
| 45 | "file": "win7-office-base.qcow2", | ||
| 46 | "format": "qcow2", | ||
| 47 | "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", | ||
| 48 | "virtual_size": 68719476736 | ||
| 49 | }, | ||
| 50 | "scripts": { | ||
| 51 | "author.ps1": "9d8779a521fb60ff3063d320babe742deeda1ca7e5cf89f54f5617473e28b998", | ||
| 52 | "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", | ||
| 53 | "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" | ||
| 54 | } | ||
| 55 | }, | ||
| 56 | "teardown": { | ||
| 57 | "absent": true | ||
| 58 | } | ||
| 59 | }, | ||
| 60 | "outline-title-native-cold": { | ||
| 61 | "capture": "evidence/m10/outline-title-native-cold", | ||
| 62 | "machine": { | ||
| 63 | "name": "m6-3512bf26", | ||
| 64 | "hostname": "ONE-M6-3512BF26" | ||
| 65 | }, | ||
| 66 | "run": { | ||
| 67 | "notebook": "/Users/clo/dev/one/evidence/m10/outline-title-native-after/notebook", | ||
| 68 | "expected_pages": 5, | ||
| 69 | "author": null, | ||
| 70 | "author_timeout_seconds": 600, | ||
| 71 | "inspect": false, | ||
| 72 | "collect_notebook": true, | ||
| 73 | "base": { | ||
| 74 | "file": "win7-office-base.qcow2", | ||
| 75 | "format": "qcow2", | ||
| 76 | "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", | ||
| 77 | "virtual_size": 68719476736 | ||
| 78 | }, | ||
| 79 | "scripts": { | ||
| 80 | "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", | ||
| 81 | "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" | ||
| 82 | } | ||
| 83 | }, | ||
| 84 | "teardown": { | ||
| 85 | "absent": true | ||
| 86 | } | ||
| 87 | }, | ||
| 88 | "outline-title-rust-cold": { | ||
| 89 | "capture": "evidence/m10/outline-title-rust-cold", | ||
| 90 | "machine": { | ||
| 91 | "name": "m6-61cabd16", | ||
| 92 | "hostname": "ONE-M6-61CABD16" | ||
| 93 | }, | ||
| 94 | "run": { | ||
| 95 | "notebook": "/Users/clo/dev/one/evidence/m10/outline-title-rust-candidate", | ||
| 96 | "expected_pages": 3, | ||
| 97 | "author": null, | ||
| 98 | "author_timeout_seconds": 600, | ||
| 99 | "inspect": false, | ||
| 100 | "collect_notebook": true, | ||
| 101 | "base": { | ||
| 102 | "file": "win7-office-base.qcow2", | ||
| 103 | "format": "qcow2", | ||
| 104 | "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346", | ||
| 105 | "virtual_size": 68719476736 | ||
| 106 | }, | ||
| 107 | "scripts": { | ||
| 108 | "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331", | ||
| 109 | "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41" | ||
| 110 | } | ||
| 111 | }, | ||
| 112 | "teardown": { | ||
| 113 | "absent": true | ||
| 114 | } | ||
| 115 | } | ||
| 116 | } | ||
crates/onestore-offline/tests/sync/outline.rs+75| ... | @@ -108,6 +108,81 @@ fn layout_and_dependent_text_survive_reopen_and_independent_remote_formatting() | ... | @@ -108,6 +108,81 @@ fn layout_and_dependent_text_survive_reopen_and_independent_remote_formatting() |
| 108 | } | 108 | } |
| 109 | } | 109 | } |
| 110 | 110 | ||
| 111 | #[test] | ||
| 112 | fn rebased_outline_movement_uses_remote_title_text_and_confirms_after_reopen() { | ||
| 113 | let (source, sid, outline, _, text_id) = fixture(); | ||
| 114 | let store = Store::parse(&source).unwrap(); | ||
| 115 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 116 | let document = Document::parse(&index).unwrap(); | ||
| 117 | let (_, page) = document.pages().unwrap()[0]; | ||
| 118 | let space = &document.spaces[&sid]; | ||
| 119 | let metadata = space.revisions[&space.contexts[&ExGuid::default()]].roots[&2]; | ||
| 120 | let second = onestore::Insertion::outline(page, 144.0, 36.0, "Second", "Author").unwrap(); | ||
| 121 | let source = PreparedEdit::insert(&source, sid, &second) | ||
| 122 | .unwrap() | ||
| 123 | .as_bytes() | ||
| 124 | .to_vec(); | ||
| 125 | let directory = tempfile::tempdir().unwrap(); | ||
| 126 | let path = directory.path().join("titles.sqlite"); | ||
| 127 | let cache = Replica::create(&path, &source).unwrap(); | ||
| 128 | let movement = cache | ||
| 129 | .outline( | ||
| 130 | &source, | ||
| 131 | sid, | ||
| 132 | outline, | ||
| 133 | Change::Position { x: 216.0, y: 72.0 }, | ||
| 134 | ) | ||
| 135 | .unwrap() | ||
| 136 | .unwrap(); | ||
| 137 | let local = cache.snapshot().unwrap(); | ||
| 138 | assert_eq!(node(&local, sid, metadata)["kind"]["title"], "Second"); | ||
| 139 | let dependent = cache | ||
| 140 | .edit_text(&local, sid, text_id, 0..0, "Local ") | ||
| 141 | .unwrap() | ||
| 142 | .unwrap(); | ||
| 143 | drop(cache); | ||
| 144 | let remote = | ||
| 145 | PreparedEdit::text(&source, sid, second.text_object(), 0..6, "Remote second 🐈").unwrap(); | ||
| 146 | let mut server = Server::new(remote.as_bytes()); | ||
| 147 | server.fault = Fault::UnknownAfter; | ||
| 148 | let cache = Replica::open(&path).unwrap(); | ||
| 149 | assert!(cache.sync_once(&mut server).is_err()); | ||
| 150 | assert!(matches!( | ||
| 151 | cache.status(movement).unwrap(), | ||
| 152 | Some(EditStatus::AwaitingConfirmation { .. }) | ||
| 153 | )); | ||
| 154 | assert_eq!( | ||
| 155 | node(&server.durable, sid, metadata)["kind"]["title"], | ||
| 156 | "Original 🦀 é" | ||
| 157 | ); | ||
| 158 | assert_eq!( | ||
| 159 | node(&server.visible, sid, metadata)["kind"]["title"], | ||
| 160 | "Remote second 🐈" | ||
| 161 | ); | ||
| 162 | drop(cache); | ||
| 163 | for id in [movement, dependent] { | ||
| 164 | let cache = Replica::open(&path).unwrap(); | ||
| 165 | assert!( | ||
| 166 | matches!(cache.sync_once(&mut server).unwrap(), Some((actual, EditStatus::Published { .. })) if actual == id) | ||
| 167 | ); | ||
| 168 | assert_eq!( | ||
| 169 | node(&server.durable, sid, metadata)["kind"]["title"], | ||
| 170 | "Remote second 🐈" | ||
| 171 | ); | ||
| 172 | assert_eq!( | ||
| 173 | node(&server.durable, sid, page)["kind"]["alternate_title"], | ||
| 174 | "Remote second 🐈" | ||
| 175 | ); | ||
| 176 | } | ||
| 177 | let cache = Replica::open(&path).unwrap(); | ||
| 178 | assert!(cache.pending().unwrap().is_empty()); | ||
| 179 | assert_eq!( | ||
| 180 | node(&cache.snapshot().unwrap(), sid, text_id)["kind"]["text"], | ||
| 181 | "Local Original 🦀 é" | ||
| 182 | ); | ||
| 183 | assert_eq!(server.publications, 2); | ||
| 184 | } | ||
| 185 | |||
| 111 | #[test] | 186 | #[test] |
| 112 | fn competing_layout_requires_current_review_and_preserves_dependent_edits() { | 187 | fn competing_layout_requires_current_review_and_preserves_dependent_edits() { |
| 113 | let (source, sid, outline, _, text_id) = fixture(); | 188 | let (source, sid, outline, _, text_id) = fixture(); |
crates/onestore/src/outline.rs+12-5| ... | @@ -2,7 +2,7 @@ use crate::{ | ... | @@ -2,7 +2,7 @@ use crate::{ |
| 2 | Error, ExGuid, RevisionIndex, Store, | 2 | Error, ExGuid, RevisionIndex, Store, |
| 3 | create::current_timestamps, | 3 | create::current_timestamps, |
| 4 | document::{Document, Kind}, | 4 | document::{Document, Kind}, |
| 5 | edit::editable_parents, | 5 | edit::{editable_parents, update_title}, |
| 6 | write::{PropertyObject, write_revision}, | 6 | write::{PropertyObject, write_revision}, |
| 7 | }; | 7 | }; |
| 8 | use serde::{Deserialize, Serialize}; | 8 | use serde::{Deserialize, Serialize}; |
| ... | @@ -13,6 +13,7 @@ use std::collections::{BTreeMap, BTreeSet}; | ... | @@ -13,6 +13,7 @@ use std::collections::{BTreeMap, BTreeSet}; |
| 13 | #[serde(deny_unknown_fields)] | 13 | #[serde(deny_unknown_fields)] |
| 14 | pub enum OutlineEdit { | 14 | pub enum OutlineEdit { |
| 15 | /// Moves an ordinary page outline to coordinates measured in points. | 15 | /// Moves an ordinary page outline to coordinates measured in points. |
| 16 | /// Refreshes automatic page titles when the leading outline changes. | ||
| 16 | Position { x: f32, y: f32 }, | 17 | Position { x: f32, y: f32 }, |
| 17 | /// Changes an ordinary outline's maximum width in points, at least 36. | 18 | /// Changes an ordinary outline's maximum width in points, at least 36. |
| 18 | /// `user_set` distinguishes an explicit width from an automatic layout hint. | 19 | /// `user_set` distinguishes an explicit width from an automatic layout hint. |
| ... | @@ -56,7 +57,7 @@ impl OutlineEdit { | ... | @@ -56,7 +57,7 @@ impl OutlineEdit { |
| 56 | let store = Store::parse(source)?; | 57 | let store = Store::parse(source)?; |
| 57 | let index = RevisionIndex::parse(&store)?; | 58 | let index = RevisionIndex::parse(&store)?; |
| 58 | index.validate_current()?; | 59 | index.validate_current()?; |
| 59 | let document = Document::parse(&index)?; | 60 | let mut document = Document::parse(&index)?; |
| 60 | let pages: Vec<_> = document | 61 | let pages: Vec<_> = document |
| 61 | .pages()? | 62 | .pages()? |
| 62 | .into_iter() | 63 | .into_iter() |
| ... | @@ -65,9 +66,12 @@ impl OutlineEdit { | ... | @@ -65,9 +66,12 @@ impl OutlineEdit { |
| 65 | let [page] = pages.as_slice() else { | 66 | let [page] = pages.as_slice() else { |
| 66 | return Err(invalid("Outline editing requires a single active page")); | 67 | return Err(invalid("Outline editing requires a single active page")); |
| 67 | }; | 68 | }; |
| 68 | let semantic = &document.spaces[&space]; | 69 | let mut semantic = document.spaces.remove(&space).unwrap(); |
| 69 | let view = &semantic.revisions[&semantic.contexts[&ExGuid::default()]]; | 70 | let view = semantic |
| 70 | let parents = editable_parents(view, &pages, object)?; | 71 | .revisions |
| 72 | .remove(&semantic.contexts[&ExGuid::default()]) | ||
| 73 | .unwrap(); | ||
| 74 | let parents = editable_parents(&view, &pages, object)?; | ||
| 71 | let node = &view.nodes[&object]; | 75 | let node = &view.nodes[&object]; |
| 72 | match self { | 76 | match self { |
| 73 | Self::Collapsed(_) => { | 77 | Self::Collapsed(_) => { |
| ... | @@ -128,6 +132,9 @@ impl OutlineEdit { | ... | @@ -128,6 +132,9 @@ impl OutlineEdit { |
| 128 | changed.insert(id, ancestor); | 132 | changed.insert(id, ancestor); |
| 129 | } | 133 | } |
| 130 | } | 134 | } |
| 135 | if matches!(self, Self::Position { .. }) { | ||
| 136 | update_title(&store, raw, view, &pages, &mut changed)?; | ||
| 137 | } | ||
| 131 | Ok(changed) | 138 | Ok(changed) |
| 132 | }) | 139 | }) |
| 133 | } | 140 | } |
crates/onestore/tests/outline.rs+163| ... | @@ -271,6 +271,146 @@ fn resizing_a_native_reserved_width_preserves_content() { | ... | @@ -271,6 +271,146 @@ fn resizing_a_native_reserved_width_preserves_content() { |
| 271 | } | 271 | } |
| 272 | } | 272 | } |
| 273 | 273 | ||
| 274 | #[test] | ||
| 275 | fn outline_movement_updates_both_automatic_title_fields_without_changing_content() { | ||
| 276 | let source = onestore::create_section("titles.one", "First", "Author").unwrap(); | ||
| 277 | let store = Store::parse(&source).unwrap(); | ||
| 278 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 279 | let document = Document::parse(&index).unwrap(); | ||
| 280 | let (sid, page) = document.pages().unwrap()[0]; | ||
| 281 | let insertion = | ||
| 282 | onestore::Insertion::outline(page, 144.0, 144.0, "Second 🦋 é", "Author").unwrap(); | ||
| 283 | let source = PreparedEdit::insert(&source, sid, &insertion) | ||
| 284 | .unwrap() | ||
| 285 | .as_bytes() | ||
| 286 | .to_vec(); | ||
| 287 | let store = Store::parse(&source).unwrap(); | ||
| 288 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 289 | let document = Document::parse(&index).unwrap(); | ||
| 290 | let space = &document.spaces[&sid]; | ||
| 291 | let rid = space.contexts[&ExGuid::default()]; | ||
| 292 | let before = &space.revisions[&rid]; | ||
| 293 | for (x, y, expected) in [ | ||
| 294 | (144.0, 0.0, "Second 🦋 é"), | ||
| 295 | (0.0, 36.0, "Second 🦋 é"), | ||
| 296 | (144.0, 36.0, "First"), | ||
| 297 | (0.0, 144.0, "First"), | ||
| 298 | ] { | ||
| 299 | let edit = PreparedEdit::outline(&source, sid, insertion.object(), Edit::Position { x, y }) | ||
| 300 | .unwrap(); | ||
| 301 | let saved = Store::parse(edit.as_bytes()).unwrap(); | ||
| 302 | let saved_index = RevisionIndex::parse(&saved).unwrap(); | ||
| 303 | saved_index.validate_current().unwrap(); | ||
| 304 | let saved_document = Document::parse(&saved_index).unwrap(); | ||
| 305 | let saved_space = &saved_document.spaces[&sid]; | ||
| 306 | let after = &saved_space.revisions[&saved_space.contexts[&ExGuid::default()]]; | ||
| 307 | let metadata = before.roots[&2]; | ||
| 308 | assert!( | ||
| 309 | matches!(&after.nodes[&metadata].kind, Kind::Metadata { title: Some(title), .. } if title == expected) | ||
| 310 | ); | ||
| 311 | assert!( | ||
| 312 | matches!(&after.nodes[&page].kind, Kind::Page { alternate_title: Some(title), .. } if title == expected) | ||
| 313 | ); | ||
| 314 | assert_eq!( | ||
| 315 | before.nodes.keys().collect::<Vec<_>>(), | ||
| 316 | after.nodes.keys().collect::<Vec<_>>() | ||
| 317 | ); | ||
| 318 | for (oid, node) in &before.nodes { | ||
| 319 | if ![page, metadata, insertion.object()].contains(oid) { | ||
| 320 | assert_eq!( | ||
| 321 | serde_json::to_value(node).unwrap(), | ||
| 322 | serde_json::to_value(&after.nodes[oid]).unwrap() | ||
| 323 | ); | ||
| 324 | } | ||
| 325 | } | ||
| 326 | assert_eq!( | ||
| 327 | format!("{:?}", index.resolve(sid, rid).unwrap()), | ||
| 328 | format!("{:?}", saved_index.resolve(sid, rid).unwrap()) | ||
| 329 | ); | ||
| 330 | assert_eq!( | ||
| 331 | PreparedEdit::outline( | ||
| 332 | edit.as_bytes(), | ||
| 333 | sid, | ||
| 334 | insertion.object(), | ||
| 335 | Edit::Position { x, y } | ||
| 336 | ) | ||
| 337 | .unwrap() | ||
| 338 | .as_bytes(), | ||
| 339 | edit.as_bytes() | ||
| 340 | ); | ||
| 341 | } | ||
| 342 | } | ||
| 343 | |||
| 344 | #[test] | ||
| 345 | fn native_automatic_and_explicit_titles_follow_outline_movement() { | ||
| 346 | let source = include_bytes!( | ||
| 347 | "../../../corpus/outline-edit/automatic-title/before/notebook/TitleControl.one" | ||
| 348 | ); | ||
| 349 | let store = Store::parse(source).unwrap(); | ||
| 350 | let index = RevisionIndex::parse(&store).unwrap(); | ||
| 351 | let document = Document::parse(&index).unwrap(); | ||
| 352 | let mut candidate = source.to_vec(); | ||
| 353 | let pages = document.pages().unwrap(); | ||
| 354 | assert_eq!(pages.len(), 3); | ||
| 355 | for (sid, page) in pages { | ||
| 356 | let space = &document.spaces[&sid]; | ||
| 357 | let view = &space.revisions[&space.contexts[&ExGuid::default()]]; | ||
| 358 | let Kind::Metadata { | ||
| 359 | title: Some(title), .. | ||
| 360 | } = &view.nodes[&view.roots[&2]].kind | ||
| 361 | else { | ||
| 362 | panic!() | ||
| 363 | }; | ||
| 364 | let outline = *view.nodes[&page].children.iter().find(|oid| { | ||
| 365 | view.nodes[oid].children.iter().any(|paragraph| view.nodes[paragraph].content.iter().any(|text| { | ||
| 366 | matches!(&view.nodes[text].kind, Kind::RichText { text, .. } if text.starts_with("Second")) | ||
| 367 | })) | ||
| 368 | }).unwrap(); | ||
| 369 | let change = Edit::Position { | ||
| 370 | x: 36.0, | ||
| 371 | y: if title == "Horizontal first" { | ||
| 372 | 108.0 | ||
| 373 | } else { | ||
| 374 | 72.0 | ||
| 375 | }, | ||
| 376 | }; | ||
| 377 | candidate = PreparedEdit::outline(&candidate, sid, outline, change) | ||
| 378 | .unwrap() | ||
| 379 | .as_bytes() | ||
| 380 | .to_vec(); | ||
| 381 | let store = Store::parse(&candidate).unwrap(); | ||
| 382 | let saved = RevisionIndex::parse(&store).unwrap(); | ||
| 383 | saved.validate_current().unwrap(); | ||
| 384 | let document = Document::parse(&saved).unwrap(); | ||
| 385 | let space = &document.spaces[&sid]; | ||
| 386 | let after = &space.revisions[&space.contexts[&ExGuid::default()]]; | ||
| 387 | let expected = if title == "Explicit title" { | ||
| 388 | "Explicit title" | ||
| 389 | } else { | ||
| 390 | "Second 🦋 é" | ||
| 391 | }; | ||
| 392 | assert!( | ||
| 393 | matches!(&after.nodes[&after.roots[&2]].kind, Kind::Metadata { title: Some(title), .. } if title == expected) | ||
| 394 | ); | ||
| 395 | for (oid, node) in &view.nodes { | ||
| 396 | assert_eq!(node.children, after.nodes[oid].children); | ||
| 397 | assert_eq!(node.content, after.nodes[oid].content); | ||
| 398 | if matches!(node.kind, Kind::RichText { .. }) { | ||
| 399 | assert_eq!( | ||
| 400 | serde_json::to_value(node).unwrap(), | ||
| 401 | serde_json::to_value(&after.nodes[oid]).unwrap() | ||
| 402 | ); | ||
| 403 | } | ||
| 404 | } | ||
| 405 | } | ||
| 406 | if let Some(output) = std::env::var_os("ONESTORE_OUTLINE_TITLE_OUTPUT") { | ||
| 407 | let output = std::path::PathBuf::from(output); | ||
| 408 | assert!(output.is_absolute()); | ||
| 409 | std::fs::create_dir(&output).unwrap(); | ||
| 410 | std::fs::write(output.join("TitleControl.one"), candidate).unwrap(); | ||
| 411 | } | ||
| 412 | } | ||
| 413 | |||
| 274 | #[test] | 414 | #[test] |
| 275 | fn outline_publication_interruptions_reopen_as_complete_old_or_new_layout() { | 415 | fn outline_publication_interruptions_reopen_as_complete_old_or_new_layout() { |
| 276 | let source = onestore::create_section("layout.one", "Before 🦀 after", "Author").unwrap(); | 416 | let source = onestore::create_section("layout.one", "Before 🦀 after", "Author").unwrap(); |
| ... | @@ -286,6 +426,11 @@ fn outline_publication_interruptions_reopen_as_complete_old_or_new_layout() { | ... | @@ -286,6 +426,11 @@ fn outline_publication_interruptions_reopen_as_complete_old_or_new_layout() { |
| 286 | .find(|id| matches!(view.nodes[id].kind, Kind::Outline { .. })) | 426 | .find(|id| matches!(view.nodes[id].kind, Kind::Outline { .. })) |
| 287 | .unwrap(); | 427 | .unwrap(); |
| 288 | let paragraph = view.nodes[&outline].children[0]; | 428 | let paragraph = view.nodes[&outline].children[0]; |
| 429 | let second = onestore::Insertion::outline(page, 144.0, 36.0, "Second", "Author").unwrap(); | ||
| 430 | let source = PreparedEdit::insert(&source, sid, &second) | ||
| 431 | .unwrap() | ||
| 432 | .as_bytes() | ||
| 433 | .to_vec(); | ||
| 289 | for (object, operation) in [ | 434 | for (object, operation) in [ |
| 290 | (outline, Edit::Position { x: 216.0, y: 72.0 }), | 435 | (outline, Edit::Position { x: 216.0, y: 72.0 }), |
| 291 | ( | 436 | ( |
| ... | @@ -344,6 +489,11 @@ fn repeated_geometry_changes_and_expansion_match_an_independent_model() { | ... | @@ -344,6 +489,11 @@ fn repeated_geometry_changes_and_expansion_match_an_independent_model() { |
| 344 | .unwrap(); | 489 | .unwrap(); |
| 345 | let paragraph = view.nodes[&outline].children[0]; | 490 | let paragraph = view.nodes[&outline].children[0]; |
| 346 | let text = view.nodes[&paragraph].content[0]; | 491 | let text = view.nodes[&paragraph].content[0]; |
| 492 | let second = onestore::Insertion::outline(page, 144.0, 36.0, "Second", "Author").unwrap(); | ||
| 493 | let original = PreparedEdit::insert(&original, sid, &second) | ||
| 494 | .unwrap() | ||
| 495 | .as_bytes() | ||
| 496 | .to_vec(); | ||
| 347 | for seed in 1..=16_u64 { | 497 | for seed in 1..=16_u64 { |
| 348 | let mut rng = seed; | 498 | let mut rng = seed; |
| 349 | let mut source = original.clone(); | 499 | let mut source = original.clone(); |
| ... | @@ -388,6 +538,19 @@ fn repeated_geometry_changes_and_expansion_match_an_independent_model() { | ... | @@ -388,6 +538,19 @@ fn repeated_geometry_changes_and_expansion_match_an_independent_model() { |
| 388 | let document = Document::parse(&index).unwrap(); | 538 | let document = Document::parse(&index).unwrap(); |
| 389 | let space = &document.spaces[&sid]; | 539 | let space = &document.spaces[&sid]; |
| 390 | let current = &space.revisions[&space.contexts[&ExGuid::default()]]; | 540 | let current = &space.revisions[&space.contexts[&ExGuid::default()]]; |
| 541 | let x = layout["x"].as_f64().unwrap(); | ||
| 542 | let y = layout["y"].as_f64().unwrap(); | ||
| 543 | let title = if y < 36.0 || (y == 36.0 && x <= 144.0) { | ||
| 544 | "Before 🦀 é" | ||
| 545 | } else { | ||
| 546 | "Second" | ||
| 547 | }; | ||
| 548 | assert!( | ||
| 549 | matches!(&current.nodes[&current.roots[&2]].kind, Kind::Metadata { title: Some(actual), .. } if actual == title) | ||
| 550 | ); | ||
| 551 | assert!( | ||
| 552 | matches!(&current.nodes[&page].kind, Kind::Page { alternate_title: Some(actual), .. } if actual == title) | ||
| 553 | ); | ||
| 391 | assert_eq!( | 554 | assert_eq!( |
| 392 | serde_json::to_value(&current.nodes[&outline].layout).unwrap(), | 555 | serde_json::to_value(&current.nodes[&outline].layout).unwrap(), |
| 393 | layout | 556 | layout |
fuzz/fuzz_targets/paragraph.rs+10| ... | @@ -33,6 +33,8 @@ static SOURCE: LazyLock<(Vec<u8>, ExGuid, ExGuid)> = LazyLock::new(|| { | ... | @@ -33,6 +33,8 @@ static SOURCE: LazyLock<(Vec<u8>, ExGuid, ExGuid)> = LazyLock::new(|| { |
| 33 | .with_formatting(4..7, &[A::Italic(true), A::Color(Some([12, 34, 56]))]) | 33 | .with_formatting(4..7, &[A::Italic(true), A::Color(Some([12, 34, 56]))]) |
| 34 | .unwrap(); | 34 | .unwrap(); |
| 35 | let edited = PreparedEdit::insert(&source, sid, &intent).unwrap(); | 35 | let edited = PreparedEdit::insert(&source, sid, &intent).unwrap(); |
| 36 | let second = Insertion::outline(page, 144.0, 36.0, "Fixed second", "Author").unwrap(); | ||
| 37 | let edited = PreparedEdit::insert(edited.as_bytes(), sid, &second).unwrap(); | ||
| 36 | (edited.as_bytes().to_vec(), sid, outline) | 38 | (edited.as_bytes().to_vec(), sid, outline) |
| 37 | }); | 39 | }); |
| 38 | 40 | ||
| ... | @@ -187,6 +189,14 @@ fuzz_target!(|input: &[u8]| { | ... | @@ -187,6 +189,14 @@ fuzz_target!(|input: &[u8]| { |
| 187 | let after_document = Document::parse(&after_index).unwrap(); | 189 | let after_document = Document::parse(&after_index).unwrap(); |
| 188 | let space = &after_document.spaces[sid]; | 190 | let space = &after_document.spaces[sid]; |
| 189 | let after_view = &space.revisions[&space.contexts[&ExGuid::default()]]; | 191 | let after_view = &space.revisions[&space.contexts[&ExGuid::default()]]; |
| 192 | let layout = &after_view.nodes[outline].layout; | ||
| 193 | if layout.y.unwrap() > 36.0 || (layout.y == Some(36.0) && layout.x.unwrap() > 144.0) { | ||
| 194 | let (_, page) = after_document.pages().unwrap()[0]; | ||
| 195 | assert!(matches!(&after_view.nodes[&after_view.roots[&2]].kind, | ||
| 196 | Kind::Metadata { title: Some(title), .. } if title == "Fixed second")); | ||
| 197 | assert!(matches!(&after_view.nodes[&page].kind, | ||
| 198 | Kind::Page { alternate_title: Some(title), .. } if title == "Fixed second")); | ||
| 199 | } | ||
| 190 | if let Some(expected) = expected_layout { | 200 | if let Some(expected) = expected_layout { |
| 191 | assert_eq!( | 201 | assert_eq!( |
| 192 | serde_json::to_value(&after_view.nodes[outline].layout).unwrap(), | 202 | serde_json::to_value(&after_view.nodes[outline].layout).unwrap(), |
tools/native/outline-title.ps1 created+48| ... | @@ -0,0 +1,48 @@ | ||
| 1 | param([Parameter(Mandatory=$true)][string]$Root, [Parameter(Mandatory=$true)][string]$CloneHost) | ||
| 2 | Set-StrictMode -Version Latest | ||
| 3 | $ErrorActionPreference = 'Stop' | ||
| 4 | & "$PSScriptRoot\cold-current.ps1" -Root $Root -CloneHost $CloneHost | ||
| 5 | $app = New-Object -ComObject OneNote.Application | ||
| 6 | $notebook = '' | ||
| 7 | try { | ||
| 8 | $app.OpenHierarchy((Join-Path $Root 'notebook'), '', [ref]$notebook, 0) | ||
| 9 | $section = '' | ||
| 10 | $app.OpenHierarchy('TitleControl.one', $notebook, [ref]$section, 0) | ||
| 11 | $deadline = [DateTime]::UtcNow.AddSeconds(30) | ||
| 12 | do { | ||
| 13 | $hierarchy = '' | ||
| 14 | $app.GetHierarchy($section, 4, [ref]$hierarchy, 1) | ||
| 15 | [xml]$tree = $hierarchy | ||
| 16 | $pages = @($tree.SelectNodes('//*[local-name()="Page"]')) | ||
| 17 | if ($pages.Count -eq 3) { break } | ||
| 18 | Start-Sleep -Milliseconds 200 | ||
| 19 | } while ([DateTime]::UtcNow -lt $deadline) | ||
| 20 | if ($pages.Count -ne 3) { throw 'Expected three outline title controls.' } | ||
| 21 | $inputs = Join-Path $Root 'inputs' | ||
| 22 | [void](New-Item -ItemType Directory -Path $inputs) | ||
| 23 | foreach ($page in $pages) { | ||
| 24 | $name = $page.GetAttribute('name') | ||
| 25 | if ($name -notin @('Vertical first', 'Horizontal first', 'Explicit title')) { throw 'Unexpected control title.' } | ||
| 26 | $content = '' | ||
| 27 | $app.GetPageContent($page.GetAttribute('ID'), [ref]$content, 1, 1) | ||
| 28 | [xml]$xml = $content | ||
| 29 | $outlines = @($xml.SelectNodes('/*/*[local-name()="Outline"]') | Where-Object { $_.InnerText -match 'Second' }) | ||
| 30 | if ($outlines.Count -ne 1) { throw 'Expected one movable outline.' } | ||
| 31 | $outline = $outlines[0] | ||
| 32 | $position = $outline.SelectSingleNode('./*[local-name()="Position"]') | ||
| 33 | $position.SetAttribute('x', '36') | ||
| 34 | $position.SetAttribute('y', $(if ($name -eq 'Horizontal first') { '108' } else { '72' })) | ||
| 35 | foreach ($child in @($xml.DocumentElement.ChildNodes)) { | ||
| 36 | if ($child -ne $outline -and $child.LocalName -notin @('QuickStyleDef', 'TagDef')) { | ||
| 37 | [void]$xml.DocumentElement.RemoveChild($child) | ||
| 38 | } | ||
| 39 | } | ||
| 40 | [IO.File]::WriteAllText((Join-Path $inputs ($name + '.xml')), $xml.OuterXml, [Text.Encoding]::UTF8) | ||
| 41 | $app.UpdatePageContent($xml.OuterXml, [DateTime]::MinValue, 1, $false) | ||
| 42 | } | ||
| 43 | $app.SyncHierarchy($notebook) | ||
| 44 | } finally { | ||
| 45 | [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app) | ||
| 46 | [GC]::Collect() | ||
| 47 | [GC]::WaitForPendingFinalizers() | ||
| 48 | } | ||
tools/test_outline_edit.py+33| ... | @@ -19,6 +19,39 @@ compare = runpy.run_path(str(ROOT / 'tools/verify-document.py'))['compare'] | ... | @@ -19,6 +19,39 @@ compare = runpy.run_path(str(ROOT / 'tools/verify-document.py'))['compare'] |
| 19 | 19 | ||
| 20 | 20 | ||
| 21 | class OutlineEditTest(unittest.TestCase): | 21 | class OutlineEditTest(unittest.TestCase): |
| 22 | def test_automatic_titles_follow_native_and_rust_outline_movement(self): | ||
| 23 | fixture = FIXTURE / 'automatic-title' | ||
| 24 | models = {} | ||
| 25 | with TemporaryDirectory() as temporary: | ||
| 26 | for phase, notebook, capture in [ | ||
| 27 | ('before', fixture / 'before/notebook', fixture / 'before/read'), | ||
| 28 | ('native', fixture / 'native/notebook', fixture / 'native/read'), | ||
| 29 | ('native-cold', fixture / 'native/cold/notebook', fixture / 'native/cold/read'), | ||
| 30 | ('rust', fixture / 'candidate', fixture / 'cold/read'), | ||
| 31 | ('rust-cold', fixture / 'cold/notebook', fixture / 'cold/read'), | ||
| 32 | ]: | ||
| 33 | folder = Path(temporary) / phase | ||
| 34 | shutil.copytree(capture, folder / 'read') | ||
| 35 | compare(notebook, folder / 'read') | ||
| 36 | subprocess.run([EXPORTER, notebook / 'TitleControl.one', folder / 'model'], check=True) | ||
| 37 | document = json.loads((folder / 'model/document.json').read_text()) | ||
| 38 | models[phase] = list(ordered_pages(document)) | ||
| 39 | self.assertEqual(len(models[phase]), 3) | ||
| 40 | for phase, pages in models.items(): | ||
| 41 | expected = ['Vertical first', 'Horizontal first', 'Explicit title'] if phase == 'before' else ['Second 🦋 é', 'Second 🦋 é', 'Explicit title'] | ||
| 42 | self.assertEqual([r['nodes'][r['roots']['2']]['kind']['title'] for _, _, r, _ in pages], expected) | ||
| 43 | for before, after in [('native', 'native-cold'), ('rust', 'rust-cold')]: | ||
| 44 | for (sid, _, a, page), (saved_sid, _, b, saved_page) in zip(models[before], models[after], strict=True): | ||
| 45 | self.assertEqual((sid, page), (saved_sid, saved_page)) | ||
| 46 | self.assertEqual(dict(walk(a, page)), dict(walk(b, page))) | ||
| 47 | for (sid, _, a, page), (saved_sid, _, b, saved_page) in zip(models['before'], models['rust'], strict=True): | ||
| 48 | self.assertEqual((sid, page), (saved_sid, saved_page)) | ||
| 49 | for oid, node in walk(a, page): | ||
| 50 | self.assertEqual(node['children'], b['nodes'][oid]['children']) | ||
| 51 | self.assertEqual(node['content'], b['nodes'][oid]['content']) | ||
| 52 | if node['kind']['type'] == 'RichText': | ||
| 53 | self.assertEqual(node, b['nodes'][oid]) | ||
| 54 | |||
| 22 | def test_rust_subtree_moves_and_deletions_survive_cold_native_reopen(self): | 55 | def test_rust_subtree_moves_and_deletions_survive_cold_native_reopen(self): |
| 23 | for name, pages in [ | 56 | for name, pages in [ |
| 24 | ('rust-tree/ordinary', 15), ('rust-tree/groups-cells', 12), | 57 | ('rust-tree/ordinary', 15), ('rust-tree/groups-cells', 12), |