`.
const bodyAnchorName = tabGroupBodyAnchorName(groupId)
// Why: memoize so a fresh style object each render doesn't break downstream memoization keyed on referential equality.
@@ -337,10 +335,10 @@ export default function TabGroupPanel({
{
- openTerminalBesideTab(tabId)
- }}
+ onOpenNewTerminal={() => void openTerminalBesideTab(tabId)}
onClosePane={() => handleRequestClosePane(activePane.id)}
/>,
activePane.container,
diff --git a/src/renderer/src/components/terminal-pane/mounting/TerminalPaneOverlayLayer.tsx b/src/renderer/src/components/terminal-pane/mounting/TerminalPaneOverlayLayer.tsx
index 3a551672315..39b58931847 100644
--- a/src/renderer/src/components/terminal-pane/mounting/TerminalPaneOverlayLayer.tsx
+++ b/src/renderer/src/components/terminal-pane/mounting/TerminalPaneOverlayLayer.tsx
@@ -3,7 +3,7 @@ import { useShallow } from 'zustand/react/shallow'
import type { Tab, TabGroup } from '../../../../../shared/tab-types'
import type { TerminalTab } from '../../../../../shared/workspace-layout/terminal-tab-types'
import { useAppStore } from '../../../store/index'
-import { useThemeBackgroundStore } from '@/store/appearance/theme-background'
+import { useWindowBackdropActive } from '@/app-shell/appearance/window-backdrop'
import {
findActivityTerminalPortal,
type ActivityTerminalPortalTarget
@@ -76,7 +76,7 @@ const TerminalPaneOverlayLayer = memo(function TerminalPaneOverlayLayer({
[worktreeId]
)
- const themeBackgroundActive = useThemeBackgroundStore((state) => state.active)
+ const backdropActive = useWindowBackdropActive()
const focusOwningGroup = useCallback(
(groupId: string) => focusGroup(worktreeId, groupId),
[focusGroup, worktreeId]
@@ -173,8 +173,8 @@ const TerminalPaneOverlayLayer = memo(function TerminalPaneOverlayLayer({
isVisible={isVisible}
isActive={isActive}
hiddenPrewarm={assignment?.isActiveInGroup === true ? hiddenTerminalPrewarm : null}
- // Why not over a theme image: a chat there is translucent, so a dim would bleed the image in.
- dimmed={groups.length > 1 && isVisible && !isActive && !themeBackgroundActive}
+ // Why not over a backdrop: group dimming also fades terminal and chat text.
+ dimmed={groups.length > 1 && isVisible && !isActive && !backdropActive}
activityTerminalPortal={activityTerminalPortal}
onFocusOwningGroup={focusOwningGroup}
consumeSuppressedPtyExit={consumeSuppressedPtyExit}
diff --git a/tests/component-fixtures/native-chat-backdrop.spec.ts b/tests/component-fixtures/native-chat-backdrop.spec.ts
new file mode 100644
index 00000000000..b7f60f98a1c
--- /dev/null
+++ b/tests/component-fixtures/native-chat-backdrop.spec.ts
@@ -0,0 +1,43 @@
+import { expect, test, type Page } from '@playwright/test'
+
+async function pixels(page: Page, sample: string): Promise {
+ return page.locator(`[data-paint-sample="${sample}"]`).screenshot()
+}
+
+test('material reaches chat without changing the neighboring terminal or exposing covered content', async ({
+ page
+}) => {
+ await page.goto('/fixtures.html?id=native-chat/appearance/NativeChatBackdrop:material&theme=dark')
+ await expect(page.locator('html')).toHaveAttribute('data-fixture-state', 'ready')
+ const backdrop = page.locator('[data-test-backdrop]')
+ await backdrop.evaluate((element) => (element.style.background = 'white'))
+ const neighbor = await pixels(page, 'neighbor')
+ await page.getByRole('button', { name: 'Toggle split chat' }).click()
+ expect(await pixels(page, 'neighbor')).toEqual(neighbor)
+
+ const samples = ['single', 'split', 'structured']
+ const lightBare = await pixels(page, 'bare')
+ const light = await Promise.all(samples.map((sample) => pixels(page, sample)))
+ for (const painted of light) {
+ expect(painted.equals(lightBare)).toBe(false)
+ }
+ await backdrop.evaluate((element) => (element.style.background = 'black'))
+ const darkBare = await pixels(page, 'bare')
+ for (const [index, sample] of samples.entries()) {
+ const painted = await pixels(page, sample)
+ expect(painted.equals(light[index])).toBe(false)
+ expect(painted.equals(darkBare)).toBe(false)
+ }
+
+ const covered = await pixels(page, 'split')
+ const neighborDark = await pixels(page, 'neighbor')
+ await page.locator('[data-terminal-ink]').evaluateAll((elements) => {
+ for (const element of elements) {
+ if (element instanceof HTMLElement) {
+ element.style.background = 'red'
+ }
+ }
+ })
+ expect(await pixels(page, 'split')).toEqual(covered)
+ expect((await pixels(page, 'neighbor')).equals(neighborDark)).toBe(false)
+})