diff --git a/corpus/outline-edit/README.md b/corpus/outline-edit/README.md
index 303d26d450ddfd45c810d2209f9f9950073c7086..68a8009af727b6d82fcafe497980eb103a1d4db1 100644
--- a/corpus/outline-edit/README.md
+++ b/corpus/outline-edit/README.md
@@ -76,3 +76,28 @@ with `ONESTORE_OFFLINE_OUTLINE_OUTPUT` set to a new absolute directory, then use
the same cold runner arguments. The test also retains a JSON record next to that
directory. Cache migration, uncertain revision retention, stale review, and twelve
offline writers are exercised in `crates/onestore-offline/tests`.
+
+`tree` extends the native controls with eleven cases involving outline groups,
+indentation gaps, numbered/bulleted subtrees, and table cells. The stored graphs,
+identities, tags, fields, and formatting survive a fresh-cache reopen. Deleting
+a group's last subtree removes the empty group. Removing the last unindented
+sibling unwraps the preceding group and transfers its indentation to the outline.
+Deleting a cell's only paragraph creates new empty paragraph/text identities;
+the cell and table remain intact. Outdenting across a two-level gap reduces the
+gap and reparents a following sibling beneath the outdented target, preserving
+that sibling's indentation. Native bullet indentation additionally substitutes
+its list marker; it is distinct from moving a subtree with unchanged formatting.
+
+These keyboard controls retain rich-text identities. Eight edits also establish
+a 423.75-point reserved wrapping width on the selected outline in the captured
+800×600 desktop. The test permits that specific property separately from
+timestamp/bookkeeping changes and verifies all other retained object fields.
+Before/after/cold native comparisons cover twelve pages, eleven/six/six tags,
+and 8,321/7,081/7,081 explicit character-format values.
+
+Regenerate using the same source notebook and runner, with
+`--author corpus/outline-edit/tree/scripts/author.ps1 --expected-pages 12
+--inspect --collect-notebook`. Run the shared `tools/native_outline_editing.py`
+controller at inspection readiness, move `before-read` to `before/read`, then
+cold-open the returned notebook with `--expected-pages 12 --collect-notebook`.
+The exact authoring script has one home in the reference corpus.
diff --git a/corpus/outline-edit/tree/actions/delete-cell-subtree.ahk b/corpus/outline-edit/tree/actions/delete-cell-subtree.ahk
new file mode 100644
index 0000000000000000000000000000000000000000..1722d9a45f625337f5af0ee0b4733e2fc32d6550
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/delete-cell-subtree.ahk
@@ -0,0 +1,15 @@
+OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1)))
+DetectHiddenWindows false
+app := ComObject("OneNote.Application")
+app.NavigateTo("{2E5F7E51-CCD6-45A6-8CF6-D073792509DB}{1}{B0}", "{B06FB066-49DD-4B3D-828B-E66AA11D889E}{46}{B0}", false)
+hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10)
+if !hwnd
+ throw Error("OneNote window did not appear")
+WinMaximize(hwnd)
+WinActivate(hwnd)
+if !WinWaitActive(hwnd,, 10)
+ throw Error("OneNote window did not become active")
+app.NavigateTo("{2E5F7E51-CCD6-45A6-8CF6-D073792509DB}{1}{B0}", "{B06FB066-49DD-4B3D-828B-E66AA11D889E}{46}{B0}", false)
+Sleep 300
+Send "{Left}^+{-}{Delete}"
+Sleep 300
diff --git a/corpus/outline-edit/tree/actions/delete-cell-subtree.json b/corpus/outline-edit/tree/actions/delete-cell-subtree.json
new file mode 100644
index 0000000000000000000000000000000000000000..f3ecc2514cfb245e8d524aba87942ac24e90c62e
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/delete-cell-subtree.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Delete cell subtree - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/actions/delete-cell-subtree.png b/corpus/outline-edit/tree/actions/delete-cell-subtree.png
new file mode 100644
index 0000000000000000000000000000000000000000..a7a241394f9e7b72fe7f9c80bf875373861367d7
Binary files /dev/null and b/corpus/outline-edit/tree/actions/delete-cell-subtree.png differ
diff --git a/corpus/outline-edit/tree/actions/delete-numbered-subtree.ahk b/corpus/outline-edit/tree/actions/delete-numbered-subtree.ahk
new file mode 100644
index 0000000000000000000000000000000000000000..25b1d9d3afe722fb8fdc6e569ba76aadd0619355
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/delete-numbered-subtree.ahk
@@ -0,0 +1,15 @@
+OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1)))
+DetectHiddenWindows false
+app := ComObject("OneNote.Application")
+app.NavigateTo("{9E821A55-182D-4F8E-B4E5-D1AF397CE319}{1}{B0}", "{A02053C2-3268-46C4-9BE5-0DF68966C6FD}{46}{B0}", false)
+hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10)
+if !hwnd
+ throw Error("OneNote window did not appear")
+WinMaximize(hwnd)
+WinActivate(hwnd)
+if !WinWaitActive(hwnd,, 10)
+ throw Error("OneNote window did not become active")
+app.NavigateTo("{9E821A55-182D-4F8E-B4E5-D1AF397CE319}{1}{B0}", "{A02053C2-3268-46C4-9BE5-0DF68966C6FD}{46}{B0}", false)
+Sleep 300
+Send "{Left}^+{-}{Delete}"
+Sleep 300
diff --git a/corpus/outline-edit/tree/actions/delete-numbered-subtree.json b/corpus/outline-edit/tree/actions/delete-numbered-subtree.json
new file mode 100644
index 0000000000000000000000000000000000000000..2ffaf3c40700297201f6a2e70966546ef83aa74a
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/delete-numbered-subtree.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Delete numbered subtree - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/actions/delete-numbered-subtree.png b/corpus/outline-edit/tree/actions/delete-numbered-subtree.png
new file mode 100644
index 0000000000000000000000000000000000000000..a79d3298824c6f66f4015c13af4f2c166a2f8779
Binary files /dev/null and b/corpus/outline-edit/tree/actions/delete-numbered-subtree.png differ
diff --git a/corpus/outline-edit/tree/actions/delete-only-grouped-subtree.ahk b/corpus/outline-edit/tree/actions/delete-only-grouped-subtree.ahk
new file mode 100644
index 0000000000000000000000000000000000000000..aab0c629dbb89a59e86836893e47d978283e6ac5
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/delete-only-grouped-subtree.ahk
@@ -0,0 +1,15 @@
+OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1)))
+DetectHiddenWindows false
+app := ComObject("OneNote.Application")
+app.NavigateTo("{CF318236-2DF3-48B2-8865-2AD5B5623F59}{1}{B0}", "{D02D46C5-FC21-45B6-BE8C-53D8012F37AD}{40}{B0}", false)
+hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10)
+if !hwnd
+ throw Error("OneNote window did not appear")
+WinMaximize(hwnd)
+WinActivate(hwnd)
+if !WinWaitActive(hwnd,, 10)
+ throw Error("OneNote window did not become active")
+app.NavigateTo("{CF318236-2DF3-48B2-8865-2AD5B5623F59}{1}{B0}", "{D02D46C5-FC21-45B6-BE8C-53D8012F37AD}{40}{B0}", false)
+Sleep 300
+Send "{Left}^+{-}{Delete}"
+Sleep 300
diff --git a/corpus/outline-edit/tree/actions/delete-only-grouped-subtree.json b/corpus/outline-edit/tree/actions/delete-only-grouped-subtree.json
new file mode 100644
index 0000000000000000000000000000000000000000..abdeeb7be827c4e39776d94894a72022ff4c2520
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/delete-only-grouped-subtree.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Delete only grouped subtree - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/actions/delete-only-grouped-subtree.png b/corpus/outline-edit/tree/actions/delete-only-grouped-subtree.png
new file mode 100644
index 0000000000000000000000000000000000000000..91d4dedfca372ba29880e502e239f57ce4d63051
Binary files /dev/null and b/corpus/outline-edit/tree/actions/delete-only-grouped-subtree.png differ
diff --git a/corpus/outline-edit/tree/actions/delete-sole-cell-paragraph.ahk b/corpus/outline-edit/tree/actions/delete-sole-cell-paragraph.ahk
new file mode 100644
index 0000000000000000000000000000000000000000..c6e515f2354480dcbe0f6e4531e49e50c6611980
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/delete-sole-cell-paragraph.ahk
@@ -0,0 +1,15 @@
+OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1)))
+DetectHiddenWindows false
+app := ComObject("OneNote.Application")
+app.NavigateTo("{A9AF5DEC-514F-45E5-B57F-5F7CDF446F72}{1}{B0}", "{42D17B21-AB2F-49CB-9115-14A2BF0D6727}{30}{B0}", false)
+hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10)
+if !hwnd
+ throw Error("OneNote window did not appear")
+WinMaximize(hwnd)
+WinActivate(hwnd)
+if !WinWaitActive(hwnd,, 10)
+ throw Error("OneNote window did not become active")
+app.NavigateTo("{A9AF5DEC-514F-45E5-B57F-5F7CDF446F72}{1}{B0}", "{42D17B21-AB2F-49CB-9115-14A2BF0D6727}{30}{B0}", false)
+Sleep 300
+Send "{Left}^+{-}{Delete}"
+Sleep 300
diff --git a/corpus/outline-edit/tree/actions/delete-sole-cell-paragraph.json b/corpus/outline-edit/tree/actions/delete-sole-cell-paragraph.json
new file mode 100644
index 0000000000000000000000000000000000000000..d2b59e91271a6fdcb0b16e590b860b1146332688
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/delete-sole-cell-paragraph.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Delete sole cell paragraph - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/actions/delete-sole-cell-paragraph.png b/corpus/outline-edit/tree/actions/delete-sole-cell-paragraph.png
new file mode 100644
index 0000000000000000000000000000000000000000..5c726dc7429343db69e9d598d34d6fa0c792211a
Binary files /dev/null and b/corpus/outline-edit/tree/actions/delete-sole-cell-paragraph.png differ
diff --git a/corpus/outline-edit/tree/actions/delete-unindented-sibling-after-group.ahk b/corpus/outline-edit/tree/actions/delete-unindented-sibling-after-group.ahk
new file mode 100644
index 0000000000000000000000000000000000000000..e665ffa6f0c2cf571f5a6130e8f6708061e3d29f
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/delete-unindented-sibling-after-group.ahk
@@ -0,0 +1,15 @@
+OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1)))
+DetectHiddenWindows false
+app := ComObject("OneNote.Application")
+app.NavigateTo("{8848F261-93E9-4A78-8FDB-9304636C2572}{1}{B0}", "{F91A5591-BDC7-4F4E-A177-6C923E93C02D}{45}{B0}", false)
+hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10)
+if !hwnd
+ throw Error("OneNote window did not appear")
+WinMaximize(hwnd)
+WinActivate(hwnd)
+if !WinWaitActive(hwnd,, 10)
+ throw Error("OneNote window did not become active")
+app.NavigateTo("{8848F261-93E9-4A78-8FDB-9304636C2572}{1}{B0}", "{F91A5591-BDC7-4F4E-A177-6C923E93C02D}{45}{B0}", false)
+Sleep 300
+Send "{Left}^+{-}{Delete}"
+Sleep 300
diff --git a/corpus/outline-edit/tree/actions/delete-unindented-sibling-after-group.json b/corpus/outline-edit/tree/actions/delete-unindented-sibling-after-group.json
new file mode 100644
index 0000000000000000000000000000000000000000..90e7234ce62512f97b991feb9d641fdf94862264
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/delete-unindented-sibling-after-group.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Delete unindented sibling after group - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/actions/delete-unindented-sibling-after-group.png b/corpus/outline-edit/tree/actions/delete-unindented-sibling-after-group.png
new file mode 100644
index 0000000000000000000000000000000000000000..94410b27ee9d449033d73f7e50cf959170afcc5f
Binary files /dev/null and b/corpus/outline-edit/tree/actions/delete-unindented-sibling-after-group.png differ
diff --git a/corpus/outline-edit/tree/actions/indent-bullet-subtree.ahk b/corpus/outline-edit/tree/actions/indent-bullet-subtree.ahk
new file mode 100644
index 0000000000000000000000000000000000000000..f8d7e6a087a606901dea5d6bf8e49988eb8e2660
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/indent-bullet-subtree.ahk
@@ -0,0 +1,15 @@
+OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1)))
+DetectHiddenWindows false
+app := ComObject("OneNote.Application")
+app.NavigateTo("{F42F95BB-4229-49D9-9535-6B3742F22B14}{1}{B0}", "{CD04E749-A732-41FB-8966-A7D752A34819}{46}{B0}", false)
+hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10)
+if !hwnd
+ throw Error("OneNote window did not appear")
+WinMaximize(hwnd)
+WinActivate(hwnd)
+if !WinWaitActive(hwnd,, 10)
+ throw Error("OneNote window did not become active")
+app.NavigateTo("{F42F95BB-4229-49D9-9535-6B3742F22B14}{1}{B0}", "{CD04E749-A732-41FB-8966-A7D752A34819}{46}{B0}", false)
+Sleep 300
+Send "{Left}^+{-}!+{Right}"
+Sleep 300
diff --git a/corpus/outline-edit/tree/actions/indent-bullet-subtree.json b/corpus/outline-edit/tree/actions/indent-bullet-subtree.json
new file mode 100644
index 0000000000000000000000000000000000000000..666dae945adaeb59d3d5c742c7ba85778929706b
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/indent-bullet-subtree.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Indent bullet subtree - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/actions/indent-bullet-subtree.png b/corpus/outline-edit/tree/actions/indent-bullet-subtree.png
new file mode 100644
index 0000000000000000000000000000000000000000..400a2ec907bca75d525a96446616c8dd6d36b036
Binary files /dev/null and b/corpus/outline-edit/tree/actions/indent-bullet-subtree.png differ
diff --git a/corpus/outline-edit/tree/actions/indent-first-subtree.ahk b/corpus/outline-edit/tree/actions/indent-first-subtree.ahk
new file mode 100644
index 0000000000000000000000000000000000000000..003388c91ae7e7227d651e01a29f4702ee55fe55
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/indent-first-subtree.ahk
@@ -0,0 +1,15 @@
+OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1)))
+DetectHiddenWindows false
+app := ComObject("OneNote.Application")
+app.NavigateTo("{B98DD413-992C-48FE-BAC1-2C734ED5CE69}{1}{B0}", "{76D86304-5E43-49D0-98EC-69FFF5011C8F}{40}{B0}", false)
+hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10)
+if !hwnd
+ throw Error("OneNote window did not appear")
+WinMaximize(hwnd)
+WinActivate(hwnd)
+if !WinWaitActive(hwnd,, 10)
+ throw Error("OneNote window did not become active")
+app.NavigateTo("{B98DD413-992C-48FE-BAC1-2C734ED5CE69}{1}{B0}", "{76D86304-5E43-49D0-98EC-69FFF5011C8F}{40}{B0}", false)
+Sleep 300
+Send "{Left}^+{-}!+{Right}"
+Sleep 300
diff --git a/corpus/outline-edit/tree/actions/indent-first-subtree.json b/corpus/outline-edit/tree/actions/indent-first-subtree.json
new file mode 100644
index 0000000000000000000000000000000000000000..ab78af6f58f0491d903cbc8f945f3c70dbe06606
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/indent-first-subtree.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Indent first subtree - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/actions/indent-first-subtree.png b/corpus/outline-edit/tree/actions/indent-first-subtree.png
new file mode 100644
index 0000000000000000000000000000000000000000..d900cb658152b73ce140155472be2b2e5143299b
Binary files /dev/null and b/corpus/outline-edit/tree/actions/indent-first-subtree.png differ
diff --git a/corpus/outline-edit/tree/actions/move-cell-subtree-down.ahk b/corpus/outline-edit/tree/actions/move-cell-subtree-down.ahk
new file mode 100644
index 0000000000000000000000000000000000000000..de65f2892db6befd12d981a0e791d14b4c17f804
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/move-cell-subtree-down.ahk
@@ -0,0 +1,15 @@
+OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1)))
+DetectHiddenWindows false
+app := ComObject("OneNote.Application")
+app.NavigateTo("{D8F75D3B-62F9-41BC-8C92-2C3257BEB470}{1}{B0}", "{B3C20949-3D36-4334-A2B1-24DB43118FCF}{46}{B0}", false)
+hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10)
+if !hwnd
+ throw Error("OneNote window did not appear")
+WinMaximize(hwnd)
+WinActivate(hwnd)
+if !WinWaitActive(hwnd,, 10)
+ throw Error("OneNote window did not become active")
+app.NavigateTo("{D8F75D3B-62F9-41BC-8C92-2C3257BEB470}{1}{B0}", "{B3C20949-3D36-4334-A2B1-24DB43118FCF}{46}{B0}", false)
+Sleep 300
+Send "{Left}^+{-}!+{Down}"
+Sleep 300
diff --git a/corpus/outline-edit/tree/actions/move-cell-subtree-down.json b/corpus/outline-edit/tree/actions/move-cell-subtree-down.json
new file mode 100644
index 0000000000000000000000000000000000000000..c2e498f910f1c5412d881cfd7157bb6e0f1e816a
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/move-cell-subtree-down.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Move cell subtree down - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/actions/move-cell-subtree-down.png b/corpus/outline-edit/tree/actions/move-cell-subtree-down.png
new file mode 100644
index 0000000000000000000000000000000000000000..91afef5babd03458361e95337f70385b88c7f90d
Binary files /dev/null and b/corpus/outline-edit/tree/actions/move-cell-subtree-down.png differ
diff --git a/corpus/outline-edit/tree/actions/move-numbered-subtree-down.ahk b/corpus/outline-edit/tree/actions/move-numbered-subtree-down.ahk
new file mode 100644
index 0000000000000000000000000000000000000000..279d47cbb8f06de4ca35fa1675fdeb90c762115d
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/move-numbered-subtree-down.ahk
@@ -0,0 +1,15 @@
+OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1)))
+DetectHiddenWindows false
+app := ComObject("OneNote.Application")
+app.NavigateTo("{1FD29A18-8CDC-4762-997D-7007C95E11C9}{1}{B0}", "{75E97E0B-8D9D-4301-BC12-B1CB7F821418}{46}{B0}", false)
+hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10)
+if !hwnd
+ throw Error("OneNote window did not appear")
+WinMaximize(hwnd)
+WinActivate(hwnd)
+if !WinWaitActive(hwnd,, 10)
+ throw Error("OneNote window did not become active")
+app.NavigateTo("{1FD29A18-8CDC-4762-997D-7007C95E11C9}{1}{B0}", "{75E97E0B-8D9D-4301-BC12-B1CB7F821418}{46}{B0}", false)
+Sleep 300
+Send "{Left}^+{-}!+{Down}"
+Sleep 300
diff --git a/corpus/outline-edit/tree/actions/move-numbered-subtree-down.json b/corpus/outline-edit/tree/actions/move-numbered-subtree-down.json
new file mode 100644
index 0000000000000000000000000000000000000000..48f1601e8396f2c2f32ff0f85f3517904d41697d
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/move-numbered-subtree-down.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Move numbered subtree down - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/actions/move-numbered-subtree-down.png b/corpus/outline-edit/tree/actions/move-numbered-subtree-down.png
new file mode 100644
index 0000000000000000000000000000000000000000..a2ce0163359fa866b9a81e3687f6634c67ca52b4
Binary files /dev/null and b/corpus/outline-edit/tree/actions/move-numbered-subtree-down.png differ
diff --git a/corpus/outline-edit/tree/actions/outdent-child-across-indentation-gap.ahk b/corpus/outline-edit/tree/actions/outdent-child-across-indentation-gap.ahk
new file mode 100644
index 0000000000000000000000000000000000000000..2594379686457ffc464f8d4a6d0c31212e079db1
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/outdent-child-across-indentation-gap.ahk
@@ -0,0 +1,15 @@
+OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1)))
+DetectHiddenWindows false
+app := ComObject("OneNote.Application")
+app.NavigateTo("{0021D1AD-E6E0-4DFE-8292-B0D7F560ECC3}{1}{B0}", "{2511F1B2-87DC-4B09-BFBF-2AA175972EFD}{45}{B0}", false)
+hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10)
+if !hwnd
+ throw Error("OneNote window did not appear")
+WinMaximize(hwnd)
+WinActivate(hwnd)
+if !WinWaitActive(hwnd,, 10)
+ throw Error("OneNote window did not become active")
+app.NavigateTo("{0021D1AD-E6E0-4DFE-8292-B0D7F560ECC3}{1}{B0}", "{2511F1B2-87DC-4B09-BFBF-2AA175972EFD}{45}{B0}", false)
+Sleep 300
+Send "{Left}^+{-}!+{Left}"
+Sleep 300
diff --git a/corpus/outline-edit/tree/actions/outdent-child-across-indentation-gap.json b/corpus/outline-edit/tree/actions/outdent-child-across-indentation-gap.json
new file mode 100644
index 0000000000000000000000000000000000000000..e1b4f3ee73c42f979096373945b97a684a35bf71
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/outdent-child-across-indentation-gap.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Outdent child across indentation gap - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/actions/outdent-child-across-indentation-gap.png b/corpus/outline-edit/tree/actions/outdent-child-across-indentation-gap.png
new file mode 100644
index 0000000000000000000000000000000000000000..cf8d6e757ece691751f5e7506cc57a8362bc7e0d
Binary files /dev/null and b/corpus/outline-edit/tree/actions/outdent-child-across-indentation-gap.png differ
diff --git a/corpus/outline-edit/tree/actions/outdent-first-group.ahk b/corpus/outline-edit/tree/actions/outdent-first-group.ahk
new file mode 100644
index 0000000000000000000000000000000000000000..5e4ddf92420663e70fa25a67634b7fd7980dd2ee
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/outdent-first-group.ahk
@@ -0,0 +1,15 @@
+OnError((exception, mode) => (FileAppend(exception.Message, "**"), ExitApp(1)))
+DetectHiddenWindows false
+app := ComObject("OneNote.Application")
+app.NavigateTo("{F8757FD3-BDA0-4187-B841-80EDF46DFEE9}{1}{B0}", "{6302145E-3541-466F-8370-48B83B7E2E53}{40}{B0}", false)
+hwnd := WinWait("ahk_class Framework::CFrame ahk_exe ONENOTE.EXE",, 10)
+if !hwnd
+ throw Error("OneNote window did not appear")
+WinMaximize(hwnd)
+WinActivate(hwnd)
+if !WinWaitActive(hwnd,, 10)
+ throw Error("OneNote window did not become active")
+app.NavigateTo("{F8757FD3-BDA0-4187-B841-80EDF46DFEE9}{1}{B0}", "{6302145E-3541-466F-8370-48B83B7E2E53}{40}{B0}", false)
+Sleep 300
+Send "{Left}^+{-}!+{Left}"
+Sleep 300
diff --git a/corpus/outline-edit/tree/actions/outdent-first-group.json b/corpus/outline-edit/tree/actions/outdent-first-group.json
new file mode 100644
index 0000000000000000000000000000000000000000..d7fa37ae07c534e75ae6f914ffaaa7176742cf1f
--- /dev/null
+++ b/corpus/outline-edit/tree/actions/outdent-first-group.json
@@ -0,0 +1,13 @@
+{
+ "exit": 0,
+ "stdout": "",
+ "stderr": "",
+ "w": 800,
+ "h": 600,
+ "error": null,
+ "win": {
+ "title": "Outdent first group - Microsoft OneNote",
+ "class": "Framework::CFrame",
+ "dialog": false
+ }
+}
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/actions/outdent-first-group.png b/corpus/outline-edit/tree/actions/outdent-first-group.png
new file mode 100644
index 0000000000000000000000000000000000000000..88ff1a85828cab57af971af1c16aa16389a63bf6
Binary files /dev/null and b/corpus/outline-edit/tree/actions/outdent-first-group.png differ
diff --git a/corpus/outline-edit/tree/after/notebook/Open Notebook.onetoc2 b/corpus/outline-edit/tree/after/notebook/Open Notebook.onetoc2
new file mode 100644
index 0000000000000000000000000000000000000000..67eb7864f0b0e8dc364cb9405119a46a74c53ad1
Binary files /dev/null and b/corpus/outline-edit/tree/after/notebook/Open Notebook.onetoc2 differ
diff --git a/corpus/outline-edit/tree/after/notebook/synthetic.one b/corpus/outline-edit/tree/after/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..f7308786826936180fee177aa4b28b16701dfb22
Binary files /dev/null and b/corpus/outline-edit/tree/after/notebook/synthetic.one differ
diff --git a/corpus/outline-edit/tree/after/read/environment.json b/corpus/outline-edit/tree/after/read/environment.json
new file mode 100644
index 0000000000000000000000000000000000000000..042f4bdf9c76fcea4a0fb08389d9c1143263b63c
--- /dev/null
+++ b/corpus/outline-edit/tree/after/read/environment.json
@@ -0,0 +1,7 @@
+{
+ "powershell": "5.1.14409.1005",
+ "schema": "xs2010",
+ "hostname": "ONE-M6-ADD5412B",
+ "cold": false,
+ "onenote": "14.0.4763.1000"
+}
diff --git a/corpus/outline-edit/tree/after/read/hierarchy.xml b/corpus/outline-edit/tree/after/read/hierarchy.xml
new file mode 100644
index 0000000000000000000000000000000000000000..78c48cd04dad372532a036f29780f5ac60b78dfe
--- /dev/null
+++ b/corpus/outline-edit/tree/after/read/hierarchy.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/outline-edit/tree/after/read/page-000.xml b/corpus/outline-edit/tree/after/read/page-000.xml
new file mode 100644
index 0000000000000000000000000000000000000000..25a3f054462e4e21079a2d49d030eba50f2387c0
--- /dev/null
+++ b/corpus/outline-edit/tree/after/read/page-000.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/after/read/page-001.xml b/corpus/outline-edit/tree/after/read/page-001.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7cd70d073c6cb32184c1286b1ba4f746c5a40ac7
--- /dev/null
+++ b/corpus/outline-edit/tree/after/read/page-001.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/after/read/page-002.xml b/corpus/outline-edit/tree/after/read/page-002.xml
new file mode 100644
index 0000000000000000000000000000000000000000..cd851088fa9c8cb094789a7860ed4211024dcee4
--- /dev/null
+++ b/corpus/outline-edit/tree/after/read/page-002.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/outline-edit/tree/after/read/page-003.xml b/corpus/outline-edit/tree/after/read/page-003.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8f122cd89d7c3f1cfa88e501c5040a593a86c97b
--- /dev/null
+++ b/corpus/outline-edit/tree/after/read/page-003.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/outline-edit/tree/after/read/page-004.xml b/corpus/outline-edit/tree/after/read/page-004.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7924c3f525cd6d44b5a2c7ac27a2a0d0414aa21e
--- /dev/null
+++ b/corpus/outline-edit/tree/after/read/page-004.xml
@@ -0,0 +1,20 @@
+
+Bold 🦀 italic é color 東京
+End]]>Fictitious: café, 東京, مرحبا]]>
diff --git a/corpus/outline-edit/tree/after/read/page-005.xml b/corpus/outline-edit/tree/after/read/page-005.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5ff4e0299ea271d896fc2eb1a89979347fda4eae
--- /dev/null
+++ b/corpus/outline-edit/tree/after/read/page-005.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/outline-edit/tree/after/read/page-006.xml b/corpus/outline-edit/tree/after/read/page-006.xml
new file mode 100644
index 0000000000000000000000000000000000000000..45e8415090124344bdc8782fb33848c3cec2eada
--- /dev/null
+++ b/corpus/outline-edit/tree/after/read/page-006.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/outline-edit/tree/after/read/page-007.xml b/corpus/outline-edit/tree/after/read/page-007.xml
new file mode 100644
index 0000000000000000000000000000000000000000..30cdc72b177fdd1688e9b4f70058feccc84c4854
--- /dev/null
+++ b/corpus/outline-edit/tree/after/read/page-007.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/after/read/page-008.xml b/corpus/outline-edit/tree/after/read/page-008.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bae8d163b086b634a47d8d32ea50a33189d71e2e
--- /dev/null
+++ b/corpus/outline-edit/tree/after/read/page-008.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/outline-edit/tree/after/read/page-009.xml b/corpus/outline-edit/tree/after/read/page-009.xml
new file mode 100644
index 0000000000000000000000000000000000000000..dc88f80bb663067961017f5023b6e0c94228f044
--- /dev/null
+++ b/corpus/outline-edit/tree/after/read/page-009.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/after/read/page-010.xml b/corpus/outline-edit/tree/after/read/page-010.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ac346ba59d772cf99a9e2c2a0d0839b71f21e11b
--- /dev/null
+++ b/corpus/outline-edit/tree/after/read/page-010.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/after/read/page-011.xml b/corpus/outline-edit/tree/after/read/page-011.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6192874c95295724954ed60c77b7ae2b7c2260c2
--- /dev/null
+++ b/corpus/outline-edit/tree/after/read/page-011.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/after/read/payloads.json b/corpus/outline-edit/tree/after/read/payloads.json
new file mode 120000
index 0000000000000000000000000000000000000000..9e1be3e2b6d87478e7c48d278ecac8ee6c97f6d0
--- /dev/null
+++ b/corpus/outline-edit/tree/after/read/payloads.json
@@ -0,0 +1 @@
+../../../../paragraph-edit/before/read/payloads.json
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/before/notebook/Open Notebook.onetoc2 b/corpus/outline-edit/tree/before/notebook/Open Notebook.onetoc2
new file mode 120000
index 0000000000000000000000000000000000000000..8f6ad22bd00746edfcf9c9d301bfe750a50fb3a3
--- /dev/null
+++ b/corpus/outline-edit/tree/before/notebook/Open Notebook.onetoc2
@@ -0,0 +1 @@
+../../after/notebook/Open Notebook.onetoc2
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/before/notebook/synthetic.one b/corpus/outline-edit/tree/before/notebook/synthetic.one
new file mode 100644
index 0000000000000000000000000000000000000000..982157a6d205b5e5619da4b30d6dc3ec3dccf945
Binary files /dev/null and b/corpus/outline-edit/tree/before/notebook/synthetic.one differ
diff --git a/corpus/outline-edit/tree/before/read/environment.json b/corpus/outline-edit/tree/before/read/environment.json
new file mode 120000
index 0000000000000000000000000000000000000000..371d170656827612f03b625ef7c6b0ac98a799d7
--- /dev/null
+++ b/corpus/outline-edit/tree/before/read/environment.json
@@ -0,0 +1 @@
+../../after/read/environment.json
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/before/read/hierarchy.xml b/corpus/outline-edit/tree/before/read/hierarchy.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5ef1e95965999cd4786782d89df187429e2cab82
--- /dev/null
+++ b/corpus/outline-edit/tree/before/read/hierarchy.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/outline-edit/tree/before/read/page-000.xml b/corpus/outline-edit/tree/before/read/page-000.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8448438769dfc4354a5d61d723e25eb58a9c9dd1
--- /dev/null
+++ b/corpus/outline-edit/tree/before/read/page-000.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/before/read/page-001.xml b/corpus/outline-edit/tree/before/read/page-001.xml
new file mode 100644
index 0000000000000000000000000000000000000000..70c607fe574c2524b19b327893a40bd22397fcd4
--- /dev/null
+++ b/corpus/outline-edit/tree/before/read/page-001.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/before/read/page-002.xml b/corpus/outline-edit/tree/before/read/page-002.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e840042547e8cd246fcd4b069a5b7abf07321d70
--- /dev/null
+++ b/corpus/outline-edit/tree/before/read/page-002.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/before/read/page-003.xml b/corpus/outline-edit/tree/before/read/page-003.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b6a32c651e550298bceb71dca62183984cf3217a
--- /dev/null
+++ b/corpus/outline-edit/tree/before/read/page-003.xml
@@ -0,0 +1,4 @@
+
+Target 🦀 é Link label]]>
diff --git a/corpus/outline-edit/tree/before/read/page-004.xml b/corpus/outline-edit/tree/before/read/page-004.xml
new file mode 100644
index 0000000000000000000000000000000000000000..21015e12435a93b8af248621fb29c9a320e01cca
--- /dev/null
+++ b/corpus/outline-edit/tree/before/read/page-004.xml
@@ -0,0 +1,20 @@
+
+Bold 🦀 italic é color 東京
+End]]>Fictitious: café, 東京, مرحبا]]>
diff --git a/corpus/outline-edit/tree/before/read/page-005.xml b/corpus/outline-edit/tree/before/read/page-005.xml
new file mode 100644
index 0000000000000000000000000000000000000000..30e0780ee7aada01d5bec8d073798ea16dfc1ab1
--- /dev/null
+++ b/corpus/outline-edit/tree/before/read/page-005.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/before/read/page-006.xml b/corpus/outline-edit/tree/before/read/page-006.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1eb80b3acf8742cfd137eff1d5dcb1371b61df77
--- /dev/null
+++ b/corpus/outline-edit/tree/before/read/page-006.xml
@@ -0,0 +1,4 @@
+
+Target 🦀 é Link label]]>
diff --git a/corpus/outline-edit/tree/before/read/page-007.xml b/corpus/outline-edit/tree/before/read/page-007.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ad9d89a8cb3bcf2de2bb25fffd2f20041aa88982
--- /dev/null
+++ b/corpus/outline-edit/tree/before/read/page-007.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/before/read/page-008.xml b/corpus/outline-edit/tree/before/read/page-008.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0d15b275b4d4873a2389aa874928106e7240db37
--- /dev/null
+++ b/corpus/outline-edit/tree/before/read/page-008.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/before/read/page-009.xml b/corpus/outline-edit/tree/before/read/page-009.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7d79ab8fde904e75eae3d9795fcc43060c68fbf3
--- /dev/null
+++ b/corpus/outline-edit/tree/before/read/page-009.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/before/read/page-010.xml b/corpus/outline-edit/tree/before/read/page-010.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1ac0ab267096814cf137f3bbbb7b66b992be1f55
--- /dev/null
+++ b/corpus/outline-edit/tree/before/read/page-010.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/before/read/page-011.xml b/corpus/outline-edit/tree/before/read/page-011.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4f1ca9ed404800e759103ac5c7eec4fef4094f43
--- /dev/null
+++ b/corpus/outline-edit/tree/before/read/page-011.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/before/read/payloads.json b/corpus/outline-edit/tree/before/read/payloads.json
new file mode 120000
index 0000000000000000000000000000000000000000..9e1be3e2b6d87478e7c48d278ecac8ee6c97f6d0
--- /dev/null
+++ b/corpus/outline-edit/tree/before/read/payloads.json
@@ -0,0 +1 @@
+../../../../paragraph-edit/before/read/payloads.json
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/cases.json b/corpus/outline-edit/tree/cases.json
new file mode 100644
index 0000000000000000000000000000000000000000..8108d6ac14891a02f788d2c8c8b1af74f017767c
--- /dev/null
+++ b/corpus/outline-edit/tree/cases.json
@@ -0,0 +1,57 @@
+[
+ {
+ "keys": "{Left}^+{-}!+{Right}",
+ "name": "Indent first subtree",
+ "shape": "first"
+ },
+ {
+ "keys": "{Left}^+{-}!+{Left}",
+ "name": "Outdent first group",
+ "shape": "group"
+ },
+ {
+ "keys": "{Left}^+{-}{Delete}",
+ "name": "Delete only grouped subtree",
+ "shape": "group"
+ },
+ {
+ "keys": "{Left}^+{-}{Delete}",
+ "name": "Delete unindented sibling after group",
+ "shape": "tail"
+ },
+ {
+ "keys": "{Left}^+{-}{Delete}",
+ "name": "Delete cell subtree",
+ "shape": "cell"
+ },
+ {
+ "keys": "{Left}^+{-}{Delete}",
+ "name": "Delete sole cell paragraph",
+ "shape": "cell-only"
+ },
+ {
+ "keys": "{Left}^+{-}!+{Down}",
+ "name": "Move numbered subtree down",
+ "shape": "number"
+ },
+ {
+ "keys": "{Left}^+{-}{Delete}",
+ "name": "Delete numbered subtree",
+ "shape": "number"
+ },
+ {
+ "keys": "{Left}^+{-}!+{Right}",
+ "name": "Indent bullet subtree",
+ "shape": "bullet"
+ },
+ {
+ "keys": "{Left}^+{-}!+{Left}",
+ "name": "Outdent child across indentation gap",
+ "shape": "gap"
+ },
+ {
+ "keys": "{Left}^+{-}!+{Down}",
+ "name": "Move cell subtree down",
+ "shape": "cell"
+ }
+]
diff --git a/corpus/outline-edit/tree/cold/commands.jsonl b/corpus/outline-edit/tree/cold/commands.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..228a8869f9764cbaed14b3b1a70fbb738826a8b0
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/commands.jsonl
@@ -0,0 +1,3 @@
+{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-C0658B70 -ExpectedPages 12", "exit": 0, "stdout": "Read 1 sections and 12 pages.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
diff --git a/corpus/outline-edit/tree/cold/notebook/Open Notebook.onetoc2 b/corpus/outline-edit/tree/cold/notebook/Open Notebook.onetoc2
new file mode 120000
index 0000000000000000000000000000000000000000..8f6ad22bd00746edfcf9c9d301bfe750a50fb3a3
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/notebook/Open Notebook.onetoc2
@@ -0,0 +1 @@
+../../after/notebook/Open Notebook.onetoc2
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/cold/notebook/synthetic.one b/corpus/outline-edit/tree/cold/notebook/synthetic.one
new file mode 120000
index 0000000000000000000000000000000000000000..72fc135c56c45b43d59f89646c2fdeb67bb4a58a
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/notebook/synthetic.one
@@ -0,0 +1 @@
+../../after/notebook/synthetic.one
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/cold/read/environment.json b/corpus/outline-edit/tree/cold/read/environment.json
new file mode 100644
index 0000000000000000000000000000000000000000..bdac5affb9c2ec078fd09a09a05b178ea38b9034
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/read/environment.json
@@ -0,0 +1,7 @@
+{
+ "powershell": "5.1.14409.1005",
+ "schema": "xs2010",
+ "hostname": "ONE-M6-C0658B70",
+ "cold": true,
+ "onenote": "14.0.4763.1000"
+}
diff --git a/corpus/outline-edit/tree/cold/read/hierarchy.xml b/corpus/outline-edit/tree/cold/read/hierarchy.xml
new file mode 100644
index 0000000000000000000000000000000000000000..041154cce9abac826f23a0ae8c280b8891eb28f8
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/read/hierarchy.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/outline-edit/tree/cold/read/page-000.xml b/corpus/outline-edit/tree/cold/read/page-000.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7b6b4a6516c10c9397f1ec8e90c132c784dd6ab2
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/read/page-000.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/outline-edit/tree/cold/read/page-001.xml b/corpus/outline-edit/tree/cold/read/page-001.xml
new file mode 100644
index 0000000000000000000000000000000000000000..033624640eb4a3038b610ea2ebd57007ff2b89a0
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/read/page-001.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/cold/read/page-002.xml b/corpus/outline-edit/tree/cold/read/page-002.xml
new file mode 100644
index 0000000000000000000000000000000000000000..519019e521f0f6014f4ae79ef500e6958ee13781
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/read/page-002.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/cold/read/page-003.xml b/corpus/outline-edit/tree/cold/read/page-003.xml
new file mode 100644
index 0000000000000000000000000000000000000000..b1dc8159ce0cb1bdadac0b511ca703eec9cb0499
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/read/page-003.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/cold/read/page-004.xml b/corpus/outline-edit/tree/cold/read/page-004.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0f78067b6983d2fe4d1d10319b2e211ab2fc2e29
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/read/page-004.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/outline-edit/tree/cold/read/page-005.xml b/corpus/outline-edit/tree/cold/read/page-005.xml
new file mode 100644
index 0000000000000000000000000000000000000000..fd1c17366cefeb03a6d790378b7d3ef9d0b4451f
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/read/page-005.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/outline-edit/tree/cold/read/page-006.xml b/corpus/outline-edit/tree/cold/read/page-006.xml
new file mode 100644
index 0000000000000000000000000000000000000000..fe3998c0e44825d7f78dbab096e9bf69a549ab98
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/read/page-006.xml
@@ -0,0 +1,20 @@
+
+Bold 🦀 italic é color 東京
+End]]>Fictitious: café, 東京, مرحبا]]>
diff --git a/corpus/outline-edit/tree/cold/read/page-007.xml b/corpus/outline-edit/tree/cold/read/page-007.xml
new file mode 100644
index 0000000000000000000000000000000000000000..3ca19bb72b5e582079a080e39ccca53ff2241892
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/read/page-007.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/outline-edit/tree/cold/read/page-008.xml b/corpus/outline-edit/tree/cold/read/page-008.xml
new file mode 100644
index 0000000000000000000000000000000000000000..caa79f2e4756e31148e10a960453fb7e4e765b95
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/read/page-008.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/cold/read/page-009.xml b/corpus/outline-edit/tree/cold/read/page-009.xml
new file mode 100644
index 0000000000000000000000000000000000000000..ed76566bef05c4d13f5678c3e12b7d459f5665be
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/read/page-009.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/cold/read/page-010.xml b/corpus/outline-edit/tree/cold/read/page-010.xml
new file mode 100644
index 0000000000000000000000000000000000000000..023f405e81388371239ee72d0f78388bb7c81b39
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/read/page-010.xml
@@ -0,0 +1,5 @@
+
+Target 🦀 é Link label]]>Child]]>
diff --git a/corpus/outline-edit/tree/cold/read/page-011.xml b/corpus/outline-edit/tree/cold/read/page-011.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4ebee9cc7f62e07c9e9e7893aeac2d9d31b8fc52
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/read/page-011.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/corpus/outline-edit/tree/cold/read/payloads.json b/corpus/outline-edit/tree/cold/read/payloads.json
new file mode 120000
index 0000000000000000000000000000000000000000..9e1be3e2b6d87478e7c48d278ecac8ee6c97f6d0
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/read/payloads.json
@@ -0,0 +1 @@
+../../../../paragraph-edit/before/read/payloads.json
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/cold/run.json b/corpus/outline-edit/tree/cold/run.json
new file mode 100644
index 0000000000000000000000000000000000000000..626bdba775945230043533785b667322a7f7f38a
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/run.json
@@ -0,0 +1,18 @@
+{
+ "notebook": "/Users/clo/dev/one/evidence/m10/tree-native-02/notebook",
+ "expected_pages": 12,
+ "author": null,
+ "author_timeout_seconds": 600,
+ "inspect": false,
+ "collect_notebook": true,
+ "base": {
+ "file": "win7-office-base.qcow2",
+ "format": "qcow2",
+ "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346",
+ "virtual_size": 68719476736
+ },
+ "scripts": {
+ "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331",
+ "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41"
+ }
+}
diff --git a/corpus/outline-edit/tree/cold/scripts/cold.ps1 b/corpus/outline-edit/tree/cold/scripts/cold.ps1
new file mode 120000
index 0000000000000000000000000000000000000000..5bc2f1837e1f0b639923a80b79c517c98c5c519a
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/scripts/cold.ps1
@@ -0,0 +1 @@
+../../../../paragraph-edit/reconciliation/keyboard/scripts/cold.ps1
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/cold/scripts/read.ps1 b/corpus/outline-edit/tree/cold/scripts/read.ps1
new file mode 120000
index 0000000000000000000000000000000000000000..85257c76cede599f47da88e69419a767dd6b99ee
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/scripts/read.ps1
@@ -0,0 +1 @@
+../../../../paragraph-edit/reconciliation/keyboard/scripts/read.ps1
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/cold/source.json b/corpus/outline-edit/tree/cold/source.json
new file mode 100644
index 0000000000000000000000000000000000000000..6b7e4176fc52076d21db3be438808183fc89a99a
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/source.json
@@ -0,0 +1,14 @@
+[
+ {
+ "path": "Open Notebook.onetoc2",
+ "bytes": 4800,
+ "sha256": "ff08c4a0c4fb231713206a367c747aaff67a07f81685a102e078563932c72322",
+ "mtime_ns": 1788895717692200807
+ },
+ {
+ "path": "synthetic.one",
+ "bytes": 111008,
+ "sha256": "2495e64a42e5f1df382633b4f7fdd0c8c73432fad8dff9e7386c6e7b23542451",
+ "mtime_ns": 1788895717692413263
+ }
+]
diff --git a/corpus/outline-edit/tree/cold/teardown.json b/corpus/outline-edit/tree/cold/teardown.json
new file mode 120000
index 0000000000000000000000000000000000000000..2bdb767b98875dfd402ac931c624ab7ec15dbcb0
--- /dev/null
+++ b/corpus/outline-edit/tree/cold/teardown.json
@@ -0,0 +1 @@
+../../../paragraph-edit/joined/teardown.json
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/commands.jsonl b/corpus/outline-edit/tree/commands.jsonl
new file mode 100644
index 0000000000000000000000000000000000000000..739f7b51329872afc4d4d933281fa6388a340ef9
--- /dev/null
+++ b/corpus/outline-edit/tree/commands.jsonl
@@ -0,0 +1,7 @@
+{"command": "powershell -NoProfile -Command \"Expand-Archive -LiteralPath C:\\one-tests\\transfer.zip -DestinationPath C:\\one-tests\\runs\\capture\\notebook\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\author.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-ADD5412B", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-ADD5412B -ExpectedPages 12 -UseCurrentCache -KeepOpen", "exit": 0, "stdout": "Read 1 sections and 12 pages.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Rename-Item C:\\one-tests\\runs\\capture\\read before-read\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -NonInteractive -ExecutionPolicy Bypass -File C:\\one-tests\\read-current.ps1 -Root C:\\one-tests\\runs\\capture -CloneHost ONE-M6-ADD5412B -UseCurrentCache -ExpectedPages -1", "exit": 0, "stdout": "Read 1 sections and 12 pages.\r\n", "stderr": "", "error": null}
+{"command": "powershell -NoProfile -Command \"Compress-Archive -Force -Path C:\\one-tests\\runs\\capture\\* -DestinationPath C:\\one-tests\\captured.zip\"", "exit": 0, "stdout": "", "stderr": "", "error": null}
diff --git a/corpus/outline-edit/tree/inputs/Delete cell subtree.xml b/corpus/outline-edit/tree/inputs/Delete cell subtree.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6127aa6679cbbf94d7371faada83ec838f0a3749
--- /dev/null
+++ b/corpus/outline-edit/tree/inputs/Delete cell subtree.xml
@@ -0,0 +1 @@
+Delete cell subtreeTarget 🦀 é Link label]]>Child]]>GrandchildCell tailOther cellOther outline
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/inputs/Delete numbered subtree.xml b/corpus/outline-edit/tree/inputs/Delete numbered subtree.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d748989761a7a2b52d6dfc8bda9b9c2222f7eeb0
--- /dev/null
+++ b/corpus/outline-edit/tree/inputs/Delete numbered subtree.xml
@@ -0,0 +1 @@
+Delete numbered subtreeAnchorTarget 🦀 é Link label]]>Child]]>GrandchildTrailing siblingOther outline
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/inputs/Delete only grouped subtree.xml b/corpus/outline-edit/tree/inputs/Delete only grouped subtree.xml
new file mode 100644
index 0000000000000000000000000000000000000000..30a282fc5a28543d431571bc5c2696530e3609ba
--- /dev/null
+++ b/corpus/outline-edit/tree/inputs/Delete only grouped subtree.xml
@@ -0,0 +1 @@
+Delete only grouped subtreeTarget 🦀 é Link label]]>Child]]>GrandchildTrailing siblingOther outline
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/inputs/Delete sole cell paragraph.xml b/corpus/outline-edit/tree/inputs/Delete sole cell paragraph.xml
new file mode 100644
index 0000000000000000000000000000000000000000..123d986d26e36d320d6b90af1760e9eb18f26f87
--- /dev/null
+++ b/corpus/outline-edit/tree/inputs/Delete sole cell paragraph.xml
@@ -0,0 +1 @@
+Delete sole cell paragraphTarget 🦀 é Link label]]>Other cellOther outline
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/inputs/Delete unindented sibling after group.xml b/corpus/outline-edit/tree/inputs/Delete unindented sibling after group.xml
new file mode 100644
index 0000000000000000000000000000000000000000..7467239ffee9f0ccc0fbea51d0189c0fd2364ef4
--- /dev/null
+++ b/corpus/outline-edit/tree/inputs/Delete unindented sibling after group.xml
@@ -0,0 +1 @@
+Delete unindented sibling after groupAnchorTarget 🦀 é Link label]]>Other outline
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/inputs/Indent bullet subtree.xml b/corpus/outline-edit/tree/inputs/Indent bullet subtree.xml
new file mode 100644
index 0000000000000000000000000000000000000000..1020d7bea5c15c39687098c3780da92c82ebc8e5
--- /dev/null
+++ b/corpus/outline-edit/tree/inputs/Indent bullet subtree.xml
@@ -0,0 +1 @@
+Indent bullet subtreeAnchorTarget 🦀 é Link label]]>Child]]>GrandchildTrailing siblingOther outline
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/inputs/Indent first subtree.xml b/corpus/outline-edit/tree/inputs/Indent first subtree.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9aac86fbb114501d1c7a44f802c91306252f3abd
--- /dev/null
+++ b/corpus/outline-edit/tree/inputs/Indent first subtree.xml
@@ -0,0 +1 @@
+Indent first subtreeTarget 🦀 é Link label]]>Child]]>GrandchildTrailing siblingOther outline
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/inputs/Move cell subtree down.xml b/corpus/outline-edit/tree/inputs/Move cell subtree down.xml
new file mode 100644
index 0000000000000000000000000000000000000000..8052f1ed8c4b1f2467a4a379eecaff03e82c3db0
--- /dev/null
+++ b/corpus/outline-edit/tree/inputs/Move cell subtree down.xml
@@ -0,0 +1 @@
+Move cell subtree downTarget 🦀 é Link label]]>Child]]>GrandchildCell tailOther cellOther outline
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/inputs/Move numbered subtree down.xml b/corpus/outline-edit/tree/inputs/Move numbered subtree down.xml
new file mode 100644
index 0000000000000000000000000000000000000000..5cd95f480acc59c52eba10a0557e5254432f8431
--- /dev/null
+++ b/corpus/outline-edit/tree/inputs/Move numbered subtree down.xml
@@ -0,0 +1 @@
+Move numbered subtree downAnchorTarget 🦀 é Link label]]>Child]]>GrandchildTrailing siblingOther outline
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/inputs/Outdent child across indentation gap.xml b/corpus/outline-edit/tree/inputs/Outdent child across indentation gap.xml
new file mode 100644
index 0000000000000000000000000000000000000000..6804020a0c3af791f4940db8ec9c6892153678ca
--- /dev/null
+++ b/corpus/outline-edit/tree/inputs/Outdent child across indentation gap.xml
@@ -0,0 +1 @@
+Outdent child across indentation gapAnchorTarget 🦀 é Link label]]>Child]]>GrandchildOther childTrailing siblingOther outline
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/inputs/Outdent first group.xml b/corpus/outline-edit/tree/inputs/Outdent first group.xml
new file mode 100644
index 0000000000000000000000000000000000000000..9c9fde4293d8c9d65c367017a10df01e5da3a02e
--- /dev/null
+++ b/corpus/outline-edit/tree/inputs/Outdent first group.xml
@@ -0,0 +1 @@
+Outdent first groupTarget 🦀 é Link label]]>Child]]>GrandchildTrailing siblingOther outline
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/run.json b/corpus/outline-edit/tree/run.json
new file mode 100644
index 0000000000000000000000000000000000000000..1bdc6d184ce0ca6d70867ad8c027c83806b4c7cf
--- /dev/null
+++ b/corpus/outline-edit/tree/run.json
@@ -0,0 +1,19 @@
+{
+ "notebook": "/Users/clo/dev/one/corpus/formatted-insertion/native-paragraph/candidate",
+ "expected_pages": 12,
+ "author": "evidence/m10/tree-reference.ps1",
+ "author_timeout_seconds": 600,
+ "inspect": true,
+ "collect_notebook": true,
+ "base": {
+ "file": "win7-office-base.qcow2",
+ "format": "qcow2",
+ "sha256": "a1a4f8fab782ee14885ff801ca2f6347c208fdcfc3513637096f314315c89346",
+ "virtual_size": 68719476736
+ },
+ "scripts": {
+ "author.ps1": "d00d7d6b83207e16e3ebdbd12f841dba1f9092713057c5e2783a6f00bf58f369",
+ "cold.ps1": "c177fc72ae6c2634d186f5671a880de20b09f9716532c37c69cb13aa15c7e331",
+ "read.ps1": "04013bfcccee40a17e2a225f9b9e96f40a3a8daad9e350609654f8eda3ccbb41"
+ }
+}
diff --git a/corpus/outline-edit/tree/scripts/author.ps1 b/corpus/outline-edit/tree/scripts/author.ps1
new file mode 100644
index 0000000000000000000000000000000000000000..21b6e3004e9c8e65dff5c32fe17786eae582206e
--- /dev/null
+++ b/corpus/outline-edit/tree/scripts/author.ps1
@@ -0,0 +1,58 @@
+param([Parameter(Mandatory=$true)][string]$Root, [Parameter(Mandatory=$true)][string]$CloneHost)
+Set-StrictMode -Version Latest
+$ErrorActionPreference = 'Stop'
+& "$PSScriptRoot\cold-current.ps1" -Root $Root -CloneHost $CloneHost
+$app = New-Object -ComObject OneNote.Application
+$notebook = ''
+try {
+ $app.OpenHierarchy((Join-Path $Root 'notebook'), '', [ref]$notebook, 0)
+ $section = ''
+ $app.OpenHierarchy('synthetic.one', $notebook, [ref]$section, 0)
+ $cases = @(
+ @{ name='Indent first subtree'; shape='first'; keys='{Left}^+{-}!+{Right}' },
+ @{ name='Outdent first group'; shape='group'; keys='{Left}^+{-}!+{Left}' },
+ @{ name='Delete only grouped subtree'; shape='group'; keys='{Left}^+{-}{Delete}' },
+ @{ name='Delete unindented sibling after group'; shape='tail'; keys='{Left}^+{-}{Delete}' },
+ @{ name='Delete cell subtree'; shape='cell'; keys='{Left}^+{-}{Delete}' },
+ @{ name='Delete sole cell paragraph'; shape='cell-only'; keys='{Left}^+{-}{Delete}' },
+ @{ name='Move numbered subtree down'; shape='number'; keys='{Left}^+{-}!+{Down}' },
+ @{ name='Delete numbered subtree'; shape='number'; keys='{Left}^+{-}{Delete}' },
+ @{ name='Indent bullet subtree'; shape='bullet'; keys='{Left}^+{-}!+{Right}' },
+ @{ name='Outdent child across indentation gap'; shape='gap'; keys='{Left}^+{-}!+{Left}' },
+ @{ name='Move cell subtree down'; shape='cell'; keys='{Left}^+{-}!+{Down}' }
+ )
+ $inputs = Join-Path $Root 'inputs'
+ [void](New-Item -ItemType Directory -Path $inputs)
+ foreach ($case in $cases) {
+ $page = ''
+ $app.CreateNewPage($section, [ref]$page, 0)
+ $label = 'Target ' + [char]::ConvertFromUtf32(0x1F980) + ' e' + [char]0x0301
+ $list = if ($case.shape -eq 'number') { '' } elseif ($case.shape -eq 'bullet') { '' } else { '' }
+ $child = if ($case.shape -in @('cell-only','tail')) { '' } else { 'Child]]>Grandchild' }
+ $target = '' + $list + '' + $label + ' Link label]]>' + $child + ''
+ $anchor = '' + $list + 'Anchor'
+ $trailing = '' + $list + 'Trailing sibling'
+ $body = switch ($case.shape) {
+ 'first' { '' + $target + $trailing + '' }
+ 'group' { '' + $target + '' + $trailing + '' }
+ 'tail' { '' + $anchor + '' + $target + '' }
+ 'gap' { 'Anchor' + $target + 'Other child' + $trailing + '' }
+ { $_ -in @('cell','cell-only') } {
+ $tail = if ($case.shape -eq 'cell-only') { '' } else { 'Cell tail' }
+ '' + $target + $tail + 'Other cell'
+ }
+ default { '' + $anchor + $target + $trailing + '' }
+ }
+ $xml = '' + $case.name + '' + $body + 'Other outline'
+ [IO.File]::WriteAllText((Join-Path $inputs ($case.name + '.xml')), $xml, [Text.Encoding]::UTF8)
+ $app.UpdatePageContent($xml, [DateTime]::MinValue, 1, $false)
+ }
+ $cases | ConvertTo-Json -Depth 6 | Set-Content (Join-Path $Root 'cases.json') -Encoding UTF8
+ $app.SyncHierarchy($notebook)
+} finally {
+ if ($notebook) { $app.CloseNotebook($notebook, $false) }
+ [void][Runtime.InteropServices.Marshal]::FinalReleaseComObject($app)
+ $app = $null
+ [GC]::Collect()
+ [GC]::WaitForPendingFinalizers()
+}
diff --git a/corpus/outline-edit/tree/scripts/cold.ps1 b/corpus/outline-edit/tree/scripts/cold.ps1
new file mode 120000
index 0000000000000000000000000000000000000000..2d413d263ed086517287ac23dc6715f760396d03
--- /dev/null
+++ b/corpus/outline-edit/tree/scripts/cold.ps1
@@ -0,0 +1 @@
+../../../paragraph-edit/reconciliation/keyboard/scripts/cold.ps1
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/scripts/read.ps1 b/corpus/outline-edit/tree/scripts/read.ps1
new file mode 120000
index 0000000000000000000000000000000000000000..732e6bac15e039405c9d8cf16d0c283ffb554545
--- /dev/null
+++ b/corpus/outline-edit/tree/scripts/read.ps1
@@ -0,0 +1 @@
+../../../paragraph-edit/reconciliation/keyboard/scripts/read.ps1
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/source.json b/corpus/outline-edit/tree/source.json
new file mode 120000
index 0000000000000000000000000000000000000000..ed7697d5f3755b356c04ede775add8ff1634313f
--- /dev/null
+++ b/corpus/outline-edit/tree/source.json
@@ -0,0 +1 @@
+../../paragraph-edit/split/source.json
\ No newline at end of file
diff --git a/corpus/outline-edit/tree/teardown.json b/corpus/outline-edit/tree/teardown.json
new file mode 120000
index 0000000000000000000000000000000000000000..2ff619474163b7ccced424b117d1e9371ef5be0e
--- /dev/null
+++ b/corpus/outline-edit/tree/teardown.json
@@ -0,0 +1 @@
+../../paragraph-edit/joined/teardown.json
\ No newline at end of file
diff --git a/tools/native_outline_editing.py b/tools/native_outline_editing.py
index e3a38c9cf4ed300926e0b2e465df2804137174f7..c17bcea5324185f29edfe7d7af4f9bd84fc5a4a3 100644
--- a/tools/native_outline_editing.py
+++ b/tools/native_outline_editing.py
@@ -24,7 +24,8 @@ def apply(output):
page = pages[case['name']]
outline = page.find('one:Outline', ns)
paragraph, = [node for node in outline.findall('.//one:OE', ns)
- if ''.join(Text(node.find('one:T', ns).text or '').parts).startswith('Target ')]
+ if node.find('one:T', ns) is not None
+ and ''.join(Text(node.find('one:T', ns).text or '').parts).startswith('Target ')]
page_id = page.get('ID')
object_id = (outline if case.get('delete') == 'outline' else paragraph).get('objectID')
stem = actions / case['name'].lower().replace(' ', '-')
diff --git a/tools/test_outline_edit.py b/tools/test_outline_edit.py
index a3be969412702df7e382223278dbd7ef531011f3..6cfb508c200184ed6d9f84cd1542ae3f009ab5ed 100644
--- a/tools/test_outline_edit.py
+++ b/tools/test_outline_edit.py
@@ -1,7 +1,9 @@
+import copy
import json
from pathlib import Path
import runpy
import shutil
+import struct
import subprocess
from tempfile import TemporaryDirectory
import unittest
@@ -17,6 +19,122 @@ compare = runpy.run_path(str(ROOT / 'tools/verify-document.py'))['compare']
class OutlineEditTest(unittest.TestCase):
+ def test_native_group_list_and_cell_tree_controls(self):
+ fixture = FIXTURE / 'tree'
+ models = {}
+ with TemporaryDirectory() as temporary:
+ for phase in ('before', 'after', 'cold'):
+ folder = Path(temporary) / phase
+ shutil.copytree(fixture / phase / 'read', folder / 'read')
+ compare(fixture / phase / 'notebook', folder / 'read')
+ subprocess.run([EXPORTER, fixture / phase / 'notebook/synthetic.one', folder / 'model'], check=True)
+ model = json.loads((folder / 'model/document.json').read_text())
+ models[phase] = {r['nodes'][r['roots']['2']]['kind']['title']: (r, page)
+ for _, _, r, page in ordered_pages(model)}
+ self.assertEqual(len(models[phase]), 12)
+ cases = json.loads((fixture / 'cases.json').read_text(encoding='utf-8-sig'))
+ self.assertEqual(len(cases), 11)
+ for name, (saved, page) in models['after'].items():
+ cold, cold_page = models['cold'][name]
+ self.assertEqual(page, cold_page)
+ self.assertEqual(dict(walk(saved, page)), dict(walk(cold, cold_page)))
+ for _, node in walk(saved, page):
+ for oid in node['kind'].get('lists', []):
+ self.assertEqual(saved['nodes'][oid], cold['nodes'][oid])
+ for case in cases:
+ name = case['name']
+ with self.subTest(case=name):
+ old, page = models['before'][name]
+ new, new_page = models['after'][name]
+ self.assertEqual(page, new_page)
+ self.assertEqual(old['nodes'][page]['children'], new['nodes'][page]['children'])
+ outline, other = [oid for oid in old['nodes'][page]['children']
+ if old['nodes'][oid]['kind']['type'] == 'Outline']
+ original = dict(walk(old, outline))
+ expected = copy.deepcopy(original)
+ actual = dict(walk(new, outline))
+ paragraphs = {old['nodes'][n['content'][0]]['kind']['text']: oid
+ for oid, n in original.items() if n['kind']['type'] == 'Paragraph'
+ and n['content'] and old['nodes'][n['content'][0]]['kind']['type'] == 'RichText'}
+ target, = [oid for text, oid in paragraphs.items() if text.startswith('Target ')]
+ parents = {child: oid for oid, node in original.items() for child in node['children']}
+ parent = parents[target]
+ if name.startswith('Delete'):
+ for oid, _ in walk(old, target):
+ del expected[oid]
+ expected[parent]['children'].remove(target)
+ if name == 'Delete only grouped subtree':
+ del expected[parent]
+ expected[outline]['children'].remove(parent)
+ elif name == 'Delete unindented sibling after group':
+ group, = expected[outline]['children']
+ expected[outline]['children'] = expected[group]['children']
+ expected[outline]['child_level'] = 2
+ del expected[group]
+ elif name == 'Delete sole cell paragraph':
+ replacement, = actual[parent]['children']
+ self.assertNotIn(replacement, original)
+ blank = actual[replacement]
+ self.assertEqual(blank['kind'], {'type': 'Paragraph', 'lists': [],
+ 'paragraph_style': None, 'collapse_state': None})
+ self.assertEqual((blank['children'], blank['tags'], blank['child_level']), ([], [], 1))
+ text, = blank['content']
+ self.assertNotIn(text, original)
+ self.assertEqual(actual[text]['kind'], {'type': 'RichText', 'text': '',
+ 'runs': [{'start': 0, 'end': 0, 'format': None, 'extra_set': None}],
+ 'paragraph_style': None, 'boilerplate': False})
+ expected[parent]['children'] = [replacement]
+ expected.update({replacement: blank, text: actual[text]})
+ elif name in ('Move numbered subtree down', 'Move cell subtree down'):
+ children = expected[parent]['children']
+ index = children.index(target)
+ children[index:index + 2] = reversed(children[index:index + 2])
+ if name == 'Move numbered subtree down':
+ expected[paragraphs['Anchor']]['child_level'] = 2
+ elif name == 'Indent first subtree':
+ group, = actual.keys() - original.keys()
+ self.assertEqual(actual[group]['kind'], {'type': 'OutlineGroup'})
+ self.assertEqual(actual[group]['children'], [target])
+ self.assertEqual((actual[group]['child_level'], actual[group]['content'],
+ actual[group]['structure'], actual[group]['tags']), (1, [], [], []))
+ expected[group] = actual[group]
+ expected[outline]['children'][0] = group
+ elif name == 'Outdent first group':
+ del expected[parent]
+ expected[outline]['children'][0] = target
+ elif name == 'Indent bullet subtree':
+ expected[outline]['children'].remove(target)
+ expected[paragraphs['Anchor']]['children'] = [target]
+ before_list, = expected[target]['kind']['lists']
+ after_list, = actual[target]['kind']['lists']
+ self.assertNotEqual(before_list, after_list)
+ self.assertEqual(old['nodes'][before_list]['kind'], {'type': 'List', 'font': 'Courier New',
+ 'format': '○', 'restart': None, 'bullet': 4})
+ self.assertEqual(new['nodes'][after_list]['kind'], {'type': 'List', 'font': 'Calibri',
+ 'format': '•', 'restart': None, 'bullet': 1})
+ expected[target]['kind']['lists'] = [after_list]
+ elif name == 'Outdent child across indentation gap':
+ self.assertEqual(expected[parent]['child_level'], 2)
+ expected[parent]['child_level'] = 1
+ expected[parent]['children'].remove(paragraphs['Other child'])
+ expected[target]['children'].append(paragraphs['Other child'])
+ else:
+ self.fail(name)
+ if case['shape'] not in ('cell', 'cell-only'):
+ reservation = actual[outline]['extra'][0][-1]
+ self.assertEqual(reservation['id'], 0x14001cdb)
+ width, = struct.unpack('