diff --git a/src/home/rail/HomeNavItem.tsx b/src/home/rail/HomeNavItem.tsx
index 55d6b37..df692d2 100644
--- a/src/home/rail/HomeNavItem.tsx
+++ b/src/home/rail/HomeNavItem.tsx
@@ -4,13 +4,19 @@ interface Props {
icon: ReactElement;
label: string;
active?: boolean;
+ onClick?: () => void;
}
-export function HomeNavItem({ icon, label, active = false }: Props) {
+export function HomeNavItem({ icon, label, active = false, onClick }: Props) {
return (
-
+
+
);
}
diff --git a/src/projects/actions.ts b/src/projects/actions.ts
index cce043b..22865ed 100644
--- a/src/projects/actions.ts
+++ b/src/projects/actions.ts
@@ -38,6 +38,10 @@ export function renameProject(id: string, newName: string): void {
const project = useProjectStore.getState().projects.find((p) => p.id === id);
if (!project) throw new Error(`unknown project: ${id}`);
if (project.name === name) return;
+ const duplicate = useProjectStore
+ .getState()
+ .projects.some((p) => p.id !== id && p.name.toLowerCase() === name.toLowerCase());
+ if (duplicate) throw new Error(`Un projet avec le nom « ${name} » existe déjà.`);
useProjectStore.getState().upsert({ ...project, name });
}
@@ -64,7 +68,7 @@ async function createProjectImpl(input: {
const projectPath = joinChild(parentDir, name);
const exists = await invoke("fs_path_exists", { path: projectPath });
if (exists) {
- throw new Error(`Un dossier existe déjà à ${projectPath}.`);
+ throw new Error(`Un projet avec ce nom existe déjà dans ce dossier.`);
}
await invoke("fs_ensure_dir", { dirPath: projectPath });
@@ -191,5 +195,10 @@ async function openProjectImpl(id: string): Promise {
}
useProjectStore.getState().setActive(id, { touch: true });
+ // Reset open tabs so stale paths from the previous project don't linger.
+ // The IDE hydration logic in `bootstrapIdeStore` handles restoring tabs
+ // from the persisted snapshot, but on a live switch the old tabs would
+ // survive until the next persistence round-trip.
+ useIdeStore.setState({ openFiles: [], activeFile: null });
useIdeStore.getState().setView("ide");
}
diff --git a/src/projects/bootstrap.ts b/src/projects/bootstrap.ts
index 9adb0ed..75d69d0 100644
--- a/src/projects/bootstrap.ts
+++ b/src/projects/bootstrap.ts
@@ -38,7 +38,13 @@ export async function bootstrapProjects(): Promise {
useProjectStore.getState().hydrate(existing);
const activeId = existing.activeProjectId ?? existing.projects[0]?.id ?? null;
if (activeId && existing.projects.some((p) => p.id === activeId)) {
- await openProject(activeId);
+ try {
+ await openProject(activeId);
+ } catch (err) {
+ // The project's folder may have been deleted externally while the
+ // app was closed. Fall through to the Home view instead of crashing.
+ console.warn("bootstrap: could not reopen last project, showing Home", err);
+ }
}
return;
}
diff --git a/src/projects/diskAutoSave.ts b/src/projects/diskAutoSave.ts
index e0ae81c..075cc1a 100644
--- a/src/projects/diskAutoSave.ts
+++ b/src/projects/diskAutoSave.ts
@@ -1,5 +1,6 @@
import { invoke } from "@tauri-apps/api/core";
+import { toast } from "../ide/shell/toastStore";
import { persistenceEvents } from "../vfs/persistence";
import type { VirtualFS } from "../vfs/VirtualFS";
import { useProjectStore } from "./projectStore";
@@ -66,6 +67,7 @@ export function attachDiskAutoSave(vfs: VirtualFS): AutoSaveHandle {
}
} catch (err) {
console.error("disk autosave job failed", relPath, err);
+ toast.error(`Impossible de sauvegarder ${relPath} sur le disque.`);
}
}
const paths = vfs.listFiles();
diff --git a/src/styles/global.css b/src/styles/global.css
index 7f993db..0e2da98 100644
--- a/src/styles/global.css
+++ b/src/styles/global.css
@@ -595,6 +595,12 @@ button.brand {
color: var(--fg-0);
background: var(--bg-1);
}
+.tab:focus-visible {
+ outline: none;
+ color: var(--fg-0);
+ background: var(--bg-1);
+ box-shadow: inset 0 -2px 0 var(--accent);
+}
.tab--active {
color: var(--fg-0);
background: var(--bg-1);
@@ -1443,6 +1449,25 @@ button.brand {
color: var(--fg-1);
font-size: 13px;
position: relative;
+ width: 100%;
+ text-align: left;
+ cursor: default;
+ transition:
+ background 0.12s,
+ color 0.12s;
+}
+.home-nav-item:not([disabled]):hover {
+ background: var(--bg-2);
+ color: var(--fg-0);
+}
+.home-nav-item:focus-visible {
+ outline: none;
+ background: var(--bg-2);
+ color: var(--fg-0);
+}
+.home-nav-item[disabled] {
+ opacity: 0.5;
+ cursor: default;
}
.home-nav-item--active {
background: var(--bg-3);
@@ -1477,7 +1502,7 @@ button.brand {
align-items: center;
gap: 8px;
padding: 6px 0;
- cursor: default;
+ cursor: pointer;
color: inherit;
}
.home-rail-link:hover {
@@ -1779,8 +1804,8 @@ button.brand {
text-align: left;
cursor: pointer;
position: relative;
- animation: proj-in 220ms ease-out both;
- animation-delay: min(calc(var(--proj-idx, 0) * 40ms), 240ms);
+ animation: proj-in 180ms ease-out both;
+ animation-delay: min(calc(var(--proj-idx, 0) * 25ms), 120ms);
transition:
background 0.12s,
border-color 0.12s,