mirror of
https://github.com/anomalyco/opencode.git
synced 2026-11-13 19:46:16 +03:00
fix(app): tabs can be renamed again after a rename (#57322)
This commit is contained in:
committed by
GitHub
Unverified
parent
6f54deb9a5
commit
b1bfe84d62
@@ -124,31 +124,42 @@ test("inactive session attention dots share the close control without marking th
|
||||
await expectPath(page, sessionHref(a.id))
|
||||
})
|
||||
|
||||
test("a tab does not reopen its title editor while a rename is saving", async ({ page }) => {
|
||||
await mockWorkspace(page, { name: "Tabs", sessions: [a, b] })
|
||||
await page.goto(sessionHref(a.id))
|
||||
const title = page.locator(`[data-titlebar-tab-slot]:has(a[href="${sessionHref(b.id)}"]) [data-slot="tab-title"]`)
|
||||
const editor = page.locator('[data-slot="tab-title"][contenteditable="true"]')
|
||||
await expect(title).toHaveText(b.title)
|
||||
const save = await holdRoute(page, (url) => url.pathname === `/api/session/${b.id}`, { method: "PATCH" })
|
||||
for (const tabLayout of ["horizontal", "vertical"] as const) {
|
||||
test(`a ${tabLayout} tab can be renamed again while its previous rename is still saving`, async ({ page }) => {
|
||||
await mockWorkspace(page, { name: "Tabs", sessions: [a, b], seed: { settings: { appearance: { tabLayout } } } })
|
||||
await page.goto(sessionHref(a.id))
|
||||
const title = page.locator(`[data-titlebar-tab-slot]:has(a[href="${sessionHref(a.id)}"]) [data-slot="tab-title"]`)
|
||||
const editor = page.locator('[data-slot="tab-title"][contenteditable="true"]')
|
||||
await expect(title).toHaveText(a.title)
|
||||
const save = await holdRoute(page, (url) => url.pathname === `/api/session/${a.id}`, { method: "PATCH" })
|
||||
|
||||
await title.dblclick()
|
||||
await expect(editor).toBeFocused()
|
||||
await editor.fill("Renamed tab")
|
||||
await editor.press("Enter")
|
||||
expect((await save.arrived).postDataJSON()).toEqual({ title: "Renamed tab" })
|
||||
await expect(editor).toHaveCount(0)
|
||||
await title.dblclick()
|
||||
await expect(editor).toHaveCount(0)
|
||||
await title.dblclick()
|
||||
await expect(editor).toBeFocused()
|
||||
await editor.fill("First name")
|
||||
await editor.press("Enter")
|
||||
expect((await save.arrived).postDataJSON()).toEqual({ title: "First name" })
|
||||
await expect(editor).toHaveCount(0)
|
||||
|
||||
// The context menu's Rename item is enabled again once the save settles.
|
||||
save.release()
|
||||
await title.click({ button: "right" })
|
||||
await expect(page.getByRole("menuitem", { name: "Rename", exact: true })).toBeEnabled()
|
||||
await page.keyboard.press("Escape")
|
||||
await title.dblclick()
|
||||
await expect(editor).toBeFocused()
|
||||
})
|
||||
await title.dblclick()
|
||||
await expect(editor).toBeFocused()
|
||||
await editor.fill("Second name")
|
||||
await editor.press("Enter")
|
||||
await expect(title).toHaveText("Second name")
|
||||
|
||||
await title.click({ button: "right" })
|
||||
await page.getByRole("menuitem", { name: "Rename", exact: true }).click()
|
||||
await expect(editor).toBeFocused()
|
||||
await editor.fill("Third name")
|
||||
await editor.press("Enter")
|
||||
await expect(title).toHaveText("Third name")
|
||||
|
||||
save.release()
|
||||
await expect
|
||||
.poll(() => save.requests.map((request) => request.postDataJSON()))
|
||||
.toEqual([{ title: "First name" }, { title: "Second name" }, { title: "Third name" }])
|
||||
await expect(title).toHaveText("Third name")
|
||||
})
|
||||
}
|
||||
|
||||
test("keyboard navigation follows the visible tab order and skips unresolved tabs", async ({ page }) => {
|
||||
await mockWorkspace(page, { name: "Tabs", sessions: [a, c], seed: { tabs: [a.id, "ses_tab_unresolved", c.id] } })
|
||||
|
||||
@@ -3,7 +3,6 @@ import { createStore } from "solid-js/store"
|
||||
import { Predicate } from "effect"
|
||||
import { makeEventListener } from "@solid-primitives/event-listener"
|
||||
import { createResizeObserver } from "@solid-primitives/resize-observer"
|
||||
import { createMutation } from "@tanstack/solid-query"
|
||||
import { IconButton } from "@opencode/ui/icon-button"
|
||||
import { Icon } from "@opencode/ui/icon"
|
||||
import { Menu } from "@opencode/ui/menu"
|
||||
@@ -48,7 +47,6 @@ export function TabNavItem(props: {
|
||||
let tabRoot!: HTMLDivElement
|
||||
let titleEl!: HTMLSpanElement
|
||||
let measureFrame: number | undefined
|
||||
const rename = createMutation(() => ({ mutationFn: props.onRename }))
|
||||
|
||||
const closeTab = (event: MouseEvent) => {
|
||||
event.preventDefault()
|
||||
@@ -162,7 +160,7 @@ export function TabNavItem(props: {
|
||||
}
|
||||
|
||||
const closeRename = async (save: boolean) => {
|
||||
if (rename.isPending || !editing()) return
|
||||
if (!editing()) return
|
||||
|
||||
const original = props.session?.title ?? ""
|
||||
const next = (titleEl.textContent ?? "").trim()
|
||||
@@ -174,7 +172,7 @@ export function TabNavItem(props: {
|
||||
return
|
||||
}
|
||||
|
||||
await rename.mutateAsync(next)
|
||||
await props.onRename(next)
|
||||
}
|
||||
|
||||
createEffect(() => {
|
||||
@@ -191,7 +189,7 @@ export function TabNavItem(props: {
|
||||
event?.preventDefault()
|
||||
event?.stopPropagation()
|
||||
|
||||
if (props.dragging || editing() || rename.isPending) return
|
||||
if (props.dragging || editing()) return
|
||||
const session = props.session
|
||||
|
||||
if (!session) return
|
||||
@@ -433,7 +431,7 @@ export function TabNavItem(props: {
|
||||
openRename()
|
||||
}}
|
||||
>
|
||||
<Menu.Item disabled={!props.session || rename.isPending} onSelect={() => setMenu("rename", true)}>
|
||||
<Menu.Item disabled={!props.session} onSelect={() => setMenu("rename", true)}>
|
||||
{language.t("common.rename")}
|
||||
</Menu.Item>
|
||||
<Menu.Item onSelect={props.onClose}>{language.t("common.closeTab")}</Menu.Item>
|
||||
|
||||
@@ -146,7 +146,9 @@ function SessionTabEntry(props: {
|
||||
const current = session()
|
||||
const currentCtx = props.serverCtx
|
||||
|
||||
if (current && currentCtx) currentCtx.data.session.remember({ ...current, title: value.title })
|
||||
// A newer rename may have replaced this title while the save was in flight.
|
||||
if (current && currentCtx && current.title === title)
|
||||
currentCtx.data.session.remember({ ...current, title: value.title })
|
||||
showToast({
|
||||
title: language.t("common.requestFailed"),
|
||||
description: err instanceof Error ? err.message : undefined,
|
||||
|
||||
Reference in New Issue
Block a user