Skip to content

fix(ui): rework turn changes panel and lift assistant footer - #844

Merged
Astro-Han merged 5 commits into
devfrom
claude/change-summary-ui-regressions
May 22, 2026
Merged

fix(ui): rework turn changes panel and lift assistant footer#844
Astro-Han merged 5 commits into
devfrom
claude/change-summary-ui-regressions

Conversation

@Astro-Han

@Astro-Han Astro-Han commented May 22, 2026

Copy link
Copy Markdown
Owner

Summary

Fix three small regressions in the turn changes panel and lift the assistant footer (copy / model / duration) out of text-part to its own turn-level component, so the panel sits between assistant prose and footer in the natural reading order.

Why

After #704 the turn changes panel had three rough edges users hit immediately: a noisy "Some shell changes were not individually captured" line, a "0 files changed +0 -0 Undone" state when everything was undone, and the panel glued tight to surrounding chat. Fixing the third one revealed that the panel was rendered after the assistant footer in DOM, which itself was caused by the footer living inside text-part — a generic part renderer that was carrying turn-level metadata it shouldn't own.

The fix removes the noise, gives the "fully undone" state a single readable summary, and lifts the footer out of text-part so the natural turn order is prose → panel → footer. As a side effect, text-part becomes a pure markdown renderer (~60 fewer lines) and the showAssistantCopyPartID / turnDurationMs prop chain that only existed to feed the embedded footer is removed across the part registry.

Related Issue

None. Captured as a follow-up to #704; the bigger Turn Changes visual redesign is tracked separately in #823.

Human Review Status

Pending

Review Focus

  • DOM order in SessionTurn: AssistantParts → SessionTurnChangesPanel → AssistantTurnFooter, with the footer gated on !working() so it doesn't appear during streaming.
  • assistantFooterTarget memo returns {message, text} for the last non-empty assistant text part so the footer's meta (agent / model / duration) is read from the message that owns the text being copied, not from "the last assistant message" which can end on a tool part.
  • Footer keeps its hover-only behaviour, now gated on session-turn-message-container:hover instead of the per-part wrapper.
  • Panel margin-top: 12px follows the "breathe" tier in DESIGN.md (paragraph-to-paragraph), because the panel is a side-effect of the same turn, not a section switch — see the new "Turn-internal product modules" paragraph added to DESIGN.md (local-only file, not in this diff).
  • message-part-registry.test.ts clipboard hardening grep stays at try-count 2 after the lift: the scanned source set now includes assistant-turn-footer.tsx alongside message-part/, so removing the footer's try { await navigator.clipboard.writeText } guard still fails the test. turnChangeFiles is the single tag-narrowing point for TurnChangeDisplay.kind; the panel and helpers consume that helper, not the union directly.

Footer form (left-aligned Agent · Model · 8s) is intentionally unchanged in this PR. DESIGN.md L519-520 specifies a different form (right-aligned, completion time only, [Copy] [Fork]) which is a follow-up.

Risk Notes

  • Behaviour change: footer always rendered at turn level (was: nested inside the last text part). User-visible result is identical for single-text-part assistant turns (the dominant case). For turns that end on a tool part, the footer now appears below the panel/tool output instead of below the last text body — this is the intended new layout.
  • Streaming gate (!working()) is preserved so the footer doesn't flash in during partial responses.
  • user-message.tsx is untouched — it already owned its own copy button via renderMetaAndActions and never went through text-part. The dead if (props.message.role !== "assistant") branch in the old text-part footer code is removed alongside the rest.

How To Verify

bun --cwd packages/ui run typecheck             ok
bun --cwd packages/app run typecheck            ok
bun --cwd packages/opencode run typecheck       ok
bun --cwd packages/ui test session-turn         pass
bun --cwd packages/ui test message-part-registry  pass (clipboard hardening scans assistant-turn-footer; try-count stays 2)
bun --cwd packages/opencode test session/turn-change  pass
bun run snap session-turn-changes               pass; grid regenerated; mixed turn asserts panel renders captured row and no uncaptured copy
bun --cwd packages/app run test:e2e:local -- e2e/session/session-turn-footer.spec.ts  2 pass (hover reveal + clipboard copy + prose→panel→footer DOM order)
Manual dev:desktop walk: prose → panel → footer order correct; hover reveals footer; panel margin reads as breathe-tier separator; fully undone turn shows "1 file undone" without the +0 -0 numerics.

