refactor(desktop): GSAP-driven animation — scroll, collapse, entrance, approval#4225
Merged
esengine merged 12 commits intoJun 13, 2026
Merged
Conversation
added 11 commits
June 13, 2026 02:18
- Install gsap + @gsap/react with Flip and ScrollToPlugin - Create useGSAPCollapse hook (dynamic height tween, replaces max-height hacks) - Create useScrollManager (GSAP ScrollToPlugin, replaces rAF scrollTop thrash) - Create useEntranceAnimation (fadeIn+slideUp stagger for new items) - Unify tool__body / reasoning__body / readonly-batch__body / turn-collapse__body collapse: all use GSAP height animation instead of max-height:5000px - Fix AssistantMessage reasoning auto-close race condition (userOverridden ref) - WarmTurnCard: GSAP collapse animation for expand/collapse - Markdown: O(depth) deepestLastInlineElement replaces O(n) TreeWalker, skip cursor inject when already positioned correctly - CSS: remove all max-height transition blocks, add overflow:hidden where needed - All animations respect prefers-reduced-motion
…ed fold borders - useEntranceAnimation: pre-seed seen set on first mount so history items never animate; add deps parameter so querySelectorAll only runs when items.length changes (not on every streaming token) - useGSAPCollapse: skip gsap.set on initial mount for elements that default to collapsed (already handled — no change needed) - Unify border-left + padding across reasoning__body, tool__body, readonly-batch__body, turn-collapse__body for consistent visual hierarchy - tool--open class drives expanded padding for tool__body matching the readonly-batch / turn-collapse pattern
Before: hot zone (30 turns) + warm zone cards ALL render synchronously in one frame. For sessions with 200+ turns this blocks the UI for hundreds of milliseconds. Now: Phase 0 (frame 1): only latest 5 turns — instant paint Phase 1 (rAF): full hot zone (up to 30 turns) Phase 2 (+80ms): warm zone collapsed cards - effectiveHotStart replaces hotStartIdx during phase 0 - showWarmZone gates WarmZone rendering until phase 2 - useEntranceAnimation: pre-seed seen set on mount, add deps parameter so querySelectorAll only runs when items.length changes
When intermediate steps (tools, phases) complete in under 500 ms, rendering them as a collapsible 'processed' card wastes space and adds unnecessary UI complexity. Now they render inline as individual ToolCard / PhaseCard elements instead.
…ames" This reverts commit 2b84236.
…-pass entrance scan
GSAP Performance skill analysis found two bottlenecks in the initial render
path for long sessions (500+ items):
1. useGSAPCollapse called gsap.set(el, { height: 0/auto }) on mount for
every collapsed element (~400 calls). While each is sub-ms, the
cumulative GSAP property-resolution and style-invalidation cost delays
first paint. Replaced with el.style.height = '0px' — the element's
initial collapsed state is already guaranteed by overflow:hidden in CSS.
2. useEntranceAnimation used two separate useEffect calls (mount-only
pre-seed + deps-driven scan), each running querySelectorAll over the
entire DOM subtree. Merged into a single effect with a firstRun flag:
first call pre-seeds the seen set (no animation), subsequent calls only
scan for genuinely new elements.
…500 ms)" This reverts commit 796f604.
…content useEffect fires AFTER paint, so every collapsible card rendered its full content for one frame before height=0 took effect — visible as a 'flash open then collapse' on initial load and session switch. useLayoutEffect fires synchronously after React commit, BEFORE the browser paints. The initial style.height = '0px' is applied before the first frame, so content starts collapsed — no flash. For toggle animations, useLayoutEffect also means the first frame of the GSAP fromTo tween is painted immediately (no 1-frame delay), making the animation feel more responsive.
- Replace gsap.to(scrollTo) during streaming with direct scrollTop=scrollHeight,
eliminating 120ms tween kill/restart jitter on every token
- useEntranceAnimation: add resetKey param to clear seen set + firstRun on
session switch, preventing 500-history-item entrance animations
- Transcript: pass sessionKey to useEntranceAnimation; remove unused
scrollToBottom from useScrollManager destructuring
- ApprovalModal: key={approval.id} fixes React instance reuse bug that
blocked subsequent approval cards; GSAP exit animation (opacity+y, 120ms)
replaces jarring instant-removal pop
- displayMode: removed 'minimal' option (was identical to compact), now only standard | compact. Default changed from minimal to compact. - expandThinking: removed the setting entirely. Thinking process now always auto-opens while streaming and auto-closes on completion. Manual toggle is preserved — user expansion is never force-closed. - Cleaned up associated UI, types, bridge bindings, and locale strings.
When WarmTurnCard closes, React swaps expanded children → preview BEFORE useGSAPCollapse measures scrollHeight, so the collapse tween started from ~40px (preview) instead of the full expanded height. Fix: render both preview and body always (display:none toggle), capture scrollHeight before the swap in the click handler, pass it to useGSAPCollapse via a new prevHeight option so the close tween starts from the correct (expanded) height.
esengine
added a commit
that referenced
this pull request
Jun 13, 2026
…l, dead code (#4238) * fix(desktop): map legacy "minimal" display mode to compact #4225 removed the minimal mode (it was identical to compact) but left existing users stranded: DesktopDisplayMode() still returned "minimal" for a persisted toml, which the frontend now rejects, so those users fell back to standard (verbose) instead of the equivalent compact. Normalize the legacy value to compact on both the Go read path and the localStorage hydrate path. * fix(desktop): drop scroll-behavior:smooth fighting instant streaming scroll The transcript pins to the bottom during streaming via a direct scrollTop = scrollHeight write, which #4225 documents as instant to avoid the perpetual tween-chase jitter. A global scroll-behavior:smooth on .transcript turns that write into an animated scroll in Chromium, reintroducing the lag it set out to remove. The two call sites that want smooth (jump-bar, warm-turn expand) already pass behavior explicitly. * refactor(desktop): drop dead code left by the GSAP animation merge - remove the no-op footer-height effect (empty if body, unused ref) - drop the constant `false` from the hot-zone useMemo deps - delete unused DUR_SLOWER/EASE_IN_OUT/EASE_SCROLL exports - correct stale "minimal mode" comments and the entrance-hook usage doc
CVEngineer66
pushed a commit
to CVEngineer66/DeepSeek-Reasonix
that referenced
this pull request
Jun 13, 2026
…ng scroll, dead code (esengine#4238) * fix(desktop): map legacy "minimal" display mode to compact esengine#4225 removed the minimal mode (it was identical to compact) but left existing users stranded: DesktopDisplayMode() still returned "minimal" for a persisted toml, which the frontend now rejects, so those users fell back to standard (verbose) instead of the equivalent compact. Normalize the legacy value to compact on both the Go read path and the localStorage hydrate path. * fix(desktop): drop scroll-behavior:smooth fighting instant streaming scroll The transcript pins to the bottom during streaming via a direct scrollTop = scrollHeight write, which esengine#4225 documents as instant to avoid the perpetual tween-chase jitter. A global scroll-behavior:smooth on .transcript turns that write into an animated scroll in Chromium, reintroducing the lag it set out to remove. The two call sites that want smooth (jump-bar, warm-turn expand) already pass behavior explicitly. * refactor(desktop): drop dead code left by the GSAP animation merge - remove the no-op footer-height effect (empty if body, unused ref) - drop the constant `false` from the hot-zone useMemo deps - delete unused DUR_SLOWER/EASE_IN_OUT/EASE_SCROLL exports - correct stale "minimal mode" comments and the entrance-hook usage doc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
概述 / Summary
将桌面端聊天面板的动画系统全面迁移到 GSAP 驱动:用精确的高度 tween 替代 CSS
max-heighthack,消除流式滚动的抖动,为审批卡片添加平滑的进场/退场动画。同时清理了废弃的minimal展示模式和expandThinking设置。Migrates the desktop chat transcript's animation to GSAP: replaces CSS
max-heighthacks with precise height tweens, eliminates streaming scroll jitter, adds smooth enter/exit transitions for approval cards, and removes the obsoleteminimaldisplay mode andexpandThinkingsetting.改动内容 / What changed
1. GSAP 折叠系统 / GSAP collapse system (
useGSAPCollapse)用统一的
useGSAPCollapsehook 替换所有max-height: 0 → 5000px的 CSS 过渡。通过 GSAP 精确测量scrollHeight并 tweenheight属性。使用useLayoutEffect在浏览器 paint 之前设置初始折叠状态,消除"闪开"帧。WarmTurnCard 关闭时通过prevHeight选项捕获展开高度后再收缩。Replaces all CSS
max-height: 0 → 5000pxtransitions on collapsible sections (reasoning, tool cards, read-only batches, turn collapse) with a single hook that measuresscrollHeightprecisely and tweensheightvia GSAP. UsesuseLayoutEffectto set the initial collapsed state before browser paint. WarmTurnCard captures expanded height before DOM swap viaprevHeightoption.2. 流式滚动:即时到位 / Streaming scroll: instant, not GSAP tween
旧代码每个 token 都运行
gsap.to(el, { scrollTo, duration: 0.12 }),造成永无止境的 kill/restart 循环。现在流式期间改用el.scrollTop = el.scrollHeight直接赋值。onScroll能区分内容增长(工具调用 output 到达使 scrollHeight 被动变大)和用户主动上滚,避免工具调用展开时误杀自动滚动。Replaced
gsap.to(scrollTo)during streaming with directscrollTopassignment — the old 120ms tween was perpetually killed/restarted by new tokens.onScrolldistinguishes content growth (tool output arriving pushesscrollHeightup) from user-initiated scroll-up by trackingscrollHeightdeltas.3. 入口动画批处理 / Entrance animation batching (
useEntranceAnimation)seenID 集合,历史条目不执行入口动画resetKey参数:切换会话时重置seen+firstRun,避免新会话的 500 条历史全部逐条 fadeIn+slideUp(≈20 秒动画)useEffect(原来是两个,各跑一次querySelectorAll扫描整个 DOM 子树)Pre-seeds
seenID set on first mount so history items never animate.resetKeyparameter clearsseen+firstRunon session switch, preventing ~20 seconds of cascading entrance animations on 500+ items. Merged into a singleuseEffect— removed the duplicatequerySelectorAllscan.4. 审批卡片退场动画 / Approval card exit animation
模型同时创建多个文件等待审批时,卡片之前是生硬消失。现在
ApprovalModal使用gsap.to(el, { opacity: 0, y: 8, duration: 0.12 })退场动画后再调用回答回调。添加key={approval.id}修复 React 实例复用 bug——第二张卡继承了第一张卡的closingRef导致后续点击被拦截。Smooth exit animation (opacity + translateY, 120ms) replaces jarring instant removal when model creates multiple files requiring approval.
key={approval.id}fixes React instance reuse where the second card inherited a staleclosingRef.5. Markdown 光标优化 / Markdown cursor optimization
injectStreamingCursor用deepestLastInlineElement(O(depth)) 替代TreeWalker(O(nodes)),光标已在正确位置时跳过整个 DOM 写入。Replaced
TreeWalker(O(nodes)) withdeepestLastInlineElement(O(depth)). Skips DOM mutation entirely when cursor is already correctly positioned.6. 删除
minimal展示模式 / Removed minimal display modeminimal模式在代码中与compact完全等价。从类型系统、SettingsPanel UI、bridge、types 和三种语言文件中移除。默认值改为compact。The
minimalmode was identical tocompactin code. Removed from type system, SettingsPanel UI, bridge bindings, types, and all 3 locale files. Default switched tocompact.7. 删除
expandThinking设置 / Removed expandThinking setting思考过程现在硬编码为"运行中展开、完成折叠"(
AssistantMessage内部逻辑不变)。用户手动展开不会被强制关闭。从 App.tsx、Transcript、SettingsPanel、bridge、types 和语言文件中移除。Hardcoded: always auto-open while streaming, auto-close on completion. Manual toggle is preserved and never force-closed. Removed from 9 files.
8. JumpBar 性能优化 / JumpBar performance
markerCache缓存元素位置,仅在 questions 变化或.jump-scroll滚动时重建mousemove不再每次调用getBoundingClientRect(避免 layout thrashing)setHovered仅在目标问题变化时触发 React 重渲染onClick恢复单击跳转barTop每次调用实时读取 DOM(nav 是 sticky,位置随 transcript 滚动变化)Position cache eliminates
getBoundingClientRecton every mousemove.setHoveredonly triggers React re-render when the target question changes.onClickrestored for single-click jump. FreshbarTopeach call for sticky nav.9. 移除
useScrollManager/ Removed useScrollManager滚动逻辑从独立模块内联回 Transcript。复杂度从 130 行的独立 hook 降为直接
scrollTop = scrollHeight+useLayoutEffect。Scrolling logic inlined from a separate 130-line module into Transcript. Simpler, fewer cross-module deps.
已知遗留 / Known Caveats
ExpandThinking(settings_app.go:158),前端忽略,无功能影响TurnCollapse/ToolCard/ReadOnlyBatch改为始终渲染 children(GSAP 测量需要),VDOM 树略大,实测 500 条会话无性能影响