Skip to content

Commit 7ee5828

Browse files
committed
fix(web): align terminal material backgrounds with glass surfaces
1 parent 1fd84c1 commit 7ee5828

4 files changed

Lines changed: 364 additions & 14 deletions

File tree

packages/web/src/features/terminal-panel/__tests__/xterm-host.test.tsx

Lines changed: 136 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -278,10 +278,12 @@ const mockTerminal = {
278278
open: vi.fn(),
279279
onData: vi.fn(() => vi.fn()), // Return dispose function
280280
onResize: vi.fn(() => vi.fn()),
281+
onRender: vi.fn(() => vi.fn()),
281282
onSelectionChange: vi.fn(() => vi.fn()),
282283
attachCustomKeyEventHandler: vi.fn(),
283284
hasSelection: vi.fn(() => false),
284285
getSelection: vi.fn(() => ""),
286+
input: vi.fn(),
285287
write: vi.fn(),
286288
writeln: vi.fn(),
287289
scrollLines: vi.fn(),
@@ -297,6 +299,9 @@ const mockTerminal = {
297299
getLine: vi.fn((row: number) => mockBufferLines.get(row)),
298300
},
299301
},
302+
parser: {
303+
registerOscHandler: vi.fn(() => ({ dispose: vi.fn() })),
304+
},
300305
options: {},
301306
};
302307