Screenshots or Recordings

docs/design/preview/screenshots/session-turn-changes.png (regenerated by bun run snap session-turn-changes):

  • light + dark, each showing applied, undone, and mixed states
  • uncaptured-only slot asserts count(session-turn-changes) === 0 instead of capturing a panel screenshot, because that kind renders no panel

Checklist

  • Type label — this PR carries exactly one of bug, enhancement, task, documentation. Type labels are author-added; the labeler bot does NOT assign them. Add the label in the GitHub UI, then tick this.
  • Routing labels — this PR carries at least one of app, ui, platform, harness, ci. The labeler bot assigns these on PR open based on changed paths. Confirm the bot's choice (or override if wrong), then tick this.
  • Priority label — this PR carries exactly one of P0, P1, P2, P3. The priority-triage bot suggests one on PR open. Confirm or override, then tick this.
  • Human Review Status above is set to Pending, Approved by @<reviewer>, or Not required: <reason> (default is Pending; "not required" is restricted to bot-authored low-risk PRs).
  • I linked the related issue, or stated in Summary why there is no issue.
  • I described the review focus and any meaningful risks.
  • I replaced the example block in How To Verify with the real verification steps and the key result for each.
  • I did not introduce unrelated refactors, dependencies, generated files, or file changes beyond the stated scope.
  • (conditional) I manually checked visible UI or copy changes when needed, with screenshots or recordings. Leave unticked only if no visible UI or copy changed.
  • (conditional) I considered macOS and Windows impact for platform, packaging, updater, signing, paths, shell, or permissions changes. Leave unticked only if no platform/packaging surface was touched.
  • (conditional) I called out docs, release notes, dependencies, permissions, credentials, deletion behavior, generated content, or local file changes when relevant. Leave unticked only if none of those surfaces was touched.
  • I reviewed the final diff for unrelated changes and suspicious dependency changes.
  • I am targeting dev, and my PR title and commit messages use Conventional Commits in English.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added footer component to assistant messages displaying metadata (model, duration) and copy-to-clipboard functionality.
    • Improved session turn changes status display with clearer "undone" state handling.
  • Style

    • Updated styling for session turn components with enhanced visibility and interaction behaviors.
  • Refactor

    • Simplified assistant message rendering and reorganized component prop structure for cleaner architecture.
  • Tests

    • Updated test assertions for clipboard functionality.

Review Change Stack

Three small regressions in the turn changes panel surfaced after PR #704:
- The "Some shell changes were not individually captured" line added
  noise to mixed/uncaptured turns. Removed; the uncaptured kind now
  renders no panel at all.
- Fully-undone turns rendered "0 files changed +0 -0" plus a separate
  "Undone" pill. Replaced with a single "N files undone" summary.
- Panel was glued to surrounding prose with margin-top: 4. Bumped to
  --space-md (12, the breathe tier) so the panel reads as a
  turn-internal side-effect rather than a section switch.

While fixing DOM order so the panel sits above the assistant footer,
the footer's home (inside text-part - a generic part) turned out to be
the root cause. Lifted the footer out:

- text.tsx loses ~60 lines of meta / duration / model / copy / hover
  logic and is now a pure markdown renderer.
- New AssistantTurnFooter component owned by SessionTurn. SessionTurn
  finds the last non-empty assistant text part, passes {message, text}
  to the footer, and renders it after the panel. Gated on !working()
  so no footer appears during streaming.
- The showAssistantCopyPartID / turnDurationMs prop chain (only ever
  fed the embedded footer) is removed from MessagePartProps,
  message-router, AssistantParts, AssistantMessageDisplay, MessageProps.
- Footer keeps hover-only visibility, now gated on the
  session-turn-message-container hover.
- user-message.tsx is untouched; it already owns its own copy button
  independent of text.tsx.

