- Scan de tous les appels `fetch` non-GET du front par préfixe : 12 préfixes
n'ont AUCUN call site sans `X-CSRF-Token` → sortis de `EXCLUDED_PATHS`
(`/db/`, `/workspace`, `/workspace/automations`, `/board/api/favorites`,
`/api/pages`, `/api/recents`, `/api/notifications`, `/api/comments`,
`/api/automations`, `/api/github`, `/api/admin`, `/api/onboarding`)
- `welcome.html` : les 2 POST `/api/onboarding/*` reçoivent le header
(`adminFetch` prouve que `/api/admin` était déjà couvert)
- reste A19 (front à équiper) : `/api/workspace` (+`/api/workspaces`),
`/api/local-workspace`, `/api/settings`, `/api/gitea`, `/api/agent` — 49 fetch
- tests : helper `anon_csrf()` (anonyme + CSRF valide) pour isoler les 401 de
route du 403 middleware — 4 tests d'anonymat ajustés
- suite **1026/1026** · `ruff check app tests` OK
- `app/templating.py` : `ENV = Environment(loader=FileSystemLoader("app/templates"), autoescape=select_autoescape(["html"]))`
- les 29 instantiations `Environment(loader=FileSystemLoader("app/templates"))` (9 routers) remplacées par `env = ENV` — plus aucune interpolation `{{ … }}` servie crue, les `|safe` redeviennent efficaces
- re-tri des `|safe` : `card_detail.html` corps d'issue et commentaires échappés (XSS stocké), placeholder de description sorti du ternaire, `sidebar_config` passé en dict + `|tojson` (échappement `</script>` en contexte script)
- `|safe` conservé sur `right_actions` (HTML fabriqué dans les templates, fiable)
- `ruff check app tests` OK · suite **1016/1016 verts**
- A3 : helper `_require_user_id()` (401 sans session) sur profile/password/token/forge ; `PUT /api/user/password` exige `current_password` vérifié ; `/api/user` sorti de la liste CSRF exemptée
- A4 : `POST /api/v1/token` → 401 sans session, chemin legacy `user_id=0` supprimé
- A5 : CRUD membres d'espace (POST/PUT/DELETE) : session + rôle admin de l'espace (ou admin global), placeholder user créé en `is_admin=0`, GET membres sans session → 401
- A6 : `_require_view` → 404 et `_require_edit` → 401 quand il n'y a pas de session (fin du legacy single-user sur les collections)
- A7 : création ET lecture de page → 401 sans session (PermissionManager conservé) ; `/board/api/pages` sorti de `EXCLUDED_PATHS` ; header CSRF manquant ajouté sur setItemIcon (local_workspace)
- A8 : seed admin sans mot de passe codé en dur — aléatoire au premier boot loggé une fois, ou `FLOWDECK_ADMIN_PASSWORD` ; re-seed seulement si absent
- tests : client connecté par défaut via `_TestSessionAuth` (session + CSRF injectés à la volée, jamais dans le cookie jar → plus de CookieConflict), helper `anon()` sur les 40 tests d'anonymat ; 1016/1016 verts, `ruff check app tests` OK
- app/db.py: ajout colonne shared_with_group_id (FK user_groups, migration
backfill v5.x) dans page_shares
- app/routers/sharing.py: POST /api/pages/{id}/share accepte group_id
(upsert, verif FK groupe), GET /shares expose kind/group_name, synchro
bidirectionnelle avec page_permissions (mirror grant/revoke) pour que
PermissionManager donne un acces effectif (view/comment/edit) aux membres
du groupe; PUT/DELETE gardent le miroir a jour
- app/routers/board.py, library.py: received/made incluent les partages via
groupes (JOIN group_members)
- app/templates/_page_editor_content.html, _page_editor_scripts.html:
dialogue Share — invite groups (fetch /api/v2/groups, filtre deja partages),
pickInviteGroup, shareInvite(group_id), rendu accessList avec avatar groupe
- tests/test_share_groups.py: 10 tests (CRUD groupe, kind, miroir ACL)
Chore: inclut evolutions v6.4.0 deja en working copy (webhooks prod,
migrations, sync, config/main) pour garder l'arbre coherent.
- Renomme var inner -> syncedInner dans le bloc synced pour lever Uncaught SyntaxError Identifier inner has already been declared
- Ce SyntaxError cassait le parsing de tout le script editor -> editorState/loadCoverIcon/etc. not defined
- Les pages clippees paraissaient vides a cause du JS casse; apres fix le rendu des blocks (bookmark/image/paragraph) fonctionne
- Rebuild Docker OK (v6.2.0, migration 19 deja appliquee)
Enregistre OpenAI, Anthropic, Mistral, Cohere, Google Gemini, Groq, DeepSeek, OpenRouter, NVIDIA NIM, Together, Perplexity, xAI, DashScope, MiniMax, Morph, Fireworks, Cerebras, SambaNova, Chutes, Xiaomi, LTX, SEA-LION, SenseNova et MemTensor (URLs de base + presets de modeles + libelles).
Settings: panneau Agent & IA repense en maitre/detail (liste rechercheable + volet de configuration) pour tenir avec des dizaines de fournisseurs, en conservant tous les boutons/fonctions.
- creation fichier/dossier a la racine du dossier courant ou d'un dossier cible (context-menu, boutons de survol) via createPage/showCreateFolderModal(parentId)
- instances de modeles en tant qu'enfant d'un dossier (parent_id) ; nom vide -> 'Untitled'
- sidebar de la librairie rafraichi apres delete/move/duplicate/rename (_syncSidebar)
- editeur: le titre se synchronise en direct dans le sidebar, le breadcrumb et l'onglet (pages et fichiers) et persiste via PUT /board/api/pages/{id}
Template-created pages (weekly report, project doc, meeting notes, to-do
list, ...) were persisted without block ids. The editor assigned ids
client-side, but the realtime room loaded the raw id-less content and sent
it back on 'sync'; applySync then merged id-less server blocks with local
blocks, producing data-bid='undefined' collisions and duplicated/shuffled
lines as soon as the user edited. Editing an empty page was unaffected
because the server state was empty.
Fixes:
- board.use_page_template: materialize unique block ids (recursively) when
instantiating built-in or user templates.
- realtime_server: unique block_id() (uuid) + ensure_block_ids() on room
load and on insert ops.
- editor: recursive ensureBlockIds() in init; gtTok() now restores
[[fddate:...]] tokens (date chips survived as labels before).
- realtime client: applySync() normalizes ids, no longer appends unknown
local blocks (duplication), and keeps local content when server is empty.
Tests: pytest (templates + realtime) and Playwright e2e covering to-do
list, weekly report date chip, Enter ordering and legacy id-less repair.
Pages created via the page-template picker (weekly report, project doc,
meeting notes, ...) previously got workspace_id=NULL and workspace=login,
so they never appeared in the active local/Gitea workspace tree — they
looked like they 'didn't save' even though the row existed.
Frontend: _useTemplate now sends the active workspace context (workspace_id
or workspace key), mirroring _createPlainPage.
Backend: use_page_template resolves/validates the workspace and persists
both workspace and workspace_id on the new page.
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.
Rocket Loader (Cloudflare) reecrit les balises script inline en type=...-text/javascript et les execute de facon differee, ce qui casse l'enregistrement des composants Alpine et l'init des pages lors d'un acces direct via le tunnel (ex. /settings ne se chargeait pas au complet). Ajout de data-cfasync=false sur les scripts inline des pages completes et des partiels du shell (settings, accounts, trash, workspace, board, gitea_workspace, welcome, import, page_editor_collection/embed, _database_table_scripts, _notification_bell, public_page, local_workspace). Les fragments charges via HTMX restent inchanges.
settingsInit est declare comme fonction globale au lieu d'etre enregistre dans alpine:init : l'evenement alpine:init ne se redeclenche pas lors d'un swap HTMX de .main-wrapper, ce qui laissait settingsInit non enregistre et provoquait des ReferenceError (llmSaving, llmTesting, llmDefaultProvider, llmMsgOk) au chargement des Reglages depuis un bouton du topbar. Le bloc du token API cree passe aussi de x-show a template x-if pour eviter le dereferencement de newToken null.
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.
Initialize coverOpen/coverLoading in editorState, render import file info with x-if instead of x-show (null deref), split x-if/x-for on the agent mention menu, guard m.steps.length and use unique keys for the icon picker. Also reduce htmx swap responses to .main-wrapper so shell scripts (agent panel, base inline) are not re-executed on partial navigation.
Intercept internal links and load only .main-wrapper via htmx.ajax, keeping the sidebar/header shell in place. Adds history pushState/popstate handling, sidebar active-state resync and document title update. Moves page script blocks inside #main-content so they run on swap, and makes editor/board scripts idempotent. Routes navigateTo/breadcrumb/library/local-workspace through window.fdNavigate and fixes the duplicated navigateTo definition.
- library: expose page tags (batch), render tag chips per row and add a tag
filter bar; register tag create/associate/remove in the shared context menu
exactly like local-workspace
- library: restore monochrome SVG icons (folder/edit/image/file) matching
local-workspace, with optional custom page_icon
- workspace: refresh tag chips/filter live on tag add/remove (reassign arrays
+ tagsVersion) instead of requiring a page reload
- ctxmenu: measure the rendered menu and clamp it to the viewport on open
- library.html: remove leftover syntax error + orphan </template></div> that
broke libraryPage() (page showed 'undefined' and no files), call the correct
fdCtx.openMenu and use self instead of a throwaway libraryPage() instance
- _ctx_menu.html: full conventional menu with shortcuts, tags + colour picker,
edit-icon picker; closes on outside click and Escape
- local_workspace.html: migrate onContextMenu to the shared fdCtx store with
complete handlers; drop legacy window._ctxMenuData DOM hacks
- dashboard.py: expose page_icon + favorited in the workspace tree
- 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