Skip to content

Separate traversal from representation with generic fold - #343

Merged
RoyalPineapple merged 1 commit into
cashapp:mainfrom
TheButtonHeist:RoyalPineapple/hierarchy-fold
Jun 23, 2026
Merged

RoyalPineapple merged 1 commit into
cashapp:mainfrom
TheButtonHeist:RoyalPineapple/hierarchy-fold

Conversation

@RoyalPineapple

@RoyalPineapple RoyalPineapple commented Jun 4, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Separates the tree traversal from the output representation by making mapNodesToHierarchy generic.

Before:  walk → AccessibilityNode tree → mapNodesToHierarchy() → [AccessibilityHierarchy]

After:   walk → AccessibilityNode tree → foldNodes<Node>()     → [Node]
                                          ├── makeElement(element, index, source) → Node
                                          └── makeContainer(container, children, source) → Node

parseAccessibilityHierarchy(in:) becomes a two-line wrapper that calls the fold with closures producing AccessibilityHierarchy. No behavioral change for existing callers.

Usage

parser.parseAccessibilityHierarchy(
    in: view,
    makeElement: { element, traversalIndex, source in
        MyNode.leaf(element, view: source)
    },
    makeContainer: { container, children, source in
        MyNode.group(container, children: children, view: source)
    }
)

source is the live NSObject that produced the element or container.

@RoyalPineapple
RoyalPineapple force-pushed the RoyalPineapple/hierarchy-fold branch 2 times, most recently from fd831fd to d5b79c2 Compare June 4, 2026 14:24
@RoyalPineapple
RoyalPineapple force-pushed the RoyalPineapple/hierarchy-fold branch from d5b79c2 to f9c561c Compare June 22, 2026 13:06
@RoyalPineapple
RoyalPineapple marked this pull request as ready for review June 22, 2026 15:54
@RoyalPineapple
RoyalPineapple force-pushed the RoyalPineapple/hierarchy-fold branch from f9c561c to 7ca0b7b Compare June 22, 2026 18:55
@RoyalPineapple
RoyalPineapple marked this pull request as draft June 22, 2026 19:00
@RoyalPineapple
RoyalPineapple force-pushed the RoyalPineapple/hierarchy-fold branch 2 times, most recently from 8505721 to 9994f1c Compare June 22, 2026 21:59
@RoyalPineapple RoyalPineapple changed the title Add generic fold to AccessibilityHierarchyParser Separate traversal from representation with generic fold Jun 22, 2026
@RoyalPineapple
RoyalPineapple marked this pull request as ready for review June 22, 2026 22:37
@RoyalPineapple
RoyalPineapple force-pushed the RoyalPineapple/hierarchy-fold branch from 0859ca5 to 3044db8 Compare June 22, 2026 23:58

@soroushsq soroushsq left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is purely additive in terms of APIs, right? The old APIs are composed out of this new one?

Comment on lines +980 to +994
let _: [String] = parser.parseAccessibilityHierarchy(
in: rootView,
userInterfaceLayoutDirectionProvider: TestUserInterfaceLayoutDirectionProvider(
userInterfaceLayoutDirection: .leftToRight
),
userInterfaceIdiomProvider: TestUserInterfaceIdiomProvider(userInterfaceIdiom: .phone),
makeElement: { elem, index, source in
capturedElementSource = source
return "element:\(elem.description)"
},
makeContainer: { cont, children, source in
capturedContainerSource = source
return "container:[\(children.joined(separator: ","))]"
}
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this one become generic over (String, Source?, Container?). I guess it's not a generic reduce, so it has to return an array of some kind or other?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it does need to be an outer array but the generic can be a tuple like that, updated the test to use
typealias FoldNode = (label: String, source: NSObject, container: AccessibilityContainer?)

Introduce parseAccessibilityHierarchy<Node>(in:makeElement:makeContainer:),
a single bottom-up fold that builds a caller-defined node type in one
traversal. makeElement receives the parsed AccessibilityElement, its
traversalIndex, and the source accessibility object; makeContainer receives
the AccessibilityContainer, its already-built children, and the source object.

parseAccessibilityHierarchy(in:) and parseAccessibilityElements(in:) become
the default instantiation producing [AccessibilityHierarchy] and ignoring
the source.
@RoyalPineapple
RoyalPineapple force-pushed the RoyalPineapple/hierarchy-fold branch from 3044db8 to c7d9d0e Compare June 23, 2026 16:17
@RoyalPineapple
RoyalPineapple merged commit ab529e4 into cashapp:main Jun 23, 2026
9 checks passed
RoyalPineapple added a commit to TheButtonHeist/AccessibilitySnapshotBH that referenced this pull request Jun 23, 2026
…l tracking, trait APIs, container actions

Rebased onto upstream/main (includes cashapp#330, cashapp#336, cashapp#339, cashapp#342, cashapp#343).

Parser features:
- elementVisitor/containerVisitor closures on parseAccessibilityElements
- _accessibilityIsScrollable SPI for SwiftUI scroll detection
- Container index API fallback (resolvedAccessibilityElements)
- Modal container preservation (isModalBoundary)
- Container membership tracking (elementIndex/elementCount on ContextProvider)
- accessibilityCustomActions surfaced on containers
- CustomAction promoted from String typealias to struct

Trait APIs on UIAccessibilityTraits:
- knownTraits, knownTraitNames, traitNames, fromNames
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants