Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
50e5614
feat(gui-extensions): add lifetime primitives, typed composition and …
Hona Oct 2, 2026
25d6a31
refactor(gui-extensions): migrate built-ins to declared dependencies …
Hona Oct 2, 2026
5fd0468
refactor(gui-extensions): rename the SDK and host vocabulary
Hona Oct 2, 2026
82078c3
refactor(gui-extensions): host APIs as context properties and canonic…
Hona Oct 2, 2026
c16331a
refactor(gui-extensions): rename summary and usage, move preferences …
Hona Oct 2, 2026
c817fed
test(app): keep What's New behaviour after its move to the updater
Hona Oct 2, 2026
a52aeb1
docs(gui-extensions): document the SDK and add an example extension
Hona Oct 2, 2026
5622cf9
ci: run the app component tests
Hona Oct 2, 2026
4c3f564
refactor(gui-extensions): make dependency and session ownership explicit
Hona Oct 2, 2026
2189ec1
chore(gui-extensions): integrate the latest v2 changes
Hona Oct 2, 2026
2ad98bf
docs(gui-extensions): teach the SDK through shipped plugins
Hona Oct 2, 2026
e87782e
fix(app): keep file tabs and migrated shortcuts correct across sessions
Hona Oct 3, 2026
8078ad0
fix(app): show extension dialogs after the interface mounts
Hona Oct 3, 2026
7c33ac1
fix(gui-extensions): close lifetime and storage gaps found in review
Hona Oct 4, 2026
a50079f
fix(gui-extensions): follow session moves and cancel dialogs reliably
Hona Oct 4, 2026
6202b3a
fix(app): wait for routes before extension writes and keep routed ses…
Hona Oct 4, 2026
b40a19d
ci: lint only the pull request's own changes
Hona Oct 4, 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
Prev Previous commit
Next Next commit
chore(gui-extensions): integrate the latest v2 changes
Keep upstream Git initialization, browser geometry transition tracking, unused-code cleanup and version updates while preserving typed composition, context properties and screen-owned extension models.
  • Loading branch information
