30ebf4e25caf826df5a3785cfd2b07a2bbfe3e4e
Everything that opens now behaves alike. Touch a row's upper half and its top edge holds, so it opens and closes downwards; touch the lower half and the bottom edge holds, which is what the list does on its own. A group's heading and the bar at its foot fall in the halves they already occupy, so they keep the behaviour they had, and a single tool call -- one card, with no bar -- gets the same choice for the first time: tapping low on an open Bash card now shuts it downwards exactly as a group's bar does. That makes the position of the tap the one mechanism, and RowEdge goes away with the pair of hardcoded ends it existed to name. Controls report where they were touched in root coordinates, which is all a control can know -- a group is one row with a control at each end and calls in the middle, and only the row knows where its own ends are -- and the row turns that into an edge. `clickableAt` is built on `clickable` rather than replacing it, so the ripple and the click action assistive technology reads are unchanged; the down position is observed on the initial pointer pass and nothing is consumed. Verified with ui-trace: on a collapsed group, a tap at y=1370 holds the heading and one at y=1450 lets the row grow upward instead. On the same nested call inside an open group, opening it from the group's upper half holds the heading at 565 and from the lower half moves it to 296. ktfmt, lint and 85 tests clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Languages
Rust
54%
Kotlin
43.6%
Shell
2.4%