+ const sessionErrorFallback = (error: unknown, reset: () => void) => {
+ createEffect(on(sessionKey, reset, { defer: true }))
+ return
+ }
+
+ const sessionPanelContent = () => (
+ <>
{sessionSync() ?? ""}
+
+ {mobileTabs(true)}
+
+
+
+
+
+ {reviewContent({
+ diffStyle: "unified",
+ classes: {
+ root: "pb-8 [&_[data-slot=session-review-list]]:pb-0",
+ header: "px-4 !h-16 !pb-4",
+ container: "px-4",
+ },
+ loadingClass: "px-4 py-4 text-text-weak",
+ emptyClass: "h-full pb-64 -mt-4 flex flex-col items-center justify-center text-center gap-6",
+ })}
+
+
+
+
+ {(_id) => (
+
+ !location.hash && !store.messageId && !ui.pendingMessage && !autoScroll.userScrolled()
+ }
+ centered={centered()}
+ setContentRef={(el) => {
+ content = el
+ autoScroll.contentRef(el)
+
+ const root = scroller
+ if (root) scheduleScrollState(root)
+ }}
+ userMessages={visibleUserMessages()}
+ setHistoryAnchor={(handlers) => {
+ captureHistoryAnchor = handlers.capture
+ restoreHistoryAnchor = handlers.restore
+ }}
+ anchor={anchor}
+ setRevealMessage={(fn) => {
+ revealMessage = fn
+ }}
+ setScrollToEnd={(fn) => {
+ scrollToEnd = fn
+ }}
+ />
+ )}
+
+
+
+
+
+
+
+
+
{(_) => composerRegion()}
+
{mobileTabs(true, true)}
+ >
+ )
+
+ return (
+
-
-
- {mobileTabs(true)}
-
-
-
-
-
- {reviewContent({
- diffStyle: "unified",
- classes: {
- root: "pb-8 [&_[data-slot=session-review-list]]:pb-0",
- header: "px-4 !h-16 !pb-4",
- container: "px-4",
- },
- loadingClass: "px-4 py-4 text-text-weak",
- emptyClass: "h-full pb-64 -mt-4 flex flex-col items-center justify-center text-center gap-6",
- })}
-
-
-
-
- {(_id) => (
-
- !location.hash && !store.messageId && !ui.pendingMessage && !autoScroll.userScrolled()
- }
- centered={centered()}
- setContentRef={(el) => {
- content = el
- autoScroll.contentRef(el)
-
- const root = scroller
- if (root) scheduleScrollState(root)
- }}
- userMessages={visibleUserMessages()}
- setHistoryAnchor={(handlers) => {
- captureHistoryAnchor = handlers.capture
- restoreHistoryAnchor = handlers.restore
- }}
- anchor={anchor}
- setRevealMessage={(fn) => {
- revealMessage = fn
- }}
- setScrollToEnd={(fn) => {
- scrollToEnd = fn
- }}
- />
- )}
-
-
-
-
-
-
-
-
-
{(_) => composerRegion()}
-
{mobileTabs(true, true)}
-
+
+ {settings.general.newLayoutDesigns() ? (
+ {sessionPanelContent()}
+ ) : (
+ sessionPanelContent()
+ )}
+
size.start()}>
@@ -1832,6 +2057,6 @@ export default function Page() {
-
+
)
}
diff --git a/packages/app/src/pages/session/composer/session-composer-region.tsx b/packages/app/src/pages/session/composer/session-composer-region.tsx
index 84cd4dc6910..600ff41e3de 100644
--- a/packages/app/src/pages/session/composer/session-composer-region.tsx
+++ b/packages/app/src/pages/session/composer/session-composer-region.tsx
@@ -125,7 +125,7 @@ export function SessionComposerRegion(props: {
language.t("ui.messagePart.option.typeOwnAnswer")
const customPlaceholder = () => language.t("ui.question.custom.placeholder")
const last = createMemo(() => store.tab >= total() - 1)
+ const collapse = useSpring(() => (store.minimized ? 1 : 0), { visualDuration: 0.3, bounce: 0 })
+ const hidden = createMemo(() => Math.max(0, Math.min(1, collapse())))
+ const optionsOff = createMemo(() => hidden() > 0.98)
const customUpdate = (value: string, selected: boolean = on()) => {
const prev = input().trim()
@@ -192,6 +198,14 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
focus(pickFocus())
})
+ createEffect(() => {
+ const el = optionsRef
+ if (!el) return
+ const update = () => setStore("optionsHeight", (height) => Math.max(height, el.scrollHeight))
+ update()
+ createResizeObserver(el, update)
+ })
+
onCleanup(() => {
if (focusFrame !== undefined) cancelAnimationFrame(focusFrame)
if (replied) return
@@ -405,7 +419,7 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
const tab = store.tab + 1
setStore("tab", tab)
setStore("editing", false)
- focus(pickFocus(tab))
+ if (!store.minimized) focus(pickFocus(tab))
}
const back = () => {
@@ -414,163 +428,212 @@ export const SessionQuestionDock: Component<{ request: QuestionRequest; onSubmit
const tab = store.tab - 1
setStore("tab", tab)
setStore("editing", false)
- focus(pickFocus(tab))
+ if (!store.minimized) focus(pickFocus(tab))
}
const jump = (tab: number) => {
if (sending()) return
setStore("tab", tab)
setStore("editing", false)
- focus(pickFocus(tab))
+ if (!store.minimized) focus(pickFocus(tab))
+ }
+
+ const minimize = () => {
+ if (sending()) return
+ setStore("editing", false)
+ setStore("minimized", true)
+ }
+
+ const restore = () => {
+ if (sending()) return
+ setStore("minimized", false)
+ focus(pickFocus())
}
return (
-
(root = el)}
- onKeyDown={nav}
- header={
- <>
- {summary()}
- 1}>
-
-
- {(_, i) => (
-
+
+
(root = el)}
+ onKeyDown={nav}
+ header={
+ <>
+ {summary()}
+
+
1}>
+
+
+ {(_, i) => (
+
+
+
+
-
- >
- }
- footer={
- <>
-
-
- 0}>
-
-
-
- >
- }
- >
-
- {question()?.question}
-
- {language.t("ui.question.singleHint")}}>
-
{language.t("ui.question.multiHint")}
-
-
-
- {(opt, i) => (
-
+
+ 0}>
+
+ {language.t("ui.common.back")}
+
+
+
+ {last() ? language.t("ui.common.submit") : language.t("ui.common.next")}
+
+
+ >
+ }
+ >
+
+ {question()?.question}
+
+
+ {language.t("ui.question.singleHint")}}>
+
{language.t("ui.question.multiHint")}
+
+
+
(optionsRef = el)}
+ data-slot="question-options"
+ aria-hidden={store.minimized || optionsOff() ? "true" : undefined}
+ classList={{ "pointer-events-none": hidden() > 0.1 }}
+ style={{
+ "max-height": `${Math.max(0, store.optionsHeight * (1 - hidden()))}px`,
+ opacity: `${Math.max(0, Math.min(1, 1 - hidden()))}`,
+ visibility: optionsOff() ? "hidden" : "visible",
+ }}
+ >
+
+ {(opt, i) => (
+
- setStore("focus", options().length)}
+ onClick={customOpen}
+ >
+
+
+ {customLabel()}
+ {input() || customPlaceholder()}
+
+
+ }
+ >
+
-
-
-
+ commitCustom()
+ }}
+ onInput={(e) => {
+ customUpdate(e.currentTarget.value)
+ resizeInput(e.currentTarget)
+ }}
+ />
+
+
+
+
+
+
)
}
diff --git a/packages/app/src/pages/session/timeline/message-timeline.tsx b/packages/app/src/pages/session/timeline/message-timeline.tsx
index 42712732bf0..70b54dba77a 100644
--- a/packages/app/src/pages/session/timeline/message-timeline.tsx
+++ b/packages/app/src/pages/session/timeline/message-timeline.tsx
@@ -31,9 +31,14 @@ import { DiffChanges } from "@opencode-ai/ui/diff-changes"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { Icon } from "@opencode-ai/ui/icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
+import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon"
+import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2"
import { DropdownMenu } from "@opencode-ai/ui/dropdown-menu"
+import { MenuV2 } from "@opencode-ai/ui/v2/menu-v2"
import { Dialog } from "@opencode-ai/ui/dialog"
+import { DialogFooter, DialogHeader, DialogTitleGroup, DialogV2 } from "@opencode-ai/ui/v2/dialog-v2"
import { InlineInput } from "@opencode-ai/ui/inline-input"
+import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2"
import { SessionRetry } from "@opencode-ai/session-ui/session-retry"
import { ScrollView } from "@opencode-ai/ui/scroll-view"
import { StickyAccordionHeader } from "@opencode-ai/ui/sticky-accordion-header"
@@ -671,6 +676,34 @@ export function MessageTimeline(props: {
if (!shareEnabled()) return
unshareMutation.mutate(id)
}
+ const copyShareUrl = () => {
+ const url = shareUrl()
+ if (!url) return
+ void navigator.clipboard
+ .writeText(url)
+ .then(() =>
+ showToast({
+ variant: "success",
+ icon: "circle-check",
+ title: language.t("session.share.copy.copied"),
+ description: url,
+ }),
+ )
+ .catch((err: unknown) =>
+ showToast({
+ title: language.t("common.requestFailed"),
+ description: errorMessage(err),
+ }),
+ )
+ }
+ const selectShareUrlText: JSX.EventHandler
= (event) => {
+ const selection = window.getSelection()
+ if (!selection) return
+ const range = document.createRange()
+ range.selectNodeContents(event.currentTarget)
+ selection.removeAllRanges()
+ selection.addRange(range)
+ }
createEffect(
on(
@@ -703,8 +736,9 @@ export function MessageTimeline(props: {
if (!sessionID() || parentID()) return
setTitle({ editing: true, draft: titleLabel() ?? "" })
requestAnimationFrame(() => {
- titleRef?.focus()
- titleRef?.select()
+ if (!titleRef) return
+ titleRef.focus()
+ titleRef.select()
})
}
@@ -856,6 +890,26 @@ export function MessageTimeline(props: {
dialog.close()
}
+ if (settings.general.newLayoutDesigns())
+ return (
+
+
+
+
+
+ dialog.close()}>
+ {language.t("common.cancel")}
+
+
+ {language.t("session.delete.button")}
+
+
+
+ )
+
return (
@@ -1302,26 +1358,31 @@ export function MessageTimeline(props: {
"w-full": true,
"pb-4": true,
"pr-3": true,
- "pl-4": settings.general.newLayoutDesigns(),
+ "pl-2": settings.general.newLayoutDesigns(),
"pl-2 md:pl-4": !settings.general.newLayoutDesigns(),
- "md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered,
+ "md:max-w-200 md:mx-auto 2xl:max-w-[1000px]": props.centered && !settings.general.newLayoutDesigns(),
}}
>