Colonnes / équation / TOC
- renderBlock() n'appelait JAMAIS renderColumnsBlock()/renderMathBlock()/renderTOC()
(3 renderers morts) → un bloc colonnes (slash « Column list », Turn into
2/3/4/5 colonnes) tombait dans le rendu générique = paragraphe vide.
Dispatch ajouté ; la × de suppression de colonne (opacity:0 sans survol)
devient visible au survol.
Tables rendues par l'IA
- _tblIsData/_tblIsSep exigeaient un | de tête → le GFM sans pipe (A | B /
--- | ---) devenait des paragraphes. Pipe de tête optionnel, séparateur
gardé à ≥2 cellules (« texte » + « --- » reste un séparateur). md2b et
paste2b partagent les helpers.
Copier / couper / coller
- Sélection de texte MULTI-BLOCS : _clipState()/_clipMarkdown() (début partiel
→ blocs entiers via blocksToMarkdown → fin partielle), clipCopy()/clipCut()
(coupure = tête du 1er bloc + queue du dernier), listeners copy/cut (Ctrl+C/X
via les événements natifs), groupe Clipboard (Cut/Copy/Paste) dans le menu du
clic droit avec la sélection figée à l'ouverture (la recherche prend le focus),
clic droit sur un bloc sélectionné = sélection multi-blocs conservée.
- Coller un seul bloc structuré (table/code/titre) = vraie insertion (avant :
texte brut) ; pasteBlocks colle après le dernier bloc sans focus.
- « Copy link to block » réparé (data-bm-act link → copy-link).
Tests: pytest tests/ -q -n auto → 1394 passed / 0 failed ; ruff OK ;
e2e v768_columns_clipboard (4 : slash+Turn into colonnes, table IA sans pipe,
copier/couper/coller, clic droit Clipboard) + v762/v764/v766/v7641/v763/
regression_editor_mount → verts sur :8081 (image flowdeck:v7.68.0).
Tiroir Comments
- Section « Reactions » au-dessus des fils : émoji, compte (si >1) et texte
ancré lu dans le DOM (reactionList() / _anchorText(), offsets source hors
pastilles) ; l'état vide ne s'affiche plus s'il reste des réactions.
- Suppression : ✕ sur la ligne du tiroir (mes réactions) ET croix ::after au
survol de la pastille sur l'émoji que j'ai posé (aucun nœud ajouté → offsets
intacts). Deux entrées → removeReaction() → POST toggle → loadReactions().
Menu de sélection (maquette ASCII)
- Ligne 4 = rangée scannée : 💬 Comment (libellé) à gauche, 🙂+ réaction à
droite (.ft-row-split) ; la réaction sort de la ligne 3.
- Entête Skills avec ≡ à droite (::after), liste Skills scrollable.
- ✨ Ask AI retiré de la toolbar : doublon exact de « Edit with AI » du pied.
- Écart volontaire à la maquette : le barré (T) de la ligne 2 est conservé.
Fix racine (flake de gate)
- _is_public_host attrape OSError (le garde SSRF renvoie un booléen, il ne
lève plus) : un socket.timeout DNS ne bascule plus fetch_og_metadata dans
son except Exception → repli silencieux → AssertionError.
- fixture stub_dns sur TestOGParser (même motif que test_agent_web_tools) :
zéro vrai DNS dans les tests OG.
Tests: pytest tests/ -q -n auto → 1390 passed / 0 failed (flake corrigé) ;
ruff check app tests → OK ; e2e v764 (réactions dans le tiroir + suppression
A/B), v762 (nouvelle ligne 4), v766, v7641, v763 → 6 passed sur :8081
(image flowdeck:v7.67.0 rebuild + redéployée).
- SM.escClose() : un seul implément, appelé par les deux chemins de fermeture
du menu slash (focus dans le bloc → onKd, focus dans l'entrée du menu →
listener document). Retire le « / » d'ouverture seulement, remet le caret,
sync() + autoSave().
- Bouton .block-comment-btn à droite du bloc (gouttière 64px) : icône bulle de
conversation + , visible au survol/focus comme le handle, masqué sur mobile
(2 breakpoints) et sur page verrouillée ; onclick = E.commentBlock(idx).
Remplace l'ancien .block-actions de renderBlock (opacity:0 sans règle de
survol → un + invisible qui capait les clics dans la gouttière).
- fmtComment() restaure la plage de la toolbar (_fmtRestore) avant d'ancrer :
commentBlock() ne se replie plus sur this._sel (plage périmée éventuelle).
Tests: pytest tests/ -q -n auto → 1386 passed ; ruff check app tests → OK ;
e2e v766 (Esc → menu fermé + bloc vide ; bouton masqué → visible au survol,
à droite du wrapper, ancre = bloc visé) + v764/v762/v7641/v763/
regression_editor_mount → 7 passed sur :8081.
- Réaction sur texte : bouton 🙂+ (« React to selected text ») dans la toolbar
de sélection → sélecteur d'émoji EXISTANT en mode réaction (en-tête = texte
sélectionné + ✋, onglets masqués, ligne Recent au-dessus de la grille
People, Filter… et barre de catégories avec + custom inchangés)
- Backend : migration 37 text_reactions (UNIQUE plage × emoji × utilisateur →
POST idempotent) + GET/POST /api/pages/{id}/reactions (GET groupé par plage
emoji/count/mine, POST toggle, 400 sur plage vide)
- Affichage : plage commentée/réagie peinte en jaune (_paintAnchors après
render/loadComments/loadReactions), pastille 😀+compte après la plage, clic
pastille = rouvrir le sélecteur, clic ancre = ouvrir et centrer le fil
- Pastilles = annotations : gtTok/gtMd les ignorent (y compris le chemin
rapide innerText) et les offsets _srcOffset/_srcNodeLen les excluent → le
texte sauvé est identique (vérifié dans le e2e)
- Tiroir refondu : carte de fil (avatar, nom, horodatage, ✓ toggle
resolve/reopen, ⋯ → Copy link #cmt-<id>/Delete), saisie « Add a comment... »
avec @ et envoi ↑/Entrée ; #cmt-<id> ouvre et centre le fil au chargement ;
loadComments + loadReactions à l'ouverture (commentCount enfin renseigné)
- Fixes : resolveComment ne pouvait pas rouvrir (toujours resolved:true) ;
showCommentBtn ignorait les sélections à ancre élément (bouton 💬 jamais
affiché)
- Tests : tests/test_v764_comments_reactions.py (8, dont toggle API réel),
e2e/v764_comments_reactions.spec.js (8 étapes) — 3/3 specs e2e verts,
suite complète 1383 passed / 0 failed, ruff OK, OpenAPI 7.64.0 (526 chemins)
- Barre flottante défilante sous le curseur (mobile ≤ 768 px) au lieu de la
popup desktop, 15 boutons dans l'ordre de la description : +▾, Turn into▾,
@, 💬, 🖼, 🗑, ←, →, ↑, ↓, ↩, ↪, Color▾, More▾, ⌨
- +▾ → feuille plein écran « Insert block » (titre centré, Cancel bleu, groupes
Basic blocks / AI writing / Media / Data / Synced / Actions, raccourcis
markdown à droite) ; items partagés avec la popup desktop (SM._slashItemHtml)
- Turn into ▾ / Color ▾ → feuilles réutilisant les générateurs extraits
_blockTurnItems / _blockColorItems (extraits de _blockTurnMenuOpen /
_blockColorMenuOpen, comportement desktop inchangé)
- More ▾ → menu contextuel de bloc existant ; @ insère + ouvre les mentions ;
💬 commentaire ; 🖼 bloc image ; 🗑 suppression ; ⌨ masque le clavier ;
↩ ↪ annuler/rétablir
- moveBlock(bid, dir) : ↑↓ réordonne (racine + enfants toggle/columns),
→ indent dans le frère précédent, ← outdent, garde-fous avec toast
- Fermeture auto de la barre quand le « / » n'est plus en tête de bloc ; taps
dans la barre/les feuilles exclus du handler « clic hors-zone » ; Cancel ne
referme que la feuille (la barre reste tant que le / est tapé)
- Raccourcis slash alignés sur les préfixes markdown (-, [], >, ---), groupe
SYNCED libellé « Synced »
- Tests : tests/test_v763_mobile_menus.py (ordre, câblage, structure, styles +
moveBlock exécuté sous node), e2e/v763_mobile_menus.spec.js (390×844, 1
passed) ; test_v761 adapté aux extractions ; suite complète 1375 passed / 0
failed, ruff OK, OpenAPI 7.63.0 (525 chemins)
- Toolbar de sélection sous le surlignage : ligne de style (T + type de bloc
dynamique → sous-menu Turn into), A/B/I/U/T barré/Tx, lien/surlignage/
code/équation/…, Comment + Ask AI, section grise Skills (4 actions) et pied
« Edit with AI » (Alt+⇧+E) ; positionnée sous la sélection (au-dessus si
manque de place), fermée au clic hors sélection
- « … » → menu contextuel de bloc existant (recherche « Search actions… »,
groupe Text, 10 actions + raccourcis, pied auteur/heure/taille)
- Info-bulle claire à droite des éléments du menu : data-tooltip-html +
classe .tooltip-light dans le tooltip global (app.js)
- Persistance du formatage inline : sérialiseur gtMd() (DOM formaté → source
markdown) branché sur sync() ; mdEsc() rend __…__ (u), ==…== (mark) et
$$…$$ (équation inline KaTeX, data-tex conservé)
- Fixes racine : askAI(idx) écrasé par askAI() → askAIBlock(idx) (menu de bloc,
Ctrl+J, Alt+⇧+E) ; ed.__x (Alpine v2) inexistant sous v3 → _xed() via
_x_dataStack[0] (showFmt/hideCommentBtn/mentions ne s'exécutaient jamais) ;
ancre de sélection nœud élément gérée ; checkMd() appelé avec un id au lieu
de l'index (exception à chaque barre d'espace)
- Tests : tests/test_v762_selection_toolbar.py (roundtrip gtMd/mdEsc sous node)
+ e2e/v762_selection_toolbar.spec.js (structure, sous-menus, géométrie,
persistance bold/highlight après reload) ; suite complète 1368 passed / 0
failed, ruff OK, OpenAPI régénérée 7.62.0 (525 chemins)
- panneau de gauche : recherche « Search actions... », section « Text », les
10 actions dans l'ordre Notion (Turn into, Color, Copy link Alt+⇧+L,
Duplicate Ctrl+D, Move to Ctrl+⇧+P, Delete Del, Comment Ctrl+⇧+M,
Suggest edits Ctrl+⇧+Alt+X, Ask AI Ctrl+J, Skills) + pied métadonnées
(auteur / heure de dernière édition / mots-caractères du bloc)
- panneau de droite au survol (fermeture différée) : Turn into avec ✓ du
type actuel + Heading 1-4, listes, To-do, Toggle, Code, Quote, Callout,
Block equation, Synced block, 2/3/4/5 columns (info-bulle « Create N
columns of blocks ») ; Color avec Last used / Text color / Background
color (10 couleurs nommées, ✓ courante, carré de couleur) ; Skills =
compétences réelles de l'espace appliquées au bloc via l'IA
- turnInto(idx,type,cols) : crée N colonnes, sélecteur de source pour les
blocs synchronisés ; setBlockColor mémorise la dernière couleur (Ctrl+⇧+H)
- nouvelles actions éditeur : commentBlock (sélection + tiroir), askAI
(composeur sans effacer le bloc), suggestEdit / runSkill (IA, insertion
sous le bloc), applyLastColor
- raccourcis : handler clavier dédié (Ctrl+J, Ctrl+⇧+H, Ctrl+⇧+M,
Ctrl+⇧+Alt+X, Alt+⇧+L, Ctrl+⇧+P, Ctrl+D sans double, Del seulement avec le
menu ouvert, Échap) — les entrées Copy/Cut/Paste du menu sont retirées
(Ctrl+C/X/V conservés)
- styles : .bm-group, .bm-foot, .bm-check, .bm-current, .bm-dot, sous-menu
60vh, recherche focalisée à l'ouverture
- tests : tests/test_v761_block_menu.py (9 tests, dont node : colonnes) +
test_v7593 adapté ; suite complète 1362 passed / 0 failed, ruff 0, eslint 0
- livraison : VERSION + app/main = 7.61.0, OpenAPI 525 chemins, CHANGELOG
- nouveau bouton .block-insert-btn émis AVANT le handle (6 points) dans le
wrapper de chaque bloc de premier niveau : clic → E.addAfter(idx) →
addAt(idx+1) → bloc inséré sous le courant et focusé (mécanisme déjà en
place, réutilisé tel quel)
- position : left:-64px (gouttière .blocks-container = 64px), -56px dans le
breakpoint où la gouttière passe à 32px ; apparaît au survol/focus comme le
handle ; masqué avec lui sur écran étroit (2 breakpoints)
- aria-label + title « Add block below »
- tests : tests/test_v760_block_insert_btn.py (2 tests : ordre dans le
wrapper, câblage E.addAfter, positions/masquages CSS)
- livraison : VERSION + app/main = 7.60.0, OpenAPI 525 chemins, CHANGELOG
- aide : bouton ✕ en haut à droite du panneau (classe partagée
settings-close, 44 px mobile) + helpInit.close() (retour en arrière,
sinon /workspaces) ; .settings-panel en position:relative pour ancrer
hamburger + ✕ au panneau
- éditeur « Insert below » (/Write with AI) : insère le markdown source
conservé à la génération (AIC._md) au lieu du texte extrait du HTML rendu
(_resultText() reste seulement en repli) → titres/listes/gras conservés
- listes numérotées : aucun numéro n'était affiché (CSS = retrait seul) ;
le rang des blocs numbered_list contigus est calculé au rendu (data-num)
et rendu via content: attr(data-num) — /numbered list et l'insertion
agent affichent 1, 2, 3
- md2b : cases à cocher - [ ] / - [x] testées avant la branche des puces
(aligné sur paste2b) + séparateur 1) accepté (CommonMark)
- menu slash : requête tapée dans le bloc (/numbered list) répercutée dans
le filtre du menu quand le focus n'a pas rejoint l'entrée
- tests : tests/test_v7592_ui_fixes.py (5 tests, dont 1 comportemental node
qui exécute le vrai md2b) ; ruff I001 préexistant sur test_agent.py
- livraison : VERSION + app/main = 7.59.2, OpenAPI 525 chemins, CHANGELOG
- page_editor_embed.html incluait seulement _page_editor_scripts.html :
window.FlowDeckDB etait undefined dans l'iframe -> une base ouverte en side
peek restait a « Loading database... ». Include _database_table_scripts.html
ajoute (meme traitement que page_editor.html).
- Le panneau #db-side-peek n'existait que dans _database_table.html : hors page
dediee, openPageInSidePeek etait un no-op. Desormais cree a la demande par
ensureDbPeek (database_table.js) ; markup unique retire du partial.
- .database-table-container : plafond 1100px retire (regle heritee de Library)
+ max-width 100% pour .db-first et database-page-title : le tableau prend la
largeur du document, plus de barre de scroll parasite sous le tableau.
- Tests: test_embed_editor_can_render_inline_databases. Suite 1265/0 (-n auto)
+ ruff 0. Bump VERSION/main.py 7.50.1, CHANGELOG, WORKLOAD, OpenAPI.
- Les 4 peeks (library.js, local_workspace.js, my_tasks.js, database_table.js)
chargent /pages/{id}?embed=1 : le panneau ne montre plus que le document en
mode edition, sans sidebar ni barre (regression v7.49.0). Le cadre du
panneau (close/full/resize partages fdWirePeekResize) est inchange.
- Calendrier My Tasks : le handler testait e.target.dataset.openRow mais les
boutons .mt-cal-open ne contiennent que leur <svg> -> clic sur l'icone =
e.target = <svg>, rien ne partait. cause racine corrigee par
t.closest('[data-open-row]') : un seul chemin tableau/calendrier/Kanban.
- Kanban My Tasks : meme bouton Open ajoute sur les tuiles (openButton(t),
overlay absolu revele au survol de la carte .mt-card).
- Menu contextuel global « Open in side peek » : etait un toast d'erreur ;
route maintenant vers le peek de la page courante (_libData.openPeekById /
_wsData.openSidePeek), repli nouvel onglet ailleurs.
- /pages/{id}?embed=1 sur une page de base (content_format=collection)
rendait un editeur de blocs vide : le template collection est garde en
embed (body.embed-mode retire sidebar+barre, le tableau reste). Bouton
pleine page des peeks de bases : navigue via dataset.page (URL propre)
au lieu du src ?embed=1.
- Tests : garde de coherence inversee (?embed=1 exige sur les 4 fichiers),
+3 nouveaux (closest, tuiles Kanban, collection embed). Fix d'un test
pre-existant casse (assertion panel.style.display, reecriture vanilla JS
v7.49.0). Suite 1264 passed / 0 failed (-n auto) + ruff 0.
- Bump VERSION/main.py 7.50.0, CHANGELOG, WORKLOAD, OpenAPI regenere.
- .db-first (page_editor_scripts._applyLayout) quand le premier bloc est une
base embarquee : .blocks-container/.page-title-block passent a margin:0 +
padding-left:var(--space-lg) au lieu de max-width:900px centre -> le tableau
demarre a x=256 (marge topbar) au lieu de x=694 sur 1920px (mesure Playwright).
- .block-embed-collection { max-width:100%; overflow:hidden } : l'embed ne
deborde plus de sa colonne (les popovers fixes restent hors du clipping).
- page_editor_collection.html : h1.database-page-title au-dessus du conteneur
(les pages collection dediees n'avaient pas de titre).
- Bump VERSION/main.py 7.49.2, CHANGELOG, WORKLOAD, OpenAPI regenere.
- Instance locale flowdeck-final rebuildee flowdeck:v7.49.2 :8081, health OK.
.database-table-container: padding 0 24px -> 0 var(--space-lg), identique a
celui de la topbar (16px) : le bord gauche du tableau s'aligne au ras du
fil d'Ariane/titre, au plus pres de la marge gauche. Bump VERSION/main.py,
CHANGELOG, WORKLOAD, OpenAPI. Instance locale flowdeck-final rebuild ee
v7.49.1, health OK, CSS verifie dans le conteneur.
- 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() —