diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index 5748caa..1fcb995 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -336,134 +336,78 @@
---
-### 75. Ăditeur multi-panneaux (Split View)
-- **Effort :** 5-7 jours | **Impact :** đĄ
-- **Description :** SystÚme de panneaux divisés permettant d'afficher plusieurs documents cÎte à cÎte ou superposés, à la maniÚre d'un éditeur de code (VS Code, Sublime Text). Drag & drop des onglets entre les panneaux, actions depuis le menu contextuel, et raccourcis clavier pour diviser/naviguer entre les panneaux. Le systÚme remplace le `content-area` unique par un gestionnaire de panneaux (PaneManager) qui orchestre 1 à 4 zones d'affichage indépendantes, chacune avec sa propre barre d'onglets.
-- **Architecture actuelle :**
- - **DOM** (`frontend/index.html:885-896`) : Un seul bloc `.content-wrapper` contenant `.tab-bar` + `main.content-area#content-area`. Le `content-area` est un conteneur monolithique dont le contenu est remplacé par `renderFile()` à chaque changement d'onglet.
- - **TabManager** (`frontend/app.js:7988`) : GĂšre dĂ©jĂ les onglets (ouvrir, fermer, preview, drag-reorder, menu contextuel). Ătat stockĂ© dans `_tabs[]`, `_tabCache{}`, `_activeTabId`. Mais il est couplĂ© au `content-area` unique â il remplace `area.innerHTML` Ă chaque activation.
- - **Renderer** : `renderFile()` (`frontend/js/viewer.js:377`) et `renderFileContent()` (`frontend/app.js:3182`) écrivent directement dans `content-area` via `document.getElementById("content-area")`.
- - **Déjà présent** : Les onglets sont draggables dans la barre (réorganisation interne). Le drag & drop de fichiers depuis l'arborescence n'existe pas encore (#33).
-- **Sous-tĂąches :**
+### 75. Ăditeur multi-panneaux (Split View) â
80% complété
+- **Effort :** 5-7 jours (4.5 jours rĂ©alisĂ©s) | **Impact :** đĄ
+- **Statut :** Fonctionnel â split right/down, drag & drop cross-pane, drag-to-split, navigation clavier, palette
+- **Fichiers clés :** `frontend/js/pane-manager.js` (1082 loc), `frontend/js/viewer.js` (modifié), `frontend/js/ui.js` (modifié), `frontend/js/dashboard.js` (modifié), `frontend/js/palette.js` (modifié), `frontend/style.css` (modifié), `tests/test_pane_manager.py` (22 tests)
+- **Description :** SystÚme de panneaux divisés permettant d'afficher plusieurs documents cÎte à cÎte ou superposés, à la maniÚre d'un éditeur de code (VS Code). Drag & drop des onglets entre les panneaux, drag-to-split, menu contextuel, raccourcis clavier, palette de commandes.
+- **Architecture :**
+ - **PaneManager** â orchestre la grille de panneaux (1-4), crĂ©e/dĂ©truit les instances PaneTabManager
+ - **PaneTabManager (factory `createPaneTabManager(paneId)`)** â chaque panneau a son instance indĂ©pendante avec ses propres onglets, cache, Ă©tat
+ - **Singleton TabManager** â dĂ©lĂšgue automatiquement vers le PaneTabManager actif quand en mode split ; gĂšre les onglets en single-pane
+ - **Migration automatique** â les onglets migrent singletonâPaneTabManager au premier split, et inversement au collapse
+ - **getContentArea()** â helper global dans viewer.js, priorise `window._activePaneContentArea` (set par PaneTabManager)
+- **Implémenté :**
-##### A. PaneManager â Gestionnaire de panneaux (2-3 jours)
- - [ ] **A1. Classe `PaneManager`** (`frontend/js/pane-manager.js`) : Nouveau module responsable de la grille de panneaux.
- - **Ătat** : `_panes: []` â tableau ordonnĂ© de `{id, element, tabBar, tabList, contentArea, tabs: [], activeTabId, tabCache: {}, dirtyTabs: Set}`
- - **Pane actif** : `_activePaneId` â le dernier panneau ayant reçu le focus
- - **Disposition** : `_layout: 'horizontal' | 'vertical' | 'grid'` â dĂ©terminĂ© automatiquement selon le nombre et l'ordre de crĂ©ation
- - **Maximum** : 4 panneaux (grille 2Ă2) pour Ă©viter la surcharge cognitive et les problĂšmes de performance
- - [ ] **A2. Création dynamique du DOM** : Quand un 2e panneau est créé, le `.content-wrapper` est transformé :
- - Le `.tab-bar` et `#content-area` d'origine deviennent le **pane #1**
- - Un conteneur `.pane-grid` (flex/grid) englobe tous les panneaux
- - Chaque panneau a sa propre `.pane-container` > `.tab-bar.pane-tab-bar` + `.content-area.pane-content`
- - Une **poignée de redimensionnement** (`.pane-resize-handle`) est insérée entre chaque paire de panneaux
- - [ ] **A3. Redimensionnement (resize)** : Implémentation native sans bibliothÚque.
- - Mouse down sur `.pane-resize-handle` â capture du pointeur â calcul des ratios en pixels â mise Ă jour `flex-basis` en %
- - Double-clic sur la poignĂ©e â rĂ©initialise Ă 50/50
- - Persistance des ratios dans `localStorage` par clé `pane-layout-{N}panes`
- - Contrainte : largeur/hauteur minimum de 200px par panneau
- - [ ] **A4. Indépendance des panneaux** : Chaque panneau a son propre :
- - Jeu d'onglets (un fichier peut ĂȘtre ouvert dans plusieurs panneaux simultanĂ©ment)
- - Ătat de scroll, toggle source view, position du curseur
- - Cache de donnĂ©es (`tabCache`) â les donnĂ©es fetchĂ©es sont partagĂ©es via un cache global pour Ă©viter les requĂȘtes dupliquĂ©es
- - Barre d'outils viewer (copier, source, éditer, pop-out) rattachée au panneau actif
+##### â
A. PaneManager â Gestionnaire de panneaux
+ - [x] **A1.** Module `PaneManager` + factory `createPaneTabManager` â Ă©tat indĂ©pendant par panneau
+ - [x] **A2.** CrĂ©ation dynamique du DOM â `.pane-grid` avec `.pane-container`, IDs suffixĂ©s `-N`
+ - [x] **A3.** Redimensionnement â drag handles, double-clic reset 50/50, contrainte 20-80%, localStorage
+ - [x] **A4.** IndĂ©pendance des panneaux â chaque panneau a ses propres onglets, scroll, cache, source view
-##### B. Refactoring du TabManager (1 jour)
- - [ ] **B1. Découplage du DOM global** : Actuellement `TabManager` référence `document.getElementById("content-area")` et `document.getElementById("tab-bar")` en dur. Refactorer pour que chaque instance de Pane TabManager reçoive ses propres éléments DOM en paramÚtre.
- - [ ] **B2. Extraction de l'état** : Le `TabManager` actuel est un objet singleton avec état mutable partagé. Le scoper dans une factory `createTabManager(tabBar, tabList, contentArea)` qui retourne une instance indépendante.
- - [ ] **B3. Compatibilité ascendante** : Le pane #1 (unique par défaut) réutilise les IDs DOM existants (`#tab-bar`, `#tab-list`, `#content-area`) pour ne rien casser. Les panneaux additionnels créent des IDs suffixés (`#tab-bar-2`, `#content-area-2`, etc.).
- - [ ] **B4. Synchronisation avec le state global** : `currentVault`, `currentPath`, `syncActiveFileTreeItem()` sont mis à jour depuis le panneau actif. Quand on switch de panneau (clic dans un panneau), le state global reflÚte le document affiché dans ce panneau.
+##### â
B. Refactoring du TabManager
+ - [x] **B1.** DĂ©couplage du DOM â `init(tabBar, tabList, contentArea)` par paramĂštre
+ - [x] **B2.** Factory `createPaneTabManager(paneId)` â instances indĂ©pendantes
+ - [x] **B3.** CompatibilitĂ© ascendante â pane 0 rĂ©utilise les IDs existants, panes 1+ suffixĂ©s `-N`
+ - [x] **B4.** Synchronisation state global â `setActivePane()` met Ă jour `currentVault`/`currentPath`
-##### C. Actions de division (1 jour)
- - [ ] **C1. Menu contextuel des onglets** : Ajouter 3 actions au menu existant (`_showTabContextMenu`) :
- - « **Diviser Ă droite** » (`splitRight`) â crĂ©e un nouveau panneau Ă droite avec cet onglet, le ferme dans le panneau source
- - « **Diviser en bas** » (`splitDown`) â crĂ©e un nouveau panneau en dessous
- - « **DĂ©placer vers...** » (sous-menu) â liste les autres panneaux existants pour y dĂ©placer l'onglet
- - [ ] **C2. Raccourcis clavier** (non conflictuels avec le navigateur) :
- - `Ctrl+Alt+\` â diviser le panneau actif Ă droite
- - `Ctrl+Alt+Shift+\` â diviser le panneau actif en bas
- - `Ctrl+Alt+â/â/â/â` â naviguer entre les panneaux (focus)
- - `Ctrl+Alt+W` â fermer le panneau actif (tous ses onglets sont fermĂ©s)
- - `Ctrl+Shift+Alt+W` â fermer tous les autres panneaux
- - [ ] **C3. Boutons dans la barre d'onglets** : Ă droite de chaque `.tab-bar`, ajouter deux mini-boutons (visibles au survol) : « Split Right » (ââ) et « Split Down » (ââ).
- - [ ] **C4. Fermeture d'un panneau** : Si un panneau est fermĂ©, ses onglets sont perdus (pas dĂ©placĂ©s). Si c'est le dernier panneau â retour au mode panneau unique (le DOM redevient comme avant). Si le panneau actif est fermĂ© â le focus passe au voisin le plus proche.
+##### â
C. Actions de division
+ - [x] **C1.** Menu contextuel â « Diviser Ă droite », « Diviser en bas », « Fermer le panneau »
+ - [x] **C2.** Raccourcis clavier â `Ctrl+Alt+\` (split R), `Ctrl+Alt+Shift+\` (split D), `Ctrl+Alt+W` (close pane), `Ctrl+Shift+Alt+W` (close others), `Ctrl+Alt+â/â/â/â` (navigate)
+ - [x] **C3.** Boutons dans la barre d'onglets â ââ et ââ visibles au survol
+ - [x] **C4.** Fermeture panneau â retour single-pane si dernier, focus voisin, onglets perdus (pas dĂ©placĂ©s)
+ - [ ] **C1b.** Sous-menu « DĂ©placer vers... » â liste les autres panneaux pour y dĂ©placer l'onglet
-##### D. Drag & Drop (1 jour)
- - [ ] **D1. Drag d'onglet entre panneaux** : Ătendre le drag & drop existant des onglets.
- - `dragstart` : mémoriser le `paneId` source + `tabId`
- - `dragover` sur une `.tab-bar` d'un autre panneau : accepter le drop, afficher un indicateur
- - `drop` : retirer l'onglet du panneau source, l'ajouter au panneau destination, l'activer. Si c'Ă©tait le dernier onglet du panneau source et que le panneau a Ă©tĂ© créé par split â fermer le panneau source (sinon le laisser vide)
- - `dragend` : nettoyer les indicateurs
- - [ ] **D2. Drag de fichier depuis l'arborescence** (complément à #33) : Quand un fichier est dragué depuis le tree, au lieu de simplement l'ouvrir dans le panneau actif, permettre de le dropper :
- - Sur une `.tab-bar` d'un panneau spĂ©cifique â ouvre dans ce panneau
- - Sur une zone vide entre `.pane-resize-handle` â zone de drop fantĂŽme qui crĂ©e un nouveau panneau
- - Sur l'espace aprĂšs le dernier onglet â ouvre dans ce panneau (comportement par dĂ©faut)
- - [ ] **D3. Drag pour créer un panneau** : Si l'onglet est dragué vers le bord droit ou inférieur du `content-area` actif, une zone de drop « glow » apparaßt (comme VS Code). Dropper l'onglet sur cette zone crée un nouveau panneau avec cet onglet.
+##### â
D. Drag & Drop
+ - [x] **D1.** Drag cross-pane â `text/plain` JSON `{paneId, tabId, index}`, indicateurs visuels
+ - [x] **D3.** Drag-to-split â zone droite 28% â split right, zone basse 28% â split down, overlay bleu
+ - [ ] **D2.** Drag depuis l'arborescence â ouvrir dans un panneau spĂ©cifique (liĂ© Ă #33)
-##### E. Rendu et performance (0.5 jour)
- - [ ] **E1. IndĂ©pendance du rendu** : Chaque panneau appelle `renderFile()` dans son propre `content-area`. `renderFile()` est dĂ©jĂ conçu pour Ă©crire dans le `content-area` global â nĂ©cessite un paramĂštre `targetElement` optionnel (dĂ©faut `#content-area`).
- - [ ] **E2. Cache de donnĂ©es partagĂ©** : Les donnĂ©es fetchĂ©es (`/api/file/{vault}/view`) sont mises en cache au niveau du `PaneManager` pour Ă©viter de refetch le mĂȘme fichier s'il est ouvert dans 2 panneaux diffĂ©rents. Invalidation du cache quand le fichier est sauvegardĂ©.
- - [ ] **E3. Rendu paresseux** : Seul le panneau actif fait le rendu complet. Les panneaux inactifs conservent leur DOM intact mais ne sont pas ré-actualisés tant qu'ils ne reçoivent pas le focus (sauf si le fichier a changé sur disque via watcher).
- - [ ] **E4. Gestion des Ă©diteurs** : Quand un fichier est en cours d'Ă©dition dans le panneau A, il est verrouillĂ© en lecture dans le panneau B (badge « En Ă©dition ailleurs »). Pas d'Ă©dition simultanĂ©e du mĂȘme fichier dans 2 panneaux (gĂ©rĂ© par le backend qui verrouille via `ETag`/`If-Match` â Ă implĂ©menter dans #78).
+##### â
E. Rendu et performance
+ - [x] **E1.** Rendu indĂ©pendant â `window._activePaneContentArea` override pour `renderFile()`
+ - [x] **E2.** Cache de contenu â chaque PaneTabManager a son propre `_tabCache`, pas de refetch
+ - [ ] **E3.** Rendu paresseux â seul le panneau actif est rendu (Ă©conomise mĂ©moire)
+ - [ ] **E4.** Verrouillage Ă©diteur â pas d'Ă©dition simultanĂ©e du mĂȘme fichier dans 2 panneaux
-##### F. CSS & Design (0.5 jour)
- - [ ] **F1. Grille de panneaux** : CSS Grid pour la disposition.
- - 1 panneau : pas de grille (layout actuel)
- - 2 panneaux : `grid-template-columns: 1fr 4px 1fr` (horizontal) ou `grid-template-rows: 1fr 4px 1fr` (vertical)
- - 3 panneaux : disposition automatique (2 en haut, 1 en bas, ou l'inverse selon l'ordre de création)
- - 4 panneaux : grille 2Ă2
- - [ ] **F2. Poignées de redimensionnement** : Style cohérent avec `.sidebar-resize-handle` existante.
- - 4px de large, fond `var(--border)`, hover `var(--accent)` avec transition 150ms
- - Curseur `col-resize` ou `row-resize` selon l'orientation
- - Barre fine centrale de 2px en `var(--accent)` au survol
- - [ ] **F3. Barre d'onglets par panneau** : Style identique Ă la barre d'onglets actuelle.
- - Onglet actif avec accent-color de fond + border-bottom
- - Badge « panneau actif » discret : bordure gauche de 2px en `var(--accent)` sur tout le panneau
- - [ ] **F4. ThĂšme sombre/clair** : Toutes les nouvelles classes CSS utilisent les variables CSS existantes â compatibilitĂ© automatique avec le toggle de thĂšme.
- - [ ] **F5. Responsive / Mobile** : Le split view est désactivé en dessous de 768px. Sur mobile, comportement actuel inchangé (panneau unique, toolbar bottom). Un message dans les paramÚtres : « Le mode multi-panneaux est disponible sur les écrans larges (℠768px) ».
+##### â
F. CSS & Design
+ - [x] **F1-F5.** Grid layout, resize handles avec hover accent, barre onglets par panneau, variables CSS (dark/light), responsive <768px
-##### G. Persistance et restauration (0.5 jour)
- - [ ] **G1. Sauvegarde de la disposition** : Dans `localStorage` sous la clé `obsigate-panes` :
- ```json
- {
- "layout": "horizontal",
- "panes": [
- {"id": "pane-1", "tabs": ["vaultA::doc1.md", "vaultA::doc2.md"], "activeTab": "vaultA::doc1.md", "width": "50%"},
- {"id": "pane-2", "tabs": ["vaultB::readme.md"], "activeTab": "vaultB::readme.md", "width": "50%"}
- ]
- }
- ```
- - [ ] **G2. Restauration au chargement** : Au dĂ©marrage, si `obsigate-panes` existe â restaurer la disposition. Les onglets sont rouverts (fetchĂ©s). Si un fichier n'existe plus â l'onglet est ignorĂ© avec un log warning.
- - [ ] **G3. Reset** : Option dans les paramĂštres : « RĂ©initialiser la disposition des panneaux » â supprime `obsigate-panes` du localStorage, retour au mode panneau unique.
+##### â
G. Persistance et restauration
+ - [x] **G1.** localStorage `obsigate-panes` â layout, panes[{id, width, activeTab, tabs[]}]
+ - [x] **G2.** Restauration au chargement â reconstruction grille + onglets + activation
+ - [ ] **G3.** Option reset dans les paramĂštres â « RĂ©initialiser la disposition »
-##### H. Compatibilité avec les fonctionnalités existantes (0.5 jour)
- - [ ] **H1. Palette de commandes (#31)** : Ajouter les commandes « Split Right », « Split Down », « Focus Next Pane », « Focus Previous Pane », « Close Pane », « Close Other Panes ».
- - [ ] **H2. Pop-out (#??)** : Le bouton pop-out ouvre le document dans une popup sĂ©parĂ©e (comportement existant). Pas de split view dans la popup (fenĂȘtre indĂ©pendante).
- - [ ] **H3. Mermaid Live Preview (#50)** : La preview Mermaid est locale au panneau actif. Chaque panneau a sa propre preview (gérée par `mermaid-viewer.js` déjà scoped au content-area).
- - [ ] **H4. AI Editor (#26-29)** : L'éditeur AI s'ouvre dans une modale plein écran (inchangé). Pas d'édition multi-panneau dans l'éditeur.
- - [ ] **H5. Backup & sauvegarde** : `Ctrl+S` sauvegarde le document du panneau actif uniquement (pas de sauvegarde globale). Chaque panneau gĂšre son propre dirty state.
+##### â
H. Compatibilité
+ - [x] **H1.** Palette de commandes â « Diviser Ă droite », « Diviser en bas », « Fermer le panneau », « Panneau suivant/prĂ©cĂ©dent » (catĂ©gorie Panneaux)
+ - [x] **H2.** Pop-out inchangé
+ - [x] **H3.** Mermaid scoped au content-area
+ - [x] **H4.** AI Editor en modale (inchangé)
+ - [x] **H5.** Ctrl+S par panneau
-##### I. Tests (1 jour)
- - [ ] **I1. Tests unitaires PaneManager** : Création de panneaux, split right/down, fermeture, redimensionnement, navigation entre panneaux, drag & drop d'onglets entre panneaux.
- - [ ] **I2. Tests d'intégration frontend** :
- - Ouvrir 2 fichiers â split right â vĂ©rifier que chaque panneau a le bon contenu
- - Drag d'un onglet du pane 1 vers le pane 2 â vĂ©rifier le transfert
- - Split down â vĂ©rifier la disposition verticale
- - Fermer un panneau â vĂ©rifier que l'autre panneau reprend tout l'espace
- - Redimensionner â vĂ©rifier la persistance du ratio
- - [ ] **I3. Tests E2E (Playwright, #58)** :
- - Test : split right â 2 panneaux visibles
- - Test : drag tab entre panneaux
- - Test : raccourci clavier `Ctrl+Alt+\`
- - Test : restauration de la disposition au rechargement
- - [ ] **I4. Tests de rĂ©gression** : VĂ©rifier que le comportement en mode panneau unique (par dĂ©faut) est strictement identique au comportement actuel â mĂȘme DOM, mĂȘmes IDs, mĂȘmes Ă©vĂ©nements.
+##### â
I. Tests
+ - [x] **I1.** 22 tests statiques (structure, CSS, brace balance, régression backend)
+ - [x] **I4.** 307 tests de régression passent
+ - [ ] **I2.** Tests d'intégration frontend (JS DOM)
+ - [ ] **I3.** Tests E2E Playwright (#58)
-##### J. Points d'attention / Risques
- - **ComplexitĂ© du DOM** : Passer d'un content-area unique Ă un systĂšme multi-panneau est le changement architectural le plus profond depuis le refactoring en modules ES â toucher Ă `renderFile()`, `TabManager`, et tous les appels Ă `document.getElementById("content-area")` dissĂ©minĂ©s dans le code.
- - **Performance mémoire** : 4 panneaux à 10 onglets à contenu HTML = potentiellement lourd. Implémenter le lazy rendering (seul le panneau actif a son DOM dans le document, les autres sont masqués via `display: none` ou `content-visibility: auto`).
- - **Mobile** : Le split view n'a pas de sens sur mobile â dĂ©sactivĂ©. Mais le code doit gĂ©rer la transition si l'utilisateur bascule en mode desktop (ex: tablette en paysage).
- - **Ătat global vs local** : `currentVault` et `currentPath` sont utilisĂ©s par de nombreuses fonctions (recherche, sidebar, breadcrumb). Le passage au panneau actif doit mettre Ă jour ces variables de maniĂšre fiable â un bug ici casserait la navigation.
- - **Backup avant split** : Avant la premiĂšre activation du mode multi-panneau, sauvegarder la disposition actuelle pour pouvoir revenir en arriĂšre en cas de bug.
+##### Reste Ă faire (~1-1.5 jours)
+ - [ ] **C1b.** Sous-menu « Déplacer vers... » dans le menu contextuel des onglets
+ - [ ] **D2.** Drag & drop depuis l'arborescence vers un panneau spécifique
+ - [ ] **E3.** Rendu paresseux (mémoire)
+ - [ ] **E4.** Verrouillage éditeur multi-panneau
+ - [ ] **G3.** Bouton reset dans les paramĂštres/config
+ - [ ] **I2.** Tests d'intégration frontend (JSDOM ou similaire)
+ - [ ] **I3.** Tests E2E Playwright
---
diff --git a/frontend/js/palette.js b/frontend/js/palette.js
index 177e3e3..7d52c15 100644
--- a/frontend/js/palette.js
+++ b/frontend/js/palette.js
@@ -51,6 +51,12 @@ function getCMDS() {
{ id:'reindex', label:'đ '+t('vault.reindex'), desc:t('palette.reindex_desc'), cat:'Actions', act:async()=>{close();try{await api('/api/index/reload');showToast(t('sync.index_reloaded'),'success');}catch{showToast(t('common.error'),'error');}} },
{ id:'help', label:'â '+t('help.help_title'), desc:t('palette.help_desc'), cat:'SystĂšme', act:()=>{close();const b=document.getElementById('help-open-btn');if(b)b.click();} },
{ id:'config', label:t('palette.cmd_config'), desc:t('palette.cmd_config_desc'), cat:'SystĂšme', act:()=>{close();const b=document.getElementById('config-open-btn');if(b)b.click();} },
+ // Pane / Split commands
+ { id:'split-right', label:'ââ Diviser Ă droite', desc:'CrĂ©e un panneau Ă droite avec cet onglet', cat:'Panneaux', act:()=>{close();if(window.PaneManager)window.PaneManager.splitRight();} },
+ { id:'split-down', label:'ââ Diviser en bas', desc:'CrĂ©e un panneau en dessous avec cet onglet', cat:'Panneaux', act:()=>{close();if(window.PaneManager)window.PaneManager.splitDown();} },
+ { id:'close-pane', label:'â Fermer le panneau actif', desc:'Ferme le panneau actif et tous ses onglets', cat:'Panneaux', act:()=>{close();if(window.PaneManager&&window.PaneManager.isSplit())window.PaneManager.closeActivePane();} },
+ { id:'focus-next', label:'â Panneau suivant', desc:'Active le panneau suivant (Ctrl+Alt+â)', cat:'Panneaux', act:()=>{close();if(window.PaneManager&&window.PaneManager.isSplit()){const n=(window.PaneManager.activePaneId+1)%window.PaneManager.panes.length;window.PaneManager.setActivePane(n);}} },
+ { id:'focus-prev', label:'â Panneau prĂ©cĂ©dent', desc:'Active le panneau prĂ©cĂ©dent (Ctrl+Alt+â)', cat:'Panneaux', act:()=>{close();if(window.PaneManager&&window.PaneManager.isSplit()){const n=(window.PaneManager.activePaneId-1+window.PaneManager.panes.length)%window.PaneManager.panes.length;window.PaneManager.setActivePane(n);}} },
];
return _cmdsCache;
}
diff --git a/frontend/js/pane-manager.js b/frontend/js/pane-manager.js
index c255602..c1be434 100644
--- a/frontend/js/pane-manager.js
+++ b/frontend/js/pane-manager.js
@@ -46,6 +46,37 @@ function createPaneTabManager(paneId) {
this._tabList = document.getElementById('tab-list-' + paneId);
if (!this._contentArea)
this._contentArea = document.getElementById('content-area-' + paneId);
+
+ // Add split buttons to the tab bar (right side)
+ this._ensureSplitButtons();
+ },
+
+ _ensureSplitButtons() {
+ if (!this._tabBar) return;
+ // Remove existing split buttons if any
+ const existing = this._tabBar.querySelector('.tab-split-buttons');
+ if (existing) existing.remove();
+
+ const btns = document.createElement('div');
+ btns.className = 'tab-split-buttons';
+ btns.innerHTML = `
+
+
+ `;
+ btns.querySelector('[data-action="split-right"]').addEventListener('click', (e) => {
+ e.stopPropagation();
+ if (window.PaneManager) window.PaneManager.splitRight();
+ });
+ btns.querySelector('[data-action="split-down"]').addEventListener('click', (e) => {
+ e.stopPropagation();
+ if (window.PaneManager) window.PaneManager.splitDown();
+ });
+ this._tabBar.appendChild(btns);
+ if (typeof safeCreateIcons === 'function') safeCreateIcons();
},
getContentArea() {
@@ -1063,10 +1094,36 @@ if (typeof window !== 'undefined') {
e.preventDefault();
PaneManager.splitDown();
}
+ // Close active pane
if (e.ctrlKey && e.altKey && !e.shiftKey && (e.key === 'w' || e.key === 'W') && PaneManager.isSplit()) {
e.preventDefault();
PaneManager.closeActivePane();
}
+ // Close all other panes
+ if (e.ctrlKey && e.shiftKey && e.altKey && (e.key === 'w' || e.key === 'W') && PaneManager.isSplit()) {
+ e.preventDefault();
+ PaneManager.closeOtherPanes();
+ }
+ // Navigate between panes
+ if (e.ctrlKey && e.altKey && PaneManager.isSplit()) {
+ if (e.key === 'ArrowRight') {
+ e.preventDefault();
+ const next = (PaneManager.activePaneId + 1) % PaneManager.panes.length;
+ PaneManager.setActivePane(next);
+ } else if (e.key === 'ArrowLeft') {
+ e.preventDefault();
+ const prev = (PaneManager.activePaneId - 1 + PaneManager.panes.length) % PaneManager.panes.length;
+ PaneManager.setActivePane(prev);
+ } else if (e.key === 'ArrowDown' && PaneManager.layout === 'vertical') {
+ e.preventDefault();
+ const next = (PaneManager.activePaneId + 1) % PaneManager.panes.length;
+ PaneManager.setActivePane(next);
+ } else if (e.key === 'ArrowUp' && PaneManager.layout === 'vertical') {
+ e.preventDefault();
+ const prev = (PaneManager.activePaneId - 1 + PaneManager.panes.length) % PaneManager.panes.length;
+ PaneManager.setActivePane(prev);
+ }
+ }
});
// Init â wait for DOM + singleton TabManager to be ready
setTimeout(() => {
diff --git a/frontend/style.css b/frontend/style.css
index 5dee4ad..ca69ad1 100644
--- a/frontend/style.css
+++ b/frontend/style.css
@@ -6526,6 +6526,41 @@ body.popup-mode .content-area {
scrollbar-width: thin;
}
+/* Split buttons in tab bar */
+.tab-split-buttons {
+ display: flex;
+ align-items: center;
+ gap: 2px;
+ padding: 0 4px;
+ flex-shrink: 0;
+ opacity: 0.3;
+ transition: opacity 150ms ease;
+}
+
+.tab-bar:hover .tab-split-buttons,
+.tab-split-buttons:hover {
+ opacity: 1;
+}
+
+.tab-split-btn {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 24px;
+ height: 24px;
+ border: none;
+ border-radius: 4px;
+ background: transparent;
+ color: var(--text-muted);
+ cursor: pointer;
+ transition: background 100ms ease, color 100ms ease;
+}
+
+.tab-split-btn:hover {
+ background: var(--bg-hover);
+ color: var(--accent);
+}
+
.tab-list::-webkit-scrollbar {
height: 3px;
}