## Summary - move delete-worktree failures out of transient toast-only UX and keep them inline in the delete modal - add parsed diagnostics for common failure modes, including a short summary, likely cause, and suggested next step - make the raw error easier to review and share with raw and sanitized copy actions Closes #301. ## BEFORE: <img width="1127" height="860" alt="image" src="https://github.com/user-attachments/assets/dd09ba1e-be8c-450c-a1dd-f1cde2a48802" /> ## AFTER: <img width="1384" height="835" alt="image" src="https://github.com/user-attachments/assets/6b0d1459-21fa-4264-9e54-45540f584538" /> ## Problem Before this change, delete-worktree failures were difficult to work with: 1. The failure message was effectively raw backend or git output. 2. Users had to infer the meaning of the error themselves. 3. The UI did not explain what likely went wrong or what to do next. 4. Sharing the error for debugging was awkward when it included machine-local absolute paths. 5. The confirmation modal was not being used as the primary diagnostic surface for a destructive action that frequently fails for understandable reasons. This was especially frustrating for common cases such as: - modified or untracked files in the worktree - a process still using the worktree directory - permission errors on Windows - missing worktree directories or stale worktree records ## What changed ### Modal failure UX - keep delete failures inline inside `packages/ui/src/components/worktree-selector.tsx` - clear modal-local error state when opening or closing the dialog - keep the success toast on successful deletion, but use the modal itself for failure presentation ### Human-readable diagnostics - parse JSON-shaped backend error payloads such as `{"error":"..."}` before classification - classify common delete failure patterns into: - `localChanges` - `inUse` - `notFound` - `permissionDenied` - `unknown` - render three user-facing lines above the raw error: - summary - likely cause - suggested next step ### Copy flows - add `Copy error` for the original failure text - add `Copy sanitized` to redact common absolute path and username patterns before copying ### Modal content and sizing - present the target worktree in a simpler two-line summary block - update the delete description text to plain English: `Deletes this branch worktree and its local folder.` - size the delete modal deliberately for desktop use while allowing vertical expansion to the viewport limit before scrolling ### i18n coverage - add the new delete diagnostic strings across all currently supported locales touched by this area: - `en` - `es` - `fr` - `he` - `ja` - `ru` - `zh-Hans` ## Why this approach - It keeps the backend contract unchanged and solves the UX problem where it occurs. - It preserves access to the raw failure text instead of hiding implementation detail entirely. - It gives users immediate guidance without forcing them to translate git errors into next actions. - It improves bug reporting without requiring a separate logging or export workflow. ## Not included - server-side preflight guards that block delete when the worktree is still assigned or in use - process-aware worktree locking detection - automatic retry or force-delete-and-retry flows Those are useful follow-ups, but this PR is intentionally scoped to failure presentation and debuggability. ## Files changed - `packages/ui/src/components/worktree-selector.tsx` - `packages/ui/src/lib/i18n/messages/en/instance.ts` - `packages/ui/src/lib/i18n/messages/es/instance.ts` - `packages/ui/src/lib/i18n/messages/fr/instance.ts` - `packages/ui/src/lib/i18n/messages/he/instance.ts` - `packages/ui/src/lib/i18n/messages/ja/instance.ts` - `packages/ui/src/lib/i18n/messages/ru/instance.ts` - `packages/ui/src/lib/i18n/messages/zh-Hans/instance.ts` ## Validation - `npm run typecheck --workspace @codenomad/ui` - `npm run build --workspace @codenomad/ui` - `npm run typecheck --workspace @neuralnomads/codenomad-electron-app` ## Notes for reviewers - The error classifier is intentionally heuristic and string-based. It is meant to improve the common cases without increasing backend coupling. - The sanitized copy flow is conservative and focused on path and username redaction, not full structured log scrubbing. --------- Co-authored-by: Shantur Rathore <i@shantur.com>
215 lines
15 KiB
TypeScript
215 lines
15 KiB
TypeScript
export const instanceMessages = {
|
|
"instanceTabs.new.title": "Nouvelle instance (Cmd/Ctrl+N)",
|
|
"instanceTabs.new.ariaLabel": "Nouvelle instance",
|
|
"instanceTabs.remote.title": "Connexion à distance",
|
|
"instanceTabs.remote.ariaLabel": "Connexion à distance",
|
|
|
|
"instanceInfo.title": "Informations de l'instance",
|
|
"instanceInfo.labels.folder": "Dossier",
|
|
"instanceInfo.labels.project": "Projet",
|
|
"instanceInfo.labels.versionControl": "Contrôle de version",
|
|
"instanceInfo.labels.opencodeVersion": "Version d'OpenCode",
|
|
"instanceInfo.labels.binaryPath": "Chemin du binaire",
|
|
"instanceInfo.labels.environmentVariables": "Variables d'environnement ({count})",
|
|
"instanceInfo.loading": "Chargement...",
|
|
"instanceInfo.server.title": "Serveur",
|
|
"instanceInfo.server.port": "Port :",
|
|
"instanceInfo.server.pid": "PID :",
|
|
"instanceInfo.server.status": "Statut :",
|
|
|
|
"instanceTab.status.permission": "En attente d'autorisation",
|
|
"instanceTab.status.compacting": "Compactage",
|
|
"instanceTab.status.working": "En cours",
|
|
"instanceTab.status.idle": "Inactif",
|
|
"instanceTab.status.ariaLabel": "Statut de l'instance : {status}",
|
|
"instanceTab.actions.close.ariaLabel": "Fermer l'instance",
|
|
|
|
"instanceShell.leftPanel.sessionsTitle": "Sessions",
|
|
"instanceShell.leftPanel.instanceInfo": "Infos de l'instance",
|
|
"instanceShell.leftDrawer.pin": "Épingler le tiroir gauche",
|
|
"instanceShell.leftDrawer.unpin": "Désépingler le tiroir gauche",
|
|
"instanceShell.leftDrawer.toggle.pinned": "Tiroir gauche épinglé",
|
|
"instanceShell.leftDrawer.toggle.open": "Ouvrir le tiroir gauche",
|
|
"instanceShell.leftDrawer.toggle.close": "Fermer le tiroir gauche",
|
|
|
|
"instanceShell.rightDrawer.pin": "Épingler le tiroir droit",
|
|
"instanceShell.rightDrawer.unpin": "Désépingler le tiroir droit",
|
|
"instanceShell.rightDrawer.toggle.pinned": "Tiroir droit épinglé",
|
|
"instanceShell.rightDrawer.toggle.open": "Ouvrir le tiroir droit",
|
|
"instanceShell.rightDrawer.toggle.close": "Fermer le tiroir droit",
|
|
|
|
"instanceShell.fullscreen.enter": "Plein écran",
|
|
"instanceShell.fullscreen.exit": "Quitter le plein écran",
|
|
|
|
"instanceShell.metrics.usedLabel": "Utilisé",
|
|
"instanceShell.metrics.availableLabel": "Dispo",
|
|
|
|
"instanceShell.commandPalette.openAriaLabel": "Ouvrir la palette de commandes",
|
|
"instanceShell.commandPalette.button": "Palette de commandes",
|
|
|
|
"instanceShell.connection.ariaLabel": "Connexion {status}",
|
|
"instanceShell.connection.connected": "Connecté",
|
|
"instanceShell.connection.connecting": "Connexion...",
|
|
"instanceShell.connection.disconnected": "Déconnecté",
|
|
"instanceShell.connection.unknown": "Inconnu",
|
|
|
|
"instanceWelcome.shortcuts.newSession": "Nouvelle session",
|
|
"instanceWelcome.empty.title": "Aucune session précédente",
|
|
"instanceWelcome.empty.description": "Créez une nouvelle session ci-dessous pour commencer",
|
|
"instanceWelcome.loading.title": "Chargement des sessions",
|
|
"instanceWelcome.loading.description": "Récupération de vos sessions précédentes...",
|
|
"instanceWelcome.resume.title": "Reprendre une session",
|
|
"instanceWelcome.resume.subtitle.one": "{count} session disponible",
|
|
"instanceWelcome.resume.subtitle.other": "{count} sessions disponibles",
|
|
"instanceWelcome.session.untitled": "Session sans titre",
|
|
"instanceWelcome.new.title": "Démarrer une nouvelle session",
|
|
"instanceWelcome.new.subtitle": "Nous réutiliserons automatiquement votre dernier agent/modèle",
|
|
"instanceWelcome.new.createButton": "Créer la session",
|
|
"instanceWelcome.overlay.close": "Fermer",
|
|
"instanceWelcome.actions.viewInstanceInfo": "Voir les infos de l'instance",
|
|
"instanceWelcome.actions.renameTitle": "Renommer la session",
|
|
"instanceWelcome.actions.deleteTitle": "Supprimer la session",
|
|
"instanceWelcome.hints.navigate": "Naviguer",
|
|
"instanceWelcome.hints.jump": "Sauter",
|
|
"instanceWelcome.hints.firstLast": "Prem./Dern.",
|
|
"instanceWelcome.hints.resume": "Reprendre",
|
|
"instanceWelcome.hints.delete": "Supprimer",
|
|
"instanceWelcome.toasts.renameError": "Impossible de renommer la session",
|
|
|
|
"instanceDisconnected.title": "Instance déconnectée",
|
|
"instanceDisconnected.folderFallback": "cet espace de travail",
|
|
"instanceDisconnected.reasonFallback": "Le serveur ne répond plus",
|
|
"instanceDisconnected.description": "{folder} n'est plus accessible. Fermez l'onglet pour continuer à travailler.",
|
|
"instanceDisconnected.details.title": "Détails",
|
|
"instanceDisconnected.details.folderLabel": "Dossier :",
|
|
"instanceDisconnected.actions.closeInstance": "Fermer l'instance",
|
|
|
|
"instanceShell.empty.title": "Aucune session sélectionnée",
|
|
"instanceShell.empty.description": "Sélectionnez une session pour voir les messages",
|
|
|
|
"instanceShell.rightPanel.title": "Panneau d'état",
|
|
"instanceShell.rightPanel.tabs.changes": "Modifications",
|
|
"instanceShell.rightPanel.tabs.gitChanges": "Changements Git",
|
|
"instanceShell.rightPanel.tabs.files": "Fichiers",
|
|
"instanceShell.rightPanel.tabs.status": "Statut",
|
|
"instanceShell.rightPanel.tabs.ariaLabel": "Onglets du panneau droit",
|
|
"instanceShell.rightPanel.actions.refresh": "Actualiser",
|
|
"instanceShell.rightPanel.actions.save": "Enregistrer (Ctrl+S)",
|
|
"instanceShell.rightPanel.actions.saveConfirm.message": "Voulez-vous enregistrer les modifications de \"{path}\" avant de changer ?",
|
|
"instanceShell.rightPanel.actions.saveConfirm.confirmLabel": "Enregistrer",
|
|
"instanceShell.rightPanel.actions.saveConfirm.cancelLabel": "Annuler les modifications",
|
|
"instanceShell.rightPanel.actions.conflict.message": "Le fichier a été modifié par l'agent. Écraser les modifications de l'agent ?",
|
|
"instanceShell.rightPanel.actions.conflict.confirmLabel": "Écraser",
|
|
"instanceShell.rightPanel.actions.conflict.cancelLabel": "Annuler",
|
|
"instanceShell.rightPanel.actions.refreshDirty.message": "Le fichier a des modifications non enregistrées. Actualiser supprimera vos modifications. Continuer ?",
|
|
"instanceShell.rightPanel.actions.refreshDirty.confirmLabel": "Actualiser",
|
|
"instanceShell.rightPanel.actions.refreshDirty.cancelLabel": "Annuler",
|
|
"instanceShell.rightPanel.toast.saveSuccess": "Fichier enregistré avec succès",
|
|
"instanceShell.rightPanel.toast.saveError": "Échec de l'enregistrement du fichier",
|
|
"instanceShell.rightPanel.sections.yoloMode": "Mode yolo",
|
|
"instanceShell.rightPanel.sections.yoloMode.tooltip": "Approuve automatiquement les demandes d'autorisation pour la session actuelle. A utiliser seulement si vous faites confiance aux outils executes.",
|
|
"instanceShell.rightPanel.sections.sessionChanges": "Changements de session",
|
|
"instanceShell.rightPanel.sections.sessionChanges.tooltip": "Fichiers modifiés dans la session actuelle. Affiche les ajouts et suppressions pour chaque fichier.",
|
|
"instanceShell.rightPanel.sections.plan": "Plan",
|
|
"instanceShell.rightPanel.sections.plan.tooltip": "Feuille de route de l'agent pour cette session. Suit les tâches et leur statut d'achèvement.",
|
|
"instanceShell.rightPanel.sections.backgroundProcesses": "Shells en arrière-plan",
|
|
"instanceShell.rightPanel.sections.backgroundProcesses.tooltip": "Processus longs démarrés par l'agent. Vous pouvez surveiller leur sortie, les arrêter ou les terminer.",
|
|
"instanceShell.rightPanel.sections.mcp": "Serveurs MCP",
|
|
"instanceShell.rightPanel.sections.mcp.tooltip": "Serveurs du protocole Model Context Protocol qui étendent les capacités de l'agent avec des outils externes.",
|
|
"instanceShell.rightPanel.sections.lsp": "Serveurs LSP",
|
|
"instanceShell.rightPanel.sections.lsp.tooltip": "Serveurs du protocole Language Server Protocol fournissant l'intelligence de code et les diagnostics.",
|
|
"instanceShell.rightPanel.sections.plugins": "Plugins",
|
|
"instanceShell.rightPanel.sections.plugins.tooltip": "Plugins qui personnalisent le comportement de l'UI et du serveur, ajoutant des fonctionnalités au-delà de MCP et LSP.",
|
|
|
|
"instanceShell.sessionChanges.noSessionSelected": "Sélectionnez une session pour voir les changements.",
|
|
"instanceShell.sessionChanges.loading": "Récupération des changements...",
|
|
"instanceShell.sessionChanges.empty": "Aucun changement pour l'instant.",
|
|
"instanceShell.sessionChanges.filesChanged": "{count} fichiers modifiés",
|
|
"instanceShell.sessionChanges.actions.show": "Afficher les changements",
|
|
|
|
"instanceShell.gitChanges.loading": "Chargement des changements Git...",
|
|
"instanceShell.gitChanges.empty": "Aucun changement Git pour l'instant.",
|
|
"instanceShell.gitChanges.deleted": "Supprimé",
|
|
"instanceShell.gitChanges.binaryViewer": "Impossible d'afficher un fichier binaire",
|
|
"instanceShell.gitChanges.sections.staged": "Changements indexés",
|
|
"instanceShell.gitChanges.sections.unstaged": "Changements",
|
|
"instanceShell.gitChanges.actions.insertContext": "Ajouter au prompt",
|
|
"instanceShell.gitChanges.actions.stage": "Indexer le fichier",
|
|
"instanceShell.gitChanges.actions.unstage": "Retirer de l'index",
|
|
"instanceShell.gitChanges.commit.placeholder": "Saisissez le message du commit",
|
|
"instanceShell.gitChanges.commit.submit": "Valider",
|
|
"instanceShell.gitChanges.commit.submitting": "Validation...",
|
|
"instanceShell.gitChanges.commit.success": "Commit créé avec succès",
|
|
"instanceShell.gitChanges.commit.error": "Impossible de créer le commit",
|
|
|
|
"instanceShell.filesShell.fileListTitle": "Liste des fichiers",
|
|
"instanceShell.filesShell.mobileSelectorLabel": "Sélectionner un fichier",
|
|
"instanceShell.filesShell.mobileSelectorEmpty": "Sélectionnez un fichier",
|
|
"instanceShell.filesShell.viewerTitle": "Visionneuse de changements",
|
|
"instanceShell.filesShell.viewerPlaceholder": "Le rendu détaillé sera ajouté à l'étape suivante.",
|
|
"instanceShell.filesShell.viewerEmpty": "Aucun fichier sélectionné.",
|
|
|
|
"instanceShell.plan.noSessionSelected": "Sélectionnez une session pour voir le plan.",
|
|
"instanceShell.plan.empty": "Aucun plan pour l'instant.",
|
|
|
|
"instanceShell.yoloMode.noSessionSelected": "Selectionnez une session pour configurer le mode yolo.",
|
|
"instanceShell.yoloMode.title": "Mode yolo",
|
|
"instanceShell.yoloMode.description": "Approuve automatiquement les demandes d'autorisation pour cette session. Desactive par defaut.",
|
|
"instanceShell.yoloMode.badge": "Mode yolo",
|
|
"instanceShell.yoloMode.badgeAriaLabel": "Mode yolo active",
|
|
|
|
"instanceShell.backgroundProcesses.empty": "Aucun processus en arrière-plan.",
|
|
"instanceShell.backgroundProcesses.status": "Statut : {status}",
|
|
"instanceShell.backgroundProcesses.output": "Sortie : {sizeKb}KB",
|
|
"instanceShell.backgroundProcesses.notify.enabled": "Notification de fin activee",
|
|
"instanceShell.backgroundProcesses.notify.disabled": "Notification de fin desactivee",
|
|
"instanceShell.backgroundProcesses.actions.output": "Sortie",
|
|
"instanceShell.backgroundProcesses.actions.stop": "Arrêter",
|
|
"instanceShell.backgroundProcesses.actions.terminate": "Terminer",
|
|
"instanceShell.worktree.delete.error.title": "Echec de suppression",
|
|
"instanceShell.worktree.delete.error.fallback": "Impossible de supprimer le worktree",
|
|
"instanceShell.worktree.delete.error.causeLabel": "Cause probable :",
|
|
"instanceShell.worktree.delete.error.nextStepLabel": "Etape suivante suggeree :",
|
|
"instanceShell.worktree.delete.error.summary.localChanges": "Git a refuse de supprimer ce worktree car il contient des fichiers modifies ou non suivis.",
|
|
"instanceShell.worktree.delete.error.summary.inUse": "CodeNomad n'a pas pu supprimer ce worktree car quelque chose utilise encore des fichiers dans ce dossier.",
|
|
"instanceShell.worktree.delete.error.summary.notFound": "CodeNomad n'a pas pu supprimer ce worktree car le dossier ou l'enregistrement du worktree est introuvable.",
|
|
"instanceShell.worktree.delete.error.summary.permissionDenied": "CodeNomad n'a pas pu supprimer ce worktree car l'acces au dossier a ete refuse.",
|
|
"instanceShell.worktree.delete.error.summary.unknown": "CodeNomad n'a pas pu supprimer ce worktree.",
|
|
"instanceShell.worktree.delete.error.cause.localChanges": "Modifications locales",
|
|
"instanceShell.worktree.delete.error.cause.inUse": "Un autre processus utilise ce worktree",
|
|
"instanceShell.worktree.delete.error.cause.notFound": "Le dossier ou l'enregistrement du worktree est manquant",
|
|
"instanceShell.worktree.delete.error.cause.permissionDenied": "Permissions du systeme de fichiers insuffisantes",
|
|
"instanceShell.worktree.delete.error.cause.unknown": "Le backend a renvoye une erreur de suppression non classee",
|
|
"instanceShell.worktree.delete.error.nextStep.localChanges": "Activez la suppression forcee si vous voulez jeter les modifications locales, ou nettoyez le worktree puis reessayez.",
|
|
"instanceShell.worktree.delete.error.nextStep.inUse": "Fermez les terminaux, editeurs, observateurs ou processus en arrière-plan qui utilisent ce worktree puis reessayez.",
|
|
"instanceShell.worktree.delete.error.nextStep.notFound": "Rechargez les worktrees puis reessayez. Si cela echoue encore, inspectez le chemin du worktree sur le disque.",
|
|
"instanceShell.worktree.delete.error.nextStep.permissionDenied": "Verifiez les permissions du systeme de fichiers et fermez les applications qui peuvent verrouiller ce dossier, puis reessayez.",
|
|
"instanceShell.worktree.delete.error.nextStep.unknown": "Consultez l'erreur brute ci-dessous pour les details, puis reessayez apres avoir corrige le probleme signale.",
|
|
"instanceShell.worktree.delete.error.copyRaw": "Copier l'erreur",
|
|
"instanceShell.worktree.delete.error.copySanitized": "Copier la version nettoyee",
|
|
"instanceShell.worktree.delete.error.copySuccess": "Erreur de suppression copiee",
|
|
"instanceShell.worktree.delete.error.copySanitizedSuccess": "Erreur de suppression nettoyee copiee",
|
|
"instanceShell.worktree.delete.error.copyFailure": "Impossible de copier l'erreur de suppression",
|
|
|
|
"versionPill.appWithVersion": "Appli {version}",
|
|
"versionPill.ui": "UI",
|
|
"versionPill.uiWithVersion": "UI {version}",
|
|
"versionPill.source": " ({source})",
|
|
|
|
"opencodeBinarySelector.title": "Binaire OpenCode",
|
|
"opencodeBinarySelector.subtitle": "Choisissez quel exécutable OpenCode doit lancer",
|
|
"opencodeBinarySelector.customPath.placeholder": "Saisissez le chemin vers le binaire opencode…",
|
|
"opencodeBinarySelector.actions.add": "Ajouter",
|
|
"opencodeBinarySelector.actions.browse": "Parcourir le binaire…",
|
|
"opencodeBinarySelector.actions.removeTitle": "Supprimer le binaire",
|
|
"opencodeBinarySelector.badge.systemPath": "Utiliser le binaire depuis le PATH système",
|
|
"opencodeBinarySelector.status.checkingVersions": "Vérification des versions…",
|
|
"opencodeBinarySelector.status.checking": "Vérification…",
|
|
"opencodeBinarySelector.dialog.title": "Sélectionner le binaire OpenCode",
|
|
"opencodeBinarySelector.dialog.description": "Parcourez les fichiers exposés par le serveur CLI.",
|
|
"opencodeBinarySelector.validation.invalidBinary": "Binaire OpenCode invalide",
|
|
"opencodeBinarySelector.validation.alreadyValidating": "Validation déjà en cours",
|
|
"opencodeBinarySelector.display.systemPath": "{name} (PATH système)",
|
|
"opencodeBinarySelector.versionLabel": "v{version}",
|
|
} as const
|