diff --git a/src/renderer/src/assets/terminal-container-geometry.test.ts b/src/renderer/src/assets/terminal-container-geometry.test.ts new file mode 100644 index 000000000..db079c909 --- /dev/null +++ b/src/renderer/src/assets/terminal-container-geometry.test.ts @@ -0,0 +1,18 @@ +import fs from 'node:fs' +import { describe, expect, it } from 'vitest' + +const terminalCss = fs.readFileSync(new URL('./terminal.css', import.meta.url), 'utf8') + +describe('terminal container geometry', () => { + it('keeps the hidden link tooltip out of the fitted terminal height', () => { + expect(terminalCss).toMatch( + /\.xterm-container\s*{[^}]*height:\s*calc\(100% - var\(--pane-padding-y, 4px\)\);/s + ) + expect(terminalCss).toMatch( + /\.pane\[data-has-title\] \.xterm-container\s*{[^}]*height:\s*calc\(100% - var\(--orca-pane-title-height\)\);/s + ) + expect(terminalCss).toMatch( + /\.pane-link-tooltip\s*{[^}]*height:\s*var\(--orca-terminal-link-tooltip-height\);/s + ) + }) +}) diff --git a/src/renderer/src/assets/terminal.css b/src/renderer/src/assets/terminal.css index 3d9a5df21..ee5e0af01 100644 --- a/src/renderer/src/assets/terminal.css +++ b/src/renderer/src/assets/terminal.css @@ -6,8 +6,7 @@ position: relative; } -/* Why: .pane-manager-root matches no live element, so a var declared there - never resolves and every calc() using it is dropped. Keep it on :root. */ +/* Shared tooltip geometry for every terminal link provider. */ :root { --orca-terminal-link-tooltip-height: 25px; } @@ -506,7 +505,7 @@ box-sizing: border-box; position: relative; width: calc(100% - var(--pane-padding-x, 4px)); - height: calc(100% - var(--pane-padding-y, 4px) - var(--orca-terminal-link-tooltip-height, 25px)); + height: calc(100% - var(--pane-padding-y, 4px)); margin-top: var(--pane-padding-y, 4px); margin-left: var(--pane-padding-x, 4px); } @@ -517,9 +516,7 @@ same amount to prevent overflow/clipping. */ .pane[data-has-title] .xterm-container { margin-top: var(--orca-pane-title-height); - height: calc( - 100% - var(--orca-pane-title-height) - var(--orca-terminal-link-tooltip-height, 25px) - ); /* match margin-top and the tooltip reserve */ + height: calc(100% - var(--orca-pane-title-height)); /* match margin-top */ } /* Ghostty-style URL hover: glued to the pane's true bottom-left corner. */ diff --git a/tests/e2e/issue-12656-terminal-link-tooltip.spec.ts b/tests/e2e/issue-12656-terminal-link-tooltip.spec.ts index 56c27dc19..74c7971b8 100644 --- a/tests/e2e/issue-12656-terminal-link-tooltip.spec.ts +++ b/tests/e2e/issue-12656-terminal-link-tooltip.spec.ts @@ -25,8 +25,8 @@ type TooltipState = { paneBottom: number terminalBottom: number tooltipTop: number + tooltipBottom: number tooltipHeight: number - reserveHeight: number } async function locateUrl(page: Page, url: string): Promise { @@ -98,7 +98,6 @@ async function readTooltipState(page: Page, tabId: string): Promise { - test('clears hover state on window blur and reserves the tooltip strip', async ({ + test('clears hover state without permanently shrinking the terminal', async ({ orcaPage }, testInfo) => { await waitForSessionReady(orcaPage) @@ -150,6 +149,8 @@ test.describe('Issue #12656 terminal link tooltip', () => { if (!probe) { throw new Error('URL probe disappeared before hover') } + const idle = await readTooltipState(orcaPage, probe.tabId) + expect(Math.abs(idle.paneBottom - idle.terminalBottom)).toBeLessThanOrEqual(1) await expect .poll(async () => { await moveToLink(orcaPage, probe) @@ -160,26 +161,19 @@ test.describe('Issue #12656 terminal link tooltip', () => { const hovered = await readTooltipState(orcaPage, probe.tabId) expect(hovered.text).toContain(url) expect(hovered.tooltipHeight).toBeGreaterThan(0) - expect(hovered.tooltipTop).toBeGreaterThanOrEqual(hovered.terminalBottom - 1) - // Why: bound the gap on both sides. A lower bound alone also passes when the - // reserve var fails to resolve and .xterm-container collapses to height:auto, - // which leaves a huge gap and an undersized terminal. - expect(hovered.paneBottom - hovered.terminalBottom).toBeGreaterThanOrEqual( - hovered.reserveHeight - 1 - ) - expect(hovered.paneBottom - hovered.terminalBottom).toBeLessThanOrEqual( - hovered.reserveHeight + 1 - ) + expect(Math.abs(hovered.paneBottom - hovered.terminalBottom)).toBeLessThanOrEqual(1) + expect(Math.abs(hovered.paneBottom - hovered.tooltipBottom)).toBeLessThanOrEqual(1) + expect(hovered.tooltipTop).toBeLessThan(hovered.terminalBottom) await captureProof(orcaPage, testInfo, 'issue-12656-fixed-hover.png') await orcaPage.evaluate(() => window.dispatchEvent(new Event('blur'))) await expect .poll(() => readTooltipState(orcaPage, probe.tabId)) .toMatchObject({ display: 'none', currentLinkText: null, cursor: 'text' }) + const cleared = await readTooltipState(orcaPage, probe.tabId) + expect(Math.abs(cleared.paneBottom - cleared.terminalBottom)).toBeLessThanOrEqual(1) await captureProof(orcaPage, testInfo, 'issue-12656-fixed-after-blur.png') - // Keep the output assertion adjacent to the visual state checks so the - // reserved strip cannot hide the final terminal line without detection. await expect.poll(() => getTerminalContent(orcaPage)).toContain(url) }) })