The commit before this one made a second tap change the transcript. It did not make the
list say which room the transcript belongs to, and on a tablet those are two halves of
one question: a reader choosing their next room is looking at the list, not at the app
bar on the other side of the window that repeats the title. With nothing marked, the
only evidence of a selection was in the half of the screen they had looked away from --
which is its own reason the pane read as unresponsive even once it worked.
**`secondaryContainer`, which is M3's container for a selected list item**, set through
`CardDefaults.cardColors` so the content colour comes with it: `cardColors`'
`contentColor` defaults to `contentColorFor(containerColor)`, so the row's text moves to
`onSecondaryContainer` without a second call site to keep in step. A row's title keeps
its `ProfileColor`, which is a fixed-lightness HSL colour and already scheme-blind, so
the swap does not change what that line is contrasted against in either scheme -- a
pre-existing question this commit neither fixes nor worsens.
**`isSelected` is a predicate, and nullable, because the two layouts disagree about
whether selection exists at all** -- not merely about its value. Where the list is the
whole screen, tapping a room navigates to it and the list is about to be gone: no room
is "the selected one", and both a highlight left behind on the way out and a reader told
"not selected" about every room would be describing a pane that is not on the screen.
`null` says that. A predicate answering false for every room does not, because that is
also exactly what a two-pane layout looks like before the first tap, and those two
states are different things for the list to be saying.
**The call site reads the layout rather than the selection**, `listPaneWidth == null`
rather than `selectedChatRoomId == null`. The selection is `rememberSaveable` and
outlives a window that narrows, so a tablet turned from landscape to portrait keeps it
-- and deriving the mark from the selection would leave a highlighted row on a
one-pane screen with no pane beside it.
**The semantics property is set only where it means something.** A row in a
selection-bearing list carries `selected`, true or false, so a screen reader can say
which of them the pane is showing; a row in a list that navigates carries nothing,
rather than announcing "not selected" about a pane that does not exist. `Modifier.then`
of an empty `Modifier` is how the row opts out, which keeps the branch at the one place
that knows.
**Tests: 2 cases, both checked against the feature removed.** The first taps two rooms
in a 1000dp window and asserts the mark lands on the right card, moves rather than
accumulating, and is absent before the first tap while the rows are already selectable
-- "asked nothing yet" and "not the one being shown" being the two states the nullable
predicate exists to tell apart. The second asserts a 700dp list has no selectable row
at all, in two halves on purpose: a list that never rendered also has no selectable
rows, so it checks the room is on screen before checking it is not selectable.
Both first drafts counted `isSelectable()` across the whole window and failed by exactly
one, because `PrimaryScrollableTabRow`'s `Tab` is selectable too. The assertions are
scoped to the rows through a matcher that requires both selectability and the room's
subject -- which works because `Card(onClick = ...)` merges its descendants, so one node
carries the subject and the selected state together. That merge is also why a selected
room is two nodes saying its subject and a merely listed one is a single node, which is
what the previous commit's case counts.
**Not done.** Still nobody has looked at this on a tablet. The tests prove the mark
follows the selection and that a one-pane list stays silent; they say nothing about
whether the colour reads as a selection at arm's length on a real screen.
976 jvm tests, 454 common tests, `m3Audit` meeting every budget.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>