fix(chat): keep the latest reply mounted through settlement and animate the timeline collapse

The streaming and settled versions of the trailing assistant reply were
two components, so settling unmounted one and mounted the other and the
entrance tween replayed over text the reader was already on. One
LatestAssistantEntry now owns that row under a single key, and the
history list excludes it. The CSS entrance keyframe that doubled the
GSAP tween is gone and the tween matches the documented 160ms. The step
timeline no longer remounts on settle: it is a button-controlled
disclosure with a 180ms grid-rows transition, the reader's own toggle
wins over the live default, and an in-flight request with no events yet
shows a queued row instead of an empty shell.

BUG-474 BUG-475

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
This commit is contained in:
Jesse_Chen
2026-09-02 04:29:01 +00:00
co-authored by Claude Fable 5.1
parent ad9dba5c79
commit aff9d19343
13 changed files with 365 additions and 103 deletions
+34 -6
View File
@@ -545,7 +545,6 @@ button:disabled { cursor: default; opacity: .45; }
@keyframes app-loading-orbit { to { transform: rotate(360deg); } }
@keyframes inline-spin { to { transform: rotate(360deg); } }
@keyframes message-enter { from { opacity: 0; transform: translateY(4px); } }
@keyframes onboarding-card-enter { from { opacity: 0; transform: translateY(6px); } }
@keyframes onboarding-caret { 50% { opacity: 0; } }
@keyframes account-overlay-enter { from { opacity: 0; } }
@@ -1034,7 +1033,7 @@ button:disabled { cursor: default; opacity: .45; }
min-height: 40vh;
color: var(--color-ink-secondary);
}
.message { display: flex; animation: message-enter 160ms var(--ease-out) both; padding: var(--space-2) 0; }
.message { display: flex; padding: var(--space-2) 0; }
.agent-avatar { width: 32px; height: 32px; display: block; flex: 0 0 32px; margin-top: var(--space-2); border-radius: 50%; background: var(--color-canvas) url("/jyotish-logo.png") center / contain no-repeat; box-shadow: 0 0 0 1px var(--ring-hairline); }
.message-content { min-width: 0; max-width: min(80%, 680px); }
.message-bubble { overflow: hidden; border: 0; padding: var(--space-3) var(--space-4); border-radius: var(--radius-lg); background: var(--color-canvas-muted); }
@@ -1133,7 +1132,7 @@ button:disabled { cursor: default; opacity: .45; }
.consultation-run-timeline {
min-width: 0;
}
.consultation-run-timeline > summary,
.consultation-run-timeline__summary,
.consultation-run-timeline__details > summary {
display: grid;
grid-template-columns: minmax(0, 1fr) 20px;
@@ -1144,12 +1143,40 @@ button:disabled { cursor: default; opacity: .45; }
list-style: none;
color: var(--color-ink-tertiary);
}
.consultation-run-timeline > summary::-webkit-details-marker,
.consultation-run-timeline__summary {
width: 100%;
margin: 0;
padding: 0;
border: 0;
background: none;
font: inherit;
text-align: left;
}
.consultation-run-timeline__summary:focus-visible {
border-radius: var(--radius-sm);
outline: 2px solid var(--color-focus);
outline-offset: 3px;
}
.consultation-run-timeline__details > summary::-webkit-details-marker {
display: none;
}
.consultation-run-timeline__summary-label {
min-width: 0;
animation: agent-activity-status-in 120ms ease-out;
}
/* Open ↔ closed is a 180ms height transition on the same element, so settling
never swaps the timeline out from under the reader. */
.consultation-run-timeline__body-wrap {
display: grid;
grid-template-rows: 0fr;
transition: grid-template-rows 180ms var(--ease-out);
}
.consultation-run-timeline.is-open > .consultation-run-timeline__body-wrap {
grid-template-rows: 1fr;
}
.consultation-run-timeline__body-inner {
min-height: 0;
overflow: hidden;
}
.consultation-run-timeline__list {
margin-top: var(--space-2);
@@ -1184,7 +1211,7 @@ button:disabled { cursor: default; opacity: .45; }
color: var(--color-ink-tertiary);
transition: transform 160ms ease;
}
.consultation-run-timeline[open] > summary > .consultation-run-timeline__chevron,
.consultation-run-timeline.is-open > .consultation-run-timeline__summary > .consultation-run-timeline__chevron,
.consultation-run-timeline__details[open] > summary > .consultation-run-timeline__chevron {
transform: rotate(180deg);
}
@@ -1221,7 +1248,8 @@ button:disabled { cursor: default; opacity: .45; }
white-space: pre-wrap;
}
@media (prefers-reduced-motion: reduce) {
.consultation-run-timeline__spinner,
.consultation-run-timeline__summary-label,
.consultation-run-timeline__body-wrap,
.consultation-run-timeline__chevron {
animation: none;
transition: none;