Skip to content

refactor(ui): stop animating messages, tab switches, and keyboard search - #329

Merged
gustmrg merged 1 commit into
mainfrom
refactor/remove-excess-motion
Nov 1, 2026
Merged

gustmrg merged 1 commit into
mainfrom
refactor/remove-excess-motion

Conversation

@gustmrg

@gustmrg gustmrg commented Oct 31, 2026 •

Copy link
Copy Markdown
Owner

Motion had built up on the surfaces people use most, and at that frequency it reads as delay. Switching conversations replayed an entrance on every visible message, tabs and settings sections slid on each switch, and search animated open on Ctrl+K. Those now change instantly. Menus and the details panel still fade, so they don't pop in, but they no longer move or scale.

Every animation went through one test: an animation stays only if it has a purpose and runs rarely enough to keep it. The reason for each change is below.

Removed

What Where Why
Message entrance (160ms fade and rise) message-view.tsx, .message-steps in aura.css Seen 100+ times a day. Rows are keyed by message, so opening a conversation replayed it across the whole transcript.
Tab content slide wisp-details.tsx, circle-details.tsx Core navigation, and repeated tab switches read as lag.
Sliding tab and segmented control indicator ui/tabs.tsx, ui/segmented-control.tsx Same navigation class. Content now changes instantly, so a 200ms glide would lag behind it.
Search open and close, including the backdrop search-dialog.tsx Opened by Ctrl+K. A keyboard action should answer immediately.
Composer + menu open and close chat-composer.tsx Also opened by Ctrl+Shift+Enter, and used while typing.
Usage report slide on period change usage-settings-section.tsx Functional data the person is reading. The slide says nothing about the values.
Settings section slide 10 settings sections, connections-panel.tsx No purpose: it always slid forward, even when going back.

Reduced

What Now Why
Dropdown, Popover, Select, Context menu, Tooltip Fade only, no zoom or slide Seen tens of times a day. The fade alone prevents a jarring pop.
Details panel and Wisp profile (panel-in) Fade only, 150ms (was 170ms with an 8px slide) Opened often, and the fade carries the same meaning.
Avatar color swatch hover Opacity, 100ms (was scale-[1.08]) Scale added nothing that opacity doesn't.
Connection progress bar 250ms (was 500ms) Over the 300ms budget for UI motion.
Button and TabsTab transition-colors (was transition-all) Press offset and focus ring now respond instantly.

Cleanup

  • Removed the unused --animate-working-pulse token and working-pulse keyframes.
  • Removed the placed ref in SegmentedControl, which only skipped the first slide.
  • Removed slideDirection and slide state, and the key={report.period} that existed only to replay the slide. The report's children hold no state.
  • Removed origin-(--transform-origin) from surfaces that no longer scale, the Radix-only data-[state=delayed-open] classes on Tooltip, and a stale transition: none on the phone's .workspace-sidebar.

Kept on purpose

  • The connection flow's forward and back slides (connection-gate.tsx, connection-screen.tsx, ssh-prompt-dialog.tsx): rare, and they show direction.
  • message-highlight: it shows where a search jump landed.
  • Working indicators (halo, subline pulse, typing dots, step spinner, Wisp sway) and the recording pulse: they show state and stop when the work does.
  • The Wisp's idle blink: part of its character, and it comes from the design studies in docs/design/.
  • First-time and occasional moments: the empty conversation intro, the model step in onboarding, regular dialogs, the update toast, and the splash fade.
  • Hover and focus color changes of 150ms or less, chevron rotation, the toggle switch, and the accordion.

Testing

  • npm run typecheck, npm run lint, npm run format:check, and npm run build pass. Lint reports 2 warnings that are already on main, in the storage settings components.
  • npm test on Node 22: 2443 tests pass. No new tests, since this only removes motion and jsdom can't observe it.
  • tsc output was rebuilt from a clean dist-electron.
  • Built app with the fake agent, measured over DevTools:
    • Reopening a conversation with 7 messages runs 0 message-in animations.
    • Ctrl+K search and its backdrop report animation-name: none, and search unmounts straight away on Escape.
    • The composer + menu reports animation-name: none.
    • A context menu reports --tw-enter-scale: 1, so it only fades.
    • The details panel's panel-in animates only opacity for 150ms.
    • Tab switches run only a text color transition, and the tab indicator has no transition.

To check by hand before merging:

  • Hover and speed feel right on a real display: switching conversations and tabs, opening menus and tooltips, and the color swatches in the avatar editor.
  • The phone layout works on a device: the list, a conversation, and the Wisp profile, which now only fades in.

Not in this PR

Tracked in SWE-268:

  • A working Wisp can show several indicators at once (subline pulse, typing dots, step spinner, Wisp sway). They may be redundant, but they are state indicators, so that is a design call.
  • The usage skeleton pulse may flash when a local report loads quickly. That needs timing on a real machine before removing it.

Refs SWE-268

Motion had built up on the surfaces people use most. Every message
faded and rose as it mounted, so switching conversations replayed the
entrance across the whole transcript. Tabs and settings sections slid
on each switch, and search opened with an animation on Ctrl+K. At that
frequency, motion reads as delay.

Remove the animations that have no purpose or run too often to keep
one, and reduce those that still help to their smallest useful form:
menus, popovers, selects, tooltips, and the details panel now only
fade. Buttons and tabs list the properties they transition instead of
using transition-all.

Long-running states, the connection flow, the message highlight after
a search jump, and the Wisp's own movement stay. They explain state or
place, and they are rare or end when the work does.
@gustmrg
gustmrg force-pushed the refactor/remove-excess-motion branch from 831b124 to 36675ca Compare November 1, 2026 03:13
@gustmrg
gustmrg merged commit 1071d40 into main Nov 1, 2026
1 check passed
@gustmrg
gustmrg deleted the refactor/remove-excess-motion branch November 1, 2026 03:17
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.

1 participant