From 8f8f32c3a66e4020856bc487847fb3e41295d947 Mon Sep 17 00:00:00 2001 From: Manikandan Sundararajan <10191300+itsrainingmani@users.noreply.github.com> Date: Thu, 15 Jan 2026 06:27:12 -0500 Subject: [PATCH 1/6] feat(desktop): implement session unshare button --- .../src/components/session/session-header.tsx | 184 ++++++++++++++---- 1 file changed, 149 insertions(+), 35 deletions(-) diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 4b083771fb8..4982047ff66 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -1,15 +1,18 @@ -import { createMemo, createResource, Show } from "solid-js" +import { createEffect, createMemo, onCleanup, Show } from "solid-js" +import type { JSX } from "solid-js" +import { createStore } from "solid-js/store" import { Portal } from "solid-js/web" import { useParams } from "@solidjs/router" import { useLayout } from "@/context/layout" import { useCommand } from "@/context/command" // import { useServer } from "@/context/server" // import { useDialog } from "@opencode-ai/ui/context/dialog" +import { usePlatform } from "@/context/platform" import { useSync } from "@/context/sync" import { useGlobalSDK } from "@/context/global-sdk" import { getFilename } from "@opencode-ai/util/path" import { base64Decode } from "@opencode-ai/util/encode" -import { iife } from "@opencode-ai/util/iife" + import { Icon } from "@opencode-ai/ui/icon" import { IconButton } from "@opencode-ai/ui/icon-button" import { Button } from "@opencode-ai/ui/button" @@ -25,6 +28,7 @@ export function SessionHeader() { // const server = useServer() // const dialog = useDialog() const sync = useSync() + const platform = usePlatform() const projectDirectory = createMemo(() => base64Decode(params.dir ?? "")) const project = createMemo(() => { @@ -44,6 +48,79 @@ export function SessionHeader() { const sessionKey = createMemo(() => `${params.dir}${params.id ? "/" + params.id : ""}`) const view = createMemo(() => layout.view(sessionKey())) + const [state, setState] = createStore({ + share: false, + unshare: false, + copied: false, + timer: undefined as number | undefined, + }) + const shareUrl = () => currentSession()?.share?.url ?? "" + const shared = createMemo(() => !!shareUrl()) + + createEffect(() => { + const url = shareUrl() + if (url) return + if (state.timer) window.clearTimeout(state.timer) + setState({ copied: false, timer: undefined }) + }) + + onCleanup(() => { + if (state.timer) window.clearTimeout(state.timer) + }) + + function shareSession() { + const session = currentSession() + if (!session || state.share) return + setState("share", true) + globalSDK.client.session + .share({ sessionID: session.id, directory: projectDirectory() }) + .catch((error) => { + console.error("Failed to share session", error) + }) + .finally(() => { + setState("share", false) + }) + } + + function unshareSession() { + const session = currentSession() + if (!session || state.unshare) return + setState("unshare", true) + globalSDK.client.session + .unshare({ sessionID: session.id, directory: projectDirectory() }) + .catch((error) => { + console.error("Failed to unshare session", error) + }) + .finally(() => { + setState("unshare", false) + }) + } + + function copyLink() { + const url = shareUrl() + if (!url) return + navigator.clipboard + .writeText(url) + .then(() => { + if (state.timer) window.clearTimeout(state.timer) + setState("copied", true) + const timer = window.setTimeout(() => { + setState("copied", false) + setState("timer", undefined) + }, 3000) + setState("timer", timer) + }) + .catch((error) => { + console.error("Failed to copy share link", error) + }) + } + + function viewShare() { + const url = shareUrl() + if (!url) return + platform.openLink(url) + } + const centerMount = createMemo(() => document.getElementById("opencode-titlebar-center")) const rightMount = createMemo(() => document.getElementById("opencode-titlebar-right")) @@ -80,7 +157,7 @@ export function SessionHeader() { {(mount) => ( - + {/* */} {/* - - - - } - > - {iife(() => { - const [url] = createResource( - () => currentSession(), - async (session) => { - if (!session) return - let shareURL = session.share?.url - if (!shareURL) { - shareURL = await globalSDK.client.session - .share({ sessionID: session.id, directory: projectDirectory() }) - .then((r) => r.data?.share?.url) - .catch((e) => { - console.error("Failed to share session", e) - return undefined - }) + + + + Share + + + } + > + + + + {state.share ? "Publishing..." : "Publish"} + + } - return shareURL - }, - { initialValue: "" }, - ) - return ( - - {(shareUrl) => } + > + + + + + {state.unshare ? "Unpublishing..." : "Unpublish"} + + + View + + + - ) - })} - + + + + + + + + From b4edff01c1a345fd98e720bb5a8f1a4dd34eada0 Mon Sep 17 00:00:00 2001 From: Manikandan Sundararajan <10191300+itsrainingmani@users.noreply.github.com> Date: Thu, 15 Jan 2026 06:42:32 -0500 Subject: [PATCH 2/6] refactor: simplify share session state management - Move share state and functions to top level of SessionHeader - Remove iife wrapper and unused createEffect/iife imports --- packages/app/src/components/session/session-header.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 4982047ff66..1e9d521a609 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -1,5 +1,4 @@ import { createEffect, createMemo, onCleanup, Show } from "solid-js" -import type { JSX } from "solid-js" import { createStore } from "solid-js/store" import { Portal } from "solid-js/web" import { useParams } from "@solidjs/router" From db540f8f1051f4141d211c75da011cfe8cb2514d Mon Sep 17 00:00:00 2001 From: Manikandan Sundararajan <10191300+itsrainingmani@users.noreply.github.com> Date: Fri, 16 Jan 2026 19:09:17 -0500 Subject: [PATCH 3/6] implement updated design for share / unpublish --- packages/app/src/components/session/session-header.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 1e9d521a609..3903c596cca 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -156,7 +156,7 @@ export function SessionHeader() { {(mount) => ( - + {/* */} {/* Date: Fri, 16 Jan 2026 19:09:55 -0500 Subject: [PATCH 4/6] add share/unshare commands to cmd palette & slash commands --- packages/app/src/pages/session.tsx | 66 ++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index ca5e73a9be9..0f1279b6574 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -655,6 +655,72 @@ export default function Page() { disabled: !params.id || visibleUserMessages().length === 0, onSelect: () => dialog.show(() => ), }, + ...(sync.data.config.share !== "disabled" + ? [ + { + id: "session.share", + title: "Share session", + description: "Share this session and copy the URL to clipboard", + category: "Session", + slash: "share", + disabled: !params.id || !!info()?.share?.url, + onSelect: async () => { + if (!params.id) return + await sdk.client.session + .share({ sessionID: params.id }) + .then((res) => { + navigator.clipboard.writeText(res.data!.share!.url).catch(() => + showToast({ + title: "Failed to copy URL to clipboard", + variant: "error", + }), + ) + }) + .then(() => + showToast({ + title: "Session shared", + description: "Share URL copied to clipboard!", + variant: "success", + }), + ) + .catch(() => + showToast({ + title: "Failed to share session", + description: "An error occurred while sharing the session", + variant: "error", + }), + ) + }, + }, + { + id: "session.unshare", + title: "Unshare session", + description: "Stop sharing this session", + category: "Session", + slash: "unshare", + disabled: !params.id || !info()?.share?.url, + onSelect: async () => { + if (!params.id) return + await sdk.client.session + .unshare({ sessionID: params.id }) + .then(() => + showToast({ + title: "Session unshared", + description: "Session unshared successfully!", + variant: "success", + }), + ) + .catch(() => + showToast({ + title: "Failed to unshare session", + description: "An error occurred while unsharing the session", + variant: "error", + }), + ) + }, + }, + ] + : []), ]) const handleKeyDown = (event: KeyboardEvent) => { From 998f45770aa1fc14e7c5eb432914fced72409949 Mon Sep 17 00:00:00 2001 From: Manikandan Sundararajan <10191300+itsrainingmani@users.noreply.github.com> Date: Fri, 16 Jan 2026 22:47:30 -0500 Subject: [PATCH 5/6] simplify shared url state and access pattern --- .../src/components/session/session-header.tsx | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 3903c596cca..808f1bba376 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -53,8 +53,7 @@ export function SessionHeader() { copied: false, timer: undefined as number | undefined, }) - const shareUrl = () => currentSession()?.share?.url ?? "" - const shared = createMemo(() => !!shareUrl()) + const shareUrl = createMemo(() => currentSession()?.share?.url) createEffect(() => { const url = shareUrl() @@ -156,7 +155,7 @@ export function SessionHeader() { {(mount) => ( - + {/* */} {/* - + Share @@ -261,7 +260,7 @@ export function SessionHeader() { > - - - + + + {state.unshare ? "Unpublishing..." : "Unpublish"} - + View @@ -302,7 +289,7 @@ export function SessionHeader() { - + Date: Fri, 16 Jan 2026 23:19:15 -0500 Subject: [PATCH 6/6] make Publish & View button sizing more consistent --- .../src/components/session/session-header.tsx | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index 808f1bba376..a7f8a884731 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -266,7 +266,7 @@ export function SessionHeader() { @@ -278,10 +278,22 @@ export function SessionHeader() { - + {state.unshare ? "Unpublishing..." : "Unpublish"} - + View