Footer form (left-aligned, agent / model / duration) is intentionally
unchanged here. DESIGN.md L519-520 calls for a different form
(right-aligned, completion time only, [Copy] [Fork]); follow-up.
@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR refactors assistant message footer functionality by extracting copy-to-clipboard and metadata display from individual text parts into a dedicated AssistantTurnFooter component rendered at the session-turn level. It simplifies component prop chains, updates session turn-changes visibility logic to exclude uncaptured turns, introduces "undone" state display for fully restored files, and adds corresponding i18n entries and CSS styling.

Changes

Footer Refactoring and Component Contract Updates

Layer / File(s) Summary
Component prop contract updates
packages/ui/src/components/message-part/registry.ts, packages/ui/src/components/message-part/assistant-message-display.tsx, packages/ui/src/components/message-part/assistant-parts.tsx, packages/ui/src/components/message-part/message-router.tsx
Remove showAssistantCopyPartID and turnDurationMs from MessageProps and MessagePartProps interfaces; remove unused prop forwarding from AssistantMessageDisplay and AssistantParts to Part component. Add new showReasoningSummaries and stateKey props.
AssistantTurnFooter component and initial styling
packages/ui/src/components/assistant-turn-footer.tsx, packages/ui/src/components/session-turn.css
Introduce new AssistantTurnFooter component that computes metadata (model, duration, interrupted state), renders metadata text, and provides a copy-to-clipboard button with transient "copied" tooltip. Add CSS styling for footer visibility (hidden by default), hover/focus reveal behavior, tooltip layout, and interrupted-state layout variations.
SessionTurn footer target selection and rendering
packages/ui/src/components/session-turn.tsx
Update SessionTurn to compute assistantFooterTarget (message + trimmed text) and visibleFooterTarget (visible when not working). Remove showAssistantCopyPartID prop from AssistantParts wiring. Add import and conditional render block for AssistantTurnFooter near the end of turn content.
Simplify TextPartDisplay and message-part styling
packages/ui/src/components/message-part/parts/text.tsx, packages/ui/src/components/message-part.css
Remove metadata computation, copy button, and copied-state logic from TextPartDisplay; retain only streaming and text memos with Markdown rendering. Simplify message-part.css by removing nested text-part-copy-wrapper and text-part-meta rules; keep only the text-part-body margin override.
Update clipboard-writing test assertion
packages/ui/src/components/message-part-registry.test.ts
Adjust hardening test to expect one navigator.clipboard.writeText occurrence instead of two, reflecting copy logic consolidation.

Session Turn-Changes UI and Visibility Logic

Layer / File(s) Summary
Turn-changes visibility logic
packages/ui/src/components/session-turn-changes.ts
Simplify hasVisibleTurnChanges to return false when display is missing/empty/uncaptured; visibility now depends only on files.length > 0 or truncated, not on count values.
Session turn-changes panel undone summary
packages/ui/src/components/session-turn-changes-panel.tsx
Refactor header rendering into a single conditional: when isUndoneTurn() is true, display "undone summary" with count; otherwise show edited summary with additions/deletions. Move omitted-lines notice into the non-undone fallback branch.
Internationalization for undone summaries
packages/ui/src/i18n/en.ts, packages/ui/src/i18n/zh.ts
Add pluralization-aware i18n keys for ui.sessionTurn.turnChanges.undoneSummary.one and .other in English and Chinese. Update key ordering in English dict; remove ui.sessionTurn.turnChanges.uncaptured from Chinese dict.

E2E Test Updates

Layer / File(s) Summary
E2E test snapshot assertions
packages/app/e2e/snap/session-turn-changes.snap.ts
In captured-turn flow, wait for session-turn-changes-undone-summary to be visible before capturing "captured-undone" screenshot. In uncaptured-turn flow, remove screenshot capture and assert that session-turn-changes panel is not rendered.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • Astro-Han/pawwork#823: Changes directly modify Turn Changes UI components (session-turn.css, session-turn-changes.tsx) to adjust layout, header/undone-summary behavior, and footer styling—the same visual and behavioral concerns targeted by this feature issue.

