diff --git a/CHANGELOG.md b/CHANGELOG.md
index 58579aa0b..7ede62163 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,7 @@
### Fixes
+- Fix tooltips around inspector dropdowns/popovers without breaking floating menu anchoring.
- Harden MCP calls with bounded page-tree responses, oversized-result errors, JSON HTTP responses, and stale WebSocket cleanup.
- Improve Figma boolean imports by preserving XOR operations as editable exclude nodes and falling back to imported fill geometry when boolean path reconstruction cannot produce a path.
- Preserve rotated Figma transform origins for imported vector nodes.
diff --git a/playwright.config.ts b/playwright.config.ts
index bd8fa684a..b2f7e0549 100644
--- a/playwright.config.ts
+++ b/playwright.config.ts
@@ -33,7 +33,12 @@ export default defineConfig({
{
name: 'openpencil-webkit',
testDir: './tests/e2e',
- testMatch: '**/*.webkit.spec.ts',
+ testMatch: [
+ '**/*.webkit.spec.ts',
+ '**/design/panel.spec.ts',
+ '**/export/basic.spec.ts',
+ '**/fonts/settings.spec.ts'
+ ],
use: {
browserName: 'webkit'
}
diff --git a/src/components/FontSettings/FontSettingsPopover.vue b/src/components/FontSettings/FontSettingsPopover.vue
index bd48864f8..643467d99 100644
--- a/src/components/FontSettings/FontSettingsPopover.vue
+++ b/src/components/FontSettings/FontSettingsPopover.vue
@@ -1,6 +1,6 @@
-
-
+
+
@@ -62,6 +68,7 @@ onMounted(() => {
-import {
- PopoverClose,
- PopoverContent,
- PopoverPortal,
- PopoverRoot,
- PopoverTrigger,
- TooltipContent,
- TooltipPortal,
- TooltipRoot,
- TooltipTrigger
-} from 'reka-ui'
+import { PopoverClose, PopoverContent, PopoverPortal, PopoverRoot, PopoverTrigger } from 'reka-ui'
import { ref } from 'vue'
import { useI18n } from '@open-pencil/vue'
@@ -21,11 +11,10 @@ import ProviderSelectField from '@/components/chat/ProviderSelect/ProviderSelect
import StockPhotoKeysSection from '@/components/chat/ProviderSettings/StockPhotoKeysSection.vue'
import { provideProviderSettings } from '@/components/chat/ProviderSettings/context'
import { usePopoverUI } from '@/components/ui/popover'
-import { useTooltipUI } from '@/components/ui/tooltip'
+import Tip from '@/components/ui/Tip.vue'
const { dialogs } = useI18n()
const cls = usePopoverUI({ content: 'isolate z-[51] w-64 p-3' })
-const tooltipCls = useTooltipUI({ content: 'animate-in zoom-in-95 fade-in' })
const popoverOpen = ref(false)
const providerSettings = provideProviderSettings()
@@ -40,53 +29,46 @@ function onInteractOutside(e: Event) {
-
-
-
-
-
-
-
-
-
- {{ dialogs.providerSettings }}
-
-
-
-
-
+
+
+
-
-
-
+
+
+
+
{{ dialogs.aiProvider }}
+
+
+
+
+
+
+
+
+ {{ dialogs.done }}
+
+
+
+
+
diff --git a/src/components/ui/Tip.vue b/src/components/ui/Tip.vue
index c8c7a4f56..6b0cad682 100644
--- a/src/components/ui/Tip.vue
+++ b/src/components/ui/Tip.vue
@@ -1,26 +1,172 @@
-
-
-
-
-
-
- {{ label }}
-
-
-
+
+
+
+
+
+ {{ label }}
+
+
diff --git a/tests/e2e/design/panel.spec.ts b/tests/e2e/design/panel.spec.ts
index 6fa7c4167..a11ece676 100644
--- a/tests/e2e/design/panel.spec.ts
+++ b/tests/e2e/design/panel.spec.ts
@@ -1,4 +1,4 @@
-import { expect, test, useEditorSetup } from '#tests/e2e/fixtures'
+import { expect, expectInViewport, test, useEditorSetup } from '#tests/e2e/fixtures'
import { expectDefined } from '#tests/helpers/assert'
const editor = useEditorSetup()
@@ -305,8 +305,19 @@ test('multi-select shows mixed header and boolean operations', async () => {
await expect(multiHeader).toContainText('Mixed')
await expect(multiHeader).toContainText('layers')
- await editor.page.getByTestId('boolean-operations-trigger').click()
- await expect(editor.page.getByTestId('boolean-operation-booleanUnion')).toBeVisible()
+ const booleanOperations = editor.page.getByTestId('boolean-operations-trigger')
+ await booleanOperations.hover()
+ await expect(
+ editor.page.locator('[role=tooltip]').filter({ hasText: 'Boolean operations' })
+ ).toBeVisible()
+
+ await booleanOperations.click()
+ await expect(
+ editor.page.locator('[role=tooltip]').filter({ hasText: 'Boolean operations' })
+ ).toHaveCount(0)
+ const booleanUnion = editor.page.getByTestId('boolean-operation-booleanUnion')
+ await expect(booleanUnion).toBeVisible()
+ await expectInViewport(editor.page, booleanUnion)
await expect(editor.page.getByTestId('boolean-operation-booleanSubtract')).toBeVisible()
await expect(editor.page.getByTestId('boolean-operation-booleanIntersect')).toBeVisible()
await expect(editor.page.getByTestId('boolean-operation-booleanExclude')).toBeVisible()
diff --git a/tests/e2e/export/basic.spec.ts b/tests/e2e/export/basic.spec.ts
index efcbe0ae4..ad1e7bf8d 100644
--- a/tests/e2e/export/basic.spec.ts
+++ b/tests/e2e/export/basic.spec.ts
@@ -1,5 +1,6 @@
import { test, expect, type Page } from '@playwright/test'
+import { expectInViewport } from '#tests/e2e/fixtures'
import { CanvasHelper } from '#tests/helpers/canvas'
let page: Page
@@ -50,9 +51,15 @@ test('remove export row decreases row count', async () => {
test('format selector changes to JPG', async () => {
const formatTrigger = exportItems().first().getByTestId('app-select-trigger').last()
+ await formatTrigger.hover()
+ await expect(page.locator('[role=tooltip]').filter({ hasText: 'Export format' })).toBeVisible()
await formatTrigger.click()
+ await expect(page.locator('[role=tooltip]').filter({ hasText: 'Export format' })).toHaveCount(0)
- await page.locator('[role="option"]').filter({ hasText: 'JPG' }).click()
+ const jpgOption = page.locator('[role="option"]').filter({ hasText: 'JPG' })
+ await expect(jpgOption).toBeVisible()
+ await expectInViewport(page, jpgOption)
+ await jpgOption.click()
await canvas.waitForRender()
await expect(formatTrigger).toHaveText('JPG')
diff --git a/tests/e2e/fixtures.ts b/tests/e2e/fixtures.ts
index bb851f358..8e84e84e0 100644
--- a/tests/e2e/fixtures.ts
+++ b/tests/e2e/fixtures.ts
@@ -1,4 +1,4 @@
-import { test, expect, type Page } from '@playwright/test'
+import { test, expect, type Locator, type Page } from '@playwright/test'
import { CanvasHelper } from '#tests/helpers/canvas'
@@ -39,4 +39,17 @@ export function useEditorSetupWithClear(url = '/') {
return ctx
}
+export async function expectInViewport(page: Page, locator: Locator) {
+ const box = await locator.boundingBox()
+ if (!box) throw new Error('Expected visible element to have a bounding box')
+
+ const viewport = page.viewportSize()
+ if (!viewport) throw new Error('Expected page to have a viewport')
+
+ expect(box.x).toBeGreaterThanOrEqual(0)
+ expect(box.y).toBeGreaterThanOrEqual(0)
+ expect(box.x + box.width).toBeLessThanOrEqual(viewport.width)
+ expect(box.y + box.height).toBeLessThanOrEqual(viewport.height)
+}
+
export { test, expect }
diff --git a/tests/e2e/fonts/settings.spec.ts b/tests/e2e/fonts/settings.spec.ts
index 7aef538f8..14fe6b0a5 100644
--- a/tests/e2e/fonts/settings.spec.ts
+++ b/tests/e2e/fonts/settings.spec.ts
@@ -18,7 +18,11 @@ test('font settings popover exposes web font access without desktop-only cache a
})
await expect(page.getByTestId('typography-section')).toBeVisible()
- await page.getByTestId('font-settings-trigger').click()
+ const fontSettings = page.getByTestId('font-settings-trigger')
+ await fontSettings.hover()
+ await expect(page.locator('[role=tooltip]').filter({ hasText: 'Font settings' })).toBeVisible()
+ await fontSettings.click()
+ await expect(page.locator('[role=tooltip]').filter({ hasText: 'Font settings' })).toHaveCount(0)
await expect(page.getByText('Allow browser access to local fonts')).toBeVisible()
await expect(page.getByTestId('font-settings-request-access')).toBeVisible()