Edit workspace files directly on web (#2270)

* feat(files): edit workspace files on web

Keep source buffers synchronized with host file changes and require an explicit overwrite or reload when revisions diverge.

* feat(panels): surface and protect modified tabs

Expose tooltip and modification state through the generic panel boundary so tabs can show stable metadata and guard every close route consistently.

* fix(tests): use portable fake timeout handle

* fix(files): harden editor conflict handling

Preserve modified panel state across tab eviction, use precise revisions for optimistic writes, coalesce concurrent file watchers, and localize the editor interface.

* fix(files): close editor concurrency gaps

Coalesce clean reloads, preserve subscriber identities and file permissions, suspend pending saves during close confirmation, and carry precise revisions through file reads.

* test(files): expect read revision metadata
This commit is contained in:
Mohamed Boudra
2026-07-21 04:41:19 +08:00
committed by GitHub
parent 9292f58896
commit 4bda2dfea9
69 changed files with 3678 additions and 241 deletions
+33
View File
@@ -470,6 +470,7 @@ export const fr: TranslationResources = {
},
tabs: {
loading: "Chargement...",
modified: "Modifications non enregistrées",
loadingAgentTitle: "Titre d'agent de chargement",
emptyPane: "Aucun onglet dans ce volet.",
fallback: {
@@ -537,6 +538,13 @@ export const fr: TranslationResources = {
failedToReloadAgent: "Échec du rechargement de l'agent",
},
confirmations: {
unsavedTitle: "Modifications non enregistrées",
unsavedMessage:
"Cet onglet contient des modifications non enregistrées. Le fermer supprimera le brouillon.",
closeWithoutSaving: "Fermer sans enregistrer",
closePaneTitle: "Fermer le volet?",
bulkUnsaved:
"{{count}} onglet(s) contiennent des modifications non enregistrées. Les fermer supprimera ces brouillons.",
close: "Fermer",
cancel: "Annuler",
archive: "Archive",
@@ -1491,6 +1499,25 @@ export const fr: TranslationResources = {
binaryPreviewUnavailable: "Aperçu binaire indisponible",
failedToLoad: "Échec du chargement du fichier",
failedToLoadPreview: "Échec du chargement de l'aperçu du fichier",
editor: {
fileSize: "Taille {{size}}",
lines: "{{count}} lignes",
editorStatus: "État de l’éditeur : {{status}}",
unsavedChanges: "Modifications non enregistrées",
saving: "Enregistrement...",
saveFailed: "Échec de l’enregistrement",
changedOnDisk: "Modifié sur le disque",
vimMode: "Mode Vim {{mode}}",
cursor: "Ligne {{line}}, colonne {{column}}",
preview: "Aperçu",
source: "Source",
unavailableTitle: "Fichier indisponible sur le disque",
conflictDescription: "Le contenu local a été conservé. Choisissez la version à garder.",
overwrite: "Écraser",
reload: "Recharger",
reloadTitle: "Recharger depuis le disque ?",
reloadMessage: "Vos modifications locales seront perdues.",
},
},
diff: {
changesLabel: "Modifications",
@@ -1586,12 +1613,18 @@ export const fr: TranslationResources = {
sections: {
general: "Général",
appearance: "Apparence",
editor: "Éditeur",
shortcuts: "Raccourcis",
integrations: "Intégrations",
permissions: "Autorisations",
diagnostics: "Diagnostic",
about: "À propos",
},
editor: {
title: "Éditeur",
vimKeybindings: "Raccourcis Vim",
vimHint: "S’applique aux fichiers source sur le web et le bureau.",
},
hostSections: {
connections: "Relations",
agents: "Agents",