summaryrefslogtreecommitdiffhomepage
path: root/packages/app/src/components
diff options
context:
space:
mode:
Diffstat (limited to 'packages/app/src/components')
-rw-r--r--packages/app/src/components/dialog-connect-provider.tsx4
-rw-r--r--packages/app/src/components/dialog-edit-project.tsx2
-rw-r--r--packages/app/src/components/dialog-fork.tsx2
-rw-r--r--packages/app/src/components/dialog-select-directory.tsx2
-rw-r--r--packages/app/src/components/dialog-select-file.tsx8
-rw-r--r--packages/app/src/components/dialog-select-server.tsx6
-rw-r--r--packages/app/src/components/file-tree.tsx3
-rw-r--r--packages/app/src/components/prompt-input.tsx230
-rw-r--r--packages/app/src/components/prompt-input/build-request-parts.ts2
-rw-r--r--packages/app/src/components/prompt-input/context-items.tsx2
-rw-r--r--packages/app/src/components/prompt-input/slash-popover.tsx2
-rw-r--r--packages/app/src/components/prompt-input/submit.test.ts2
-rw-r--r--packages/app/src/components/prompt-input/submit.ts29
-rw-r--r--packages/app/src/components/session-context-usage.tsx2
-rw-r--r--packages/app/src/components/session/session-context-tab.tsx4
-rw-r--r--packages/app/src/components/session/session-header.tsx4
-rw-r--r--packages/app/src/components/session/session-new-view.tsx2
-rw-r--r--packages/app/src/components/session/session-sortable-tab.tsx2
-rw-r--r--packages/app/src/components/session/session-sortable-terminal-tab.tsx2
-rw-r--r--packages/app/src/components/terminal.tsx24
-rw-r--r--packages/app/src/components/titlebar.tsx75
21 files changed, 200 insertions, 209 deletions
diff --git a/packages/app/src/components/dialog-connect-provider.tsx b/packages/app/src/components/dialog-connect-provider.tsx
index 41225d02a..e30574379 100644
--- a/packages/app/src/components/dialog-connect-provider.tsx
+++ b/packages/app/src/components/dialog-connect-provider.tsx
@@ -327,7 +327,7 @@ export function DialogConnectProvider(props: { provider: string }) {
if (loading()) return
if (methods().length === 1) {
auto = true
- selectMethod(0)
+ void selectMethod(0)
}
})
@@ -373,7 +373,7 @@ export function DialogConnectProvider(props: { provider: string }) {
key={(m) => m?.label}
onSelect={async (selected, index) => {
if (!selected) return
- selectMethod(index)
+ void selectMethod(index)
}}
>
{(i) => (
diff --git a/packages/app/src/components/dialog-edit-project.tsx b/packages/app/src/components/dialog-edit-project.tsx
index eb962f47e..ea5d70065 100644
--- a/packages/app/src/components/dialog-edit-project.tsx
+++ b/packages/app/src/components/dialog-edit-project.tsx
@@ -9,7 +9,7 @@ import { createStore } from "solid-js/store"
import { useGlobalSDK } from "@/context/global-sdk"
import { useGlobalSync } from "@/context/global-sync"
import { type LocalProject, getAvatarColors } from "@/context/layout"
-import { getFilename } from "@opencode-ai/util/path"
+import { getFilename } from "@opencode-ai/shared/util/path"
import { Avatar } from "@opencode-ai/ui/avatar"
import { useLanguage } from "@/context/language"
diff --git a/packages/app/src/components/dialog-fork.tsx b/packages/app/src/components/dialog-fork.tsx
index 9e1b896fa..710618c30 100644
--- a/packages/app/src/components/dialog-fork.tsx
+++ b/packages/app/src/components/dialog-fork.tsx
@@ -9,7 +9,7 @@ import { List } from "@opencode-ai/ui/list"
import { showToast } from "@opencode-ai/ui/toast"
import { extractPromptFromParts } from "@/utils/prompt"
import type { TextPart as SDKTextPart } from "@opencode-ai/sdk/v2/client"
-import { base64Encode } from "@opencode-ai/util/encode"
+import { base64Encode } from "@opencode-ai/shared/util/encode"
import { useLanguage } from "@/context/language"
interface ForkableMessage {
diff --git a/packages/app/src/components/dialog-select-directory.tsx b/packages/app/src/components/dialog-select-directory.tsx
index 91e23f8ff..903cb1915 100644
--- a/packages/app/src/components/dialog-select-directory.tsx
+++ b/packages/app/src/components/dialog-select-directory.tsx
@@ -3,7 +3,7 @@ import { Dialog } from "@opencode-ai/ui/dialog"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { List } from "@opencode-ai/ui/list"
import type { ListRef } from "@opencode-ai/ui/list"
-import { getDirectory, getFilename } from "@opencode-ai/util/path"
+import { getDirectory, getFilename } from "@opencode-ai/shared/util/path"
import fuzzysort from "fuzzysort"
import { createMemo, createResource, createSignal } from "solid-js"
import { useGlobalSDK } from "@/context/global-sdk"
diff --git a/packages/app/src/components/dialog-select-file.tsx b/packages/app/src/components/dialog-select-file.tsx
index e21be77fb..186906f92 100644
--- a/packages/app/src/components/dialog-select-file.tsx
+++ b/packages/app/src/components/dialog-select-file.tsx
@@ -4,8 +4,8 @@ import { FileIcon } from "@opencode-ai/ui/file-icon"
import { Icon } from "@opencode-ai/ui/icon"
import { Keybind } from "@opencode-ai/ui/keybind"
import { List } from "@opencode-ai/ui/list"
-import { base64Encode } from "@opencode-ai/util/encode"
-import { getDirectory, getFilename } from "@opencode-ai/util/path"
+import { base64Encode } from "@opencode-ai/shared/util/encode"
+import { getDirectory, getFilename } from "@opencode-ai/shared/util/path"
import { useNavigate } from "@solidjs/router"
import { createMemo, createSignal, Match, onCleanup, Show, Switch } from "solid-js"
import { formatKeybind, useCommand, type CommandOption } from "@/context/command"
@@ -348,8 +348,8 @@ export function DialogSelectFile(props: { mode?: DialogSelectFileMode; onOpenFil
const open = (path: string) => {
const value = file.tab(path)
- tabs().open(value)
- file.load(path)
+ void tabs().open(value)
+ void file.load(path)
if (!view().reviewPanel.opened()) view().reviewPanel.open()
layout.fileTree.setTab("all")
props.onOpenFile?.(path)
diff --git a/packages/app/src/components/dialog-select-server.tsx b/packages/app/src/components/dialog-select-server.tsx
index ca4c42a37..dd92edec3 100644
--- a/packages/app/src/components/dialog-select-server.tsx
+++ b/packages/app/src/components/dialog-select-server.tsx
@@ -344,7 +344,7 @@ export function DialogSelectServer() {
createEffect(() => {
items()
- refreshHealth()
+ void refreshHealth()
const interval = setInterval(refreshHealth, 10_000)
onCleanup(() => clearInterval(interval))
})
@@ -498,7 +498,7 @@ export function DialogSelectServer() {
async function handleRemove(url: ServerConnection.Key) {
server.remove(url)
if ((await platform.getDefaultServer?.()) === url) {
- platform.setDefaultServer?.(null)
+ void platform.setDefaultServer?.(null)
}
}
@@ -536,7 +536,7 @@ export function DialogSelectServer() {
items={sortedItems}
key={(x) => x.http.url}
onSelect={(x) => {
- if (x) select(x)
+ if (x) void select(x)
}}
divider={true}
class="px-5 [&_[data-slot=list-search-wrapper]]:w-full [&_[data-slot=list-scroll]]h-[300px] [&_[data-slot=list-scroll]]:overflow-y-auto [&_[data-slot=list-items]]:bg-surface-base [&_[data-slot=list-items]]:rounded-md [&_[data-slot=list-item]]:min-h-14 [&_[data-slot=list-item]]:p-3 [&_[data-slot=list-item]]:!bg-transparent"
diff --git a/packages/app/src/components/file-tree.tsx b/packages/app/src/components/file-tree.tsx
index 930832fb6..211ce05ef 100644
--- a/packages/app/src/components/file-tree.tsx
+++ b/packages/app/src/components/file-tree.tsx
@@ -14,7 +14,6 @@ import {
Switch,
untrack,
type ComponentProps,
- type JSXElement,
type ParentProps,
} from "solid-js"
import { Dynamic } from "solid-js/web"
@@ -149,7 +148,7 @@ const FileTreeNode = (
classList={{
"w-full min-w-0 h-6 flex items-center justify-start gap-x-1.5 rounded-md px-1.5 py-0 text-left hover:bg-surface-raised-base-hover active:bg-surface-base-active transition-colors cursor-pointer": true,
"bg-surface-base-active": local.node.path === local.active,
- ...(local.classList ?? {}),
+ ...local.classList,
[local.class ?? ""]: !!local.class,
[local.nodeClass ?? ""]: !!local.nodeClass,
}}
diff --git a/packages/app/src/components/prompt-input.tsx b/packages/app/src/components/prompt-input.tsx
index eedbc91cf..156b0b3a4 100644
--- a/packages/app/src/components/prompt-input.tsx
+++ b/packages/app/src/components/prompt-input.tsx
@@ -35,7 +35,6 @@ import { useLanguage } from "@/context/language"
import { usePlatform } from "@/context/platform"
import { useSessionLayout } from "@/pages/session/session-layout"
import { createSessionTabs } from "@/pages/session/helpers"
-import { promptEnabled, promptProbe } from "@/testing/prompt"
import { createTextFragment, getCursorPosition, setCursorPosition, setRangeEdge } from "./prompt-input/editor-dom"
import { createPromptAttachments } from "./prompt-input/attachments"
import { ACCEPTED_FILE_TYPES } from "./prompt-input/files"
@@ -55,6 +54,8 @@ import { PromptImageAttachments } from "./prompt-input/image-attachments"
import { PromptDragOverlay } from "./prompt-input/drag-overlay"
import { promptPlaceholder } from "./prompt-input/placeholder"
import { ImagePreview } from "@opencode-ai/ui/image-preview"
+import { useQuery } from "@tanstack/solid-query"
+import { loadAgentsQuery, loadProvidersQuery } from "@/context/global-sync/bootstrap"
interface PromptInputProps {
class?: string
@@ -101,6 +102,7 @@ const NON_EMPTY_TEXT = /[^\s\u200B]/
export const PromptInput: Component<PromptInputProps> = (props) => {
const sdk = useSDK()
+
const sync = useSync()
const local = useLocal()
const files = useFile()
@@ -213,9 +215,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
if (!view().reviewPanel.opened()) view().reviewPanel.open()
layout.fileTree.setTab("all")
const tab = files.tab(item.path)
- tabs().open(tab)
+ void tabs().open(tab)
tabs().setActive(tab)
- Promise.resolve(files.load(item.path)).finally(() => queueCommentFocus())
+ void Promise.resolve(files.load(item.path)).finally(() => queueCommentFocus())
}
const recent = createMemo(() => {
@@ -639,7 +641,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
const handleSlashSelect = (cmd: SlashCommand | undefined) => {
if (!cmd) return
- promptProbe.select(cmd.id)
closePopover()
const images = imageAttachments()
@@ -728,21 +729,6 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
element?.scrollIntoView({ block: "nearest", behavior: "smooth" })
})
})
-
- if (promptEnabled()) {
- createEffect(() => {
- promptProbe.set({
- popover: store.popover,
- slash: {
- active: slashActive() ?? null,
- ids: slashFlat().map((cmd) => cmd.id),
- },
- })
- })
-
- onCleanup(() => promptProbe.clear())
- }
-
const selectPopoverActive = () => {
if (store.popover === "at") {
const items = atFlat()
@@ -1156,7 +1142,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
}
if (working()) {
- abort()
+ void abort()
event.preventDefault()
event.stopPropagation()
return
@@ -1222,7 +1208,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
return
}
if (working()) {
- abort()
+ void abort()
event.preventDefault()
}
return
@@ -1262,10 +1248,18 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
) {
return
}
- handleSubmit(event)
+ void handleSubmit(event)
}
}
+ const agentsQuery = useQuery(() => loadAgentsQuery(sdk.directory))
+ const agentsLoading = () => agentsQuery.isLoading
+
+ const globalProvidersQuery = useQuery(() => loadProvidersQuery(null))
+ const providersQuery = useQuery(() => loadProvidersQuery(sdk.directory))
+
+ const providersLoading = () => agentsLoading() || providersQuery.isLoading || globalProvidersQuery.isLoading
+
return (
<div class="relative size-full _max-h-[320px] flex flex-col gap-0">
<PromptPopover
@@ -1461,53 +1455,89 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
<span class="truncate text-13-medium text-text-strong">{language.t("prompt.mode.shell")}</span>
<div class="size-4 shrink-0" />
</div>
- <div class="flex items-center gap-1.5 min-w-0 flex-1">
- <div data-component="prompt-agent-control">
- <TooltipKeybind
- placement="top"
- gutter={4}
- title={language.t("command.agent.cycle")}
- keybind={command.keybind("agent.cycle")}
- >
- <Select
- size="normal"
- options={agentNames()}
- current={local.agent.current()?.name ?? ""}
- onSelect={(value) => {
- local.agent.set(value)
- restoreFocus()
- }}
- class="capitalize max-w-[160px] text-text-base"
- valueClass="truncate text-13-regular text-text-base"
- triggerStyle={control()}
- triggerProps={{ "data-action": "prompt-agent" }}
- variant="ghost"
- />
- </TooltipKeybind>
- </div>
- <Show when={store.mode !== "shell"}>
- <div data-component="prompt-model-control">
- <Show
- when={providers.paid().length > 0}
- fallback={
+ <div class="flex items-center gap-1.5 min-w-0 flex-1 h-7">
+ <Show when={!agentsLoading()}>
+ <div data-component="prompt-agent-control">
+ <TooltipKeybind
+ placement="top"
+ gutter={4}
+ title={language.t("command.agent.cycle")}
+ keybind={command.keybind("agent.cycle")}
+ >
+ <Select
+ size="normal"
+ options={agentNames()}
+ current={local.agent.current()?.name ?? ""}
+ onSelect={(value) => {
+ local.agent.set(value)
+ restoreFocus()
+ }}
+ class="capitalize max-w-[160px] text-text-base"
+ valueClass="truncate text-13-regular text-text-base"
+ triggerStyle={control()}
+ triggerProps={{ "data-action": "prompt-agent" }}
+ variant="ghost"
+ />
+ </TooltipKeybind>
+ </div>
+ </Show>
+ <Show when={!providersLoading()}>
+ <Show when={store.mode !== "shell"}>
+ <div data-component="prompt-model-control">
+ <Show
+ when={providers.paid().length > 0}
+ fallback={
+ <TooltipKeybind
+ placement="top"
+ gutter={4}
+ title={language.t("command.model.choose")}
+ keybind={command.keybind("model.choose")}
+ >
+ <Button
+ data-action="prompt-model"
+ as="div"
+ variant="ghost"
+ size="normal"
+ class="min-w-0 max-w-[320px] text-13-regular text-text-base group"
+ style={control()}
+ onClick={() => {
+ void import("@/components/dialog-select-model-unpaid").then((x) => {
+ dialog.show(() => <x.DialogSelectModelUnpaid model={local.model} />)
+ })
+ }}
+ >
+ <Show when={local.model.current()?.provider?.id}>
+ <ProviderIcon
+ id={local.model.current()?.provider?.id ?? ""}
+ class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
+ style={{ "will-change": "opacity", transform: "translateZ(0)" }}
+ />
+ </Show>
+ <span class="truncate">
+ {local.model.current()?.name ?? language.t("dialog.model.select.title")}
+ </span>
+ <Icon name="chevron-down" size="small" class="shrink-0" />
+ </Button>
+ </TooltipKeybind>
+ }
+ >
<TooltipKeybind
placement="top"
gutter={4}
title={language.t("command.model.choose")}
keybind={command.keybind("model.choose")}
>
- <Button
- data-action="prompt-model"
- as="div"
- variant="ghost"
- size="normal"
- class="min-w-0 max-w-[320px] text-13-regular text-text-base group"
- style={control()}
- onClick={() => {
- void import("@/components/dialog-select-model-unpaid").then((x) => {
- dialog.show(() => <x.DialogSelectModelUnpaid model={local.model} />)
- })
+ <ModelSelectorPopover
+ model={local.model}
+ triggerAs={Button}
+ triggerProps={{
+ variant: "ghost",
+ size: "normal",
+ style: control(),
+ class: "min-w-0 max-w-[320px] text-13-regular text-text-base group",
+ "data-action": "prompt-model",
}}
+ onClose={restoreFocus}
>
<Show when={local.model.current()?.provider?.id}>
<ProviderIcon
@@ -1520,67 +1550,35 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
{local.model.current()?.name ?? language.t("dialog.model.select.title")}
</span>
<Icon name="chevron-down" size="small" class="shrink-0" />
- </Button>
+ </ModelSelectorPopover>
</TooltipKeybind>
- }
- >
+ </Show>
+ </div>
+ <div data-component="prompt-variant-control">
<TooltipKeybind
placement="top"
gutter={4}
- title={language.t("command.model.choose")}
- keybind={command.keybind("model.choose")}
+ title={language.t("command.model.variant.cycle")}
+ keybind={command.keybind("model.variant.cycle")}
>
- <ModelSelectorPopover
- model={local.model}
- triggerAs={Button}
- triggerProps={{
- variant: "ghost",
- size: "normal",
- style: control(),
- class: "min-w-0 max-w-[320px] text-13-regular text-text-base group",
- "data-action": "prompt-model",
+ <Select
+ size="normal"
+ options={variants()}
+ current={local.model.variant.current() ?? "default"}
+ label={(x) => (x === "default" ? language.t("common.default") : x)}
+ onSelect={(value) => {
+ local.model.variant.set(value === "default" ? undefined : value)
+ restoreFocus()
}}
- onClose={restoreFocus}
- >
- <Show when={local.model.current()?.provider?.id}>
- <ProviderIcon
- id={local.model.current()?.provider?.id ?? ""}
- class="size-4 shrink-0 opacity-40 group-hover:opacity-100 transition-opacity duration-150"
- style={{ "will-change": "opacity", transform: "translateZ(0)" }}
- />
- </Show>
- <span class="truncate">
- {local.model.current()?.name ?? language.t("dialog.model.select.title")}
- </span>
- <Icon name="chevron-down" size="small" class="shrink-0" />
- </ModelSelectorPopover>
+ class="capitalize max-w-[160px] text-text-base"
+ valueClass="truncate text-13-regular text-text-base"
+ triggerStyle={control()}
+ triggerProps={{ "data-action": "prompt-model-variant" }}
+ variant="ghost"
+ />
</TooltipKeybind>
- </Show>
- </div>
- <div data-component="prompt-variant-control">
- <TooltipKeybind
- placement="top"
- gutter={4}
- title={language.t("command.model.variant.cycle")}
- keybind={command.keybind("model.variant.cycle")}
- >
- <Select
- size="normal"
- options={variants()}
- current={local.model.variant.current() ?? "default"}
- label={(x) => (x === "default" ? language.t("common.default") : x)}
- onSelect={(value) => {
- local.model.variant.set(value === "default" ? undefined : value)
- restoreFocus()
- }}
- class="capitalize max-w-[160px] text-text-base"
- valueClass="truncate text-13-regular text-text-base"
- triggerStyle={control()}
- triggerProps={{ "data-action": "prompt-model-variant" }}
- variant="ghost"
- />
- </TooltipKeybind>
- </div>
+ </div>
+ </Show>
</Show>
</div>
</div>
diff --git a/packages/app/src/components/prompt-input/build-request-parts.ts b/packages/app/src/components/prompt-input/build-request-parts.ts
index a1076e60c..c268af35e 100644
--- a/packages/app/src/components/prompt-input/build-request-parts.ts
+++ b/packages/app/src/components/prompt-input/build-request-parts.ts
@@ -1,4 +1,4 @@
-import { getFilename } from "@opencode-ai/util/path"
+import { getFilename } from "@opencode-ai/shared/util/path"
import { type AgentPartInput, type FilePartInput, type Part, type TextPartInput } from "@opencode-ai/sdk/v2/client"
import type { FileSelection } from "@/context/file"
import { encodeFilePath } from "@/context/file/path"
diff --git a/packages/app/src/components/prompt-input/context-items.tsx b/packages/app/src/components/prompt-input/context-items.tsx
index b138fe3ef..9f20f1c04 100644
--- a/packages/app/src/components/prompt-input/context-items.tsx
+++ b/packages/app/src/components/prompt-input/context-items.tsx
@@ -2,7 +2,7 @@ import { Component, For, Show } from "solid-js"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { Tooltip } from "@opencode-ai/ui/tooltip"
-import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/util/path"
+import { getDirectory, getFilename, getFilenameTruncated } from "@opencode-ai/shared/util/path"
import type { ContextItem } from "@/context/prompt"
type PromptContextItem = ContextItem & { key: string }
diff --git a/packages/app/src/components/prompt-input/slash-popover.tsx b/packages/app/src/components/prompt-input/slash-popover.tsx
index 65eb01c79..0c8c95923 100644
--- a/packages/app/src/components/prompt-input/slash-popover.tsx
+++ b/packages/app/src/components/prompt-input/slash-popover.tsx
@@ -1,7 +1,7 @@
import { Component, For, Match, Show, Switch } from "solid-js"
import { FileIcon } from "@opencode-ai/ui/file-icon"
import { Icon } from "@opencode-ai/ui/icon"
-import { getDirectory, getFilename } from "@opencode-ai/util/path"
+import { getDirectory, getFilename } from "@opencode-ai/shared/util/path"
export type AtOption =
| { type: "agent"; name: string; display: string }
diff --git a/packages/app/src/components/prompt-input/submit.test.ts b/packages/app/src/components/prompt-input/submit.test.ts
index 03bece2e3..cf9949723 100644
--- a/packages/app/src/components/prompt-input/submit.test.ts
+++ b/packages/app/src/components/prompt-input/submit.test.ts
@@ -74,7 +74,7 @@ beforeAll(async () => {
showToast: () => 0,
}))
- mock.module("@opencode-ai/util/encode", () => ({
+ mock.module("@opencode-ai/shared/util/encode", () => ({
base64Encode: (value: string) => value,
}))
diff --git a/packages/app/src/components/prompt-input/submit.ts b/packages/app/src/components/prompt-input/submit.ts
index 2a3a3d0e9..6805f619c 100644
--- a/packages/app/src/components/prompt-input/submit.ts
+++ b/packages/app/src/components/prompt-input/submit.ts
@@ -1,9 +1,9 @@
import type { Message, Session } from "@opencode-ai/sdk/v2/client"
import { showToast } from "@opencode-ai/ui/toast"
-import { base64Encode } from "@opencode-ai/util/encode"
-import { Binary } from "@opencode-ai/util/binary"
+import { base64Encode } from "@opencode-ai/shared/util/encode"
+import { Binary } from "@opencode-ai/shared/util/binary"
import { useNavigate, useParams } from "@solidjs/router"
-import type { Accessor } from "solid-js"
+import { batch, type Accessor } from "solid-js"
import type { FileSelection } from "@/context/file"
import { useGlobalSync } from "@/context/global-sync"
import { useLanguage } from "@/context/language"
@@ -13,7 +13,6 @@ import { usePermission } from "@/context/permission"
import { type ContextItem, type ImageAttachmentPart, type Prompt, usePrompt } from "@/context/prompt"
import { useSDK } from "@/context/sdk"
import { useSync } from "@/context/sync"
-import { promptProbe } from "@/testing/prompt"
import { Identifier } from "@/utils/id"
import { Worktree as WorktreeState } from "@/utils/worktree"
import { buildRequestParts } from "./build-request-parts"
@@ -139,13 +138,17 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
messageID,
})
- setBusy()
- add()
+ batch(() => {
+ setBusy()
+ add()
+ })
try {
if (!(await wait())) {
- setIdle()
- remove()
+ batch(() => {
+ setIdle()
+ remove()
+ })
return false
}
@@ -159,8 +162,10 @@ export async function sendFollowupDraft(input: FollowupSendInput) {
})
return true
} catch (err) {
- setIdle()
- remove()
+ batch(() => {
+ setIdle()
+ remove()
+ })
throw err
}
}
@@ -290,7 +295,7 @@ export function createPromptSubmit(input: PromptSubmitInput) {
const mode = input.mode()
if (text.trim().length === 0 && images.length === 0 && input.commentCount() === 0) {
- if (input.working()) abort()
+ if (input.working()) void abort()
return
}
@@ -307,7 +312,6 @@ export function createPromptSubmit(input: PromptSubmitInput) {
input.addToHistory(currentPrompt, mode)
input.resetHistoryNavigation()
- promptProbe.start()
const projectDirectory = sdk.directory
const isNewSession = !params.id
@@ -427,7 +431,6 @@ export function createPromptSubmit(input: PromptSubmitInput) {
return
}
- promptProbe.submit({ sessionID: session.id, directory: sessionDirectory })
input.onSubmit?.()
if (mode === "shell") {
diff --git a/packages/app/src/components/session-context-usage.tsx b/packages/app/src/components/session-context-usage.tsx
index d7c249ab0..6b7fe4ef7 100644
--- a/packages/app/src/components/session-context-usage.tsx
+++ b/packages/app/src/components/session-context-usage.tsx
@@ -24,7 +24,7 @@ function openSessionContext(args: {
}) {
if (!args.view.reviewPanel.opened()) args.view.reviewPanel.open()
if (args.layout.fileTree.opened() && args.layout.fileTree.tab() !== "all") args.layout.fileTree.setTab("all")
- args.tabs.open("context")
+ void args.tabs.open("context")
args.tabs.setActive("context")
}
diff --git a/packages/app/src/components/session/session-context-tab.tsx b/packages/app/src/components/session/session-context-tab.tsx
index 4e7dc8e78..abf4c9334 100644
--- a/packages/app/src/components/session/session-context-tab.tsx
+++ b/packages/app/src/components/session/session-context-tab.tsx
@@ -1,8 +1,8 @@
import { createMemo, createEffect, on, onCleanup, For, Show } from "solid-js"
import type { JSX } from "solid-js"
import { useSync } from "@/context/sync"
-import { checksum } from "@opencode-ai/util/encode"
-import { findLast } from "@opencode-ai/util/array"
+import { checksum } from "@opencode-ai/shared/util/encode"
+import { findLast } from "@opencode-ai/shared/util/array"
import { same } from "@/utils/same"
import { Icon } from "@opencode-ai/ui/icon"
import { Accordion } from "@opencode-ai/ui/accordion"
diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx
index 495b32340..7acfdfc37 100644
--- a/packages/app/src/components/session/session-header.tsx
+++ b/packages/app/src/components/session/session-header.tsx
@@ -7,8 +7,8 @@ import { Keybind } from "@opencode-ai/ui/keybind"
import { Spinner } from "@opencode-ai/ui/spinner"
import { showToast } from "@opencode-ai/ui/toast"
import { Tooltip, TooltipKeybind } from "@opencode-ai/ui/tooltip"
-import { getFilename } from "@opencode-ai/util/path"
-import { createEffect, createMemo, For, onCleanup, Show } from "solid-js"
+import { getFilename } from "@opencode-ai/shared/util/path"
+import { createEffect, createMemo, For, Show } from "solid-js"
import { createStore } from "solid-js/store"
import { Portal } from "solid-js/web"
import { useCommand } from "@/context/command"
diff --git a/packages/app/src/components/session/session-new-view.tsx b/packages/app/src/components/session/session-new-view.tsx
index e4ef36393..d2cac28fc 100644
--- a/packages/app/src/components/session/session-new-view.tsx
+++ b/packages/app/src/components/session/session-new-view.tsx
@@ -5,7 +5,7 @@ import { useSDK } from "@/context/sdk"
import { useLanguage } from "@/context/language"
import { Icon } from "@opencode-ai/ui/icon"
import { Mark } from "@opencode-ai/ui/logo"
-import { getDirectory, getFilename } from "@opencode-ai/util/path"
+import { getDirectory, getFilename } from "@opencode-ai/shared/util/path"
const MAIN_WORKTREE = "main"
const CREATE_WORKTREE = "create"
diff --git a/packages/app/src/components/session/session-sortable-tab.tsx b/packages/app/src/components/session/session-sortable-tab.tsx
index dfda91c16..fb2275c44 100644
--- a/packages/app/src/components/session/session-sortable-tab.tsx
+++ b/packages/app/src/components/session/session-sortable-tab.tsx
@@ -5,7 +5,7 @@ import { FileIcon } from "@opencode-ai/ui/file-icon"
import { IconButton } from "@opencode-ai/ui/icon-button"
import { TooltipKeybind } from "@opencode-ai/ui/tooltip"
import { Tabs } from "@opencode-ai/ui/tabs"
-import { getFilename } from "@opencode-ai/util/path"
+import { getFilename } from "@opencode-ai/shared/util/path"
import { useFile } from "@/context/file"
import { useLanguage } from "@/context/language"
import { useCommand } from "@/context/command"
diff --git a/packages/app/src/components/session/session-sortable-terminal-tab.tsx b/packages/app/src/components/session/session-sortable-terminal-tab.tsx
index ba697f91a..2d88ed180 100644
--- a/packages/app/src/components/session/session-sortable-terminal-tab.tsx
+++ b/packages/app/src/components/session/session-sortable-terminal-tab.tsx
@@ -44,7 +44,7 @@ export function SortableTerminalTab(props: { terminal: LocalPTY; onClose?: () =>
const close = () => {
const count = terminal.all().length
- terminal.close(props.terminal.id)
+ void terminal.close(props.terminal.id)
if (count === 1) {
props.onClose?.()
}
diff --git a/packages/app/src/components/terminal.tsx b/packages/app/src/components/terminal.tsx
index c8430d8bb..57e91d6d3 100644
--- a/packages/app/src/components/terminal.tsx
+++ b/packages/app/src/components/terminal.tsx
@@ -13,7 +13,6 @@ import { useSDK } from "@/context/sdk"
import { useServer } from "@/context/server"
import { monoFontFamily, useSettings } from "@/context/settings"
import type { LocalPTY } from "@/context/terminal"
-import { terminalAttr, terminalProbe } from "@/testing/terminal"
import { disposeIfDisposable, getHoveredLinkText, setOptionIfSupported } from "@/utils/runtime-adapters"
import { terminalWriter } from "@/utils/terminal-writer"
@@ -178,7 +177,6 @@ export const Terminal = (props: TerminalProps) => {
let container!: HTMLDivElement
const [local, others] = splitProps(props, ["pty", "class", "classList", "autoFocus", "onConnect", "onConnectError"])
const id = local.pty.id
- const probe = terminalProbe(id)
const restore = typeof local.pty.buffer === "string" ? local.pty.buffer : ""
const restoreSize =
restore &&
@@ -193,7 +191,7 @@ export const Terminal = (props: TerminalProps) => {
const scrollY = typeof local.pty.scrollY === "number" ? local.pty.scrollY : undefined
let ws: WebSocket | undefined
let term: Term | undefined
- let ghostty: Ghostty
+ let _ghostty: Ghostty
let serializeAddon: SerializeAddon
let fitAddon: FitAddon
let handleResize: () => void
@@ -349,9 +347,6 @@ export const Terminal = (props: TerminalProps) => {
}
onMount(() => {
- probe.init()
- cleanups.push(() => probe.drop())
-
const run = async () => {
const loaded = await loadGhostty()
if (disposed) return
@@ -377,12 +372,10 @@ export const Terminal = (props: TerminalProps) => {
cleanup()
return
}
- ghostty = g
+ _ghostty = g
term = t
output = terminalWriter((data, done) =>
t.write(data, () => {
- probe.render(data)
- probe.settle()
done?.()
}),
)
@@ -422,7 +415,7 @@ export const Terminal = (props: TerminalProps) => {
if (local.autoFocus !== false) focusTerminal()
if (typeof document !== "undefined" && document.fonts) {
- document.fonts.ready.then(scheduleFit)
+ void document.fonts.ready.then(scheduleFit)
}
const onResize = t.onResize((size) => {
@@ -534,7 +527,6 @@ export const Terminal = (props: TerminalProps) => {
const handleOpen = () => {
if (disposed) return
tries = 0
- probe.connect()
local.onConnect?.()
scheduleSize(t.cols, t.rows)
}
@@ -599,13 +591,6 @@ export const Terminal = (props: TerminalProps) => {
socket.addEventListener("close", handleClose)
}
- probe.control({
- disconnect: () => {
- if (!ws) return
- ws.close(4_000, "e2e")
- },
- })
-
open()
}
@@ -645,12 +630,11 @@ export const Terminal = (props: TerminalProps) => {
<div
ref={container}
data-component="terminal"
- {...{ [terminalAttr]: id }}
data-prevent-autofocus
tabIndex={-1}
style={{ "background-color": terminalColors().background }}
classList={{
- ...(local.classList ?? {}),
+ ...local.classList,
"select-text": true,
"size-full px-6 py-3 font-mono relative overflow-hidden": true,
[local.class ?? ""]: !!local.class,
diff --git a/packages/app/src/components/titlebar.tsx b/packages/app/src/components/titlebar.tsx
index 0a41f3119..b7edb85ed 100644
--- a/packages/app/src/components/titlebar.tsx
+++ b/packages/app/src/components/titlebar.tsx
@@ -1,4 +1,4 @@
-import { createEffect, createMemo, onCleanup, Show, untrack } from "solid-js"
+import { createEffect, createMemo, Show, untrack } from "solid-js"
import { createStore } from "solid-js/store"
import { useLocation, useNavigate, useParams } from "@solidjs/router"
import { IconButton } from "@opencode-ai/ui/icon-button"
@@ -252,41 +252,48 @@ export function Titlebar() {
</div>
</div>
</Show>
- <Show when={hasProjects()}>
- <div
- class="flex items-center gap-0 transition-transform"
- classList={{
- "translate-x-0": !layout.sidebar.opened(),
- "-translate-x-[36px]": layout.sidebar.opened(),
- "duration-180 ease-out": !layout.sidebar.opened(),
- "duration-180 ease-in": layout.sidebar.opened(),
- }}
- >
- <Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
- <Button
- variant="ghost"
- icon="chevron-left"
- class="titlebar-icon w-6 h-6 p-0 box-border"
- disabled={!canBack()}
- onClick={back}
- aria-label={language.t("common.goBack")}
- />
- </Tooltip>
- <Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
- <Button
- variant="ghost"
- icon="chevron-right"
- class="titlebar-icon w-6 h-6 p-0 box-border"
- disabled={!canForward()}
- onClick={forward}
- aria-label={language.t("common.goForward")}
- />
- </Tooltip>
- </div>
- </Show>
+ <div
+ class="flex items-center shrink-0"
+ classList={{
+ "translate-x-0": !layout.sidebar.opened(),
+ "-translate-x-[36px]": layout.sidebar.opened(),
+ "duration-180 ease-out": !layout.sidebar.opened(),
+ "duration-180 ease-in": layout.sidebar.opened(),
+ }}
+ >
+ <Show when={hasProjects()}>
+ <div class="flex items-center gap-0 transition-transform">
+ <Tooltip placement="bottom" value={language.t("common.goBack")} openDelay={2000}>
+ <Button
+ variant="ghost"
+ icon="chevron-left"
+ class="titlebar-icon w-6 h-6 p-0 box-border"
+ disabled={!canBack()}
+ onClick={back}
+ aria-label={language.t("common.goBack")}
+ />
+ </Tooltip>
+ <Tooltip placement="bottom" value={language.t("common.goForward")} openDelay={2000}>
+ <Button
+ variant="ghost"
+ icon="chevron-right"
+ class="titlebar-icon w-6 h-6 p-0 box-border"
+ disabled={!canForward()}
+ onClick={forward}
+ aria-label={language.t("common.goForward")}
+ />
+ </Tooltip>
+ </div>
+ </Show>
+ <div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
+ {["beta", "dev"].includes(import.meta.env.VITE_OPENCODE_CHANNEL) && (
+ <div class="bg-icon-interactive-base text-[#FFF] font-medium px-2 rounded-sm uppercase font-mono">
+ {import.meta.env.VITE_OPENCODE_CHANNEL.toUpperCase()}
+ </div>
+ )}
+ </div>
</div>
</div>
- <div id="opencode-titlebar-left" class="flex items-center gap-3 min-w-0 px-2" />
</div>
<div class="min-w-0 flex items-center justify-center pointer-events-none">