Skip to content
This repository was archived by the owner on Apr 1, 2026. It is now read-only.

Commit a8836c5

Browse files
committed
wip(desktop): layout improvements
1 parent 779a276 commit a8836c5

27 files changed

Lines changed: 880 additions & 316 deletions

packages/desktop/index.html

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,12 @@
88
<title>OpenCode</title>
99
</head>
1010
<body class="antialiased overscroll-none select-none text-12-regular">
11-
<!-- <script> -->
12-
<!-- ;(function () { -->
13-
<!-- const savedTheme = localStorage.getItem("theme") || "opencode" -->
14-
<!-- const savedDarkMode = localStorage.getItem("darkMode") !== "false" -->
15-
<!-- document.documentElement.setAttribute("data-theme", savedTheme) -->
16-
<!-- document.documentElement.setAttribute("data-dark", savedDarkMode.toString()) -->
17-
<!-- })() -->
18-
<!-- </script> -->
11+
<script>
12+
;(function () {
13+
const savedTheme = localStorage.getItem("theme") || "oc-2-paper"
14+
document.documentElement.setAttribute("data-theme", savedTheme)
15+
})()
16+
</script>
1917
<noscript>You need to enable JavaScript to run this app.</noscript>
2018
<div id="root"></div>
2119
<script src="/src/index.tsx" type="module"></script>

