mirror of
https://github.com/delibae/claude-prism.git
synced 2026-08-28 05:14:59 +00:00
Merge pull request #102 from delibae/fix/windows-project-creation-and-linux-segfault
fix: resolve Windows project creation failure (#101) and Linux segfault (#100)
This commit is contained in:
commit
0f9755d7e1
10 changed files with 158 additions and 47 deletions
|
|
@ -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()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
50
apps/desktop/src-tauri/symbols.map
Normal file
50
apps/desktop/src-tauri/symbols.map
Normal file
|
|
@ -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_*;
|
||||
};
|
||||
|
|
@ -220,7 +220,7 @@ const CodeBlock: FC<{ language: string; code: string }> = ({
|
|||
<span className="text-xs">
|
||||
Run in{" "}
|
||||
<code className="rounded bg-muted px-1 text-xs">
|
||||
{projectRoot?.split("/").pop()}/
|
||||
{projectRoot?.split(/[/\\]/).pop()}/
|
||||
</code>
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -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 }) {
|
|||
>
|
||||
<PaperclipIcon className="size-3 shrink-0 text-muted-foreground/70" />
|
||||
<span className="max-w-[140px] truncate text-foreground/80">
|
||||
{path.split("/").pop()}
|
||||
{path.split(/[/\\]/).pop()}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => handleRemoveAttachment(path)}
|
||||
|
|
@ -447,7 +451,7 @@ function ScratchForm({ onBack }: { onBack: () => void }) {
|
|||
</div>
|
||||
{!locationOpen && projectFolder && projectName.trim() && (
|
||||
<span className="min-w-0 max-w-[180px] truncate rounded-md bg-muted/40 px-2 py-0.5 font-mono text-[11px] text-muted-foreground/60">
|
||||
.../{projectFolder.split("/").pop()}/{projectName.trim()}
|
||||
.../{projectFolder.split(/[/\\]/).pop()}/{projectName.trim()}
|
||||
</span>
|
||||
)}
|
||||
<ChevronDownIcon
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import { useState, useCallback, useEffect, useRef } from "react";
|
|||
import { open } from "@tauri-apps/plugin-dialog";
|
||||
import { mkdir, writeTextFile } from "@tauri-apps/plugin-fs";
|
||||
import { getCurrentWebview } from "@tauri-apps/api/webview";
|
||||
import { documentDir } from "@tauri-apps/api/path";
|
||||
import { homeDir } from "@tauri-apps/api/path";
|
||||
import { toast } from "sonner";
|
||||
import {
|
||||
ChevronLeftIcon,
|
||||
ChevronRightIcon,
|
||||
|
|
@ -159,9 +160,15 @@ export function TemplatePreview() {
|
|||
if (lastProjectFolder) {
|
||||
setProjectFolder(lastProjectFolder);
|
||||
} else {
|
||||
documentDir()
|
||||
.then((dir) => setProjectFolder(dir))
|
||||
.catch(() => {});
|
||||
homeDir()
|
||||
.then((home) => join(home, "Documents", "ClaudePrism"))
|
||||
.then(async (dir) => {
|
||||
await mkdir(dir, { recursive: true }).catch(() => {});
|
||||
setProjectFolder(dir);
|
||||
})
|
||||
.catch((err) =>
|
||||
console.warn("Failed to resolve default project folder:", err),
|
||||
);
|
||||
}
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
|
|
@ -400,7 +407,7 @@ export function TemplatePreview() {
|
|||
|
||||
try {
|
||||
const projectPath = await join(projectFolder, projectName.trim());
|
||||
await mkdir(projectPath, { recursive: true }).catch(() => {});
|
||||
await mkdir(projectPath, { recursive: true });
|
||||
|
||||
const mainTexPath = await join(projectPath, template.mainFileName);
|
||||
const mainExists = await exists(mainTexPath);
|
||||
|
|
@ -418,12 +425,12 @@ export function TemplatePreview() {
|
|||
|
||||
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
|
||||
|
|
@ -465,6 +472,9 @@ export function TemplatePreview() {
|
|||
closePreview();
|
||||
} 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);
|
||||
}
|
||||
|
|
@ -650,7 +660,7 @@ export function TemplatePreview() {
|
|||
>
|
||||
<PaperclipIcon className="size-3 shrink-0 text-muted-foreground/70" />
|
||||
<span className="max-w-30 truncate text-foreground/80">
|
||||
{path.split("/").pop()}
|
||||
{path.split(/[/\\]/).pop()}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => handleRemoveAttachment(path)}
|
||||
|
|
@ -713,7 +723,7 @@ export function TemplatePreview() {
|
|||
</div>
|
||||
{!locationOpen && projectFolder && projectName.trim() && (
|
||||
<span className="min-w-0 max-w-35 truncate rounded-md bg-muted/40 px-2 py-0.5 font-mono text-[11px] text-muted-foreground/60">
|
||||
.../{projectFolder.split("/").pop()}/
|
||||
.../{projectFolder.split(/[/\\]/).pop()}/
|
||||
{projectName.trim()}
|
||||
</span>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -152,8 +152,7 @@ export function UvSetupDialog({ open, onClose }: UvSetupDialogProps) {
|
|||
title={pythonPath}
|
||||
>
|
||||
Python:{" "}
|
||||
{pythonPath.split("/").pop() ||
|
||||
pythonPath.split("\\").pop()}
|
||||
{pythonPath.split(/[/\\]/).pop()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -344,7 +344,7 @@ function SnapshotRow({
|
|||
{hasFiles && (
|
||||
<div className="mt-0.5 truncate text-muted-foreground text-xs">
|
||||
{snapshot.changed_files
|
||||
.map((f) => f.split("/").pop())
|
||||
.map((f) => f.split(/[/\\]/).pop())
|
||||
.join(", ")}
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -241,7 +241,7 @@ export const useDocumentStore = create<DocumentState>()((set, get) => ({
|
|||
for (const f of fsFiles) {
|
||||
const pf: ProjectFile = {
|
||||
id: f.relativePath,
|
||||
name: f.relativePath.split("/").pop() || f.relativePath,
|
||||
name: f.relativePath.split(/[/\\]/).pop() || f.relativePath,
|
||||
relativePath: f.relativePath,
|
||||
absolutePath: f.absolutePath,
|
||||
type: f.type,
|
||||
|
|
@ -796,7 +796,7 @@ export const useDocumentStore = create<DocumentState>()((set, get) => ({
|
|||
const newAbsPath = await join(state.projectRoot, newRelativePath);
|
||||
await renameFileOnDisk(file.absolutePath, newAbsPath);
|
||||
|
||||
const newName = newRelativePath.split("/").pop() || file.name;
|
||||
const newName = newRelativePath.split(/[/\\]/).pop() || file.name;
|
||||
migratePdfBytesKey(fileId, newRelativePath);
|
||||
set((s) => {
|
||||
const compileErrorCache = migrateCacheKey(
|
||||
|
|
@ -833,7 +833,7 @@ export const useDocumentStore = create<DocumentState>()((set, get) => ({
|
|||
const state = get();
|
||||
if (!state.projectRoot) return;
|
||||
|
||||
const folderName = folderPath.split("/").pop()!;
|
||||
const folderName = folderPath.split(/[/\\]/).pop()!;
|
||||
const newFolderPath = targetFolder
|
||||
? `${targetFolder}/${folderName}`
|
||||
: folderName;
|
||||
|
|
@ -911,7 +911,7 @@ export const useDocumentStore = create<DocumentState>()((set, get) => ({
|
|||
// New file on disk
|
||||
const pf: ProjectFile = {
|
||||
id: fsFile.relativePath,
|
||||
name: fsFile.relativePath.split("/").pop() || fsFile.relativePath,
|
||||
name: fsFile.relativePath.split(/[/\\]/).pop() || fsFile.relativePath,
|
||||
relativePath: fsFile.relativePath,
|
||||
absolutePath: fsFile.absolutePath,
|
||||
type: fsFile.type,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ export const useProjectStore = create<ProjectState>()(
|
|||
setLastProjectFolder: (path) => set({ lastProjectFolder: path }),
|
||||
|
||||
addRecentProject: (path) => {
|
||||
const name = path.split("/").pop() || path;
|
||||
const name = path.split(/[/\\]/).pop() || path;
|
||||
set((state) => {
|
||||
const filtered = state.recentProjects.filter((p) => p.path !== path);
|
||||
return {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue