chore: update merge branch with latest dev
This commit is contained in:
commit
2d7de820ea
4 changed files with 75 additions and 22 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
import { base64Encode } from "@opencode-ai/core/util/encode"
|
import { base64Encode } from "@opencode-ai/core/util/encode"
|
||||||
import { expect, test, type Page } from "@playwright/test"
|
import { expect, test, type Page } from "@playwright/test"
|
||||||
import { mockOpenCodeServer } from "../utils/mock-server"
|
import { mockOpenCodeServer } from "../utils/mock-server"
|
||||||
|
import { installSseTransport } from "../utils/sse-transport"
|
||||||
import { expectSessionTitle } from "../utils/waits"
|
import { expectSessionTitle } from "../utils/waits"
|
||||||
|
|
||||||
const directory = "C:/OpenCode/RequestDocks"
|
const directory = "C:/OpenCode/RequestDocks"
|
||||||
|
|
@ -100,6 +101,67 @@ test("shows a pending permission dock", async ({ page }) => {
|
||||||
expect(request.postDataJSON()).toEqual({ response: "once" })
|
expect(request.postDataJSON()).toEqual({ response: "once" })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test("restores the draft caret before typing after a request dock closes", async ({ page }) => {
|
||||||
|
const transport = await installSseTransport(page, {
|
||||||
|
server: `http://${process.env.PLAYWRIGHT_SERVER_HOST ?? "127.0.0.1"}:${process.env.PLAYWRIGHT_SERVER_PORT ?? "4096"}`,
|
||||||
|
retry: 20,
|
||||||
|
})
|
||||||
|
await mockServer(page, { questions: [] })
|
||||||
|
await page.goto(`/${base64Encode(directory)}/session/${sessionID}`)
|
||||||
|
await transport.waitForConnection()
|
||||||
|
await expectSessionTitle(page, title)
|
||||||
|
|
||||||
|
const editor = page.locator('[data-component="prompt-input"][contenteditable="true"]')
|
||||||
|
const draft = "keep the caret at the end"
|
||||||
|
await editor.fill(draft)
|
||||||
|
await page.evaluate(() => new Promise<void>((resolve) => requestAnimationFrame(() => resolve())))
|
||||||
|
for (let index = 0; index < 4; index++) await page.keyboard.press("ArrowLeft")
|
||||||
|
const cursor = draft.length - 4
|
||||||
|
await expect
|
||||||
|
.poll(() =>
|
||||||
|
editor.evaluate((element) => {
|
||||||
|
const selection = window.getSelection()
|
||||||
|
if (!selection?.rangeCount || !element.contains(selection.anchorNode)) return -1
|
||||||
|
const range = selection.getRangeAt(0).cloneRange()
|
||||||
|
range.selectNodeContents(element)
|
||||||
|
range.setEnd(selection.anchorNode!, selection.anchorOffset)
|
||||||
|
return range.toString().length
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.toBe(cursor)
|
||||||
|
await transport.send({
|
||||||
|
directory,
|
||||||
|
payload: {
|
||||||
|
type: "question.asked",
|
||||||
|
properties: {
|
||||||
|
id: "question-caret",
|
||||||
|
sessionID,
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
header: "Continue",
|
||||||
|
question: "Continue?",
|
||||||
|
options: [{ label: "Yes", description: "Continue the session" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
tool: { messageID: "message-caret", callID: "call-caret" },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
const question = page.locator('[data-component="dock-prompt"][data-kind="question"]')
|
||||||
|
await expect(question).toBeVisible()
|
||||||
|
await expect(editor).toHaveCount(0)
|
||||||
|
|
||||||
|
await transport.send({
|
||||||
|
directory,
|
||||||
|
payload: { type: "question.rejected", properties: { sessionID, requestID: "question-caret" } },
|
||||||
|
})
|
||||||
|
await expect(question).toHaveCount(0)
|
||||||
|
await expect(editor).toBeVisible()
|
||||||
|
await page.keyboard.press("x")
|
||||||
|
|
||||||
|
await expect(editor).toHaveText(`${draft.slice(0, cursor)}x${draft.slice(cursor)}`)
|
||||||
|
})
|
||||||
|
|
||||||
async function mockServer(
|
async function mockServer(
|
||||||
page: Page,
|
page: Page,
|
||||||
requests: {
|
requests: {
|
||||||
|
|
|
||||||
|
|
@ -633,7 +633,9 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||||
const isImeComposing = (event: KeyboardEvent) => event.isComposing || composing() || event.keyCode === 229
|
const isImeComposing = (event: KeyboardEvent) => event.isComposing || composing() || event.keyCode === 229
|
||||||
|
|
||||||
const handleBlur = () => {
|
const handleBlur = () => {
|
||||||
savedCursor = currentCursor()
|
const cursor = currentCursor()
|
||||||
|
savedCursor = cursor
|
||||||
|
if (cursor !== null && cursor !== prompt.cursor()) prompt.set(prompt.current(), cursor)
|
||||||
closePopover()
|
closePopover()
|
||||||
setComposing(false)
|
setComposing(false)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -240,8 +240,8 @@ function useHomeSessionHeaderOpacity(groups: () => HomeSessionGroup[]) {
|
||||||
return { setViewport, setContentRef, setHeaderRef, update, titleOpacity }
|
return { setViewport, setContentRef, setHeaderRef, update, titleOpacity }
|
||||||
}
|
}
|
||||||
|
|
||||||
// Middle-click or Cmd+click on macOS (Ctrl+click elsewhere) opens a session
|
// Cmd+click on macOS (Ctrl+click elsewhere) opens a session tab in the
|
||||||
// tab in the background without navigating, matching browser conventions.
|
// background without navigating, matching browser conventions.
|
||||||
function isBackgroundOpen(event: MouseEvent) {
|
function isBackgroundOpen(event: MouseEvent) {
|
||||||
return shouldOpenSessionInBackground({
|
return shouldOpenSessionInBackground({
|
||||||
button: event.button,
|
button: event.button,
|
||||||
|
|
@ -466,8 +466,8 @@ export function NewHome() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function editProject(conn: ServerConnection.Any, project: LocalProject) {
|
function editProject(conn: ServerConnection.Any, project: LocalProject) {
|
||||||
void import("@/components/dialog-edit-project-v2").then((x) => {
|
void import("@/components/dialog-edit-project").then((x) => {
|
||||||
void dialog.show(() => <x.DialogEditProjectV2 server={conn} project={project} />)
|
dialog.show(() => <x.DialogEditProject server={conn} project={project} />)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1387,15 +1387,7 @@ function HomeSessionSearchResultRow(props: {
|
||||||
group: !!showProjectName(),
|
group: !!showProjectName(),
|
||||||
}}
|
}}
|
||||||
onMouseEnter={() => props.onHighlight()}
|
onMouseEnter={() => props.onHighlight()}
|
||||||
onMouseDown={(event) => {
|
|
||||||
if (event.button === 1) event.preventDefault()
|
|
||||||
}}
|
|
||||||
onClick={(event) => props.onSelect(props.record.session, { background: isBackgroundOpen(event) })}
|
onClick={(event) => props.onSelect(props.record.session, { background: isBackgroundOpen(event) })}
|
||||||
onAuxClick={(event) => {
|
|
||||||
if (!isBackgroundOpen(event)) return
|
|
||||||
event.preventDefault()
|
|
||||||
props.onSelect(props.record.session, { background: true })
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<HomeSessionLeading
|
<HomeSessionLeading
|
||||||
project={props.record.project}
|
project={props.record.project}
|
||||||
|
|
@ -1455,15 +1447,7 @@ function HomeSessionRow(props: {
|
||||||
type="button"
|
type="button"
|
||||||
data-component="home-session-row"
|
data-component="home-session-row"
|
||||||
class={`${HOME_ROW} h-10 min-w-0 flex-1 gap-2 py-3 pl-3 pr-10`}
|
class={`${HOME_ROW} h-10 min-w-0 flex-1 gap-2 py-3 pl-3 pr-10`}
|
||||||
onMouseDown={(event) => {
|
|
||||||
if (event.button === 1) event.preventDefault()
|
|
||||||
}}
|
|
||||||
onClick={(event) => props.openSession(props.record.session, { background: isBackgroundOpen(event) })}
|
onClick={(event) => props.openSession(props.record.session, { background: isBackgroundOpen(event) })}
|
||||||
onAuxClick={(event) => {
|
|
||||||
if (!isBackgroundOpen(event)) return
|
|
||||||
event.preventDefault()
|
|
||||||
props.openSession(props.record.session, { background: true })
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<HomeSessionLeading
|
<HomeSessionLeading
|
||||||
project={props.record.project}
|
project={props.record.project}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,8 @@ import { useSync } from "@/context/sync"
|
||||||
import { useTabs } from "@/context/tabs"
|
import { useTabs } from "@/context/tabs"
|
||||||
import { TerminalProvider, useTerminal } from "@/context/terminal"
|
import { TerminalProvider, useTerminal } from "@/context/terminal"
|
||||||
import { PromptInput } from "@/components/prompt-input"
|
import { PromptInput } from "@/components/prompt-input"
|
||||||
|
import { setCursorPosition } from "@/components/prompt-input/editor-dom"
|
||||||
|
import { promptLength } from "@/components/prompt-input/history"
|
||||||
import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
|
import { type FollowupDraft, sendFollowupDraft } from "@/components/prompt-input/submit"
|
||||||
import {
|
import {
|
||||||
createPromptInputController,
|
createPromptInputController,
|
||||||
|
|
@ -1017,7 +1019,10 @@ export default function Page() {
|
||||||
|
|
||||||
if (event.key.length === 1 && event.key !== "Unidentified" && !(event.ctrlKey || event.metaKey)) {
|
if (event.key.length === 1 && event.key !== "Unidentified" && !(event.ctrlKey || event.metaKey)) {
|
||||||
if (composer.blocked() || isChildSession()) return
|
if (composer.blocked() || isChildSession()) return
|
||||||
inputRef?.focus()
|
const input = inputRef
|
||||||
|
if (!input) return
|
||||||
|
input.focus()
|
||||||
|
setCursorPosition(input, prompt.cursor() ?? promptLength(prompt.current()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue