# Architecture FlowDeck — Document Complet v7.70.0 > **FlowDeck** = clone complet de **Notion** intégré nativement à **Gitea**, servi par une application **FastAPI** monolithique (SSR Jinja2 + htmx + Alpine.js), avec **API REST publique v2**, **agent IA conversationnel**, **automatisations**, **SSO entreprise** et **édition collaborative temps réel**. > > Document d'architecture à jour de la version **7.70.0** (`VERSION`). Historique complet : voir §17 et `CHANGELOG.md`. Modèle de données détaillé table par table : `docs/DATA_MODEL.md`. ## Table des matières 1. [Vue d'ensemble](#1-vue-densemble) 2. [Architecture système](#2-architecture-système) 3. [Structure du backend](#3-structure-du-backend) 4. [Routage & API](#4-routage--api) 5. [Modèle de données](#5-modèle-de-données) 6. [Authentification & comptes](#6-authentification--comptes) 7. [SSO entreprise : SAML, OIDC, SCIM, 2FA, WebAuthn](#7-sso-entreprise-saml-oidc-scim-2fa-webauthn) 8. [Sécurité applicative](#8-sécurité-applicative) 9. [Autorisation : rôles, permissions granulaires, gouvernance](#9-autorisation-rôles-permissions-granulaires-gouvernance) 10. [Workspaces & collaboration multi-utilisateurs](#10-workspaces--collaboration-multi-utilisateurs) 11. [Databases : propriétés, formules, rollups, dépendances](#11-databases-propriétés-formules-rollups-dépendances) 12. [Système de vues](#12-système-de-vues) 13. [Pages, éditeur de blocs & temps réel](#13-pages-éditeur-de-blocs--temps-réel) 14. [Wiki, liens, blocs synchronisés & teamspaces](#14-wiki-liens-blocs-synchronisés--teamspaces) 15. [Partage, publication, Library, My Tasks & Trash](#15-partage-publication-library-my-tasks--trash) 16. [IA : agent, compétences, connecteurs & écriture](#16-ia-agent-compétences-connecteurs--écriture) 17. [Recherche : FTS5, sémantique hybride & Ask AI](#17-recherche-fts5-sémantique-hybride--ask-ai) 18. [AI Meeting Notes & calendrier](#18-ai-meeting-notes--calendrier) 19. [Automatisations, workers, rappels, notifications & webhooks](#19-automatisations-workers-rappels-notifications--webhooks) 20. [Intégration Gitea / GitHub](#20-intégration-gitea--github) 21. [Import & Export](#21-import--export) 22. [Frontend : rendu, CSP, PWA & Web Clipper](#22-frontend-rendu-csp-pwa--web-clipper) 23. [Déploiement & exploitation](#23-déploiement--exploitation) 24. [Outils de développement, tests & CI](#24-outils-de-développement-tests--ci) 25. [Historique des versions v1.0 → v7.70.0](#25-historique-des-versions-v10--v7700) 26. [Chantiers ouverts & suites](#26-chantiers-ouverts--suites) 27. [Références](#27-références) --- ## 1. Vue d'ensemble FlowDeck est un **produit unique en un serveur** : une application FastAPI (Python 3.13) qui rend des pages HTML complètes (Jinja2), interagit via htmx et Alpine.js, persiste tout dans **une base SQLite unique** (mode WAL), et se branche sur un **Gitea externe** (OAuth2, API, webhooks) comme forge optionnel — tout en restant utilisable seul (comptes locaux, workspaces locaux, GitHub en option). ### Principes architecturaux | Principe | Application | |---|---| | **Monolithe lisible** | Un seul processus uvicorn : routage, rendu, services, scheduler d'arrière-plan dans le `lifespan` FastAPI. Pas de worker Celery, pas de file externe. | | **SSR d'abord** | Les pages sont rendues serveur ; JS = hydratation (Alpine) + interactions ciblées (htmx). Navigation partielle maison (`static/js/app.js`) sans rechargement du shell. | | **SQLite + JSON document-oriented** | Pas d'ORM : SQL brut via `get_conn()`, ~35 colonnes `*_json` portent les structures riches (blocs, configs de vues, schémas de propriétés, tool calls). | | **Zéro bundler front** | Aucun pipeline JS : les libs (Alpine build CSP, htmx, KaTeX, highlight.js, Chart.js, Leaflet, SortableJS) sont **vendorisées** dans `static/js/` ; CSP stricte par nonce, `unsafe-eval` banni (audit A20, achevé v7.43). | | **Sécurité vérifiée par audit** | 43 items d'audit (A1–A43, `ROADMAP.md`) traités v7.3→v7.35 : 401 partout, CSRF unifié, CSP sans eval, SSRF, autoescape, rate limiting, SQLite hors event loop. | | **Multi-tenant par workspace** | Isolation des données par `workspace_id`, rôles par workspace + ACL granulaires page/collection/propriété, moindre privilège par défaut. | | **IA au service du produit** | Agent ReAct, AI Writing, Ask AI, Meeting Notes : tous branchés sur un client LLM unique à 23 fournisseurs + mode offline déterministe ; l'agent n'a **jamais plus de droits que l'utilisateur appelant**. | | **Compat Gitea historique** | Boards Kanban legacy ↔ Collections Notion-like via un adaptateur (`GiteaBoardCompat`) ; le miroir dépôt/page privée coexiste avec le workspace local. | ### Stack technique - **Backend** : Python 3.13, FastAPI 0.115, uvicorn[standard] 0.34, Jinja2, httpx, pydantic v2 + pydantic-settings, itsdangerous (sessions signées), cryptography (Fernet), python3-saml, authlib, pyotp, webauthn, WeasyPrint/xhtml2pdf (PDF), openpyxl/python-docx/pypdf/BeautifulSoup/PyYAML (imports). - **Frontend** : htmx 2.0.4, `@alpinejs/csp` 3.17.4, SortableJS, Chart.js, Leaflet, KaTeX, highlight.js — tous self-hosted. - **Persistance** : SQLite (WAL, FTS5), migrations versionnées maison (baseline v1 + 38 migrations). - **Empaquetage** : Docker multi-stage Python (pas de stage node), verrouillage des dépendances avec **uv** (`uv.lock`). --- ## 2. Architecture système ```mermaid graph TD B[Navigateur / PWA + service worker] -->|HTML, htmx, fetch nonce-CSP| W[uvicorn :8080 FastAPI] E[Extension Web Clipper MV3] -->|Bearer fd_ token| A2 X[Clients tiers / agents externes] -->|Bearer + scopes| A2[API publique /api/v2 + OpenAPI /docs] W --> SSO[SSO : SAML / OIDC / SCIM] W --> AG[Agent IA / LLM client] AG --> P23[23 fournisseurs LLM + mode offline] W --> GT[Gitea externe OAuth2, API, webhooks] W --> GH[GitHub API] W --> DB[(SQLite WAL /data/flowdeck.db + FTS5)] W --> FS[/data uploads avatars backups] W --> WS[WebSocket /ws/pages/id temps réel] W --> OUT[Webhooks sortants HMAC] SCH[lifespan : 9 schedulers asyncio] --> DB ``` ### 2.1 Cycle de vie applicatif (`app/main.py`) Le `lifespan` FastAPI orchestre, au démarrage : 1. `init_db()` (`app/db.py`) — baseline idempotent + `apply_migrations()` (`app/migrations.py`, version courante **38**) ; 2. démarrage des **schedulers asyncio** lancés via `_spawn(name, coro)` — un wrapper `A34` qui journalise toute exception et redémarre le scheduler au bout de 10 s : | Scheduler | Service | Rôle | |---|---|---| | `agent_scheduler` | `app/routers/agent.py` | déclencheurs `schedule` des agents (60 s) | | `automation_scheduler` | `app/services/automations.py` | crons + retries des automatisations, mutualisé avec `run_due_workers()` | | `backup_scheduler` | `app/services/backup.py` | snapshot quotidien SQLite (checkpoint WAL avant copie) | | `project_sync_scheduler` | `app/services/projects.py` | synchronisation du registre `projects` avec la forge | | `trash_purge_scheduler` | `app/services/trash.py` | purge de la corbeille | | `reminder_scheduler` | `app/services/reminders.py` | scan des rappels (60 s) | | `semantic_index_scheduler` | `app/services/semantic_search.py` | indexation incrémentale des embeddings (300 s) | | `calendar_sync_scheduler` | `app/services/calendar_sync.py` | resync bidirectionnelle Google/CalDAV | | `webhook_retry_scheduler` | `app/services/webhook_outbound.py` | ledger de retry des webhooks sortants (condé par `WEBHOOK_RETRY_ENABLED`) | 3. arrêt gracieux de ces tâches au shutdown. ### 2.2 Chaîne de middleware (ordre d'exécution, requête entrante) Ajoutées dans `app/main.py` dans l'ordre inverse de l'exécution (Starlette : la dernière ajoutée tourne en premier) : ``` Requête → CORS (A37, origines fermées + regex localhost/extension) → RateLimitMiddleware (fenêtre glissante par IP, config à chaud A33) → ContentSecurityPolicyMiddleware (nonce par requête, script-src fermé) → CSRFMiddleware (double soumission cookie→header, exemptions machine-to-machine) → SessionMiddleware (1 h, stockage transitoire état OAuth `state` uniquement) → route ``` La **vraie session applicative** n'est pas la SessionMiddleware : c'est la cookie signée `flowdeck_session` résolue par `app/auth/session.py` (§6.4). ### 2.3 Conventions de rendu - Deux modes de réponse selon la surface : **HTML SSR** (pages, fragments htmx) ou **JSON** (API internes `/api/…` cookie-auth et API publiques `/api/v1`, `/api/v2` Bearer). - Les erreurs API v2 suivent **RFC 7807** ; réponses standardisées `SuccessResponse {status:"ok", data}` / `ErrorResponse` (`app/models/responses.py`). - `app/templating.py` : Jinja2 avec **`select_autoescape(["html"])`** (audit A10), globals `fd_icon`, `csrf_token`, `csp_nonce()`, `app_base_url()`. --- ## 3. Structure du backend ``` app/ ├── main.py # FastAPI, middlewares, include_router ×40, lifespan/schedulers ├── config.py # pydantic-settings (.env), db_path, secrets, feature flags ├── db.py # baseline schema (105 tables au total avec migrations), get_conn() ├── migrations.py # @register(version) + schema_version, 1 migration = 1 transaction ├── templating.py # Jinja2 autoescape + globals ├── password_utils.py # hash SHA-256 + sel, validation force ├── auth/ # oauth.py, session.py, providers/ (oidc, saml) ├── middleware/ # csrf.py, security.py (CSP, rate limit, uploads) ├── models/ # requests.py / responses.py (Pydantic, partiel) ├── schemas/ # schémas API ├── templates/ # ~35 gabarits + fragments _*.html (§22) ├── routers/ # surface HTTP (§4) — découplés en packages par l'audit A28 │ ├── api_v2/ # API publique v2 : collections, properties, views, projects, │ │ # planning, engagement, sharing, identity, workspaces, │ │ # templates_io, webhooks, admin │ ├── board/ # Kanban Gitea + pages legacy + wiki + synced + embed + import │ ├── collections/ # Databases Notion-like (/db) : crud, properties, views, │ │ # data_api, pages, linked, meta, boards, task_db, dashboards │ ├── dashboard/ # Pages : local_workspace, workspaces, account_*, pages_*, │ │ # public, settings │ └── *.py # auth, sso, scim, webauthn, security, permissions, governance, │ # audit, admin, agent, api_v2_agent, meetings, automations, │ # workers, notifications, realtime, search, search_ai, sites, │ # library, my_tasks, notes, projects, gitea, github_routes, │ # workspace, sharing, sidebar_config, export, imports, │ # web_clipper, sync, emoji, onboarding, wiki, collaboration, │ # public_api, api, webhooks └── services/ # logique métier (~80 modules, §11–21) ``` **Pattern** : les *routers* font l'authentification, la validation d'entrée et le rendu ; les *services* portent la logique (accès données, moteur de règles, intégrations). Les quatre plus gros domaines de routes ont été restructurés en **packages** (`api_v2`, `board`, `collections`, `dashboard`) par l'audit **A28** ; chaque package a un `_common.py` (dépendances partagées) et un `__init__.py` qui agrége ses sous-routeurs par scan de modules. --- ## 4. Routage & API Carte des surfaces HTTP par préfixe (tous les routeurs sont déclarés dans `app/main.py`) : | Préfixe | Fichiers | Nature | Contenu | |---|---|---|---| | `/` | `dashboard/` (pas de préfixe) | SSR | Pages : accueil/dashboard, `/library`, `/workspace…`, `/settings`, `/trash`, `/help`, `/welcome`, `/onboarding`, `accounts`, page éditeur `/pages/{id}` (+ variantes collection/embed), workspaces Gitea | | `/board` | `board/` (13 modules) | SSR + API | Kanban Gitea (`/board/{owner}/{repo}/view/{kanban|detailed|table|status|teamload}`), pages legacy, médias, partage, sync, wiki, embed, import, `page-templates` | | `/db` | `collections/` (13 modules) | SSR + API | CRUD bases, propriétés, vues, data API des lignes, pages de ligne, linked databases, meta, boards synchronisés, dashboards, conversion `task-db` | | `/my-tasks` | `my_tasks.py` | SSR + `/my-tasks/api` | Dashboard transverses des tâches assignées (3 vues) | | `/notes` | `notes.py` | SSR | Notes markdown par projet Gitea | | `/workspace` | `workspace.py` | SSR + JSON | Workspaces locaux, arborescence, upload ; `public_router` exempté d'auth pour le partage | | `/api` | `api.py`, `collaboration.py`, `sharing.py`, `security.py`, `sidebar_config.py`, `admin.py`, `agent.py`, `search.py`, `notifications.py`, `projects.py`, `gitea.py`, `github_routes.py`, `library.py`, `import.py`, `export.py`, `emoji.py`, `realtime.py`, `onboarding.py` | JSON cookie-auth | API internes du front : `/api/search`, `/api/settings/*` (tokens, sessions), `/api/agent/*` (panneau IA), `/api/nav/menu`, `/api/pages/{id}/comments|reactions|mentions|backlinks`, `/api/export/*`, `/api/health` | | `/api/v1` | `public_api.py` | JSON Bearer | API publique v1 en **lecture seule** (compat) | | `/api/v2` | `api_v2/` (14 modules), `permissions.py`, `api_v2_agent.py`, `sync.py`, `web_clipper.py` | JSON Bearer | **API publique v2 complète** : CRUD tous domaines + scopes, `/api/v2/agents/*` (run synchrone, rollback), `/api/v2/skills/*`, `/api/v2/search/{ask,hybrid}`, `/api/v2/sync/{batch,delta}`, `/api/v2/web-clipper/clip`, `/api/v2/sso/*`, `/api/v2/scim/tokens`, `/api/v2/audit/logs`, `/api/v2/groups`, `/api/v2/users`, OpenAPI `/docs` + `docs/openapi-v2.json` | | `/api/webhook` | `webhooks.py` | JSON | **Webhooks entrants** Gitea (secret partagé, exemption CSRF) | | `/auth` | `auth.py`, `webauthn.py`, `sso.py`, `scim.py` (en partie) | HTML/JSON | Login local/OAuth (`/auth/login`, `/auth/callback`), `/auth/2fa/*`, `/auth/webauthn/*`, `/auth/saml/*`, `/auth/oidc/*`, `/auth/logout` | | `/scim/v2` | `scim.py` | JSON Bearer | Provisioning SCIM 2.0 (`/scim/v2/Users` GET/POST/PUT/PATCH/DELETE) | | `/s/` | `sites.py` | HTML | **Sites publics** multi-pages (mot de passe, expiration, stats) | | `/f/` | `sites.py` | HTML/POST | **Formulaires publics** anonymes → collection | | `/g/` | `wiki.py` | HTML | Accès **guest** à une page sans compte | | `/wiki` | `wiki.py` | JSON/SSR | Teamspaces, vérification, follows | | `/api/v2/meetings` | `meetings.py` | JSON | AI Meeting Notes (upload audio, statut, résumé) | | `/api/v2/workers` | `workers.py` | JSON | Workers Python sandboxés (cron/partage/fork) | | `/workspace/automations` | `automations.py` | SSR/JSON | Éditeur visuel d'automations (steps) | | `/static` | `StaticFiles("static")` | fichiers | JS/CSS vendorisés, manifest.json, sw.js, icônes | ### 4.1 API publique v2 — contrat - **Auth** : Bearer `fd_` (scopes hiérarchiques `read < write < admin`) **ou** session admin pour les routes sensibles (CSRF alors contrôlé *dans* le routeur, `/api/v2` étant exempté par la middleware). - **Pagination / filtres / tri** systématiques, **erreurs RFC 7807**, **idempotence** par en-tête `Idempotency-Key` (table `idempotency_keys`, réponse rejouée), **rate limiting par jeton** (`API_V2_RATE_LIMIT_PER_TOKEN`), **audit** systématique (`api_audit_log`). - **Ouverture** : OpenAPI servi sur `/docs` ; schéma exporté dans `docs/openapi-v2.json` ; guide complet `docs/API_GUIDE_V6.md`. - **Sync offline** : `POST /api/v2/sync/batch` (rejeu des mutations PWA) + `GET /api/v2/sync/delta` (pull par `sync_version`) — backend `app/routers/sync.py`. ### 4.2 Plugins à effet réel sur le routage Le registre `plugins` (`app/services/plugins.py`, v7.58) coupe **de vraies routes** quand un module est désactivé : `include_router(..., dependencies=[Depends(plugin_required("automations"))])` ou `("web-clipper")` dans `app/main.py` → toutes les routes du router renvoient 404, et le scheduler associé est arrêté. Les outils `web-tools` éteints retirent leurs outils du registre de l'agent (`PLUGIN_TOOLS`). --- ## 5. Modèle de données > Référence exhaustive table par table (colonnes, FK, fichiers, numéros de ligne) : **`docs/DATA_MODEL.md`**. Cette section en donne la synthèse architecturale. ### 5.1 Moteur : SQLite uniquement Contrairement à certaines notes anciennes, **FlowDeck ne supporte pas PostgreSQL**. `app/db.py` est du `sqlite3` stdlib typé `sqlite3.Connection`, sans ORM ni SQLAlchemy : - une base par instance : `settings.db_path` (par défaut `sqlite:///…/flowdeck.db` sous `/data` en Docker) ; - `PRAGMA journal_mode=WAL` (un writer, multi-readers), `foreign_keys=ON`, `busy_timeout=5000` pour la concurrence (tests xdist, schedulers) ; - `get_conn()` = context-manager avec `row_factory = sqlite3.Row` ; les conversions dict↔JSON des colonnes `*_json` sont faites à la main dans les services ; - `DATABASE_URL` n'est interprété que pour le préfixe `sqlite:///` (gestion des chemins Windows, bug UNC corrigé par l'audit A26) ; - l'audit **A21** a sorti les accès SQLite bloquants de l'event loop (migration des ~510 call sites, achevée pour api_v2 et les routes principales v7.9–v7.26). ### 5.2 Migrations versionnées (`app/migrations.py`) Alternative assumée à Alembic : | Composant | Rôle | |---|---| | `init_db()` (`app/db.py`) | baseline « version 1 » : `CREATE TABLE IF NOT EXISTS` + ALTER `try/except OperationalError`, réexécutée à chaque boot, idempotente | | `schema_version` | registre `(version, name, applied_at)` ; version max = version courante (actuellement **38**) | | `@register(version, name)` | décorateur d'enregistrement, tri par version, doublons refusés | | `apply_migrations()` | appelée en fin de `init_db()` ; applique exactement une fois chaque migration > version courante | | `_apply_one()` | **1 migration = 1 transaction** (DDL tout-ou-rien, audit A31) | | `fts5_available()` | détection FTS5, repli recherche `LIKE` sinon | ### 5.3 Inventaire : 105 tables + 1 table virtuelle, par domaine | Domaine | Tables principales | Points notables | |---|---|---| | **Kanban & forge** (v0.x–v1.x) | `boards`, `cards`, `col_mapping`, `notes`, `checklists(_items)`, `project_properties`, `property_values`, `ai_keywords`, `gitea_private_pages`, `user_tokens`, `projects` | `projects` = registre forge-agnostique (gitea/github/builtin) synchronisé par cron | | **Auth & identités** | `users`, `login_history`, `user_oauth_tokens`, `user_sessions`, `api_tokens`, `webauthn_credentials`, `scim_tokens`, `domain_claims`, `sso_config`, `sso_login_history`, `sso_requests` | `users.auth_method` : local / gitea / github / saml / oidc ; secrets chiffrés Fernet | | **LLM** | `llm_config` (ligne unique `CHECK(id=1)`), `user_llm_keys` | clés API stockées serveur, jamais exposées en clair | | **Workspaces & permissions** | `workspaces`, `workspace_members`, `teamspaces(_members)`, `user_groups`, `group_members`, `page/collection/property_permissions`, `permission_audit_log`, `page_shares`, `guest_shares` | ACL : grant sur user **XOR** group (`CHECK`) | | **Collaboration** | `favorites`, `recents`, `notifications`, `comments`, `comment_reactions`, `text_reactions`, `page_follows`, `page_verifications`, `tags`, `page_tags`, `custom_emojis` | `comments` cible polymorphe (`page` ou `collection_page`) + ancre texte `anchor_start/end` (v7.64) | | **Pages & éditeur** | `pages`, `page_versions`, `page_history`, `page_global_templates`, `synced_blocks`, `page_synced_blocks`, `page_views`, **`pages_fts`** (virtuelle FTS5) | `pages.content` = markdown legacy **ou** JSON de blocs selon `content_format` ; page-ombre de ligne via `pages.collection_row_id` (v6.5) ; trigger `sync_version` | | **Collections (bases)** | `collections`, `collection_pages` (lignes), `collection_views`, `collection_properties`, `collection_data_sources`, `collection_dashboards`, `database_templates`, `page_templates`, `page_dependencies`, `sprints`, `sprint_pages`, `reminder_log` | `collections.schema_json` + `collection_pages.property_values_json` + `collection_views.config_json` = le trio documentaire du système de bases | | **Automatisation** | `automations`, `automation_steps`, `automation_runs`, `workers`, `worker_runs`, `webhook_subscriptions` (créée hors migrations par `webhook_outbound.py`), `webhook_deliveries`, `idempotency_keys` | steps = chaîne trigger/condition/delay/action (v7.0) | | **Agent IA** | `agents`, `agent_conversations`, `agent_messages`, `agent_actions`, `agent_skills`, `agent_triggers`, `agent_feedback`, `agent_policies`, `agent_approvals`, `agent_memory`, `agent_connectors`, `connector_tokens`, `plugins`, `semantic_embeddings`, `semantic_index_state` | `agent_actions.undo_snapshot_json` = journal annulable ; embeddings = vecteurs hashed-TF 256-dims en BLOB | | **Sites & formulaires** (v6.8) | `sites`, `site_pages`, `site_views`, `form_responses` | RGPD : compteurs sans IP brute, `ip_hash` tournant | | **Calendrier & réunions** (v7.1) | `calendar_links`, `meeting_transcripts`, `meeting_consent_log`, `meeting_distributions` | tokens OAuth calendrier chiffrés Fernet | | **Import / offline / clipper** | `import_jobs`, `import_items`, `offline_sync_queue`, `extension_devices`, `extension_clips` | re-import idempotent par `(workspace, source, external_id)` | | **API & audit** | `api_audit_log` | écriture « best-effort » : un échec d'audit ne casse jamais le chemin métier | ### 5.4 Relations clés ```mermaid graph TD USERS[users] WS[workspaces] --> USERS PAGES[pages] --> WS PAGES -->|parent_id self| PAGES COLS[collections] --> WS CP[collection_pages lignes] --> COLS CP -->|parent_id sub-items| CP PAGES -->|collection_row_id page-ombre| CP CV[collection_views] --> COLS CPV[collection_properties] --> COLS CPV -->|relation| COLS CPV -->|button_automation_id| AUTO[automations] AG[agents] --> WS CONV[agent_conversations] --> AG MSG[agent_messages + actions] --> CONV GRP[user_groups] --> WS ACL[page/collection/property_permissions] --> USERS ACL --> GRP SITES[sites] --> PAGES TRANS[meeting_transcripts] --> PAGES CAL[calendar_links] --> COLS ``` À retenir : 1. **`users` = hub d'identité** (30+ FK entrantes), **`workspaces` = hub d'isolation multi-tenant**. 2. **Deux univers de « pages »** : `pages` (éditeur wiki, soft-delete `deleted_at`, corbeille) et `collection_pages` (lignes de base). Le pont v6.5 : chaque ligne peut porter une **page-ombre** (`pages.collection_row_id`, CASCADE) qui héberge son contenu blocs — versions, synced blocks et temps réel opèrent sur elle. 3. **Chaînes de cascades** nettoyant l'éditorial ; les tables d'audit privilégient `SET NULL` sur l'acteur pour survivre à la suppression d'un compte. 4. **Sync Gitea par ID numérique** (`gitea_issue_id`, `gitea_owner/repo`) : jointures sans FK, système externe. 5. **Polymorphisme applicatif** : `notifications.resource_type/id`, `semantic_embeddings.resource_type/id`, `automations.collection_id` (TEXT) — cohérence maintenue par le code, pas par le moteur. ### 5.5 Couche Pydantic `app/models/requests.py` / `responses.py` ne couvrent qu'une petite partie du contrat (FileSaveRequest, Issue{Create,Update}Request, CardMoveRequest, ColMappingRequest, enveloppes `SuccessResponse`/`ErrorResponse`) ; la majorité des handlers valide en dur. --- ## 6. Authentification & comptes ### 6.1 Types de comptes Tous les comptes vivent dans la table unique `users` (colonne `auth_method`, défaut `local`) : | Type | `auth_method` | Particularités | |---|---|---| | **Local** | `local` | `password_hash` (SHA-256 + sel, `app/password_utils.py`), min. 6 caractères ; le **premier inscrit local devient admin** | | **OAuth forge** | `gitea` / `github` | login préfixé `{provider}_{login}` (ex. `gitea_alice`), upsert à chaque callback, token stocké dans `user_oauth_tokens` | | **SSO provisioned** | `saml` / `oidc` | créé par `handle_sso_login()` si `auto_provision=1` ; **fusion par email** si le compte existe déjà | | **SCIM** | `saml` | provisionné par `POST /scim/v2/Users` ; désactivation = `is_active=0` + révocation des sessions (jamais de suppression destructrice) | | **Admin** | tout type | `is_admin=1` ; sur instance `sso_only`, seuls les admins conservent l'accès local | | **Agent** | — | pas de compte de service dédié : l'agent agit **au plus avec les permissions de l'utilisateur appelant** ; la gouvernance passe par `agent_policies`/`agent_approvals` (§9.3) | Un utilisateur sans rôle explicite retombe sur le rôle implicite **`viewer`** (`role_in_workspace()` dans `app/routers/dashboard/_common.py`). ### 6.2 Flux OAuth2 Gitea (+ GitHub) - Providers déclarés dans `app/auth/providers/__init__.py` (`GiteaProvider`, `GitHubProvider`, registre `get_provider()`) + singleton legacy `GiteaOAuth` (`app/auth/oauth.py`). Un provider n'est « configuré » que si ses identifiants ne sont pas des placeholders (durcissement d'audit). - `GET /auth/login?provider=gitea|github|local` → `{forge}/login/oauth/authorize?client_id&redirect_uri&response_type=code&state` ; `state` = `secrets.token_hex(32)` stocké dans la SessionMiddleware (1 h), avec le mode `link` (rattacher OAuth à un compte local depuis Réglages → Intégrations) encodé dans l'état. - `redirect_uri` : override `OAUTH_REDIRECT_URI` sinon dérivé de la requête (`X-Forwarded-Proto/Host`), réutilisé tel quel au callback. - `GET /auth/callback?code&state` : échange du code, profil via `GET {forge}/api/v1/user`, upsert `users`, stockage du token, émission de la cookie `flowdeck_session`, redirection `/workspaces`. ### 6.3 Comportement du sidebar selon le compte La cookie `flowdeck_workspace` porte soit un id numérique (workspace local), soit `gitea:/` — auquel cas la sidebar affiche **l'arbre du dépôt Gitea *et* le miroir local du même nom en parallèle** (comportement conservé depuis v4.0, sections Home : Workspace / Repository / Meetings / Recents / Favorites / Agents / Teamspaces / Shared / Published / Private — voir §22.1). ### 6.4 Sessions (`app/auth/session.py`, v5.2.0) - Cookie `flowdeck_session` : payload `{user, created_at, sid}` **signé** (`itsdangerous.URLSafeTimedSerializer`, `APP_SECRET_KEY`), non chiffré → `public_user()` strippe `password_hash` avant toute sortie ; - `sid` = une ligne `user_sessions` (ip, user-agent, `revoked`, `last_seen_at`) : **révocation immédiate par session**, visible et gérable dans Réglages (`GET /api/settings/sessions`, `POST …/revoke`) ; - expiration **7 jours** (cookie + signature cohérentes) ; contrôle `revoked` à chaque décodage, **fail-open** si la table est inaccessible (compat vieilles installations) ; - verrouillage : 5 échecs → 15 min (`locked_until`, HTTP 423) ; `is_active=0` → 403 ; - historique dans `login_history` ; déconnexion SSO → relais SLO si `_sso_name_id` présent. ### 6.5 Jetons API Bearer - `POST /api/settings/tokens` (`app/routers/security.py`) crée `fd_`, affiché **une seule fois**, stocké **hashé SHA-256** (`api_tokens.token_hash`, `token_prefix` pour l'affichage) ; - **scopes hiérarchiques** `read < write < admin` (`SCOPE_RANK`, `app/services/api_v2_helpers.py`) ; factory `require_scope()` (audit A30) ; - `resolve_bearer_token()` accepte 3 sources : `api_tokens`, `extension_devices` (Web Clipper), `user_tokens` legacy (token Gitea en clair, portée implicite read+write) ; - fallback dev `fd-public-key` uniquement si `PUBLIC_API_INSECURE_OK=true`. --- ## 7. SSO entreprise : SAML, OIDC, SCIM, 2FA, WebAuthn (v6.7.0 SSO ; v7.2.0 SCIM/2FA/passkeys — doc `docs/V6_SSO_SAML_Enterprise_Auth.md`, `docs/V72_Enterprise_SCIM_2FA.md`) ### 7.1 SAML 2.0 (`app/auth/providers/saml_provider.py`, `app/routers/sso.py`) SP basé `python3-saml` (OneLogin), mode strict : - `GET /auth/saml/login?next=…` (AuthnRequest HTTP-Redirect, `RelayState = .`) ; `POST /auth/saml/callback` (ACS, exempté CSRF car POST cross-site mais protégé par le jeton single-use) ; `GET /auth/saml/metadata` (XML à coller dans l'IdP) ; `GET|POST /auth/saml/logout` (SLO). - Validations : signature d'assertion (`wantAssertionsSigned`, RSA-SHA256), schéma XML, Conditions/Audience/Destination/Issuer/Status, `InResponseTo`, rejet des algorithmes dépréciés. - Clé privée SP générée à la demande et **chiffrée au repos (Fernet)** dérivée de `APP_SECRET_KEY`. ### 7.2 OpenID Connect (`app/auth/providers/oidc_provider.py`) Flow **authorization code + PKCE (S256)**, client confidentiel : découverte `.well-known/openid-configuration` (cache 1 h), `GET /auth/oidc/login` → `GET|POST /auth/oidc/callback` (form_post supporté). Vérification ID token contre la JWKS (sélection par `kid`) + contrôles explicites `iss/aud/exp/iat` (dérive 300 s), `nonce`, `sub`. Enrichissement best-effort via `userinfo_endpoint` (source habituelle des groupes). Logout : révocation locale + `end_session_endpoint` si présent. ### 7.3 Provisioning & config (`app/services/sso_provisioning.py`) - Table `sso_config` (une ligne active) : type, mapping d'attributs, `groups_mapping`, `auto_provision`, `sso_only`, `default_workspace_id` ; la table prime sur les variables d'env `SSO_*` (fallback bootstrap) ; secrets chiffrés Fernet, jamais rendus par `GET /api/v2/sso/config` ; - `handle_sso_login()` : résolution par **email (fusion)** puis **login** ; création si `auto_provision`, sinon rejet ; - `sync_sso_groups()` : mapping groupe IdP → rôle `workspace_members` (owner/admin/editor/viewer), réappliqué à chaque login ; `POST /api/v2/sso/sync` force le re-sync global ; - **anti-replay** : table `sso_requests` (TTL 600 s, consommation atomique) ; anti open-redirect : `safe_next_path()` n'accepte que `/chemin` ; - `sso_only` : refuse login/enregistrement local pour les non-admins ; les **domaines vérifiés** `enforce_sso=1` (`domain_claims`) imposent le SSO aux comptes de ce domaine email ; - chaque login/refus écrit `sso_login_history` ; rate limit dédiée 5 tentatives/min/IP ; API admin `GET /api/v2/sso/providers` (boutons de la page de login). ### 7.4 SCIM 2.0 (`app/routers/scim.py`) `/scim/v2/Users` (GET liste, POST create, GET/PUT/PATCH/DELETE). Auth Bearer contre `scim_tokens.token_hash` (SHA-256, `revoked=0`). `active=false` ou DELETE = **suspension** (`is_active=0`) + révocation des sessions — contenu et audit préservés. Gestion des jetons : `POST|GET/DELETE /api/v2/scim/tokens` (préfixe `scim_`, valeur affichée une seule fois). ### 7.5 2FA TOTP (`app/services/two_factor.py`) `POST /auth/2fa/setup` → secret `pyotp` + `otpauth://` (inactif tant que non vérifié) ; `/2fa/activate` (fenêtre ±1 pas) → `users.totp_secret_enc` chiffré Fernet + **10 backup codes** à usage unique (hashés) ; `/2fa/status`, `/2fa/disable`. Parcours login : `/auth/local-login` renvoie `{status:"2fa_required", pending}` (jeton signé 5 min, salt `totp-pending`), puis `/auth/local-verify` échange pending + code contre la cookie de session — **aucune session avant la seconde facteur**. ### 7.6 WebAuthn / passkeys (`app/routers/webauthn.py`) `/auth/webauthn/register/begin|finish` (session requise, `excludeCredentials`), `/auth/webauthn/login/begin|finish` (**passwordless**), `GET /auth/webauthn/keys`, `DELETE …/keys/{id}`. Challenges en mémoire (TTL 300 s — déploiement mono-processus assumé, comme pour les rooms WebSocket) ; `rp_id` dérivé de l'hôte de la requête ; table `webauthn_credentials`, `sign_count` entretenu. --- ## 8. Sécurité applicative Récapitulatif des garde-fous (la grande majorité provient de l'audit sécurité v7.3→v7.35, A1–A43). ### 8.1 CSRF (`app/middleware/csrf.py`) Double soumission : cookie `csrf_token` (JS-readable, samesite=lax, 1 j) validée par `secrets.compare_digest` contre l'en-tête `X-CSRF-Token` sur POST/PUT/PATCH/DELETE. **A19 (v7.33)** : plus aucun préfixe cookie-auth exempté (46 sites du front équipés en v7.3.6) ; restent exemptés uniquement le machine-to-machine (`/api/webhook`, `/api/v1`, `/api/v2`, `/scim/v2`), les callbacks d'authentification (`/auth/*`), le public (`/s/`, `/f/`) et l'infrastructure (`/api/csrf-token`, `/api/frontend-error`). Compensations : jeton single-use `sso_requests`, validation complète assertion/ID token, et **contrôle CSRF dans le routeur** pour les routes `/api/v2` sensibles acceptant une session (mix session+Bearer). Le front porte le jeton via `` (htmx) et `fetch` manuel. ### 8.2 CSP (`app/middleware/security.py`) — audit A20 `script-src 'self' 'nonce-…'` — **ni `unsafe-inline` ni `unsafe-eval`** (achevé v7.43) : - nonce par requête exposé aux templates via la ContextVar `CSP_NONCE` **avant** `call_next` (A43), global Jinja `csp_nonce()` ; - `script-src-attr 'unsafe-inline'` **détaché** pour couvrir les ~74 handlers `onclick=` Alpine ; - htmx neutralisé : `` (v7.37) ; - Alpine servi en **build officiel `@alpinejs/csp`** (0 `eval`/`new Function`) ; - `connect-src 'self' ws://… wss://…` fermé (A20 phase 2 : CDN retiré, vendors localisés — test `test_csp_no_cdn_and_vendor`), `object-src 'none'`, `base-uri 'self'`, `form-action 'self'`. ### 8.3 Rate limiting & CORS - fenêtre glissante in-memory par IP sur `/api/`, `/board/api/`, `/auth/`, `/scim/v2/`, `/workspace/`, `/db/` ; `RATE_LIMIT_REQUESTS` lu à chaud, purge du store à 5000 clés, `X-Forwarded-For` honoré uniquement derrière proxy privé (A33) ; pages publiques `/s/`, `/f/` : seuls les non-GET plafonnés ; - CORS A37 : origines fermées issues de `APP_BASE_URL` + regex localhost/extensions navigateur, `allow_credentials=True`, jamais `*`+credentials ; - rate limit par jeton API v2 (`API_V2_RATE_LIMIT_PER_TOKEN` req/min), 300 req/min pour le run agent synchrone, 30 req/min pour Ask AI. ### 8.4 SSRF, uploads, XSS - garde-fou SSRF aux points de sortie HTTP (pas en middleware) : `_is_public_host()` (`app/services/importers/url_fetch.py`) rejette les hôtes `private/loopback/link-local/reserved/multicast` ; `app/services/og_fetcher.py` re-vérifie l'hôte **à chaque redirection** (A12) ; `app/services/connectors.py` impose la garde sur tout fetch ; - uploads : liste `ALLOWED_EXTENSIONS` + 10 Mo max (`validate_upload`) ; - XSS : autoescape Jinja2 global (A10, 326 interpolations crues corrigées) ; sérialisation markdown échappée de l'éditeur (`gtMd`/`mdEsc`). ### 8.5 Chiffrement au repos Fernet (dérivé de `APP_SECRET_KEY`) sur : secrets SSO (`client_secret`, clé SP), `totp_secret_enc`, `calendar_links.tokens_enc`, `connector_tokens.tokens_enc`, `agent_connectors.secret_encrypted`. Hash SHA-256 pour : `api_tokens`, `scim_tokens`, `extension_devices`, backup codes. Note factuelle : le **mot de passe local est SHA-256 + sel** (rapide, pas bcrypt/argon2 — choix « SQLite simplicity » assumé en commentaire de `app/password_utils.py`). ### 8.6 Audit de sécurité A36 & exceptions muettes Les `except: pass` silencieux ont été loggés (v7.3.9), l'API `/api/admin/*` est réservée aux admins, les logs d'exceptions sont tracés, et `scripts/audit_functional.py` rejoue un audit bout-en-bout sur base SQLite isolée (v7.46). --- ## 9. Autorisation : rôles, permissions granulaires, gouvernance ### 9.1 Couche 1 — rôles de workspace `workspace_members.role` : **owner > admin > editor > commenter > viewer** (`app/services/permission_manager.py`). Ensembles sémantiques : `READ_ROLES`, `WRITE_ROLES = {editor, admin, owner}`, `DESTRUCTIVE_ROLES = {admin, owner}`. Le propriétaire d'un workspace a le rôle implicite owner ; tout utilisateur sans ligne explicite retombe sur **viewer**. ### 9.2 Couche 2 — ACL granulaires (v6.0/v6.1) Trois ressources protégées par grant explicite, sur un **user OU un groupe** (`user_groups`/`group_members`, workspace-scopés, `CHECK user_id XOR group_id`) : | Ressource | Rôles | Table | |---|---|---| | Page (éditeur) | viewer / commenter / editor / owner | `page_permissions` | | Collection (base) | viewer / commenter / editor / owner | `collection_permissions` | | Propriété d'une base | viewer / editor | `property_permissions` | Chaque ressource porte un `permission_type` : `inherit` (suit la chaîne page → collection → workspace), `restricted`, `private` (seuls les grants explicites font foi). **Règle de résolution : moindre privilège** — un grant explicite surcharge la chaîne héritée, le meilleur rang gagne (`_explicit_grant_role`). Cache de résolution mémoïsé 60 s, invalidé à chaque mutation. Les rôles SSO se matérialisent par la ligne `workspace_members` du mapping groupe → rôle (`get_sso_roles`). API (`app/routers/permissions.py`, sous `/api/v2`) : `GET|POST /pages/{id}/permissions` (+ `permissions/batch`, `DELETE …/{perm_id}`, `POST …/permission-type`), homologue pour `/collections/{id}/permissions` + `…/properties/{pid}/permissions` + `/properties/visible` (visibilité de propriétés) ; CRUD groupes `/api/v2/groups[/{id}[/members[/{user_id}]]]` (réservé admin/owner de workspace) ; sélecteur d'utilisateurs `/api/v2/users`. Chaque grant/revoke/type_change/group_*/member_* écrit une ligne **immuable** `permission_audit_log` (lecture `GET /api/v2/audit/permissions`, owner/admin, limit ≤ 500). ### 9.3 Gouvernance de l'agent (v7.2) `app/services/agent_policies.py` + `app/routers/governance.py` : table `agent_policies` (ligne globale `workspace_id IS NULL` ou override par workspace) avec `allowed_tools_json` (liste blanche), `max_steps ≤ 50`, `require_approval`. `check_tool()` est consulté **avant** `assert_can()` par l'AgentEngine. En mode `require_approval`, un appel d'écriture crée une demande dans `agent_approvals` (pending/approved/rejected), **suspend l'action** et émet le webhook `agent.run.approval_requested` ; `decide_approval()` approuve/rejette. Le gate d'outil exige editor+ pour les `WRITE_TOOLS`, admin/owner **et mode confirm** (HTTP 428) pour les `DESTRUCTIVE_TOOLS`. API : `GET|POST /api/v2/agent-policies`, `GET /api/v2/agent-approvals`, `POST /api/v2/agent-approvals/{id}/decide` (owner du workspace ou admin). ### 9.4 Journal d'audit unifié (v7.2) Quatre sources, toutes en écriture « best-effort » (un échec d'audit ne casse jamais le chemin métier) : | Table | Écrit par | Contenu | |---|---|---| | `api_audit_log` | `audit_log()` (`api_v2_helpers.py`) | user_id, token_id, action, resource, ip, détail ≤ 1000 car. | | `permission_audit_log` | `PermissionManager.log_permission_change()` | mutations d'ACL (§9.2) | | `sso_login_history` | `sso_provisioning.log_sso_login()` | chaque tentative SSO réussie **ou rejetée** | | `login_history` | `auth._log_login` | login local (ip + user-agent) | API unifiée **`GET /api/v2/audit/logs`** (`app/routers/audit.py`, admin only — session admin ou Bearer scope `admin`) : fusion par `source=all|api|permissions|sso`, filtres `actor`/`action`, `limit ≤ 500`, **export CSV** `?format=csv` (10 000 lignes, rétention visée 365 j). Vues annexes : `GET /api/v2/sso/history`, `GET /api/v2/audit/permissions`, `GET /api/admin/audit`. --- ## 10. Workspaces & collaboration multi-utilisateurs ### 10.1 Modèle de workspace - **`workspaces`** : espace de travail multi-utilisateur (`owner_id → users`, `settings_json`) ; la table `workspace_members` définit le rôle de chacun (§9.1). - Deux familles exposées dans l'UI : **workspaces locaux** (explorateur `local_workspace.html` — arbre, fil d'Ariane de dossiers, sélection multi-fichiers Maj/Ctrl, uploads avec progression/vitesse, vues table/liste/détails/cartes — routes `/workspace…` de `app/routers/dashboard/local_workspace.py`) et **espaces Gitea** (un dépôt = pseudo-workspace `gitea:/`, navigateur `gitea_workspace.html`, pages privées miroir `gitea_private_pages`). - **Teamspaces** (v7.3) : namespaces de pages + bases **au sein d'un workspace**, avec leurs propres membres/rôles (`teamspace_members`) et confidentialité (`private=1` → 404 pour les non-membres) — §14.4. - Le workspace courant est mémorisé par la cookie `flowdeck_workspace` ; les listes de ressources résolvent le rattachement via `effective_workspace_sql`. ### 10.2 Collaboration | Fonctionnalité | Implémentation | |---|---| | Commentaires threadés | `comments` (cible polymorphe `page`/`collection_page`, réponses par `parent_id`, ancre inline `anchor_block_id` + `anchor_start/end`) — endpoints `/pages/{id}/comments` dans `app/routers/collaboration.py` | | Réactions | `comment_reactions` (emoji par commentaire) + `text_reactions` (plage de texte d'un bloc, v7.64) | | Mentions & suivis | notifications `@`/commentaires/assignations ; abonnements `page_follows` → notification `page.updated` | | Favoris / Récents | `favorites`, `recents` (`source_type` local/gitea/… — « Recents réels » sidebar v7.69.6) | | Historique de page | `page_versions` (snapshot complet `blocks_json` par save, undo/restore) pour l'éditeur ; `page_history` (legacy) pour les lignes de base | | Templates | `database_templates` (bases prêtes, seed `db_templates.py`), `page_templates` (lignes, éventuellement récurrentes), `page_global_templates` (blocs) | | Tags | `tags` (`user_id=0` = globales) + `page_tags` | | Icônes | `custom_emojis` uploadés par workspace (`app/routers/emoji.py`) | | Verrouillage | `pages.is_locked`/`locked_by` + mode « Suggest edits » (v5.12) | | Vues analytiques | `page_views` (compteur quotidien par page, v7.3) | ### 10.3 Gestion des conflits d'édition Trois mécaniques selon la surface : 1. **Temps réel éditeur** : merge 3-voies versionné (§13.4) avec drapeau de conflit champ-par-champ ; 2. **Hors temps réel** : colonne `sync_version` entretenue par triggers SQLite (`*_sync_version_bu` sur `pages`/`collection_pages`/`collections`) = verrou optimiste ; l'API offline s'appuie dessus (`GET /api/v2/sync/delta`) et `POST /api/v2/sync/batch` rejoue les mutations en file avec résolution de conflits ; 3. **Lignes de base hors-ligne** : last-write-wins par cellule ; calendrier synchronisé : last-write-wins + notification `calendar.conflict`. --- ## 11. Databases : propriétés, formules, rollups, dépendances Le moteur « base de données Notion-like » vit sous `/db` (`app/routers/collections/`, 13 modules), avec la logique dans `app/services/property_types.py`, `formula_engine.py`, `rollup_engine.py`, `collection_adapter.py`, `collection_lifecycle.py`, `row_pages.py`, `task_databases.py`. ### 11.1 Architecture : schéma déclaratif + valeurs JSON `collection_properties` porte la **définition** (type, options, validation, format, cible de relation, expression, agrégat) ; chaque ligne (`collection_pages`) stocke ses **valeurs** dans `property_values_json` (`{property_name → valeur}`). Conséquences : ajouter une propriété ne demande **aucune migration de table** ; le requêtage se fait via `json_extract()` SQLite ; `collections.schema_json` garde une copie déclarative du schéma complet. ``` collection_properties (schéma) collection_pages.property_values_json ┌──────────────────────────┐ ┌──────────────────────────────┐ │ name: "Status" │ │ { "Status": "Done", │ │ prop_type: "status" │ ↔ │ "Priority": "P1", │ │ options_json: [Todo, │ │ "DueDate": "2026-07-15", │ │ Done] │ │ "Assignee": ["bruno"], │ │ │ │ "Tags": ["page_42"] } │ └──────────────────────────┘ └──────────────────────────────┘ ``` ### 11.2 21 types de propriétés | Catégorie | Types | Stockage exemple | |---|---|---| | Simple | `title`, `text`, `number`, `select`, `multi_select`, `status` (+ couleur), `date` (ISO 8601), `person`, `checkbox`, `url`, `email`, `phone`, `files` | `"En cours"` ; `["Frontend","Backend"]` ; `[{"id":1,"login":"bruno"}]` ; `[{"url":"…","name":"img.png"}]` | | Avancés | `unique_id` (auto-incrément) ; `relation` (bidirectionnel inter-collections, `related_collection_id` + `relation_property_id/target_property_id`) ; `rollup` (agrégat via relation, `rollup_function`) ; `formula` (`formula_expression`) ; `button` (déclenche une automatisation, `button_automation_id`) | `[42, 57]` ; valeurs calculées | | Auto (calculées serveur) | `created_time`, `created_by`, `last_edited_time`, `last_edited_by` | horodatages/acteurs | Vérification : `validate_property_value()` par type + contraintes `validation_json` (required / unique / min / max, mig. 4). Regroupement UI par `group_name`. Sous-ensemble `SIMPLE_TYPES` pour l'import/export CSV. ### 11.3 Formula Engine (`app/services/formula_engine.py`) Moteur d'expressions JavaScript-like, **19 fonctions** : `prop`, `now`, `today`, `if`, `concat`, `round`, `contains`, `length`, `toNumber`, `formatDate`, `dateAdd`, `dateSubtract`, `replace`, `replaceAll`, `join`, `empty`, `and`, `or`, `not`. L'expression est stockée dans `collection_properties.formula_expression` et évaluée à la lecture. ### 11.4 Rollup Engine (`app/services/rollup_engine.py`) Agrégats calculés au travers d'une propriété `relation` vers la collection cible : **12 fonctions** — `count`, `count_values`, `empty`, `not_empty`, `sum`, `average`, `median`, `min`, `max`, `range`, `unique`, plus les concaténations de listes. Le bloc `progress` de l'éditeur réutilise le RollupEngine pour sa barre. ### 11.5 Sub-items & dépendances (v1.8) - **Sub-items** : `collection_pages.parent_id` (auto-référence, hiérarchie illimitée) ; agrégation de statut — le parent passe `Done` quand tous ses enfants le sont. - **Dépendances** : `page_dependencies(page_id, dependency_id, dependency_type)` — Blocking/Blocked by, **contrainte à la transition de statut** (impossible de passer Done une tâche qui en bloque une ouverte) et `auto_shift` (décalage des dates en cas d'overlap). ### 11.6 Sprints & bases de tâches - **Sprints** : `sprints` + `sprint_pages` (points de vélocité, statut au démarrage, `auto_complete`) par base de tâches. - **Task databases** (v7.47) : une collection marquée `is_task` avec mapping explicite `task_assignee_prop` / `task_status_prop` / `task_due_prop` → colonnes existantes — c'est la clé de My Tasks (§15.3) : le front envoie des **champs logiques** (`status`, `due`, `title`), jamais d'identifiant de colonne ; le serveur résout. - **Linked databases** (v4.1) : `collection_data_sources` — une page peut monter une vue liée d'une collection source (`is_linked`), schéma partagé. --- ## 12. Système de vues ### 12.1 Config JSON d'une vue `collection_views` (`view_type` + **`config_json`**, `position`, `created_by` NULL = vue partagée sinon personnelle — mig. 10). Persistance via `PUT /db/views/{id}/config` et `POST /db/{id}/views/save-as`. Exemple : ```json { "group_by": "Status", "sub_group_by": "Assignee", "filters": [ {"property": "Status", "operator": "is_not", "value": "Archivé"}, {"property": "DueDate", "operator": "is_after", "value": "2026-01-01"} ], "filter_conjunction": "and", "sorts": [ {"property": "Priority", "direction": "asc"}, {"property": "DueDate", "direction": "desc"} ], "visible_properties": ["Title", "Status", "Assignee", "DueDate"], "card_size": "medium", "cover_property": "Files", "date_property": "DueDate", "date_range_property": "EndDate" } ``` ### 12.2 Catalogue des vues (trois surfaces) 1. **Vues interactives client** (`static/js/database_table.js`, composant `DBInstance` — pages `/db/{id}`, `page_editor_collection.html`, blocs `database` du plan d'un document) : **Table** (par défaut), **Board/Kanban** (group par select/status/person/multi_select/text, **swimlanes** `sub_group_by`, WIP limits, taille et couvertures de carte), **Calendar** (grille mensuelle, drag & drop d'événements), **Gallery** (card size S/M/L, cover icon/color/property, chips de propriétés), **List** (compacte avec preview). 2. **Vues rendues serveur** (`app/routers/collections/_renderers.py`, dispatch `_render_view` appelé par `dashboard_views.py`, route `/db/{id}?view_type=…`) : les mêmes + **Timeline** (barres Gantt horizontales), **Gantt**, **Chart** (bar/line/pie/doughnut/scatter via Chart.js vendorisé + widgets **KPI** count/sum/avg/min/max, plafonné `CHART_MAX_GROUPS`), **Form** (formulaire générateur de lignes — alimente aussi les formulaires publics `/f/`), **Map** (Leaflet vendorisé, géocodage `lat,lng` depuis les propriétés), **Feed** (flux chronologique). 3. **Board Gitea legacy** (`app/routers/board/board_views.py`, `board.html`, SSR/htmx `GET /board/{owner}/{repo}/view/{view}`) : **kanban** (`board_fragment.html`), **detailed**, **table** (`table_view.html`), **status overview** (`status_overview.html` — donut SVG), **team load** (`team_load.html` — barres empilées), rendu depuis les issues du dépôt. ### 12.3 Chaîne de rendu ``` GET /db/{id} (vue sauvegardée) → charger collection_views (view_type + config_json) → charger collection_pages WHERE collection_id → appliquer config_json.filters (conjunction and/or) → appliquer config_json.sorts → grouper par group_by (sous-groupes sub_group_by pour les swimlanes) → projeter visible_properties (alléger le payload) → rendu : fragment client (database_table.js) ou SSR selon la surface ``` Les **dashboards** (`collection_dashboards.layout_json`) combinent plusieurs vues/widgets en colonnes sur une page ; les **vues de site** publiées (`site_views`) sont une projection en lecture des vues de collection. --- ## 13. Pages, éditeur de blocs & temps réel ### 13.1 Le modèle `pages` `pages` est la table pivot du produit : hiérarchie (`parent_id` auto-référence, `sort_order`),rattachement (`workspace_id`, `collection_id` pour une base plein écran, `collection_row_id` pour la page-ombre d'une ligne de base, `teamspace_id`), contenu (`content` = markdown legacy **ou** JSON de blocs selon `content_format`), publication (`published`/`is_published`/`publish_slug`/`is_shared`), aspect (`cover_url`, `page_icon`, `full_width`, `font_small`), cycle de vie (`deleted_at` = corbeille), contrôle (`is_locked`/`locked_by`, `permission_type`, `search_excluded`, `sync_version`). ### 13.2 Types de blocs Le menu slash et le « Turn into » de l'éditeur (`static/js/page_editor_scripts.js`, rendu serveur `app/services/wiki_blocks.py`) : | Famille | Blocs | |---|---| | Texte | `paragraph`, `heading_1…4`, `bulleted_list`, `numbered_list`, `to_do`, `toggle` (enfants `children`, `expanded`), `quote`, `callout` (icône + couleurs), `divider` | | Technique | `code` (sélecteur de langue, Prism/hljs), `table_of_contents` (rendu client), `math` (bloc KaTeX), `equation_inline` (`$$…$$`), `mermaid` (SVG via `mmdc` si installé, sinon rendu navigateur) | | Données | `table` (grille éditable `.ftable-editor`, GFM sans pipe de tête optionnel), `database` (base inline), `button` (Bloc bouton → automatisation, `automation_id`), `progress` (barre calculée par le RollupEngine) | | Médias | `image`, `embed` (upload/fichier, `embed_type:download`), `bookmark` (unfurl OG), `video`, `audio` | | Structure | `columns` (2/3/4/5 colonnes, `.block-column-wrapper` ×N), `synced` (bloc synchronisé), `meeting` (AI Meeting Notes) | ### 13.3 Mécanique d'édition - **Markdown en saisie continue** : `checkMd` (`# `, `- `, `1. `, `[] `, `> `, `---`, ``` `` ```…), Enter prolonge les listes, Backspace/Delete partagent la garde « bloc vide → supprimer le bloc » (jamais le dernier) ; - **Sérialisation DOM→markdown `gtMd()`** : gras/ital/souligné/barré/code/surlignage `==…==`/liens ; les chips wiki gardent leurs tokens (`gtTok`) ; - **Handle ⋮⋮ + réordonnancement SortableJS** ; **bouton « + »** à gauche du handle (insert below, v7.60) ; **Maj-clic = sélection multi-blocs** ; - **Menu contextuel de bloc façon Notion** (v7.61) : recherche d'action + 10 actions (Turn into ›, Color ›, Copy link to block `Alt+⇧+L`, Duplicate, Move to, Delete, Comment, Suggest edits, Ask AI, Skills ›), pied « Last edited by… / N words » ; sous-menu Color mémorise le dernier usage (`localStorage`) ; - **Toolbar de sélection sur texte surligné** (v7.62–7.67) : 4 lignes (Turn into ; A/B/I/U/T/Tx ; lien/code/équation ; 💬 Comment + réaction) + Skills + « Edit with AI » ; - **Presse-papiers multi-blocs** (v7.68–7.69) : copier/couper/coller de plages partielles → blocs entiers → plage partielle, Ctrl+C/X intercepté au `keydown` capture (le navigateur ne sérialise pas une sélection traversant deux `contenteditable`) ; - **Coller intelligent** : `handleSmartPaste` → `md2b`/`paste2b` (markdown/GFM → blocs structurés) ; - **Undo/redo** par snapshots `page_versions` (`pushHistory`, une ligne par save) + **autoSave debouncé** ; - **Menus mobiles** (v7.63, ≤768 px) : barre horizontale flottante au `/` (15 boutons) + feuille plein écran « Insert block » ; - **IA dans l'éditeur** : slash commands AI, autocomplétion inline (`AIAC`), panneau « Notion AI » Ctrl+J, remplissage de propriétés (`POST /api/agent/writing*`, §16.4). ### 13.4 Temps réel (`app/services/realtime_server.py`, `realtime_merge.py`, `app/routers/realtime.py`) Passerelle WebSocket **`/ws/pages/{page_id}`**, auth par cookie `flowdeck_session` (fermeture `4401` sinon). Rooms **en mémoire par page** (uvicorn mono-worker), persistance `page.content` avec debounce, file sortante + tâche writer par connexion (broadcast non-bloquant, curseurs coalescés 1/flush, éjection propre du client trop lent code `4413`, anti-flood 400 ops/10 s). Protocole JSON (champ `t`) : client→serveur `hello`, `sync_req`, `ping`, `op` (insert|update|delete|move + `base` + `v`), `title`, `sel` ; serveur→client `sync`, `ack` (`v`, `stale`, `merged`, `conflict`), `op` broadcast (**le bloc fusionné est diffusé**, jamais la proposition brute), `sel`, `welcome`, `peer_join/leave`, `synced_update`. **Merge « CRDT-lite » 3 voies** (`realtime_merge.py`) : au-delà du LWW, chaque `update` porte la version `base` dont il dérive ; règle champ-par-champ — inchangé d'un côté ⇒ l'autre gagne ; identiques ⇒ sans conflit ; régions disjointes ⇒ `merge_text_3way` garde **les deux saisies** (diff3-lite par trim préfixe/suffixe commun) ; chevauchement ⇒ LWW par champ **avec drapeau de conflit** renvoyé au client. Le client (`page_editor_realtime.js`) gère présence (avatars déterministes), curseurs live (offsets restaurés), resynchro complète si `stale`, et **repli polling 10 s** si le WS tombe. --- ## 14. Wiki, liens, blocs synchronisés & teamspaces ### 14.1 Liens wiki & mentions Les références inline vivent dans le texte brut des blocs sous forme de **tokens** `[[fdpage:ID]]` et `[[fddate:YYYY-MM-DD[Thh:mm]]]` ; le label est résolu **au rendu** — renommer une page la met donc à jour partout. Le sélecteur de mentions (`WM`, `/api/wiki/pages`) remplace « [[ » tapé par une chip non éditable qui re-sérialise en token. ### 14.2 Backlinks `GET /api/pages/{page_id}/backlinks` (`app/routers/board/pages.py`) : « Lié depuis… » en scannant blocs et markdown brut de toutes les pages non supprimées, tri par fraîcheur. ### 14.3 Blocs synchronisés (transclusion, v5.14 → prod v6.5) `synced_blocks` = **source de vérité** du contenu ; `page_synced_blocks` = références (page, index). L'édition de la source se propage : rooms temps réel via le message `synced_update`, ou re-résolution à la lecture (`resolve_content_json`, garantie de fraîcheur). Source supprimée ⇒ état explicite `_synced_deleted` (plus de « Loading… » éternel). ### 14.4 Teamspaces & wiki d'équipe (v7.3) `app/services/wiki.py` + `app/routers/wiki.py` (doc `docs/V73_Wiki_Teamspaces_Polish.md`) : namespaces de pages/bases partagées avec rôles et confidentialité (`private=1` → 404), **badges de vérification ✅ avec expiration 90 j** (`POST /api/v2/wiki/pages/{id}/verify`), **abonnements** (`page_follows` → notification `page.updated`), **réactions** (`comment_reactions`, `text_reactions`), **partages invités sans compte** `/g/` (`guest_shares` : token, rôle, expiration, révocation), compteurs de vues journaliers (`page_views`). --- ## 15. Partage, publication, Library, My Tasks & Trash ### 15.1 Sharing & Publish - **Partage de page** (`page_shares`) : vers un user, un groupe ou un email, permission view/edit/comment ; sections sidebar « Shared → Par moi / Avec moi » et onglet Library correspondant ; - **Publication** : drapeaux unifiés `published` + `is_published` (+ `publish_slug` garanti, v7.69.6) → page publique rendue par `public_page.html` (thème sombre autonome, OG tags, `og:image` en URL absolue via le global `app_base_url()` v7.69.1) ; - **Sites publics** (`app/routers/sites.py`, v6.8, doc `docs/V68_Sites_Forms.md`) : mini-sites multi-pages `/s/` (navigation, verrou **mot de passe + expiration**, SEO, `noindex`, analytics id, stats de consultation sans IP brute `site_views`, domaine personnalisé `custom_domain`) ; - **Formulaires publics** `/f/` : champs générés depuis `collection_properties` (hors formules), validation, **rate limiting**, soumissions anonymes vers la collection (`form_responses` + `collections.form_config_json`), notification aux membres, événement `form.submitted` pour les automations ; - **Partage public de base** (`/workspace` public_router, `share_mode`) et API de partage `app/routers/sharing.py` + `api_v2/sharing.py`. ### 15.2 Library `app/templates/library.html` + `static/js/library.js` (API `/api/library`, `app/routers/library.py`) — page « toutes mes ressources » à **onglets** : Recents, **AI Meeting Notes** (regroupées Today/Yesterday/This week/Last week/This month/Older, v7.69.6), Favorites, Shared (sous-filtre direction Tous/Par moi/Avec moi), Private, Published, Workspace, Repository (Gitea). Tableau à **colonnes pilotées par « Show columns »** (Created by, Last edited by/time, Last visited time, Source avec icône — Page name verrouillée, ordre des en-têtes corrigé v7.69.8) ; recherche, sélection multiple, publication unifiée ; ouverture en **side peek**. ### 15.3 My Tasks (v1.9 → refonte v7.47) Dashboard transverse **multi-workspaces** des tâches assignées (`app/routers/my_tasks.py`, `static/js/my_tasks.js`) : **3 vues façon Notion** — tableau dense (statut/échéance éditables au clic), kanban (colonnes = options de statut de la base d'origine, drag = écriture du statut), calendrier (mois courant sur les échéances). Sources opt-in, mapping explicite via les collections `is_task` (§11.6) : le front envoie un champ logique, jamais un id de colonne. Conversion d'une base en base de tâches par la modale `task_db_link.js` (`POST /db/{id}/task-db/api`, max 10 bases). ### 15.4 Trash Corbeille unifiée (`trash.html`, `app/services/trash.py`) : soft-delete `deleted_at`, restauration, **purge** (immédiate ou planifiée par `trash_purge_scheduler`), actions groupées, tri/filtres réels, fenêtre de confirmation thématisée (v7.45.5). ### 15.5 Sidebar & navigation (v7.46) Sidebar façon Notion dans `base.html` : en-tête workspace + badge d'auth, **4 onglets compressibles Home / Chat / Meeting / Inbox** (badges non-lus ; le panneau actif garde son label, les autres se replient sur l'icône), sections du panneau Home : Workspace (arbre), Repository, Meetings, Recents, Favorites, Agents, Teamspaces, Shared, Published, Private — chacune avec menu contextuel ; panneau « Customize sidebar » (`/api/sidebar`, `users.sidebar_config` JSON) ; **zone peek de sidebar** (survol quand repliée, épinglable, redimensionnable) ; **side peek** global (Alt+Click / menu contextuel) : panneau latéral ouvrant le document **nu, sans sidebar ni barre** (`page_editor_embed.html` + `?embed=1`, redimensionnable, `postMessage fd-page-renamed` pour synchroniser les titres). --- ## 16. IA : agent, compétences, connecteurs & écriture ### 16.1 Fournisseurs LLM & configuration **Client unifié** `app/services/llm_client.py` : abstraction `LLMClient` sur **23 fournisseurs + un mode `offline`**, tous en protocole OpenAI-compatible chat-completions (Anthropic, Google `/v1beta/openai`, Cohere `/compatibility/v1` exposent une surface compatible). `PROVIDERS` porte (base_url, modèle par défaut) ; `PROVIDER_MODELS` les presets curatés exposés par `GET /api/agent/providers`. Ollama local (`http://localhost:11434/v1`, sans clé). Le mode **`offline`** est un *mock planner* déterministe — l'agent et les tests restent fonctionnels sans appel externe. **Précédence de configuration en 4 niveaux** (`app/services/llm_config.py`) : 1. `provider`/`model` passés explicitement à l'endpoint de run ; 2. clé de l'utilisateur pour ce provider (`user_llm_keys` : api_key, api_base, default_model, `models_json`, flag `verified`) ; 3. ligne globale `llm_config` (id=1) gérée par l'UI admin ; 4. `settings.llm_*` (`.env`). Les clés sont **stockées en base serveur**, jamais exposées (masquées) ; le changement de clé réinitialise `verified` ; les providers à catalogue sur-vendu (NVIDIA, Mistral) sont **validés par sonde chat** avec filtrage des modèles non-chat (embeddings, TTS, génération d'images…). Routes : `/api/agent/keys[/{provider}]`, `/test`, `/models`, `/api/agent/providers`. ### 16.2 Agent conversationnel — moteur ReAct (`app/services/agent_engine.py`) Boucle `objectif → compréhension → contexte → raisonnement ↔ action → résultat` ; `MAX_ITERATIONS = 12` (plafonnée par la politique workspace et `AGENT_MAX_ITERATIONS`), budget tokens, timeout par passe. Le LLM n'émet que des **intentions d'outils** (function calls) ; chaque appel passe par `AgentPolicies.check_tool()` puis `PermissionManager.assert_can()` avant exécution par le `ToolRegistry`. Chaque action est **journalisée dans `agent_actions` avec snapshot d'annulation** (`undo_snapshot_json`) → rollback unitaire (`POST /api/agent/actions/{id}/undo`). Le run émet un **flux SSE** (`reasoning`, `action`, `notice`, `final`, `error`) et déclenche les webhooks `agent.run.started|finished|failed`. **Panneau & conversations** (`app/routers/agent.py`, `/api/agent/*` ; UI `agent_panel.html` + `static/js/agent_panel_*.js`) : CRUD agents personnalisés (`agents` : instructions système, `scope_json` d'outils autorisés, `approval_mode`, modèle), conversations (provider/model persistés, toggle `memory_enabled`), feedback 👍/👎 (`agent_feedback`), mentions `@`/`+`, run SSE, **trigger externe** (`POST /api/agent/{id}/trigger` manuel SSE ; pendant synchrone JSON `POST /api/v2/agents/{id}/trigger` Bearer+scope write), **agents planifiés** (`agent_triggers`, scheduler 60 s). **API publique agent** (`app/routers/api_v2_agent.py`, v6.6) : mince wrapper Bearer sur le même moteur — `POST /api/v2/agents/conversations/{id}/run` **tamponne le SSE et renvoie un JSON unique** (status, final, reasoning, actions, events, durée), rate limit 300/min, idempotency-key, audit. Une seule implémentation, jamais re-développée. ### 16.3 Contexte, outils, mémoire, skills - **Context builder** (`context_builder.py`) : snapshot Markdown **filtré par permissions** — collections (+schéma, verrou), pages récentes, documents, espaces, mentions résolues (`@document:`, `@collection:`, `@page:`, `@folder:`), fichiers épinglés, plus un guide in-app `APP_GUIDE` pour les questions « comment faire ». - **Outils** (`tool_registry.py`) : **26 outils statiques** — fins wrappers des opérations des routers humains, chaque mutateur renvoyant un snapshot d'undo. Lecture : `search_workspace`, `read_collection/page/workspaces/document` ; écriture : `create_collection/view/page/document`, `add_property/relation/sub_item/dependency`, `update_page`, `write_blocks`, `apply_template`, `delete_*` ; Gitea : `read_gitea_issues`, `sync_gitea`, `create_gitea_issue` ; **web** (v7.46) : `web_search` (provider Exa, repli DuckDuckGo sans clé), `fetch_url` ; **GitHub** : `search_code` ; **connecteurs** : `connector_fetch`. S'y ajoutent les **outils MCP dynamiques** : fusion à chaque run du cache `agent_connectors.tools_json` (noms `mcp__`) ; retrait des outils des plugins éteints. - **Mémoire** (`agent_memory.py`, v7.54) : par conversation, une ligne résumé upsert dans `agent_memory` (20 échanges max, injection plafonnée 4 000 car.), ré-injectée au run suivant ; toggle `memory_enabled` = aucune lecture/écriture ; ne fait jamais échouer un run. - **Skills** (`skill_gallery.py`, v6.6) : prompts paramétrés + liste d'outils autorisés (`agent_skills`) ; CRUD/apply/édition (`/api/agent/skills`, `PATCH` v7.52) ; **galerie de 17 presets** installables (rapport-hebdo, compte-rendu-reunion, base-crm, okr, sprint-review, veille-techno, triage-incident, briefing-quotidien…) ; **format portable `flowdeck-skill` v1** (export/import JSON rejouable) ; doubles routes cookie et `/api/v2/skills/*`. ### 16.4 Hub « Menu + » de l'assistant (v7.51→v7.58, 8 phases livrées) Design `docs/V74_Agent_Plus_Menu.md`. Le bouton + du panneau devient un menu à sections à deux niveaux : | Phase | Version | Section | Réalisation | |---|---|---|---| | 1 | v7.51 | Fichiers/répertoires (hub de contexte) | parcours d'arborescence `GET /api/nav/menu?parent_id=`, jeton `folder:` | | 2 | v7.52 | Compétences | « Gérer » (CRUD) + « Parcourir » (galerie) | | 3 | v7.53 | Design System – Canevas | canevas `design_system` (`block_templates.py`), insertion de blocs | | 4 | v7.54 | Mémoire | `agent_memory` + toggle persisté | | 5 | v7.55 | Connecteurs (socle) | `connectors.py` : catalogue natifs (gitea, github, web, google, ms365) + connecteurs personnels `agent_connectors` (kind custom|discord|telegram|mcp), fetch gardé SSRF, secrets Fernet | | 6 | v7.56 | Google + Microsoft 365 | `oauth_connectors.py` : OAuth PKCE S256, scopes **lecture seule** (Drive/Gmail/Calendar ; Graph Files/Mail/Calendars), tokens chiffrés, refresh auto | | 7 | v7.57 | Discord, Telegram, Teams, MCP | presets d'auth ; client MCP complet `mcp_client.py` (JSON-RPC 2.0 `initialize → tools/list → tools/call`, réponse JSON ou SSE, cache en base) | | 8 | v7.58 | Add plugins | `plugins.py` : catalogue on/off **à effet réel** (§4.2) | > ⚠️ `docs/FLOWDECK_MCP_SERVER_GUIDE.md` est une conception pour exposer FlowDeck **comme serveur MCP** externe (parité `mcp.notion.com`) ; le code livré côté FlowDeck est le **client** MCP. Un serveur externe s'appuierait sur l'API v2 + les tokens existants. ### 16.5 AI Writing (`app/services/ai_writing.py`, v5.9) Service **headless sans outils**, 6 actions : `write`, `summarize`, `translate`, `continue`, `autocomplete`, `properties`. Consommé par l'éditeur (slash commands, autocomplétion inline) et par les bases (remplissage de propriétés par suggestions structurées, défauts offline déterministes). Entrées HTTP : `POST /api/agent/writing`, `/writing/properties`, `/agent/generate` (génération sur document). --- ## 17. Recherche : FTS5, sémantique hybride & Ask AI - **Palette Ctrl+K / Ctrl+P** (`#fd-command-palette` dans `base.html`, v5.0, doc `docs/V6…`) : recherche **FTS5** (`pages_fts`, table virtuelle synchronisée par triggers `AFTER INSERT/UPDATE/DELETE` sur `pages`, backfill au boot, repli `LIKE` si FTS5 absent) portée aux espaces accessibles (`app/services/search.py`, `/api/search`) ; **onglet « ✨ Réponses IA »** depuis v7.44. - **Recherche sémantique + Ask AI** (v6.9, `app/services/semantic_search.py`, doc `docs/V69_Search_Ask_AI.md`) : retrieval **hybride** = lexical + similarité cosinus vectorielle, fusion **RRF (k=60)**, filtrage ACL `PermissionManager` (un chunk non autorisé n'entre jamais dans un prompt). Vecteurs : encodeur **TF hashé maison** (`hash-256`, 256 dims, zéro dépendance pip) ; chunking 1 200 car. / overlap 150 / 50 chunks max par ressource ; tables `semantic_embeddings` + `semantic_index_state`, scheduler d'indexation incrémental 300 s. - **Ask AI** : `POST /api/v2/search/ask` — RAG (top 8 chunks autorisés) avec **citations cliquables** `[[fdpage:ID]]`, cache 10 min, rate limit 30/min, réponse extractive offline sinon ; `GET /api/v2/search/hybrid`. - `flag pages.search_excluded` pour sortir une page de l'index. --- ## 18. AI Meeting Notes & calendrier ### 18.1 AI Meeting Notes (v7.1 → v7.69, bloc `meeting` de l'éditeur) `app/services/meetings.py` + `app/routers/meetings.py` (`/api/v2/meetings/*`) ; spec détaillée `docs/architecture-meeting-notion-flowdeck.md`, doc `docs/V71_Calendar_Meetings.md`. - **Machine à états** du bloc : `idle / recording / paused / processing / done / failed` ; modes de capture `mic_only / tab_plus_mic / import` ; audio (mp3/wav/m4a/ogg/flac/aac, 100 Mo max) dans `data/uploads/meetings/`. - **Transcription** : backend externe par variable `STT_COMMAND` (ex. Whisper local, timeout 10 min) ou transcript collé ; **segments groupés par locuteur**, ré-attribution des speakers. - **Consentement bloquant journalisé** (`meeting_consent_log`, 6 méthodes : attestation de démarrage, verbal, chat, audio, add-on Meet, force workspace). - **Résumé façon Notion** : pipeline `run_processing()` en 8 étapes *Thinking* persistées (lecture → analyse → références personnes → compréhension → **classification de complétude** → rédaction → **titre généré** avec diagnostic honnête si réunion brève → **validation des citations obligatoires**) ; presets d'instructions (auto, standup, team, sales, 1:1, interview) ; résumé structuré JSON via `AIWritingService` (overview / décisions / action items), repli offline déterministe ; rendu Markdown → blocs sur la page. - **Partage tracé** (`meeting_distributions` : copy_link/email/slack) ; événement **`meeting.summarized`** → déclenche les automations ; **index unique partiel `event_occurrence_id`** : une occurrence calendrier = au plus une note. - UI : onglet Library dédié (§15.2), section Meeting de la sidebar, setup visible avec test micro et retry sans perte (v7.69.5). ### 18.2 Sync calendrier (`app/services/calendar_sync.py`, v7.1) Synchronisation **bidirectionnelle** collection ↔ **Google Calendar** (REST) ou **CalDAV** générique (REPORT/PUT bruts, zéro dépendance). Liens par utilisateur dans `calendar_links` (tokens chiffrés Fernet, `date_property` de la collection), matching des événements par `collection_pages.external_event_id`, conflit = last-write-wins + notification `calendar.conflict`, `GET /db/{id}/calendar/freebusy`, resync périodique par scheduler. --- ## 19. Automatisations, workers, rappels, notifications & webhooks ### 19.1 Moteur d'automatisations (`app/services/automations.py`, v5.1 → v2 multi-step v7.0) *If-this-then-that* : triggers **`event` / `cron` / `button`** (tables `automations`, `automation_runs`), conditions AND sur propriétés (`eq, neq, contains, not_contains, is_empty, is_not_empty, changed`), actions séquentielles. **v7.0 — steps chaînés** (`automation_steps`) : kinds `trigger|condition|delay|action` ; multi-trigger mode `any/all` (fenêtre 300 s) ; delay plafonné 300 s ; **8 types d'actions** : `webhook`, `set_property`, `create_page`, `notify`, `slack`, `email`, `forge_issue` (issue Gitea/GitHub) et **`agent_trigger`** (lance un agent FlowDeck dans sa propre conversation journalisée). Bouton natif de base (`press_button()` sur une propriété `button`, `button_automation_id`). `fire_event()` est le point d'entrée des événements `page.*`, `collection.*`, `form.submitted`, `meeting.summarized`… ; scheduler cron 60 s mutualisé avec les workers. **Éditeur visuel v7.45** : pipeline de cartes ordonnées dans Settings → Automations (`/workspace/automations/{id}/steps`), config typée par kind, réordonnancement, « ✨ Convertir le JSON en pipeline ». ### 19.2 Workers (`app/services/workers.py`, v7.0) Extraits **Python custom exécutés sur l'infrastructure FlowDeck** (parité Notion Workers). Exécution manuelle, **cron**, ou **partagée en équipe avec fork**. Sandbox best-effort single-process : lint **AST blacklist** (`import os/sys/subprocess/socket…`, `open/exec/eval/compile/__import__`, attributs dunder), pas de réseau ni filesystem, builtins restreints, API limitée à `log()`, `ctx`, `result` ; **timeout 30 s** et **budget journalier secondes/workspace** (`daily_budget_s`). Tables `workers`/`worker_runs`, routes `/api/v2/workers*`. ### 19.3 Récurrence & rappels (`recurrence.py`, `reminders.py`, v5.8) - **Récurrence** : sous-ensemble RRULE (`daily/weekly/monthly`, interval, COUNT, UNTIL, BYDAY, fuseau `zoneinfo`) stocké dans `property_values_json` sous la clé `__recurrence__` ; **occurrences virtuelles calculées à la volée**, jamais persistées. - **Rappels** : règle `__reminder__` (lead minutes/heures/jours par propriété date) ; `scan_and_fire()` (60 s) calcule la prochaine occurrence (timezone-aware), envoie notification in-app + email si opt-in ; dédupliqué via `reminder_log` par (page, occurrence) — **il n'existe pas de table `reminders`**, les dates vivent dans les propriétés `due`. ### 19.4 Notifications (`app/services/notifications.py`, v4.9) Table `notifications` (mentions `@`, commentaires, changements de page, assignations `notify_assignment()`, `page.updated`, `calendar.conflict`…) ; préférences utilisateur + fuseau (`/api/notifications/prefs`) ; livraison email via `app/services/mailer.py` (SMTP optionnel) ; UI cloche `_notification_bell.html` + onglet Inbox de la sidebar. ### 19.5 Webhooks sortants (`app/services/webhook_outbound.py`, v2.1 → prod v6.4) **~50 événements** au catalogue (pages, commentaires, collections, sprints, partages, `automation.fired/failed/retrying`, `agent.run.*` y compris `approval_requested`, workspaces, fichiers, imports, `ping`). Signature **HMAC-SHA256** (`X-FlowDeck-Signature`), **retry 2 s/10 s/60 s** par ledger `webhook_deliveries`, abonnements avec wildcards (`page.*`, `*`) dans `webhook_subscriptions`, CRUD `/api/v2/webhooks` + endpoint entrant de test. Gestion admin `app/routers/webhooks.py` (webhooks **entrants** Gitea : `POST /api/webhook/gitea` avec secret partagé). --- ## 20. Intégration Gitea / GitHub ### 20.1 Flux de données Gitea ``` Création Collection Sync bidirectionnelle ────────────────── ────────────────────── 1. Collection vide 5. POST /board/api/sync/{o}/{r} (pas de lien Gitea) ├─ GET issues Gitea ├─ Map issues → collection_pages 2. Collection liée │ (via _issue_column) (gitea_owner + gitea_repo) ├─ Extract AI keywords └─ Upsert cards en DB 3. Pull initial GET /api/v1/repos/{o}/{r}/issues 6. Webhook Gitea entrant 4. Mapping colonnes POST /api/webhook (secret partagé) col_mapping: ├─ issue.opened → create page column_name → gitea_label ├─ issue.closed → archive page └─ issue.labeled → move column ``` - `app/services/gitea_client.py` : wrapper API REST (token admin `GITEA_TOKEN` ou token OAuth de l'utilisateur) ; - Le dépôt est un **pseudo-workspace** (`gitea:/`) avec son navigateur `gitea_workspace.html`, ses pages privées miroir (`gitea_private_pages`) et son kanban legacy (`boards`/`cards`/`col_mapping`) ; - L'agent peut lire/créer des issues (`read_gitea_issues`, `create_gitea_issue`, `sync_gitea`). ### 20.2 Adaptateur de compatibilité ```python class GiteaBoardCompat: """Adaptateur : board Gitea legacy → Collection.""" @staticmethod def from_board(board_row) -> dict: return { "collection_id": f"gitea:{board_row.id}", "name": f"{board_row.project_owner}/{board_row.project_name}", "gitea_owner": board_row.project_owner, "gitea_repo": board_row.project_name, "schema": [ {"name": "Title", "type": "title"}, {"name": "Status", "type": "select", "options": json.loads(board_row.columns_json)}, {"name": "Priority", "type": "select", "options": ["P1", "P2", "P3", "P4"]}, ], "is_gitea_linked": True, } @staticmethod def from_collection_page(page_row) -> dict: """Convertit collection_page → card (pour templates legacy).""" return { "id": str(page_row.get("gitea_issue_number", page_row["id"])), "title": page_row["title"], "status": _derive_status(page_row), ... } ``` (`app/services/collection_adapter.py`, `app/routers/board/import_.py` pour l'import de boards legacy.) ### 20.3 GitHub Support forge secondaire : `app/services/github_adapter.py`, `app/routers/github_routes.py` (`/api/github`), OAuth de connexion (`GITHUB_OAUTH_CLIENT_ID/SECRET`), registre unifié `projects` (`proj_type` gitea|github|builtin, `app/services/projects.py` + cron de sync), outil agent `search_code` (API REST), `forge_issue` en action d'automatisation, import de dépôts (`importers/forge_repo.py`). --- ## 21. Import & Export ### 21.1 Import (`app/services/importers/`, `app/routers/imports.py`, `/api/import` + page `import.html`) Pipeline unifié en 6 phases (v5.6) : socle (`base.py`, `pipeline.py`, `jobs.py` avec `import_jobs`/`import_items` pour statut, rapport et **dédup idempotent** par `(workspace, source, external_id)` → re-import incrémental), puis formats : | Importer | Source | |---|---| | `notion.py` | export ZIP Notion (pages + databases) | | `markdown.py`, `html_notes.py`, `standard_notes.py`, `apple_notes? (via html)` | notes et dossiers de fichiers | | `obsidian.py`, `outline.py` | vaults Obsidian, espaces Outline | | `docx.py`, `pdf.py` | documents bureautiques (python-docx, pypdf) | | `tabular.py` | CSV/TSV/XLSX (openpyxl) → collections | | `bookmarks.py`, `opml.py` | signets | | `calendar.py` | ICS | | `url_fetch.py` | import par URL (gardienné SSRF) | | `forge.py`, `forge_repo.py` | données de forge (issues, dépôts) | ### 21.2 Export (`app/services/export.py`, `/api/export`, v4.7) Export **Markdown** (avec images), **PDF** (WeasyPrint + xhtml2pdf), **HTML** standalone, **site statique `.zip`** ; CSV des bases (sous-ensemble `SIMPLE_TYPES` des propriétés) ; export/import **skill portable** `flowdeck-skill` v1 (§16.3). --- ## 22. Frontend : rendu, CSP, PWA & Web Clipper ### 22.1 Gabarits (`app/templates/`) Répertoire **plat** — la distinction page/fragment est portée par la convention du préfixe `_` : - **`base.html`** (~3 000 l.) = shell de toutes les pages authentifiées : `` PWA/OG/CSS/JS vendors, `` (CSRF porté par htmx), **sidebar Notion à 4 onglets** (§15.5), topbar `{% block topbar %}{% include '_header.html' %}`, `#main-content` swappé par la navigation partielle, menu contextuel global, **palette Ctrl+K**, enregistrements `Alpine.data` obligatoires (build CSP), inclusion de `agent_panel.html` + service worker + `offline.js`. - **`_header.html`** : topbar unifiée avec fil d'Ariane piloté par JSON embarqué (`#fd-breadcrumb-data`) + composant Alpine `fdBreadcrumb()`. - Fragments : `_page_editor_content/scripts/realtime.html`, `_database_table(+_scripts).html`, `_ctx_menu.html`, `_icons.html`, `_icon_picker.html`, `_notification_bell.html`, `_workspace_tree_macro.html`. - Pages : `dashboard`, `library`, `local_workspace`, `gitea_workspace`, `page_editor(_collection|_embed)`, `workspace(s)`, `notes`, `board`, `settings`, `help` (géométrie `settings-overlay` partagée avec Settings, v7.69.3), `trash`, `accounts`, `import`, `agent_message`. - Autonomes (hors `base.html`) : `landing` (page de présentation pré-login), `welcome` (onboarding), `public_page` (rendu public thème sombre), `card(_detail)`, fragments SSR échangés par htmx (`board_fragment`, `detailed_board`, `table_view`, `status_overview`, `team_load`). ### 22.2 Modules JS (`static/js/`) | Module | Rôle | |---|---| | `page_editor_scripts.js` (~3 500 l.) | cœur de l'éditeur : blocs, sérialisation `gtMd`, menu slash, toolbars, tableaux, colonnes, synced blocks, undo, autoSave, uploads, collage intelligent, presse-papiers multi-blocs, mentions `WM`, complétion IA `AIAC` | | `page_editor_realtime.js` | client WebSocket présence/curseurs/ops (§13.4) | | `database_table.js` | composant `DBInstance` : vues, cellules, filtres/tris, config persistée | | `library.js`, `local_workspace.js`, `my_tasks.js` | pages §15 | | `app.js` | **navigation partielle maison** : interception des liens « clic gauche simple », fetch + swap de `.main-wrapper` seul + `history.pushState` + remontée explicite d'Alpine (`Alpine.initTree`) ; `window.fdNavigate(url)` point d'entrée programmatif ; tooltips, helpers peek | | `agent_panel_1/2.js` | panneau agent, Menu + hub (§16.4) | | `meeting_block.js` | UI du bloc AI Meeting Notes | | `settings.js`, `workspaces.js`, `board.js`, `gitea_workspace.js`, `import.js`, `help.js`, `task_db_link.js`, `offline.js`, `code-highlight.js`, `_ctx_menu.js`, `_icon_picker_*.js` | modules de page | | vendors | `htmx.min.js` (2.0.4), **`alpine.csp.min.js`** (build officiel CSP 3.17.4), `sortable.min.js`, `katex`, `highlight(.extra)/prism`, `vendor/chart.umd.js`, `vendor/leaflet/` | Gardes d'idempotence (`window.__fdEditorScriptsLoaded`, v7.45.2) pour survivre aux re-jeux de scripts lors des swaps ; anti-FOUC au chargement (v7.45.3-4). **Aucun bundler** : pas de `package.json` racine ni d'esbuild — les libs sont vendorisées telles quelles (`scripts/vendor_hljs.py` pour highlight.js) ; cache-busting par `?v=VERSION`. ### 22.3 PWA offline - `static/manifest.json` : `standalone`, icônes 72→512 régénérées depuis le logo (`scripts/generate_pwa_icons.py`), `lang: fr` ; - `static/sw.js` : deux caches — statique `flowdeck-v*` (pré-cache CSS/JS/vendors/icônes, bump à chaque release) et données `flowdeck-data-v*` ; stratégies : **mutations → always network** (la file est côté client), HTML → network-first (timeout 4 s, repli cache puis **page `/offline.html` fabriquée**), GET API → network-first cache données, statique → cache-first ; Background Sync si disponible ; - `static/js/offline.js` (`window.FlowOffline`) : IndexedDB `flowdeck-offline` (`pages_offline`, `collections_offline`, `sync_queue`, `sync_meta`) ; écritures locales optimistes hors ligne, **file de mutations rejouée sur reconnexion via `POST /api/v2/sync/batch`** (lots ≤ 100, rétention 30 j), pull via `GET /api/v2/sync/delta`, id d'appareil `fd_device_id`. ### 22.4 Web Clipper (`extension/`, v6.2) Extension navigateur **Manifest V3** (« FlowDeck Web Clipper », Chrome/Edge/Firefox) : menus contextuels + **bouton flottant draggable** (sélection/capture), sélecteur Readability ; captures **article / sélection / bookmark / screenshot** envoyées à `POST /api/v2/web-clipper/clip` (Bearer token d'appareil `extension_devices`, device id `dev_…` persisté) ; popup de configuration serveur/token + vérification de connexion ; notification des onglets FlowDeck ouverts après un clip. Coupée par le plugin `web-clipper` (§4.2). --- ## 23. Déploiement & exploitation ### 23.1 Docker ```yaml # docker-compose.yml — un seul service ; Gitea reste externe services: flowdeck: build: . container_name: flowdeck ports: - "${APP_PORT:-8080}:8080" volumes: - flowdeck_data:/data env_file: - .env restart: unless-stopped volumes: flowdeck_data: ``` - `Dockerfile` : **2 stages Python** (`builder` : `pip wheel` → `/wheels` ; `runtime` : `python:3.13-slim` + libs système WeasyPrint pango/harfbuzz/gdk-pixbuf, fonts dejavu + noto-color-emoji, curl) ; **aucun stage node** (front non compilé) ; `EXPOSE 8080` ; `HEALTHCHECK` sur `/api/health` (30 s) ; CMD `uvicorn app.main:app --host 0.0.0.0 --port 8080 --proxy-headers --forwarded-allow-ips '*'`. - Démarrage local : `cp .env.example .env` (token Gitea) puis `docker compose up -d` → http://localhost:8080 ; ou venv + `uvicorn app.main:app --reload --port 8082` (CONTRIBUTING). ### 23.2 Variables d'environnement (`app/config.py`, pydantic-settings, `extra="ignore"`) | Groupe | Variables | |---|---| | App | `APP_SECRET_KEY`, `APP_HOST`, `APP_PORT`, `APP_BASE_URL`, `LOG_LEVEL`, `DEFAULT_LANG`, `FLOWDECK_DATA_DIR` | | Forge/OAuth | `GITEA_URL`, `GITEA_TOKEN`, `GITEA_OAUTH_CLIENT_ID/SECRET`, `GITEA_WEBHOOK_SECRET`, `GITHUB_OAUTH_CLIENT_ID/SECRET`, `OAUTH_REDIRECT_URI`, `WEBHOOK_BASE_URL` | | Base / sync | `DATABASE_URL` (sqlite uniquement), `SYNC_INTERVAL`, `GITEA_CACHE_TTL` | | Rate/API | `RATE_LIMIT_ENABLED`, `RATE_LIMIT_REQUESTS`, `PUBLIC_API_INSECURE_OK`, `API_V2_RATE_LIMIT_PER_TOKEN` | | Sauvegardes | `BACKUP_ENABLED`, `BACKUP_DIR`, `BACKUP_INTERVAL_HOURS`, `BACKUP_KEEP` | | Fonds | `PROJECT_SYNC_ENABLED`, `PROJECT_SYNC_INTERVAL_HOURS`, `REMINDERS_ENABLED`, `REMINDER_SCAN_INTERVAL_SECONDS`, `WEBHOOK_RETRY_ENABLED`, `WEBHOOK_RETRY_INTERVAL_SECONDS` | | SMTP | `SMTP_HOST/PORT/USER/PASSWORD/FROM/USE_TLS` | | SSO | `SSO_PROVIDER`, `SSO_NAME`, `SSO_ENTITY_ID`, `SSO_SSO_URL`, `SSO_SLO_URL`, `SSO_X509_CERTIFICATE`, `SSO_ISSUER_URL`, `SSO_CLIENT_ID/SECRET`, `SSO_SCOPE`, `SSO_ATTRIBUTE_MAPPING`, `SSO_GROUPS_MAPPING`, `SSO_AUTO_PROVISION`, `SSO_ONLY`, `SSO_SIGN_REQUESTS`, `SSO_DEFAULT_WORKSPACE_ID` | | Agent/IA | `AGENT_ENABLED`, `LLM_PROVIDER/MODEL/API_KEY/API_BASE`, `AGENT_MAX_ITERATIONS`, `AGENT_MAX_TOKENS_BUDGET`, `AGENT_RUN_TIMEOUT_SECONDS`, `AGENT_MEMORY_DEFAULT`, `GOOGLE_CLIENT_ID/SECRET`, `MS_CLIENT_ID/SECRET`, `WEB_SEARCH_PROVIDER`, `EXA_API_KEY`, `GITHUB_TOKEN`, `WEB_FETCH_MAX_CHARS`, `STT_COMMAND` (transcription réunions) | ### 23.3 Données & sauvegardes Volume nommé `flowdeck_data` monté sur `/data` : base SQLite, uploads (dont `data/uploads/meetings/`), avatars, backups. **Service de backup interne** (`app/services/backup.py`, v5.2) : snapshot quotidien du fichier (checkpoint WAL avant copie), nommage `flowdeck-.db`, rétention `BACKUP_KEEP`. `docker-backups/` à la racine = copies hôtes manuelles de checkpoints avant opérations risquées (convention d'équipe, pas automatisée). --- ## 24. Outils de développement, tests & CI ### 24.1 Environnement - **Python 3.13** aligné partout (Docker, `pyproject.toml` target, CI — audit A35, v7.35) ; dépendances verrouillées avec **uv** (`uv.lock`, `requirements*.txt`) ; - Lint : **Ruff** (`pyproject.toml` : line-length 110, règles E/F/I/UP/B/W, isort first-party `app`/`tests`) + **black** ; **ESLint v9** flat config (`eslint.config.mjs`, 4 règles souples en warn sur `static/js`, ignores `*.min.js`/`vendor/`, via `npx --yes eslint static/js`) ; - Conventions (`CONTRIBUTING.md`) : Conventional Commits ; **code et commentaires en anglais, docs en français**. ### 24.2 Modèle de branches (`BRANCHING.md`) Git-flow simplifié : `main` (prod, tags `vX.Y.Z`, Docker auto sur :8080) ← `develop` (intégration) ← `feat/` / `fix/` ; **jamais de push direct** sur main/develop (PR only), CI verte avant merge, kebab-case, branche supprimée après merge ; hotfix depuis main puis resync `main → develop`. (Note : `CONTRIBUTING.md` dit encore `feature/xxx → main` — divergence à harmoniser.) ### 24.3 Pyramide de tests | Niveau | Outil | Volume | |---|---|---| | Unitaires / intégration | **pytest** (+ pytest-xdist `-n auto`, pytest-asyncio) dans `tests/` — TestClient FastAPI sur SQLite temporaire, isolation verrouillée par `conftest.py` (audit A1) | **~1 394 tests verts** (v7.68) ; couverture des routers portée de 0 à l'identifiable par l'audit **A32** | | E2E navigateur | **Playwright** (`e2e/`, `@playwright/test ^1.63`) ; specs nommées par version (`v768_columns_clipboard.spec.js`, `pwa_offline.spec.js`, `mobile_regression.spec.js`…) ; exigence de **vrais gestes** (Ctrl+C, drag) et **contrôles négatifs** (même spec contre ancien build) | fondations posées v7.36 | | Audit fonctionnel | `scripts/audit_functional.py` — bout-en-bout sur base isolée ; `scripts/_routescan.py` — inventaire des routes (677 routes auditées en v7.46) | récurrent | | Grille visuelle manuelle | `TESTING.md` (61 cas ; document daté v2.1, à rafraîchir) | — | ### 24.4 CI (`/.gitea/workflows/ci.yml`) Gitea Actions, 3 jobs sur push + PR → main/develop : `lint` (ruff + eslint), `test` (pytest `-n auto` + coverage, deps système WeasyPrint), `docker` (build + smoke `from app.main import app`). ### 24.5 Versionnage `scripts/bump_version.py` réécrit `version=` dans `app/main.py`, bump `VERSION` et crée le tag git. Le `CHANGELOG.md` est la source d'évolution de référence (les bannières README/WORKLOAD sont figées plus tôt — voir §26). --- ## 25. Historique des versions v1.0 → v7.70.0 Version courante : **7.70.0** (`VERSION`). Bandes thématiques vérifiées contre les titres réels du `CHANGELOG.md` (⚠️ le CHANGELOG saute v2.8→v4.5, documentées uniquement dans `ROADMAP.md` ; plusieurs versions portent un titre « bande roadmap » décalé — ex. `## v5.11.7 — v5.8.0 Calendrier & Rappels` — et trois 7.46.0 distinctes coexistent). ### Socle v1.0 – v2.x (résumé, inchangé du doc v4.0) - **v1.0–v1.2** — clone de board Kanban Gitea (boards, cartes, colonnes, notes) ; UI Notion-style. - **v1.3–v1.5** — Collections indépendantes, 21 types de propriétés, relations, rollups (12 agrégations), formules (19 fonctions), GiteaBoardCompat. - **v1.6–v1.7** — vues multiples (Table/Board/Calendar/Gallery/List/Timeline/Status Overview/Team Load). - **v1.8** — sub-items + dépendances Blocking/Blocked by. **v1.9** — My Tasks. - **v2.0** — workspaces + rôles, commentaires, historique de page, favoris, templates, import/export CSV, partage public. **v2.1–v2.7** — polish, pages privées Gitea, workspaces multiples. ### v4.x — Plateforme comptes & partage - **v4.0.0** — Accounts, Integrations & Sharing (MVP) : types de comptes, matrice d'intégrations, Library, share/publish, trash (§6, §15). - **v4.0.1–v4.0.2** — Onboarding & polish ; qualité/robustesse. **v4.1–v4.2** — data sources & linked databases ; templates & dashboards. **v4.3** — Database Views complètes (10 types). **v4.4–v4.5** — tasks/sub-items/dependencies ; sprints & My Tasks. - **v4.6–v4.9** — callouts, TOC, KaTeX self-hosté, multi-colonnes, toggles ; **export MD/PDF/HTML/site-zip** ; bloc tableau ; commentaires inline + @mentions + notifications (+ SMTP). - **v4.10–v4.15** — **naissance de l'agent** : moteur ReAct natif (§16.2), config LLM dans l'UI, clés par utilisateur + multi-fournisseurs dynamiques, panneau « Notion AI » Ctrl+J, contexte universel, AI Meeting Note (ébauche), mentions @ et skills `/` dans le panneau. ### v5.x — Socle moderne - **v5.0–v5.2** — palette Ctrl+K + **FTS5** ; **migrations versionnées** `app/migrations.py` ; collage intelligent, inline databases, templates de bases, validation de propriétés ; moteur **d'automatisations if-this-then-that** ; sessions révocables + tokens API ; backup interne. - **v5.3–v5.9** — **temps réel WebSocket** (v5.3, ébauche LWW + polling) ; interactions de bloc (undo/redo, menu ⋮, drag multi-sélection) ; partage réorganisé ; **database avancée** (personnes, vues sauvegardées, swimlanes + WIP limits, calendar drag & drop, gallery) ; **calendrier & rappels** (récurrences RRULE, fuseaux) ; **import 6 phases** ; **AI Writing** dans l'éditeur. - **v5.11–v5.15** — wiki-links `[[` + chips, mentions page/date ; templates et verrouillage de page, pleine largeur ; **synced blocks** ; webhooks v2. ### v6.x — Plateforme ouverte & entreprise - **v6.0–v6.2** — **PWA offline** (SW + IndexedDB + sync) ; **permissions granulaires** (ACL page/collection/propriété + groupes + audit) ; **Web Clipper MV3**. - **v6.3–v6.6** — **API publique REST `/api/v2`** complète (Bearer + scopes, RFC 7807, idempotence, OpenAPI) ; **temps réel en production** (merge 3-voies) ; synced blocks en prod (page-ombre de ligne) ; **API agent publique** (run synchrone JSON, rollback, trigger externe) + **marketplace de skills**. - **v6.7–v6.9** — **SSO entreprise SAML + OIDC** (provisioning, mapping groupes, SSO only) ; **Sites & Forms** publics ; **recherche hybride + Ask AI** (embeddings maison, RAG citations). ### v7.x — Automatiser, gouverner, polir - **v7.0–v7.3** — **Automations v2** (steps chaînés) + **Workers** sandboxés ; **Calendar sync** Google/CalDAV + **AI Meeting Notes** complètes ; **enterprise admin** (SCIM 2.0, 2FA TOTP, passkeys WebAuthn, domain claims, audit unifié, **gouvernance agent**) ; **Wiki/Teamspaces** (badges vérifiés, follows, guests, réactions, analytics). - **v7.3.1–v7.35** — **cycle d'audit** (43 items A1–A43, `ROADMAP.md`) : sécurité P0→P2 (401 partout, CSRF A19/A38, SSRF A12, autoescape A10, logs A36, CSP A20/A43), A21 SQLite hors event loop, A32 tests routers, A27 extraction JS (−85 %), A28 routers → packages, A35 Python 3.13, A42 httpx partagé. - **v7.36–v7.50** — fondations **E2E Playwright** ; **A20 terminé : Alpine build CSP, `unsafe-eval` retiré** (v7.43) ; éditeur visuel d'automations (v7.45) ; palette avec onglet Réponses IA ; navigation partielle anti-FOUC ; **web tools de l'agent** + 11 skills (v7.46) ; **sidebar à onglets** Home/Chat/Meeting/Inbox ; **My Tasks 3 vues transverse** ; **side peek** plein écran. - **v7.51–v7.58** — **Menu + de l'assistant** en 8 phases : hub de contexte, skills gérer/parcourir, canevas Design System, mémoire d'agent, connecteurs (socle → Google/M365 OAuth lecture seule → Discord/Telegram/Teams/**MCP**), plugins catalogue on/off à effet réel. - **v7.59–v7.70.0** — éditeur **façon Notion** : mobile (drawer réglable, menus au `/`), bouton + d'insertion, menus contextuels de bloc et de sélection refondus, **commentaires ancrés sur la sélection de texte** (surlignage jaune, tiroir refondu), **réactions sur texte + tiroir**, **copier/couper/coller multi-blocs**, colonnes réellement rendues, tables GFM, identité visuelle (logo/bannière/icônes PWA/`og:image`), **Library** (onglet AI Meeting Notes groupé par date, Recents réels, Publish unifié, colonnes visited/source) ; **projet Gitea** : miroir local ouvert en parallèle du dépôt, sidebar Repository distincte. --- ## 26. Chantiers ouverts & suites Extraits de `ROADMAP.md` (audit du 2026-09-30) et `WORKLOAD.md` : - **Cycle v6 et cycle v7 (v6.8→v7.3) livrés intégralement** ; `WORKLOAD.md` : 52/52 features, tableau de bord **figé à v7.3.0** (les ~36 versions suivantes ne sont suivies que par le CHANGELOG). - **Audit A1–A43 quasi clos**. Restes ouverts / décisions assumées : - **A38 phase 3** : fusion des `workspace-tree.js` Library / local_workspace **reportée** (pas de couverture E2E suffisante pour risquer la refactorisation) ; - **A39** : htmx **conservé** (décision explicite — pas de remplacement) ; - migration complète des call sites SQLite sync → async (A21) encore partielle hors chemins chauds ; - mono-processus assumé : rooms temps réel, challenges WebAuthn, store SSO env → multi-worker non supporté sans refonte. - **Faiblesses documentaires connues** : bannières `README.md` (cite 7.3.9 / 764 tests) et `TESTING.md` (v2.1) à rafraîchir ; divergence BRANCHING ↔ CONTRIBUTING sur la cible de merge des features. - **Pistes naturelles de la suite du produit** (non planifiées formellement) : serveur MCP officiel (`docs/FLOWDECK_MCP_SERVER_GUIDE.md`), export de masse enrichi, collaboration avancée (suggestions d'édition étendues), PostgreSQL si un jour un dual-engine est décidé (aujourd'hui **non supporté**, §5.1). --- ## 27. Références | Document | Contenu | |---|---| | `docs/DATA_MODEL.md` | **modèle de données exhaustif** (105 tables, colonnes, FK, JSON, relations) | | `docs/API_GUIDE_V6.md` + `docs/openapi-v2.json` + `/docs` | API publique v2 (guide + spécification OpenAPI vivante) | | `docs/Guide_Complet_Notion_database.md`, `NOTION_DATABASE_TASKS_GUIDE.md` | systèmes de bases et de tâches | | `docs/Guide_Complet_Notion_AI.md`, `Guide_Complet_Notion_Agent_2026.md`, `Flowdeck_Agent_integration.md` | benchmark IA, design agent | | `docs/Guide_Complet_Notion_sharing_collaborartion.md` | partage & collaboration | | `docs/V6_SSO_SAML_Enterprise_Auth.md`, `V6_Granular_Permissions.md`, `V6_PWA_Progressive_Web_App.md`, `V6_Web_Clipper.md` | features v6.x | | `docs/V68_Sites_Forms.md`, `V69_Search_Ask_AI.md`, `V70_Automations_Workers.md`, `V71_Calendar_Meetings.md`, `V72_Enterprise_SCIM_2FA.md`, `V73_Wiki_Teamspaces_Polish.md`, `V74_Agent_Plus_Menu.md` | features v6.8–v7.4 | | `docs/architecture-meeting-notion-flowdeck.md`, `docs/FLOWDECK_MCP_SERVER_GUIDE.md` | spec Meeting Notes ; conception serveur MCP (non implémentée) | | `CHANGELOG.md` | journal détaillé v7.36→v7.70.0 (et au-delà, désordonné — voir §25) | | `ROADMAP.md` | plans v2.8–v4.5 + **audit A1–A43** + suivi des cycles v6/v7 | | `WORKLOAD.md`, `TESTING.md`, `CONTRIBUTING.md`, `BRANCHING.md` | pilotage, grille visuelle, conventions, branches | --- *Ce document décrit l'état du projet à la version 7.70.0 (2026-10-09). Les affirmations marquées « factuel » ou « ⚠️ » signalent des écarts connus entre le code et les documents de pilotage plus anciens.*