Skip to content

Commit 9db2b70

Browse files
Merge branch 'dev' into fix/24286-web-ui-version-mismatch
2 parents 66bc5e7 + 55bafa2 commit 9db2b70

167 files changed

Lines changed: 2738 additions & 1681 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/sync-zed-extension.yml

Lines changed: 0 additions & 35 deletions
This file was deleted.

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ ts-dist
1515
.turbo
1616
**/.serena
1717
.serena/
18+
**/.omo
19+
.omo/
1820
/result
1921
refs
2022
Session.vim

.opencode/command/translate.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
description: translate English to other languages
3-
model: opencode/claude-opus-4-7
3+
model: opencode/claude-opus-4-8
44
---
55

66
run git diff and translate changed english doc and UI copy files to other international languages. Translate all languages in parallel to save time.

bun.lock

Lines changed: 41 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

nix/hashes.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
{
22
"nodeModules": {
3-
"x86_64-linux": "sha256-rQ8kz/fChREJWnwY2Jp2zp06TYesyd3hia44hdo8l+s=",
4-
"aarch64-linux": "sha256-t5WKzAN8NRO/4g2l+4V5SatK/LO3ZPBfmKjJFf/MsD4=",
5-
"aarch64-darwin": "sha256-QbNaxGNiKdJ0/mKaTUk392qsOvlRYVi5mTuMmFByEic=",
6-
"x86_64-darwin": "sha256-lewm6WvqxphR+rvXz9e7ZKvgu98MH3cxosvQkz3mLuA="
3+
"x86_64-linux": "sha256-+jW0q5WKHBKJw6LgbT39XccrorJRcOkFtLfzrUGBMUU=",
4+
"aarch64-linux": "sha256-ITpAwTHD6q3GQp/dYSJGt19ZSzja8ZrPdl4N+168ps4=",
5+
"aarch64-darwin": "sha256-lY3gef57ASnYkMnYSRm0cj3uxdxARcT3jTWQ5zojuZY=",
6+
"x86_64-darwin": "sha256-yHICQCPYYlJh4S+AyBnaEDK67U9affGBuddJiP/GzeM="
77
}
88
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import { expect, test, type Page } from "@playwright/test"
2+
import { base64Encode } from "@opencode-ai/core/util/encode"
3+
import { mockOpenCodeServer } from "../utils/mock-server"
4+
5+
const directory = "C:/OpenCode/PromptThinkingLevelRegression"
6+
const projectID = "proj_prompt_thinking_level_regression"
7+
const sessionID = "ses_prompt_thinking_level_regression"
8+
9+
test("shows the V2 thinking level control while relevant", async ({ page }) => {
10+
await mockOpenCodeServer(page, {
11+
directory,
12+
project: {
13+
id: projectID,
14+
worktree: directory,
15+
vcs: "git",
16+
name: "prompt-thinking-level-regression",
17+
time: { created: 1700000000000, updated: 1700000000000 },
18+
sandboxes: [],
19+
},
20+
provider: {
21+
all: [
22+
{
23+
id: "opencode",
24+
name: "OpenCode",
25+
models: {
26+
"thinking-model": {
27+
id: "thinking-model",
28+
name: "Thinking Model",
29+
limit: { context: 200_000 },
30+
variants: { high: {} },
31+
},
32+
},
33+
},
34+
],
35+
connected: ["opencode"],
36+
default: { providerID: "opencode", modelID: "thinking-model" },
37+
},
38+
sessions: [
39+
{
40+
id: sessionID,
41+
slug: "prompt-thinking-level-regression",
42+
projectID,
43+
directory,
44+
title: "Prompt thinking level regression",
45+
version: "dev",
46+
time: { created: 1700000000000, updated: 1700000000000 },
47+
},
48+
],
49+
pageMessages: () => ({ items: [] }),
50+
})
51+
await page.addInitScript(() => {
52+
localStorage.setItem("settings.v3", JSON.stringify({ general: { newLayoutDesigns: true } }))
53+
})
54+
55+
await page.goto(`/${base64Encode(directory)}/session/${sessionID}`)
56+
const composer = page.locator('[data-component="session-composer"]')
57+
const input = composer.locator('[data-component="prompt-input"]')
58+
const control = composer.locator('[data-component="prompt-variant-control"]')
59+
await expect(composer).toBeVisible()
60+
61+
await idleComposer(page)
62+
await expect(control).toBeHidden()
63+
64+
await composer.hover()
65+
await expect(control).toBeVisible()
66+
67+
await control.locator('[data-action="prompt-model-variant"]').click()
68+
const high = page.getByRole("option", { name: "high" })
69+
await expect(high).toBeVisible()
70+
await page.mouse.move(0, 0)
71+
await expect(control).toBeVisible()
72+
await expect(high).toBeVisible()
73+
await high.click()
74+
75+
await idleComposer(page)
76+
await input.focus()
77+
await expect(control).toBeVisible()
78+
79+
await idleComposer(page)
80+
await expect(control).toBeVisible()
81+
})
82+
83+
async function idleComposer(page: Page) {
84+
await page.mouse.move(0, 0)
85+
await page.evaluate(() => (document.activeElement as HTMLElement | null)?.blur())
86+
}

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

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
277277
draggingType: "image" | "@mention" | null
278278
mode: "normal" | "shell"
279279
applyingHistory: boolean
280+
variantOpen: boolean
280281
}>({
281282
popover: null,
282283
historyIndex: -1,
@@ -285,6 +286,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
285286
draggingType: null,
286287
mode: "normal",
287288
applyingHistory: false,
289+
variantOpen: false,
288290
})
289291
const [picker, setPicker] = createStore({
290292
projectOpen: false,
@@ -1101,6 +1103,8 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
11011103
)
11021104

11031105
const variants = createMemo(() => ["default", ...local.model.variant.list()])
1106+
// Check provider variants directly: `variants` also includes the UI-only default option.
1107+
const showVariantControl = createMemo(() => local.model.variant.list().length > 0)
11041108
const accepting = createMemo(() => {
11051109
const id = params.id
11061110
if (!id) return permission.isAutoAcceptingDirectory(sdk.directory)
@@ -1571,6 +1575,39 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
15711575
<ComposerPickerTrigger state={newProjectTriggerState()} />
15721576
</Show>
15731577
<ComposerModelControl state={modelControlState()} />
1578+
<Show when={store.mode !== "shell" && showVariantControl()}>
1579+
<div
1580+
data-component="prompt-variant-control"
1581+
classList={{
1582+
"hidden group-hover/prompt-input:block group-focus-within/prompt-input:block":
1583+
!local.model.variant.current() && !store.variantOpen,
1584+
}}
1585+
>
1586+
<TooltipKeybind
1587+
placement="top"
1588+
gutter={4}
1589+
title={language.t("command.model.variant.cycle")}
1590+
keybind={command.keybind("model.variant.cycle")}
1591+
>
1592+
<Select
1593+
size="normal"
1594+
options={variants()}
1595+
current={local.model.variant.current() ?? "default"}
1596+
label={(x) => (x === "default" ? language.t("common.default") : x)}
1597+
onOpenChange={(open) => setStore("variantOpen", open)}
1598+
onSelect={(value) => {
1599+
local.model.variant.set(value === "default" ? undefined : value)
1600+
restoreFocus()
1601+
}}
1602+
class="capitalize max-w-[160px] justify-start text-v2-text-text-faint"
1603+
valueClass="truncate text-[13px] font-[440] leading-5 text-v2-text-text-faint"
1604+
triggerStyle={control()}
1605+
triggerProps={{ "data-action": "prompt-model-variant" }}
1606+
variant="ghost"
1607+
/>
1608+
</TooltipKeybind>
1609+
</div>
1610+
</Show>
15741611
</div>
15751612
<Tooltip placement="top" inactive={!working() && blank()} value={tip()}>
15761613
<IconButton
@@ -1890,7 +1927,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
18901927
</TooltipKeybind>
18911928
</Show>
18921929
</div>
1893-
<Show when={variants().length > 2}>
1930+
<Show when={showVariantControl()}>
18941931
<div
18951932
data-component="prompt-variant-control"
18961933
style={providersShouldFadeIn() ? { animation: "fade-in 0.3s" } : undefined}

packages/app/src/components/titlebar.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,9 @@ type TitlebarV2RightState = {
707707
function TitlebarV2Right(props: { state: TitlebarV2RightState }) {
708708
return (
709709
<div class="relative z-20 flex shrink-0 items-center justify-end gap-0 overflow-visible">
710-
<TitlebarUpdateIconButton state={props.state.update} />
710+
<Show when={props.state.update.visible}>
711+
<TitlebarUpdateIconButton state={props.state.update} />
712+
</Show>
711713
<div id="opencode-titlebar-right" class="flex shrink-0 items-center justify-end gap-0" />
712714
</div>
713715
)

packages/app/src/pages/layout.tsx

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ import {
8989
} from "./layout/sidebar-workspace"
9090
import { ProjectDragOverlay, SortableProject, type ProjectSidebarContext } from "./layout/sidebar-project"
9191
import { SidebarContent } from "./layout/sidebar-shell"
92+
import { runUpdateAndRestart } from "./layout/update"
9293

9394
export default function Layout(props: ParentProps) {
9495
const [store, setStore, , ready] = persisted(
@@ -183,11 +184,7 @@ export default function Layout(props: ParentProps) {
183184
return updateQuery.data.version ?? ""
184185
}
185186
const installUpdate = () => {
186-
if (!platform.updateAndRestart) return
187-
setUpdate("installing", true)
188-
void platform.updateAndRestart().catch(() => {
189-
setUpdate("installing", false)
190-
})
187+
runUpdateAndRestart(platform.updateAndRestart, (installing) => setUpdate("installing", installing))
191188
}
192189
const titlebarUpdate: TitlebarUpdate = {
193190
version: updateVersion,
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { describe, expect, test } from "bun:test"
2+
import { runUpdateAndRestart } from "./update"
3+
4+
describe("runUpdateAndRestart", () => {
5+
test("clears the installing state when restart resolves without exiting", async () => {
6+
const states: boolean[] = []
7+
await new Promise<void>((resolve) => {
8+
runUpdateAndRestart(
9+
async () => {},
10+
(installing) => {
11+
states.push(installing)
12+
if (states.length === 2) resolve()
13+
},
14+
)
15+
})
16+
17+
expect(states).toEqual([true, false])
18+
})
19+
})

0 commit comments

Comments
 (0)