From 184732fc2097166921dd46fbb9a8ce433a96b237 Mon Sep 17 00:00:00 2001 From: David Hill <1879069+iamdavidhill@users.noreply.github.com> Date: Thu, 12 Mar 2026 18:26:50 +0000 Subject: fix(app): titlebar cleanup (#17206) --- .../app/src/pages/session/message-timeline.tsx | 202 ++++++++++++++++++++- 1 file changed, 196 insertions(+), 6 deletions(-) (limited to 'packages/app/src/pages/session') diff --git a/packages/app/src/pages/session/message-timeline.tsx b/packages/app/src/pages/session/message-timeline.tsx index 50f9b452a..ad4ef8443 100644 --- a/packages/app/src/pages/session/message-timeline.tsx +++ b/packages/app/src/pages/session/message-timeline.tsx @@ -11,15 +11,19 @@ import { InlineInput } from "@opencode-ai/ui/inline-input" import { Spinner } from "@opencode-ai/ui/spinner" import { SessionTurn } from "@opencode-ai/ui/session-turn" import { ScrollView } from "@opencode-ai/ui/scroll-view" +import { TextField } from "@opencode-ai/ui/text-field" import type { AssistantMessage, Message as MessageType, Part, TextPart, UserMessage } from "@opencode-ai/sdk/v2" import { showToast } from "@opencode-ai/ui/toast" import { Binary } from "@opencode-ai/util/binary" import { getFilename } from "@opencode-ai/util/path" +import { Popover as KobaltePopover } from "@kobalte/core/popover" import { shouldMarkBoundaryGesture, normalizeWheelDelta } from "@/pages/session/message-gesture" import { SessionContextUsage } from "@/components/session-context-usage" import { useDialog } from "@opencode-ai/ui/context/dialog" import { useLanguage } from "@/context/language" import { useSessionKey } from "@/pages/session/session-layout" +import { useGlobalSDK } from "@/context/global-sdk" +import { usePlatform } from "@/context/platform" import { useSettings } from "@/context/settings" import { useSDK } from "@/context/sdk" import { useSync } from "@/context/sync" @@ -215,12 +219,14 @@ export function MessageTimeline(props: { let touchGesture: number | undefined const navigate = useNavigate() + const globalSDK = useGlobalSDK() const sdk = useSDK() const sync = useSync() const settings = useSettings() const dialog = useDialog() const language = useLanguage() const { params, sessionKey } = useSessionKey() + const platform = usePlatform() const rendered = createMemo(() => props.renderedUserMessages.map((message) => message.id)) const sessionID = createMemo(() => params.id) @@ -298,6 +304,8 @@ export function MessageTimeline(props: { return sync.session.get(id) }) const titleValue = createMemo(() => info()?.title) + const shareUrl = createMemo(() => info()?.share?.url) + const shareEnabled = createMemo(() => sync.data.config.share !== "disabled") const parentID = createMemo(() => info()?.parentID) const showHeader = createMemo(() => !!(titleValue() || parentID())) const stageCfg = { init: 1, batch: 3 } @@ -314,9 +322,55 @@ export function MessageTimeline(props: { saving: false, menuOpen: false, pendingRename: false, + pendingShare: false, }) let titleRef: HTMLInputElement | undefined + const [share, setShare] = createStore({ + open: false, + dismiss: null as "escape" | "outside" | null, + }) + + let more: HTMLButtonElement | undefined + + const [req, setReq] = createStore({ share: false, unshare: false }) + + const shareSession = () => { + const id = sessionID() + if (!id || req.share) return + if (!shareEnabled()) return + setReq("share", true) + globalSDK.client.session + .share({ sessionID: id, directory: sdk.directory }) + .catch((err: unknown) => { + console.error("Failed to share session", err) + }) + .finally(() => { + setReq("share", false) + }) + } + + const unshareSession = () => { + const id = sessionID() + if (!id || req.unshare) return + if (!shareEnabled()) return + setReq("unshare", true) + globalSDK.client.session + .unshare({ sessionID: id, directory: sdk.directory }) + .catch((err: unknown) => { + console.error("Failed to unshare session", err) + }) + .finally(() => { + setReq("unshare", false) + }) + } + + const viewShare = () => { + const url = shareUrl() + if (!url) return + platform.openLink(url) + } + const errorMessage = (err: unknown) => { if (err && typeof err === "object" && "data" in err) { const data = (err as { data?: { message?: string } }).data @@ -329,7 +383,15 @@ export function MessageTimeline(props: { createEffect( on( sessionKey, - () => setTitle({ draft: "", editing: false, saving: false, menuOpen: false, pendingRename: false }), + () => + setTitle({ + draft: "", + editing: false, + saving: false, + menuOpen: false, + pendingRename: false, + pendingShare: false, + }), { defer: true }, ), ) @@ -678,23 +740,42 @@ export function MessageTimeline(props: { gutter={4} placement="bottom-end" open={title.menuOpen} - onOpenChange={(open) => setTitle("menuOpen", open)} + onOpenChange={(open) => { + setTitle("menuOpen", open) + if (open) return + }} > { + more = el + }} /> { - if (!title.pendingRename) return - event.preventDefault() - setTitle("pendingRename", false) - openTitleEditor() + if (title.pendingRename) { + event.preventDefault() + setTitle("pendingRename", false) + openTitleEditor() + return + } + if (title.pendingShare) { + event.preventDefault() + requestAnimationFrame(() => { + setShare({ open: true, dismiss: null }) + setTitle("pendingShare", false) + }) + } }} > {language.t("common.rename")} + + { + setTitle({ pendingShare: true, menuOpen: false }) + }} + > + + {language.t("session.share.action.share")} + + + void archiveSession(id())}> {language.t("common.archive")} @@ -717,6 +809,104 @@ export function MessageTimeline(props: { + + more} + placement="bottom-end" + gutter={4} + modal={false} + onOpenChange={(open) => { + if (open) setShare("dismiss", null) + setShare("open", open) + }} + > + + { + setShare({ dismiss: "escape", open: false }) + event.preventDefault() + event.stopPropagation() + }} + onPointerDownOutside={() => { + setShare({ dismiss: "outside", open: false }) + }} + onFocusOutside={() => { + setShare({ dismiss: "outside", open: false }) + }} + onCloseAutoFocus={(event) => { + if (share.dismiss === "outside") event.preventDefault() + setShare("dismiss", null) + }} + > +
+
+
+ {language.t("session.share.popover.title")} +
+
+ {shareUrl() + ? language.t("session.share.popover.description.shared") + : language.t("session.share.popover.description.unshared")} +
+
+
+ + {req.share + ? language.t("session.share.action.publishing") + : language.t("session.share.action.publish")} + + } + > +
+ +
+ + +
+
+
+
+
+
+
+
)} -- cgit v1.2.3