Possibly related PRs

  • Astro-Han/pawwork#676: Both PRs modify the session-turn "turn changes" UI surface, refactoring it into SessionTurnChangesPanel and adjusting its header/visibility logic.
  • Astro-Han/pawwork#611: Both PRs modify packages/ui/src/components/session-turn.tsx; the retrieved PR changes message/assistantMessages data flow while this PR changes how SessionTurn selects content for the new footer component.
  • Astro-Han/pawwork#408: This PR builds directly on the turn-change display types and logic introduced by PR #408, refining hasVisibleTurnChanges and undo/undone header rendering.

Poem

A footer hops down to tell the tale,
Copy-metadata, no detail too small,
TextParts now lean, their burden is gone,
"Undone" summaries greet the dawn—
Refactored with care, the UI stands tall! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main changes: fixing the turn changes panel regressions and lifting the assistant footer to turn level.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The PR description is comprehensive and well-structured, covering all required template sections with clear detail on changes, rationale, verification, and checklist completion.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/change-summary-ui-regressions

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 and usage tips.

@github-actions github-actions Bot added app Application behavior and product flows ui Design system and user interface P2 Medium priority labels May 22, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested priority: P2 (includes non-doc, non-test paths outside the low-risk bucket).

P1/P0 are reserved for maintainer confirmation. Please relabel manually if this is a release blocker, security issue, data-loss risk, or updater/runtime failure.

@Astro-Han Astro-Han added the bug Something isn't working label May 22, 2026

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the assistant message UI by introducing a dedicated AssistantTurnFooter component and consolidating the copy-to-clipboard and metadata display logic. It also updates the session turn changes panel to improve the display of undone turns and removes the 'uncaptured' shell changes indicator. A regression was identified in the footer logic where the 'Copy Response' button now only copies the last text part instead of the entire response; a suggestion was provided to aggregate all text parts across assistant messages.

Comment thread packages/ui/src/components/session-turn.tsx
@github-actions

github-actions Bot commented May 22, 2026

Copy link
Copy Markdown

Perf delta summary

Comparator: pass

Profile / Scenario interaction median interaction worst long task max tbt frame gap p95 frame gap max jank count cls status
default / homepage-cold 24 -> 24 (0) 56 -> 24 (-32) 72 -> 63 (-9) 22 -> 13 (-9) 16.8 -> 16.8 (0) 166.6 -> 166.6 (0) 4 -> 3 (-1) 0 -> 0 (0) pass
default / long-session-input-lag 40 -> 48 (+8) 48 -> 48 (0) 0 -> 0 (0) 0 -> 0 (0) 16.7 -> 16.7 (0) 16.8 -> 16.8 (0) 0 -> 0 (0) 0 -> 0 (0) pass
default / session-streaming-long 40 -> 40 (0) 56 -> 64 (+8) 0 -> 0 (0) 0 -> 0 (0) 16.8 -> 16.8 (0) 33.4 -> 33.3 (-0.1) 0 -> 0 (0) 0 -> 0 (0) pass
default / tool-call-expand 24 -> 24 (0) 24 -> 24 (0) 0 -> 0 (0) 0 -> 0 (0) 16.8 -> 16.8 (0) 16.8 -> 16.8 (0) 0 -> 0 (0) 0 -> 0 (0) pass
default / tool-default-open-heavy-bash 16 -> 16 (0) 16 -> 24 (+8) 61 -> 63 (+2) 11 -> 13 (+2) 50 -> 50 (0) 150 -> 116.6 (-33.4) 3 -> 2 (-1) 0 -> 0 (0) pass
default / terminal-side-panel-open 48 -> 48 (0) 48 -> 56 (+8) 0 -> 0 (0) 0 -> 0 (0) 33.3 -> 33.4 (+0.1) 33.3 -> 33.4 (+0.1) 0 -> 0 (0) 0 -> 0 (0) pass
default / session-scroll-reading 40 -> 40 (0) 40 -> 56 (+16) 0 -> 0 (0) 0 -> 0 (0) 33.2 -> 33.4 (+0.2) 33.2 -> 33.4 (+0.2) 0 -> 0 (0) 0 -> 0 (0) pass
low-end / session-scroll-reading-long 0 -> 0 (0) 112 -> 120 (+8) 86 -> 73 (-13) 105 -> 93 (-12) 33.4 -> 33.4 (0) 100 -> 133.3 (+33.3) 7 -> 8 (+1) 0.011 -> 0.011 (0) pass
low-end / session-timeline-recompute 32 -> 40 (+8) 48 -> 40 (-8) 0 -> 0 (0) 0 -> 0 (0) 33.3 -> 33.3 (0) 33.4 -> 33.4 (0) 0 -> 0 (0) 0.214 -> 0.214 (0) pass
low-end / concurrent-shimmer-extreme 0 -> 0 (0) 0 -> 0 (0) 0 -> 0 (0) 0 -> 0 (0) 16.8 -> 16.8 (0) 16.8 -> 16.8 (0) 0 -> 0 (0) 0 -> 0 (0) pass

