Home: a Chief's chat opens at its newest message, or where you left it

AU
6d ago
+942-8

Fixes https://github.com/manaflow-ai/cmux/issues/27627.

What changes

Picking a Chief in Home's list builds a new transcript view (TopHomePageView.show → HomeHostView → MessagesLabHomeView), so any scroll position was thrown away. The chat now opens the way Messages opens a conversation:

  • Left at the newest message (or never opened): it opens at the newest message, including anything that arrived while another Chief was shown.
  • Left scrolled up: it opens on the message the person was reading, at the same distance above the message field, even if the pane changed height in between.

How:

  • Where the place is kept: HomeStore already had a per-conversation HomeScrollAnchor (kept with the drafts and written to the Home cache), but the native transcript never wrote or read it. A closing view now writes it, as the newest message bubble on screen and how far its bottom sits above the field. At the newest message it writes none.
  • Forgotten at the newest message: scrolling back down to the newest message (or a send landing there) clears the kept place at once (forgetPlaceAtNewest), not only when the view closes, so a quit or crash leaves no stale place in the Home cache.
  • Stopped before the next view reads it: both TopHomePageView and HomeNewMessageView.showConversation stop the outgoing transcript before building the next one.
  • When it's applied: the new view applies the place once it has been laid out at its pane's size (HostView.onLaidOut) and has rows. MessagesLabHomeView.init installs rows at the controller's 628Ɨ1041 fixture size, so placing any earlier put the message in the wrong spot.
  • After the remeasure: while rows still carry estimated heights, the place is shown again after each off-main width measure (MessagesWindowView.onWindowMeasured, a small hook in the vendored view). It stops as soon as the person scrolls.
  • Fallbacks to the newest message: a place whose message is not in the loaded window, a permalink open (as before), and no saved place.
  • Measured from bubbles: the place uses the message's last part: row, so read receipts, edited lines and reply counts don't move it. A closing view steps back past typing dots, notices, fading rows and running streams to a message the store names.
  • Debug only: debug.home.drive rows now reports atNewest, and debug.home.drive scroll clamps to the content like a wheel.

Tests

Head 94d9a40f72935e1c0b73e983b1dd8666d8632fcd, base 46c97e90fda7b4b6737eae1a2a328a069667db78.

New suite HomeOpenPlacementTests (Packages/Shared/CmuxMessagesLab). It opens a real MessagesLabHomeView over a real HomeStore in Home's order: built in init with no size, added to the pane at zero size, given the pane's size later, then the off-main remeasure.

  • Red: on the test-only commit f8adcdd08cba98739eeafdda3361609a2e0a8936, aChatLeftScrolledUpOpensWhereThePersonWasReading failed (reopened pinned to the bottom). On 3d4927db, aChatScrolledBackDownForgetsItsPlaceWithoutClosing failed before forgetPlaceAtNewest (cmux-ci step 29877327f05692b46f324550).
  • Green at 94d9a40f72935e1c0b73e983b1dd8666d8632fcd, all 9 suites passed (cmux-ci step 6177413d2f0d5c1444e9638c): HomeOpenPlacementTests, HomeResizeFollowTests, HomeOlderPageAnchorTests, HomeScrollToFirstMessageTests, HomeThreadScrollTests, HomeSendLandingTests, HomeBareTopTests, HomeResizeWhileStreamingTests, HomeStreamingResizeTests.
    cmux-ci run --class light --script scripts/ci/package-test-lane.sh --ref <sha> --arg=suite --arg=Packages/Shared/CmuxMessagesLab --arg=<suites> --env CMUX_CI_XCODE_APP=/Applications/Xcode_26.6.app

End to end on the tagged app

scripts/cmux-next/home-open-placement.py against fleet build dc4cab6c18c3568f010e6fd4 (tag 27627-chief-chat-scroll, head 94d9a40f), docked Home, two local channels of 60 messages, switched with home.openConversation, isolated HOME, never activated. All checks passed:

ok   1. A, first open: its newest message shows
ok   2. A, scrolled up: no longer at its newest message
ok   2. B, first open: its newest message shows
ok   2. A, reopened: the rows the person read, where they were
ok   3. A, a new message meanwhile: still opens where the person was reading
ok   4. A, scrolled back down: its newest message shows
ok   4. A, left at the bottom: opens at its newest message, the one that came meanwhile
ok   5. B, left at the bottom: opens at its newest message
all checks passed

The window snapshots of A scrolled up and A reopened after B show the same rows (A 21 to A 39) at the same place; A reopened after being left at the bottom shows "A newest: came while B showed" above the field.

Observed / Not run

  • Observed: the fleet runs and end-to-end run above, and scripts/cmux-next/prepush-gate.py with no new violations.
  • Not run: the e2e ran on a downloaded fleet build on an agent Mac in the background (no GUI host was reachable from it), not on a capture host. Real pointer wheel input was not used; debug.home.drive scroll moves the transcript the way a wheel does.
  • No capture: the change is where the transcript scrolls to on open, not a new look; the window snapshots above are the visual evidence.
  • Restarts: the anchor is kept in the Home cache across relaunches. After a relaunch it usually falls back to the newest message, because the first page doesn't hold an old place.

Changelog

  • Fixed: Switching to a Chief opens its chat at the newest message, or where you left it if you had scrolled up.

šŸ¤– Generated with Claude Code


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.


Summary by cubic

Switching to a Chief in Home built a new transcript view, so any scroll position was thrown away. The chat now opens at its newest message, including anything that arrived while another Chief was shown, or back at the message the person was reading, at the same distance above the field, if they'd left scrolled up. Fixes https://github.com/manaflow-ai/cmux/issues/27627.

  • A closing view writes the per-conversation HomeScrollAnchor; the next view applies it after its first layout in the pane at the pane's size, since init installs rows at a fixture size.
  • The place is re-applied after each off-main width measure while rows carry estimated heights, and stops once the person scrolls; a send or jump to the newest message during the measure wins.
  • The anchor is cleared as soon as the chat is back at its newest message (a scroll down, a send), not only on close, so a quit or crash leaves no stale place.
  • A permalink being opened opens at the newest message; a kept message outside the loaded window falls back to the newest message.
  • Home stops the outgoing chat's transcript on a Chief switch or when the New Message view drops it so the place is kept, and no read cursor moves before the view is placed.
  • New HomeOpenPlacementTests cover a first open, a chat left at the bottom with new messages, a scrolled-up chat reopened in the same or a taller pane, a chat left at the bottom after being scrolled up, a place forgotten when scrolled back down without closing, and a stale kept place.
  • New home-open-placement.py drives the real docked Home over two local channels via home.openConversation, checking the same cases end to end along with a message arriving while another chat shows; the debug socket's rows reply reports atNewest and scroll clamps within the loaded rows.