Les modals (create/rename/delete) étaient des siblings de ws-split dans le DOM,
pas des enfants — à cause d'un problème de nesting HTML avec les <template> Alpine.
Résultat: x-show="showCreate" ne trouvait pas 'showCreate' dans le scope parent.
Fix: wrapper <div x-data="_wsInitData"> autour de tout le {% block content %},
plus déplacement des @dragover/@dragleave/@drop/@keydown sur ce wrapper.
Le ws-split garde ses attributs restants (@contextmenu, @click, tabindex, x-ref).
- Ajouté texte 'New File' et 'New Folder' aux boutons icônes
- Ajouté CSS .btn-create avec width:auto pour accommoder le texte
(l'ancien CSS .btn-icon imposait width:34px fixe qui coupait le texte)
- Les boutons étaient des icônes seules (📄📁) sans texte,
donc difficiles à trouver pour l'utilisateur
Alpine évalue les expressions comme tree, flatTree, online, etc.
dans un scope qui n'hérite pas toujours de _wsInitData.
En les exposant comme window.X, Alpine les trouve toujours.
Supprime les 36 erreurs restantes sur /local-workspace.
- Changé x-data="window._wsInitData" → x-data="_wsInitData"
(Alpine évalue mieux sans le préfixe global)
- Supprimé <template x-for="node in flatTree"> dans une table cachée
(x-show="false", jamais visible mais Alpine l'évaluait quand même)
Cause: l'IIFE window._wsInitData était située APRÈS le <div ws-split>.
Alpine évaluait x-data="window._wsInitData" AVANT que l'IIFE ne tourne
→ toutes les propriétés (flatTree, tree, online, ctxMenu, preview*, etc.)
étaient undefined → 148 erreurs Alpine Expression Error.
Fix: déplacement de l'IIFE (lignes 871-2405) avant le ws-split (ligne 339).
Ordre corrigé: IIFE → _wsInitData prêt → ws-split x-data OK.
Vérification: le diagnostic log « ws-split about to render, _wsInitData exists: »
affichera maintenant true au lieu de false.
- app.js: intercepte window.onerror + unhandledrejection
Envoie automatiquement au backend via POST /api/frontend-error
Déduplication, throttling 1/sec, max 50 erreurs buffer local
window.__flowdeck_errors accessible en console debug
- api.py: 2 nouvelles routes
POST /api/frontend-error — reçoit erreurs JS, déduplique, logge
GET /api/frontend-errors?clear=true — Hermes lit les erreurs
- csrf.py: exemption /api/frontend-error du CSRF
Usage Hermes après chaque déploiement:
curl -s http://localhost:8080/api/frontend-errors | jq .
→ Voir TOUTES les erreurs JS en temps réel
Alpine 3.14.9 ne supporte pas les getters dans le Proxy x-data.
Converti en propriétés mises à jour via _recompute() dans:
- init() après chargement tree
- _loadFolder() après chaque navigation
- doSort() / doFilter() après modifs
Getters remplacés:
- pathValue, canGoBack, canGoForward, flatTree,
filteredTableItems, contentSnippetMap
- _computeFilteredTableItems() extrait de get filteredTableItems
Zéro getter restant, braces/parens équilibrés, 73 tests OK
Cause racine: local_workspace.html override le block topbar avec son
propre hamburger button qui avait encore ; au lieu de , dans l'expression
ternaire. Alpine parse les expressions via return <expr> → le ; terminait
le return prématurément → SyntaxError → cascade d'échecs d'initialisation
des composants enfants.
Corrigé dans:
- base.html: sidebar overlay @click (1 occurrence)
- local_workspace.html topbar hamburger (1 occurrence)
- local_workspace.html: 10 autres expressions (viewMode, searchQuery,
filterType, draggedItemId, tagFilter, ctxAddNewTag, navigateToPath)
73 tests OK
Le hamburger button avait mobileSidebarOpen = true; sidebarCollapsed = false
Le ; casse le parser d'expression Alpine (return <expr> → le ; termine le statement)
En cascade, l'erreur empêche l'initialisation correcte du composant parent,
ce qui brise tous les composants enfants (wsInit → ctxMenu, modals, preview, etc.)
C'est le bug qui persistait depuis des jours à travers 5+ approches différentes.
- x-data="_wsInitData" référence la variable globale JS (pas window.)
- var _wsInitData = IIFE — propriété window automatique pour compatibilité window._wsData
- Headers Cache-Control: no-cache, no-store, must-revalidate sur la page HTML
- 73 tests OK
- Changé function wsInit() → Alpine.data('wsInit', ...) avec addEventListener('alpine:init')
- x-data="wsInit()" → x-data="wsInit" (syntaxe correcte pour Alpine.data)
- Ajouté ?v=2.4.6 sur CSS et JS pour bust le cache navigateur
- Version bumpée à 2.4.6
- wsInit() était enregistré via Alpine.data() dans alpine:init →
ne fonctionnait pas → toutes les variables undefined (ReferenceError)
- Changé en function wsInit() globale → x-data='wsInit()' appelle
la fonction directement, zéro enregistrement Alpine
- Supprimé les wrappers Alpine.data() et addEventListener('alpine:init')
- Braces vérifiés: 397/397 équilibrés
- window._wsData.set dans init() avant await (contexte menu)
- @contextmenu.prevent passe par window._wsData (global) au lieu du scope Alpine
→ contourne tout problème de résolution de scope Alpine
- window._wsData = this déplacé AVANT le await fetch(...) dans init()
→ disponible immédiatement, pas après la réponse API
- Conserve @click.self pour fermer le menu en cliquant sur le fond
- Erreur critique: @contextmenu.prevent="onContextMenu" manquait
→ le handler n'était JAMAIS appelé par Alpine
- Rajouté @contextmenu.prevent sur ws-split (entre @keydown et @click.self)
- ctxMenu utilise mutations individuelles (pas de remplacement d'objet)
pour une meilleure réactivité Alpine
- @click.self conserve la fermeture en cliquant sur le fond du workspace
- @click.away sur ctx-menu retiré (fermait le menu immédiatement)
- Fermeture via @click.self sur ws-split (clic sur fond du workspace)
- Fermeture via Escape (déjà présent)
- Fermeture via les items du menu (ctxMenuOpenPage, etc.)
- Mode debug: console.log conservé pour confirmer l'appel handler
- Ajouté console.log('onContextMenu fired') pour diagnostiquer si le handler
Alpine est bien appelé au clic droit
- Fichier local_workspace.html restauré depuis b32b94e (sans le handler
natif cassé qui causait des erreurs Alpine)
- Vérifier la console navigateur: si 'onContextMenu fired' apparaît au
clic droit, le problème est post-handler. Sinon, c'est Alpine/l'événement
- Le handler Alpine @contextmenu.prevent="onContextMenu" ne fonctionnait pas
(problème de scope/resolution Alpine sur le clic droit)
- Remplacement par addEventListener('contextmenu', ...) natif dans init()
→ événement capturé directement sur le DOM, bypass complet Alpine
- Propriétés ctxMenu modifiées individuellement (pas d'objet remplacé)
pour garantir la réactivité Alpine sur les nested properties
- L'ancien handler onContextMenu est gardé en fallback avec le même pattern
- Suppression du @contextmenu.prevent du template HTML (évite double-fire)
- @click.outside détectait le clic-droit d'ouverture comme 'outside' → menu
fermé immédiatement après ouverture. Même bug que le dropdown sidebar.
- @click.away utilise setTimeout → le handler s'enregistre APRÈS le cycle
d'événement courant → le clic d'ouverture n'est pas capté comme 'away'.
- Fonctionne dans TOUTES les vues: tree (renderChildren), list, details,
cards, content — elles ont toutes data-ws-id
- Bouton ☰ (x-show="sidebarCollapsed") positionné juste AVANT le bouton
All Workspaces dans la breadcrumb row. Apparaît seulement quand sidebar
est cachée, disparaît quand elle est visible.
- Scope Alpine: sidebarCollapsed hérité de appState() (parent de wsInit())
- Remplace l'ancien bouton fixed qui chevauchait le contenu
- Bouton show sidebar (uncollapse) supprimé de position:fixed → intégré au
hamburger du topbar. Si sidebar collapsed → hamburger l'ouvre directement.
Plus aucun chevauchement avec le contenu.
- .ws-split padding: 40px 24px 0 → 8px 16px 0 (gain 32px verticaux)
- Tous les éléments sont remontés, plus d'espace visible sans scroll
- Tags affichés avec pastille de couleur (tag-dot 6px) dans TOUTES les vues:
tree (renderChildren), table filtrée, details, cards, content, preview panel
- Barre de filtres tags: pastille couleur + nom + compteur
- CSS .tag-dot: cercle 6px avec background dynamique
- Hauteur réduite: breadcrumb-row padding 3px, toolbar-row padding 3px
margin-bottom 4px, flèches nav 24px au lieu de 28px
- Bouton show sidebar (uncollapse): top 60px au lieu de 12px →
ne chevauche plus la breadcrumb/toolbar
- Route manquante GET /api/settings/avatar/{filename} → sert le fichier uploadé
(résout le 404 sur l'image uploadée)
- Colonne avatar_color ajoutée à users (migration ALTER TABLE try/except)
- set_avatar_color sauvegarde la couleur dans la DB (pas seulement local)
- _sidebar_data() lit avatar_url + avatar_color depuis la DB
- Sidebar avatar: fond coloré quand pas d'image, image quand uploadée
avec background-image:url() + initiale en fallback
- Remplacé le clamping statique (Math.min 200/300) par mesure réelle
du menu après rendu DOM via $nextTick + getBoundingClientRect()
- Si le menu dépasse à droite → repositionné à gauche
- Si le menu dépasse en bas → repositionné vers le haut
- Marge de 4px des bords pour éviter le clipping
- Fonctionne pour toutes les tailles de menu (tags, sous-menus dépliés)
- loadWorkspaceTags() utilise /api/settings/tags/all (TOUS les tags, pas
seulement ceux déjà utilisés via JOIN page_tags). Avant: 2/6 tags visibles.
- Nouvelle grille couleurs 'New tag': grid 7 colonnes × 2 rangées,
carrés responsifs via padding-bottom:100%. + bouton Add à droite.
- .ctx-menu: max-height:80vh + overflow-y:auto → plus de clipping
en bas de fenêtre. z-index 600 (au-dessus du settings panel).
- .color-swatch-ctx: carrés parfaits dans la grid, hover scale 1.15
- 📋 Add existing tag: item cliquable qui déplie la liste des tags configurés
avec pastilles de couleur, noms et compteurs. Clic sur un tag → ajout + repli
- ➕ New tag: item cliquable qui déplie palette couleurs + input texte
- Chevrons ▸/▾ indiquent l'état ouvert/fermé
- ctxTagExistingOpen state réinitialisé à chaque ouverture du menu
- Les tags déjà assignés restent visibles en haut avec ✕ pour retirer
- Section TAGS toujours visible (plus besoin de cliquer pour ouvrir un sous-menu)
- Tags existants affichés avec leur pastille de couleur (⦿ 10px) + bouton ✕
- Liste des tags DISPONIBLES (pas encore assignés) avec couleurs,
clic → ajoute le tag à l'élément. Compteur d'utilisation affiché
- Palette 14 couleurs pour créer un nouveau tag (carrés 18px arrondis)
- Input pour nouveau tag avec palette de couleurs
- CSS: .ctx-section, .ctx-tag-color, .color-swatch-ctx, .ctx-add-tag-item
- Palette 14 couleurs pour avatar: clic sur un carré coloré → applique la couleur
au fond de l'avatar avec l'initiale. API POST /api/settings/avatar-color
- Upload photo: efface la couleur custom et utilise l'image uploadée
- Color swatches tags: border-radius 6px (carrés arrondis) au lieu de 50% (ovales)
- CSRF: /api/settings ajouté à EXCLUDED_PATHS → 403 corrigé sur avatar + tags
- /api/settings ajouté à EXCLUDED_PATHS → avatar upload et tags CRUD
n'étaient pas exemptés du CSRF → 403 Forbidden sur POST/PUT/DELETE
- Color swatches: border-radius 6px (carrés arrondis) au lieu de 50% (ovales)
- Avatar upload et tags create/update/delete fonctionnent maintenant
1. @click="workspaceMenuOpen = !workspaceMenuOpen" inline (pas toggleWorkspaceMenu)
2. @click.away (pas click.outside) — click.away ignore le clic sur le trigger
3. Wrapper <div style=position:relative> autour header+dropdown →
position:absolute s'ancre correctement, header et dropdown sont siblings
donc le clic header ne déclenche PAS le @click.away du dropdown
- folderStack: supprimé {id:0, name:'Root'} dans init() et navigateToRoot()
→ breadcrumb n'affiche plus 'Workspace / Root' mais juste 'Workspace / dossier'
- Sidebar: overflow-y:auto déplacé de .sidebar vers .sidebar-scroll (flex child)
→ le header et son dropdown ne sont PLUS clippés par overflow
→ position: absolute + top:100% fonctionne maintenant sur le dropdown
- .sidebar-scroll wrapper autour du contenu scrollable (nav row → footer)
- Bouton All workspaces (🏠) déplacé du topbar-right vers le breadcrumb row,
positionné AVANT les flèches ← → de navigation
- Retiré 📁 icône + nom workspace du topbar (redondant avec le breadcrumb)
- Topbar allégée: hamburger menu + breadcrumb serveur uniquement
- Retiré le bouton collapse en double dans la nav row (déjà dans le header)
- Ajouté position: relative sur .sidebar-workspace-header → dropdown s'ancre
correctement sous le header au lieu de se positionner n'importe où
- Sans position: relative, le dropdown absolute remontait jusqu'à un ancêtre
positionné et sortait de l'écran ou était caché par overflow
- Collapse << dans le header (opacity 0, visible au hover avec @click.stop)
- Search 🔍 déplacé à droite du collapse dans la nav row
- Nom affiché: user.full_name ou user.login (pas workspaceName)
- Chevron ▾ conservé comme indicateur dropdown (tout le header reste cliquable)
- Le patch précédent a retiré le wrapper <div style="display:flex..."> mais
son </div> fermant est resté → balise fermante orpheline → DOM cassé
- Résultat: la page ne s'affiche plus du tout (rendu HTML invalide)
- Alpine évalue les expressions même dans x-show caché → previewItem null → crash
- Ajout de <template x-if="previewItem"> autour du preview panel entier
- x-if empêche toute évaluation tant que previewItem est null/false
- @click='for(var k in selectedIds)...' causait SyntaxError: Unexpected token 'for'
- Cette erreur empêchait l'exécution de TOUT Alpine.data('wsInit', ...)
- Résultat: toutes les variables Alpine 'not defined' (cascade de 80+ erreurs)
- Fix: méthode clearSelection() appelée depuis @click, plus robuste et réactive