Skip to content
Open
14 changes: 11 additions & 3 deletions apps/desktop/src/electron/ElectronProtocol.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,11 @@ describe("ElectronProtocol", () => {
assert.equal(yield* Effect.promise(() => response.text()), "ok");
assert.include(
response.headers.get("content-security-policy") ?? "",
"script-src 'self' 'unsafe-inline' https://clerk.t3.codes https://challenges.cloudflare.com",
"script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://clerk.t3.codes https://challenges.cloudflare.com",
);
assert.include(
response.headers.get("content-security-policy") ?? "",
"connect-src 'self' http: https: ws: wss:",
"connect-src 'self' data: http: https: ws: wss:",
);
assert.include(
response.headers.get("content-security-policy") ?? "",
Expand Down Expand Up @@ -212,10 +212,18 @@ describe("ElectronProtocol", () => {
assert.deepEqual(directives["script-src"], [
"'self'",
"'unsafe-inline'",
"'wasm-unsafe-eval'",
"https://clerk.t3.codes",
"https://challenges.cloudflare.com",
]);
assert.deepEqual(directives["connect-src"], ["'self'", "http:", "https:", "ws:", "wss:"]);
assert.deepEqual(directives["connect-src"], [
"'self'",
"data:",
"http:",
"https:",
"ws:",
"wss:",
]);
assert.deepEqual(directives["img-src"], [
"'self'",
"t3code:",
Expand Down
5 changes: 4 additions & 1 deletion apps/desktop/src/electron/ElectronProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ export function makeDesktopContentSecurityPolicy(input: DesktopProtocolRegistrat
const scriptSources = [
"'self'",
"'unsafe-inline'",
// Required to compile the ghostty-web terminal's WebAssembly module.
"'wasm-unsafe-eval'",
...(clerkOrigin ? [clerkOrigin] : []),
"https://challenges.cloudflare.com",
];
Expand All @@ -79,7 +81,8 @@ export function makeDesktopContentSecurityPolicy(input: DesktopProtocolRegistrat
// the build-configured Clerk, relay, and OTLP endpoints. Those environment
// origins are not known when this response policy is created, so restrict
// connections by the network schemes the client supports instead of by host.
const connectSources = ["'self'", "http:", "https:", "ws:", "wss:"];
// data: allows ghostty-web to fetch its embedded WebAssembly payload.
const connectSources = ["'self'", "data:", "http:", "https:", "ws:", "wss:"];

return [
"default-src 'self'",
Expand Down
83 changes: 83 additions & 0 deletions apps/server/src/terminal/ghosttyStyle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import { describe, expect, it } from "vite-plus/test";

import {
ghosttyThemeSearchPaths,
parseGhosttyConfig,
splitThemeSelection,
} from "./ghosttyStyle.ts";

const posixPath = {
isAbsolute: (value: string) => value.startsWith("/"),
join: (...segments: ReadonlyArray<string>) =>
segments
.map((segment, index) =>
index === 0 ? segment.replace(/\/$/, "") : segment.replace(/^\//, ""),
)
.join("/"),
};

const windowsPath = {
isAbsolute: (value: string) => /^[A-Za-z]:[\\/]/.test(value),
join: (...segments: ReadonlyArray<string>) => segments.join("\\"),
};

describe("parseGhosttyConfig", () => {
it("resets scalar and palette colors with empty assignments", () => {
const config = parseGhosttyConfig(`
background = #000000
foreground = #ffffff
palette = 0=#111111
palette = 1=#222222
background =
foreground = ""
palette = 0=
cursor-color = #c0ffee
`);

expect(config.colors.background).toBeUndefined();
expect(config.colors.foreground).toBeUndefined();
expect(config.colors.cursor).toBe("#c0ffee");
expect(config.colors.palette[0]).toBe("");
expect(config.colors.palette[1]).toBe("#222222");
});
});

describe("splitThemeSelection", () => {
it("keeps a Windows absolute theme path as a bare selection", () => {
expect(splitThemeSelection("C:/Users/Alex/Ghostty Themes/t3code")).toEqual({
light: "C:/Users/Alex/Ghostty Themes/t3code",
dark: "C:/Users/Alex/Ghostty Themes/t3code",
});
});

it("still parses explicit light and dark theme selections", () => {
expect(splitThemeSelection("light:Day,dark:Night")).toEqual({
light: "Day",
dark: "Night",
});
});
});

describe("ghosttyThemeSearchPaths", () => {
it("searches beside the macOS Application Support config", () => {
const candidates = ghosttyThemeSearchPaths(posixPath, {
home: "/Users/alex",
xdgConfigHome: "/Users/alex/.config",
themeName: "t3code",
});

expect(candidates).toContain(
"/Users/alex/Library/Application Support/com.mitchellh.ghostty/themes/t3code",
);
});

it("tries an absolute theme file before named-theme directories", () => {
const candidates = ghosttyThemeSearchPaths(windowsPath, {
home: "C:\\Users\\alex",
xdgConfigHome: "C:\\Users\\alex\\.config",
themeName: "D:\\themes\\t3code",
});

expect(candidates[0]).toBe("D:\\themes\\t3code");
});
});
Loading
Loading