- ROADMAP.md: réorganisé avec priorités claires (v4.0.0=complété, v4.0.1=onboarding PRIO MAX)
- Ajout BRANCHING.md: convention main/develop/feat/*, workflow complet
- Branche develop créée et poussée sur Gitea
- Sections Parité Notion réparties en v4.1–v4.10 ordonnées par priorité
- Résumé des phases mis à jour
- createPage() now detects active workspace context (library, local-workspace page, sidebar)
- In a local/Gitea workspace: creates workspace item via /api/local-workspace/items with prompt
- No workspace: creates general Notion page via /board/api/pages (no prompt)
- Added hidden #fd-local-ws-id in local_workspace.html to expose workspace_id
- _confirmCreateFolder() passes workspace_id for Gitea workspaces
- Added _getContext() helper that checks multiple context sources in priority order
- Added _toast() helper for cross-component notifications
- Add global window.FlowDeck namespace with createPage() and showCreateFolderModal()
- createPage() creates a Notion-style page via /board/api/pages (same as sidebar bottom-right button)
- showCreateFolderModal() opens a global themed modal (dark/light) instead of browser prompt()
- All New Page buttons now call window.FlowDeck.createPage()
- All New Folder buttons now call window.FlowDeck.showCreateFolderModal()
- Global New Folder modal uses existing .flowdeck-modal CSS classes for theme consistency
- Updated Ctrl+N keyboard shortcut to use unified createPage()
Le x-show sur #lib-table dépendait de flatItems.length,
qu'Alpine 3.14.9 ne détecte pas. Maintenant _renderTable()
contrôle l'affichage/masquage directement via style.display.
Abandon complet d'Alpine pour le rendu du tableau :
- _renderTable() fait innerHTML directement sur #lib-table-body
- Plus de x-for, plus de x-html, plus de getters
- SortableJS supprimé (causait removeEventListener sur null)
- Fix startRename/commitRenameById pour utiliser les IDs DOM
Le x-for d'Alpine 3.14.9 ne détecte pas les changements d'array.
Remplacement complet par x-html avec génération HTML manuelle.
Ajout de window._libData pour les onclick handlers globaux.
Fonctions _byId pour toggle/expand/rename/open depuis le HTML.
Alpine 3.14.9 ne détecte pas le changement de référence d'array
(this.flatItems = result). Utilisation de splice() pour muter
l'array en place. Ajout de $nextTick pour forcer le re-render.
Ajoute des logs dans init(), loadTab() et _recomputeFlatItems()
pour identifier pourquoi flatItems reste vide malgré le chargement API.
CSP: ajout fonts.googleapis.com/gstatic.com pour débloquer Google Fonts.
Remplacé visibility:hidden par opacity:0.4 pour les hover-only.
Cela garantit que les éléments sont RENDUS et visibles en permanence,
avec accentuation au hover (opacity 1).
Les pages du workspace local ont workspace='' mais workspace_id=27.
Le filtre workspace != '' les excluait, retournant 0 items.
Ajout de OR workspace_id IS NOT NULL pour inclure les pages locales.
Simplification du frontend: le workspace tab appelle l'API standard avec workspace_id.
- Nouvel endpoint /api/library/local-workspace?workspace_id=X
interroge local_workspace_items et formate pour la Library
- Nouvel endpoint /api/library/local-workspace-children/{id}
pour l'expansion d'arborescence des items du workspace local
- Frontend: détecte workspaceId + !isGiteaActive → appelle local-workspace
- Frontend: toggleExpand route vers le bon endpoint selon source_type
- Tous les getters (flatItems, selectedCount, allSelected, emptyIcon/Title/Text)
remplacés par des propriétés + fonctions _recompute().
Les getters causent un bug connu d'Alpine 3.14.9 qui bloque l'init.
- Private tab: x-show="!workspaceId && !isGiteaActive"
(caché quand un workspace local OU distant est ouvert)
- CSS hover-only: visibility:hidden/visible au lieu de opacity (infaillible)
- Workspace tab: filtré par workspace_id quand un workspace est actif
- Tab Private remplacé par Repository (visible seulement si Gitea workspace actif)
- Nouvel endpoint /api/library/repository pour le contenu Gitea
- dashboard.py passe is_gitea_workspace, owner, repo au template
Les échappements de quotes dans le patch précédent étaient cassés
(\\' au lieu de \'), causant des erreurs de syntaxe JS qui
empêchaient l'initialisation de _wsInitData et donc d'Alpine.
- Drag handle (6 dots), checkbox, OPEN bouton intégrés dans la colonne Page name
- CSS hover-only: opacity 0 → 1 au mouseover (pas de visibilité permanente)
- Checkbox checked toujours visible (opacity:1) même sans hover
- OPEN bouton aligné à droite via margin-left:auto dans le name cell
- Double-clic sur le nom ouvre la page, simple clic = rename
- toggleExpand() force la réactivité Alpine via this.items = [...this.items]
- Colonnes drag/select/open séparées supprimées (layout Notion)
- Library: ajout /api/library/children/{id} pour charger les enfants à la demande
- Library: drag handle (.drag-handle) et checkbox (.lib-checkbox) toujours visibles
- Library: SortableJS init pour drag-and-drop réordonner les rows
- Library: toggleExpand() async avec chargement des enfants depuis l'API
- Local workspace: drag handle 6-dots + checkbox toujours visible dans les tree items
- Local workspace: renderChildren() inclut checkbox + drag handle + inline rename
- Local workspace: clic sur nom de fichier = inline rename (plus navigation directe)
- Local workspace: bouton Open pour ouvrir le fichier (double-clic aussi)
- CSS: .item-checkbox toujours visible (plus opacity:0)
- CSS: .drag-handle avec opacité .45 par défaut, 1.0 au hover
1. Hover effects fixed: converted table to div-based flexbox layout
(tr/td don't support display:flex). .lib-row now properly shows
drag handle, checkbox, and OPEN button on hover.
2. Recents filter by workspace: /api/library/recents now accepts
workspace_id parameter. Template passes active_workspace_id from
sidebar context to API calls.
3. Tree hierarchy: API now enriches items with has_children via
_enrich_children() batch query — shows expand chevrons for
pages with sub-pages.
4. Rename on click: single click on title starts inline rename
(was opening page). OPEN button still opens side peek.
5. Code cleanup: extracted _enrich_children() helper to eliminate
duplicate code across 6 endpoints.
Complete rewrite of /library page to match Notion's Library design:
1. Table view with columns: Page name (with icon), Created by (avatar),
Source, Last edited time, Last visited time
2. Hover effects showing drag handle (6 dots), checkbox, OPEN button
3. Side peek panel: opens on right, shows page content preview,
close button, favorite toggle, copy link, more menu
4. Multi-selection: checkbox per row, select-all header, 'X selected' bar
with Delete and '...' menu
5. ... menu: Remove from Recents, Copy links to all, Move to, Move to Trash
6. Inline rename: double-click title → edit field
7. 6 tabs: Recents, Favorites, Shared, Published, Private, Workspace
8. Tree expand/collapse for nested pages (has_children support)
9. + Add new row at bottom
10. Search bar toggleable
API additions:
- library.py: enhanced _build_item with icon, source_label, author
- dashboard.py: new /api/pages/{id}/content, /rename, /trash endpoints
- All 133 tests pass
Rollback des commits 2226e5e et 2534e2b — les fichiers continuent
de s'ouvrir dans page_editor.html (layout normal avec sidebar),
pas dans le viewer standalone _render_file_viewer.
The real route for /pages/{page_id} is in dashboard.py (no prefix router),
not board.py (prefix /board). Both routes now call _render_file_viewer
for content_format='file' pages.
- view_page now calls _render_file_viewer for content_format='file' pages
instead of rendering the full page_editor template (which was the old path)
- URL-encode filenames in file_url using urllib.parse.quote() to handle
spaces and special characters correctly
- Fixed in both board.py (viewer + page_data) and dashboard.py
The _render_file_viewer was dead code — defined but never called. File pages
were going through page_editor.html which rendered PDFs in an iframe embedded
in the editor UI. Now they get a clean standalone HTML viewer.
- page_editor: étoile ⭐/☆ dynamique selon favorited (x-text)
- toggleFavorite() appelle refreshFavorites() du sidebar
- base.html: refreshFavorites() fetch /api/library/favorites + rebuild DOM
- Context menu 'Add to Favorites' → refreshFavorites() au lieu de location.reload()
- Les items dynamiques ont onclick/oncontextmenu pour fonctionner hors Alpine
- _header.html: remplace click par hover (mouseenter/mouseleave)
avec timer 200ms anti-flicker, cascade sous-menus au hover
- dashboard.py: ajoute nav_workspace_id à trash, library, workspace,
gitea-workspace, local-workspace, workspaces, settings, pages/{id}
- La section du popover affiche 'Workspaces' pour Home, 'Private' pour le reste
- Les clics dans les popovers ferment le menu + naviguent
Backend:
- Added _nav_breadcrumb() to build page hierarchy chain (root → current)
- New /api/nav/menu endpoint returns workspace/folder children with has_children flag
- view_page() and view_page_root() now pass breadcrumb_items, nav_workspace_id, nav_page_id to template
Header template (_header.html):
- Replaced static breadcrumb with Alpine.js fdBreadcrumb() component
- Breadcrumb items now clickable with dropdown menus showing
Config:
- Fixed SQLite path parsing on Windows (handle drive letters without prepending /)
- Changed default theme from dark to light
- Updated OAuth test credentials (gitea_oauth_client_id/secret)
Auth:
- First real user (excluding default admin with no password) becomes admin
- Changed user count query to exclude users without password_hash
File viewer:
- Removed separate _render_file_viewer() —