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()
108108 }
109109}
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
111186#[test]
112187fn competing_layout_requires_current_review_and_preserves_dependent_edits() {
113188 let (source, sid, outline, _, text_id) = fixture();
crates/onestore/src/outline.rs+12-5
......@@ -2,7 +2,7 @@ use crate::{
22 Error, ExGuid, RevisionIndex, Store,
33 create::current_timestamps,
44 document::{Document, Kind},
5 edit::editable_parents,
5 edit::{editable_parents, update_title},
66 write::{PropertyObject, write_revision},
77};
88use serde::{Deserialize, Serialize};
......@@ -13,6 +13,7 @@ use std::collections::{BTreeMap, BTreeSet};
1313#[serde(deny_unknown_fields)]
1414pub enum OutlineEdit {
1515 /// Moves an ordinary page outline to coordinates measured in points.
16 /// Refreshes automatic page titles when the leading outline changes.
1617 Position { x: f32, y: f32 },
1718 /// Changes an ordinary outline's maximum width in points, at least 36.
1819 /// `user_set` distinguishes an explicit width from an automatic layout hint.
......@@ -56,7 +57,7 @@ impl OutlineEdit {
5657 let store = Store::parse(source)?;
5758 let index = RevisionIndex::parse(&store)?;
5859 index.validate_current()?;
59 let document = Document::parse(&index)?;
60 let mut document = Document::parse(&index)?;
6061 let pages: Vec<_> = document
6162 .pages()?
6263 .into_iter()
......@@ -65,9 +66,12 @@ impl OutlineEdit {
6566 let [page] = pages.as_slice() else {
6667 return Err(invalid("Outline editing requires a single active page"));
6768 };
68 let semantic = &document.spaces[&space];
69 let view = &semantic.revisions[&semantic.contexts[&ExGuid::default()]];
70 let parents = editable_parents(view, &pages, object)?;
69 let mut semantic = document.spaces.remove(&space).unwrap();
70 let view = semantic
71 .revisions
72 .remove(&semantic.contexts[&ExGuid::default()])
73 .unwrap();
74 let parents = editable_parents(&view, &pages, object)?;
7175 let node = &view.nodes[&object];
7276 match self {
7377 Self::Collapsed(_) => {
......@@ -128,6 +132,9 @@ impl OutlineEdit {
128132 changed.insert(id, ancestor);
129133 }
130134 }
135 if matches!(self, Self::Position { .. }) {
136 update_title(&store, raw, view, &pages, &mut changed)?;
137 }
131138 Ok(changed)
132139 })
133140 }
crates/onestore/tests/outline.rs+163
......@@ -271,6 +271,146 @@ fn resizing_a_native_reserved_width_preserves_content() {
271271 }
272272}
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
274414#[test]
275415fn outline_publication_interruptions_reopen_as_complete_old_or_new_layout() {
276416 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() {
286426 .find(|id| matches!(view.nodes[id].kind, Kind::Outline { .. }))
287427 .unwrap();
288428 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();
289434 for (object, operation) in [
290435 (outline, Edit::Position { x: 216.0, y: 72.0 }),
291436 (
......@@ -344,6 +489,11 @@ fn repeated_geometry_changes_and_expansion_match_an_independent_model() {
344489 .unwrap();
345490 let paragraph = view.nodes[&outline].children[0];
346491 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();
347497 for seed in 1..=16_u64 {
348498 let mut rng = seed;
349499 let mut source = original.clone();
......@@ -388,6 +538,19 @@ fn repeated_geometry_changes_and_expansion_match_an_independent_model() {
388538 let document = Document::parse(&index).unwrap();
389539 let space = &document.spaces[&sid];
390540 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 );
391554 assert_eq!(
392555 serde_json::to_value(&current.nodes[&outline].layout).unwrap(),
393556 layout
fuzz/fuzz_targets/paragraph.rs+10
......@@ -33,6 +33,8 @@ static SOURCE: LazyLock<(Vec<u8>, ExGuid, ExGuid)> = LazyLock::new(|| {
3333 .with_formatting(4..7, &[A::Italic(true), A::Color(Some([12, 34, 56]))])
3434 .unwrap();
3535 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();
3638 (edited.as_bytes().to_vec(), sid, outline)
3739});
3840
......@@ -187,6 +189,14 @@ fuzz_target!(|input: &[u8]| {
187189 let after_document = Document::parse(&after_index).unwrap();
188190 let space = &after_document.spaces[sid];
189191 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 }
190200 if let Some(expected) = expected_layout {
191201 assert_eq!(
192202 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']
1919
2020
2121class 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
2255 def test_rust_subtree_moves_and_deletions_survive_cold_native_reopen(self):
2356 for name, pages in [
2457 ('rust-tree/ordinary', 15), ('rust-tree/groups-cells', 12),