From 7de42ca242af2045fdfc4bbbe1ada33069d2769e Mon Sep 17 00:00:00 2001 From: adamelmore <2363879+adamdottv@users.noreply.github.com> Date: Tue, 27 Jan 2026 11:34:30 -0600 Subject: feat(app): improved layout --- packages/app/src/pages/session.tsx | 314 ++++++++++++------------------------- 1 file changed, 104 insertions(+), 210 deletions(-) (limited to 'packages/app/src/pages') diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index a75b1169a..146450293 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -23,7 +23,6 @@ import { IconButton } from "@opencode-ai/ui/icon-button" import { Button } from "@opencode-ai/ui/button" import { Icon } from "@opencode-ai/ui/icon" import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip" -import { DiffChanges } from "@opencode-ai/ui/diff-changes" import { ResizeHandle } from "@opencode-ai/ui/resize-handle" import { Tabs } from "@opencode-ai/ui/tabs" import { useCodeComponent } from "@opencode-ai/ui/context/code" @@ -433,7 +432,7 @@ export default function Page() { expanded: {} as Record, messageId: undefined as string | undefined, turnStart: 0, - mobileTab: "session" as "session" | "review", + mobileTab: "session" as "session" | "changes", newSessionWorktree: "main", promptHeight: 0, }) @@ -693,12 +692,12 @@ export default function Page() { onSelect: () => view().terminal.toggle(), }, { - id: "review.toggle", - title: language.t("command.review.toggle"), + id: "fileTree.toggle", + title: language.t("command.fileTree.toggle"), description: "", category: language.t("command.category.view"), keybind: "mod+shift+r", - onSelect: () => view().reviewPanel.toggle(), + onSelect: () => layout.fileTree.toggle(), }, { id: "terminal.new", @@ -822,7 +821,7 @@ export default function Page() { const sessionID = params.id if (!sessionID) return if (status()?.type !== "idle") { - await sdk.client.session.abort({ sessionID }).catch(() => {}) + await sdk.client.session.abort({ sessionID }).catch(() => { }) } const revert = info()?.revert?.messageID // Find the last user message that's not already reverted @@ -905,69 +904,69 @@ export default function Page() { }, ...(sync.data.config.share !== "disabled" ? [ - { - id: "session.share", - title: language.t("command.session.share"), - description: language.t("command.session.share.description"), - category: language.t("command.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: language.t("toast.session.share.copyFailed.title"), - variant: "error", - }), - ) - }) - .then(() => + { + id: "session.share", + title: language.t("command.session.share"), + description: language.t("command.session.share.description"), + category: language.t("command.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: language.t("toast.session.share.success.title"), - description: language.t("toast.session.share.success.description"), - variant: "success", - }), - ) - .catch(() => - showToast({ - title: language.t("toast.session.share.failed.title"), - description: language.t("toast.session.share.failed.description"), + title: language.t("toast.session.share.copyFailed.title"), variant: "error", }), ) - }, + }) + .then(() => + showToast({ + title: language.t("toast.session.share.success.title"), + description: language.t("toast.session.share.success.description"), + variant: "success", + }), + ) + .catch(() => + showToast({ + title: language.t("toast.session.share.failed.title"), + description: language.t("toast.session.share.failed.description"), + variant: "error", + }), + ) }, - { - id: "session.unshare", - title: language.t("command.session.unshare"), - description: language.t("command.session.unshare.description"), - category: language.t("command.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: language.t("toast.session.unshare.success.title"), - description: language.t("toast.session.unshare.success.description"), - variant: "success", - }), - ) - .catch(() => - showToast({ - title: language.t("toast.session.unshare.failed.title"), - description: language.t("toast.session.unshare.failed.description"), - variant: "error", - }), - ) - }, + }, + { + id: "session.unshare", + title: language.t("command.session.unshare"), + description: language.t("command.session.unshare.description"), + category: language.t("command.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: language.t("toast.session.unshare.success.title"), + description: language.t("toast.session.unshare.success.description"), + variant: "success", + }), + ) + .catch(() => + showToast({ + title: language.t("toast.session.unshare.failed.title"), + description: language.t("toast.session.unshare.failed.description"), + variant: "error", + }), + ) }, - ] + }, + ] : []), ]) @@ -1067,40 +1066,31 @@ export default function Page() { .filter((tab) => tab !== "context"), ) - const mobileReview = createMemo(() => !isDesktop() && view().reviewPanel.opened() && store.mobileTab === "review") + const mobileChanges = createMemo(() => !isDesktop() && store.mobileTab === "changes") - const showTabs = createMemo(() => view().reviewPanel.opened()) + const fileTreeTab = () => layout.fileTree.tab() + const setFileTreeTab = (value: "changes" | "all") => layout.fileTree.setTab(value) const [tree, setTree] = createStore({ - fileTreeTab: "changes" as "changes" | "all", reviewScroll: undefined as HTMLDivElement | undefined, pendingDiff: undefined as string | undefined, }) - const fileTreeTab = () => tree.fileTreeTab - const setFileTreeTab = (value: "changes" | "all") => setTree("fileTreeTab", value) const reviewScroll = () => tree.reviewScroll const setReviewScroll = (value: HTMLDivElement | undefined) => setTree("reviewScroll", value) const pendingDiff = () => tree.pendingDiff const setPendingDiff = (value: string | undefined) => setTree("pendingDiff", value) const showAllFiles = () => { - if (!layout.fileTree.opened()) return if (fileTreeTab() !== "changes") return setFileTreeTab("all") } - createEffect(() => { - if (!layout.fileTree.opened()) return - setFileTreeTab("changes") - }) - createEffect( on( () => tabs().active(), (active) => { if (!active) return - if (!layout.fileTree.opened()) return if (fileTreeTab() !== "changes") return if (!file.pathFromTab(active)) return showAllFiles() @@ -1197,49 +1187,30 @@ export default function Page() { const activeTab = createMemo(() => { const active = tabs().active() - if (layout.fileTree.opened() && fileTreeTab() === "all") { - if (active && active !== "review" && active !== "context") return normalizeTab(active) - - const first = openedTabs()[0] - if (first) return first - return "review" - } - if (active) return normalizeTab(active) - if (hasReview()) return "review" + if (active === "context") return "context" + if (active && file.pathFromTab(active)) return normalizeTab(active) const first = openedTabs()[0] if (first) return first if (contextOpen()) return "context" - return "review" + return "empty" }) createEffect(() => { if (!layout.ready()) return if (tabs().active()) return - if (!hasReview() && openedTabs().length === 0 && !contextOpen()) return - tabs().setActive(activeTab()) - }) - - createEffect(() => { - if (!layout.fileTree.opened()) return - if (fileTreeTab() !== "all") return - - const first = openedTabs()[0] - if (!first) return + if (openedTabs().length === 0 && !contextOpen()) return - const active = tabs().active() - if (active && active !== "review" && active !== "context") return - tabs().setActive(first) + const next = activeTab() + if (next === "empty") return + tabs().setActive(next) }) createEffect(() => { const id = params.id if (!id) return - const wants = isDesktop() - ? view().reviewPanel.opened() && - (layout.fileTree.opened() ? fileTreeTab() === "changes" : activeTab() === "review") - : view().reviewPanel.opened() && store.mobileTab === "review" + const wants = isDesktop() ? fileTreeTab() === "changes" : store.mobileTab === "changes" if (!wants) return if (sync.data.session_diff[id] !== undefined) return @@ -1654,8 +1625,8 @@ export default function Page() {
- {/* Mobile tab bar - only shown on mobile when user opened review */} - + {/* Mobile tab bar */} + setStore("mobileTab", "review")} + onClick={() => setStore("mobileTab", "changes")} > {language.t("session.review.filesChanged", { count: reviewCount() })} - {language.t("session.tab.review")} + {language.t("session.review.change.other")} @@ -1690,7 +1661,7 @@ export default function Page() { "flex-1 md:flex-none pt-6 md:pt-3": true, }} style={{ - width: isDesktop() && showTabs() ? `${layout.session.width()}px` : "100%", + width: isDesktop() ? `${layout.session.width()}px` : "100%", "--prompt-height": store.promptHeight ? `${store.promptHeight}px` : undefined, }} > @@ -1699,7 +1670,7 @@ export default function Page() { @@ -1789,7 +1760,6 @@ export default function Page() { "sticky top-0 z-30 bg-background-stronger": true, "w-full": true, "px-4 md:px-6": true, - "md:max-w-200 md:mx-auto": !showTabs(), }} >
@@ -1814,13 +1784,7 @@ export default function Page() {
0}>
@@ -1868,10 +1832,7 @@ export default function Page() {
(promptDock = el)} class="absolute inset-x-0 bottom-0 pt-12 pb-4 flex flex-col justify-center items-center z-50 px-4 md:px-0 bg-gradient-to-t from-background-stronger via-background-stronger to-transparent pointer-events-none" > -
+
{(perm) => (
@@ -2000,7 +1956,7 @@ export default function Page() {
- +
- {/* Desktop tabs panel (Review + Context + Files) - hidden on mobile */} - + {/* Desktop side panel - hidden on mobile */} +