C1b: Sous-menu « Déplacer vers... » (flyout hover) — liste les autres panneaux
D2: Drag depuis l'arborescence vers panneau — format 'tree:{vault,path}'
E3: Rendu paresseux — display:none sur contenu des panneaux inactifs
E4: Verrouillage éditeur — même fichier déjà ouvert → focus le panneau existant
G3: Bouton reset (🔄 Réinitialiser les panneaux) dans la palette
ROADMAP: section 75 marquée ✅ Complété (reste I2/I3 tests)
Ajouts:
- C3: Boutons Split ⊞→ ⊞↓ dans la barre d'onglets (visibles au survol)
- C2: Ctrl+Alt+←/→/↑/↓ navigation entre panneaux
- C2: Ctrl+Shift+Alt+W fermer tous les autres panneaux
- H1: 5 commandes palette (Split R/D, Close Pane, Focus Next/Prev)
- ROADMAP.md: section 75 mise à jour avec statut 80% complété
CSS: .tab-split-buttons, .tab-split-btn avec opacity hover
_buildGrid: quand on passe de single-pane à multi-pane, migre les onglets
du TabManager singleton vers le PaneTabManager du pane 0.
Sans ça, transferTab() ne trouvait pas l'onglet (il était resté dans le singleton).
_collapseToSingle: migration inverse (PaneTabManager → singleton)
quand on ferme le dernier split.
- dragstart: setData 'text/plain' (universel, pas de custom MIME)
- drop container + tab: getData('text/plain')
- console.log dans le drop handler pour diagnostic
- Suppression application/obsigate-tab (non fiable cross-browser)
Bug 1 — bookmarks/récents sans tab:
dashboard.js:135: openFile() → TabManager.openPreview()
_createCard() est partagé par DashboardRecentWidget ET DashboardBookmarkWidget
Bug 2 — drag-to-split bloqué en single-pane:
La condition 'if (dragData.paneId === paneId) return' empêchait le split
car en single-pane, le drag part du pane 0 et drop sur le pane 0.
→ Supprimé. Edge detection vérifiée avant, cross-pane vérifié après.
Même pane + pas edge → laissé au tab handler (reorder).
Cause: _setupPaneDropZone était uniquement appelé sur .pane-container (créé par split).
En mode single-pane, le DOM est .content-wrapper > .tab-bar + .content-area sans wrapper.
→ Ajout de _setupPaneDropZone sur .content-wrapper dans PaneManager.init()
→ CSS: position:relative sur .content-wrapper pour les indicateurs absolute
Changements:
- pane-manager.js: init() ajoute drop zone sur .content-wrapper (single-pane)
- style.css: position:relative sur .content-wrapper
- dragstart: effectAllowed='move' + double setData (obsigate-tab + text/plain)
- dragenter/dragover: acceptent TOUS les drags (plus de filtre types.includes cassant)
- drop: fallback text/plain si application/obsigate-tab indisponible
- console.debug au lieu de silent catch pour diagnostiquer les erreurs restantes
1. types.includes → Array.from(types).includes (DOMStringList n'a pas .includes)
2. dragleave: compteur _dragCounter empêche clear sur child enter/leave
3. drag-to-split: zone recalculée dans drop handler (plus de closure corrompue)
4. drop: stopPropagation + skip same-paneId dans container handler
5. dragenter: appel preventDefault pour accepter le drop dès l'entrée
- _setupPaneDropZone: dragover/drop sur .pane-container (fonctionne même si tab bar vide)
- Drag-to-split: déposer un onglet dans les 25% droit → split right, 25% bas → split down
- Indicateurs visuels: zone bleue semi-transparente au drop, tab-bar dashed outline
- stopPropagation dans le drop handler des tabs pour éviter double-fire
- CSS: .pane-drop-zone-indicator, .tab-bar.drop-target, position:relative sur pane-container
- ui.js: window.TabManager et window._singletonTabManager exposés dans init()
- pane-manager.js: référence corrigée vers window.TabManager (n'était pas en scope)
- PaneTabManager.close/transferTabTo: ferme automatiquement le panneau quand vide (sauf dernier)
- Déployé sur 192.168.30.101:2020, 307 tests verts
- PaneTabManager factory: chaque panneau a sa propre instance avec ses propres onglets
- Drag & drop d'onglets entre panneaux (application/obsigate-tab dataTransfer)
- Split copie l'onglet actif dans le nouveau panneau
- TabManager singleton: délégation automatique vers le panneau actif en mode split
- Persistance complète par panneau (tabs, activeTab, previewTab)
- viewer.js: getContentArea() priorise window._activePaneContentArea
- 307 tests verts, déployé sur 192.168.30.101:2020
- Added copyToClipboard() in utils.js with dual strategy:
- HTTPS: navigator.clipboard.writeText() (modern API)
- HTTP: textarea + execCommand('copy') (legacy fallback)
- viewer.js: uses copyToClipboard() instead of direct navigator.clipboard
- popout.html: same dual-strategy inline
- Fixed: 'Erreur lors de la copie' on HTTP contexts
- CSS: .code-block-wrapper with relative positioning
- CSS: .copy-code-btn with hover opacity transition (hidden until hover)
- CSS: .copied state (green accent) after successful copy
- viewer.js: wraps each <pre> in .code-block-wrapper + appends copy button
- popout.html: same logic with vanilla JS (ES5 compatible)
- Button: 'Copier' → '✓ Copié' on click, reverts after 2s
- Toast: showToast('Code copié dans le presse-papiers') via navigator.clipboard
- Iterates through ALL computed CSS variables (--*) from main app
- Uses getComputedStyle which works even if setProperty was used (themes.js)
- Sets properties via setProperty on popout root — EXACT theme match
- Fallback: localStorage obsigate-theme-mode for direct popout access
- Fixed typo in --text-primary (was 'teext-primary')
- Copies documentElement.style.cssText from main app (has all CSS variables from themes.js setProperty)
- This propagates ANY theme (Github, Linear, Spotify, Notion, etc.) dark+light
- Fallback: localStorage obsigate-theme + obsigate-theme-mode for direct popout access
- Removes the !important hack — replaced by proper cssText inheritance
- Added <style id='theme-force'> with !important dark theme defaults
- JS updates inline style for light mode
- Completely bypasses CSS variable loading, caching, or path issues
- Guarantees body bg/text color regardless of stylesheet state
- Added full dark theme variables to bare :root selector (no attribute required)
- This ensures dark theme always loads even if data-theme is not set or stripped
- :root[data-theme='light'] still overrides for light mode
- Popout windows now always get dark theme baseline
- Pass current theme as ?theme=dark|light in popout URL
- viewer.js, app.js, palette.js all pass theme to popout
- popout.html reads theme from URL param first (fallback: localStorage, then dark)
- Also syncs theme back to localStorage when set via query param
- Root package.json has type:commonjs which breaks ES module syntax checks
- Added frontend/js/package.json with type:module for Node.js parsing
- All 27 JS modules now parse correctly in CI environment
- Both buttons call window.handleLogout (AuthManager.logout) or doLogoutFallback
- doLogoutFallback: XHR POST, hides #app, shows #login-screen, clears token
- No more localStorage.clear() or hard redirect — mimics showLoginScreen()
- Removed all conflicting JS event listeners
- Header #logout-btn: inline fetch+clear localStorage+redirect
- Settings #profile-logout: inline window.handleLogout or fetch fallback
- Removed all JS event listeners that were causing race conditions
- Zero dependency on ES module loading order
- profile-logout now handles logout directly via onclick attribute in HTML
- Calls window.handleLogout() (auth.js global) or direct fetch fallback
- Removed broken JS handler from config.js to avoid double execution
- Replaced window.handleLogout indirection with direct AuthManager.logout() call
- config.js already imports AuthManager from auth.js — no race condition
- Same direct call as header button (auth.js line 210)
- Removed localStorage restore of obsigate-right-sidebar-visible on init
- TOC only opens when user clicks the TOC button in a document
- Width preference still restored from localStorage
- Header logout (#logout-btn) already had no confirm (AuthManager.logout called directly)
- Settings panel (#logout-btn + #profile-logout) now call handleLogout() without confirm
- Both buttons redirect to login screen immediately — consistent behavior
- Both #logout-btn and #profile-logout now delegate to window.handleLogout()
- Previously they did bare fetch+redirect without clearing JWT token or session state
- Added credentials: 'include' fallback for robustness
#74 — Support PDF complet:
- backend/pdf_reader.py — pymupdf/pypdf text extraction
- .pdf added to SUPPORTED_EXTENSIONS in indexer.py
- PDF-aware file view API endpoint (metadata, text preview)
- Streaming endpoint /api/file/{vault}/pdf/stream
- Frontend PDF viewer with iframe, toolbar, download
- CSS for PDF viewer container
#75 — Split View (PaneManager):
- New module frontend/js/pane-manager.js — ES module
- 1-4 pane CSS grid with resize handles
- Split right/down via keyboard (Ctrl+Alt+\) or PaneManager API
- Persistence in localStorage (obsigate-panes)
- Collapse to single pane, per-pane active state
285 tests passent
Initialize i18n before auth so t() works when rendering
auth-dependent UI, and build the command palette lazily so
translations are loaded first. Bump service worker cache version.
The SW was the root cause of all persistent French text issues.
Even after rebuild, browsers with a registered SW would serve
cached old files (viewer.js without data-i18n, old locale JSONs).
SW-killer runs before any module script:
1. Detects if SW is controlling the page
2. Unregisters all SW registrations
3. Clears all caches
4. Reloads the page once (sessionStorage flag to prevent loop)
Other fixes in this commit:
- auth.js: import setLocale statically (was dynamic import)
- auth.js: import loadVaults/loadTags from sidebar.js
- sw.js: add JS modules + locales to STATIC_ASSETS for versioning
- sw.js: CACHE_VERSION v4→v5
Root cause: cacheFirstStatic() in sw.js was caching viewer.js,
i18n.js, auth.js, dashboard.js, and locale JSON files under the old
CACHE_VERSION. Even after rebuild, the old SW served stale cached
files without data-i18n attributes, so dashboard stayed in French.
Changes:
- sw.js: add js/viewer.js, js/i18n.js, js/auth.js, js/dashboard.js,
js/app.js, locales/en.json, locales/fr.json to STATIC_ASSETS
so they are versioned with CACHE_VERSION and properly invalidated
- CACHE_VERSION already bumped to v5 in previous commit
Manual fix for existing clients:
Chrome → F12 → Application → Service Workers → Unregister
Or: chrome://serviceworker-internals/ → Unregister
Then Ctrl+Shift+R to hard-refresh
- auth.js: window.handleLogout at module top level (was inside
renderUserMenu, not always called). Fixes settings dropdown
'Sign out' onclick='handleLogout()' doing nothing.
- auth.js: add missing imports (loadVaults, loadTags from sidebar.js)
and dynamic import for IndexUpdateManager (circular dep avoidance).
Fixes 'loadVaults is not defined' error on login.
- sw.js: CACHE_VERSION v4→v5 to bust stale Service Worker cache
that was serving old viewer.js without data-i18n attributes,
causing dashboard tabs to stay French despite English profile.
To apply: rebuild container + unregister SW in DevTools + reload.