diff --git a/apps/desktop/src-tauri/build.rs b/apps/desktop/src-tauri/build.rs index 2f2429d..0b99e58 100644 --- a/apps/desktop/src-tauri/build.rs +++ b/apps/desktop/src-tauri/build.rs @@ -6,5 +6,20 @@ fn main() { println!("cargo:rustc-env={key}={val}"); } } + + // On Linux, apply a version script to hide statically linked ICU/HarfBuzz/ + // FreeType/Fontconfig symbols from the dynamic symbol table. This prevents + // symbol collisions with the system copies loaded by WebKit2GTK (segfault). + // See: https://github.com/delibae/claude-prism/issues/100 + #[cfg(target_os = "linux")] + { + let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR"); + println!( + "cargo:rustc-link-arg=-Wl,--version-script={}/symbols.map", + manifest_dir + ); + println!("cargo:rerun-if-changed=symbols.map"); + } + tauri_build::build() } diff --git a/apps/desktop/src-tauri/capabilities/default.json b/apps/desktop/src-tauri/capabilities/default.json index 55457d0..4b93ce6 100644 --- a/apps/desktop/src-tauri/capabilities/default.json +++ b/apps/desktop/src-tauri/capabilities/default.json @@ -21,28 +21,61 @@ "dialog:allow-message", "dialog:allow-ask", "fs:default", - "fs:allow-read", - "fs:allow-write", - "fs:allow-exists", - "fs:allow-mkdir", - "fs:allow-remove", - "fs:allow-rename", - "fs:allow-copy-file", - "fs:allow-read-dir", - "fs:allow-read-text-file", - "fs:allow-write-text-file", - "fs:allow-read-file", - "fs:allow-write-file", - "fs:allow-stat", - "fs:allow-watch", { - "identifier": "fs:scope", - "allow": [ - { "path": "$HOME/**" }, - { "path": "$DOCUMENT/**" }, - { "path": "$DESKTOP/**" }, - { "path": "$TEMP/**" } - ] + "identifier": "fs:allow-read", + "allow": [{ "path": "$HOME/**" }, { "path": "$DOCUMENT/**" }, { "path": "$DESKTOP/**" }, { "path": "$TEMP/**" }] + }, + { + "identifier": "fs:allow-write", + "allow": [{ "path": "$HOME/**" }, { "path": "$DOCUMENT/**" }, { "path": "$DESKTOP/**" }, { "path": "$TEMP/**" }] + }, + { + "identifier": "fs:allow-exists", + "allow": [{ "path": "$HOME/**" }, { "path": "$DOCUMENT/**" }, { "path": "$DESKTOP/**" }, { "path": "$TEMP/**" }] + }, + { + "identifier": "fs:allow-mkdir", + "allow": [{ "path": "$HOME/**" }, { "path": "$DOCUMENT/**" }, { "path": "$DESKTOP/**" }, { "path": "$TEMP/**" }] + }, + { + "identifier": "fs:allow-remove", + "allow": [{ "path": "$HOME/**" }, { "path": "$DOCUMENT/**" }, { "path": "$DESKTOP/**" }, { "path": "$TEMP/**" }] + }, + { + "identifier": "fs:allow-rename", + "allow": [{ "path": "$HOME/**" }, { "path": "$DOCUMENT/**" }, { "path": "$DESKTOP/**" }, { "path": "$TEMP/**" }] + }, + { + "identifier": "fs:allow-copy-file", + "allow": [{ "path": "$HOME/**" }, { "path": "$DOCUMENT/**" }, { "path": "$DESKTOP/**" }, { "path": "$TEMP/**" }] + }, + { + "identifier": "fs:allow-read-dir", + "allow": [{ "path": "$HOME/**" }, { "path": "$DOCUMENT/**" }, { "path": "$DESKTOP/**" }, { "path": "$TEMP/**" }] + }, + { + "identifier": "fs:allow-read-text-file", + "allow": [{ "path": "$HOME/**" }, { "path": "$DOCUMENT/**" }, { "path": "$DESKTOP/**" }, { "path": "$TEMP/**" }] + }, + { + "identifier": "fs:allow-write-text-file", + "allow": [{ "path": "$HOME/**" }, { "path": "$DOCUMENT/**" }, { "path": "$DESKTOP/**" }, { "path": "$TEMP/**" }] + }, + { + "identifier": "fs:allow-read-file", + "allow": [{ "path": "$HOME/**" }, { "path": "$DOCUMENT/**" }, { "path": "$DESKTOP/**" }, { "path": "$TEMP/**" }] + }, + { + "identifier": "fs:allow-write-file", + "allow": [{ "path": "$HOME/**" }, { "path": "$DOCUMENT/**" }, { "path": "$DESKTOP/**" }, { "path": "$TEMP/**" }] + }, + { + "identifier": "fs:allow-stat", + "allow": [{ "path": "$HOME/**" }, { "path": "$DOCUMENT/**" }, { "path": "$DESKTOP/**" }, { "path": "$TEMP/**" }] + }, + { + "identifier": "fs:allow-watch", + "allow": [{ "path": "$HOME/**" }, { "path": "$DOCUMENT/**" }, { "path": "$DESKTOP/**" }, { "path": "$TEMP/**" }] }, "shell:default", "shell:allow-open", diff --git a/apps/desktop/src-tauri/symbols.map b/apps/desktop/src-tauri/symbols.map new file mode 100644 index 0000000..32a364c --- /dev/null +++ b/apps/desktop/src-tauri/symbols.map @@ -0,0 +1,50 @@ +/* Version script to hide statically linked library symbols. + * + * Prevents symbol collisions between our statically linked + * ICU/HarfBuzz/FreeType/Fontconfig (from vcpkg, used by Tectonic) + * and the dynamically linked versions used by WebKit2GTK at runtime. + * + * Without this, the ELF dynamic linker resolves WebKit2GTK's ICU + * calls to our static symbols (different version), causing a segfault + * at startup. Fixes #100; preserves #91 fix (static ICU = no + * libicuuc.so.70 not-found error on newer distros). + */ +{ + local: + /* ICU4C */ + u_*; + ucnv_*; + ucol_*; + ures_*; + udata_*; + ucal_*; + udat_*; + unum_*; + uset_*; + ustr_*; + ubidi_*; + ubrk_*; + uidna_*; + uloc_*; + unorm_*; + unorm2_*; + utrans_*; + icu_*; + UCNV_*; + + /* HarfBuzz */ + hb_*; + + /* FreeType */ + FT_*; + ft_*; + + /* Fontconfig */ + Fc*; + + /* Graphite2 */ + gr_*; + + /* libpng (often pulled in by FreeType) */ + png_*; +}; diff --git a/apps/desktop/src/components/claude-chat/markdown-renderer.tsx b/apps/desktop/src/components/claude-chat/markdown-renderer.tsx index bc4d83b..4c08078 100644 --- a/apps/desktop/src/components/claude-chat/markdown-renderer.tsx +++ b/apps/desktop/src/components/claude-chat/markdown-renderer.tsx @@ -220,7 +220,7 @@ const CodeBlock: FC<{ language: string; code: string }> = ({ Run in{" "} - {projectRoot?.split("/").pop()}/ + {projectRoot?.split(/[/\\]/).pop()}/ diff --git a/apps/desktop/src/components/project-wizard.tsx b/apps/desktop/src/components/project-wizard.tsx index f583381..e318349 100644 --- a/apps/desktop/src/components/project-wizard.tsx +++ b/apps/desktop/src/components/project-wizard.tsx @@ -3,6 +3,7 @@ import { open } from "@tauri-apps/plugin-dialog"; import { mkdir, writeTextFile } from "@tauri-apps/plugin-fs"; import { getCurrentWebview } from "@tauri-apps/api/webview"; import { homeDir } from "@tauri-apps/api/path"; +import { toast } from "sonner"; import { ArrowLeftIcon, FolderOpenIcon, @@ -133,11 +134,11 @@ function ScratchForm({ onBack }: { onBack: () => void }) { } else { homeDir() .then((home) => join(home, "Documents", "ClaudePrism")) - .then((dir) => { - mkdir(dir, { recursive: true }).catch(() => {}); + .then(async (dir) => { + await mkdir(dir, { recursive: true }).catch(() => {}); setProjectFolder(dir); }) - .catch(() => {}); + .catch((err) => console.warn("Failed to resolve default project folder:", err)); } }, []); // eslint-disable-line react-hooks/exhaustive-deps @@ -234,7 +235,7 @@ function ScratchForm({ onBack }: { onBack: () => void }) { try { const projectPath = await join(projectFolder, projectName.trim()); - await mkdir(projectPath, { recursive: true }).catch(() => {}); + await mkdir(projectPath, { recursive: true }); // Create CLAUDE.md for Claude Code context const claudeMdPath = await join(projectPath, "CLAUDE.md"); @@ -259,12 +260,12 @@ function ScratchForm({ onBack }: { onBack: () => void }) { if (attachments.length > 0) { const attachmentsDir = await join(projectPath, "attachments"); - await mkdir(attachmentsDir, { recursive: true }).catch(() => {}); + await mkdir(attachmentsDir, { recursive: true }); } if (purpose.trim()) { const attachmentNames = attachments - .map((p) => p.split("/").pop()) + .map((p) => p.split(/[/\\]/).pop()) .filter(Boolean); const attachmentSection = attachmentNames.length > 0 @@ -303,6 +304,9 @@ function ScratchForm({ onBack }: { onBack: () => void }) { } } catch (err) { console.error("Failed to create project:", err); + toast.error("Failed to create project", { + description: err instanceof Error ? err.message : String(err), + }); } finally { setIsCreating(false); } @@ -383,7 +387,7 @@ function ScratchForm({ onBack }: { onBack: () => void }) { > - {path.split("/").pop()} + {path.split(/[/\\]/).pop()}