packages/desktop/src/components/prompt-input.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
347347
<Show when={store.popoverIsOpen}>
348348
<div
349349
class="absolute inset-x-0 -top-3 -translate-y-full origin-bottom-left max-h-[252px] min-h-10
350-
overflow-auto no-scrollbar flex flex-col p-2 pb-0 rounded-2xl
350+
overflow-auto no-scrollbar flex flex-col p-2 pb-0 rounded-md
351351
border border-border-base bg-surface-raised-stronger-non-alpha shadow-md"
352352
>
353353
<Show when={flat().length > 0} fallback={<div class="text-text-weak px-2">No matching files</div>}>
@@ -382,7 +382,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
382382
onSubmit={handleSubmit}
383383
classList={{
384384
"bg-surface-raised-stronger-non-alpha border border-border-strong-base": true,
385-
"rounded-2xl overflow-clip focus-within:border-transparent focus-within:shadow-xs-border-select": true,
385+
"rounded-md overflow-clip focus-within:border-transparent focus-within:shadow-xs-border-select": true,
386386
[props.class ?? ""]: !!props.class,
387387
}}
388388
>
@@ -396,17 +396,17 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
396396
onInput={handleInput}
397397
onKeyDown={handleKeyDown}
398398
classList={{
399-
"w-full p-3 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
399+
"w-full px-5 py-3 text-14-regular text-text-strong focus:outline-none whitespace-pre-wrap": true,
400400
"[&>[data-type=file]]:text-icon-info-active": true,
401401
}}
402402
/>
403403
<Show when={!session.prompt.dirty()}>
404-
<div class="absolute top-0 left-0 p-3 text-14-regular text-text-weak pointer-events-none">
404+
<div class="absolute top-0 left-0 px-5 py-3 text-14-regular text-text-weak pointer-events-none">
405405
Plan and build anything
406406
</div>
407407
</Show>
408408
</div>
409-
<div class="p-3 flex items-center justify-between">
409+
<div class="relative p-3 flex items-center justify-between">
410410
<div class="flex items-center justify-start gap-1">
411411
<Select
412412
options={local.agent.list().map((agent) => agent.name)}
@@ -489,7 +489,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
489489
disabled={!session.prompt.dirty() && !session.working()}
490490
icon={session.working() ? "stop" : "arrow-up"}
491491
variant="primary"
492-
class="rounded-full"
492+
class="h-10 w-8 absolute right-2 bottom-2"
493493
/>
494494
</Tooltip>
495495
</div>
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { useLocal } from "@/context/local"
2+
import { useSession } from "@/context/session"
3+
import { FileIcon } from "@/ui"
4+
import { getDirectory, getFilename } from "@/utils"
5+
import { Accordion, Button, Diff, DiffChanges, Icon, IconButton, Tooltip } from "@opencode-ai/ui"
6+
import { For, Match, Show, Switch } from "solid-js"
7+
import { StickyAccordionHeader } from "./sticky-accordion-header"
8+
import { createStore } from "solid-js/store"
9+
10+
export const SessionReview = (props: { split?: boolean; class?: string; hideExpand?: boolean }) => {
11+
const local = useLocal()
12+
const session = useSession()
13+
const [store, setStore] = createStore({
14+
open: session.diffs().map((d) => d.file),
15+
})
16+
17+
const handleChange = (open: string[]) => {
18+
setStore("open", open)
19+
}
20+
21+
const handleExpandOrCollapseAll = () => {
22+
if (store.open.length > 0) {
23+
setStore("open", [])
24+
} else {
25+
setStore(
26+
"open",
27+
session.diffs().map((d) => d.file),
28+
)
29+
}
30+
}
31+
32+
return (
33+
<div
34+
classList={{
35+
"flex flex-col gap-3 h-full overflow-y-auto no-scrollbar": true,
36+
[props.class ?? ""]: !!props.class,
37+
}}
38+
>
39+
<div class="sticky top-0 z-20 bg-background-stronger h-8 shrink-0 flex justify-between items-center self-stretch">
40+
<div class="text-14-medium text-text-strong">Session changes</div>
41+
<div class="flex items-center gap-x-4 pr-px">
42+
<Button size="normal" icon="chevron-grabber-vertical" onClick={handleExpandOrCollapseAll}>
43+
<Switch>
44+
<Match when={store.open.length > 0}>Collapse all</Match>
45+
<Match when={true}>Expand all</Match>
46+
</Switch>
47+
</Button>
48+
<Show when={!props.hideExpand}>
49+
<Tooltip value="Open in tab">
50+
<IconButton
51+
icon="expand"
52+
variant="ghost"
53+
onClick={() => {
54+
local.layout.review.tab()
55+
session.layout.setActiveTab("review")
56+
}}
57+
/>
58+
</Tooltip>
59+
</Show>
60+
</div>
61+
</div>
62+
<Accordion multiple value={store.open} onChange={handleChange}>
63+
<For each={session.diffs()}>
64+
{(diff) => (
65+
<Accordion.Item value={diff.file}>
66+
<StickyAccordionHeader class="top-11 data-expanded:before:-top-11">
67+
<Accordion.Trigger class="bg-background-stronger">
68+
<div class="flex items-center justify-between w-full gap-5">
69+
<div class="grow flex items-center gap-5 min-w-0">
70+
<FileIcon node={{ path: diff.file, type: "file" }} class="shrink-0 size-4" />
71+
<div class="flex grow min-w-0">
72+
<Show when={diff.file.includes("/")}>
73+
<span class="text-text-base truncate-start">{getDirectory(diff.file)}&lrm;</span>
74+
</Show>
75+
<span class="text-text-strong shrink-0">{getFilename(diff.file)}</span>
76+
</div>
77+
</div>
78+
<div class="shrink-0 flex gap-4 items-center justify-end">
79+
<DiffChanges changes={diff} />
80+
<Icon name="chevron-grabber-vertical" size="small" />
81+
</div>
82+
</div>
83+
</Accordion.Trigger>
84+
</StickyAccordionHeader>
85+
<Accordion.Content>
86+
<Diff
87+
diffStyle={props.split ? "split" : "unified"}
88+
before={{
89+
name: diff.file!,
90+
contents: diff.before!,
91+
}}
92+
after={{
93+
name: diff.file!,
94+
contents: diff.after!,
95+
}}
96+
/>
97+
</Accordion.Content>
98+
</Accordion.Item>
99+
)}
100+
</For>
101+
</Accordion>
102+
</div>
103+
)
104+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { Accordion } from "@opencode-ai/ui"
2+
import { ParentProps } from "solid-js"
3+
4+
export function StickyAccordionHeader(props: ParentProps<{ class?: string }>) {
5+
return (
6+
<Accordion.Header
7+
classList={{
8+
"sticky top-0 data-expanded:z-10": true,
9+
"data-expanded:before:content-[''] data-expanded:before:z-[-10]": true,
10+
"data-expanded:before:absolute data-expanded:before:inset-0 data-expanded:before:bg-background-stronger": true,
11+
[props.class ?? ""]: !!props.class,
12+
}}
13+
>
14+
{props.children}
15+
</Accordion.Header>
16+
)
17+
}

packages/desktop/src/context/local.tsx

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -465,11 +465,11 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
465465
width: 240,
466466
},
467467
review: {
468-
state: "closed" as "open" | "closed" | "tab",
468+
state: "pane" as "pane" | "tab",
469469
},
470470
}),
471471
{
472-
name: "default-layout",
472+
name: "_default-layout",
473473
},
474474
)
475475

@@ -492,11 +492,8 @@ export const { use: useLocal, provider: LocalProvider } = createSimpleContext({
492492
},
493493
review: {
494494
state: createMemo(() => store.review?.state ?? "closed"),
495-
open() {
496-
setStore("review", "state", "open")
497-
},
498-
close() {
499-
setStore("review", "state", "closed")
495+
pane() {
496+
setStore("review", "state", "pane")
500497
},
501498
tab() {
502499
setStore("review", "state", "tab")

0 commit comments

Comments
 (0)