diff --git a/apps/web/package.json b/apps/web/package.json index 6dc40046395b..381ae4a29fc2 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -36,7 +36,6 @@ "class-variance-authority": "^0.7.1", "culori": "^4.0.2", "effect": "catalog:", - "ghostty-web": "^0.4.0", "heic-to": "^1.5.2", "jose": "catalog:", "jsonc-parser": "3.3.1", diff --git a/apps/web/src/components/GhosttyTerminalSplitView.tsx b/apps/web/src/components/GhosttyTerminalSplitView.tsx deleted file mode 100644 index 92bdd6bcddd1..000000000000 --- a/apps/web/src/components/GhosttyTerminalSplitView.tsx +++ /dev/null @@ -1,736 +0,0 @@ -/** - * Mini Terminal Split View powered by libghostty (via ghostty-web WASM). - * - * This component demonstrates embedding Ghostty's battle-tested VT100 parser - * (compiled to WebAssembly from the original Zig source) into a React-based - * split terminal pane layout. - * - * Instead of xterm.js's JavaScript-based terminal emulation, this uses - * libghostty-vt — the same core used by the native Ghostty terminal app — - * providing superior Unicode handling, SIMD-optimized parsing, and proper - * support for complex scripts (Devanagari, Arabic, etc.). - * - * Architecture: - * ghostty-web (npm) → WASM (libghostty-vt compiled from Zig) → Canvas renderer - * React component → manages split pane layout, focus, resize - * Server PTY → WebSocket → ghostty-web Terminal.write() - */ - -import { init as initGhostty, Terminal, FitAddon, type ITheme } from "ghostty-web"; -import { - GripVertical, - Maximize2, - Minimize2, - Plus, - Split, - Terminal as TerminalIcon, - X, -} from "lucide-react"; -import { - type PointerEvent as ReactPointerEvent, - useCallback, - useEffect, - useMemo, - useRef, - useState, -} from "react"; -import type { EnvironmentApi } from "@t3tools/contracts"; -import { readNativeApi } from "~/nativeApi"; -import type { ThreadId } from "@t3tools/contracts"; -import { - contrastSafeTerminalColor, - normalizeAccentColor, - resolveAccentColorRgba, -} from "../accentColor"; -import { resolveTerminalFontFamily } from "../lib/terminalFont"; -import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip"; - -// ─── Ghostty WASM Initialization ──────────────────────────────────────────── -// ghostty-web requires a one-time async init to load the WASM module. -// We track the promise globally so multiple components share the same load. - -let ghosttyInitPromise: Promise | null = null; -let ghosttyReady = false; - -function ensureGhosttyInit(): Promise { - if (ghosttyReady) return Promise.resolve(); - if (!ghosttyInitPromise) { - ghosttyInitPromise = initGhostty().then(() => { - ghosttyReady = true; - }); - } - return ghosttyInitPromise; -} - -// ─── Theme ────────────────────────────────────────────────────────────────── - -const DARK_BG_HEX = "#0e1218"; -const LIGHT_BG_HEX = "#ffffff"; - -function clampByte(v: number): number { - return Math.min(255, Math.max(0, Math.round(v))); -} - -function mixHexWithWhite(hex: string, ratio: number): string { - const r = Number.parseInt(hex.slice(1, 3), 16); - const g = Number.parseInt(hex.slice(3, 5), 16); - const b = Number.parseInt(hex.slice(5, 7), 16); - const mr = clampByte(r + (255 - r) * ratio); - const mg = clampByte(g + (255 - g) * ratio); - const mb = clampByte(b + (255 - b) * ratio); - return `#${mr.toString(16).padStart(2, "0")}${mg.toString(16).padStart(2, "0")}${mb.toString(16).padStart(2, "0")}`; -} - -function ghosttyThemeFromApp(): ITheme { - const isDark = document.documentElement.classList.contains("dark"); - const bodyStyles = getComputedStyle(document.body); - const rootStyles = getComputedStyle(document.documentElement); - const background = - bodyStyles.backgroundColor || (isDark ? "rgb(14, 18, 24)" : "rgb(255, 255, 255)"); - const foreground = bodyStyles.color || (isDark ? "rgb(237, 241, 247)" : "rgb(28, 33, 41)"); - const accentColor = normalizeAccentColor(rootStyles.getPropertyValue("--accent-color")); - const bgHex = isDark ? DARK_BG_HEX : LIGHT_BG_HEX; - const terminalBlue = contrastSafeTerminalColor(accentColor, bgHex); - const brightMix = isDark ? 0.3 : 0.18; - const terminalBrightBlue = contrastSafeTerminalColor( - mixHexWithWhite(accentColor, brightMix), - bgHex, - ); - const selectionBackground = resolveAccentColorRgba(accentColor, isDark ? 0.3 : 0.22); - - if (isDark) { - return { - background, - foreground, - cursor: terminalBrightBlue, - selectionBackground, - black: "rgb(24, 30, 38)", - red: "rgb(255, 122, 142)", - green: "rgb(134, 231, 149)", - yellow: "rgb(244, 205, 114)", - blue: terminalBlue, - magenta: "rgb(208, 176, 255)", - cyan: "rgb(124, 232, 237)", - white: "rgb(210, 218, 230)", - brightBlack: "rgb(110, 120, 136)", - brightRed: "rgb(255, 168, 180)", - brightGreen: "rgb(176, 245, 186)", - brightYellow: "rgb(255, 224, 149)", - brightBlue: terminalBrightBlue, - brightMagenta: "rgb(229, 203, 255)", - brightCyan: "rgb(167, 244, 247)", - brightWhite: "rgb(244, 247, 252)", - }; - } - - return { - background, - foreground, - cursor: terminalBlue, - selectionBackground, - black: "rgb(44, 53, 66)", - red: "rgb(191, 70, 87)", - green: "rgb(60, 126, 86)", - yellow: "rgb(146, 112, 35)", - blue: terminalBlue, - magenta: "rgb(132, 86, 149)", - cyan: "rgb(53, 127, 141)", - white: "rgb(210, 215, 223)", - brightBlack: "rgb(112, 123, 140)", - brightRed: "rgb(212, 95, 112)", - brightGreen: "rgb(85, 148, 111)", - brightYellow: "rgb(173, 133, 45)", - brightBlue: terminalBrightBlue, - brightMagenta: "rgb(153, 107, 172)", - brightCyan: "rgb(70, 149, 164)", - brightWhite: "rgb(236, 240, 246)", - }; -} - -// ─── Constants ────────────────────────────────────────────────────────────── - -const MIN_PANE_WIDTH_PX = 120; -const MAX_PANES = 4; -const MIN_CONTAINER_HEIGHT = 200; -const MAX_CONTAINER_HEIGHT = 600; -const DEFAULT_CONTAINER_HEIGHT = 350; - -// ─── Types ────────────────────────────────────────────────────────────────── - -interface SplitPane { - id: string; - terminalId: string; -} - -// ─── Single Ghostty Terminal Pane ─────────────────────────────────────────── - -interface GhosttyPaneProps { - threadId: ThreadId; - terminalId: string; - cwd: string; - runtimeEnv?: Record; - isActive: boolean; - onFocus: () => void; - onClose: () => void; - resizeEpoch: number; - containerHeight: number; -} - -function GhosttyPane({ - threadId, - terminalId, - cwd, - runtimeEnv, - isActive, - onFocus, - onClose, - resizeEpoch, - containerHeight, -}: GhosttyPaneProps) { - const containerRef = useRef(null); - const terminalRef = useRef(null); - const fitAddonRef = useRef(null); - const [status, setStatus] = useState<"loading" | "ready" | "error">("loading"); - - // Initialize ghostty-web terminal - useEffect(() => { - const mount = containerRef.current; - if (!mount) return; - - let disposed = false; - - const setup = async () => { - try { - // Ensure WASM is loaded - await ensureGhosttyInit(); - if (disposed) return; - - const fitAddon = new FitAddon(); - const terminal = new Terminal({ - cursorBlink: true, - fontSize: 12, - scrollback: 5_000, - fontFamily: resolveTerminalFontFamily(), - theme: ghosttyThemeFromApp(), - }); - - terminal.loadAddon(fitAddon); - terminal.open(mount); - fitAddon.fit(); - - terminalRef.current = terminal; - fitAddonRef.current = fitAddon; - - if (disposed) { - terminal.dispose(); - return; - } - - setStatus("ready"); - - // Connect to backend PTY - // GhosttyTerminalSplitView accesses terminal via the environment API surface. - // Cast to EnvironmentApi since the local API includes terminal at runtime. - const api = readNativeApi() as unknown as EnvironmentApi | undefined; - if (!api) return; - - // Handle user input → send to PTY - const inputDisposable = terminal.onData((data) => { - void api.terminal.write({ threadId, terminalId, data }).catch((err: unknown) => { - terminal.write( - `\r\n[ghostty] ${err instanceof Error ? err.message : "Write failed"}\r\n`, - ); - }); - }); - - // Attach to the terminal session: the stream replays a snapshot first, - // then delivers ordered live events for this thread/terminal pair. - fitAddon.fit(); - const unsubscribe = api.terminal.attach( - { - threadId, - terminalId, - cwd, - cols: terminal.cols, - rows: terminal.rows, - ...(runtimeEnv ? { env: runtimeEnv } : {}), - }, - (event) => { - const activeTerminal = terminalRef.current; - if (!activeTerminal || disposed) return; - - switch (event.type) { - case "snapshot": - activeTerminal.write("\u001bc"); - if (event.snapshot.history.length > 0) { - activeTerminal.write(event.snapshot.history); - } - if (isActive) { - window.requestAnimationFrame(() => activeTerminal.focus()); - } - break; - case "output": - activeTerminal.write(event.data); - break; - case "restarted": - activeTerminal.write("\u001bc"); - if (event.snapshot.history.length > 0) { - activeTerminal.write(event.snapshot.history); - } - break; - case "cleared": - activeTerminal.clear(); - activeTerminal.write("\u001bc"); - break; - case "error": - activeTerminal.write(`\r\n[ghostty] ${event.message}\r\n`); - break; - case "exited": { - const details = [ - typeof event.exitCode === "number" ? `code ${event.exitCode}` : null, - typeof event.exitSignal === "number" ? `signal ${event.exitSignal}` : null, - ] - .filter((v): v is string => v !== null) - .join(", "); - activeTerminal.write( - `\r\n[ghostty] ${details ? `Process exited (${details})` : "Process exited"}\r\n`, - ); - break; - } - } - }, - ); - - // Theme observer - const themeObserver = new MutationObserver(() => { - const t = terminalRef.current; - if (!t) return; - t.options.theme = ghosttyThemeFromApp(); - }); - themeObserver.observe(document.documentElement, { - attributes: true, - attributeFilter: ["class", "style"], - }); - - // Cleanup on unmount - return () => { - disposed = true; - unsubscribe(); - inputDisposable.dispose(); - themeObserver.disconnect(); - terminalRef.current = null; - fitAddonRef.current = null; - terminal.dispose(); - }; - } catch (err) { - if (!disposed) { - setStatus("error"); - console.error("[ghostty-web] Init failed:", err); - } - } - }; - - const cleanupPromise = setup(); - - return () => { - disposed = true; - void cleanupPromise?.then((cleanup) => cleanup?.()); - }; - }, [cwd, runtimeEnv, terminalId, threadId]); - - // Handle focus - useEffect(() => { - if (!isActive) return; - const terminal = terminalRef.current; - if (!terminal) return; - const frame = window.requestAnimationFrame(() => terminal.focus()); - return () => window.cancelAnimationFrame(frame); - }, [isActive]); - - // Handle resize - useEffect(() => { - const api = readNativeApi() as unknown as EnvironmentApi | undefined; - const terminal = terminalRef.current; - const fitAddon = fitAddonRef.current; - if (!api || !terminal || !fitAddon) return; - - const frame = window.requestAnimationFrame(() => { - fitAddon.fit(); - terminal.scrollToBottom(); - void api.terminal - .resize({ - threadId, - terminalId, - cols: terminal.cols, - rows: terminal.rows, - }) - .catch(() => undefined); - }); - return () => window.cancelAnimationFrame(frame); - }, [containerHeight, resizeEpoch, terminalId, threadId]); - - return ( -
- {/* Pane header */} -
-
- - - {status === "loading" ? "Loading WASM…" : status === "error" ? "Error" : "ghostty"} - - {status === "ready" && ( - - libghostty - - )} -
- -
- - {/* Terminal canvas area */} -
-
- ); -} - -// ─── Split Divider ────────────────────────────────────────────────────────── - -interface SplitDividerProps { - onPointerDown: (e: ReactPointerEvent) => void; - onPointerMove: (e: ReactPointerEvent) => void; - onPointerUp: (e: ReactPointerEvent) => void; -} - -function SplitDivider({ onPointerDown, onPointerMove, onPointerUp }: SplitDividerProps) { - return ( -
- -
- ); -} - -// ─── Main Split View Component ────────────────────────────────────────────── - -export interface GhosttyTerminalSplitViewProps { - threadId: ThreadId; - cwd: string; - runtimeEnv?: Record; -} - -let nextPaneCounter = 0; -function createPaneId(): string { - nextPaneCounter += 1; - return `ghostty-pane-${nextPaneCounter}-${Date.now().toString(36)}`; -} - -export default function GhosttyTerminalSplitView({ - threadId, - cwd, - runtimeEnv, -}: GhosttyTerminalSplitViewProps) { - const [panes, setPanes] = useState(() => { - const id = createPaneId(); - return [{ id, terminalId: `ghostty-${id}` }]; - }); - const [activePaneId, setActivePaneId] = useState(() => panes[0]!.id); - const [containerHeight, setContainerHeight] = useState(DEFAULT_CONTAINER_HEIGHT); - const [isCollapsed, setIsCollapsed] = useState(false); - const [resizeEpoch, setResizeEpoch] = useState(0); - const containerRef = useRef(null); - const resizeStateRef = useRef<{ - pointerId: number; - startY: number; - startHeight: number; - } | null>(null); - - const canSplit = panes.length < MAX_PANES; - - // ─── Pane management ──────────────────────────────────────────────── - - const handleSplit = useCallback(() => { - if (!canSplit) return; - const id = createPaneId(); - const newPane: SplitPane = { id, terminalId: `ghostty-${id}` }; - setPanes((prev) => [...prev, newPane]); - setActivePaneId(id); - setResizeEpoch((e) => e + 1); - }, [canSplit]); - - const handleClosePane = useCallback( - (paneId: string) => { - setPanes((prev) => { - if (prev.length <= 1) return prev; // keep at least one - const next = prev.filter((p) => p.id !== paneId); - if (activePaneId === paneId) { - setActivePaneId(next[0]?.id ?? ""); - } - setResizeEpoch((e) => e + 1); - return next; - }); - }, - [activePaneId], - ); - - // ─── Vertical resize (container height) ───────────────────────────── - - const handleResizePointerDown = useCallback( - (e: ReactPointerEvent) => { - if (e.button !== 0) return; - e.preventDefault(); - e.currentTarget.setPointerCapture(e.pointerId); - resizeStateRef.current = { - pointerId: e.pointerId, - startY: e.clientY, - startHeight: containerHeight, - }; - }, - [containerHeight], - ); - - const handleResizePointerMove = useCallback((e: ReactPointerEvent) => { - const state = resizeStateRef.current; - if (!state || state.pointerId !== e.pointerId) return; - e.preventDefault(); - const nextHeight = Math.min( - MAX_CONTAINER_HEIGHT, - Math.max(MIN_CONTAINER_HEIGHT, state.startHeight + (state.startY - e.clientY)), - ); - setContainerHeight(nextHeight); - }, []); - - const handleResizePointerUp = useCallback((e: ReactPointerEvent) => { - const state = resizeStateRef.current; - if (!state || state.pointerId !== e.pointerId) return; - resizeStateRef.current = null; - if (e.currentTarget.hasPointerCapture(e.pointerId)) { - e.currentTarget.releasePointerCapture(e.pointerId); - } - setResizeEpoch((v) => v + 1); - }, []); - - // ─── Window resize ────────────────────────────────────────────────── - - useEffect(() => { - const onResize = () => setResizeEpoch((v) => v + 1); - window.addEventListener("resize", onResize); - return () => window.removeEventListener("resize", onResize); - }, []); - - // ─── Keyboard shortcut for splitting ──────────────────────────────── - - useEffect(() => { - const onKeyDown = (e: KeyboardEvent) => { - // Cmd/Ctrl + Shift + D to split - if ((e.metaKey || e.ctrlKey) && e.shiftKey && e.key === "d") { - e.preventDefault(); - handleSplit(); - } - }; - window.addEventListener("keydown", onKeyDown); - return () => window.removeEventListener("keydown", onKeyDown); - }, [handleSplit]); - - // ─── Pane label map ───────────────────────────────────────────────── - - const paneLabelMap = useMemo( - () => new Map(panes.map((p, i) => [p.id, `Pane ${i + 1}`])), - [panes], - ); - - if (isCollapsed) { - return ( -
- -
- ); - } - - return ( -
- {/* Resize handle (top edge) */} -
- - {/* Toolbar */} -
-
- - - Ghostty Split View - - - libghostty - -
- -
- {/* Pane tabs */} - {panes.length > 1 && - panes.map((pane) => ( - - ))} - -
- - {/* Split button */} - - - } - > - - - - {canSplit ? `Split (max ${MAX_PANES})` : `Max ${MAX_PANES} panes`} - - - - {/* Add new pane */} - - - } - > - - - {canSplit ? "New pane" : `Max ${MAX_PANES} panes`} - - - {/* Collapse */} - - setIsCollapsed(true)} - aria-label="Collapse terminal split view" - /> - } - > - - - Collapse - -
-
- - {/* Split pane container */} -
- {panes.map((pane, index) => ( -
- {index > 0 && ( - {}} - onPointerMove={() => {}} - onPointerUp={() => {}} - /> - )} - setActivePaneId(pane.id)} - onClose={() => handleClosePane(pane.id)} - resizeEpoch={resizeEpoch} - containerHeight={containerHeight} - /> -
- ))} -
-
- ); -} diff --git a/apps/web/src/components/ProviderLogo.tsx b/apps/web/src/components/ProviderLogo.tsx deleted file mode 100644 index 9508cac7361f..000000000000 --- a/apps/web/src/components/ProviderLogo.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import { type ComponentProps } from "react"; - -import { useAppSettings } from "../appSettings"; -import { cn } from "../lib/utils"; -import type { ProviderKind } from "../providerKind"; -import { - type Icon, - ACPRegistryIcon, - AmpIcon, - ClaudeAI, - CursorIcon, - DroidIcon, - FxIcon, - Gemini, - GitHubIcon, - GrokIcon, - HermesIcon, - KiloIcon, - OhMyPiIcon, - OpenAI, - OpenCodeIcon, - PiAgentIcon, -} from "./Icons"; - -const PROVIDER_ICON_BY_PROVIDER: Record = { - acp: ACPRegistryIcon, - codex: OpenAI, - copilot: GitHubIcon, - claudeAgent: ClaudeAI, - cursor: CursorIcon, - droid: DroidIcon, - fx: FxIcon, - grok: GrokIcon, - opencode: OpenCodeIcon, - geminiCli: Gemini, - amp: AmpIcon, - kilo: KiloIcon, - hermes: HermesIcon, - pi: PiAgentIcon, - ohMyPi: OhMyPiIcon, -}; - -export type ProviderLogoProps = ComponentProps & { - provider: ProviderKind; -}; - -export function ProviderLogo({ provider, className, style, ...props }: ProviderLogoProps) { - const { settings } = useAppSettings(); - const ProviderIcon = PROVIDER_ICON_BY_PROVIDER[provider]; - const isAccentAppearance = settings.providerLogoAppearance === "accent"; - const accentColor = settings.providerAccentColors[provider] ?? settings.accentColor; - - return ( - - ); -} diff --git a/apps/web/src/environmentBootstrap.ts b/apps/web/src/environmentBootstrap.ts deleted file mode 100644 index dacb07298c5e..000000000000 --- a/apps/web/src/environmentBootstrap.ts +++ /dev/null @@ -1,86 +0,0 @@ -import { createKnownEnvironment, type KnownEnvironment } from "@t3tools/client-runtime/environment"; -import type { DesktopEnvironmentBootstrap } from "@t3tools/contracts"; - -function normalizeBaseUrl(rawValue: string): string { - return new URL(rawValue, window.location.origin).toString(); -} - -function swapBaseUrlProtocol( - rawValue: string, - nextProtocol: "http:" | "https:" | "ws:" | "wss:", -): string { - const url = new URL(normalizeBaseUrl(rawValue)); - url.protocol = nextProtocol; - return url.toString(); -} - -function createKnownEnvironmentFromWsUrl(input: { - readonly id: string; - readonly label: string; - readonly source: KnownEnvironment["source"]; - readonly wsUrl: string; -}): KnownEnvironment { - const wsBaseUrl = normalizeBaseUrl(input.wsUrl); - const httpBaseUrl = wsBaseUrl.startsWith("wss:") - ? swapBaseUrlProtocol(wsBaseUrl, "https:") - : swapBaseUrlProtocol(wsBaseUrl, "http:"); - - return createKnownEnvironment({ - id: input.id, - label: input.label, - source: input.source, - target: { - httpBaseUrl, - wsBaseUrl, - }, - }); -} - -function createKnownEnvironmentFromDesktopBootstrap( - bootstrap: DesktopEnvironmentBootstrap | null | undefined, -): KnownEnvironment | null { - if (!bootstrap?.wsBaseUrl) { - return null; - } - - return createKnownEnvironmentFromWsUrl({ - id: `desktop:${bootstrap.label}`, - label: bootstrap.label, - source: "desktop-managed", - wsUrl: bootstrap.wsBaseUrl, - }); -} - -export function getPrimaryKnownEnvironment(): KnownEnvironment | null { - const desktopEnvironment = createKnownEnvironmentFromDesktopBootstrap( - window.desktopBridge?.getLocalEnvironmentBootstraps()?.[0], - ); - if (desktopEnvironment) { - return desktopEnvironment; - } - - const configuredWsUrl = import.meta.env.VITE_WS_URL; - if (typeof configuredWsUrl === "string" && configuredWsUrl.length > 0) { - return createKnownEnvironmentFromWsUrl({ - id: "configured-primary", - label: "Primary environment", - source: "configured", - wsUrl: configuredWsUrl, - }); - } - - return createKnownEnvironmentFromWsUrl({ - id: "window-origin", - label: "Primary environment", - source: "window-origin", - wsUrl: window.location.origin, - }); -} - -export function resolvePrimaryEnvironmentBootstrapUrl(): string { - const baseUrl = getPrimaryKnownEnvironment()?.target.httpBaseUrl ?? null; - if (!baseUrl) { - throw new Error("Unable to resolve a known environment bootstrap URL."); - } - return baseUrl; -} diff --git a/apps/web/src/lib/terminalFont.ts b/apps/web/src/lib/terminalFont.ts deleted file mode 100644 index ccc206af10cd..000000000000 --- a/apps/web/src/lib/terminalFont.ts +++ /dev/null @@ -1,30 +0,0 @@ -const DEFAULT_TERMINAL_FONT_FAMILY = [ - '"Symbols Nerd Font Mono"', - '"Symbols Nerd Font"', - '"JetBrainsMono Nerd Font Mono"', - '"JetBrainsMonoNL Nerd Font Mono"', - '"Hack Nerd Font Mono"', - '"SauceCodePro Nerd Font Mono"', - '"FiraCode Nerd Font Mono"', - '"MesloLGS NF"', - '"CaskaydiaMono Nerd Font Mono"', - '"Geist Mono"', - '"SF Mono"', - '"SFMono-Regular"', - "Consolas", - '"Liberation Mono"', - "Menlo", - "monospace", -].join(", "); - -export function resolveTerminalFontFamily(): string { - if (typeof window === "undefined") { - return DEFAULT_TERMINAL_FONT_FAMILY; - } - - const configured = getComputedStyle(document.documentElement) - .getPropertyValue("--terminal-font-family") - .trim(); - - return configured.length > 0 ? configured : DEFAULT_TERMINAL_FONT_FAMILY; -} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts index 98418c167aa5..e676c44ee019 100644 --- a/apps/web/vite.config.ts +++ b/apps/web/vite.config.ts @@ -183,7 +183,6 @@ export default defineConfig(() => { "@pierre/diffs/editor", "@pierre/diffs/react", "@pierre/diffs/worker/worker.js", - "ghostty-web", "effect/Array", "effect/Order", "react-dom/client", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 90f1ce7fb510..c9a2712318c1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -635,9 +635,6 @@ importers: effect: specifier: 4.0.0-beta.103 version: 4.0.0-beta.103(patch_hash=af36b7948b6f9c56623074662b51dade5699880c1a7c71245de73e13c3185fb6) - ghostty-web: - specifier: ^0.4.0 - version: 0.4.0 heic-to: specifier: ^1.5.2 version: 1.5.2 @@ -7274,9 +7271,6 @@ packages: resolution: {integrity: sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==} engines: {node: '>=6'} - ghostty-web@0.4.0: - resolution: {integrity: sha512-0puDBik2qapbD/QQBW9o5ZHfXnZBqZWx/ctBiVtKZ6ZLds4NYb+wZuw1cRLXZk9zYovIQ908z3rvFhexAvc5Hg==} - github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} @@ -17511,8 +17505,6 @@ snapshots: getenv@2.0.0: {} - ghostty-web@0.4.0: {} - github-slugger@2.0.0: {} glob-parent@5.1.2: