nandi/jolt-nativepublic Fork 0
4f920af
Commits
Clone
git clone https://git.rickub.com/nandi/jolt-native.git
git clone ssh://git@rickub.com/nandi/jolt-native.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

Tell the client where a scroll area landed, not where it changed

A scroll area reported its place — "end" or "away" — whenever that answer
differed from the one in the memo it keeps per `scroll-key`. The memo is not
the client's belief, though, and the two part company wherever something
other than the reader's hand moved the list.

Two of those, and both end the same way: a "jump to present" button over a
backlog that is already at its newest line, which hides itself when clicked
and scrolls nowhere, because there is nowhere to scroll to.

The jump branch marked `at_end` on the way past, before the snap it was
asking for had happened. So the report that came back agreed with the memo
and was never passed on — the same mistake the reveal branch above it carries
a paragraph warning against.

And a list that has just mounted says nothing, because the place it opens at
is the place the memo guessed it would. Nothing CHANGED. But the client's
belief is about the list this one replaced, and since frq gave every room its
own `scroll-key` that is another conversation entirely: read back through
yesterday in one channel, open another, and the button comes along.

So the memo carries what the client was last told, apart from where the list
is, and a report is worth making exactly when those differ. A list that has
just mounted has been told nothing, whatever was said about the last one
under its name.

The decision either way is now a pair of functions over the ask and the memo
— `scroll_move` and `report` — which is the whole of the thinking and none of
the toolkit, so it can be read and tested without a window to put it in.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
nandi committed 2026-09-12T10:44:20-07:00 Browse files
4f920af parent: 6316bb1
modified crates/jolt-cosmic/src/lib.rs +174 -32
@@ -229,6 +229,20 @@ struct ScrollMemo {
229229 /// Whether the reader is at the newest line. A `stick-to-bottom` list
230230 /// follows what arrives only while this holds.
231231 at_end: bool,
232+ /// What the client was last told about that, and `None` while it has been
233+ /// told nothing.
234+ ///
235+ /// Held apart from `at_end` because the two answer different questions.
236+ /// `at_end` is where this list is; `told` is what the client believes,
237+ /// and a report is worth making exactly when they differ. Reporting on a
238+ /// change in `at_end` alone loses two cases, and both of them end with a
239+ /// "jump to present" button over a backlog that is already at its newest
240+ /// line. A list mounting says nothing, because the place it opens at is
241+ /// the place the memo guessed it would — but the client's belief is about
242+ /// the list this one REPLACED, which in frq is another room entirely. And
243+ /// a jump says nothing, because the branch that asked for it marked the
244+ /// memo on the way past, so the report that came back agreed with it.
245+ told: Option<bool>,
232246 offset_y: f32,
233247 /// How tall the viewport was when the reader last moved it, which is what
234248 /// a jump centres a row in. Zero until they have: a list nobody has
@@ -329,6 +343,71 @@ fn scroll_asks(before: &Tree, now: &Tree) -> Vec<ScrollAsk> {
329343 asks
330344 }
331345
346+/// What a commit asks of one scroll area, decided.
347+#[derive(Debug, PartialEq)]
348+enum ScrollMove {
349+ /// Show this row of it.
350+ Reveal(i32),
351+ /// Take it to its newest line.
352+ End,
353+ /// Put it back where the reader left it, in points.
354+ Restore(f32),
355+ /// Leave it alone.
356+ Stay,
357+}
358+
359+/// What to do with one scroll area, and the memo brought up to date.
360+///
361+/// Split out from `take_tree` because it is the whole of the thinking and none
362+/// of the toolkit: everything here is the ask beside what is remembered, so it
363+/// can be read — and tested — without a window to put it in.
364+fn scroll_move(ask: &ScrollAsk, memo: &mut ScrollMemo) -> ScrollMove {
365+ // A list this tree did not have a moment ago is a list the client has
366+ // heard nothing about, whatever it heard about the last one under this
367+ // name. Forgetting what it was told is what makes the next report happen,
368+ // so that what it believes is about the list it is looking at — in frq,
369+ // the room it is in rather than the room it came from.
370+ if ask.fresh {
371+ memo.told = None;
372+ }
373+ let jumped = !ask.fresh && ask.tick.is_some() && ask.tick != ask.tick_before;
374+ // A row is asking to be shown. Whether or not it can be shown yet,
375+ // nothing else may move this list while it is asking: the branch below
376+ // would otherwise take a reader who was at the newest line — which is
377+ // most readers, most of the time — straight back to it, and a jump that
378+ // ends at the bottom of the room reads as a jump that did nothing.
379+ //
380+ // Nothing below marks the memo, either. Where a list lands is `report`'s
381+ // to hear from the toolkit and pass on; a memo that wrote the answer down
382+ // here would agree with the report when it came and keep it from the
383+ // client — the list moved, nobody was told, and the client went on
384+ // believing whatever it believed before. Which is a "jump to present"
385+ // button over a backlog that is already at its newest line.
386+ if let Some(row) = ask.reveal {
387+ ScrollMove::Reveal(row)
388+ } else if jumped || (ask.stick && memo.at_end) {
389+ ScrollMove::End
390+ } else if ask.fresh {
391+ ScrollMove::Restore(memo.offset_y)
392+ } else {
393+ ScrollMove::Stay
394+ }
395+}
396+
397+/// What to tell the client now that this list is at `at_end`, if anything.
398+///
399+/// Against what it was last told rather than against where the list was a
400+/// moment ago. The two are the same answer for a list the reader is moving by
401+/// hand, and they part company wherever something else moved it — see `told`.
402+fn report(memo: &mut ScrollMemo, at_end: bool) -> Option<&'static str> {
403+ (memo.told != Some(at_end)).then(|| {
404+ memo.told = Some(at_end);
405+ // "end" or "away", the strings libvidya emits: frq's handler compares
406+ // against "end".
407+ if at_end { "end" } else { "away" }
408+ })
409+}
410+
332411 /// Where the rows of the scroll area called `name` were last laid out.
333412 ///
334413 /// By name and not by node, because a scroll area outlives the node ids of a
@@ -543,37 +622,21 @@ impl App {
543622 .entry(ask.name.clone())
544623 .or_insert(ScrollMemo {
545624 at_end: ask.stick,
625+ told: None,
546626 offset_y: 0.0,
547627 height: 0.0,
548628 });
549- let jumped = !ask.fresh && ask.tick.is_some() && ask.tick != ask.tick_before;
550- // A row asking to be shown, and a place written down for it by the
551- // last layout. Both, or there is nothing to do yet: the row is
552- // measured on the frame it appears, and the ask stands until it
553- // has been.
554- // A row is asking to be shown. Whether or not it can be shown yet,
555- // nothing else may move this list while it is asking: the branch
556- // below would otherwise take a reader who was at the newest line —
557- // which is most readers, most of the time — straight back to it,
558- // and a jump that ends at the bottom of the room reads as a jump
559- // that did nothing.
560- if let Some(row) = ask.reveal {
561- // The memo is not told where this lands. `scrolled` is what
562- // knows, and it tells the client only when the answer CHANGES
563- // — so a memo that marked itself away from the end here stole
564- // that change from it: the list moved, the client was never
565- // told, and it went on believing the reader was at the newest
566- // line. Which is a "jump to present" button that does not
567- // appear until the reader scrolls to the present by hand.
568- tasks.push(reveal(ask.name.clone(), row, memo.height));
569- } else if jumped || (ask.stick && memo.at_end) {
570- memo.at_end = true;
571- tasks.push(snap_to_end(&ask.name));
572- } else if ask.fresh {
573- tasks.push(iced_scrollable::scroll_to(
629+ // A row asking to be shown is measured on the frame it appears,
630+ // so the ask stands until the layout has a place for it — see
631+ // `reveal`, which asks again rather than giving up.
632+ match scroll_move(&ask, memo) {
633+ ScrollMove::Reveal(row) => tasks.push(reveal(ask.name.clone(), row, memo.height)),
634+ ScrollMove::End => tasks.push(snap_to_end(&ask.name)),
635+ ScrollMove::Restore(y) => tasks.push(iced_scrollable::scroll_to(
574636 scroll_id(&ask.name),
575- AbsoluteOffset { x: None, y: Some(memo.offset_y) },
576- ));
637+ AbsoluteOffset { x: None, y: Some(y) },
638+ )),
639+ ScrollMove::Stay => {}
577640 }
578641 }
579642 // A list that was never scrolled keeps no memo worth the space; one
@@ -589,17 +652,14 @@ impl App {
589652 let at_end = room - y <= AT_END_SLACK;
590653 let memo = self.scrolls.entry(name).or_insert(ScrollMemo {
591654 at_end,
655+ told: None,
592656 offset_y: y,
593657 height: viewport.bounds().height,
594658 });
595- let was = memo.at_end;
596659 memo.at_end = at_end;
597660 memo.offset_y = y;
598661 memo.height = viewport.bounds().height;
599- // "end" or "away", the strings libvidya emits: frq's handler compares
600- // against "end".
601- if was != at_end {
602- let place = if at_end { "end" } else { "away" };
662+ if let Some(place) = report(memo, at_end) {
603663 post(node, "change", place.to_owned(), 0.0);
604664 }
605665 }
@@ -1796,6 +1856,88 @@ mod tests {
17961856 assert_eq!(scroll_asks(&t, &t)[0].reveal, None);
17971857 }
17981858
1859+ /// An ask for the scroll area called `name`, with nothing going on.
1860+ fn ask(name: &str) -> ScrollAsk {
1861+ ScrollAsk {
1862+ name: name.to_owned(),
1863+ stick: true,
1864+ tick: Some(0.0),
1865+ tick_before: Some(0.0),
1866+ fresh: false,
1867+ reveal: None,
1868+ }
1869+ }
1870+
1871+ fn memo() -> ScrollMemo {
1872+ ScrollMemo { at_end: true, told: Some(true), offset_y: 0.0, height: 600.0 }
1873+ }
1874+
1875+ #[test]
1876+ fn a_jump_asks_for_the_end_without_saying_it_arrived() {
1877+ let mut m = memo();
1878+ m.at_end = false;
1879+ m.told = Some(false);
1880+ let mut a = ask("backlog");
1881+ a.tick = Some(1.0);
1882+ assert_eq!(scroll_move(&a, &mut m), ScrollMove::End);
1883+ // The memo still says what the last report said, so the report that
1884+ // comes back from the toolkit is a change, and is passed on. A memo
1885+ // that marked itself here would swallow it, and the client would go
1886+ // on believing the reader was away from the newest line.
1887+ assert!(!m.at_end);
1888+ assert_eq!(report(&mut m, true), Some("end"));
1889+ assert_eq!(report(&mut m, true), None);
1890+ }
1891+
1892+ #[test]
1893+ fn a_list_that_has_just_mounted_says_where_it_is() {
1894+ // The reader left this list away from the end, and the client was
1895+ // told so. It comes back — another room under the same widget, or the
1896+ // same room after the lightbox took the screen — and lands at the end
1897+ // because it sticks there. Nothing about `at_end` CHANGED across
1898+ // that, and the client still has to hear it: what it believes is
1899+ // about the list this one replaced.
1900+ let mut m = memo();
1901+ m.at_end = true;
1902+ m.told = Some(false);
1903+ let mut a = ask("backlog");
1904+ a.fresh = true;
1905+ assert_eq!(scroll_move(&a, &mut m), ScrollMove::End);
1906+ assert_eq!(report(&mut m, true), Some("end"));
1907+ }
1908+
1909+ #[test]
1910+ fn a_list_nobody_moved_is_not_reported_twice() {
1911+ let mut m = memo();
1912+ assert_eq!(report(&mut m, true), None);
1913+ assert_eq!(report(&mut m, false), Some("away"));
1914+ assert_eq!(report(&mut m, false), None);
1915+ assert_eq!(report(&mut m, true), Some("end"));
1916+ }
1917+
1918+ #[test]
1919+ fn a_row_asking_to_be_shown_beats_the_end() {
1920+ let mut m = memo();
1921+ let mut a = ask("backlog");
1922+ a.reveal = Some(42);
1923+ a.tick = Some(1.0);
1924+ assert_eq!(scroll_move(&a, &mut m), ScrollMove::Reveal(42));
1925+ }
1926+
1927+ #[test]
1928+ fn a_list_coming_back_is_put_where_it_was_left() {
1929+ let mut m = memo();
1930+ m.at_end = false;
1931+ m.offset_y = 512.0;
1932+ let mut a = ask("backlog");
1933+ a.fresh = true;
1934+ assert_eq!(scroll_move(&a, &mut m), ScrollMove::Restore(512.0));
1935+ // And it is asked about again, since the client's belief is about
1936+ // whatever was under this name before.
1937+ assert_eq!(m.told, None);
1938+ assert_eq!(report(&mut m, false), Some("away"));
1939+ }
1940+
17991941 #[test]
18001942 fn a_jump_is_the_counter_moving() {
18011943 let mut t = Tree::default();
@@ -229,6 +229,20 @@ struct ScrollMemo {
229 /// Whether the reader is at the newest line. A `stick-to-bottom` list229 /// Whether the reader is at the newest line. A `stick-to-bottom` list
230 /// follows what arrives only while this holds.230 /// follows what arrives only while this holds.
231 at_end: bool,231 at_end: bool,
232+ /// What the client was last told about that, and `None` while it has been
233+ /// told nothing.
234+ ///
235+ /// Held apart from `at_end` because the two answer different questions.
236+ /// `at_end` is where this list is; `told` is what the client believes,
237+ /// and a report is worth making exactly when they differ. Reporting on a
238+ /// change in `at_end` alone loses two cases, and both of them end with a
239+ /// "jump to present" button over a backlog that is already at its newest
240+ /// line. A list mounting says nothing, because the place it opens at is
241+ /// the place the memo guessed it would — but the client's belief is about
242+ /// the list this one REPLACED, which in frq is another room entirely. And
243+ /// a jump says nothing, because the branch that asked for it marked the
244+ /// memo on the way past, so the report that came back agreed with it.
245+ told: Option<bool>,
232 offset_y: f32,246 offset_y: f32,
233 /// How tall the viewport was when the reader last moved it, which is what247 /// How tall the viewport was when the reader last moved it, which is what
234 /// a jump centres a row in. Zero until they have: a list nobody has248 /// a jump centres a row in. Zero until they have: a list nobody has
@@ -329,6 +343,71 @@ fn scroll_asks(before: &Tree, now: &Tree) -> Vec<ScrollAsk> {
329 asks343 asks
330 }344 }
331 345
346+/// What a commit asks of one scroll area, decided.
347+#[derive(Debug, PartialEq)]
348+enum ScrollMove {
349+ /// Show this row of it.
350+ Reveal(i32),
351+ /// Take it to its newest line.
352+ End,
353+ /// Put it back where the reader left it, in points.
354+ Restore(f32),
355+ /// Leave it alone.
356+ Stay,
357+}
358+
359+/// What to do with one scroll area, and the memo brought up to date.
360+///
361+/// Split out from `take_tree` because it is the whole of the thinking and none
362+/// of the toolkit: everything here is the ask beside what is remembered, so it
363+/// can be read — and tested — without a window to put it in.
364+fn scroll_move(ask: &ScrollAsk, memo: &mut ScrollMemo) -> ScrollMove {
365+ // A list this tree did not have a moment ago is a list the client has
366+ // heard nothing about, whatever it heard about the last one under this
367+ // name. Forgetting what it was told is what makes the next report happen,
368+ // so that what it believes is about the list it is looking at — in frq,
369+ // the room it is in rather than the room it came from.
370+ if ask.fresh {
371+ memo.told = None;
372+ }
373+ let jumped = !ask.fresh && ask.tick.is_some() && ask.tick != ask.tick_before;
374+ // A row is asking to be shown. Whether or not it can be shown yet,
375+ // nothing else may move this list while it is asking: the branch below
376+ // would otherwise take a reader who was at the newest line — which is
377+ // most readers, most of the time — straight back to it, and a jump that
378+ // ends at the bottom of the room reads as a jump that did nothing.
379+ //
380+ // Nothing below marks the memo, either. Where a list lands is `report`'s
381+ // to hear from the toolkit and pass on; a memo that wrote the answer down
382+ // here would agree with the report when it came and keep it from the
383+ // client — the list moved, nobody was told, and the client went on
384+ // believing whatever it believed before. Which is a "jump to present"
385+ // button over a backlog that is already at its newest line.
386+ if let Some(row) = ask.reveal {
387+ ScrollMove::Reveal(row)
388+ } else if jumped || (ask.stick && memo.at_end) {
389+ ScrollMove::End
390+ } else if ask.fresh {
391+ ScrollMove::Restore(memo.offset_y)
392+ } else {
393+ ScrollMove::Stay
394+ }
395+}
396+
397+/// What to tell the client now that this list is at `at_end`, if anything.
398+///
399+/// Against what it was last told rather than against where the list was a
400+/// moment ago. The two are the same answer for a list the reader is moving by
401+/// hand, and they part company wherever something else moved it — see `told`.
402+fn report(memo: &mut ScrollMemo, at_end: bool) -> Option<&'static str> {
403+ (memo.told != Some(at_end)).then(|| {
404+ memo.told = Some(at_end);
405+ // "end" or "away", the strings libvidya emits: frq's handler compares
406+ // against "end".
407+ if at_end { "end" } else { "away" }
408+ })
409+}
410+
332 /// Where the rows of the scroll area called `name` were last laid out.411 /// Where the rows of the scroll area called `name` were last laid out.
333 ///412 ///
334 /// By name and not by node, because a scroll area outlives the node ids of a413 /// By name and not by node, because a scroll area outlives the node ids of a
@@ -543,37 +622,21 @@ impl App {
543 .entry(ask.name.clone())622 .entry(ask.name.clone())
544 .or_insert(ScrollMemo {623 .or_insert(ScrollMemo {
545 at_end: ask.stick,624 at_end: ask.stick,
625+ told: None,
546 offset_y: 0.0,626 offset_y: 0.0,
547 height: 0.0,627 height: 0.0,
548 });628 });
549- let jumped = !ask.fresh && ask.tick.is_some() && ask.tick != ask.tick_before;629+ // A row asking to be shown is measured on the frame it appears,
550- // A row asking to be shown, and a place written down for it by the630+ // so the ask stands until the layout has a place for it — see
551- // last layout. Both, or there is nothing to do yet: the row is631+ // `reveal`, which asks again rather than giving up.
552- // measured on the frame it appears, and the ask stands until it632+ match scroll_move(&ask, memo) {
553- // has been.633+ ScrollMove::Reveal(row) => tasks.push(reveal(ask.name.clone(), row, memo.height)),
554- // A row is asking to be shown. Whether or not it can be shown yet,634+ ScrollMove::End => tasks.push(snap_to_end(&ask.name)),
555- // nothing else may move this list while it is asking: the branch635+ ScrollMove::Restore(y) => tasks.push(iced_scrollable::scroll_to(
556- // below would otherwise take a reader who was at the newest line —
557- // which is most readers, most of the time — straight back to it,
558- // and a jump that ends at the bottom of the room reads as a jump
559- // that did nothing.
560- if let Some(row) = ask.reveal {
561- // The memo is not told where this lands. `scrolled` is what
562- // knows, and it tells the client only when the answer CHANGES
563- // — so a memo that marked itself away from the end here stole
564- // that change from it: the list moved, the client was never
565- // told, and it went on believing the reader was at the newest
566- // line. Which is a "jump to present" button that does not
567- // appear until the reader scrolls to the present by hand.
568- tasks.push(reveal(ask.name.clone(), row, memo.height));
569- } else if jumped || (ask.stick && memo.at_end) {
570- memo.at_end = true;
571- tasks.push(snap_to_end(&ask.name));
572- } else if ask.fresh {
573- tasks.push(iced_scrollable::scroll_to(
574 scroll_id(&ask.name),636 scroll_id(&ask.name),
575- AbsoluteOffset { x: None, y: Some(memo.offset_y) },637+ AbsoluteOffset { x: None, y: Some(y) },
576- ));638+ )),
639+ ScrollMove::Stay => {}
577 }640 }
578 }641 }
579 // A list that was never scrolled keeps no memo worth the space; one642 // A list that was never scrolled keeps no memo worth the space; one
@@ -589,17 +652,14 @@ impl App {
589 let at_end = room - y <= AT_END_SLACK;652 let at_end = room - y <= AT_END_SLACK;
590 let memo = self.scrolls.entry(name).or_insert(ScrollMemo {653 let memo = self.scrolls.entry(name).or_insert(ScrollMemo {
591 at_end,654 at_end,
655+ told: None,
592 offset_y: y,656 offset_y: y,
593 height: viewport.bounds().height,657 height: viewport.bounds().height,
594 });658 });
595- let was = memo.at_end;
596 memo.at_end = at_end;659 memo.at_end = at_end;
597 memo.offset_y = y;660 memo.offset_y = y;
598 memo.height = viewport.bounds().height;661 memo.height = viewport.bounds().height;
599- // "end" or "away", the strings libvidya emits: frq's handler compares662+ if let Some(place) = report(memo, at_end) {
600- // against "end".
601- if was != at_end {
602- let place = if at_end { "end" } else { "away" };
603 post(node, "change", place.to_owned(), 0.0);663 post(node, "change", place.to_owned(), 0.0);
604 }664 }
605 }665 }
@@ -1796,6 +1856,88 @@ mod tests {
1796 assert_eq!(scroll_asks(&t, &t)[0].reveal, None);1856 assert_eq!(scroll_asks(&t, &t)[0].reveal, None);
1797 }1857 }
1798 1858
1859+ /// An ask for the scroll area called `name`, with nothing going on.
1860+ fn ask(name: &str) -> ScrollAsk {
1861+ ScrollAsk {
1862+ name: name.to_owned(),
1863+ stick: true,
1864+ tick: Some(0.0),
1865+ tick_before: Some(0.0),
1866+ fresh: false,
1867+ reveal: None,
1868+ }
1869+ }
1870+
1871+ fn memo() -> ScrollMemo {
1872+ ScrollMemo { at_end: true, told: Some(true), offset_y: 0.0, height: 600.0 }
1873+ }
1874+
1875+ #[test]
1876+ fn a_jump_asks_for_the_end_without_saying_it_arrived() {
1877+ let mut m = memo();
1878+ m.at_end = false;
1879+ m.told = Some(false);
1880+ let mut a = ask("backlog");
1881+ a.tick = Some(1.0);
1882+ assert_eq!(scroll_move(&a, &mut m), ScrollMove::End);
1883+ // The memo still says what the last report said, so the report that
1884+ // comes back from the toolkit is a change, and is passed on. A memo
1885+ // that marked itself here would swallow it, and the client would go
1886+ // on believing the reader was away from the newest line.
1887+ assert!(!m.at_end);
1888+ assert_eq!(report(&mut m, true), Some("end"));
1889+ assert_eq!(report(&mut m, true), None);
1890+ }
1891+
1892+ #[test]
1893+ fn a_list_that_has_just_mounted_says_where_it_is() {
1894+ // The reader left this list away from the end, and the client was
1895+ // told so. It comes back — another room under the same widget, or the
1896+ // same room after the lightbox took the screen — and lands at the end
1897+ // because it sticks there. Nothing about `at_end` CHANGED across
1898+ // that, and the client still has to hear it: what it believes is
1899+ // about the list this one replaced.
1900+ let mut m = memo();
1901+ m.at_end = true;
1902+ m.told = Some(false);
1903+ let mut a = ask("backlog");
1904+ a.fresh = true;
1905+ assert_eq!(scroll_move(&a, &mut m), ScrollMove::End);
1906+ assert_eq!(report(&mut m, true), Some("end"));
1907+ }
1908+
1909+ #[test]
1910+ fn a_list_nobody_moved_is_not_reported_twice() {
1911+ let mut m = memo();
1912+ assert_eq!(report(&mut m, true), None);
1913+ assert_eq!(report(&mut m, false), Some("away"));
1914+ assert_eq!(report(&mut m, false), None);
1915+ assert_eq!(report(&mut m, true), Some("end"));
1916+ }
1917+
1918+ #[test]
1919+ fn a_row_asking_to_be_shown_beats_the_end() {
1920+ let mut m = memo();
1921+ let mut a = ask("backlog");
1922+ a.reveal = Some(42);
1923+ a.tick = Some(1.0);
1924+ assert_eq!(scroll_move(&a, &mut m), ScrollMove::Reveal(42));
1925+ }
1926+
1927+ #[test]
1928+ fn a_list_coming_back_is_put_where_it_was_left() {
1929+ let mut m = memo();
1930+ m.at_end = false;
1931+ m.offset_y = 512.0;
1932+ let mut a = ask("backlog");
1933+ a.fresh = true;
1934+ assert_eq!(scroll_move(&a, &mut m), ScrollMove::Restore(512.0));
1935+ // And it is asked about again, since the client's belief is about
1936+ // whatever was under this name before.
1937+ assert_eq!(m.told, None);
1938+ assert_eq!(report(&mut m, false), Some("away"));
1939+ }
1940+
1799 #[test]1941 #[test]
1800 fn a_jump_is_the_counter_moving() {1942 fn a_jump_is_the_counter_moving() {
1801 let mut t = Tree::default();1943 let mut t = Tree::default();