Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
102 changes: 102 additions & 0 deletions apps/server/src/terminal/ghosttyStyle.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import { describe, expect, it } from "vite-plus/test";

import {
ghosttyThemeSearchPaths,
mergeGhosttyColors,
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).toBe("");
expect(config.colors.foreground).toBe("");
expect(config.colors.cursor).toBe("#c0ffee");
expect(config.colors.palette[0]).toBe("");
expect(config.colors.palette[1]).toBe("#222222");
});

it("keeps explicit color clears when merging user config over a theme", () => {
const theme = parseGhosttyConfig(`
background = #000000
palette = 0=#111111
palette = 1=#222222
`);
const user = parseGhosttyConfig(`
background =
palette = 0=
`);

const colors = mergeGhosttyColors(theme.colors, user.colors);

expect(colors.background).toBe("");
expect(colors.palette[0]).toBe("");
expect(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