From a23b4c0e0f593bc1e016484e6f9c77812321df63 Mon Sep 17 00:00:00 2001 From: srfwb <264158739+srfwb@users.noreply.github.com> Date: Sat, 25 Apr 2026 16:51:51 +0200 Subject: [PATCH 1/7] fix(bootstrap): catch missing project folder on startup instead of crashing --- src/projects/bootstrap.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) 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; } From f756f6f70777813fb034bda045dd22137041e8f3 Mon Sep 17 00:00:00 2001 From: srfwb <264158739+srfwb@users.noreply.github.com> Date: Sat, 25 Apr 2026 16:52:49 +0200 Subject: [PATCH 2/7] fix(autosave): toast on disk write failure instead of silent loss --- src/projects/diskAutoSave.ts | 2 ++ 1 file changed, 2 insertions(+) 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(); From b9e38a5d5f3afddd3c2902891e3ea2dc63826487 Mon Sep 17 00:00:00 2001 From: srfwb <264158739+srfwb@users.noreply.github.com> Date: Sat, 25 Apr 2026 16:53:10 +0200 Subject: [PATCH 3/7] fix(projects): user-friendly error when project folder already exists --- src/projects/actions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/projects/actions.ts b/src/projects/actions.ts index cce043b..e032fd2 100644 --- a/src/projects/actions.ts +++ b/src/projects/actions.ts @@ -64,7 +64,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 }); From 51891d10ab5c7cbc8dbce1f6bc168f907591d808 Mon Sep 17 00:00:00 2001 From: srfwb <264158739+srfwb@users.noreply.github.com> Date: Sat, 25 Apr 2026 16:53:58 +0200 Subject: [PATCH 4/7] fix(projects): reject rename to a name already used by another project --- src/projects/actions.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/projects/actions.ts b/src/projects/actions.ts index e032fd2..9f64a1b 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 }); } From ee8d0e28bdab19fc7c1a801a9627be084cabfe35 Mon Sep 17 00:00:00 2001 From: srfwb <264158739+srfwb@users.noreply.github.com> Date: Sat, 25 Apr 2026 16:54:32 +0200 Subject: [PATCH 5/7] fix(ide): clear open tabs when switching projects --- src/projects/actions.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/projects/actions.ts b/src/projects/actions.ts index 9f64a1b..22865ed 100644 --- a/src/projects/actions.ts +++ b/src/projects/actions.ts @@ -195,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"); } From 6a3f5e8613a9ae126d4f4d602fa42ad9a509da23 Mon Sep 17 00:00:00 2001 From: srfwb <264158739+srfwb@users.noreply.github.com> Date: Sat, 25 Apr 2026 16:55:08 +0200 Subject: [PATCH 6/7] style(home): snappier project card stagger (25ms step, 120ms cap) --- src/styles/global.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/styles/global.css b/src/styles/global.css index 7f993db..60207ad 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -1779,8 +1779,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, From 62eaf99fc5deb46467d982b571710e8fa3b3a5e4 Mon Sep 17 00:00:00 2001 From: srfwb <264158739+srfwb@users.noreply.github.com> Date: Sat, 25 Apr 2026 16:57:09 +0200 Subject: [PATCH 7/7] fix(a11y): tab focus-visible, nav items as buttons, rail link cursor --- src/home/rail/HomeNavItem.tsx | 12 +++++++++--- src/styles/global.css | 27 ++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 4 deletions(-) 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/styles/global.css b/src/styles/global.css index 60207ad..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 {