Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
4436678
fix(desktop): guard destroyed recovery windows (#37406)
Hona Jul 17, 2026
3a1c6df
fix(app): deduplicate diff summaries linearly (#37414)
Hona Jul 17, 2026
52f65f3
feat(console): promote Kimi K3 usage limits (#37442)
opencode-agent[bot] Jul 17, 2026
a414e06
go: kimi k3 2x usage
fwang Jul 17, 2026
90291fc
chore: generate
opencode-agent[bot] Jul 17, 2026
3238daa
Revert "chore: generate"
fwang Jul 17, 2026
f7d5a1c
feat(desktop): align provider onboarding dialogs (#36733)
usrnk1 Jul 17, 2026
aca52ba
chore: generate
opencode-agent[bot] Jul 17, 2026
b527f60
feat(app): update review panel tooltip to v2 (#37095)
arvsrn Jul 17, 2026
ed926be
feat(app): review panel reactivity improvements (#37089)
arvsrn Jul 17, 2026
c0a258b
feat(session-ui): rewrite v2 prompt input (#37102)
Brendonovich Jul 17, 2026
82a3270
chore: generate
opencode-agent[bot] Jul 17, 2026
86978e7
feat(ui): updates to design system (#37471)
arvsrn Jul 17, 2026
ba6cf38
feat(desktop): add focus debug toggle (#37465)
Brendonovich Jul 17, 2026
4bffbb6
chore: update nix node_modules hashes
opencode-agent[bot] Jul 17, 2026
efb6cc2
fix(session-ui): preserve prompt editing behavior (#37483)
Brendonovich Jul 17, 2026
be08207
docs(go): update DeepSeek and MiMo pricing (#37509)
MrMushrooooom Jul 17, 2026
518b0bf
chore: generate
opencode-agent[bot] Jul 17, 2026
69a8066
fix(stats): polish comparison chart
adamdotdevin Jul 17, 2026
ed64643
chore: sync upstream dev through 69a80663a
LogicLyra Jul 17, 2026
c80dde5
fix(app): preserve v2 prompt structure and localization
LogicLyra Jul 17, 2026
24f2168
fix(desktop): harden focus debug toggle
LogicLyra Jul 17, 2026
169366a
fix(session-ui): preserve structured prompt editing
LogicLyra Jul 18, 2026
82d3a2f
fix(app): defer provider row activation
LogicLyra Jul 18, 2026
8f6a0e6
fix(session-ui): preserve command focus targets
LogicLyra Jul 18, 2026
8167c1b
fix(session-ui): isolate prompt cursor module
LogicLyra Jul 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions nix/hashes.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"nodeModules": {
"x86_64-linux": "sha256-F1luclnqCPQk9yxfmeSYGaM/nScf28yBu9K3Fv+Xd24=",
"aarch64-linux": "sha256-XW0XZnsCRkU3MFJH9TjMRYZHffzVy3cQyiNCkec2gl4=",
"aarch64-darwin": "sha256-bf8kvORs3Fs2UYLp3PekF+AJR7NKOcHb+fIQA79RtMk=",
"x86_64-darwin": "sha256-sBdQPkzd7JXNW6Lbi9JHiAsfHwdLwTKWY+uPeXAv2Nw="
"x86_64-linux": "sha256-XWoSYsV18a5uvjM3KuyGQWiFAtcCV0ij8brOLuFOW+g=",
"aarch64-linux": "sha256-50cI0CuzzMRO8vznPxTHRhgHirE7siOg6AmyKLhZ2oo=",
"aarch64-darwin": "sha256-5JpSBNyKqiB+gOrkxMNWyWGWLdNlbEiirA56GDhAS4E=",
"x86_64-darwin": "sha256-X1r9aOSEdMUqWtqa4a/tBK2nRSwIWfKOwqJfpFknhII="
}
}
9 changes: 3 additions & 6 deletions packages/app/e2e/regression/prompt-thinking-level.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,18 +54,15 @@ test("shows the V2 thinking level control while relevant", async ({ page }) => {
})

await page.goto(`/${base64Encode(directory)}/session/${sessionID}`)
const composer = page.locator('[data-component="session-composer"]')
const composer = page.locator('[data-component="prompt-input-v2"]')
const input = composer.locator('[data-component="prompt-input"]')
const control = composer.locator('[data-component="prompt-variant-control"]')
const control = composer.locator('button[title="Choose model variant"]')
await expectAppVisible(composer)

await idleComposer(page)
await expect(control).toBeHidden()

await composer.hover()
await expect(control).toBeVisible()

await control.locator('[data-action="prompt-model-variant"]').click()
await control.click()
const high = page.getByRole("menuitemradio", { name: "high" })
await expect(high).toBeVisible()
await page.mouse.move(0, 0)
Expand Down
2 changes: 1 addition & 1 deletion packages/app/e2e/smoke/session-timeline.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -736,5 +736,5 @@ async function switchTitlebarSession(page: Page, sessionID: string, title: strin
}

async function expectSessionReady(page: Page) {
await expectAppVisible(page.getByRole("textbox", { name: /Ask anything/i }))
await expectAppVisible(page.getByRole("textbox", { name: "Prompt" }))
}
59 changes: 58 additions & 1 deletion packages/app/src/components/debug-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { makeEventListener } from "@solid-primitives/event-listener"
import { Tooltip } from "@opencode-ai/ui/tooltip"
import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2"
import { useLanguage } from "@/context/language"
import { usePlatform } from "@/context/platform"

type Mem = Performance & {
memory?: {
Expand Down Expand Up @@ -107,15 +108,53 @@ function Cell(props: {
)
}

function FocusCell(props: { active: boolean; inline?: boolean; onClick: () => void }) {
const content = () => (
<button
type="button"
aria-label="Force focus styles on all interactive elements"
aria-pressed={props.active}
classList={{
"flex min-w-0 items-center font-mono uppercase hover:bg-surface-raised-base focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-[-2px] focus-visible:outline-border-focus": true,
"min-h-[20px] w-fit flex-row justify-start gap-1.5 rounded px-1.5 py-0.5 text-left": !!props.inline,
"min-h-[42px] w-full flex-col justify-center rounded-[8px] px-0.5 py-1 text-center": !props.inline,
"bg-surface-raised-base text-text-strong": props.active,
}}
onClick={props.onClick}
>
<span class="text-[10px] leading-none font-black tracking-[0.04em] opacity-70">FOCUS</span>
<span classList={{ "leading-none font-bold": true, "text-[11px]": !!props.inline, "text-[13px]": !props.inline }}>
{props.active ? "ON" : "OFF"}
</span>
</button>
)

if (props.inline) {
return (
<TooltipV2 value="Force focus styles on all interactive elements" placement="top">
{content()}
</TooltipV2>
)
}

return (
<Tooltip value="Force focus styles on all interactive elements" placement="top">
{content()}
</Tooltip>
)
}

export function DebugBar(props: { inline?: boolean } = {}) {
const language = useLanguage()
const platform = usePlatform()
const location = useLocation()
const routing = useIsRouting()
const [state, setState] = createStore({
cls: undefined as number | undefined,
delay: undefined as number | undefined,
fps: undefined as number | undefined,
gap: undefined as number | undefined,
focus: false,
heap: {
limit: undefined as number | undefined,
used: undefined as number | undefined,
Expand All @@ -142,6 +181,21 @@ export function DebugBar(props: { inline?: boolean } = {}) {
}
const longv = () => (state.long.count === undefined ? na() : `${time(state.long.block) ?? na()}/${state.long.count}`)
const navv = () => (state.nav.pending ? "..." : (time(state.nav.dur) ?? na()))
const toggleFocus = async () => {
if (!platform.setForceFocus) return
const enabled = !state.focus
try {
await platform.setForceFocus(enabled)
} catch {
await platform.setForceFocus(false).catch(() => undefined)
return
}
setState("focus", enabled)
}

onCleanup(() => {
if (state.focus) void platform.setForceFocus?.(false).catch(() => undefined)
})

let prev = ""
let start = 0
Expand Down Expand Up @@ -490,8 +544,11 @@ export function DebugBar(props: { inline?: boolean } = {}) {
bad={bad(heap(), 0.8)}
dim={state.heap.used === undefined}
inline={props.inline}
wide
wide={!platform.setForceFocus}
/>
{platform.setForceFocus && (
<FocusCell active={state.focus} inline={props.inline} onClick={() => void toggleFocus()} />
)}
</div>
</aside>
)
Expand Down
73 changes: 73 additions & 0 deletions packages/app/src/components/dialog-connect-provider.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
// @ts-nocheck
import { Button } from "@opencode-ai/ui/button"
import { useDialog } from "@opencode-ai/ui/context/dialog"
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query"
import { mockProviderAuth } from "@/context/server-sync"
import { onCleanup, onMount } from "solid-js"
import { DialogConnectProvider, useProviderConnectController } from "./dialog-connect-provider"

function ConnectProviderDialogStory() {
const dialog = useDialog()
const open = () => dialog.show(() => <DialogConnectProvider />)

onMount(open)

return (
<Button variant="secondary" onClick={open}>
Open connect provider dialog
</Button>
)
}

function ProviderConnectionDialogStory(props) {
onCleanup(mockProviderAuth(props.provider, props.methods))
const dialog = useDialog()
const controller = useProviderConnectController()
controller.select(props.provider)
const open = () => dialog.show(() => <DialogConnectProvider controller={controller} />)

onMount(open)

return (
<Button variant="secondary" onClick={open}>
Open {props.provider} connection dialog
</Button>
)
}

function renderConnection(provider, methods) {
return () => (
<QueryClientProvider client={new QueryClient()}>
<ProviderConnectionDialogStory provider={provider} methods={methods} />
</QueryClientProvider>
)
}

export default {
title: "App/Dialogs/Connect Provider",
id: "app-dialog-connect-provider",
}

export const V2 = {
render: () => (
<QueryClientProvider client={new QueryClient()}>
<ConnectProviderDialogStory />
</QueryClientProvider>
),
}

export const ApiKey = {
render: renderConnection("openrouter", [{ type: "api", label: "API key" }]),
}

export const OpenCodeZen = {
render: renderConnection("opencode", [{ type: "api", label: "API key" }]),
}

export const LoginMethods = {
render: renderConnection("openai", [
{ type: "oauth", label: "ChatGPT Pro/Plus (browser)" },
{ type: "oauth", label: "ChatGPT Pro/Plus (headless)" },
{ type: "api", label: "API key" },
]),
}
Loading
Loading