@Astro-Han

Copy link
Copy Markdown
Owner Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/ui/src/components/session-turn-changes-panel.tsx`:
- Around line 120-124: The omitted-lines notice is rendered outside the
isUndoneTurn() conditional so it appears for both undone and non-undone turns;
move the Show block that renders the omitted notice (the span with
data-slot="session-turn-changes-omitted" and the i18n.t call using
props.turnChange.omittedCount) into the non-undone fallback branch of the Show
that checks isUndoneTurn(props.turnChange) so it only displays when the turn is
not undone.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: ace4e5e1-2733-4525-ab04-4403e4588b62

📥 Commits

Reviewing files that changed from the base of the PR and between e7e3a23 and 9dd4671.

📒 Files selected for processing (15)
  • packages/app/e2e/snap/session-turn-changes.snap.ts
  • packages/ui/src/components/assistant-turn-footer.tsx
  • packages/ui/src/components/message-part-registry.test.ts
  • packages/ui/src/components/message-part.css
  • packages/ui/src/components/message-part/assistant-message-display.tsx
  • packages/ui/src/components/message-part/assistant-parts.tsx
  • packages/ui/src/components/message-part/message-router.tsx
  • packages/ui/src/components/message-part/parts/text.tsx
  • packages/ui/src/components/message-part/registry.ts
  • packages/ui/src/components/session-turn-changes-panel.tsx
  • packages/ui/src/components/session-turn-changes.ts
  • packages/ui/src/components/session-turn.css
  • packages/ui/src/components/session-turn.tsx
  • packages/ui/src/i18n/en.ts
  • packages/ui/src/i18n/zh.ts
💤 Files with no reviewable changes (5)
  • packages/ui/src/components/message-part/message-router.tsx
  • packages/ui/src/components/message-part.css
  • packages/ui/src/components/message-part/assistant-parts.tsx
  • packages/ui/src/components/message-part/registry.ts
  • packages/ui/src/components/message-part/assistant-message-display.tsx

Comment thread packages/ui/src/components/session-turn-changes-panel.tsx
Astro-Han added 4 commits May 22, 2026 22:12
Adds e2e regression coverage for the lifted AssistantTurnFooter: hover-only
reveal, clipboard copy of the trimmed assistant text, and DOM ordering below
the turn changes panel in a mixed text+patch turn.
… helper

Introduces a tag-aware turnChangeFiles helper so that downstream UI code
(panel, hasVisibleTurnChanges, turnChangeAction) no longer pattern-matches
on TurnChangeDisplay.kind directly. Future file-bearing variants only need
to opt in inside this helper.
Adds a mixed-kind fixture pass to session-turn-changes snap: apply_patch +
shell touch in the same turn. Asserts the panel renders the captured file
row while the uncaptured diagnostic copy stays suppressed, pinning the
no-"未逐个捕获" rule against regression.
The clipboard try/catch contract moved out of message-part/ when the footer
was lifted to turn level, so the hardening grep no longer scanned the file
that actually holds the guard. Pulls assistant-turn-footer.tsx into the
hardening source set and bumps the expected match count to 2.
@Astro-Han
Astro-Han merged commit d28cbc6 into dev May 22, 2026
28 checks passed
@Astro-Han
Astro-Han deleted the claude/change-summary-ui-regressions branch May 22, 2026 14:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app Application behavior and product flows bug Something isn't working P2 Medium priority ui Design system and user interface

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant