Le rebind (`app.config.settings = Settings()`) laissait tous les modules déjà
importés (sso_provisioning, trash, …) sur un objet périmé : le test
test_v67_sso::test_env_config_fallback_when_table_empty échouait dès qu'il
tournait après test_v54 dans le même worker (-n auto). Mutation sur place
comme le préconise conftest.py.
- Add HMAC SHA-256 signature verification for webhook payloads
- Implement retry logic with delays (2s, 10s, 60s) and max 4 attempts
- Add 20+ new events (total ~50 events) covering pages, blocks, users, etc.
- Add API v2 endpoints for testing HMAC signature and retrying deliveries
- Add comprehensive test suite for webhooks v2 functionality
Generated by opencode.
- 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.
Un api_base stocke (ex. https://api.mistral.ai/v2) ecrasait l'URL par defaut corrigee et faisait echouer le test de connexion. Desormais: normalisation a l'ecriture (une base egale au defaut n'est pas stockee), possibilite de vider le champ (api_base='' vs None), et migration 14 qui efface les bases obsoletes/redondantes dans user_llm_keys et llm_config.
Tests de non-regression ajoutes.
Cohere -> /compatibility/v1, Google Gemini -> /v1beta/openai, Perplexity -> host racine, Chutes -> llm.chutes.ai/v1, SenseNova -> compatible-mode/v1. Mise a jour des modeles par defaut (xAI grok-4.6, Fireworks deepseek-v4-pro-0813, Cohere command-a-plus).
Retrait de LTX Studio (aucune API chat-completions) et MemTensor/MemOS (API memoire non OpenAI-compatible, auth Token + /chat). Google utilise desormais l'auth Bearer pour lister les modeles. Test de non-regression ajoute.
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.
- test_get_redirect_uri_from_host_header: épingle oauth_redirect_uri à vide
pour tester la dérivation Host de façon isolée (le .env du projet définit
OAUTH_REDIRECT_URI, qui passe prioritaire par design)
- conftest: pose FLOWDECK_DATA_DIR vers un répertoire temporel inscriptible
pour les tests emoji/docx/covers qui dépendaient de /data (conteneur)
- ajoute scripts/audit_functional.py: audit de bout-en-bout des processus
(notes, DB, tâches, partage, publication, export, recherche, agents)
Suite locale: 538 passed
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.
tests/conftest.py: mutate the settings singleton (instead of rebinding) so DB + backup dir are isolated per test -> pytest-xdist safe.
Real backup tests (snapshot/prune/admin API) and OAuth mock tests (Gitea/GitHub/link) replace the previous skips.
init_db() now also creates webhook_subscriptions (full schema without the FastAPI lifespan).
ruff check is clean; .eslintrc.json migrated to eslint.config.mjs (flat config).
CI: lint job (ruff + eslint), parallel tests (-n auto), run on every branch push.
VERSION 5.11.1.
- 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
- Passerelle WebSocket WS /ws/pages/{page_id} (auth cookie, close 4401/4404), rooms par page en mémoire
- Protocole hello/sync/op/ack/title/sel/ping/peer_join/peer_leave ; version de page + stale => resync
- Merge des ops de blocs insert/update/delete/move, last-write-wins par bloc, ordre d'arrivée
- Client éditeur : diff local -> ops (debounce), application distante discret (LWW sur bloc focalisé), re-focus du bloc actif
- Présence (avatars topbar) + curseurs live (calque dédié, positions à l'édition/au scroll)
- Titre synchronisé (debounce) sans écraser le titre en cours d'édition
- Fallback polling 10 s si WS indisponible (adopté seulement sans brouillon local) + reconnexion auto
- Persistance debounce ~1 s (content/title) + flush à la déconnexion du dernier client
- CSP connect-src étendu à ws: ; peers sans données sensibles (id/login/full_name/couleur)
- 12 tests tests/test_realtime.py (auth, page absente, hello->sync, LWW 2 clients + persistance, présence, curseurs, titre, stale resync, apply_op/merge_ops) ; suite 298 verte (3 PDF pré-existants)
- Bump v5.3.0 (VERSION, main.py, CHANGELOG) ; ROADMAP v5.13.0 livré
Validation:
- _validate_chat_models utilise le payload de production (temperature 0.2,
sans max_tokens) : un 200 a la validation == 200 a l'usage reel.
- Race corrigee : payload reconstruit par modele (plus de noms croises entre
requetes concurrentes) — la v5.1.8 retombait par intermittence sur la liste
brute (68 modeles dont la plupart en 404/410).
- Retente une fois sur timeout/5xx (les modeles lents mais fonctionnels
survivent) ; rejette les 4xx (404 inconnu, 410 retire) ; garde les 429.
- Verifie en live contre l'API NVIDIA : 12 modeles valides au lieu de 68.
Repli runtime:
- LLMClient._http_complete : sur 404/410, retente une fois avec le modele par
defaut du provider et marque un notice dans LLMResponse.
- AgentEngine emet un evenement SSE "notice" (bandeau .fd-ap-notice dans le
panneau Agent, reset a chaque conversation) ; le modele reel est persistee.
UI:
- FAB (rond bas droite) et logo header du panneau : rond noir/blanc qui suit
le theme clair/sombre (--text-primary / --bg-primary), eclair monochrome
SVG a la place de l'emoji robot.
Tests:
- 2 nouveaux tests (429 garde / 410 retire ; repli runtime sur 410).
- 278 tests passent.
Editeur:
- sync() lisait el.textContent qui supprime les <br> : ajout de gt()
(innerText) utilise par sync(), tableaux (toggle/columns) et _resultText.
- Scission sur Entree + limites haut/bas de bloc via splitCaret() (marqueur
temporaire au curseur), plus de perte de fin de bloc multi-lignes.
Agent:
- fetch_provider_models() valide la liste nvidia : filtre des modeles
non-chat (nom) puis probe reelle /chat/completions (6 paralleles, 6s),
seuls les modeles 2xx restent. 429 conserve (route, donc utilisable).
- Repli sur la liste filtree si toutes les validations echouent.
- tests/test_llm_config.py : 5 cas (catalogue mock, 404 chat, autres
providers non valides, chute de securite, offline). 276 tests passent.
- LLMClient : default_model scope par provider — tester nvidia alors que deepseek est le provider actif ne lui envoie plus 'deepseek-v4-flash' (cause du 404 'model not found' de NVIDIA)
- presets NVIDIA actualises (anciens modeles retires de la plateforme / premium)
- test de regression ajoute (46 passed)
- /api/agent/mentions : reponse en sections (fichiers de l'espace courant ouvert, collections, pages de collections, autres documents), workspace_id transmis par le frontend (localWsId) et prioritaire
- Navigation clavier up/down : la liste defile pour garder la selection visible (menus @ et /)
- Deduplication des propositions (meme type + meme titre affiches une seule fois)
- Skills : plus d'injection du texte du skill dans la boite d'edition; requete composee en arriere-plan (contexte + skills + texte) a l'envoi; les skills integres injectent leur template, les enregistres partent via skill_ids
- Tests : shape des mentions, dedup, scoping workspace
- Les skills (menu /, integres et enregistres) s'epinglent comme des bulles dans le composer, au meme titre que les mentions @; plusieurs peuvent coexister sur un meme post
- L'API /run accepte skill_ids (tableau) et l'engine injecte tous leurs prompts dans le system prompt (skill_id conserve pour retro-compat)
- Clic sur une bulle de contexte (@ document/page/base) ouvre l'element comme document courant (/pages/<id> ou /db/<id>); le bouton x retire sans ouvrir
- Envoi possible avec des skills seuls sans texte
- test_engine_multiple_skills_applied
- L'engine envoie le message assistant avec ses tool_calls (id) et chaque resultat d'outil avec son tool_call_id, y compris en cas de refus; la passe suivante n'est plus refusee par l'API
- Un fournisseur reel configure qui echoue remonte maintenant une erreur (SSE error) au lieu de repeter la question via le mock hors-ligne (mock reserve a offline/sans cle)
- llm_client conserve id + arguments_raw des tool_calls
- test de regression test_engine_tool_protocol_messages
- Composeur contenteditable : mentions inline (pastilles icône+nom), barre de contexte groupée documents/skills, sélecteur @ (icône+titre+chemin), skills intégrés + agent_skills + commandes admin via '/'
- Actions sous chaque réponse agent : copier, insérer dans la page, feedback 👍/👎
- Backend : table agent_feedback, GET /api/agent/mentions, POST /api/agent/feedback, contexte document:<id>/page:<id>/collection:<id> résolu en contenu réel
- Inclut le travail v4.14.0 (documents/espace de travail + outils + auto-titre de conversation)