authorgravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-08 20:30:37-07:00
committergravatar for git@paperclover.netclover caruso <git@paperclover.net> 2026-09-08 20:30:37-07:00
log50cc916d18c0cf6e5bf920d93cbfc83a8d827dec
tree3030968bc6c42fe70ea6f23f2192387798c78882
parentcc0cc614db453797dd5ba20e68e57618cb9c323f
signature Signed by SSH key SHA256:52mNGHRsVFBDED9IAX5pe+LRWUefqTbxEReunq21QvU

fix: refresh automatic titles when moving outlines

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-astra

30 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
3Three native pages distinguish vertical ordering, horizontal ordering and an
4explicit title. Moving the second outline ahead of the first changes the two
5automatic titles to `Second 🦋 é`; the explicit title remains unchanged.
6
7`before` is the native source. `native` records the corresponding COM moves and
8an independent cold reopen. `candidate` is the Rust edit; `cold` is its independent
9OneNote reopen. Cold captures preserve exact active graphs. Rust movement retains
10all text identities, content, formatting and history. Identical artifacts link
11to one copy; `provenance.json` records the native runs and clone teardown.
12
13The Rust regression originally left both automatic-title fields unchanged.
14Position edits now update the existing page and metadata caches within the same
15revision as the coordinates. The offline regression additionally changes the
16remote title-producing text, loses the publication reply and reopens the cache
17before confirmation and a dependent edit.
18
19Run `tools.test_outline_edit.OutlineEditTest.test_automatic_titles_follow_native_and_rust_outline_movement`
20through Python unittest after building the workspace examples with all features.
21The Rust test `native_automatic_and_explicit_titles_follow_outline_movement` can
22export a new candidate directory through `ONESTORE_OUTLINE_TITLE_OUTPUT`.
23
24To regenerate the native controls, copy `fixture.json` and the synthetic section
25from `corpus/native-delete/deletion-05/before` into a new input notebook directory.
26Capture 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
29cold-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}
110110
111#[test]
112fn 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]
112fn competing_layout_requires_current_review_and_preserves_dependent_edits() {187fn 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};
8use serde::{Deserialize, Serialize};8use 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)]
14pub enum OutlineEdit {14pub 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<_> = document61 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}
273273
274#[test]
275fn 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]
345fn 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]
275fn outline_publication_interruptions_reopen_as_complete_old_or_new_layout() {415fn 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 layout556 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});
3840
...@@ -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 @@
1param([Parameter(Mandatory=$true)][string]$Root, [Parameter(Mandatory=$true)][string]$CloneHost)
2Set-StrictMode -Version Latest
3$ErrorActionPreference = 'Stop'
4& "$PSScriptRoot\cold-current.ps1" -Root $Root -CloneHost $CloneHost
5$app = New-Object -ComObject OneNote.Application
6$notebook = ''
7try {
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']
1919
2020
21class OutlineEditTest(unittest.TestCase):21class 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),