From 970fdd7440162d30295c9e624ff2d295da7869f5 Mon Sep 17 00:00:00 2001 From: clover caruso Date: Sun, 11 Oct 2026 17:47:37 -0700 Subject: [PATCH] fix(ios): offer only valid directional page moves Keep expanded page moves scoped to one page, matching the OneNote movement corpus. Omit Move Down when the first page would leave a subpage leading the section. Resolve directional actions against the current section and stable page ID, and return the actual arrangement acceptance result. Fixes: #134 Assisted-by: gpt-6.1-sol --- apps/ios/Snowbound/Arranging.swift | 34 ++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/apps/ios/Snowbound/Arranging.swift b/apps/ios/Snowbound/Arranging.swift index a55f601d9f1b29ea976a53d4ec7deb2843e6966b..123ad7ae4bd9bae9d1c4760b594c849431722c8c 100644 --- a/apps/ios/Snowbound/Arranging.swift +++ b/apps/ios/Snowbound/Arranging.swift @@ -91,14 +91,16 @@ enum Arranging { } /// Puts the pages of `section` in `order`, page `id` the one moved, as one edit. - static func arrange(_ section: Section, _ order: [Placed], moving id: String) { - guard available() else { return } + @discardableResult + static func arrange(_ section: Section, _ order: [Placed], moving id: String) -> Bool { + guard available() else { return false } let before = section.rows.map { Placed(id: $0.id, level: $0.level) } - guard order != before, section.arrange(order, moving: id) else { return } + guard order != before, section.arrange(order, moving: id) else { return false } let (notebook, path) = (section.notebook, section.tab.path) done("Move Page") { opening(path, of: notebook) { arrange($0, before, moving: id) } } + return true } /// Gives page `id` of `section` `level` where it stands, as Make Subpage and Promote @@ -393,10 +395,13 @@ extension PagesViewController: UITableViewDragDelegate, UITableViewDropDelegate } /// Page `id` moved to `index` of the section's pages. - private func move(_ id: String, to index: Int) { - guard let section else { return } - Arranging.arrange(section, Arranging.order(section.rows, moving: id, to: index), moving: id) + @discardableResult + private func move(_ id: String, to index: Int) -> Bool { + guard let section else { return false } + let order = Arranging.order(section.rows, moving: id, to: index) + guard order.first?.level == 1, Arranging.arrange(section, order, moving: id) else { return false } becomeFirstResponder() + return true } /// Page `id` a level deeper or shallower, where it stays. @@ -449,15 +454,26 @@ extension PagesViewController: UITableViewDragDelegate, UITableViewDropDelegate /// VoiceOver's ways to move a page without dragging it. func arrangeActions(_ item: Item) -> [UIAccessibilityCustomAction]? { - guard !Arranging.busy, item.version == nil, let rows = section?.rows, let at = rows.firstIndex(where: { $0.id == item.row.id }) + guard !Arranging.busy, item.version == nil, let section, + let at = section.rows.firstIndex(where: { $0.id == item.row.id }) else { return nil } + let rows = section.rows let id = item.row.id var actions: [UIAccessibilityCustomAction] = [] + for (name, direction) in [("Move Up", -1), ("Move Down", 1)] { + guard rows.indices.contains(at + direction), + at > 0 || direction < 0 || rows[1].level == 1 + else { continue } + actions.append(UIAccessibilityCustomAction(name: name) { [weak self] _ in + guard let self, self.section === section, + let at = section.rows.firstIndex(where: { $0.id == id }), section.rows.indices.contains(at + direction) + else { return false } + return move(id, to: at + direction) + }) + } let add = { (name: String, act: @escaping () -> Void) in actions.append(UIAccessibilityCustomAction(name: name) { _ in act(); return true }) } - if at > 0 { add("Move Up") { [weak self] in self?.move(id, to: at - 1) } } - if at < rows.count - 1 { add("Move Down") { [weak self] in self?.move(id, to: at + 1) } } if indentable(rows, at) { add("Make Subpage") { [weak self] in self?.indent(id, by: 1) } } if rows[at].level > 1 { add("Promote Subpage") { [weak self] in self?.indent(id, by: -1) } } if !moveActions(item).isEmpty { -- 2.54.0