In multi-step turns (assistant -> tool_call -> assistant -> tool_call ->
final assistant), only the turn-final assistant bubble was rendering the
'jump to question' navigation button because the gate keyed on
isTurnFinalAssistant. Intermediate assistant bubbles that *do* have a
resolvable question raw-index lost the affordance entirely.
Switch the gate to 'show whenever questionRawIdxByAssistantRawIdx has a
target for this rawIdx', which is the actual precondition for the button
being meaningful. Turn-finality was a proxy for 'has a question target'
that under-covered multi-step turns.
No template/CSS change needed; _questionJumpButtonHtml already handles
the rawIdx-or-undefined contract.
- Stop provider-qualified or slash-qualified model inputs from fuzzy-matching a
sibling catalog entry when the exact requested model is missing from the
curated picker list.
- Preserve the raw typed selection so uncatalogued provider-routed models
fall through to a temporary custom option instead of silently snapping to a
nearby curated model.
- Add generalized regression coverage for provider-qualified uncatalogued
picker selections.
A touch-primary device (`matchMedia('(pointer:coarse)')` is true) can
still have a physical keyboard available — Android tablet + Bluetooth
keyboard, detachable Surface, iPad + Magic Keyboard. The existing
`_mobileDefault` gate flipped Enter to newline on every such device the
moment the visual-viewport heuristic *thought* the soft keyboard was
open, which it often did when the on-screen IME hadn't actually come up
because the user is typing on the hardware keys. Result: Shift+Enter and
Ctrl+Enter never sent and the user could not submit at all.
Add `_hasFinePointerCoexisting()` (`(any-pointer:fine)`) and short-
circuit the mobile-default path when ANY fine pointer is present. That
flag is true whenever a real mouse/trackpad/stylus is paired, which is
the strongest browser signal we have for 'there is a hardware input rig
in the picture too'. Pure-touch phones/tablets are unaffected.
The active-session 'is it externally updated?' fallback poll fires every
5 s. On long sessions this causes visible scroll churn (the rendered
message list is rebuilt and the scrollTop is restored on a 5-second
cadence) and a measurable network/CPU floor even when the user is just
reading.
This poll is a *fallback* for the case where the SSE session-events
stream is unavailable; SSE already invalidates the active session in
real time. Pushing it to 30 s keeps the safety net for SSE-broken
environments without it acting as a primary refresh path.
Regression introduced in 467ef33a.
_cliToolResultSnippet truncated to 200 chars while the backend's
_tool_result_snippet uses 4000. This caused tool card details to be
more aggressively truncated after session reload than during live
streaming.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>