Skip to content

feat(app): travelling harmonic dot + bottom-up card entry animation - #266

Merged
jeonghun-jj-lee merged 5 commits into
local/amicodefrom
travelling-dot-animation
Aug 28, 2026
Merged

feat(app): travelling harmonic dot + bottom-up card entry animation#266
jeonghun-jj-lee merged 5 commits into
local/amicodefrom
travelling-dot-animation

Conversation

@jeonghun-jj-lee

Copy link
Copy Markdown
Contributor

Summary

The harmonic dot now tracks the last prose-fragment card within a running text-part row rather than staying pinned to the first text line. As new paragraph cards settle during streaming, the dot slides smoothly down the rail, keeping the loading indicator visible near the newest content.

Changes

  • measureDotCentre targets last [data-prose-fragment] instead of first text node when fragments are present (both running and done states)
  • ThoughtRail accepts a settled prop; dot gets .thought-rail-dot--settled class after first measurement to gate the transition (prevents slide on initial mount)
  • Rail line gets matching height transition so spine extends in sync
  • Prose-fragment cards get a dedicated entry animation: 150ms ease-out, translateY(10px) + opacity fade, no blur (snappier than the general timeline-enter)
  • All transitions/animations honour prefers-reduced-motion: reduce
  • Sanity ceiling (80px) removed for fragment-targeting measurements since the dot can travel far down a long response

Test coverage

CSS regression test (travelling-dot.test.ts) guards:

  • Settled dot transition declarations
  • Rail line height transition
  • Prose-fragment-enter keyframe and duration
  • Reduced motion disabling of all above

Closes #265

The harmonic dot now tracks the LAST prose-fragment card within a running
text-part row rather than staying pinned to the first text line. As new
paragraph cards settle during streaming, the dot slides smoothly down the
rail (CSS transition, 150ms ease-out), keeping the loading indicator
visible near the newest content.

Changes:
- measureDotCentre targets last [data-prose-fragment] instead of first text
  node when fragments are present (works for both running and done states)
- ThoughtRail accepts a settled prop; dot gets .thought-rail-dot--settled
  class after first measurement to gate the transition (prevents slide on
  initial mount)
- Rail line gets matching height transition so spine extends in sync
- Prose-fragment cards get a dedicated entry animation: 150ms ease-out,
  translateY(10px) + opacity fade, no blur (snappier than the general
  timeline-enter)
- All transitions/animations honour prefers-reduced-motion: reduce
- Sanity ceiling (80px) removed for fragment-targeting measurements since
  the dot can travel far down a long response

Closes #265
@coderabbitai

coderabbitai Bot commented Aug 28, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 22b4575c-1faa-4562-9376-8236011d3770


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

When a text part is followed by another part (e.g. a tool call), the text
content is finalized — the model has moved on. Previously, the streaming
signal was keyed only on message.time.completed (message-level), so the
text tail stayed withheld until the entire turn finished. This caused a
visual ordering bug: the tool row ('Shell [command]') rendered before the
preceding text was visible.

Now the streaming signal also checks whether subsequent parts exist in the
same message. If the text part is not the last part, it's marked as done
immediately, flushing the withheld tail so text renders before its
following tool row.

Part of #265
The done-dot (filled circle) should mark the row's origin — the first
text line — while only the running harmonic dot tracks the last
prose-fragment card. Previously measureDotCentre always targeted the last
fragment regardless of state, which left the done-dot at the bottom of
long responses.

Now the measurement is conditional: fragments are targeted only when
the rail is running (r.last && r.running). When the turn completes and
the ResizeObserver re-measures, it falls back to the original first-
text-node behavior and the done-dot snaps to the top.
Tighten the gap between streaming output and the composer to create
an iMessage-like feel where content and input share the same surface.

Spacing hierarchy:
- Within a turn (pt-3): 12px
- Text-part margin-top: 12px
- Between turns (TurnGap): 24px
- Bottom to composer (paddingEnd): 24px

Changes:
- Hide ThinkingMeta during streaming (historical record only)
- Add hover tooltip on running harmonic dot (elapsed + tokens)
- Anchor timer to userMessage.time.created (survives session switch)
- Thread turnStartedAt through Thinking/AssistantPart row data
- Update stop button tooltip to 'Interrupt (Esc)'
- Collapse text-part-copy-wrapper to position:absolute (no layout impact)
- Remove pt-3 from ThinkingMeta row
- Simplify TimelineThinkingMetaRow (remove dead turnRunning branch)
- Fix shouldRenderRail test (Thinking row always provides first node)
- Update e2e smoke test for new spacer height
Replace the per-text-part hover copy overlay (position: absolute,
translateY, z-index conflicts) with a single persistent copy-trace
button on the completed-turn footer row.

The button copies the full assistant trace — all text parts plus
tool command/output, skipping exploration noise (read/glob/grep/list).

Changes:
- Remove text-part-copy-wrapper from TextPartDisplay and its CSS
- Extract buildTrace() into @opencode-ai/session-ui/build-trace (tested)
- Add TurnFooter component to AssistantParts (session-ui path)
- Add copy button to TimelineThinkingMetaRow (app timeline path)
- Add placement prop to MessageActionButton (tooltip below)
- Add turn-footer and session-turn-thinking-meta flex layout styles
- Remove text-part margin-top: 12px (parent gap handles spacing)
- Add i18n key ui.message.copyTrace
@jeonghun-jj-lee
jeonghun-jj-lee marked this pull request as ready for review August 28, 2026 16:55
@jeonghun-jj-lee
jeonghun-jj-lee merged commit 7256129 into local/amicode Aug 28, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(app): travelling harmonic dot + bottom-up card entry animation

1 participant