Centre a closed call on the tap, not the group around it

A call inside an open group was the one case still anchored by an edge: the
group held its top, which is right when a call is opened -- the heading under
the finger is the edge being pressed -- and wrong when one is shut by however
far down the open card the reader pressed. On a card of output that is most of
the screen, and what it looks like is the card collapsing into its own top, a
long way from the hand. It is the same rule as every other close now: what is
left of the call lands centred on the finger that shut it.

A call is not a row, so the scroll is still asked for against the group and
merely aimed at the call. What makes that possible is the group reporting how
far down its own top edge the call is drawn and how tall that card is, which is
the part only it knows; the calls above the one toggled do not move, so shifting
the group by the difference puts the call where the finger wants it.

Checked with ktfmtFormat, compileDebugKotlin, lintDebug and testDebugUnitTest,
and on the emulator against a real imported conversation: a call opened from its
heading inside a group of twelve leaves that heading where it is, and closing it
again from the middle of its output at 1800 lands the closed call at 1738..1860
-- centred on the tap to the pixel. The group's own close still centres on its
heading (1287..1413 for a tap at 1350).
This commit is contained in:
iris-ai committed 2026-09-16 03:22:55 -04:00
1 parent 914985b8b2
commit ee5bef3686
3 files changed
+125 -63

No files matched your search

+28 -24
View File
@@ -1189,25 +1189,28 @@ dev-updater (Kotlin 2.4.x, CMP 1.11.x, JDK 21).
question *was*, and "Deny" alone does not say Allow was the alternative.
One rule in two places (`AskedQuestion` and `PermissionAsk`). An answer
typed into **Other** matches no option, so that one is still written out.
- **Opening a row keeps still the end nearest the tap; closing one lands
the closed row centred on the tap itself; and a call opened or shut
*inside* a group keeps the group's top edge** (2026-09-16). A row about
to open is small, so both its edges are within a heading's height of the
finger and the wanted one is the edge pressed: touch the upper half and
the top edge holds, so it opens downwards; touch the lower half and the
bottom edge holds, as the list does by default. A row about to close
leaves a heading where a screenful of card was, and the place that
heading belongs is under the finger that shut it — a card taller than the
screen settles it, since both of its edges can be a screen's length from
the hand, and holding a *share* of the card's height was the same miss in
miniature: tap away from the middle of a long card and the heading landed
a card's height off. The third case is the one where the row is not the
thing being opened at all but the container of it; what the reader is
looking at is the call under their finger, and holding the group's top
edge is what keeps everything above the change — that call's own heading
included — exactly where it is. Centring the group instead threw a group
of six the length of the screen. Where there is not enough conversation
on the far side to scroll, the row lands as close as the list can put it.
- **Opening keeps still the edge nearest the tap; closing lands the closed
thing centred on the tap itself** (2026-09-16), and the *thing* is
whatever the reader pressed: the row, or the one call inside an open
group. A row about to open is small, so both its edges are within a
heading's height of the finger and the wanted one is the edge pressed:
touch the upper half and the top edge holds, so it opens downwards;
touch the lower half and the bottom edge holds, as the list does by
default. A row about to close leaves a heading where a screenful of card
was, and the place that heading belongs is under the finger that shut it
— a card taller than the screen settles it, since both of its edges can
be a screen's length from the hand. Holding a *share* of the card's
height was the same miss in miniature, and holding the group's top edge
when a call inside it was shut was the same miss again: the card
appeared to collapse into its own top, a long way from the hand. Where
there is not enough conversation on the far side to scroll, it lands as
close as the list can put it.
A call is not a row, so the scroll is asked for against the group and
aimed at the call: `ToolGroup` reports how far down its own top edge the
call is drawn and how tall that card is (`onToolToggle`), which is the
part only it knows, and the calls above the one toggled do not move, so
shifting the group by the difference puts the call where the finger
wants it.
**The scroll is asked for in the gesture, not from the layout that
discovers the new height**, and that is what makes it invisible: a
request made at the tap is consumed by the same measure pass that first
@@ -1216,14 +1219,15 @@ dev-updater (Kotlin 2.4.x, CMP 1.11.x, JDK 21).
from the placement phase is never picked up by another measure and does
nothing at all; one from the measure phase lands on the *next* frame,
with the uncorrected one drawn first; and correcting by the error each
pass can see is that again, once per pass. None of the three positions
needs the new height, which is what lets them be asked for before it
pass can see is that again, once per pass. Nothing it asks for needs the
new height, which is what lets it be asked for before that height
exists: a top edge holds by placing the item *above* the row where it
already is, since that item's bottom edge is the row's top edge whatever
becomes of the row, and it does not have to be composed for the list to
place it. A close places the row itself, against the height it had at the
moment it was opened — what the reader is shutting is the card they
opened, so that is the height it is going back to (`closedHeights`).
place it. A close places the row against the height it had at the moment
it was opened — what the reader is shutting is the card they opened, so
that is the height it is going back to (`closedHeights`,
`closedCallHeights`).
- **The full-screen image lives on the screen, not in the row that drew
the thumbnail** (`SessionImageViewer`). A `Read` whose result is an image
is a row of one call until the next call arrives and makes it a group — a