From 333948711d347973640211e236463a79cd5459c9 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Mon, 24 Nov 2025 20:35:51 -0600 Subject: [PATCH] fix(desktop): content animations --- packages/ui/src/components/session-turn.css | 94 ++++++++++++++++++++- packages/ui/src/components/session-turn.tsx | 37 +++++--- packages/ui/src/hooks/create-seen.ts | 20 ----- packages/ui/src/hooks/index.ts | 1 - 4 files changed, 118 insertions(+), 34 deletions(-) delete mode 100644 packages/ui/src/hooks/create-seen.ts diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css index 1dfb54c56..d2a3d618a 100644 --- a/packages/ui/src/components/session-turn.css +++ b/packages/ui/src/components/session-turn.css @@ -101,7 +101,99 @@ } &[data-fade="true"] > * { - animation: fade-up-text 0.3s ease-out forwards; + animation: fadeUp 0.4s ease-out forwards; + opacity: 0; + + &:nth-child(1) { + animation-delay: 0.1s; + } + &:nth-child(2) { + animation-delay: 0.2s; + } + &:nth-child(3) { + animation-delay: 0.3s; + } + &:nth-child(4) { + animation-delay: 0.4s; + } + &:nth-child(5) { + animation-delay: 0.5s; + } + &:nth-child(6) { + animation-delay: 0.6s; + } + &:nth-child(7) { + animation-delay: 0.7s; + } + &:nth-child(8) { + animation-delay: 0.8s; + } + &:nth-child(9) { + animation-delay: 0.9s; + } + &:nth-child(10) { + animation-delay: 1s; + } + &:nth-child(11) { + animation-delay: 1.1s; + } + &:nth-child(12) { + animation-delay: 1.2s; + } + &:nth-child(13) { + animation-delay: 1.3s; + } + &:nth-child(14) { + animation-delay: 1.4s; + } + &:nth-child(15) { + animation-delay: 1.5s; + } + &:nth-child(16) { + animation-delay: 1.6s; + } + &:nth-child(17) { + animation-delay: 1.7s; + } + &:nth-child(18) { + animation-delay: 1.8s; + } + &:nth-child(19) { + animation-delay: 1.9s; + } + &:nth-child(20) { + animation-delay: 2s; + } + &:nth-child(21) { + animation-delay: 2.1s; + } + &:nth-child(22) { + animation-delay: 2.2s; + } + &:nth-child(23) { + animation-delay: 2.3s; + } + &:nth-child(24) { + animation-delay: 2.4s; + } + &:nth-child(25) { + animation-delay: 2.5s; + } + &:nth-child(26) { + animation-delay: 2.6s; + } + &:nth-child(27) { + animation-delay: 2.7s; + } + &:nth-child(28) { + animation-delay: 2.8s; + } + &:nth-child(29) { + animation-delay: 2.9s; + } + &:nth-child(30) { + animation-delay: 3s; + } } } diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index 6e5e74fad..a7bd456a4 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -2,7 +2,7 @@ import { AssistantMessage } from "@opencode-ai/sdk" import { useData } from "../context" import { Binary } from "@opencode-ai/util/binary" import { getDirectory, getFilename } from "@opencode-ai/util/path" -import { createEffect, createMemo, createSignal, For, Match, ParentProps, Show, Switch } from "solid-js" +import { createEffect, createMemo, createSignal, For, Match, onMount, ParentProps, Show, Switch } from "solid-js" import { DiffChanges } from "./diff-changes" import { Typewriter } from "./typewriter" import { Message } from "./message-part" @@ -55,10 +55,11 @@ export function SessionTurn(
{(msg) => { - const titleSeen = createMemo(() => true) - const contentSeen = createMemo(() => true) + const titleKey = `app:seen:session:${props.sessionID}:${msg().id}:title` + const contentKey = `app:seen:session:${props.sessionID}:${msg().id}:content` const [detailsExpanded, setDetailsExpanded] = createSignal(false) - const [titled, setTitled] = createSignal(titleSeen()) + const [titled, setTitled] = createSignal(true) + const [faded, setFaded] = createSignal(true) const assistantMessages = createMemo(() => { return messages()?.filter((m) => m.role === "assistant" && m.parentID == msg().id) as AssistantMessage[] @@ -68,7 +69,7 @@ export function SessionTurn( const parts = createMemo(() => data.part[msg().id]) const lastTextPart = createMemo(() => assistantMessageParts() - .filter((p) => p.type === "text") + .filter((p) => p?.type === "text") ?.at(-1), ) const hasToolPart = createMemo(() => assistantMessageParts().some((p) => p?.type === "tool")) @@ -79,11 +80,23 @@ export function SessionTurn( const lastTextPartShown = createMemo(() => !msg().summary?.body && (lastTextPart()?.text?.length ?? 0) > 0) // allowing time for the animations to finish - createEffect(() => { - if (titleSeen()) return - const title = msg().summary?.title - if (title) setTimeout(() => setTitled(true), 10_000) + onMount(() => { + const titleSeen = sessionStorage.getItem(titleKey) === "true" + const contentSeen = sessionStorage.getItem(contentKey) === "true" + + if (!titleSeen) { + setTitled(false) + const title = msg().summary?.title + if (title) setTimeout(() => setTitled(true), 10_000) + setTimeout(() => sessionStorage.setItem(titleKey, "true"), 1000) + } + + if (!contentSeen) { + setFaded(false) + setTimeout(() => sessionStorage.setItem(contentKey, "true"), 1000) + } }) + createEffect(() => { const completed = !messageWorking() setTimeout(() => setCompleted(completed), 1200) @@ -120,7 +133,7 @@ export function SessionTurn( )} @@ -201,14 +214,14 @@ export function SessionTurn( const parts = createMemo(() => data.part[assistantMessage.id]) const last = createMemo(() => parts() - .filter((p) => p.type === "text") + .filter((p) => p?.type === "text") .at(-1), ) if (lastTextPartShown() && lastTextPart()?.id === last()?.id) { return ( p.id !== last()?.id)} + parts={parts().filter((p) => p?.id !== last()?.id)} /> ) } diff --git a/packages/ui/src/hooks/create-seen.ts b/packages/ui/src/hooks/create-seen.ts deleted file mode 100644 index c25726e75..000000000 --- a/packages/ui/src/hooks/create-seen.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { createSignal, onCleanup, onMount } from "solid-js" -import { isServer } from "solid-js/web" - -export function createSeen(key: string, delay = 1000) { - // 1. Initialize state based on storage (default to true on server to avoid flash) - const storageKey = `app:seen:${key}` - const [hasSeen] = createSignal(!isServer && sessionStorage.getItem(storageKey) === "true") - - onMount(() => { - // 2. If we haven't seen it, mark it as seen for NEXT time - if (!hasSeen()) { - const timer = setTimeout(() => { - sessionStorage.setItem(storageKey, "true") - }, delay) - onCleanup(() => clearTimeout(timer)) - } - }) - - return hasSeen -} diff --git a/packages/ui/src/hooks/index.ts b/packages/ui/src/hooks/index.ts index 98b339cfa..7eef78091 100644 --- a/packages/ui/src/hooks/index.ts +++ b/packages/ui/src/hooks/index.ts @@ -1,2 +1 @@ export * from "./use-filtered-list" -export * from "./create-seen"