- app.js : x-ignore sur .main-wrapper au swap fdLoad (nœud remplacé
uniquement), démontage unique quand tous les <script src> ont exécuté
(load/error + filet 4s) via Alpine.initTree idempotent (_x_marker) —
fini les cascades « Undefined variable » (éditeur) et « reading 'has' »
($store.fdCtx) : la zone s'initialisait avant composants et stores.
- 5 scripts de page gardés contre la ré-exécution (SyntaxError
« Identifier 'LW' has already been declared » à la 2e visite
partielle) : local_workspace, board, settings, database_table,
page_editor_realtime.
- if (window.Alpine) → Alpine.data immédiat sinon listener alpine:init
(déjà passé sur swap) : 8 scripts + 7 templates inline.
- app.css : purge des 7 blocs @font-face Inter orphelins (fichiers
inexistants → 302 HTML → « Failed to decode downloaded font »).
- Gates : e2e/regression_editor_mount + e2e/regression_partial_nav
(7 pages x complet/1er/2e passage) + regression_logout_dnd + smoke
= 6/6 verts sur l'image rebuildée · pytest 1094 passed · ruff OK ·
OpenAPI 7.45.2.
Ajout — e2e/smoke.spec.js (2 gates verts contre l'instance de test) :
- gate A39 : bascule de vues d'une collection (clic onglet Calendar →
?view_type=calendar, grille .calendar + .cal-header rendue ; collection
créée puis SUPPRIMÉE = répétable)
- gate A20 : palette Ctrl+K (ouverture Alpine .open, recherche GET rend
.cmd-palette-item, fermeture Échap)
- filet console : 0 erreur JS/CSP (bruit Failed to load resource 401/403
filtré)
- Service Workers bloqués : /sw.js sert sa page « hors ligne » sur les
navigations redirigées (redirect:'manual') — pwa_offline.spec.js couvre
le SW
- bootstrap autonome : login OU création du compte e2e documenté (jamais
de mot de passe deviné), workspace si absent
- commande : cd e2e && node node_modules/@playwright/test/cli.js test smoke.spec.js
Fixed — trouvés par les gates :
1. Bascule de vues standalone JAMAIS fonctionnelle : les onglets
émettaient ?view=… mais la route lit `view_type` (FastAPI) → l'onglet
restait sur Table quel que soit le clic (bug pré-existant, A28 n'y est
pour rien). Onglets → ?view_type= ; test_all_view_tabs_present adapté +
assertion comportementale (GET ?view_type=calendar rend .calendar).
2. Inter bloqué par la CSP depuis v7.27 : app.css importait encore
Google Fonts (@import raté par le grep de la passe v7.27) → violation
style-src sur chaque page + police en fallback. Inter auto-hébergé :
2 faces variables (100-900, latin + latin-ext) dans static/fonts/,
@import supprimé (8 fichiers dupliqués dédupliqués → 2).
suite **1093/1093** · ruff OK · E2E **2/2** · docs à jour
v5.11.0 Wiki-links & mentions de page :
- tokens [[fdpage:ID]] / [[fddate:ISO]] dans le texte des blocs,
service app/services/wiki_links.py (labels, rendu HTML, extraction)
- taper [[ ouvre le picker de pages (recherche floue, clavier) ;
le menu @ gagne les sections Pages et Date (today/tomorrow/YYYY-MM-DD)
- chips atomiques contenteditable=false relues en tokens par gtTok()
(autosave/drag/undo preservent les liens) ; renommage propage via
GET /board/api/wiki/titles ; backlinks reconnaissent les tokens ;
page publique rend les chips (echopee)
v5.12.0 Templates & verrouillage :
- template picker global sur + New page : 5 built-in
(app/services/block_templates.py) + templates perso
(table page_global_templates, migration 13)
- POST /board/api/page-templates (save current page) + /{id}/use
(instantiate, id 0 = built-in par cle)
- page lock : POST /api/pages/{id}/lock, garde _ensure_page_editable
-> 423 en ecriture pour les non-privileged, deblocage par
locked_by ou admin seulement (403 sinon), banniere + read-only UI
- full-width / small text par page (pages.full_width/font_small,
POST /api/pages/{id}/options, classes CSS)
- migration 13 : is_locked, locked_by, full_width, font_small,
page_global_templates
Tests : tests/test_v511_v512_wiki_templates.py (15) ; suite complete
538 verte ; ruff OK ; node --check des templates JS OK.
Stop collapsing the sidebar on navigation (was hiding the nav when opening Shared pages). Show the hamburger in the top-left on desktop when collapsed, add a left-edge hover zone that floats the sidebar in and auto-collapses on mouse leave, and bind Ctrl/Cmd+\\ to toggle it.
- board/dashboard: helper _load_shared_sidebar_pages intégrant les pages marquées is_shared directement
- _workspace_tree_macro: badge 👥 N&B sur les fichiers partagés du tree du sidebar
- local_workspace: badge 👥 en noir & blanc
- static/css/app.css: style .page-shared-badge
- Correctif : la fenêtre Share rechargait ses partages à chaque ouverture (before: liste vide après refresh)
- Sidebar « Shared » scindée en sous-groupes « Par moi » (partages nominatifs + liens) et « Avec moi » (partages reçus), sans doublons
- Bibliothèque : filtre « Tous / Par moi / Avec moi » sur l'onglet Shared ; /api/library/shared?dir=made|received|all + share_dir par élément
- Document : bouton barre affiche « 👥 Shared ▾ » quand la page est partagée (membre, lien ou publiée), recalculé à la volée
- Workspace local : emoji 👥 juste avant le nom des fichiers partagés (is_shared exposé par /api/local-workspace/tree)
- Tests +6 (tests/test_sharing.py) : direction made/received/all, fallback dir invalide, rendu pageIsShared ; suite 325 verte (+3 PDF pré-existants)
- Fix "Invalid permission" : le client envoyait editor/commenter/viewer alors que
l'API attend view/comment/edit (invitePermission -> 'edit', menu participants aligné)
- PUT /api/pages/{page_id}/share/{share_id} : mise à jour de permission persistée
- Autocomplete des membres existants dans le champ d'invitation (search users + debounce,
navigation clavier, envoi user_id pour lier le partage au compte)
- Upsert anti-doublon dans le partage + trim email backend
- 12 tests tests/test_sharing.py ; suite 319 verte (+3 PDF pre-existants)
Sans oublier v5.4.0 (non publie jusque-la) :
- Undo/Redo (Ctrl+Z/Ctrl+Shift+Z/Ctrl+Y), duplicate (Ctrl+D), menu de bloc (turn
into, couleurs, lien #fdblk-, move to, delete), drag&drop multi-selection,
slash "Actions", en-tetes de tableau (has_header/first_col_header) + exports,
sync realtime immédiat apres mutation ; 9 tests test_block_interactions.py
- Titre : data-placeholder="New Page" + bascule .empty a l'input (mecanisme des
blocs). Le placeholder s'affiche en gris pale quand le titre est vide, y compris
apres effacement. save() stocke un titre vide (plus de "New page" force), la
sidebar garde son fallback, duplicatePage retombe sur "New Page".
- Blocs : les placeholders ("Press 'space' for AI...", "Heading 1", "List"...)
passent sous :focus-within -> ne s'affichent que quand le bloc est focalise.
Idem pour "Type code..." du bloc code.
v5.1.6
- Placeholder "Press 'space'..." : la classe .empty est basculee sur l'element
interne [data-bid], mais la regle CSS ciblait .block-content (l'exterieur),
donc le ::before ne se rendait jamais. La regle cible desormais
.block-content [data-bid].empty::before.
- Fleches haut/bas du menu / : ajout d'un ecouteur keydown au niveau document
(actif quand le menu est ouvert) pour naviguer quel que soit le focus
(l'input vit dans #_slashMenu, voisin de #_blocksCt). Selection .selected en
!important pour gagner sur :hover + auto-scroll de l'element selectionne.
- Item "callout" du menu / : icone texte 'lightbulb' remplacee par l'emoji
(et 'image' par ). Le texte n'est plus mal positionne.
- Composer "Edit with AI" : largeur alignee sur celle du bloc (min 280px).
v5.1.4
- AI (space) -> 400 DeepSeek: fdAgent.generate now uses the clean no-tools
/api/agent/generate endpoint instead of the tool-enabled conversation engine
(which sends tools/tool_choice schemas DeepSeek rejects). Fixes the composer,
Ask AI, AI meeting note and summarize flows.
- Slash menu arrows: selection highlight was invisible on open (nothing marked),
so up/down appeared dead. Call _rs() on open + after filtering, and give the
selected item an accent background + left bar. Verified with jsdom harness:
Down/Up move selection, Enter applies.
- Block outline when selected: suppress focus rings (outline/box-shadow) on all
editable blocks ([data-bid], .block-content, page title).
- Drag handle (6-dot): add .block-wrapper:hover/:focus-within .block-handle so
it shows on hover for normal blocks (only column-wrappers showed it before).
- Empty-block hint: brighter (--text-secondary, opacity .75) so the
"Press space for AI or / for commands" text is clearly visible on empty paras.
- VERSION + app.main -> 5.1.3; CHANGELOG updated.
- 271 tests pass; page renders 200; /api/agent/generate route registered;
JS validated (node --check + jsdom).
- share-dialog/more-menu/activity-popover/move-dialog: enfants de .page-editor-wrapper
(overflow-y:auto) ancores en absolute top:100% -> invisibles sous le viewport.
Repositionnes en fixed sous la topbar, scope .page-editor-wrapper > (pas de
regression sur .more-menu de library/local_workspace) + variante mobile <768px.
- Item Export du menu More faisait moreOpen=false avant d'ouvrir exportOpen:
les 4 formats etaient inaccessibles. Devient un toggle, le menu reste ouvert.
- Cache busting app.css v=4.7.1; VERSION/main.py bump 4.7.1; CHANGELOG+ROADMAP a jour.
- Verifie Playwright headless: Share/More/Export/download Markdown/toast OK, 0 pageerror; 190/190 tests.
- Service serveur app/services/export.py: conversion blocs vers Markdown / HTML / PDF
- 4 endpoints: /api/export/markdown|html|pdf|site/{page_id}
- Export Markdown complet (tous les blocs + images + sous-pages recursives)
- Export PDF via xhtml2pdf (pur Python, aucune lib systeme)
- Export HTML standalone self-contained (styles inline)
- Export Site: zip multi-pages (index.html + une page par sous-page)
- UI: menu 'More > Export' dans l'editeur (Markdown, HTML, PDF, Site .zip)
- Tests: 190 passing (6 nouveaux)
- Page title placeholder: new pages created with empty title, CSS :empty::before shows 'New page' in gray
- Database is now inline embed within page (not full-page replacement)
- Embed block 'collection' renders database table via FlowDeckDB.renderInto()
- Open button in column 1 of each row opens page in side peek panel
- Side peek: iframe loading /pages/{id}?embed=1, close/fullscreen/resize
- DB scripts refactored as reusable DBInstance + global FlowDeckDB API
- Column resize: drag handle between headers to resize column width
- SVG outline icons per property type in header (Aa, #, ☰, 📅, ☑, 🔗, ✉, 📞)
- Header context menu on click: Show page icon toggle, AI Autofill, Filter,
Sort (asc/desc), Group, Calculate (None/Count/Percent), Freeze, Wrap,
Insert left/right
- Submenus for AI Autofill, Sort, Calculate with nested options
- New Page button moved below last data row (bottom of table)
- New rows inserted at end of table
- Migration: add collection_id column to pages table
- POST /api/pages/{id}/convert-to-database: transforms page into database
- GET /api/collections/{id}/table-data: returns props + pages for table view
- POST /api/collections/{id}/pages: creates new row in collection
- New page_editor_collection.html template for database view
- _database_table.html: Notion-style table with columns, rows, New Page, Add Property
- Alpine.js component for cell editing, new page creation, property mgmt
- CSS: complete database table styling (view bar, table, cells, modals, dropdowns)
- Frontend: createDatabase() now converts page to full-page DB (was inline embed)
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() —
Téléchargés et servis depuis /static/js/:
- alpine.min.js (44 KB) — remplace unpkg.com
- htmx.min.js (51 KB) — remplace unpkg.com
- sortable.min.js (45 KB) — remplace cdn.jsdelivr.net
- prism.min.js (19 KB) + prism.css (1.3 KB) — remplace cdnjs
Corrige le bug 'rien ne fonctionne' quand le réseau est lent/absent:
sans CDN, Alpine.js/HTMX/SortableJS ne chargeaient pas →
tous les @click, x-show, x-data, drag-drop inopérants.
1. _require_gitea: retrait fallback admin token → 401 si pas connecté
2. user-menu-dropdown: background var(--bg-primary) au lieu de #1E1E1E
3. applyTheme() déjà immédiat via style.setProperty sur documentElement
- 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
- 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)
- 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)
- 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
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).
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>
- API /api/local-workspace/tree: arbre récursif complet (support optionnel ?folder=ID)
- API /api/local-workspace/breadcrumb: fil d'Ariane parent pour un dossier
- Page /local-workspace: supporte ?folder=ID pour naviguer dans un dossier
- Sidebar: expand/collapse (▶/▼) sur les dossiers, clic dossier → /local-workspace?folder=ID
- CSS: styles chevron .tree-chevron, .tree-folder-link
- Alpine.js: expandedFolders, toggleTreeFolder, navigateToFolder