mirror of
https://github.com/anomalyco/opencode.git
synced 2026-04-24 06:45:22 +00:00
fix(ui): cache reduced-motion query and clean up deferRender timers
- Add module-level prefersReducedMotion signal in message-part.tsx, replacing per-mount window.matchMedia calls in useToolFade - Cancel pending rAF/setTimeout in session.tsx deferRender when session key changes rapidly, and clean up on unmount
This commit is contained in:
@@ -38,6 +38,7 @@ import { createScrollSpy } from "@/pages/session/scroll-spy"
|
||||
import { SessionReviewTab, type DiffStyle, type SessionReviewTabProps } from "@/pages/session/review-tab"
|
||||
import { TerminalPanel } from "@/pages/session/terminal-panel"
|
||||
import { MessageTimeline } from "@/pages/session/message-timeline"
|
||||
import { AnimationDebugPanel } from "@opencode-ai/ui/animation-debug-panel"
|
||||
import { useSessionCommands } from "@/pages/session/use-session-commands"
|
||||
import { SessionComposerRegion, createSessionComposerState } from "@/pages/session/composer"
|
||||
import { SessionMobileTabs } from "@/pages/session/session-mobile-tabs"
|
||||
@@ -419,16 +420,28 @@ export default function Page() {
|
||||
deferRender: false,
|
||||
})
|
||||
|
||||
let deferFrame: number | undefined
|
||||
let deferTimer: ReturnType<typeof setTimeout> | undefined
|
||||
createComputed((prev) => {
|
||||
const key = sessionKey()
|
||||
if (key !== prev) {
|
||||
if (deferFrame !== undefined) cancelAnimationFrame(deferFrame)
|
||||
if (deferTimer !== undefined) clearTimeout(deferTimer)
|
||||
setStore("deferRender", true)
|
||||
requestAnimationFrame(() => {
|
||||
setTimeout(() => setStore("deferRender", false), 0)
|
||||
deferFrame = requestAnimationFrame(() => {
|
||||
deferFrame = undefined
|
||||
deferTimer = setTimeout(() => {
|
||||
deferTimer = undefined
|
||||
setStore("deferRender", false)
|
||||
}, 0)
|
||||
})
|
||||
}
|
||||
return key
|
||||
}, sessionKey())
|
||||
onCleanup(() => {
|
||||
if (deferFrame !== undefined) cancelAnimationFrame(deferFrame)
|
||||
if (deferTimer !== undefined) clearTimeout(deferTimer)
|
||||
})
|
||||
|
||||
const turnDiffs = createMemo(() => lastUserMessage()?.summary?.diffs ?? [])
|
||||
const reviewDiffs = createMemo(() => (store.changes === "session" ? diffs() : turnDiffs()))
|
||||
@@ -1162,6 +1175,7 @@ export default function Page() {
|
||||
|
||||
return (
|
||||
<div class="relative bg-background-base size-full overflow-hidden flex flex-col">
|
||||
{import.meta.env.DEV && <AnimationDebugPanel />}
|
||||
<SessionHeader />
|
||||
<div class="flex-1 min-h-0 flex flex-col md:flex-row">
|
||||
<SessionMobileTabs
|
||||
|
||||
@@ -50,7 +50,7 @@ import { list } from "./text-utils"
|
||||
import { AnimatedCountList } from "./tool-count-summary"
|
||||
import { ToolStatusTitle } from "./tool-status-title"
|
||||
import { GrowBox } from "./grow-box"
|
||||
import { animate, type AnimationPlaybackControls, clearFadeStyles, clearMaskStyles, FADE_SPRING, WIPE_MASK } from "./motion"
|
||||
import { animate, type AnimationPlaybackControls, clearFadeStyles, clearMaskStyles, GROW_SPRING, WIPE_MASK } from "./motion"
|
||||
|
||||
interface Diagnostic {
|
||||
range: {
|
||||
@@ -293,6 +293,14 @@ const pageVisible = /* @__PURE__ */ (() => {
|
||||
return visible
|
||||
})()
|
||||
|
||||
const prefersReducedMotion = /* @__PURE__ */ (() => {
|
||||
if (typeof window === "undefined") return () => false
|
||||
const mql = window.matchMedia("(prefers-reduced-motion: reduce)")
|
||||
const [reduced, setReduced] = createSignal(mql.matches)
|
||||
mql.addEventListener("change", () => setReduced(mql.matches))
|
||||
return reduced
|
||||
})()
|
||||
|
||||
function createGroupOpenState() {
|
||||
const [state, setState] = createSignal<Record<string, boolean>>({})
|
||||
const read = (key?: string, collapse?: boolean) => {
|
||||
@@ -1496,7 +1504,7 @@ function useToolFade(
|
||||
|
||||
const el = ref()
|
||||
if (!el || typeof window === "undefined") return
|
||||
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return
|
||||
if (prefersReducedMotion()) return
|
||||
|
||||
const mask =
|
||||
wipe &&
|
||||
@@ -1529,10 +1537,10 @@ function useToolFade(
|
||||
? animate(
|
||||
node,
|
||||
{ opacity: 1, filter: "blur(0px)", transform: "translateX(0)", maskPosition: "0% 0%" },
|
||||
{ ...FADE_SPRING, delay },
|
||||
{ ...GROW_SPRING, delay },
|
||||
)
|
||||
: animate(node, { opacity: 1, filter: "blur(0px)", transform: "translateX(0)" }, { ...FADE_SPRING, delay })
|
||||
: animate(node, { opacity: 1, filter: "blur(0px)", transform: "translateY(0)" }, { ...FADE_SPRING, delay })
|
||||
: animate(node, { opacity: 1, filter: "blur(0px)", transform: "translateX(0)" }, { ...GROW_SPRING, delay })
|
||||
: animate(node, { opacity: 1, filter: "blur(0px)", transform: "translateY(0)" }, { ...GROW_SPRING, delay })
|
||||
|
||||
anim?.finished.then(() => {
|
||||
const value = ref()
|
||||
|
||||
Reference in New Issue
Block a user