From 7811e01c8efc57d56b91547463c707baf2eb6815 Mon Sep 17 00:00:00 2001 From: Adam <2363879+adamdotdevin@users.noreply.github.com> Date: Sun, 18 Jan 2026 05:26:24 -0600 Subject: fix(app): new layout improvements --- packages/ui/src/components/message-part.tsx | 35 +++++- packages/ui/src/components/session-turn.css | 95 +++++++++++++--- packages/ui/src/components/session-turn.tsx | 161 +++++++--------------------- 3 files changed, 152 insertions(+), 139 deletions(-) (limited to 'packages/ui/src') diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx index 47403786b..b3fd01c2d 100644 --- a/packages/ui/src/components/message-part.tsx +++ b/packages/ui/src/components/message-part.tsx @@ -46,6 +46,7 @@ import { checksum } from "@opencode-ai/util/encode" import { Tooltip } from "./tooltip" import { IconButton } from "./icon-button" import { createAutoScroll } from "../hooks" +import { createResizeObserver } from "@solid-primitives/resize-observer" interface Diagnostic { range: { @@ -297,6 +298,23 @@ export function AssistantMessageDisplay(props: { message: AssistantMessage; part export function UserMessageDisplay(props: { message: UserMessage; parts: PartType[] }) { const dialog = useDialog() const [copied, setCopied] = createSignal(false) + const [expanded, setExpanded] = createSignal(false) + const [canExpand, setCanExpand] = createSignal(false) + let textRef: HTMLDivElement | undefined + + const updateCanExpand = () => { + const el = textRef + if (!el) return + if (expanded()) return + setCanExpand(el.scrollHeight > el.clientHeight + 2) + } + + createResizeObserver( + () => textRef, + () => { + updateCanExpand() + }, + ) const textPart = createMemo( () => props.parts?.find((p) => p.type === "text" && !(p as TextPart).synthetic) as TextPart | undefined, @@ -304,6 +322,11 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp const text = createMemo(() => textPart()?.text || "") + createEffect(() => { + text() + updateCanExpand() + }) + const files = createMemo(() => (props.parts?.filter((p) => p.type === "file") as FilePart[]) ?? []) const attachments = createMemo(() => @@ -335,7 +358,7 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp } return ( -
+
0}>
@@ -365,8 +388,16 @@ export function UserMessageDisplay(props: { message: UserMessage; parts: PartTyp
-
+
(textRef = el)}> +
diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css index f7ab97179..a3c87c576 100644 --- a/packages/ui/src/components/session-turn.css +++ b/packages/ui/src/components/session-turn.css @@ -44,23 +44,33 @@ } } - [data-slot="session-turn-sticky-title"] { - width: 100%; + [data-slot="session-turn-sticky"] { + width: calc(100% + 9px); position: sticky; - top: 0; + top: var(--session-title-height, 0px); + z-index: 20; background-color: var(--background-stronger); - z-index: 21; + margin-left: -9px; + padding-left: 9px; + padding-bottom: 12px; + display: flex; + flex-direction: column; + gap: 12px; + + &::before { + content: ""; + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: var(--background-stronger); + z-index: -1; + } } [data-slot="session-turn-response-trigger"] { - position: sticky; - top: calc(var(--sticky-header-height, 0px)); - background-color: var(--background-stronger); - z-index: 20; - width: calc(100% + 9px); - margin-left: -9px; - padding-left: 9px; - padding-bottom: 8px; + width: fit-content; } [data-slot="session-turn-message-header"] { @@ -75,6 +85,61 @@ max-width: 100%; } + [data-component="user-message"] [data-slot="user-message-text"] { + max-height: var(--user-message-collapsed-height, 64px); + } + + [data-component="user-message"][data-expanded="true"] [data-slot="user-message-text"] { + max-height: none; + } + + [data-component="user-message"][data-can-expand="true"] [data-slot="user-message-text"] { + padding-right: 36px; + padding-bottom: 28px; + } + + [data-component="user-message"] [data-slot="user-message-text"] [data-slot="user-message-expand"] { + display: none; + position: absolute; + bottom: 6px; + right: 6px; + padding: 0; + } + + [data-component="user-message"][data-can-expand="true"] + [data-slot="user-message-text"] + [data-slot="user-message-expand"], + [data-component="user-message"][data-expanded="true"] + [data-slot="user-message-text"] + [data-slot="user-message-expand"] { + display: inline-flex; + align-items: center; + justify-content: center; + height: 22px; + width: 22px; + border: none; + border-radius: 6px; + background: transparent; + cursor: pointer; + color: var(--text-weak); + + [data-slot="icon-svg"] { + transition: transform 0.15s ease; + } + } + + [data-component="user-message"][data-expanded="true"] + [data-slot="user-message-text"] + [data-slot="user-message-expand"] + [data-slot="icon-svg"] { + transform: rotate(180deg); + } + + [data-component="user-message"] [data-slot="user-message-text"] [data-slot="user-message-expand"]:hover { + background: var(--surface-raised-base); + color: var(--text-base); + } + [data-slot="session-turn-user-badges"] { display: flex; align-items: center; @@ -266,11 +331,7 @@ } [data-component="sticky-accordion-header"] { - top: var(--sticky-header-height, 40px); - - &[data-expanded]::before { - top: calc(-1 * var(--sticky-header-height, 40px)); - } + position: static; } [data-slot="session-turn-accordion-trigger-content"] { diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx index 8b807af82..e5fe4ba1c 100644 --- a/packages/ui/src/components/session-turn.tsx +++ b/packages/ui/src/components/session-turn.tsx @@ -13,17 +13,13 @@ import { getDirectory, getFilename } from "@opencode-ai/util/path" import { Binary } from "@opencode-ai/util/binary" import { createEffect, createMemo, createSignal, For, Match, on, onCleanup, ParentProps, Show, Switch } from "solid-js" -import { createResizeObserver } from "@solid-primitives/resize-observer" import { DiffChanges } from "./diff-changes" -import { Typewriter } from "./typewriter" import { Message, Part } from "./message-part" import { Markdown } from "./markdown" import { Accordion } from "./accordion" import { StickyAccordionHeader } from "./sticky-accordion-header" import { FileIcon } from "./file-icon" import { Icon } from "./icon" -import { ProviderIcon } from "./provider-icon" -import type { IconName } from "./provider-icons/types" import { IconButton } from "./icon-button" import { Tooltip } from "./tooltip" import { Card } from "./card" @@ -331,8 +327,6 @@ export function SessionTurn( const response = createMemo(() => lastTextPart()?.text) const responsePartId = createMemo(() => lastTextPart()?.id) - const sessionInfo = createMemo(() => data.store.session.find((item) => item.id === props.sessionID)) - const sessionTitle = createMemo(() => props.sessionTitle ?? sessionInfo()?.title) const hasDiffs = createMemo(() => (data.store.session_diff?.[props.sessionID]?.length ?? 0) > 0) const hideResponsePart = createMemo(() => !working() && !!responsePartId()) @@ -371,15 +365,11 @@ export function SessionTurn( const diffBatch = 20 const [store, setStore] = createStore({ - stickyTitleRef: undefined as HTMLDivElement | undefined, - stickyTriggerRef: undefined as HTMLDivElement | undefined, - stickyHeaderHeight: 0, retrySeconds: 0, diffsOpen: [] as string[], diffLimit: diffInit, status: rawStatus(), duration: duration(), - titleShown: false, }) createEffect( @@ -393,18 +383,6 @@ export function SessionTurn( ), ) - createEffect(() => { - if (!sessionTitle()) { - setStore("titleShown", false) - return - } - if (store.titleShown) return - const first = allMessages().find((item) => item?.role === "user") - if (!first) return - if (first.id !== props.messageID) return - setStore("titleShown", true) - }) - createEffect(() => { const r = retry() if (!r) { @@ -420,22 +398,6 @@ export function SessionTurn( onCleanup(() => clearInterval(timer)) }) - createResizeObserver( - () => store.stickyTitleRef, - ({ height }) => { - const triggerHeight = store.stickyTriggerRef?.offsetHeight ?? 0 - setStore("stickyHeaderHeight", height + triggerHeight + 8) - }, - ) - - createResizeObserver( - () => store.stickyTriggerRef, - ({ height }) => { - const titleHeight = store.stickyTitleRef?.offsetHeight ?? 0 - setStore("stickyHeaderHeight", titleHeight + height + 8) - }, - ) - createEffect(() => { const timer = setInterval(() => { setStore("duration", duration()) @@ -491,99 +453,58 @@ export function SessionTurn( data-message={msg().id} data-slot="session-turn-message-container" class={props.classes?.container} - style={{ "--sticky-header-height": `${store.stickyHeaderHeight}px` }} > - -
setStore("stickyTitleRef", el)} data-slot="session-turn-sticky-title"> -
-
+
+ {/* User Message */} +
+ +
+ + {/* Trigger (sticky) */} + +
+
+ · + {store.duration} + 0}> + + +
-
- - - -
- - {(msg() as UserMessage).agent} - - - - - {(msg() as UserMessage).model?.modelID} - - - - {(msg() as UserMessage).variant} - -
-
- {/* User Message */} -
- +
- - {/* Trigger (sticky) */} - -
setStore("stickyTriggerRef", el)} data-slot="session-turn-response-trigger"> - -
-
{/* Response */} 0}>
-- cgit v1.2.3