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,