diff --git a/packages/app/e2e/prompt/prompt-slash-open.spec.ts b/packages/app/e2e/prompt/prompt-slash-open.spec.ts index 310ee69f8..c7b9a69da 100644 --- a/packages/app/e2e/prompt/prompt-slash-open.spec.ts +++ b/packages/app/e2e/prompt/prompt-slash-open.spec.ts @@ -1,15 +1,54 @@ +import { mkdir, writeFile } from "node:fs/promises" +import { join } from "node:path" +import type { Locator } from "@playwright/test" import { test, expect } from "../fixtures" import { promptSelector } from "../selectors" +async function expectOnTop(locator: Locator) { + await expect(locator).toBeVisible() + await expect + .poll(async () => { + return locator.evaluate((el) => { + const rect = el.getBoundingClientRect() + const target = document.elementFromPoint(rect.left + rect.width / 2, rect.top + rect.height / 2) + return !!target && (target === el || el.contains(target)) + }) + }) + .toBe(true) +} + +async function expectViewportGap(locator: Locator) { + await expect + .poll(async () => { + return locator.evaluate((el) => el.getBoundingClientRect().top) + }) + .toBeGreaterThanOrEqual(8) +} + +async function expectHoverPaint(locator: Locator) { + await locator.hover() + await expect + .poll(async () => { + return locator.evaluate((el) => getComputedStyle(el).backgroundColor) + }) + .not.toBe("rgba(0, 0, 0, 0)") +} + test("/open opens file picker dialog", async ({ page, gotoSession }) => { await gotoSession() await page.locator(promptSelector).click() - await page.keyboard.type("/open") + await page.keyboard.type("/") + + const popover = page.locator('[data-component="prompt-slash-popover"]') + await expectOnTop(popover) + await expectViewportGap(popover) + + await page.keyboard.type("open") const command = page.locator('[data-slash-id="file.open"]') - await expect(command).toBeVisible() - await command.hover() + await expectOnTop(command) + await expectHoverPaint(command) await page.keyboard.press("Enter") @@ -20,3 +59,40 @@ test("/open opens file picker dialog", async ({ page, gotoSession }) => { await page.keyboard.press("Escape") await expect(dialog).toHaveCount(0) }) + +test("home composer shows slash commands after a bare slash", async ({ page, project }) => { + await project.open() + + await page.locator(promptSelector).click() + await page.keyboard.type("/") + + const popover = page.locator('[data-component="prompt-slash-popover"]') + await expectOnTop(popover) + await expectViewportGap(popover) + const command = page.locator('[data-slash-id="file.open"]') + await expectOnTop(command) + await expectHoverPaint(command) +}) + +test("@mention file suggestions stay visible above the composer", async ({ page, project }) => { + await project.open({ + setup: async (directory) => { + await mkdir(join(directory, "src"), { recursive: true }) + await writeFile(join(directory, "src", "mention-target.md"), "hello") + }, + }) + + await page.locator(promptSelector).click() + const sep = process.platform === "win32" ? "\\" : "/" + const file = ["src", "mention-target.md"].join(sep) + const filePattern = /src[\\/]+\s*mention-target\.md/ + + await page.keyboard.type(`@${file}`) + + const popover = page.locator('[data-component="prompt-at-popover"]') + await expectOnTop(popover) + + const suggestion = page.getByRole("button", { name: filePattern }).first() + await expectOnTop(suggestion) + await expectHoverPaint(suggestion) +}) diff --git a/packages/app/src/components/prompt-input/slash-popover.tsx b/packages/app/src/components/prompt-input/slash-popover.tsx index 622bc9a6b..844b13727 100644 --- a/packages/app/src/components/prompt-input/slash-popover.tsx +++ b/packages/app/src/components/prompt-input/slash-popover.tsx @@ -38,8 +38,8 @@ export const PromptPopover: Component = (props) => { ref={(el) => { if (props.popover === "slash") props.setSlashPopoverRef(el) }} - class="absolute inset-x-0 -top-2 -translate-y-full origin-bottom-left max-h-80 min-h-10 - overflow-auto no-scrollbar flex flex-col p-2 rounded-[12px] + class="absolute inset-x-0 -top-2 -translate-y-full z-50 origin-bottom-left max-h-80 min-h-10 + pointer-events-auto overflow-auto no-scrollbar flex flex-col p-2 rounded-[12px] bg-surface-raised shadow-[var(--shadow-lg-border-base)]" onMouseDown={(e) => e.preventDefault()} > @@ -60,7 +60,7 @@ export const PromptPopover: Component = (props) => { return ( - - - - } - > - {(request) => ( - { - props.onResponseSubmit() - props.state.decide(response) - }} + + + {(revert) => ( + + )} + + + - )} - - + + + props.actionReady ?? true} + abortReady={() => props.abortReady ?? props.actionReady ?? true} + selectedSkill={props.selectedSkill} + /> + + } + > + + {language.t("session.child.promptDisabled")} + + + + + + } + > + {(request) => ( + { + props.onResponseSubmit() + props.state.decide(response) + }} + /> + )} + + +