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