Hona committed Oct 2, 2026
commit 2189ec15551fedf283aea95f2f4026830726f02d
1 change: 0 additions & 1 deletion packages/app/src/composer/editor/editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ import type {
ComposerComment,
ComposerFilePart,
ComposerOption,
ComposerPersistedState,
ComposerPrompt,
ComposerSkillPart,
ComposerSuggestion,
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/runtime/extension/embeds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -153,13 +153,13 @@ function EmbedView(props: EmbedProps & { input: Input; bridge: Bridge }) {
if (placed !== id) hide()
placed = id

const geometry = {
const box = {
visible,
bounds: { x: left, y: top, width: Math.max(0, right - left), height: Math.max(0, bottom - top) },
radius: Math.round((props.radius ?? 0) * zoom),
}

props.bridge.embed(id, rgba ? { ...geometry, background: [rgba[0], rgba[1], rgba[2], rgba[3]] as const } : geometry)
props.bridge.embed(id, rgba ? { ...box, background: [rgba[0], rgba[1], rgba[2], rgba[3]] as const } : box)
}

const tick = () => {
Expand Down
1 change: 0 additions & 1 deletion packages/app/src/runtime/extension/host-apis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
runWithOwner,
untrack,
type Accessor,
type Owner,
} from "solid-js"
import { createStore, produce, type Store } from "solid-js/store"
import type { Schema } from "effect"
Expand Down
114 changes: 0 additions & 114 deletions packages/app/src/settings/general/general.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,34 +18,8 @@ import { SettingsRow } from "@/settings/row"
import { createShellOptions, type ShellSettingsController } from "./controllers"
import "@/settings/settings.css"

const schemeOptions: ("system" | "light" | "dark")[] = ["system", "light", "dark"]

const tabLayoutOptions: ("horizontal" | "vertical")[] = ["horizontal", "vertical"]

const fontSettings = {
ui: {
action: "settings-ui-font",
title: "settings.general.row.uiFont.title",
description: "settings.general.row.uiFont.description",
font: "ui",
input: "setUI",
},
code: {
action: "settings-code-font",
title: "settings.general.row.font.title",
description: "settings.general.row.font.description",
font: "code",
input: "setCode",
},
terminal: {
action: "settings-terminal-font",
title: "settings.general.row.terminalFont.title",
description: "settings.general.row.terminalFont.description",
font: "terminal",
input: "setTerminal",
},
} as const

const AutoApprovePermissionsSetting: Component = () => {
const language = useLanguage()
const settings = useSettings()
Expand Down Expand Up @@ -187,94 +161,6 @@ const FollowUpBehaviorSetting: Component = () => {
)
}

const AppearanceSection: Component<{ controller: AppearanceSettingsController }> = (props) => {
const language = useLanguage()

return (
<div class="settings-section">
<h3 class="settings-section-title">{language.t("settings.general.section.appearance")}</h3>
<SettingsList>
<SettingsRow
title={language.t("settings.general.row.colorScheme.title")}
description={language.t("settings.general.row.colorScheme.description")}
>
<Select
data-action="settings-color-scheme"
options={schemeOptions}
current={schemeOptions.find((option) => option === props.controller.scheme.current())}
placement="bottom-end"
gutter={6}
label={(option) => {
if (option === "system") return language.t("theme.scheme.system")

if (option === "light") return language.t("theme.scheme.light")

return language.t("theme.scheme.dark")
}}
onSelect={(option) => option && props.controller.scheme.select(option)}
/>
</SettingsRow>

<SettingsRow
title={language.t("settings.general.row.theme.title")}
description={
<>
{language.t("settings.general.row.theme.description")}{" "}
<ExternalLink class="settings-link" href="https://opencode.ai/docs/themes/">
{language.t("common.learnMore")}
</ExternalLink>
</>
}
>
<Select
data-action="settings-theme"
options={props.controller.theme.options()}
current={props.controller.theme.current()}
placement="bottom-end"
gutter={6}
value={(option) => option.id}
label={(option) => option.name}
onSelect={props.controller.theme.select}
/>
</SettingsRow>

<FontSetting kind="ui" fonts={props.controller.fonts} />
<FontSetting kind="code" fonts={props.controller.fonts} />
<FontSetting kind="terminal" fonts={props.controller.fonts} />
</SettingsList>
</div>
)
}

const FontSetting: Component<{
kind: "ui" | "code" | "terminal"
fonts: AppearanceSettingsController["fonts"]
}> = (props) => {
const language = useLanguage()
const config = () => fontSettings[props.kind]

return (
<SettingsRow title={language.t(config().title)} description={language.t(config().description)}>
<div class="w-full sm:w-[220px]">
<TextInput
data-action={config().action}
type="text"
appearance="base"
value={props.fonts[config().font]().value}
onInput={(event) => props.fonts[config().input](event.currentTarget.value)}
placeholder={props.fonts[config().font]().placeholder}
spellcheck={false}
autocorrect="off"
autocomplete="off"
autocapitalize="off"
aria-label={language.t(config().title)}
style={{ "font-family": props.fonts[config().font]().family }}
/>
</div>
</SettingsRow>
)
}

const LanguageSetting = () => {
const language = useLanguage()

Expand Down
1 change: 0 additions & 1 deletion packages/app/src/shell/commands/palette.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import { displayName } from "@opencode/ui/project-avatar"
import { resolveProjectForSession } from "@/shell/layout/helpers"
import { useExtensionHost } from "@/runtime/extension/host"
import { useExtensionAttachment } from "@/runtime/extension/host-apis"
import { useServer } from "@/runtime/server/current"
import { looksLikeSessionID } from "@/session/search"

export type CommandPaletteEntry = {
Expand Down
54 changes: 53 additions & 1 deletion packages/gui-extensions/src/review/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function createReviewModel(input: {
const directory = () => screen.file.root

// The filter is transient by design: a persisted filter would silently hide files after a reload.
const [state, setState] = createStore({ filter: "" })
const [state, setState] = createStore({ filter: "", initializingGit: false })

// The review panel's scroller and the file it scrolls to belong to the session the view shows.
const [scroll, setScroll] = createVisitState<HTMLDivElement | undefined>(undefined)
Expand Down Expand Up @@ -239,6 +239,58 @@ export function createReviewModel(input: {
return !diffQuery.isPending
}

const lifetime = { disposed: false }

onCleanup(() => {
lifetime.disposed = true
})

// Initializes Git for the routed session. The request keeps that session's object, so its server and location stay
// its own; the outcome applies only while the screen still routes it.
const initializeGit = () => {
if (state.initializingGit) return

const session = view()
const location = session.location

if (!location || !session.server.connected) {
showToast({ variant: "error", title: ctx.t("common.requestFailed") })

return
}

const current = () => !lifetime.disposed && !ctx.signal.aborted && view().key === session.key

setState("initializingGit", true)
void session.server.client.vcs
.init({ location, provider: "git" })
.then(async () => {
if (!current()) return

const data = session.server.data

data.project.invalidate()
data.session.invalidate(session.id)
data.location.invalidate(location)
data.location.vcs.invalidate(location)
await data.project.sync()
await data.session.sync(session.id)
await Promise.all([data.location.syncInfo(location), data.location.vcs.sync(location)])
})
.catch((error) => {
if (!current()) return

showToast({
variant: "error",
title: ctx.t("common.requestFailed"),
description: error instanceof Error ? error.message : undefined,
})
})
.finally(() => {
if (current()) setState("initializingGit", false)
})
}

const loadDiff = async (path: string, version?: number): Promise<FileDiffInfo | undefined> => {
const source = diffs().find((diff) => diff.file === path)

Expand Down
2 changes: 1 addition & 1 deletion packages/gui-extensions/src/sdk/host-apis.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Data } from "@opencode/client/solid"
import type { LocationRef, OpenCodeClient, ProjectListOutput, WorktreeDirectory } from "@opencode/client/promise"
import type { Schema } from "effect"
import type { Accessor, JSX } from "solid-js"
import type { JSX } from "solid-js"
import type { Store } from "solid-js/store"
import type { Cleanup, OS, Persisted, StoreFrom } from "./core"
import type { IconName, Link } from "./points"
Expand Down
1 change: 0 additions & 1 deletion packages/gui-extensions/src/updater/actions.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import type { JSX } from "solid-js"
import { Button } from "@opencode/ui/button"
import { Dialog, DialogFooter, DialogHeader, DialogTitleGroup } from "@opencode/ui/dialog"
import { Icon } from "@opencode/ui/icon"
Expand Down
Loading
You are viewing a condensed version of this merge commit. You can view the full changes here.