diff --git a/packages/desktop/src/components/session-review.tsx b/packages/desktop/src/components/session-review.tsx
new file mode 100644
index 00000000..8aefbbd4
--- /dev/null
+++ b/packages/desktop/src/components/session-review.tsx
@@ -0,0 +1,104 @@
+import { useLocal } from "@/context/local"
+import { useSession } from "@/context/session"
+import { FileIcon } from "@/ui"
+import { getDirectory, getFilename } from "@/utils"
+import { Accordion, Button, Diff, DiffChanges, Icon, IconButton, Tooltip } from "@opencode-ai/ui"
+import { For, Match, Show, Switch } from "solid-js"
+import { StickyAccordionHeader } from "./sticky-accordion-header"
+import { createStore } from "solid-js/store"
+
+export const SessionReview = (props: { split?: boolean; class?: string; hideExpand?: boolean }) => {
+ const local = useLocal()
+ const session = useSession()
+ const [store, setStore] = createStore({
+ open: session.diffs().map((d) => d.file),
+ })
+
+ const handleChange = (open: string[]) => {
+ setStore("open", open)
+ }
+
+ const handleExpandOrCollapseAll = () => {
+ if (store.open.length > 0) {
+ setStore("open", [])
+ } else {
+ setStore(
+ "open",
+ session.diffs().map((d) => d.file),
+ )
+ }
+ }
+
+ return (
+
+
+
Session changes
+
+
+
+
+ {
+ local.layout.review.tab()
+ session.layout.setActiveTab("review")
+ }}
+ />
+
+
+
+
+
+
+ {(diff) => (
+
+
+
+
+
+
+
+
+ {getDirectory(diff.file)}
+
+ {getFilename(diff.file)}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+
+ )
+}
diff --git a/packages/desktop/src/components/sticky-accordion-header.tsx b/packages/desktop/src/components/sticky-accordion-header.tsx
new file mode 100644
index 00000000..cb3f0d5e
--- /dev/null
+++ b/packages/desktop/src/components/sticky-accordion-header.tsx
@@ -0,0 +1,17 @@
+import { Accordion } from "@opencode-ai/ui"
+import { ParentProps } from "solid-js"
+
+export function StickyAccordionHeader(props: ParentProps<{ class?: string }>) {
+ return (
+
+ {props.children}
+
+ )
+}
diff --git a/packages/desktop/src/context/local.tsx b/packages/desktop/src/context/local.tsx
index 1785bdf0..1cef1c9f 100644
--- a/packages/desktop/src/context/local.tsx
+++ b/packages/desktop/src/context/local.tsx
@@ -465,11 +465,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
width: 240,
},
review: {
- state: "closed" as "open" | "closed" | "tab",
+ state: "pane" as "pane" | "tab",
},
}),
{
- name: "default-layout",
+ name: "_default-layout",
},
)
@@ -492,11 +492,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
},
review: {
state: createMemo(() => store.review?.state ?? "closed"),
- open() {
- setStore("review", "state", "open")
- },
- close() {
- setStore("review", "state", "closed")
+ pane() {
+ setStore("review", "state", "pane")
},
tab() {
setStore("review", "state", "tab")
diff --git a/packages/desktop/src/pages/session.tsx b/packages/desktop/src/pages/session.tsx
index f16729cc..0e5eb1f3 100644
--- a/packages/desktop/src/pages/session.tsx
+++ b/packages/desktop/src/pages/session.tsx
@@ -44,12 +44,14 @@ import {
useDragDropContext,
} from "@thisbeyond/solid-dnd"
import type { DragEvent, Transformer } from "@thisbeyond/solid-dnd"
-import type { JSX, ParentProps } from "solid-js"
+import type { JSX } from "solid-js"
import { useSync } from "@/context/sync"
import { type AssistantMessage as AssistantMessageType } from "@opencode-ai/sdk"
import { Markdown } from "@opencode-ai/ui"
import { Spinner } from "@/components/spinner"
import { useSession } from "@/context/session"
+import { StickyAccordionHeader } from "@/components/sticky-accordion-header"
+import { SessionReview } from "@/components/session-review"
export default function Page() {
const local = useLocal()
@@ -83,6 +85,15 @@ export default function Page() {
setStore("fileSelectOpen", true)
return
}
+ if (event.ctrlKey && event.key.toLowerCase() === "t") {
+ event.preventDefault()
+ const currentTheme = localStorage.getItem("theme") ?? "oc-1"
+ const themes = ["oc-1", "oc-2-paper"]
+ const nextTheme = themes[(themes.indexOf(currentTheme) + 1) % themes.length]
+ localStorage.setItem("theme", nextTheme)
+ document.documentElement.setAttribute("data-theme", nextTheme)
+ return
+ }
const focused = document.activeElement === inputRef
if (focused) {
@@ -216,18 +227,15 @@ export default function Page() {
// @ts-ignore
- props.onTabClick(props.tab)}>
+ props.onTabClose(props.tab)} />}
+ hideCloseButton
+ onClick={() => props.onTabClick(props.tab)}
+ >
{(f) => }
- props.onTabClose(props.tab)}
- />
@@ -277,38 +285,40 @@ export default function Page() {
-
- Chat
-
-
- {session.usage.context() ?? 0}%
-
+
+
+
Chat
+
+
+ {session.usage.context() ?? 0}%
+
+
-
-
-
-
-
-
Review
-
-
- {session.info()?.summary?.files ?? 0}
-
+
+ }
+ >
+
+
+
-
+
+
Review
+
+
+ {session.info()?.summary?.files ?? 0}
+
+
+
@@ -333,24 +343,17 @@ export default function Page() {
-
+
-
-
-
-
-
1}>
@@ -358,8 +361,8 @@ export default function Page() {
role="list"
classList={{
"mr-8 shrink-0 flex flex-col items-start": true,
- "absolute right-full w-60 @7xl:gap-2": local.layout.review.state() !== "open",
- "mt-1": local.layout.review.state() === "open",
+ "absolute right-full w-60 mt-3 @7xl:gap-2 @7xl:mt-1": local.layout.review.state() === "tab",
+ "mt-3": local.layout.review.state() === "pane",
}}
>
@@ -379,7 +382,7 @@ export default function Page() {
div]:bg-icon-strong-base data-[active=true]:[&>div]:w-full": true,
- "@7xl:hidden": local.layout.review.state() !== "open",
+ "@7xl:hidden": local.layout.review.state() === "tab",
}}
>
@@ -407,7 +410,7 @@ export default function Page() {