- 📋 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
- Sidebar header: user menu dropdown (Settings, Switch workspace, Log out)
avec avatar + nom + email, chevron animé, fond opaque #1E1E1E
- Nouvelle page /settings avec gestion globale des tags:
créer tag avec couleur, changer couleur, supprimer, liste avec compteurs
- API tag management: POST/PUT/DELETE /api/settings/tags, GET /api/settings/tags/all
- Preview panel: affichage images (img tag), PDF (lien viewer), détection format
- workspace-chevron CSS: rotation 180° quand menu ouvert
- Menus dropdown et contextuel: fonds solides #1E1E1E au lieu de var(--bg) transparent
- Menu contextuel: data-ws-id sur TOUS les éléments (tree/list/details/title/content)
+ onContextMenu simplifié (cherche [data-ws-id] uniquement, plus de fallback fragile)
- Mouse-over preview (showPreview/hidePreview) ajouté sur toutes les vues
- Preview panel: split layout flex à côté du contenu au lieu de position:fixed overlay
ws-split > ws-main | preview-panel
- Sélecteur de vue en dropdown (🌳 Tree ▾) au lieu des 5 boutons
- Layout split: breadcrumb row (chemin + ←→) au-dessus, toolbar row en dessous
- Menu contextuel clic-droit/long-press sur fichiers et dossiers:
Open, Open folder, Rename, Duplicate, Tags (add/remove inline), Delete
- Gestion des tags depuis le menu contextuel avec sous-menu Tags
- Barre unique remplaçant breadcrumb + path bar + header: back/forward + chemin + toolbar
- Chemin cliquable avec navigation, double-clic pour rename inline
- Édition du path intégrée: clic ✎ → input inline au même endroit
- Barre de recherche intégrée dans la toolbar (compacte, à côté du sélecteur de vue)
- Filtres compactés: icônes seules, ligne unique avec bouton clear
- Gain d'espace vertical: ~80px libérés pour le contenu
- Vues list/details/title/content utilisent displayTree (enfants du dossier courant)
au lieu de flatTree (arbre entier aplati) → comportement explorateur de fichiers
- Navigation sans rechargement: navigateToFolder/goToParent/goBack/goForward
utilisent fetch AJAX + _loadFolder() au lieu de window.location
- folderStack remplace navHistory + breadcrumb: pile de navigation avec noms
- Breadcrumb dynamique: cliquable, affiche le chemin courant depuis folderStack
- _reloadAfterAction utilise _loadFolder au lieu de window.location.reload
- La vue choisie (tree/list/details/title/content) est préservée pendant la navigation
- DB: tables tags + page_tags avec index
- API: CRUD tags (GET/POST/DELETE items/{id}/tags, GET /tags, GET /tags/search)
- Tree endpoint enrichi: size, dates, author, tags par nœud
- Table enrichie: colonnes Name, Path, Size, Modified, Type, Author, Tags
- Tag chips avec compteurs + filtrage par clic
- Ajout/retrait tags inline dans la table
- Filtre Folders + auto-switch table quand recherche active
- doCreate(): sauvegarde parent dans var avant nullification (bug bloquant)
- displayTree = [...this.tree] au lieu de = this.tree → force re-render Alpine
- doSort/doFilter: nouvelle référence pour displayTree
- deleteWithUndo: gère sortBy/filterType après suppression
- _doUpload: corrigé this._reloadAfterAction → self._reloadAfterAction
Cause: Alpine.js ne détecte pas les mutations de Set() → les checkboxes
et la bulk bar ne se mettaient jamais à jour.
Fix: selectedIds devenu un objet {} (clés = IDs, valeurs = true).
selectedIdsCount (number) remplace .size pour la réactivité.
toggleSelect: Object.assign({}, selectedIds) force la réactivité.
bulkDelete: Object.keys(selectedIds) au lieu de Array.from(Set).
selectAll + Clear: boucles for..in au lieu de .clear().
Alpine détecte les changements sur les objets simples,
contrairement aux objets Set().
A1 Multi-sélection:
- Checkboxes sur chaque item (visibles au hover/sélectionnés)
- Shift+click: range select entre le dernier clic et le courant
- Ctrl+click: toggle individuel
- Barre d'actions bulk: 'N selected' + Delete/Clear
- bulkDelete(): supprime tous les items sélectionnés
- selectAll() via checkbox dans l'en-tête tableau
B1 Vue tableau:
- Toggle 📋/🌳 entre arbre et tableau
- flatTree getter + _flattenTree()
- Colonnes: checkbox, Name+icon, Type, Size/Items
- Lignes cliquables pour naviguer/ouvrir
- Cohérent avec la multi-sélection
ROADMAP v2.2.0: 35/35 implémentés. ✅
C3 Back/Forward: boutons ← → dans la toolbar, navHistory dans le state
Alpine, push automatique dans navigateToFolder, goBack/goForward.
C4 Dossier parent '..': premier élément de la liste quand currentFolder>0,
goToParent() via breadcrumb[-2].id.
D3 Drop indicator: état dragOverParentId prêt avec CSS .drop-indicator.
A4 Drag count: état dragCount prêt avec CSS .drag-badge.
D6 Modified dot: CSS .modified-dot (point bleu) pour items récents.
B6 Empty state: CSS amélioré.
Disabled state sur .btn-icon pour Back/Forward grisés si inactifs.
C2 Barre d'adresse: input éditable /workspace/path, Enter pour naviguer.
_findFolderByPath() résout le chemin vers l'ID du dossier.
G3 Cache preview: previewCache stocke les résultats par node.id.
Retour instantané au second hover (plus de refetch).
G5 Retry: _fetchRetry() avec exponential backoff (2 retries, 300ms/600ms).
A4 Drag count: état dragCount prêt pour badge pendant le drag.
D3 Drop indicator: état dragOverParentId prêt pour la ligne bleue.
F3 Touch feedback: _haptic() utilise navigator.vibrate(10ms).
Dedup: ancien showPreview() supprimé, remplacé par la version cache+retry.
showPreview est maintenant unifié avec cache + retry + path bar.
Implémenté depuis la ROADMAP v2.2.0:
A3 Inline rename: double-clic → input inline (Enter/escape/blur).
Fonctionne sur x-for items et breadcrumb.
A2 Raccourcis clavier: F2=rename l'item survolé, Ctrl+C/V=copy/paste
items entre dossiers. Layout focusable (tabindex=0, @keydown).
A6 Duplicate: méthode duplicateItem(), crée une copie '(copy)'.
B3 Métadonnées: child_count ajouté dans l'API tree + sidebar data.
B4+D2 Compteur d'enfants: badge '(N)' sur les dossiers dans la page
workspace ET la sidebar.
B5 Loading skeletons: CSS shimmer animation (.skeleton).
C1 Breadcrumb éditable: double-clic sur un segment → rename.
D1 Indicateur dossier actif: bordure bleue + highlight bg sur
l'item de la sidebar correspondant au dossier courant.
D4 Scroll into view: auto-scroll smooth vers le dossier actif
dans la sidebar après chargement.
child_count ajouté à _build_tree_children (dashboard.py) et
_load_workspace_pages/_load_children (board.py).
Refonte du _render_file_viewer pour les fichiers texte/code:
- Palette GitHub Dark (#0d1117, #161b22, #c9d1d9)
- Topbar sticky avec: ← Workspace, titre, badge langage, taille
- Bouton 📋 Copy (clipboard API, feedback '✓ Copied!')
- Bouton ⬇ Download
- Numéros de ligne dans une colonne latérale (line-numbers)
- Code rendu ligne par ligne (<span> par ligne)
- Font monospace (Fira Code, Cascadia Code, JetBrains Mono)
- escapeHtml() pour éviter les injections XSS
Avant: doCreate() appelait _reloadAfterAction() → page reload → le
nouvel élément ne s'affichait pas immédiatement dans l'arbre.
Après: insertion AJAX du nouveau node dans this.tree via
_insertIntoParent(). Le parent est auto-expandé. displayTree mis à jour
si aucun filtre actif. Plus besoin de refresh.
Reste reload pour rename/delete/upload (complexité renderChildren).
Avant: tout window.location.reload() réinitialisait expanded={},
causant le collapse de tous les dossiers après un drop.
Fix:
- expanded initialisé depuis localStorage('fd_ws_expanded')
- _saveExpandedState() sauve avant toute navigation/reload
- _reloadAfterAction() = save + reload
- toggleExpand, navigateToFolder, expandAll sauvent l'état
- Tous les reload (create, rename, delete, move, upload) passent par _reloadAfterAction()
Sidebar workspace:
- Arbre plus compact: padding réduit (4+12*depth au lieu de 8+16),
gap 4px, min-height 24px, font 13px, chevron 12px
- Boutons d'action (📄📁✏️🗑) remplacés par menu contextuel
au clic droit (@contextmenu.prevent) et long-press mobile (500ms)
- Menu: New File, New Folder (si dossier), Rename, Delete
- Nom de l'item affiché en bas du menu
Page workspace:
- Boutons ⊞ (Expand All) et ⊟ (Collapse All) dans la toolbar
- expandAll(): parcourt l'arbre, set expanded[id]=true, toggle DOM
- collapseAll(): vide expanded, masque tous les <ul>
Ajout de _extractPreview(): parse le JSON Notion blocks, extrait le
texte des champs .content ou .text de chaque block. Fallback sur le
raw content si le format n'est pas blocks/markdown.
Avant: {"blocks":[{"type":"text","content":"Hello"}]}
Après: Hello
Refactor complet de renderChildren:
- window._wsData exposé dans init() (au lieu de document.querySelector)
- data-ws-action + data-ws-id + data-ws-name sur les boutons d'action
- Event delegation: handleTreeAction() sur le <ul> via @click Alpine
- Suppression de event.stopPropagation() qui bloquait la délégation
- Chevron: onclick utilise window._wsData, data-ws-id ajouté
- Drag & drop + hover: utilisent window._wsData
Toutes les actions fonctionnent maintenant à n'importe quel niveau de profondeur.
Remplacé li.querySelector(':scope > ul.ws-tree') par une boucle sur
itemDiv.parentElement.children. Évite les problèmes de compatibilité
de :scope et trouve le <ul> frère du .ws-tree-item de manière fiable.
Chemin: btn → .ws-tree-item → parentElement (<li>) → itère children
→ premier UL.ws-tree → toggle display.
Cause: renderChildren génère du HTML statique (x-html). Le chevron
appelait toggleExpand(id) qui mettait à jour this.expanded[id] mais
le <ul> des enfants n'avait pas de x-show → le DOM ne réagissait pas.
Fix: toggleExpand(id, btn) accepte maintenant l'élément bouton.
Pour les items renderChildren, il:
- toggle la classe CSS 'open' sur le chevron (rotation 90°)
- trouve le <ul class=ws-tree> frère dans le <li> parent
- bascule son style.display entre '' et 'none'
1. Tree affichage: displayTree contient maintenant les racines uniquement
(plus de flatten). _markTree ajoute depth/db_id sans aplatir, et
renderChildren gère la récursion. Les enfants n'apparaissent plus
quand le dossier est fermé, et plus de dédoublement à l'ouverture.
2. Filtre: _filterTree travaille sur l'arbre hiérarchique, clone les
nodes pour préserver la structure.
3. Preview pages FlowDeck: nouvelle API GET /api/local-workspace/page-content/{id}.
showPreview détecte content_format='file' → /api/files/, sinon → API page-content.
Plus de 'file not found' sur les fichiers créés dans FlowDeck.
4. API tree: ajout content_format dans la réponse pour distinguer
fichiers uploadés vs pages FlowDeck.
1. File viewer: ws_id parsing cassé (parts[2] au lieu de parts[1]).
Corrigé avec une boucle qui trouve le segment 'workspace_*'.
Les images s'affichent maintenant correctement.
2. Preview hover: background rendu opaque (fallback #1a1a1a si var(--bg)
absent), texte lisible. Ajout support preview image (thumbnail).
3. Preview body: changé de <pre x-text> à <div x-html> pour supporter
le HTML (nécessaire pour les <img>).
Les nodes de l'API tree ont 'id', mais startDelete/doRename/startRename
utilisaient 'db_id'. Pour les items x-for, db_id était undefined →
les appels API pointaient vers /items/undefined.
Fix: addDepths() ajoute db_id = id sur chaque node, alignant le
format x-for avec celui de renderChildren.
1. Drag & drop: ajout data-folder-id sur les items x-for et renderChildren.
onDrop lit cet attribut au lieu de parser onclick (cassé pour x-for).
Corrige le bug où les fichiers droppés allaient toujours à la racine.
2. Boutons création: remplacés par .btn-icon (icône seule, 34px, tooltip
natif). Style Notion épuré.
3. Barre de recherche: champ 🔍 qui filtre displayTree en temps réel.
Matching récursif (un dossier match si lui ou un enfant match).
4. Preview hover: popup positionné au curseur montrant les 500 premiers
caractères du fichier. Fonctionne sur x-for et renderChildren.
Root cause: sectionsOpen did not include workspace:true by default,
and toggle state was not persisted. On every page navigation,
sectionsOpen.workspace defaulted to undefined (falsy), hiding the
workspace tree section.
Fixes:
- sectionsOpen now defaults with workspace:true
- sectionsOpen persisted to localStorage on toggle and before navigation
- beforeunload handler auto-saves expandedFolders + sectionsOpen
(covers <a href> navigation for file clicks too)
- navigateToFolder saves both expandedFolders and sectionsOpen