@@ -356,6 +361,8 @@ describe("XtermHost", () => {
356361
mockTerminal.write.mockImplementation((_data: Uint8Array | string, callback?: () => void) => {
357362
callback?.();
358363
});
364+
mockTerminal.onRender.mockImplementation(() => vi.fn());
365+
mockTerminal.input.mockImplementation(() => {});
359366
mockTerminal.writeln.mockImplementation(() => {});
360367
mockTerminal.reset.mockImplementation(() => {});
361368
mockTerminal.scrollLines.mockImplementation((amount: number) => {
@@ -1204,7 +1211,7 @@ describe("XtermHost", () => {
12041211
expect.objectContaining({
12051212
theme: expect.objectContaining({
12061213
...getThemeById("mint-light").terminalTheme,
1207-
background: "transparent",
1214+
background: "#00000000",
12081215
}),
12091216
})
12101217
);
@@ -2703,9 +2710,10 @@ describe("XtermHost", () => {
27032710

27042711
expect(Terminal).toHaveBeenCalledWith(
27052712
expect.objectContaining({
2713+
allowTransparency: true,
27062714
theme: expect.objectContaining({
27072715
...getThemeById("mint-dark").terminalTheme,
2708-
background: "transparent",
2716+
background: "#00000000",
27092717
}),
27102718
})
27112719
);
@@ -2724,9 +2732,10 @@ describe("XtermHost", () => {
27242732

27252733
expect(Terminal).toHaveBeenCalledWith(
27262734
expect.objectContaining({
2735+
allowTransparency: true,
27272736
theme: expect.objectContaining({
27282737
...getThemeById("mint-light").terminalTheme,
2729-
background: "transparent",
2738+
background: "#00000000",
27302739
}),
27312740
})
27322741
);
@@ -2761,7 +2770,7 @@ describe("XtermHost", () => {
27612770
expect.objectContaining({
27622771
theme: expect.objectContaining({
27632772
...getThemeById("mint-dark").terminalTheme,
2764-
background: "transparent",
2773+
background: "#00000000",
27652774
}),
27662775
})
27672776
);
@@ -2786,7 +2795,7 @@ describe("XtermHost", () => {
27862795
expect.objectContaining({
27872796
theme: expect.objectContaining({
27882797
...getThemeById("graphite-light").terminalTheme,
2789-
background: "transparent",
2798+
background: "#00000000",
27902799
}),
27912800
})
27922801
);
@@ -2827,13 +2836,133 @@ describe("XtermHost", () => {
28272836
expect.objectContaining({
28282837
theme: expect.objectContaining({
28292838
...getThemeById("graphite-light").terminalTheme,
2830-
background: "transparent",
2839+
background: "#00000000",
28312840
}),
28322841
})
28332842
);
28342843
});
28352844
});
28362845

2846+
it("reports semantic terminal colors for OSC 10/11 queries while keeping the rendered background transparent", async () => {
2847+
const store = createStore();
2848+
const sendTerminalInput = vi.fn().mockResolvedValue(undefined);
2849+
store.set(themeAtom, "mint-light");
2850+
store.set(wsClientAtom, {
2851+
sendTerminalInput,
2852+
sendCommand: vi.fn().mockResolvedValue({ status: "ok" }),
2853+
subscribe: vi.fn(() => () => {}),
2854+
getStatus: vi.fn(() => "connected"),
2855+
onStatus: vi.fn(() => () => {}),
2856+
} as never);
2857+
2858+
render(
2859+
<Provider store={store}>
2860+
<XtermHost terminalId="osc-query-terminal" workspaceId="test-workspace" />
2861+
</Provider>
2862+
);
2863+
2864+
const fgHandlerCall = mockTerminal.parser.registerOscHandler.mock.calls.find(
2865+
([ident]) => ident === 10
2866+
);
2867+
const bgHandlerCall = mockTerminal.parser.registerOscHandler.mock.calls.find(
2868+
([ident]) => ident === 11
2869+
);
2870+
2871+
expect(fgHandlerCall).toBeTruthy();
2872+
expect(bgHandlerCall).toBeTruthy();
2873+
2874+
const fgHandler = fgHandlerCall?.[1] as
2875+
| ((data: string) => boolean | Promise<boolean>)
2876+
| undefined;
2877+
const bgHandler = bgHandlerCall?.[1] as
2878+
| ((data: string) => boolean | Promise<boolean>)
2879+
| undefined;
2880+
2881+
await act(async () => {
2882+
expect(await fgHandler?.("?")).toBe(true);
2883+
expect(await bgHandler?.("?")).toBe(true);
2884+
});
2885+
2886+
await waitFor(() => {
2887+
expect(sendTerminalInput).toHaveBeenNthCalledWith(
2888+
1,
2889+
"osc-query-terminal",
2890+
textEncoder.encode("\u001b]10;rgb:1f1f/2323/2828\u001b\\"),
2891+
"system",
2892+
undefined
2893+
);
2894+
expect(sendTerminalInput).toHaveBeenNthCalledWith(
2895+
2,
2896+
"osc-query-terminal",
2897+
textEncoder.encode("\u001b]11;rgb:fcfc/ffff/fdfd\u001b\\"),
2898+
"system",
2899+
undefined
2900+
);
2901+
});
2902+
2903+
expect(mockTerminal.options).toEqual(
2904+
expect.objectContaining({
2905+
theme: expect.objectContaining({
2906+
...getThemeById("mint-light").terminalTheme,
2907+
background: "#00000000",
2908+
}),
2909+
})
2910+
);
2911+
});
2912+
2913+
it("normalizes rendered ANSI cell backgrounds to the active surface opacity", async () => {
2914+
const store = createStore();
2915+
store.set(themeAtom, "mint-light");
2916+
store.set(appearancePersonalizationAtom, {
2917+
version: 1,
2918+
common: {
2919+
backgroundMode: "image",
2920+
backgroundAssetId: "asset-terminal-material-alpha",
2921+
backgroundFit: "cover",
2922+
backgroundDimness: 18,
2923+
backgroundBlur: 4,
2924+
glassEnabled: true,
2925+
glassIntensity: 28,
2926+
surfaceOpacity: 52,
2927+
},
2928+
desktop: {},
2929+
mobile: {},
2930+
});
2931+
2932+
let renderListener: ((viewport: { start: number; end: number }) => void) | undefined;
2933+
mockTerminal.onRender.mockImplementationOnce((listener) => {
2934+
renderListener = listener;
2935+
return vi.fn();
2936+
});
2937+
2938+
const { container } = render(
2939+
<Provider store={store}>
2940+
<XtermHost terminalId="terminal-material-alpha" workspaceId="test-workspace" />
2941+
</Provider>
2942+
);
2943+
2944+
const host = container.querySelector(".xterm-host") as HTMLDivElement | null;
2945+
expect(host).toBeTruthy();
2946+
expect(renderListener).toBeTypeOf("function");
2947+
2948+
const rowsElement = document.createElement("div");
2949+
rowsElement.className = "xterm-rows";
2950+
const row = document.createElement("div");
2951+
const cell = document.createElement("span");
2952+
cell.textContent = "你好";
2953+
cell.style.backgroundColor = "rgb(55, 55, 55)";
2954+
cell.style.color = "rgb(255, 255, 255)";
2955+
row.appendChild(cell);
2956+
rowsElement.appendChild(row);
2957+
host!.appendChild(rowsElement);
2958+
2959+
await act(async () => {
2960+
renderListener?.({ start: 0, end: 0 });
2961+
});
2962+
2963+
expect(cell.style.backgroundColor).toBe("rgba(55, 55, 55, 0.52)");
2964+
});
2965+
28372966
it("uses the high-contrast dark terminal palette for hc-dark", async () => {
28382967
const { Terminal } = await import("@xterm/xterm");
28392968
const store = createStore();
@@ -2849,7 +2978,7 @@ describe("XtermHost", () => {
28492978
expect.objectContaining({
28502979
theme: expect.objectContaining({
28512980
...getThemeById("hc-dark").terminalTheme,
2852-
background: "transparent",
2981+
background: "#00000000",
28532982
}),
28542983
})
28552984
);

0 commit comments

Comments
 (0)