From b15fb211917de83b39d9ec3a1d66ae4353d1e6e0 Mon Sep 17 00:00:00 2001
From: Filip <34747899+neriousy@users.noreply.github.com>
Date: Sun, 1 Mar 2026 15:41:47 +0100
Subject: feat(app): add compact ui (#15578)
---
packages/ui/src/components/message-part.css | 27 ++++++++++++++++++++++
packages/ui/src/components/message-part.tsx | 15 ++++++++++++
packages/ui/src/components/session-turn.css | 6 +++++
packages/ui/src/components/session-turn.tsx | 36 ++++++++++++++++++-----------
4 files changed, 70 insertions(+), 14 deletions(-)
(limited to 'packages/ui/src/components')
diff --git a/packages/ui/src/components/message-part.css b/packages/ui/src/components/message-part.css
index 58227f625..6727bb22f 100644
--- a/packages/ui/src/components/message-part.css
+++ b/packages/ui/src/components/message-part.css
@@ -225,6 +225,33 @@
}
}
+[data-component="compaction-part"] {
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ align-items: stretch;
+
+ [data-slot="compaction-part-divider"] {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 10px 0;
+ width: 100%;
+ }
+
+ [data-slot="compaction-part-line"] {
+ flex: 1 1 auto;
+ height: 1px;
+ background: var(--border-weak-base);
+ }
+
+ [data-slot="compaction-part-label"] {
+ flex: 0 0 auto;
+ white-space: nowrap;
+ text-align: center;
+ }
+}
+
[data-component="reasoning-part"] {
width: 100%;
color: var(--text-base);
diff --git a/packages/ui/src/components/message-part.tsx b/packages/ui/src/components/message-part.tsx
index 6b6dfe2e5..02a99f9dd 100644
--- a/packages/ui/src/components/message-part.tsx
+++ b/packages/ui/src/components/message-part.tsx
@@ -1037,6 +1037,21 @@ PART_MAPPING["tool"] = function ToolPartDisplay(props) {
)
}
+PART_MAPPING["compaction"] = function CompactionPartDisplay() {
+ const i18n = useI18n()
+ return (
+
+
+
+
+ {i18n.t("ui.messagePart.compaction")}
+
+
+
+
+ )
+}
+
PART_MAPPING["text"] = function TextPartDisplay(props) {
const data = useData()
const i18n = useI18n()
diff --git a/packages/ui/src/components/session-turn.css b/packages/ui/src/components/session-turn.css
index 9639e6635..4af87b361 100644
--- a/packages/ui/src/components/session-turn.css
+++ b/packages/ui/src/components/session-turn.css
@@ -37,6 +37,12 @@
max-width: 100%;
}
+ [data-slot="session-turn-compaction"] {
+ width: 100%;
+ min-width: 0;
+ align-self: stretch;
+ }
+
[data-slot="session-turn-thinking"] {
display: flex;
align-items: center;
diff --git a/packages/ui/src/components/session-turn.tsx b/packages/ui/src/components/session-turn.tsx
index bd4f2843a..e329b1170 100644
--- a/packages/ui/src/components/session-turn.tsx
+++ b/packages/ui/src/components/session-turn.tsx
@@ -6,7 +6,7 @@ import { Binary } from "@opencode-ai/util/binary"
import { getDirectory, getFilename } from "@opencode-ai/util/path"
import { createEffect, createMemo, createSignal, For, on, ParentProps, Show } from "solid-js"
import { Dynamic } from "solid-js/web"
-import { AssistantParts, Message, PART_MAPPING } from "./message-part"
+import { AssistantParts, Message, Part, PART_MAPPING } from "./message-part"
import { Card } from "./card"
import { Accordion } from "./accordion"
import { StickyAccordionHeader } from "./sticky-accordion-header"
@@ -139,7 +139,6 @@ export function SessionTurn(
props: ParentProps<{
sessionID: string
messageID: string
- lastUserMessageID?: string
showReasoningSummaries?: boolean
shellToolDefaultOpen?: boolean
editToolDefaultOpen?: boolean
@@ -187,18 +186,18 @@ export function SessionTurn(
return msg
})
- const lastUserMessageID = createMemo(() => {
- if (props.lastUserMessageID) return props.lastUserMessageID
-
+ const pending = createMemo(() => {
const messages = allMessages() ?? emptyMessages
- for (let i = messages.length - 1; i >= 0; i--) {
- const msg = messages[i]
- if (msg?.role === "user") return msg.id
- }
- return undefined
+ return messages.findLast(
+ (item): item is AssistantMessage => item.role === "assistant" && typeof item.time.completed !== "number",
+ )
+ })
+ const active = createMemo(() => {
+ const msg = message()
+ const item = pending()
+ if (!msg || !item) return false
+ return item.parentID === msg.id
})
-
- const isLastUserMessage = createMemo(() => props.messageID === lastUserMessageID())
const parts = createMemo(() => {
const msg = message()
@@ -206,6 +205,8 @@ export function SessionTurn(
return list(data.store.part?.[msg.id], emptyParts)
})
+ const compaction = createMemo(() => parts().find((part) => part.type === "compaction"))
+
const diffs = createMemo(() => {
const files = message()?.summary?.diffs
if (!files?.length) return emptyDiffs
@@ -285,7 +286,7 @@ export function SessionTurn(
})
const status = createMemo(() => data.store.session_status[props.sessionID] ?? idle)
- const working = createMemo(() => status().type !== "idle" && isLastUserMessage())
+ const working = createMemo(() => status().type !== "idle" && active())
const showReasoningSummaries = createMemo(() => props.showReasoningSummaries ?? true)
const assistantCopyPartID = createMemo(() => {
@@ -365,6 +366,13 @@ export function SessionTurn(
+
+ {(part) => (
+
+ )}
+
0}>
-
+
0 && !working()}>
--
cgit v1.2.3