Files
flowdeck/docs/architecture-vues-notion-flowdeck.md
bruno fc8548194a
FlowDeck CI / lint (push) Failing after 1m32s
FlowDeck CI / test (push) Failing after 27m50s
FlowDeck CI / docker (push) Skipped
feat(templates): refonte complète des templates façon Notion + vues/agents-skills
Templates (v7.71.x) :

- registre unifié \	emplates\ (migrations 48-49) + TemplateService.instantiate unique (UI, API v2, agent, scheduler)

- sélecteur (pilule page vide, menu •••, commande /template), gestionnaire /templates, menu New ▾, From template, base inline dans un document

- 141 presets système (59 pages, 42 bases, 15 blocs, 25 lignes), titre auto depuis le template, variables title réservée

- récurrences RRULE + scheduler dédupliqué, agent apply_template/list_templates, API /api/templates + /api/v2/fd-templates

- correctifs : bouton Templates, centrage fenêtre, filtres CSP, flux de création, variable title

- tests : tests/test_fd_templates.py (19) et e2e/templates_picker.spec.js (8)

Inclut le travail déjà présent dans le working tree (vues Notion : view_query/view_aggregate/form_projection/geocoding, property_types, database_table, docs agents-skills) et ignore .playwright-mcp/.
2026-10-10 18:52:19 -04:00

1349 lines
142 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Architecture — Vues de bases de données « Chart, Dashboard, Timeline, Feed, Map, Form » (modèle Notion) pour Flowdeck
| Champ | Valeur |
|---|---|
| Version | 1.0 |
| Date | 10 octobre 2026 |
| Auteur | Spark, pour Bruno |
| Statut | Proposition d'architecture — à valider contre le code réel de Flowdeck |
| Produit cible | Flowdeck **v7.69.8** (monolithe FastAPI / SSR Jinja2 + htmx + Alpine.js CSP, SQLite WAL, API publique v2) — d'après `ARCHITECTURE.md` fourni par Bruno le 9 octobre 2026 |
| Références fonctionnelles | Les six vues de bases de données de Notion documentées dans son centre d'aide : *Dashboards*, *Charts*, *Timelines*, *Feeds*, *Maps*, *Forms* (liens fournis par Bruno le 10 octobre 2026), plus les **8 captures d'écran** de sa propre base « Todo » dans Notion, fournies le même jour et conservées dans `vues-notion-reference/` |
| Documents compagnons | `architecture-meeting-notion-flowdeck.md` (v1.2) et `architecture-agents-skills-notion-flowdeck.md` (v1.1) — même méthode, même ancrage v7.69.8 |
> **Historique des versions**
>
> - **1.0 — 10 octobre 2026** : analyse fonctionnelle des six vues d'après la documentation publique de Notion et les 8 captures de Bruno, état des lieux Flowdeck ancré sur le §12 (*Système de vues*), le §11 (*Databases*), le §15 (*Partage, Library, My Tasks*) et le §22 (*Frontend*) de son architecture v7.69.8, puis architecture cible complète : unification des vues dans le composant client `DBInstance`, moteur d'agrégation partagé, type de propriété `place`, vue Dashboard à widgets, et Form builder v2. Migrations proposées **44 à 47**, reséquencées pour passer **après** les migrations 39 à 43 déjà proposées par le document Agents & Skills (voir §10.1 — le moteur de migrations n'applique que les versions supérieures à la version courante, actuellement **38**).
> **Avertissement méthodologique — à lire avant tout**
>
> L'architecture interne de Notion est propriétaire et n'est pas publique. Ce document ne prétend donc pas décrire « comment Notion est construit en interne ».
>
> Il contient trois choses distinctes, clairement séparées :
>
> 1. **Une analyse fonctionnelle** (section 3) de ce que font les six vues dans Notion, établie à partir de sa documentation publique officielle consultée le 10 octobre 2026 et des captures de Bruno. C'est le *comportement observable* du produit, résumé avec mes mots.
> 2. **Un état des lieux Flowdeck** (section 4) tiré du document d'architecture v7.69.8 fourni par Bruno : ce qui existe déjà, ce qui existe partiellement, ce qui manque. C'est le point le plus important de ce document : **contrairement aux Meetings et aux Agents, les six vues ne sont pas absentes de Flowdeck — elles existent déjà comme rendus serveur de second rang**. Le travail est donc une **promotion** et une **unification**, pas une création ex nihilo. Toute la conception en découle.
> 3. **Une architecture cible originale** (sections 5 à 23) pour porter les six vues au niveau d'expérience de Notion. Les choix techniques, le modèle de données, les API et les maquettes sont des propositions qui respectent les invariants de Flowdeck (monolithe, SQLite, SQL brut, zéro bundler, libs vendorisées, CSP stricte sans `unsafe-eval`, un seul processus), pas une reproduction de l'existant Notion.
>
> **Note de vocabulaire.** On garde les noms anglais des vues (*Chart*, *Dashboard*, *Timeline*, *Feed*, *Map*, *Form*) parce que ce sont déjà les `view_type` utilisés par Flowdeck (§12.2 de son architecture). *Form builder* désigne l'écran de construction du formulaire, par opposition au formulaire public rempli par les répondants. *Widget* désigne un bloc d'un Dashboard qui affiche une vue de base de données. *Place* désigne le nouveau type de propriété géographique proposé en §13.
---
## Table des matières
1. [Résumé exécutif](#1-résumé-exécutif)
2. [Périmètre, personas et cas d'usage](#2-périmètre-personas-et-cas-dusage)
3. [Analyse fonctionnelle : les six vues dans Notion](#3-analyse-fonctionnelle--les-six-vues-dans-notion)
3A. [Parcours visuel — les 8 captures de la base « Todo » de Bruno](#3a-parcours-visuel--les-8-captures-de-la-base--todo--de-bruno)
3B. [Maquettes ASCII des six vues — disposition des composants](#3b-maquettes-ascii-des-six-vues--disposition-des-composants)
4. [État des lieux Flowdeck v7.69.8 et analyse d'écart](#4-état-des-lieux-flowdeck-v7698-et-analyse-décart)
5. [Principes directeurs pour Flowdeck](#5-principes-directeurs-pour-flowdeck)
6. [Vue d'ensemble du système (C4)](#6-vue-densemble-du-système-c4)
7. [Architecture front-end : le registre de vues de `DBInstance`](#7-architecture-front-end--le-registre-de-vues-de-dbinstance)
8. [Architecture back-end : service de requêtes de vues et moteur d'agrégation](#8-architecture-back-end--service-de-requêtes-de-vues-et-moteur-dagrégation)
9. [Vue Chart](#9-vue-chart)
10. [Modèle de données et migrations 44 à 47](#10-modèle-de-données-et-migrations-44-à-47)
11. [API et événements](#11-api-et-événements)
12. [Vue Timeline](#12-vue-timeline)
13. [Vue Map et type de propriété `place`](#13-vue-map-et-type-de-propriété-place)
14. [Vue Feed](#14-vue-feed)
15. [Vue Dashboard](#15-vue-dashboard)
16. [Vue Form et Form builder](#16-vue-form-et-form-builder)
17. [Sécurité, permissions et vie privée](#17-sécurité-permissions-et-vie-privée)
18. [Exigences non fonctionnelles](#18-exigences-non-fonctionnelles)
19. [Résilience et gestion des échecs](#19-résilience-et-gestion-des-échecs)
20. [Déploiement et exploitation](#20-déploiement-et-exploitation)
21. [Plan d'implémentation par phases](#21-plan-dimplémentation-par-phases)
22. [Décisions d'architecture (ADR — résumé)](#22-décisions-darchitecture-adr--résumé)
23. [Risques et mitigations](#23-risques-et-mitigations)
24. [Questions ouvertes pour Flowdeck](#24-questions-ouvertes-pour-flowdeck)
- [Annexe A — Correspondance Notion → Flowdeck, vue par vue](#annexe-a--correspondance-notion--flowdeck-vue-par-vue)
- [Annexe B — Schémas `config_json` de référence par vue](#annexe-b--schémas-config_json-de-référence-par-vue)
- [Annexe C — Glossaire](#annexe-c--glossaire)
- [Sources](#sources)
---
## 1. Résumé exécutif
Dans Notion, une base de données n'a pas « une » présentation : elle a un **catalogue de vues interchangeables**, créables depuis le même menu `+`, sauvegardées comme des onglets côte à côte, et qui partagent toutes le même système de filtres, tris, groupes et visibilité des propriétés. Les captures de Bruno le montrent sur sa base « Todo » : *Table, Chart, Dashboard, Timeline, Feed, Map* et *Form builder* y sont des onglets d'égale dignité, au même niveau que *Table*.
**Le constat Flowdeck est contre-intuitif, et il change tout le plan.** D'après son architecture v7.69.8 (§12.2), Flowdeck sait *déjà rendre* les six vues :
| Vue | État décrit en v7.69.8 |
|---|---|
| Chart | Rendu serveur (`_renderers.py`, dispatch `?view_type=…`) : bar / line / pie / doughnut / scatter via Chart.js vendorisé, plus widgets KPI (count / sum / avg / min / max), groupes plafonnés par `CHART_MAX_GROUPS` |
| Timeline | Rendu serveur : barres horizontales type Gantt — et une vue *Gantt* distincte en plus |
| Map | Rendu serveur : Leaflet vendorisé, géocodage `lat,lng` **depuis les propriétés existantes** |
| Feed | Rendu serveur : flux chronologique |
| Form | Rendu serveur : formulaire générateur de lignes, qui alimente aussi les formulaires publics `/f/<token>` (v6.8) avec validation, rate limiting et événement `form.submitted` |
| Dashboard | `collection_dashboards.layout_json` : combinaison de plusieurs vues/widgets en colonnes **sur une page** |
Mais ces rendus vivent sur une **deuxième surface**, séparée de la surface principale : le composant client `DBInstance` (`static/js/database_table.js`), celui qui sert les pages `/db/{id}`, les blocs `database` de l'éditeur et les vues sauvegardées (`collection_views` + `config_json`, §12.1), ne connaît que **cinq** vues interactives — *Table, Board, Calendar, Gallery, List*. Conséquences concrètes pour l'utilisateur, qui correspondent exactement à ce que Bruno constate (« il me manque ces vues ») :
- on ne peut pas les **créer** depuis le menu `+` d'une base, ni les retrouver comme onglets sauvegardés au même titre que Table ou Board ;
- leur configuration n'est pas le `config_json` unifié (filtres / tris / groupes / propriétés visibles) éditable par les mêmes panneaux de réglages ;
- elles ne sont pas **éditables de façon interactive** au niveau des autres (pas de drilldown de graphique, pas de drag des dates en Timeline, pas de commentaires dans le Feed…) ;
- deux d'entre elles reposent sur des fondations incomplètes par rapport à Notion : la Map n'a **pas de type de propriété `place`** (Flowdeck compte 21 types, §11.2 — Notion en a un dédié, avec saisie par nom/adresse et localisation courante), et le Form n'a **pas de builder intégré à la vue** (questions synchronisées aux propriétés, logique conditionnelle, écran de confirmation, propriété répondant).
**Les six écarts structurants** (détaillés en section 4) :
| # | Écart | Nature du travail |
|---|---|---|
| **E1** | **Deux surfaces de vues disjointes** : 5 vues clientes interactives d'un côté, 6+ vues SSR de l'autre, sans registre commun ni création unifiée | **Unification** : registre déclaratif de vues dans `DBInstance`, migration des renderers SSR en modules clients alimentés par une API de données commune, `config_json` versionné par type de vue (§7, §8) |
| **E2** | **Pas de moteur d'agrégation exposé en API** : le Chart SSR calcule côté serveur au rendu de la page ; il n'existe pas d'endpoint d'agrégation réutilisable (grouper / sous-grouper / cumuler / limiter) que le client, les widgets de Dashboard et les drilldowns partageraient | **Service d'agrégation** unique, côté serveur, en SQL sur `property_values_json` (`json_extract`), avec plafonds (200 groupes / 50 sous-groupes, alignés sur Notion) et contrat de drilldown (§8.3, §9) |
| **E3** | **Aucun type `place`** : la Map géocode des coordonnées bricolées dans des propriétés texte/nombre, sans saisie assistée, sans cache, sans fournisseur déclaré | **22ᵉ type de propriété** `place` (valeur JSON structurée), service de géocodage à fournisseur branchable avec cache et garde-fous vie privée, autocomplétion dans l'éditeur de cellule (§13) |
| **E4** | **Le Dashboard n'est pas une vue** : c'est une mise en page de page (`collection_dashboards`), sans modes Lecture/Édition, sans widgets référençant des vues d'**autres** bases, sans filtres globaux multi-sources | **Vue `dashboard`** de première classe : widgets = références à des vues (même base ou autre source via `collection_data_sources`), grille lignes × colonnes (max 4 par ligne, 12 au total), modes View/Edit, filtres globaux (§15) |
| **E5** | **Le Form builder est hors de la vue** : les formulaires publics v6.8 fonctionnent, mais il n'y a pas l'écran de construction *dans* l'onglet de la base — questions ↔ propriétés synchronisées dans les deux sens, caractère requis, descriptions, types de question, logique conditionnelle, écran de soumission, aperçu, panneau de partage gradué | **Form builder v2** intégré à la vue `form`, modèle de questions persisté, réutilisant le pipeline public `/f/` existant pour la soumission (§16) |
| **E6** | **Timeline et Feed sont des rendus, pas des interactions** : pas de redimensionnement des barres par glisser des bords, pas d'échelles heure→année unifiées, pas de table latérale avec calculs (Timeline) ; pas de cartes avec corps de page, réactions, commentaires en ligne et compteur de vues (Feed) | **Interactivité ciblée** sur les deux vues : drag de dates branché sur l'écriture de propriété existante, table latérale réutilisant les calculs de Table ; cartes Feed alimentées par la page-ombre de ligne (v6.5), les commentaires polymorphes et `page_views` déjà présents (§12, §14) |
**Recommandation principale.** Ne pas écrire six vues nouvelles : **promouvoir les six rendus existants dans le registre de vues du client, derrière un service de données commun**. Le seul vrai développement « neuf » est concentré sur trois objets : le service d'agrégation (E2), le type `place` (E3) et le modèle de widgets du Dashboard (E4). Le plan en 5 phases (§21) ordonne le travail pour que chaque phase rende les suivantes moins chères : le Chart d'abord (il force la création du service d'agrégation et du registre), Timeline et Feed ensuite (gains rapides sur le socle), Map avec `place`, Dashboard quand toutes les vues-widgets existent, Form builder en dernier (il touche à la surface publique et à sa sécurité). Aucune phase n'exige de nouvelle table de lignes : le schéma déclaratif + valeurs JSON de Flowdeck (§11.1) absorbe l'essentiel ; quatre migrations ciblées (44 à 47, §10) suffisent.
---
## 2. Périmètre, personas et cas d'usage
### 2.1 Dans le périmètre
**Socle commun aux six vues**
- Création de chacune des six vues depuis le menu `+` de la barre d'onglets d'une base (pleine page, inline dans l'éditeur, bloc `database` d'un document), avec nom, icône et duplication — au même titre que Table/Board/Calendar/Gallery/List.
- Sauvegarde comme `collection_views` (partagée si `created_by` est NULL, personnelle sinon — mécanique de la migration 10, inchangée), configuration persistée par `PUT /db/views/{id}/config`.
- Panneaux de réglages communs : filtres (conjonction `and`/`or`), tris, visibilité et ordre des propriétés, recherche dans la vue — les mêmes pour les six vues, avec en plus un panneau propre à chaque vue (§7.4).
- Respect des permissions existantes : ACL de collection et de propriété (§9.2 de l'architecture Flowdeck) appliquées **avant** tout calcul — une vue n'agrège, ne géocode et n'affiche jamais une propriété que l'utilisateur n'a pas le droit de voir.
- Fonctionnement dans les trois surfaces : page `/db/{id}`, vue liée (`collection_data_sources`, base liée v4.1) et **vue de site publiée** (`site_views`, lecture seule).
**Par vue** (le détail fonctionnel est en section 3, la conception dans les sections 9 et 12 à 16)
- **Chart** : 5 types (barres verticales, barres horizontales, lignes, donut, nombre/KPI) ; axes configurables, regroupement et sous-regroupement, cumul, groupes masquables, styles ; **drilldown** en tableau au clic sur un segment ; export PNG/SVG ; aucune édition de ligne depuis le graphique.
- **Timeline** : échelles de l'heure à l'année, marqueur « aujourd'hui », glisser-déplacer des barres et de leurs bords (écriture des dates), choix de la propriété de tracé (plage unique ou début/fin séparés), seau « sans date », table latérale affichable avec calculs de colonnes, limite de chargement.
- **Feed** : cartes empilées avec en-tête auteur/date d'édition, titre, propriétés visibles, aperçu du contenu de la page-ombre, réactions, commentaires en ligne, compteur de vues ; défilement paginé par curseur.
- **Map** : type de propriété `place`, épingles interactives (clic → ouverture de la ligne), zoom/déplacement, choix de la propriété de tracé s'il y en a plusieurs, plafond d'affichage, filtres textuels sur nom/adresse.
- **Dashboard** : vue composée de widgets affichant chacun une vue (de la même base ou d'une autre source), grille de lignes (4 widgets/ligne, 12 max), modes Lecture et Édition, redimensionnement largeur/hauteur, duplication/suppression de widget, **filtres globaux** multi-sources, chargement parallèle borné.
- **Form** : builder intégré (titre, description, icône/couverture, questions), synchronisation bidirectionnelle question ↔ propriété (avec possibilité de désynchroniser le libellé), questions requises, descriptions d'aide, types de question alignés sur les types de propriétés, nombre maximal de sélections, logique conditionnelle simple, écran de soumission personnalisable, aperçu, partage gradué (membres du workspace / web public / fermé), réponses anonymes ou attribuées (propriété répondant), accès du répondant à sa soumission, automatisations sur soumission (déjà possibles via `form.submitted`).
### 2.2 Hors périmètre (assumé)
- **Paywall / plans** : Notion réserve le Dashboard aux plans Business/Enterprise, limite le Chart gratuit à un graphique et la logique conditionnelle des formulaires aux plans payants. Flowdeck est auto-hébergé et n'a pas de notion de plan : **on ne reproduit aucune de ces limites commerciales**. Les seuls garde-fous retenus sont techniques (plafonds de performance) et de permission (qui peut éditer).
- **Notion Calendar** : l'intégration « gérer la Timeline dans le calendrier Notion » est hors sujet ; Flowdeck a déjà sa sync calendrier (§18.2 de son architecture) et sa vue Calendar cliente. Le bouton équivalent, s'il est souhaité, est un simple renvoi vers la vue Calendar de la même base (§12.6, question ouverte Q4).
- **Géocodage de masse et calcul de distances** : Notion ne calcule pas de distance entre lieux ; Flowdeck non plus dans ce chantier. Pas d'itinéraires, pas de géofencing, pas de heatmap en v1 des vues (points d'extension notés en §13.7).
- **Graphiques avancés** : pas de graphiques combinés, pas d'axes doubles, pas de pivot chart libre au-delà de groupe + sous-groupe ; le type *scatter* SSR existant est conservé mais n'est pas promu en type configurable v1 (§9.2).
- **Constructeur de rapports libre** : le Dashboard compose des vues existantes ; ce n'est ni un éditeur de page (les colonnes de pages existent déjà pour le libre-forme), ni un outil BI (pas de SQL utilisateur, pas de jointures libres — les relations et rollups existants font foi).
### 2.3 Personas
| Persona | Ce qu'elle attend des six vues |
|---|---|
| **Bruno, en gestion de projets dans Flowdeck** (rôle clarifié le 10 octobre 2026 : Flowdeck = outil de gestion de projets) | Sur une base de tâches : un Chart d'avancement par statut, une Timeline de jalons déplaçable à la souris, un Dashboard « état du projet » ouvert le matin, un Feed des comptes rendus |
| **Membre d'équipe** | Consommer un Dashboard en mode Lecture sans risquer de casser sa mise en page ; remplir un formulaire interne sans accès à la base complète ; commenter une carte de Feed |
| **Répondant externe** (formulaires publics) | Remplir un formulaire web sans compte, recevoir un écran de confirmation clair, éventuellement retrouver sa soumission selon le réglage choisi |
| **Administrateur de workspace** | Contrôler le fournisseur de géocodage (ou le désactiver), interdire le partage web des formulaires, voir quelles vues personnelles/partagées existent |
| **Agent IA de Flowdeck** (lien avec le document Agents & Skills) | Créer et ajuster des vues par les mêmes API : générer un Dashboard de premier jet, ajouter un widget, créer un Chart — Notion expose exactement ce rôle à son Agent ; l'API de vues doit donc être complète et scriptable (§11.4) |
### 2.4 Cas d'usage de référence (servant aux tests d'acceptation, §21)
1. **CU-Chart** — Sur la base Todo de référence (6 lignes, statuts : 4 *Not started*, 1 *In progress*, 1 *Done*), un donut par `Status` affiche 3 secteurs (66,7 % / 16,7 % / 16,7 %), le total `6` au centre ; cliquer le secteur *Not started* ouvre un drilldown listant les 4 lignes.
2. **CU-Timeline** — La ligne datée du 22 au 27 octobre 2026 y apparaît comme une barre ; ses bords se tirent pour changer les dates ; les 5 lignes sans date sont comptées dans le seau « No date (5) » et restent accessibles.
3. **CU-Map** — Les lignes portant un lieu (McMasterville, Beloeil, Montréal, France, Washington DC…) apparaissent en épingles ; un clic ouvre la ligne ; au-delà de 100 lignes géocodées, la vue demande de filtrer.
4. **CU-Feed** — Les mêmes lignes s'affichent en cartes empilées, auteur et ancienneté d'édition en en-tête, commentaire ajoutable sans ouvrir la page.
5. **CU-Dashboard** — Un Dashboard « Projet » combine le donut de statut, une table des tâches prioritaires et une Timeline de jalons ; un filtre global sur `Assignee` s'applique aux widgets qui possèdent cette propriété et ignore les autres.
6. **CU-Form** — Un formulaire « Demande » créé depuis la base génère ses questions depuis les propriétés ; renommer une question renomme la propriété (sauf désynchronisation explicite) ; une soumission web anonyme crée une ligne et déclenche l'automatisation `form.submitted`.
---
## 3. Analyse fonctionnelle : les six vues dans Notion
*Rappel : cette section résume, avec mes mots, le comportement documenté publiquement par Notion et visible sur les captures de Bruno. Aucune architecture interne de Notion n'est connue ni supposée.*
### 3.0 Le socle commun : une vue est un onglet sauvegardé
Les six vues partagent le modèle général des vues de bases de données Notion :
- elles se créent par le bouton `+` à côté des onglets existants (ou par commande slash dans une page : `/chart`, `/timeline view`, `/feed view`, `/map`, `/form`, `/dash`), en choisissant un type dans un sélecteur qui présente, à égalité : *Table, Board, Gallery, List, Chart, Dashboard, Timeline, Feed, Map, Calendar, Form* — plus l'ajout d'une nouvelle source de données (capture 03) ;
- elles héritent des réglages communs : filtres, tris, groupes, visibilité des propriétés, réglages accessibles par l'icône des curseurs en haut à droite de la base ;
- les modifications de filtres/tris faites en consultation ne sont pas forcément conservées pour tout le monde : Notion distingue l'état local de l'utilisateur et l'enregistrement explicite pour tous par un éditeur autorisé ;
- chaque vue suppose parfois un prérequis de schéma : la Timeline exige au moins une propriété date (avec plage) ; la Map exige une propriété `place` — créée automatiquement si elle manque ; le Form exige un accès complet à la base pour être créé depuis celle-ci.
### 3.1 Chart
- **Types** : barres verticales, barres horizontales, lignes, donut, et graphique « nombre » (une valeur unique mise en avant).
- **Création** : comme vue d'une base existante, ou par `/chart` dans une page en liant une base — un graphique peut donc vivre hors de sa base, par exemple parmi d'autres dans une page-tableau de bord libre.
- **Configuration des barres/lignes** : axe X = propriété à représenter (par ex. le statut), tri des groupes, groupes visibles/masqués, omission ou non des valeurs à zéro ; axe Y = *Count* ou une propriété, avec regroupement secondaire optionnel ; mode **cumulatif** possible quand l'axe Y est un compte ou une somme et l'axe X trié en ordre croissant.
- **Configuration du donut** : la propriété affichée, la propriété qui définit les parts, le tri et la visibilité des groupes.
- **Style** : palette de couleurs, hauteur (de petite à très grande), lignes de grille, noms d'axes, étiquettes de données, ligne lissée et aire en dégradé (lignes), valeur au centre et « couleur selon la valeur » (donut/barres), légende affichable/masquable.
- **Interactions** : survol avec infobulle ; clic sur un élément ou une entrée de légende pour isoler/masquer des groupes ; **clic dans un groupe = drilldown**, présenté comme un tableau des lignes concernées (on y ouvre ensuite chaque page pour l'éditer ; actions de masse, colonnes figées, calculs et création de ligne n'y sont pas disponibles) ; le drilldown peut être **enregistré comme une vue** dans une page.
- **Export** : le graphique s'exporte en image (copie ou téléchargement PNG, téléchargement SVG), avec choix d'arrière-plan dans les plans payants.
- **Limites documentées** : 200 groupes et 50 sous-groupes affichés au maximum ; certaines propriétés ne sont pas représentables (rollups, boutons, identifiants uniques, fichiers/médias, certaines formules — notamment celles qui rendent des listes) ; la vue n'est **pas éditable** (aucune modification de ligne depuis le graphique) ; les sous-éléments ne se reflètent dans le graphique que si la base les affiche en liste aplatie.
### 3.2 Dashboard
- **Nature** : une vue qui transforme une base en centre de contrôle « en un coup d'œil », en agençant des **widgets** — chacun affiche une vue de base de données (table, board, calendrier, graphique, timeline…) — dans une grille de lignes. Les widgets peuvent provenir d'**une ou plusieurs bases**.
- **Disponibilité** : plans Business et Enterprise chez Notion (la capture 04 de Bruno montre l'écran d'invitation à la mise à niveau : sans le plan, la vue est visible mais ni l'ajout de widgets ni l'édition de la disposition ne sont possibles).
- **Création** : par `+` dans la base, par `/dash` dans une page, manuellement ou par génération d'un premier jet par l'Agent Notion, qu'on ajuste ensuite. La création débouche directement en **mode Édition**.
- **Widgets** : ajout par le `+` d'une ligne ou en bas de page, en choisissant une vue existante ou en en créant une pour le tableau de bord ; duplication et suppression par le menu du widget (clic droit ou clic sur son titre) ; **plafonds : 4 widgets par ligne, 12 widgets au total**.
- **Disposition** : déplacement des widgets entre lignes et dans une ligne par glisser-déposer (ou par menu) ; largeur réglée en tirant la poignée entre deux widgets ; hauteur de ligne réglée en tirant le séparateur entre deux lignes.
- **Contenu du widget** : ses propres réglages de vue (filtres, tris, groupes, type de visualisation) s'éditent depuis le widget ; si la vue sous-jacente est partagée, la modification peut se répercuter partout où elle est utilisée.
- **Filtres globaux** : un filtre peut s'appliquer à plusieurs widgets à la fois, y compris de sources différentes ; il ne touche que les widgets dont la vue comporte la propriété filtrée ; plusieurs filtres globaux peuvent coexister.
- **Deux modes** : *Lecture* (consommer : ouvrir les pages, utiliser les filtres/tris/groupes visibles dans un widget, interagir avec les éléments selon ses permissions) et *Édition* (concevoir : widgets, lignes, hauteurs, choix des vues). Les changements de filtres faits en Lecture restent locaux sauf enregistrement explicite pour tous par un éditeur.
- **Permissions** : celles de la base sous-jacente ; éditer la disposition exige un accès en édition à la base ; en lecture seule, on ne peut que consulter en mode Lecture.
- **Performance (guidance Notion)** : les tableaux de bord chargent beaucoup de données ; Notion recommande des widgets ciblés et filtrés, d'éviter les grosses tables non filtrées, et de traiter les widgets comme des points d'entrée vers le détail.
### 3.3 Timeline
- **Nature** : projection chronologique des lignes sur une frise, pour suivre l'avancement d'un projet dans le temps ; fonctionne avec toute base ayant au moins une propriété date contenant des plages — sans elle, rien n'est tracé.
- **Échelles** : réglables de l'**heure** à l'**année** via un sélecteur d'unité à côté du bouton *Today* (la capture 05 montre l'échelle *Quarter* — trimestre — avec les mois d'août à novembre 2026 et le marqueur du jour courant, le 10 octobre, en rouge).
- **Interactions de dates** : tirer le bord gauche ou droit d'un projet pour allonger/raccourcir sa plage, avec indicateurs de dates pendant le geste ; déplacer un élément verticalement pour réordonner ; petites flèches en bord de ligne signalant un projet qui commence avant ou finit après la fenêtre visible, cliquables pour y sauter ; bouton *Today* pour revenir au jour courant.
- **Lignes sans date** : comptées à part (capture 05 : « No date (5) ») et accessibles séparément.
- **Choix du tracé** : si plusieurs propriétés date existent, on choisit celle qui sert au tracé ; début et fin peuvent venir de deux propriétés distinctes ou d'une seule propriété-plage.
- **Table latérale** : une table peut être affichée/masquée à gauche de la frise (`>>` / `<<`) ; elle liste les projets en permanence, possède ses propres propriétés visibles et ordonnables, et supporte les **calculs de colonnes** habituels (comptes, pourcentages de vide, dates extrêmes, somme/moyenne/médiane/min/max/plage pour les nombres).
- **Présentation** : visibilité et ordre des propriétés affichées sur la frise ; limite de chargement (nombre de projets affichés à la fois) ; lien vers le calendrier Notion pour gérer les mêmes dates ailleurs (*Manage in Calendar*, capture 05).
### 3.4 Feed
La page d'aide de cette vue est la plus courte des six : le Feed affiche les pages d'une base en **cartes empilées linéairement**, à la manière d'un blog ou d'un fil social, pensé pour les communications d'équipe et les points d'avancement. Il permet de :
- faire défiler et parcourir le contenu de façon continue ;
- **commenter** directement chaque publication ;
- suivre le **nombre de vues** de ses publications ;
- régler les **propriétés visibles** sur les cartes depuis les réglages de la vue.
La capture 06 précise la forme : chaque carte porte un en-tête avec avatar et nom de l'auteur, l'ancienneté de la dernière édition (« 3m (edited) », « 2d (edited) », « Oct 3 (edited) »), le titre de la ligne en grand, un bouton d'ajout de réaction, et un champ « Add a comment… » pré-rempli de l'avatar de l'utilisateur courant.
### 3.5 Map
- **Nature** : visualisation des lignes sur une carte interactive (projets de voyage, lieux visités, travail de terrain…).
- **Prérequis central : le type de propriété `place`**. Il se crée comme n'importe quelle propriété ; sa valeur se saisit de trois façons : accès à la **position courante** de l'utilisateur, saisie d'un **nom de lieu**, ou saisie d'une **adresse**. La recherche d'adresse passe par un **fournisseur tiers** qui traite la requête — Notion prévient explicitement que qualité et couverture varient selon les régions.
- **Création de la vue** : par `/map` dans une page ou par `+` dans la base, en nommant la vue ; si la base n'a pas de propriété `place`, elle est créée automatiquement à cette occasion (source d'un piège documenté : un doublon de propriétés `place` peut ensuite exister, et la vue semble « ne pas montrer les bonnes épingles » tant qu'on n'a pas choisi la bonne propriété de tracé).
- **Interactions** : clic sur une épingle → ouverture de la page correspondante ; zoom et déplacement libres ; bouton de recentrage visible sur la capture 07.
- **Choix du tracé** : en présence de plusieurs propriétés `place`, le réglage *Layout → Map by* sélectionne celle qui est affichée.
- **Filtrage/tri** : textuels — on filtre sur le texte contenu dans le nom ou l'adresse du lieu, on trie alphabétiquement.
- **Limites documentées** : **100 éléments maximum affichés à la fois** (filtrer pour réduire) ; pas de calcul de distance entre deux lieux ; la conversion d'une propriété texte en `place` peut exiger un nettoyage des adresses pour qu'elles apparaissent.
### 3.6 Form
- **Nature** : un formulaire **connecté à une base** — chaque question correspond à une propriété ; les réponses deviennent des lignes de cette base, consultables notamment dans une vue Table nommée par défaut « Responses ». Les formulaires servent aussi à collecter auprès de personnes **hors du workspace**, voire sans compte.
- **Création** : par `/form` dans une page (une propriété `Respondent` est alors créée automatiquement pour capter le nom du répondant), ou par `+` depuis une base existante (il faut un accès complet à la base ; on peut créer une propriété *Created by* pour capter le répondant). L'édition exige un accès en édition ou complet.
- **Le builder** (capture 08) : titre du formulaire et description facultative, icône et couverture ; avertissement de portée en tête (« seuls les membres de cet espace peuvent remplir ce formulaire », modifiable) ; boutons **Preview** et **Share form** ; chaque question est une carte éditable montrant le libellé (qui est le nom de la propriété) et un champ « Respondent's answer » désactivé en guise d'aperçu.
- **Synchronisation question ↔ propriété** : par défaut, éditer une question dans le builder modifie la base — créer une question crée la propriété du même nom, renommer la question renomme la propriété ; un réglage par question permet de **désynchroniser** le libellé du formulaire du nom de la propriété.
- **Réglages par question** (menu `•••` de la question) : caractère **requis** ; description d'aide ; affichage des options en liste ou en menu déroulant pour les choix ; réponse longue pour le texte ; **type de question** (équivalent du type de propriété — choix multiple, date…) ; nombre maximal de sélections pour multi-sélect / relation / personnes ; duplication et suppression ; **logique conditionnelle** (montrer une question selon la réponse à une question à choix) — réservée aux plans Business/Enterprise chez Notion.
- **Écran de soumission** : couleur et texte du bouton d'envoi, titre et corps de la confirmation, possibilité de recevoir une copie courriel de chaque soumission.
- **Partage** : trois portées — membres du workspace disposant du lien (les invités seulement s'ils ont accès à la base), **toute personne sur le web disposant du lien**, ou **aucun accès** (formulaire fermé). Pour les formulaires de workspace non anonymes, un réglage définit ce que le répondant peut faire de **sa soumission** après envoi : rien, voir, commenter, éditer, ou accès complet. Branding Notion retirable (plans payants) ; réponses **anonymes** possibles — automatiques pour les formulaires web. Les propriétaires Enterprise peuvent interdire globalement le partage web des formulaires.
- **Exploitation** : réponses visibles/éditables selon l'accès à la base ; les gros volumes ralentissent le chargement ; on ne peut pas exporter la vue Form elle-même (on exporte depuis la Table) ; des **automatisations** peuvent être attachées au formulaire (déclencheurs sur nouvelle réponse ou sur une réponse donnée, actions comme notifier le répondant).
- **Contrainte d'appareil** : création et personnalisation se font sur desktop/web, pas sur mobile.
### 3.7 Ce que les six vues ont en commun chez Notion — et ce qui fera la parité
1. **Un seul système de vues** : même création, mêmes onglets, mêmes réglages de base — c'est précisément ce qui manque à Flowdeck (E1).
2. **Le schéma d'abord** : chaque vue déclare ses prérequis de propriétés (date, `place`) et sait en créer un par défaut plutôt que d'afficher un écran vide incompréhensible.
3. **Des plafonds assumés et documentés** (200/50 groupes, 100 épingles, 12 widgets) : la performance est traitée comme une propriété du produit, pas comme un accident.
4. **La lecture n'est pas l'édition** : Chart non éditable, Dashboard à deux modes, drilldown restreint — chaque vue définit explicitement ce qu'on peut y modifier.
5. **Le partage est un réglage de la vue** (surtout Form et Dashboard), pas un mécanisme séparé.
---
## 3A. Parcours visuel — les 8 captures de la base « Todo » de Bruno
Captures fournies par Bruno le 10 octobre 2026, conservées dans `vues-notion-reference/`. Base « Todo », privée, 6 lignes (`ssdfsdfsdffsd`, `bruno`, `Montreal, QC, Canada`, `tache 2`, `France`, `Tache 1`), propriétés visibles en Table : `Name`, `Status` (Not started / In progress / Done), `Assignee` (Bruno Charest), `Due` (plage, ex. 22 → 27 octobre 2026), `Place` (adresses complètes). Cette base est un excellent banc d'essai : elle possède **toutes** les propriétés requises par les six vues.
| # | Capture | Ce qu'on y observe | Conséquence pour la conception Flowdeck |
|---|---|---|---|
| 01 | `01-vue-table-todo.png` — Table | Barre d'onglets : *Table, Chart, Dashboard, Timeline, Feed, Map, Form builder* ; à droite, icônes filtre / tri / automatisations / IA / recherche / réglages, bouton **New** bleu avec menu ; colonne `Place` avec adresses textuelles complètes | Les six vues sont des **onglets sauvegardés de la même base** — c'est l'état cible E1. La `Place` de la capture est encore saisie comme adresse complète : le type `place` stocke nom + adresse structurés (§13.2) |
| 02 | `02-vue-chart-donut.png` — Chart | Donut centré, total **6** au centre, légende sous le graphique en 3 états (*To-do* gris, *In progress* bleu, *Complete* vert), étiquettes externes `4 (66.7%)`, `1 (16.7%)`, `1 (16.7%)` ; l'onglet *Chart* est actif | Le donut par défaut groupe par la propriété `status` et hérite de **ses couleurs** ; le modèle d'agrégation doit renvoyer comptes + pourcentages + couleurs de l'option, et le total (§9.3) |
| 03 | `03-menu-ajouter-vue.png` — menu `+` | Grille des types créables : *Table, Board, Gallery, List, Chart, Dashboard, Timeline, Feed, Map, Calendar, Form*, puis « New data source » ; *Table* y est présélectionnée | Le sélecteur de création est une **grille unique à 11 types** ; Flowdeck y ajoutera les 6 vues au même niveau que les 5 existantes, avec badge du prérequis manquant (§7.3) |
| 04 | `04-vue-dashboard-paywall.png` — Dashboard | Écran de présentation : titre « Visualize your work with dashboards », mention que les widgets exigent le plan Business, 3 cartes d'explication (génération par l'IA, ajout de charts/tables/listes, filtres multi-sources avec un exemple `Status: In progress`), bouton **Upgrade now** ; en mode Dashboard, la barre d'outils se réduit à l'icône de réglages | Chez Flowdeck, **pas de paywall** (§2.2) : cet écran devient l'**état vide utile** du Dashboard — les 3 cartes y deviennent des actions réelles (créer avec l'agent, ajouter un widget, exemples de filtres globaux) au lieu d'un argumentaire commercial (§15.4) |
| 05 | `05-vue-timeline.png` — Timeline | Échelle *Quarter*, mois août→novembre 2026, graduations hebdomadaires, ligne rouge du jour (10), pastille « bruno » sur sa plage ; en-tête : « No date (5) », *Manage in Calendar*, sélecteur d'échelle, navigation *Today* ; `+ New` à gauche | Trois éléments de conception non négociables : le **seau sans date** compté et visible, le **marqueur aujourd'hui**, et l'**échelle commutable** y compris trimestre (§12.2) |
| 06 | `06-vue-feed.png` — Feed | Cartes empilées centrées, en-tête auteur + ancienneté d'édition, titre en grand, bouton réaction, champ de commentaire avec avatar ; barre d'outils complète avec **New** | La carte Feed est un objet social complet (auteur, réaction, commentaire), pas une simple liste de titres : §14.2 fixe son anatomie exacte |
| 07 | `07-vue-map.png` — Map | Carte sombre de l'est de l'Amérique du Nord, 2 épingles bleues visibles (Québec et Washington DC — les autres lieux sont hors cadre ou regroupés à ce zoom), bouton de recentrage en haut à droite de la carte | La carte doit gérer le **cadrage automatique sur l'étendue des épingles** à l'ouverture — sur la capture, le cadrage par défaut laisse la France hors champ ; Flowdeck cadrera sur le bounding box des points (§13.5) |
| 08 | `08-vue-form-builder.png` — Form builder | Onglet renommé « Form builder » ; titre de formulaire vide (« Form title » en filigrane), description facultative, bandeau de portée verrouillée aux membres avec lien *Change*, questions-cartes `Name` et `Due` avec champ « Respondent's answer » ; en-tête de vue : **Preview** et **Share form** (bleu) remplacent le bouton *New* | Le builder a sa **propre barre d'actions** (aperçu/partage) distincte des autres vues : le registre de vues doit permettre à une vue de déclarer ses actions d'en-tête (§7.4). Le bandeau de portée est visible **dans** le builder, pas caché dans un réglage |
---
## 3B. Maquettes ASCII des six vues — disposition des composants
Dispositions cible pour Flowdeck, fidèles aux captures (thème sombre Flowdeck, sidebar à gauche non représentée). Les zones numérotées sont référencées dans les sections de conception.
### 3B.1 Chart (donut, d'après capture 02)
```text
┌──────────────────────────────────────────────────────────────────────────┐
│ Todo [⚙] [New]│
│ [Table] [●Chart] [Dashboard] [Timeline] [Feed] [Map] [Form] [+] │
├──────────────────────────────────────────────────────────────────────────┤
│ │
│ 1 (16.7%) │
│ ╱──────────────╲ (1) étiquette externe │
│ 1 (16.7%) │ ╭───────╮ │ valeur + % │
│ ────────────┤ │ 6 │ ├────────── │
│ │ │ Total │ │ (2) anneau : secteurs │
│ ╲──┤ │──╱ colorés par option │
│ ╰───────╯ (3) centre : total ou │
│ │ somme, commutable │
│ │ 4 (66.7%) │
│ │
│ ■ To-do ■ In progress ■ Complete │
│ (4) légende cliquable (masquer/isol­er) │
├──────────────────────────────────────────────────────────────────────────┤
│ (5) Réglages (panneau ⚙) : Type · Axe X / Données · Axe Y · Groupe par · │
│ Style (palette, hauteur, étiquettes, légende) · Export PNG/SVG │
│ (6) Clic sur un secteur → drilldown (tableau des lignes du groupe) │
└──────────────────────────────────────────────────────────────────────────┘
```
### 3B.2 Dashboard (mode Édition)
```text
┌──────────────────────────────────────────────────────────────────────────┐
│ Todo — Vue Projet [Lecture|●Édition] [⚙] │
│ [Table] [Chart] [●Dashboard] [Timeline] [Feed] [Map] [Form] [+] │
├──────────────────────────────────────────────────────────────────────────┤
│ Filtres globaux : [Assignee : Bruno ▾] [Status ≠ Done ▾] [ + ] │
│ ┌───────────────────────────┐ ┌───────────────────────────────────────┐ │
│ │ (1) Widget Chart (donut) │ │ (2) Widget KPI : 5 tâches ouvertes │ │
│ │ ≡ titre ··· menu │ │ │ │
│ └───────────────────────────┘ └───────────────────────────────────────┘ │
│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────────┐ │
│ │ (3) Table │ │ (4) Board │ │ (5) Timeline │ │
│ │ prioritaires │ │ par statut │ │ jalons │ │
│ └───────────────────┘ └───────────────────┘ └───────────────────────┘ │
│ [ + Ajouter un widget ] │
│ (6) Glisser : déplacer · poignée verticale : largeur · séparateur de │
│ ligne : hauteur — uniquement en mode Édition │
└──────────────────────────────────────────────────────────────────────────┘
```
### 3B.3 Timeline (d'après capture 05)
```text
┌──────────────────────────────────────────────────────────────────────────┐
│ Todo No date (5) [⚙] [New] │
│ [Table] [Chart] [Dashboard] [●Timeline] [Feed] [Map] [Form] [+] │
├──────────────────────────────────────────────────────────────────────────┤
│ Août 2026 Septembre Octobre [Trimestre ▾] │
│ 17 24 31 7 14 21 28 5 ⑩ 12 19 26 [< Today >] │
│ ──────────────────────────────────────┼────────────────────────────── │
│ + New │ ┌──────────────┐ │
│ │ │ bruno │ ← (1) barre = │
│ │ └──────────────┘ plage de │
│ (2) table latérale (optionnelle) │ dates, bords la ligne │
│ repliable par >> / << │ tirables │
│ │ (3) ligne rouge = aujourd'hui │
└──────────────────────────────────────────────────────────────────────────┘
```
### 3B.4 Feed (d'après capture 06)
```text
┌──────────────────────────────────────────────────────────────────────────┐
│ Todo [⚙] [New]│
│ [Table] [Chart] [Dashboard] [Timeline] [●Feed] [Map] [Form] [+] │
├──────────────────────────────────────────────────────────────────────────┤
│ ┌────────────────────────────────────────────┐ │
│ │ (avatar) Bruno Charest · 3m (edited) · 👁 12│ │
│ │ │ │
│ │ bruno │ (1) titre │
│ │ [Status: Not started] [Due: 22 oct.] │ (2) props │
│ │ Aperçu du contenu de la page… │ (3) extrait │
│ │ ☺+ │ (4) réaction │
│ │ (avatar) Add a comment… │ (5) comment. │
│ └────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────┐ │
│ │ (avatar) Bruno Charest · 2d (edited) │ │
│ │ Montreal, QC, Canada │ │
│ │ … │ │
│ └────────────────────────────────────────────┘ │
│ (6) chargement par curseur au défilement │
└──────────────────────────────────────────────────────────────────────────┘
```
### 3B.5 Map (d'après capture 07)
```text
┌──────────────────────────────────────────────────────────────────────────┐
│ Todo [⚙] [New]│
│ [Table] [Chart] [Dashboard] [Timeline] [Feed] [●Map] [Form] [+] │
├──────────────────────────────────────────────────────────────────────────┤
│ ┌──────────────────────────────────────────────────────────────┐ [◎] │
│ │ │ (1) │
│ │ (carte Leaflet) │ recen- │
│ │ │ trage │
│ │ 📍 ← épingle (Montréal/Beloeil) │ │
│ │ │ │
│ │ │ │
│ │ 📍 ← épingle (Washington, DC) │ │
│ │ │ │
│ │ (2) clic épingle → popup titre + [Ouvrir] → page de la ligne│ │
│ │ (3) à l'ouverture : cadrage sur l'étendue de toutes les │ │
│ │ épingles filtrées ; plafond 100, message si dépassé │ │
│ └──────────────────────────────────────────────────────────────┘ │
│ Tracé par : [Place ▾] 6 lieux · 5 affichés · 1 sans coordonnées │
└──────────────────────────────────────────────────────────────────────────┘
```
### 3B.6 Form builder (d'après capture 08)
```text
┌──────────────────────────────────────────────────────────────────────────┐
│ Todo [👁 Preview] [Share form]│
│ [Table] [Chart] [Dashboard] [Timeline] [Feed] [Map] [●Form builder] [+] │
├──────────────────────────────────────────────────────────────────────────┤
│ Form title (1) titre │
│ Description (optional) (2) descr. │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ 🔒 Only members at Bruno can fill out this form. [Change]│ (3) │
│ └───────────────────────────────────────────────────────────────┘ portée │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Name [••• menu] │ │
│ │ [ Respondent's answer ] │ (4) │
│ └───────────────────────────────────────────────────────────────┘ quest. │
│ ┌───────────────────────────────────────────────────────────────┐ │
│ │ Due [••• menu] │ │
│ │ [ Respondent's answer ▾] │ │
│ └───────────────────────────────────────────────────────────────┘ │
│ [ + Add a question ] │
│ (5) Menu ••• d'une question : Requis · Description · Type · Max choix · │
│ Logique conditionnelle · Libellé synchronisé (on/off) · Dupliquer · │
│ Supprimer — toute création/renommage se reflète dans les propriétés │
└──────────────────────────────────────────────────────────────────────────┘
```
---
## 4. État des lieux Flowdeck v7.69.8 et analyse d'écart
*Sources : `ARCHITECTURE.md` v7.69.8, §11 (Databases), §12 (Système de vues), §13 (Pages et page-ombre), §15 (Partage, sites, formulaires publics — via §5.3 et §15.1), §19 (automatisations), §22 (Frontend). Quand l'architecture ne dit pas si un comportement précis existe dans le code, il est marqué « à vérifier » plutôt que supposé absent.*
### 4.1 Ce qui existe déjà (et que le chantier doit réutiliser, pas réécrire)
| Actif v7.69.8 | Référence | Usage dans ce chantier |
|---|---|---|
| `collection_views` : `view_type` + `config_json` + `position` + `created_by` (NULL = partagée) ; persistance `PUT /db/views/{id}/config`, `POST /db/{id}/views/save-as` | §12.1 | Support de sauvegarde des six vues — **aucun changement de modèle nécessaire** pour Chart, Timeline, Feed, Map, Form |
| Chaîne de traitement d'une vue : charger les lignes → appliquer filtres (`and`/`or`) → tris → groupement → projection des propriétés visibles | §12.3 | Devient le service de requêtes de vues partagé (§8.1) ; les six vues en sont des consommateurs, pas des variantes |
| 21 types de propriétés, schéma déclaratif (`collection_properties`), valeurs JSON (`property_values_json`), requêtage `json_extract()` | §11.1–11.2 | `place` s'ajoute comme 22ᵉ type sans migration de lignes (§13.2) |
| Formula Engine (19 fonctions) et Rollup Engine (12 agrégats) évalués à la lecture | §11.3–11.4 | Sources de valeurs pour les axes de Chart, avec les mêmes exclusions que Notion (§9.3) |
| Renderers SSR des six vues (`app/routers/collections/_renderers.py`, dispatch `_render_view`) : Chart (5 types graphiques + KPI, `CHART_MAX_GROUPS`), Timeline/Gantt, Map Leaflet, Feed, Form | §12.2 surface 2 | **Logique métier à extraire** (calculs, regroupements) vers le service d'agrégation et les modules clients ; les routes SSR deviennent des coquilles de compatibilité (§7.6) |
| Chart.js et Leaflet **déjà vendorisés** (`static/js/vendor/chart.umd.js`, `vendor/leaflet/`) | §22.2 | Aucune nouvelle dépendance front pour Chart et Map ; chargement paresseux à ajouter |
| `collection_dashboards.layout_json` : combinaison de vues/widgets en colonnes sur une page | §12.3 | Antécédent du Dashboard-vue : la grille de widgets §15 peut réutiliser son format de layout comme point de départ, mais pas son rattachement (page ≠ vue) |
| Formulaires publics `/f/<token>` : champs depuis `collection_properties` (hors formules), validation, rate limiting, `form_responses` + `collections.form_config_json`, notification aux membres, événement `form.submitted` | §15 (sites & formulaires v6.8) | Le Form builder v2 écrit la **même** configuration que ce pipeline consomme ; la soumission ne change pas (§16.5) |
| Page-ombre de ligne (`pages.collection_row_id`, v6.5) hébergeant le contenu blocs de chaque ligne, avec versions et temps réel | §5.4 | Source de l'extrait de contenu des cartes Feed (§14.2) |
| Commentaires polymorphes (`page` ou `collection_page`) + ancres, réactions sur commentaires, `text_reactions` | §5.3 | Commentaires et réactions des cartes Feed sans nouveau modèle (§14.3) |
| `page_views` (consultations de pages) | §5.3 | Compteur de vues des cartes Feed (§14.3) — *à vérifier : granularité par ligne de collection* |
| Sub-items (`parent_id`, hiérarchie illimitée) et dépendances entre pages (Blocking/Blocked by, `auto_shift` des dates) | §11.5 | Timeline : affichage aplati ou hiérarchique, et garde-fou d'écriture des dates par glisser (§12.4) |
| Bus d'événements des automatisations (`fire_event()`, moteur v5.1 → multi-step v7.0) | §19.1 | Déjà alimenté par `form.submitted` ; sert aussi à invalider les caches de vues (§8.5) |
| Temps réel (WebSocket, présence, merge) | §13.4 | Rafraîchissement des vues quand une ligne change dans une autre vue (§7.5) — périmètre minimal en v1 |
| ACL granulaires collection/propriété, rôles de workspace | §9.1–9.2 | Filtrage des propriétés avant agrégation/affichage (§17.1) |
| PWA offline : IndexedDB `collections_offline`, file de mutations rejouée | §22.3 | Les six vues sont en **lecture dégradée** hors ligne ; aucune n'est créable hors ligne en v1 (§18.4) |
| Agent IA et ses outils (création/édition de pages et de bases, vues y compris cartes et formulaires d'après le document Agents & Skills) | §16 | L'API de vues doit exposer création de vue + réglage de `config_json` pour permettre la génération de Dashboards/Charts par l'agent (§11.4) |
### 4.2 Analyse d'écart détaillée, vue par vue
Légende : ✅ présent · 🟡 partiel / surface secondaire · ❌ absent (d'après l'architecture v7.69.8).
| Capacité | Chart | Timeline | Feed | Map | Dashboard | Form |
|---|---|---|---|---|---|---|
| Rendu du type de vue quelque part dans le produit | ✅ SSR | ✅ SSR | ✅ SSR | ✅ SSR | 🟡 layout de page | ✅ SSR + public |
| Vue cliente interactive dans `DBInstance` | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| Création depuis le menu `+` d'une base, onglet sauvegardé | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
| `config_json` unifié (filtres/tris/groupes/visibilité) éditable par les panneaux communs | 🟡 (paramètres propres au renderer) | 🟡 | 🟡 | 🟡 | 🟡 `layout_json` séparé | 🟡 `form_config_json` séparé |
| Interaction signature de la vue chez Notion | ❌ drilldown | ❌ drag des dates | ❌ commentaires en ligne | 🟡 clic épingle *(à vérifier)* | ❌ modes View/Edit | ❌ builder intégré |
| Prérequis de schéma géré (création assistée) | — | 🟡 choix de la date | — | ❌ pas de type `place` | — | 🟡 champs depuis propriétés |
| Plafonds de performance explicites | ✅ `CHART_MAX_GROUPS` | ❌ | ❌ pagination ? | ❌ plafond 100 ? | ❌ | ✅ rate limiting public |
| Partage/portée propre à la vue | — | — | — | — | ❌ | ✅ token public (sans les niveaux fins du builder, §16.4) |
Lecture : la première ligne est presque entièrement verte — c'est le piège de ce chantier. **Le manque ressenti par Bruno n'est pas un manque de rendus, c'est un manque d'unification, d'interactivité et de deux fondations (`place`, builder).** Un plan qui « ajouterait les six vues » en réécrivant des renderers créerait un troisième système de vues et aggraverait le problème.
### 4.3 Les six écarts structurants (rappel et ancrage)
- **E1 — Deux surfaces disjointes** (§12.2 surfaces 1 et 2). Travail : §7 (registre client) + §7.6 (retrait progressif du SSR).
- **E2 — Pas d'endpoint d'agrégation.** Le regroupement du Chart SSR est interne au rendu HTML ; ni le client, ni un widget, ni un drilldown ne peuvent le réutiliser. Travail : §8.3 + §9.
- **E3 — Pas de type `place`.** La Map actuelle géocode « `lat,lng` depuis les propriétés » : l'utilisateur doit produire lui-même des coordonnées. Aucune saisie par adresse, aucun cache, aucun fournisseur, aucune politique d'envoi d'adresses à un tiers. Travail : §13.
- **E4 — Dashboard ≠ vue.** `collection_dashboards` compose des widgets **dans une page**, pas comme onglet d'une base ; pas de widgets cross-sources formalisés, pas de filtres globaux, pas de séparation Lecture/Édition. Travail : §15.
- **E5 — Form sans builder intégré.** Le pipeline public est solide (v6.8) mais la configuration vit dans `collections.form_config_json`, éditée hors du contexte de la vue ; questions, logique conditionnelle, écran de confirmation et niveaux d'accès du répondant n'ont pas de modèle. Travail : §16.
- **E6 — Timeline et Feed non interactives.** Rendus figés : pas d'écriture de dates par le geste, pas de table latérale calculée, pas d'engagement (réaction/commentaire/vues) dans le Feed. Travail : §12 et §14.
---
## 5. Principes directeurs pour Flowdeck
1. **Une vue = un `view_type` + un schéma de `config_json` + un module client enregistré.** Rien d'autre ne distingue une vue d'une autre. Toute nouvelle vue future doit pouvoir s'ajouter en suivant ce seul patron (c'est aussi ce qui rendra Board/Gallery améliorable plus tard).
2. **Le serveur calcule, le client dessine.** Agrégations, regroupements, géocodage et résolutions de permissions se font côté serveur ; le client reçoit des données prêtes à afficher et ne rapatrie jamais toute une base pour la réduire localement (contraire à la chaîne §12.3 actuelle côté client pour les petites bases — on conserve ce mode uniquement sous un seuil documenté, §18.2).
3. **Aucune vue ne contourne les permissions.** Le filtrage ACL propriété se fait dans le service de requêtes, **avant** agrégation : un compte sur un graphique ne doit pas laisser deviner la distribution d'une propriété invisible.
4. **Écrire par les mêmes chemins que l'édition directe.** Le drag d'une Timeline écrit la propriété date par l'API d'édition de cellule existante ; un commentaire de Feed est un commentaire polymorphe ordinaire ; une soumission de formulaire crée une ligne par le service de création ordinaire. Aucun raccourci d'écriture propre à une vue.
5. **Les plafonds sont des réglages produit, affichés.** Quand une limite est atteinte (groupes, épingles, lignes de Timeline), l'interface le dit et propose le remède (filtrer), à la manière de Notion — jamais de troncature silencieuse.
6. **Zéro nouvelle librairie tant qu'un vendorisé suffit.** Chart.js et Leaflet sont déjà là ; le Dashboard et le Form builder se font en Alpine CSP + htmx comme le reste. Toute exception passe par un ADR (§22).
7. **Compatibilité des liens existants.** Les URL SSR `/db/{id}?view_type=…` et les formulaires publics `/f/<token>` déjà partagés doivent continuer de fonctionner après la promotion (§7.6) : ce sont des liens que des tiers peuvent détenir.
8. **Pas de limite commerciale simulée.** Les plafonds repris de Notion sont ceux de performance ; les restrictions de plan ne sont pas reproduites (§2.2).
---
## 6. Vue d'ensemble du système (C4)
### 6.1 Contexte
```mermaid
graph TD
U[Utilisateur Flowdeck] --> FD[Flowdeck - vues de bases]
R[Répondant externe] -->|formulaire public /f/| FD
FD --> GEO[Fournisseur de géocodage<br/>optionnel, configurable]
FD --> TILES[Fonds de carte<br/>tuiles Leaflet existantes]
AG[Agent IA Flowdeck] -->|API de vues| FD
```
### 6.2 Conteneurs (à l'intérieur du monolithe — aucun nouveau service)
```mermaid
graph LR
subgraph Client [Navigateur - DBInstance]
REG[Registre de vues<br/>database_table.js + modules par vue]
REG --> CH[view_chart.js]
REG --> TL[view_timeline.js]
REG --> FE[view_feed.js]
REG --> MA[view_map.js]
REG --> DA[view_dashboard.js]
REG --> FO[view_form.js - builder]
end
subgraph Serveur [FastAPI - monolithe existant]
API[Routeurs collections<br/>vues / données / agrégation]
VQS[View Query Service<br/>filtres - tris - groupes - ACL]
AGG[Aggregation Service<br/>groupes, sous-groupes, cumul]
GEO_S[Geocoding Service<br/>cache + fournisseur]
FRM[Form Service<br/>questions, soumissions - sur /f/ existant]
API --> VQS --> AGG
API --> GEO_S
API --> FRM
end
DB[(SQLite WAL<br/>collections, collection_pages,<br/>collection_views + mig. 44-47)]
Client -->|JSON - routes §11| API
VQS --> DB
AGG --> DB
GEO_S --> DB
FRM --> DB
```
Points d'architecture :
- **Aucun nouveau processus, aucune nouvelle base** : tout vit dans le monolithe et SQLite, comme l'exige l'invariant « un seul processus » (§5.1, §26 de l'architecture Flowdeck).
- Le **View Query Service** est l'extraction, en service partagé, de la chaîne de rendu décrite en §12.3 de l'architecture Flowdeck (filtres → tris → groupement → projection), aujourd'hui dupliquée entre le client et les renderers SSR.
- Le **Geocoding Service** est le seul point de sortie réseau nouveau ; il est désactivable et mis en cache (§13.4, §17.3).
---
## 7. Architecture front-end : le registre de vues de `DBInstance`
### 7.1 Le problème à résoudre dans `database_table.js`
Le composant `DBInstance` connaît aujourd'hui ses cinq vues en dur. Ajouter six vues en dur dans le même fichier (déjà central pour Table/Board/Calendar/Gallery/List) le rendrait ingérable et contredirait le principe 1. On introduit donc un **registre** : chaque vue est un module séparé, chargé à la demande, qui s'enregistre sous son `view_type`.
### 7.2 Contrat d'un module de vue (proposition)
```js
// static/js/views/view_chart.js — patron commun aux six modules
window.FDViews = window.FDViews || {};
window.FDViews.register('chart', {
label: 'Chart', icon: 'chart-pie',
// Prérequis de schéma déclarés (voir §7.3)
requires: [], // ex. map: [{type:'place', autoCreate:true}]
// Le module sait-il éditer ? (affiché dans les réglages; Chart : non)
editable: false,
headerActions: [], // ex. form: [Preview, Share form]
mount(el, ctx) { /* ctx: {view, config, dataSource, callbacks} */ },
update(ctx) { /* nouvelles données / nouveau config */ },
destroy() { /* libérer Chart.js / Leaflet / écouteurs */ },
settings: [ /* descripteurs du panneau propre à la vue, §7.4 */ ],
});
```
Contraintes héritées du frontend Flowdeck (§22) à respecter par chaque module :
- **Alpine build CSP** : aucun `x-data` avec expression interdite, aucun `eval` ; les modules sont du JS vanilla orchestré par `DBInstance`, les petits états locaux d'UI en composants `Alpine.data` enregistrés comme l'existant.
- **Navigation partielle** (`app.js` intercepte et swappe `.main-wrapper`) : `destroy()` doit être idempotent et les gardes d'idempotence (`window.__fd…Loaded`) suivies ; un graphique Chart.js ou une carte Leaflet non détruits fuient entre deux navigations.
- **Chargement paresseux** : Chart.js (~vendorisé) et Leaflet ne sont fetchés que lorsqu'une vue `chart`/`map`/`dashboard` contenant un widget de ce type est effectivement affichée.
- **`?v=VERSION`** : les nouveaux fichiers suivent le cache-busting existant.
### 7.3 Création d'une vue et prérequis de schéma
Le menu `+` (capture 03) devient une grille générée **depuis le registre** (11 types : les 5 existants + les 6 promus ; *Calendar* déjà cliente y figure déjà, le sélecteur Flowdeck la liste avec les autres). À la sélection d'un type :
1. le client vérifie les `requires` du module contre le schéma de la base ;
2. si un prérequis manque et `autoCreate` est vrai (cas `map` → propriété `place` ; cas `form` depuis une base sans propriété répondant — §16.3), une confirmation explicite propose de le créer (« Cette vue a besoin d'une propriété Lieu — la créer ? »), puis l'API de propriété ordinaire est appelée ;
3. si un prérequis manque sans création automatique possible (cas `timeline` sans aucune propriété date), la création est refusée avec l'explication et un bouton « Ajouter une propriété date » — jamais de vue vide muette ;
4. la vue est créée par `POST /db/{id}/views` (existant) avec un `config_json` **par défaut raisonnable** propre au type (donut sur la première propriété `status`/`select` pour Chart ; première propriété date pour Timeline ; ordre `last_edited_time desc` pour Feed…), de sorte que la vue affiche quelque chose de juste immédiatement, comme le donut de la capture 02.
### 7.4 Panneaux de réglages : commun + propre à la vue
Les réglages existants (filtres, tris, propriétés visibles, groupement) restent les panneaux communs. Chaque module déclare ses réglages propres sous forme de descripteurs (type de contrôle, chemin dans `config_json`, libellé) ; le panneau est généré depuis ces descripteurs plutôt que codé six fois. Toute modification passe par le `PUT /db/views/{id}/config` existant, avec :
- validation serveur du `config_json` contre le schéma du `view_type` (§10.3) ;
- distinction **vue partagée / vue personnelle** inchangée : éditer les réglages d'une vue partagée exige le droit d'édition sur la base ;
- modifications « locales » non enregistrées (essayer un filtre dans un Dashboard en lecture) : conservées en mémoire du client uniquement, avec action explicite « Enregistrer pour tout le monde » si l'utilisateur en a le droit — aligné sur le comportement Notion (§3.0, §3.2).
### 7.5 Données : comment chaque vue est alimentée
| Besoin | Endpoint (détail §11) | Vues concernées |
|---|---|---|
| Lignes filtrées/triées/projetées, paginées par curseur | `GET …/views/{view_id}/rows` | Table/Board/… existantes, Feed, Timeline (fenêtre), Map (borné) |
| Agrégat groupé (groupes, sous-groupes, mesures, cumul) | `POST …/views/{view_id}/aggregate` | Chart, widgets Chart/KPI de Dashboard, drilldown (avec filtre de groupe) |
| Fenêtre temporelle (bornes + lignes sans date) | `GET …/views/{view_id}/rows?window=…` | Timeline |
| Widgets d'un Dashboard (résolution multi-sources) | `GET …/views/{view_id}/widgets/data` (un appel par widget, parallélisable) | Dashboard |
| Questions + état du formulaire | `GET …/views/{view_id}/form` | Form builder |
Le temps réel (§13.4 de l'architecture Flowdeck) est branché de façon minimale : quand une ligne de la base change (événement existant de la room de la collection, si présent — *à vérifier*), la vue visible se rafraîchit en douceur (re-fetch de ses données, pas de re-mount). Les vues non visibles ne se rafraîchissent pas.
### 7.6 Sortie progressive des renderers SSR
1. **Étape 1 (phases 1–2)** : les routes `/db/{id}?view_type=chart|timeline|feed|map|form` rendent la page collection normale avec la vue cliente correspondante présélectionnée (créée à la volée comme vue éphémère si elle n'existe pas en `collection_views`). Le code des renderers reste en place, inutilisé mais disponible en repli par feature flag.
2. **Étape 2 (phase 4)** : les anciens fragments SSR sont supprimés, **sauf** le pipeline public `/f/<token>` (autre produit : répondants anonymes, pas de session `DBInstance`) et l'aperçu imprimable/exporté s'il en dépend (*à vérifier*).
3. Les gabarits autonomes du Board Gitea legacy (`status_overview`, `team_load`, §12.2 surface 3) **ne sont pas concernés** : ils rendent des issues de dépôt, pas des `collection_pages`.
---
## 8. Architecture back-end : service de requêtes de vues et moteur d'agrégation
### 8.1 View Query Service (`app/services/view_query.py`, nouveau — extraction)
Responsabilité unique : étant donnés `(collection, view.config_json, user)`, produire l'ensemble de lignes **autorisé, filtré, trié, groupé, projeté**. Étapes, dans l'ordre, toutes obligatoires :
```text
1. Charger la collection et son schéma (collection_properties)
2. Résoudre les ACL : propriétés visibles pour user (collection + propriété)
→ retirer des filtres/tris/groupes/affichages toute propriété non visible
3. Charger les lignes (collection_pages) — SQL avec json_extract pour
les filtres poussables, évaluation Python pour le reste (formules…)
4. Appliquer filters (conjonction and/or, opérateurs existants)
5. Appliquer sorts (stable, multi-clés)
6. Projeter visible_properties (+ toujours : id, titre, page-ombre id)
7. Renvoyer, selon l'appelant : lignes paginées | groupes | fenêtre temps
```
C'est l'étape 2 qui fait la différence avec l'existant : aujourd'hui, le groupement client part des lignes déjà projetées pour l'utilisateur connecté ; un agrégat serveur qui regrouperait **avant** le filtrage ACL fuirait des distributions (§17.1).
### 8.2 Pagination et bornes
- Pagination **par curseur** (dernier couple `(valeur de tri, id)`) pour Feed et les listes de widgets ; offset proscrit sur les grosses bases.
- Bornes dures côté serveur, configurables : `VIEW_ROWS_MAX` (défaut 1 000 lignes par réponse), `MAP_POINTS_MAX` (défaut 100, aligné Notion), `TIMELINE_LOAD_MAX` (défaut 200, réglable par vue — le « load limit » de Notion), `CHART_MAX_GROUPS` (existant, conservé) complété par `CHART_MAX_SUBGROUPS` (défaut 50).
### 8.3 Aggregation Service (`app/services/view_aggregate.py`, nouveau)
Contrat d'entrée (extrait) :
```json
{
"group_by": "Status",
"sub_group_by": null,
"measure": {"kind": "count"},
"cumulative": false,
"omit_zero": true,
"hidden_groups": ["Archivé"],
"order": {"by": "group_order", "direction": "asc"}
}
```
- **Mesures** : `count` (lignes), ou agrégat d'une propriété nombre/formule/rollup : `sum`, `average`, `median`, `min`, `max` — en réutilisant les définitions du Rollup Engine (§11.4) plutôt qu'une seconde implémentation.
- **Groupes** : valeurs distinctes de la propriété de regroupement ; pour `select`/`status`/`multi_select`, l'**ordre du schéma** (ordre des options déclarées) est l'ordre naturel, avant l'ordre alphabétique ou par valeur — c'est ce qui donne au donut de la capture 02 son ordre *To-do → In progress → Complete* et ses **couleurs d'options**.
- **Zéros** : avec `omit_zero: false`, les options déclarées sans ligne apparaissent à 0 (sémantique Notion §3.1, y compris son libellé trompeur — l'UI Flowdeck nommera le réglage « Afficher les groupes vides » pour éviter la confusion).
- **Cumul** : somme courante sur les groupes ordonnés, calculée après agrégation, avant masquage éventuel — point à tester (§21, CU-Chart bis).
- **Formules et relations** : un regroupement sur une formule s'évalue ligne à ligne via le Formula Engine ; coût borné par `VIEW_ROWS_MAX` de lignes scannées et mise en cache courte (§8.5). Les types non représentables (rollup en axe, bouton, `unique_id`, fichiers) sont refusés à la validation du `config_json`, comme chez Notion.
- **Sortie** normalisée, identique pour Chart, KPI et drilldown :
```json
{
"groups": [
{"key": "Not started", "label": "Not started", "color": "gray",
"value": 4, "percent": 0.667,
"sub": [{"key": "Bruno Charest", "value": 3}] }
],
"total": 6, "truncated": false, "scanned": 6
}
```
### 8.4 Drilldown
Le drilldown n'est pas un nouvel objet : c'est le View Query Service appelé avec un **filtre supplémentaire éphémère** (`group_by = valeur cliquée`, et `sub_group_by = …` si applicable), rendu par le module Table existant en mode restreint (pas de création de ligne, pas d'actions de masse, pas de calculs — restrictions alignées sur Notion §3.1), dans un panneau latéral ou une modale. « Enregistrer comme vue » crée une vraie `collection_views` de type `table` avec ce filtre matérialisé dans son `config_json`.
### 8.5 Cache et invalidation
- Les agrégats sont mis en cache en mémoire du processus (le monolithe est mono-processus, §26) avec clé `(view_id, hash du config + filtres globaux, user_acl_fingerprint)` et TTL court (30 s par défaut).
- Invalidation active : toute écriture de ligne de la collection (création/édition/suppression — événements déjà émis pour les automatisations) invalide les entrées de cette collection.
- Les vues d'un Dashboard partagent le cache entre widgets de même source : deux widgets groupant par `Status` sur la même base ne calculent qu'une fois.
### 8.6 Export de graphique (PNG/SVG)
Chart.js sait rendre dans un `<canvas>` : l'export PNG se fait **côté client** (capture du canvas, fond au choix clair/sombre/transparent). L'export SVG n'est pas natif à Chart.js ; deux options : (a) générer un SVG côté serveur depuis la sortie normalisée de §8.3 pour les types barres/lignes/donut (surface réduite, testable), (b) renoncer au SVG en v1. **Recommandation : (a) différé en phase 5**, PNG seul en phase 1 — Notion lui-même réserve certains raffinements d'export à ses plans payants, l'attente utilisateur est donc calibrée (§24, Q2).
---
## 9. Vue Chart
### 9.1 Objectif
Promouvoir le Chart SSR en vue cliente configurable, alimentée par le service d'agrégation, avec drilldown et export PNG. C'est la vue pilote du chantier : elle force la création du registre (§7), du View Query Service et de l'Aggregation Service (§8), dont toutes les autres vues profitent.
### 9.2 Types retenus
| Type Notion | `chart_type` Flowdeck | Rendu | Note |
|---|---|---|---|
| Barres verticales | `bar` | Chart.js `bar` | existe en SSR |
| Barres horizontales | `bar_horizontal` | Chart.js `bar`, `indexAxis: 'y'` | nouveau libellé, même moteur |
| Lignes | `line` | Chart.js `line` | existe ; options lissage + aire en dégradé |
| Donut | `doughnut` | Chart.js `doughnut` | existe ; valeur au centre via plugin local |
| Nombre | `number` | **pas de Chart.js** : carte KPI HTML | regroupe les « widgets KPI » SSR actuels dans la vue Chart, comme chez Notion |
| *(existant SSR)* Scatter | `scatter` | conservé en compatibilité de config | **non promu** en v1 : pas d'équivalent dans le modèle d'axes Notion ; réévaluer si usage réel constaté (*à vérifier* dans les configs existantes) |
| *(existant SSR)* Pie | `pie` | mappé sur `doughnut` avec trou à 0 | conversion à la lecture du config, pas de migration de données |
### 9.3 Configuration (extrait de `config_json` — schéma complet en Annexe B)
```json
{
"version": 2,
"chart_type": "doughnut",
"group_by": "Status",
"sub_group_by": null,
"measure": {"kind": "count"},
"omit_zero": false,
"cumulative": false,
"hidden_groups": [],
"group_order": "schema",
"style": {"palette": "property", "height": "medium",
"data_labels": "percent", "legend": true,
"center_value": "total", "grid": false, "smooth": false,
"gradient": false, "color_by_value": false}
}
```
Règles :
- `palette: "property"` = couleurs des options du schéma (statut/select) quand le regroupement porte sur une telle propriété — comportement visible sur la capture 02 ; sinon palettes nommées (`colorful`, `blue`, `green`, tons monochromes avec `color_by_value`).
- Pour bar/line, `group_by` joue le rôle de l'axe X et `measure` + `sub_group_by` celui de l'axe Y ; le panneau de réglages présente les libellés « Axe X / Axe Y » pour ces types et « Données / Parts » pour le donut, comme Notion, tout en écrivant les mêmes clés.
- Hauteurs : `small | medium | large | extra_large` → hauteurs fixes en `rem`, pour que les widgets de Dashboard aient des tailles prévisibles.
- Toute propriété non représentable en `group_by`/`measure` (rollup en axe, bouton, `unique_id`, `files`, formule rendant une liste) est **exclue des sélecteurs** du panneau et refusée à la validation serveur (§8.3).
### 9.4 Interactions
- Survol : infobulle Chart.js (libellé, valeur, pourcentage) — natif.
- Légende cliquable : masque/ré-affiche un groupe **localement** (sans écrire `hidden_groups` ; le réglage persistant passe par le panneau) — écart assumé avec Notion, qui mélange les deux ; noter ce choix en aide contextuelle.
- Clic sur un élément → drilldown §8.4.
- Pas d'édition de ligne, sous aucune forme (principe repris de Notion, §3.7 point 4) ; le drilldown permet d'**ouvrir** chaque ligne dans son éditeur ordinaire.
- Accessibilité : le graphique est doublé d'un tableau de données masqué visuellement mais lisible par les technologies d'assistance (groupes, valeurs, pourcentages) ; navigation clavier dans la légende.
### 9.5 Cas limites
- Base vide ou tout filtré : état vide avec l'explication et le rappel des filtres actifs (jamais un anneau vide sans texte).
- Un seul groupe : donut plein, pourcentage 100 %, centre = total.
- Regroupement sur une date : regroupement par jour/semaine/mois selon l'étendue (règle : ≤ 62 jours → jour ; ≤ 53 semaines → semaine ; sinon mois), `group_order: chronological` forcé ; plafond de 200 groupes appliqué après regroupement temporel.
- `truncated: true` dans la réponse d'agrégation → bandeau « 200 groupes affichés sur N — affinez les filtres ».
---
## 10. Modèle de données et migrations 44 à 47
### 10.1 Règle de séquencement (rappel du document Agents & Skills, v1.1)
Le moteur de migrations de Flowdeck (`@register(version, name)`, §5.2 de l'architecture) n'applique que les migrations **strictement supérieures** à la version courante (38) — et le document Agents & Skills a déjà réservé **39 à 43** dans l'ordre de ses phases. Les migrations du présent chantier sont donc numérotées **44 à 47**, dans l'ordre des phases du §21. Si le chantier Agents & Skills n'était finalement pas implémenté, renuméroter en 39 à 42 **avant** le premier déploiement de la phase 1, jamais après.
### 10.2 Ce qui ne demande aucune migration
Grâce au schéma déclaratif (§11.1) et au `config_json` des vues (§12.1) :
- les cinq `view_type` promus (`chart`, `timeline`, `feed`, `map`, `form`) comme vues sauvegardées ;
- le type de propriété `place` **en définition** (`collection_properties.prop_type = 'place'`) et **en valeur** (objet JSON dans `property_values_json`) ;
- toutes les configurations de vues (Annexe B), les réglages de Dashboard non-structurels et les questions de formulaire simples — stockés en JSON ;
- le drilldown, le cache d'agrégation (mémoire), l'export PNG.
### 10.3 Migrations proposées
| Version | Nom | Contenu | Phase |
|---|---|---|---|
| **44** | `view_config_v2` | Colonne `collection_views.config_version INTEGER NOT NULL DEFAULT 1` ; index `(collection_id, view_type)` sur `collection_views` s'il n'existe pas *(à vérifier)*. Aucune réécriture des `config_json` existants : la lecture tolère v1 et v2 (§10.4) | 1 |
| **45** | `place_geocode_cache` | Table `geocode_cache(query_hash TEXT PRIMARY KEY, provider TEXT, query_text TEXT, result_json TEXT, lat REAL, lng REAL, created_at TEXT, hit_count INTEGER DEFAULT 0)` + index sur `created_at` (purge). Le cache est **partagé entre workspaces** par défaut (une adresse publique n'est pas une donnée d'utilisateur) — réglage pour l'isoler par workspace, §17.3 | 3 |
| **46** | `dashboard_widgets` | Table `dashboard_widgets(id INTEGER PK, view_id INTEGER FK→collection_views ON DELETE CASCADE, position INTEGER, row_index INTEGER, col_span INTEGER DEFAULT 1, height_units INTEGER DEFAULT 2, source_collection_id INTEGER FK→collections, source_view_id INTEGER FK→collection_views NULL, inline_config_json TEXT, created_at TEXT)` + index `(view_id, row_index, position)`. `source_view_id NULL` + `inline_config_json` = widget dont la vue est propre au Dashboard (cas « créer une vue pour ce tableau de bord » de Notion) | 4 |
| **47** | `form_questions_v2` | Table `form_questions(id INTEGER PK, view_id INTEGER FK→collection_views ON DELETE CASCADE, property_name TEXT, position INTEGER, label_override TEXT NULL, sync_label INTEGER DEFAULT 1, help_text TEXT, required INTEGER DEFAULT 0, widget TEXT, max_selections INTEGER NULL, conditional_json TEXT NULL, UNIQUE(view_id, property_name))` + colonnes sur `form_responses` : `view_id INTEGER NULL`, `respondent_user_id INTEGER NULL FK→users`, `anonymous INTEGER DEFAULT 0` (ALTER, valeurs existantes préservées). La table ne remplace pas `collections.form_config_json` : elle devient la source du **builder**, et un projecteur régénère le `form_config_json` consommé par le pipeline public existant (§16.5) | 5 |
Pourquoi des tables pour Dashboard et Form, mais pas pour les autres vues ? Parce que ces deux objets **référencent et contraignent d'autres objets** (un widget pointe une vue d'une autre base, avec des règles d'intégrité et de suppression en cascade ; une question pointe une propriété avec un ordre et une logique conditionnelle) — exactement le critère qui, dans le modèle Flowdeck, distingue ce qui va en table de ce qui reste en JSON de configuration.
### 10.4 Validation et versionnage des `config_json`
- Chaque `view_type` promu a un **schéma de validation** serveur (fonctions Python dédiées, dans l'esprit de `validate_property_value()` §11.2 — pas d'introduction de framework) : clés inconnues ignorées à la lecture, refusées à l'écriture ; types et énumérations vérifiés ; propriétés référencées vérifiées contre le schéma et les ACL.
- `config_version: 2` marque les configs écrites par le nouveau système. La lecture d'une config v1 (rendus SSR) applique les conversions de §9.2 (pie→doughnut…) à la volée, sans réécriture ; la première sauvegarde depuis le nouveau panneau écrit du v2.
---
## 11. API et événements
### 11.1 Endpoints (préfixe existant `/db`, session authentifiée, CSRF porté par htmx comme l'existant)
```text
# Socle de vues (extensions de l'existant)
POST /db/{collection_id}/views # créer une vue (view_type + nom)
# → applique les prérequis §7.3
PUT /db/views/{view_id}/config # EXISTANT — validation v2 ajoutée
# Données de vues
GET /db/{collection_id}/views/{view_id}/rows?cursor=…&window=…
# lignes filtrées/triées/projetées
POST /db/{collection_id}/views/{view_id}/aggregate
# corps : surcharge d'agrégation
# optionnelle (filtres globaux)
GET /db/{collection_id}/views/{view_id}/drilldown?group=…&sub=…
# = rows + filtre de groupe éphémère
# Map / Place
GET /db/geocode/search?q=… # autocomplétion d'adresses
# (permission : éditer une ligne
# de la collection concernée)
POST /db/geocode/resolve # adresse → coordonnées (cache §13.4)
# Dashboard
GET /db/{collection_id}/views/{view_id}/widgets
PUT /db/{collection_id}/views/{view_id}/widgets
# remplace la disposition complète
# (mode Édition uniquement)
GET /db/{collection_id}/views/{view_id}/widgets/{widget_id}/data
# rows ou aggregate selon le type
# de la vue source du widget
# Form builder
GET /db/{collection_id}/views/{view_id}/form # questions + réglages de partage
PUT /db/{collection_id}/views/{view_id}/form # questions (déclenche la
# projection form_config_json)
POST /db/{collection_id}/views/{view_id}/form/preview-token
# jeton d'aperçu non-répertorié,
# soumission désactivée
```
### 11.2 Formulaires publics — inchangés en surface
```text
GET /f/<token> # EXISTANT (v6.8) — rend le formulaire depuis le
# form_config_json projeté (§16.5)
POST /f/<token> # EXISTANT — validation, rate limiting, form_responses,
# événement form.submitted ; renseigne désormais
# view_id / respondent_user_id / anonymous (mig. 47)
```
### 11.3 API publique v2
Les mêmes opérations sont exposées en lecture dans l'API v2 (`/api/v2/…`, jetons Bearer existants) : récupérer les lignes d'une vue et l'agrégat d'une vue Chart — c'est ce qui permet aux exports, aux Workers et à l'agent de consommer une vue **telle qu'elle est configurée**, plutôt que de réimplémenter ses filtres. Écritures (création de vue, disposition de Dashboard, questions de formulaire) : API v2 avec le même niveau de permission que l'UI, scopées par jeton.
### 11.4 Événements
| Événement | Émetteur | Consommateurs |
|---|---|---|
| `view.created`, `view.config_updated`, `view.deleted` | routeurs de vues | audit unifié (§9.4 de l'architecture), invalidation de cache |
| `collection.row_created/updated/deleted` *(existants, noms à vérifier)* | services de lignes | invalidation du cache d'agrégation §8.5, rafraîchissement temps réel §7.5 |
| `form.submitted` *(existant)* | pipeline `/f/` | automatisations (inchangé) |
| `dashboard.layout_updated` | builder Dashboard | audit ; aucun effet métier |
| `place.geocode_failed` | Geocoding Service | métriques d'exploitation §20 (jamais le texte de l'adresse dans les journaux — §17.3) |
### 11.5 Contrat pour l'agent IA
L'agent de Flowdeck (§16) doit pouvoir, par ses outils existants d'édition de bases enrichis de deux actions : `create_view(collection, view_type, name, config)` et `set_dashboard_widgets(view, layout)`. La génération « premier jet » d'un Dashboard (équivalent de l'usage de l'Agent Notion, §3.2) se ramène alors à : analyser le schéma → créer 3–6 vues sources → créer la vue Dashboard → poser les widgets. Aucun outil spécial « dashboard » n'est nécessaire au-delà de ces deux actions génériques (lien avec le document Agents & Skills : le routeur de skills peut empaqueter cette recette en skill « tableau de bord de projet »).
---
## 12. Vue Timeline
### 12.1 Objectif
Promouvoir le rendu SSR « barres Gantt » en Timeline cliente interactive, et trancher son rapport à la vue *Gantt* SSR existante : **la Timeline promue absorbe l'usage Gantt courant** ; le Gantt SSR reste accessible par ses anciennes URL pendant la transition (§7.6), et ses éventuelles spécificités projet (dépendances affichées) sont reprises comme option de la Timeline (§12.5) plutôt que comme vue séparée.
### 12.2 Configuration
```json
{
"version": 2,
"date_property": "Due",
"separate_end_property": null,
"scale": "quarter",
"show_table": false,
"table_properties": ["Name", "Status", "Assignee"],
"load_limit": 200,
"show_dependencies": false,
"sub_items": "flattened"
}
```
- `date_property` : propriété date/plage de tracé (réglage « Show timeline by » de Notion) ; `separate_end_property` non nul = début et fin dans deux propriétés.
- `scale` : `hour | day | week | month | quarter | year` — les six échelles de Notion, `quarter` étant celle de la capture 05.
- `sub_items` : `flattened` (défaut — aligné sur le comportement Chart de Notion, les sous-éléments comptent comme des lignes) ou `top_level`.
### 12.3 Rendu et interactions
- **Axe temporel** : en-tête à deux niveaux (mois/année au-dessus, graduations de l'échelle en dessous), calculé côté client à partir de la fenêtre demandée ; défilement horizontal avec chargement de fenêtre (l'API §11.1 reçoit `window`) ; **ligne rouge du jour** et bouton *Today* (capture 05).
- **Barres** : une par ligne datée, libellé = titre, couleur = couleur de l'option du `group_by` éventuel (statut) ; débordements signalés par des flèches en bord de ligne, cliquables pour recentrer (comportement Notion §3.3).
- **Glisser** : (a) déplacer la barre = décaler début et fin du même delta ; (b) tirer un bord = changer cette date seule ; pendant le geste, infobulle de la date visée ; au relâcher, écriture par l'API d'édition de cellule de la propriété date (principe 4), avec retour visuel en cas d'échec (la barre revient). Granularité d'accrochage : le jour (l'heure en échelle `hour`, si la propriété porte des heures).
- **Réordonnancement vertical** : glisser une ligne dans la liste ne change **pas** de données métier chez Notion (c'est un ordre d'affichage) ; Flowdeck le persiste dans `config_json` (`row_order`, liste d'ids) plutôt que d'inventer une propriété — les lignes non listées suivent le tri de la vue.
- **Seau « sans date »** : compteur toujours visible en en-tête (« No date (5) », capture 05) ; son ouverture affiche les lignes concernées en panneau Table restreint, avec bouton d'ajout de date ligne à ligne.
- **Clic sur une barre** : ouvre la page de la ligne (peek latéral ou pleine page selon le réglage général d'ouverture des vues, hérité de Table).
### 12.4 Écriture et garde-fous
- Une ligne dont la plage est verrouillée par les permissions (ACL propriété en lecture seule) a des barres non déplaçables, avec curseur explicite.
- **Dépendances** (§11.5 de l'architecture) : si la ligne est liée par une dépendance bloquante, le déplacement qui violerait la contrainte de statut n'est pas bloqué au niveau des dates (les dépendances Flowdeck contraignent les transitions de statut, pas les dates), mais `auto_shift` s'applique comme dans l'édition ordinaire — la Timeline ne réimplémente pas ces règles, elle appelle le même service d'écriture.
- Annulation : chaque glisser = une écriture = une entrée d'historique ordinaire, annulable par les mécanismes existants (*à vérifier : portée exacte de l'undo des éditions de cellules*).
### 12.5 Table latérale et calculs
La table latérale (§3B.3, zone 2) est le module **Table existant en mode embarqué restreint** : colonnes = `table_properties`, calculs de pied de colonne réutilisant les définitions déjà en place pour Table (comptes, pourcentages, extrêmes de dates, agrégats de nombres — la liste de Notion §3.3 recoupe celles de Flowdeck) ; édition de cellule autorisée (c'est une vraie table), contrairement au drilldown de Chart.
### 12.6 Rapport au calendrier
La Timeline ne duplique pas la vue Calendar cliente. Un lien « Voir en calendrier » (équivalent fonctionnel du *Manage in Calendar* de la capture 05) bascule vers la vue Calendar de la même base filtrée pareillement, si elle existe, ou propose de la créer. Aucune synchronisation externe nouvelle : la sync calendrier §18.2 de l'architecture reste le seul pont vers l'extérieur.
---
## 13. Vue Map et type de propriété `place`
### 13.1 Objectif
Remplacer le géocodage opportuniste « `lat,lng` depuis les propriétés » par le modèle de Notion : un **type de propriété dédié**, une saisie assistée, et une vue qui sait quel propriété tracer — tout en ajoutant ce que l'auto-hébergement de Flowdeck exige et que Notion n'a pas à gérer : **le choix et le contrôle du fournisseur de géocodage** (§13.4, §17.3).
### 13.2 Le type `place` (22ᵉ type de propriété)
- Enregistrement dans `property_types.py` au même titre que les 21 existants : nom `place`, catégorie « Avancés », icône épingle.
- **Valeur** (dans `property_values_json`, aucun changement de table) :
```json
{"name": "Beloeil", "address": "660 Rue Aragon, Beloeil, QC J3G, Canada",
"lat": 45.56, "lng": -73.20, "provider": "nominatim", "place_id": "…",
"resolved_at": "2026-10-10T13:00:00Z", "manual": false}
```
- `lat`/`lng` peuvent être `null` (lieu nommé non résolu) : la ligne existe, la Map la compte comme « sans coordonnées » (§3B.5).
- `manual: true` = coordonnées saisies/corrigées à la main ; un re-géocodage ne les écrase jamais sans confirmation.
- `validate_property_value()` pour `place` : objet, `address` ou `name` requis, `lat ∈ [-90, 90]`, `lng ∈ [-180, 180]` si présents.
- **Conversion** depuis une propriété `text` : assistant en deux temps (échantillon des valeurs actuelles → résolution en lot par le Geocoding Service, avec rapport succès/échecs → bascule du `prop_type` seulement après validation de l'utilisateur) — répond au piège de nettoyage documenté par Notion (§3.5).
- Affichage dans les autres vues : cellule = nom + adresse tronquée, cliquable (peek avec mini-carte si coordonnées) ; en Table, tri alphabétique sur `name` puis `address`, filtre textuel `contains` sur les deux champs (sémantique Notion §3.5), plus opérateurs nouveaux `is_resolved` / `is_not_resolved`.
- Import/export CSV : `place` rejoint le sous-ensemble `SIMPLE_TYPES` au format texte `name|address|lat,lng` documenté ; un CSV ne contenant que des adresses s'importe en lieux non résolus.
### 13.3 Éditeur de cellule `place`
Champ texte avec **autocomplétion** (débounce 300 ms, `GET /db/geocode/search`) alimentée par le fournisseur configuré ; trois modes de saisie comme Notion : texte libre (nom ou adresse), « Utiliser ma position » (API de géolocalisation du navigateur, **à la demande explicite du clic**, coordonnées seules envoyées en géocodage inverse), et saisie manuelle des coordonnées (mode avancé, repli sans fournisseur). Sélection d'une suggestion = valeur complète résolue ; frappe sans sélection = lieu nommé non résolu, résoluble plus tard (bouton « Résoudre » dans la cellule et action de masse dans les réglages de la propriété).
### 13.4 Geocoding Service (`app/services/geocoding.py`, nouveau)
- **Interface de fournisseur** unique : `search(query) → [suggestions]`, `resolve(address) → {lat, lng, place_id}`, `reverse(lat, lng) → adresse`. Implémentations : `none` (défaut — saisie manuelle uniquement, aucun appel externe), `nominatim` (OpenStreetMap ; respecte sa politique d'usage : User-Agent identifié, ≤ 1 req/s, cache obligatoire), `custom` (URL d'endpoint compatible Nominatim, pour un serveur auto-hébergé ou un fournisseur choisi par l'admin). *Un fournisseur commercial (Google/Mapbox) n'est pas livré dans ce chantier* : l'interface le permet, la décision est renvoyée en question ouverte (Q1).
- **Configuration** au niveau instance (section dans `app/config.py`, panneau admin) : fournisseur, endpoint, clé éventuelle (stockée chiffrée Fernet comme les autres secrets, §8.5 de l'architecture), activation par workspace (opt-in).
- **Cache** (migration 45) consulté avant tout appel : clé = hash de la requête normalisée (minuscules, espaces compactés) + fournisseur. Les résolutions de masse (conversion §13.2, bouton « résoudre les non-résolus ») passent par un Worker existant (§19.2) avec débit limité et journal d'avancement, jamais en ligne dans une requête HTTP.
- **Journaux** : compteurs et échecs seulement ; **jamais les adresses en clair** dans les logs applicatifs (§17.3) — le `query_text` du cache est une donnée fonctionnelle (nécessaire à l'affichage admin du cache), protégée par les permissions d'administration et purgeable (bouton + TTL réglable, défaut 180 jours).
### 13.5 La vue Map
- Module client sur Leaflet vendorisé ; tuiles = même source que la Map SSR actuelle *(à vérifier : URL de tuiles et attribution)*.
- **Tracé** : `config_json.place_property` (réglage « Map by » de Notion) ; si plusieurs propriétés `place`, sélecteur dans le panneau ; si une seule, implicite ; si aucune → prérequis §7.3.
- **À l'ouverture** : cadrage automatique sur le bounding box des épingles filtrées (avec padding ; zoom max borné pour un point unique) — amélioration explicite par rapport au cadrage de la capture 07 (§3A).
- **Plafond** : `MAP_POINTS_MAX` (100) appliqué aux lignes **avec coordonnées** après filtres ; dépassement → seules les 100 premières (ordre de la vue) s'affichent + bandeau « N lieux au total — affinez les filtres », jamais de carte muettement incomplète.
- **Regroupement** : en dessous d'un zoom faible, épingles superposées regroupées en cluster avec compteur — implémentation maison légère sur Leaflet (pas de nouveau vendorisé sans ADR) ; au-delà de quelques centaines de mètres d'écart, épingles individuelles.
- **Clic épingle** : popup (titre, propriétés visibles de la vue, miniature si propriété fichier image désignée en couverture) + bouton d'ouverture de la page.
- Filtres/tris : ceux de la vue, appliqués par le View Query Service avant extraction des coordonnées (la vue ne reçoit que des points : `{row_id, title, lat, lng, props}`).
### 13.6 Permissions et vie privée de la Map
- Une propriété `place` masquée par ACL n'est ni tracée ni proposée en « Map by ».
- Le bouton « Utiliser ma position » ne déclenche **aucune** demande de géolocalisation du navigateur tant qu'il n'est pas cliqué, et la position n'est **jamais** stockée ailleurs que dans la valeur de la cellule éditée (pas de télémétrie de position des utilisateurs).
### 13.7 Points d'extension (hors v1)
Heatmap, tracé de polygones (propriété « zone »), distances et itinéraires, fond de carte hors ligne — tous exclus (§2.2) mais compatibles avec la valeur `place` telle que définie (coordonnées explicites, fournisseur découplé).
---
## 14. Vue Feed
### 14.1 Objectif
Promouvoir le flux chronologique SSR en cartes interactives fidèles à la capture 06 : le Feed est la vue de **lecture et d'engagement** d'une base (comptes rendus, annonces, journaux de projet), complémentaire de Table (édition) et de Chart (synthèse).
### 14.2 Anatomie d'une carte (figée par la capture 06 et §3B.4)
1. **En-tête** : avatar + nom de l'auteur, ancienneté relative de la dernière édition avec mention « (edited) » quand la ligne a été modifiée après création (*auteur* = créateur de la ligne ; si `last_edited_by` diffère, l'en-tête affiche le dernier éditeur — règle à trancher en revue de maquette, défaut retenu : dernier éditeur, comme le laisse lire la capture) ; compteur de vues (zone 1 de §3B.4).
2. **Titre** de la ligne, en grand, cliquable (ouverture de la page).
3. **Propriétés visibles** de la vue, en puces sous le titre (statut coloré, dates, personnes…) — réglées par le panneau commun de visibilité.
4. **Extrait de contenu** : texte rendu de la page-ombre (§13 de l'architecture : blocs → markdown → texte), tronqué à ~280 caractères ou ~3 blocs, images ignorées en v1 sauf la première si une propriété de couverture est désignée. Les lignes sans page-ombre ou sans contenu n'affichent pas de zone d'extrait.
5. **Réactions** : bouton d'ajout + réactions existantes avec compteurs, utilisant les réactions de la page-ombre / de la ligne (*mécanisme à vérifier : `text_reactions` vise des ancres de texte ; si aucune réaction « de page » n'existe, la phase 2 ajoute le plus petit mécanisme compatible — réaction cible polymorphe `collection_page` — plutôt qu'un système dédié au Feed*).
6. **Commentaires** : dernier commentaire visible + champ d'ajout en ligne (« Add a comment… »), fil complet dépliable ; écriture = création d'un commentaire polymorphe ordinaire (§4.1).
### 14.3 Données et règles
- **Tri par défaut** : `last_edited_time desc` (le Feed est un flux d'activité éditoriale) ; tout autre tri de la vue reste possible mais l'en-tête de carte continue d'afficher les temps d'édition.
- **Pagination** par curseur sur le couple `(last_edited_time, id)`, pages de 20, chargement au défilement avec sentinelle (pas de bouton « charger plus » sauf échec réseau).
- **Compteur de vues** : incrémenté quand la page d'une ligne est **ouverte** (pas à la simple impression de la carte — sinon le compteur mesure le défilement, pas la lecture) ; source `page_views` de la page-ombre. Affiché à l'auteur et aux éditeurs de la base ; masquable par réglage de vue (`show_view_count`, utile pour ne pas transformer le Feed en tableau de surveillance — §17.4).
- Sous-éléments : aplatis comme les autres vues ; un filtre par défaut suggéré à la création (« masquer les lignes archivées » si une propriété statut possède une option d'archivage) — proposition, pas obligation.
### 14.4 Ce que le Feed n'est pas
Pas d'édition de propriétés dans la carte (ouvrir la ligne pour éditer — seuls réactions et commentaires sont éditables en place, comme chez Notion) ; pas de fil social global multi-bases (le « feed » d'accueil éventuel relèverait d'un autre chantier, en lien avec My Tasks et les notifications).
---
## 15. Vue Dashboard
### 15.1 Objectif
Créer la vue `dashboard` qui manque (E4) : un onglet de base affichant une grille de widgets, chacun montrant une vue — de cette base **ou d'une autre source** — avec séparation stricte entre consulter et concevoir, à la manière de Notion (§3.2). Le Dashboard-page actuel (`collection_dashboards`) continue d'exister pour les pages composites ; un utilitaire de conversion est proposé (§15.7).
### 15.2 Modèle
```text
collection_views (view_type = 'dashboard')
└─ config_json : { version, global_filters: [...], row_heights: {...} }
└─ dashboard_widgets (mig. 46)
├─ source_collection_id + source_view_id → widget « vue existante »
├─ source_collection_id + inline_config → widget « vue propre »
└─ row_index, position, col_span, height_units
```
- **Grille** : lignes de widgets ; **max 4 widgets par ligne, max 12 widgets** (plafonds Notion repris comme garde-fous de performance, §3.7) ; `col_span` exprime la largeur relative dans la ligne (les largeurs se normalisent à la ligne) ; `height_units` par widget, la hauteur de ligne = le max de ses widgets (alignement propre des lignes, argument Notion pour la vue plutôt que les colonnes de page).
- **Source** : une collection du même workspace, atteignable par l'utilisateur ; une « source » au sens des bases liées (`collection_data_sources`) — le widget ne crée pas de nouveau droit d'accès (§17.2).
### 15.3 Modes Lecture et Édition
| | Lecture (défaut) | Édition |
|---|---|---|
| Qui | tout utilisateur ayant accès à la base | accès **édition** à la base |
| Widgets | consultation, ouverture des lignes, interactions propres à chaque vue embarquée (drilldown de chart, drag de timeline **désactivé** par défaut dans un widget — réglage par widget) | ajout/suppression/duplication, déplacement, largeurs et hauteurs |
| Filtres de widget | modifiables localement, non persistés | persistés via « enregistrer » explicite |
| Filtres globaux | utilisables | définissables + utilisables |
Le mode est un état client ; l'entrée en Édition est un bouton explicite, la sortie propose d'enregistrer ou d'abandonner la disposition (PUT complet §11.1, transaction côté serveur : disposition tout-ou-rien).
### 15.4 État vide (remplace l'écran commercial de la capture 04)
Trois cartes **fonctionnelles**, reprenant la pédagogie de Notion sans son paywall :
1. « Créer avec l'agent » — ouvre le panneau agent avec une consigne pré-remplie nommant la base (§11.5) ; désactivée si l'agent est coupé par plugin.
2. « Ajouter un widget » — ouvre directement le sélecteur de vue (vues de cette base d'abord, puis « autre source », puis « nouvelle vue pour ce tableau de bord »).
3. « Partir d'un modèle » — trois gabarits livrés : *État d'équipe*, *Suivi d'incidents/tickets*, *Rapport de direction* (les trois patrons documentés par Notion §3.2, traduits en dispositions de widgets sur les propriétés de la base quand elles existent : chart par statut, table filtrée prioritaire, timeline de jalons).
### 15.5 Filtres globaux
- Définis dans `config_json.global_filters` : liste de `{property_name, operator, value}` **résolus par nom de propriété**, appliqués à chaque widget dont la source possède une propriété de ce nom et d'un type compatible ; silencieusement ignorés par les widgets qui ne l'ont pas (sémantique Notion §3.2).
- À l'exécution, un filtre global devient une **surcharge** passée au View Query Service / à l'agrégation du widget (§8.3), jamais une modification de la config de la vue source.
- Homonymie-piège (deux sources avec un `Status` de significations différentes) : l'UI d'édition liste, pour chaque filtre global, les widgets concernés — et permet d'**exclure** un widget d'un filtre (`excluded_widget_ids` dans l'entrée du filtre).
### 15.6 Chargement et performance
- Les widgets se chargent **en parallèle borné** (file de 4 requêtes simultanées côté client, plafond serveur par utilisateur aligné sur le rate limiting existant) ; chaque widget a son propre état de chargement et d'erreur — un widget en échec n'empêche pas les autres de s'afficher, et propose « réessayer » seul.
- Les widgets Table sont **plafonnés** (50 lignes affichées, lien « ouvrir la vue » pour le reste) ; un widget Chart consomme l'agrégation (peu coûteuse), un widget Feed est proscrit en v1 (cartes trop hautes, engagement trompeur hors de sa vue).
- Conseil intégré d'édition : si un widget Table n'a aucun filtre, un avertissement non bloquant le signale (reprise de la guidance de performance de Notion §3.2, sous forme d'aide plutôt que de documentation externe).
### 15.7 Conversion depuis `collection_dashboards`
Un script de conversion (commande d'administration, pas une migration automatique) transforme une page-Dashboard existante en vue Dashboard de sa collection principale quand tous ses widgets référencent des vues de collections ; les widgets non convertibles sont listés et laissés en place. **Aucune suppression** de l'ancien mécanisme dans ce chantier.
### 15.8 Permissions
- Voir un Dashboard = voir la base ; chaque widget applique en outre les ACL de **sa** source : un widget dont la source n'est pas accessible à l'utilisateur affiche un cartouche « source non accessible » sans titre de vue ni compte — pas de fuite par les métadonnées.
- Éditer la disposition exige l'édition sur la base porteuse ; éditer la vue source d'un widget exige l'édition sur la source (l'UI désactive l'action sinon, avec explication).
---
## 16. Vue Form et Form builder
### 16.1 Objectif
Ajouter le builder intégré qui manque (E5) **sans toucher au pipeline de soumission qui fonctionne** : le builder est une nouvelle surface d'édition de configuration ; le formulaire public `/f/<token>` et ses protections (validation, rate limiting, RGPD des sites/formulaires v6.8) restent la seule voie de soumission.
### 16.2 L'écran builder (fidélité à la capture 08 et §3B.6)
- Barre d'actions propre à la vue (déclarée par le module dans le registre, §7.2) : **Preview** et **Share form** remplacent le bouton *New*.
- Zone d'édition centrale, colonne unique : titre (grand, éditable en place), description, icône/couverture du formulaire, **bandeau de portée** toujours visible avec action *Change* qui ouvre le panneau de partage (§16.4).
- Chaque question est une carte : libellé éditable, aperçu du champ désactivé (« Respondent's answer »), poignée de réordonnancement, menu `•••` (§16.3).
- Bouton « + Add a question » en bas : crée **une propriété** (type choisi dans le sélecteur de types ordinaire) et la question correspondante — dans cet ordre, dans une seule transaction d'API.
### 16.3 Questions : modèle et synchronisation
Source de vérité : `form_questions` (migration 47), une ligne par propriété exposée dans le formulaire.
- **Synchronisée par défaut** (`sync_label = 1`) : le libellé de la question **est** le nom de la propriété ; le renommer renomme la propriété partout (comportement Notion §3.6), avec confirmation explicite quand la propriété est utilisée par des formules/rollups/vues (recherche des références avant renommage, liste affichée — Flowdeck peut faire mieux que Notion ici grâce à son schéma déclaratif centralisé).
- **Désynchronisée** (`sync_label = 0`, `label_override` renseigné) : libellé propre au formulaire, propriété inchangée.
- Réglages par question (menu `•••`) : requis ; description d'aide ; affichage des options (liste / menu déroulant) pour les choix ; réponse longue (texte) ; type de question = changement de type de propriété, soumis aux règles de conversion existantes ; `max_selections` pour multi-sélect / relation / personnes ; duplication (duplique la question **et crée une nouvelle propriété** — jamais deux questions sur la même propriété, garanti par la contrainte d'unicité de la migration 47) ; suppression (deux options explicites : « retirer du formulaire » ou « supprimer aussi la propriété », la seconde exigeant la confirmation de perte de données).
- **Types non exposables** : formules, rollups, boutons, `unique_id`, propriétés automatiques (créé le/par…) — cohérent avec le pipeline public existant qui génère déjà ses champs « hors formules ».
- **Logique conditionnelle** (v1 volontairement simple) : `conditional_json = {"if_property": "Type", "equals": "Autre", "then": "show"}` — une condition, sur une question à choix (select/status), montrant/masquant une question ; évaluation côté client du formulaire public (affichage) **et** côté serveur à la soumission (une question masquée ne peut pas être requise, ses valeurs soumises sont ignorées). Les conditions en chaîne et les embranchements multi-niveaux sont hors v1 (§24, Q5).
### 16.4 Partage et accès (panneau Share form)
| Réglage | Valeurs | Correspondance existante |
|---|---|---|
| Qui peut remplir | Membres du workspace avec le lien · Toute personne sur le web avec le lien · Fermé (aucun accès) | le token `/f/` existant + contrôle d'accès à ajouter pour le mode « membres » (session requise) et l'état « fermé » (410 sur la page publique, distinct de la suppression) |
| Réponses anonymes | oui / non (forcé oui en mode web) | propriété répondant : `created_by` si la base en a une, sinon colonne `respondent_user_id` de `form_responses` (mig. 47) ; **création assistée** de la propriété répondant à la première activation du mode non-anonyme (équivalent du `Respondent`/`Created by` de Notion) |
| Accès du répondant à sa soumission | Aucun · Voir · Commenter · Éditer · Accès complet | Implémenté comme un **partage de ligne** ordinaire (mécanisme `page_shares` / partages invités existants) créé au moment de la soumission quand le répondant est identifié ; « Accès complet » suit la définition du partage existant |
| Branding | Afficher/masquer la mention Flowdeck sur la page publique | simple réglage d'affichage (pas de notion de plan) |
| Interdiction globale | L'admin de workspace peut interdire le mode « web » pour tous les formulaires | réglage de workspace, vérifié à l'ouverture du lien comme à l'enregistrement du réglage |
### 16.5 Projection vers le pipeline existant
Le builder n'écrit **jamais** directement ce que le public consomme :
```text
form_questions + réglages de la vue form
│ (à chaque PUT …/form, transaction)
▼
projecteur app/services/form_projection.py
│ régénère collections.form_config_json
│ (format v6.8 inchangé) + invalide le cache de rendu /f/
▼
GET/POST /f/<token> (code existant, non modifié hors §11.2)
```
Bénéfices : zéro régression possible sur les formulaires déjà partagés ; le builder peut être activé/désactivé par feature flag sans effet sur le public ; un test de parité compare le `form_config_json` projeté à celui qu'aurait produit l'ancien éditeur pour les configurations simples.
### 16.6 Écran de soumission, aperçu, exploitation
- **Écran de confirmation** : titre, corps (markdown léger), texte et couleur du bouton d'envoi — réglages de la vue, projetés comme les questions ; option « recevoir une copie de chaque soumission par courriel » réutilisant les notifications existantes (les membres sont déjà notifiés des soumissions ; la copie courriel vise un destinataire désigné).
- **Preview** : rend le formulaire réel dans un cadre d'aperçu, avec jeton d'aperçu (§11.1) dont les soumissions sont **refusées côté serveur** (pas seulement masquées côté client) ; bascule desktop/mobile purement visuelle.
- **Réponses** : consultées dans la base (vue Table « Responses » créée automatiquement à la première soumission si elle n'existe pas — nommage aligné sur Notion ; c'est une vue ordinaire, renommable) ; pas d'export depuis la vue Form (exporter depuis la Table, comme chez Notion).
- **Automatisations** : aucun nouveau mécanisme — l'événement `form.submitted` alimente déjà le moteur (§19.1) ; le builder expose un raccourci « Automatisations de ce formulaire » qui ouvre l'éditeur d'automatisations pré-filtré sur cet événement et cette collection.
- **Mobile** : le builder est desktop/web (comme chez Notion) ; le formulaire public, lui, est pleinement responsive — c'est lui que les répondants utilisent sur mobile.
---
## 17. Sécurité, permissions et vie privée
### 17.1 Règles transverses
- Toute donnée de vue (lignes, agrégats, points de carte, widgets) transite par le View Query Service, qui applique les ACL **collection et propriété** avant tout calcul (§8.1) : pas d'agrégat ni de comptage sur une propriété invisible, pas de drilldown vers des lignes non lisibles.
- Les endpoints de données de vues exigent la même permission de lecture que `GET /db/{id}` ; les écritures de configuration exigent l'édition de la base ; les vues **personnelles** (`created_by` non NULL) ne sont lisibles et modifiables que par leur auteur (et les admins de workspace), y compris leurs agrégats.
- Validation systématique des `config_json` côté serveur (§10.4) : un client compromis ne peut pas faire référencer à une vue une propriété d'une autre collection, sauf pour les widgets de Dashboard — qui re-vérifient l'accès à la source à **chaque lecture** (§15.8), pas seulement à la création.
### 17.2 Dashboards multi-sources
Le risque propre au Dashboard est la **combinaison** : assembler des vues de sources différentes peut créer un écran qu'aucun rôle n'était censé voir d'un bloc. La règle §15.8 (cartouche sans métadonnées pour les sources inaccessibles) s'applique aussi aux filtres globaux : un filtre global ne doit pas révéler, par son libellé ou ses valeurs proposées, les options d'une propriété que l'utilisateur ne voit dans aucun widget accessible — les valeurs proposées sont calculées depuis les seuls widgets accessibles.
### 17.3 Géocodage et adresses
- L'envoi d'une adresse à un fournisseur tiers est un **transfert de données hors de l'instance** : désactivé par défaut (`none`), activé par un administrateur qui choisit le fournisseur, avec mention explicite dans le panneau d'administration de ce qui est envoyé (le texte de la requête de recherche/résolution, rien d'autre — ni l'identité de l'utilisateur, ni le contenu de la base).
- L'autocomplétion n'envoie la frappe qu'après 3 caractères et 300 ms d'arrêt ; un mode « résolution manuelle uniquement » par workspace désactive même cela.
- Journaux sans adresses (§13.4) ; cache purgeable et à TTL ; les coordonnées d'une ligne restent, elles, une donnée ordinaire de la base, soumise à ses ACL et à l'export.
### 17.4 Feed : vues et surveillance
Le compteur de vues est une information sur le **comportement de lecture des collègues**. Règles : visible seulement pour l'auteur de la ligne et les éditeurs de la base ; désactivable par vue (`show_view_count`) et par réglage de workspace ; jamais exposé dans l'API publique v2 en v1 ; aucune notification « X a vu votre publication » n'est créée (le compteur est consultatif).
### 17.5 Formulaires publics
- Aucun changement aux protections v6.8 (rate limiting, validation, `ip_hash` tournant sans IP brute) ; le mode « fermé » et l'interdiction workspace du mode web sont vérifiés **à la soumission comme à l'affichage** (un réglage changé pendant qu'un formulaire est ouvert doit prendre effet).
- La logique conditionnelle ne doit jamais servir de contrôle d'accès : les valeurs des questions masquées sont ignorées, mais une question **requise** masquée ne bloque pas la soumission ; les validations métier restent côté serveur, identiques avec ou sans condition.
- L'accès « répondant à sa soumission » crée un partage de ligne **révocable** comme tout partage ; la suppression de la ligne ou la fermeture du formulaire ne laisse pas de partage orphelin (nettoyage par les cascades existantes, à tester).
### 17.6 CSP et contenu embarqué
- Tuiles de carte et canvas de graphique n'élargissent pas la CSP : les domaines de tuiles déjà utilisés par la Map SSR sont ceux autorisés ; si un fond de carte différent est choisi dans le futur, la CSP se met à jour dans le même commit (audit A20 : jamais d'`unsafe-eval`, les plugins Chart.js locaux sont du code propre au module, pas des chaînes évaluées).
- Les extraits de Feed proviennent de contenu utilisateur déjà sanitisé par le pipeline de l'éditeur ; l'extrait est rendu en **texte** (tags retirés), jamais en HTML réinterprété.
---
## 18. Exigences non fonctionnelles
### 18.1 Performance — budgets par vue (base de 10 000 lignes, poste standard)
| Vue | Budget | Mécanisme |
|---|---|---|
| Chart | agrégat < 300 ms serveur, rendu < 500 ms | Aggregation Service + cache §8.5 ; plafonds 200/50 |
| Timeline | première fenêtre < 800 ms | chargement par fenêtre, `load_limit` |
| Feed | première page (20 cartes) < 800 ms | curseur ; extraits pré-calculés à la lecture de la page-ombre, tronqués côté serveur |
| Map | 100 points < 500 ms | points seuls (pas de lignes complètes), clusters côté client |
| Dashboard | utilisable < 1,5 s avec 12 widgets, aucun widget bloquant | parallélisme borné §15.6, états par widget |
| Form builder | ouverture < 800 ms | questions paginées au-delà de 100 (rare), aperçu à la demande |
### 18.2 Seuil de calcul client conservé
Pour les bases ≤ 500 lignes, le mode actuel de `DBInstance` (charger les lignes, filtrer/grouper côté client) peut être conservé pour Table/Board/etc. ; les six vues promues utilisent **toujours** les endpoints serveur pour leurs données propres (agrégat, points, fenêtre), afin que le comportement soit identique quelle que soit la taille de la base — pas de double implémentation de la sémantique d'agrégation.
### 18.3 Accessibilité
Chaque vue : navigation clavier complète (onglets, panneaux, barres de Timeline sélectionnables et déplaçables aux flèches par pas d'un jour, épingles atteignables dans une liste jumelle masquée-visuellement pour la Map), rôles ARIA sur les widgets de Dashboard, alternative tabulaire au graphique (§9.4), contrastes des couleurs de statut hérités du thème (les couleurs d'options du schéma sont supposées déjà conformes).
### 18.4 Hors ligne (PWA)
En lecture : la dernière réponse de données d'une vue peut être servie par le cache de données existant, avec bandeau « données potentiellement anciennes ». En écriture : aucune création de vue, aucun déplacement de Timeline, aucune soumission de formulaire interne n'est mis en file en v1 (les soumissions **publiques** ne relèvent pas de la PWA). À la reconnexion, aucune file spécifique aux vues n'est à rejouer.
### 18.5 Compatibilité navigateur et mobile
Mêmes cibles que le reste de Flowdeck ; les vues Chart/Timeline/Map/Dashboard sont pleinement utilisables en desktop ; en mobile : Chart et Feed en lecture complète, Timeline en lecture + déplacement par panneau de dates (le glisser fin au doigt est dégradé mais les dates restent éditables en ouvrant la ligne), Map complète (c'est un cas d'usage terrain), Dashboard en lecture (grille empilée), builder de Form en lecture seule avec invitation à passer sur desktop (aligné sur Notion §3.6).
---
## 19. Résilience et gestion des échecs
| Échec | Comportement |
|---|---|
| Fournisseur de géocodage injoignable / lent (> 3 s) | autocomplétion silencieusement désactivée pour la session d'édition ; saisie manuelle proposée ; résolution en lot mise en file avec reprises par le Worker ; aucune perte de la valeur déjà saisie |
| Agrégation trop coûteuse (formule sur grande base) | plafond de lignes scannées atteint → résultat marqué `truncated` + message ; timeout serveur renvoyé en erreur de vue claire, jamais un 500 nu |
| Vue source d'un widget supprimée | widget en état « vue supprimée » en Lecture (cartouche neutre) ; en Édition, action « remplacer » ou « retirer » ; aucune cascade de suppression du Dashboard |
| Propriété référencée par un config supprimée | validation à la lecture : la référence tombe, la vue affiche un état « propriété manquante » réparable dans les réglages (pas d'écran blanc) — même politique pour axes de Chart, `place_property`, questions de Form (question orpheline signalée dans le builder, ignorée par la projection §16.5) |
| Base source d'un Dashboard supprimée / accès révoqué | §15.8 : cartouche « source non accessible », aucune donnée résiduelle servie (le cache d'agrégation est indexé par empreinte d'ACL — §8.5) |
| Conflit d'édition d'une disposition de Dashboard | PUT complet avec `If-Match` sur la date de modification de la vue ; conflit → l'éditeur voit la nouvelle disposition et choisit de la recharger (pas d'écrasement silencieux) |
| Double soumission de formulaire (re-clic, retour navigateur) | protections existantes du pipeline `/f/` (inchangées) ; le builder n'ajoute aucun état côté client dont dépendrait la soumission |
---
## 20. Déploiement et exploitation
- **Aucun changement d'infrastructure** : pas de nouveau service Docker, pas de nouvelle variable obligatoire ; les réglages de géocodage rejoignent `app/config.py` avec des valeurs par défaut sûres (`GEOCODER_PROVIDER=none`, plafonds §8.2 en constantes de config).
- **Feature flags par vue** (mécanisme de plugins existant, §4.2 de l'architecture) : `views-chart`, `views-timeline`, `views-feed`, `views-map`, `views-dashboard`, `views-form-builder` — permettent l'activation progressive par phase et le repli §7.6 sans redéploiement de code.
- **Métriques minimales** (compteurs dans l'audit / les journaux structurés existants) : temps d'agrégation par `view_type`, taux de `truncated`, hits/miss du cache de géocodage, échecs de fournisseur, nombre de widgets par Dashboard (pour valider les plafonds).
- **Sauvegardes** : les migrations 44–47 suivent les règles existantes (1 migration = 1 transaction) ; les nouvelles tables entrent dans la sauvegarde SQLite ordinaire ; le cache de géocodage est **exclu** des sauvegardes restaurées comme état (il se reconstruit — le marquer comme table régénérable dans `DATA_MODEL.md`).
---
## 21. Plan d'implémentation par phases
Ordre justifié par les dépendances : le registre et l'agrégation (phase 1) servent partout ; Timeline et Feed sont des promotions quasi pures (phase 2) ; `place` est indépendant mais introduit la seule dépendance externe (phase 3) ; le Dashboard n'a de sens que lorsque toutes les vues-widgets existent (phase 4) ; le Form builder touche la surface publique, il vient quand le reste est stable (phase 5). Estimations en **tickets** (1 ticket ≈ 0,5–2 jours), à recouper avec les conventions des documents de phases précédents si Bruno veut un découpage par phase séparé.
### Phase 1 — Socle de vues unifiées + Chart client (migration 44) — ~16 tickets
- Registre de vues `FDViews` + extraction du View Query Service (§7, §8.1) sans changement de comportement des 5 vues existantes.
- Menu `+` généré depuis le registre ; création de vues avec prérequis et configs par défaut (§7.3).
- Aggregation Service + endpoint + cache (§8.3, §8.5) ; validation serveur des `config_json` v2 (§10.4).
- Module Chart complet (§9) : 5 types, panneau de réglages, drilldown, export PNG ; coquille de compatibilité SSR (§7.6 étape 1) pour `chart`.
- **Acceptation** : CU-Chart (§2.4) passe ; les anciennes URL `?view_type=chart` affichent la vue cliente ; aucune régression sur Table/Board (suite de tests existante).
### Phase 2 — Timeline interactive + Feed (aucune migration) — ~14 tickets
- Module Timeline (§12) : échelles, fenêtre, glisser des barres et des bords, seau sans date, table latérale ; coquille SSR pour `timeline`.
- Module Feed (§14) : cartes, extraits de page-ombre, commentaires en ligne, réactions (avec le mini-mécanisme de §14.2 si nécessaire), compteur de vues.
- **Acceptation** : CU-Timeline et CU-Feed passent ; une date déplacée en Timeline se relit correctement en Table, Calendar et dans la page de la ligne.
### Phase 3 — Type `place` + Map (migration 45) — ~12 tickets
- Type `place` (validation, affichages Table/autres vues, conversion depuis `text`, import/export CSV).
- Geocoding Service + panneau d'administration + cache ; éditeur de cellule avec autocomplétion et saisie manuelle.
- Module Map (§13.5) : tracé par propriété, cadrage auto, plafond 100, clusters, popup.
- **Acceptation** : CU-Map passe avec fournisseur `none` (coordonnées manuelles) **et** avec un fournisseur configuré ; aucun appel externe quand le fournisseur est `none` (vérifié par test réseau).
### Phase 4 — Vue Dashboard (migration 46) — ~15 tickets
- Table `dashboard_widgets`, endpoints de disposition et de données par widget ; modes Lecture/Édition ; état vide fonctionnel et 3 gabarits (§15.4).
- Filtres globaux multi-sources avec exclusions (§15.5) ; chargement borné et états par widget.
- Action agent `set_dashboard_widgets` (§11.5) ; script de conversion des `collection_dashboards` (§15.7).
- **Acceptation** : CU-Dashboard passe, y compris le filtre global qui ignore le widget dépourvu de la propriété ; un utilisateur en lecture seule ne peut ni entrer en Édition ni apprendre le contenu d'une source inaccessible.
### Phase 5 — Form builder v2 (migration 47) — ~14 tickets
- Table `form_questions`, projecteur vers `form_config_json`, parité de projection testée (§16.5).
- Builder complet (§16.2–16.3) : synchronisation des libellés, requis, aides, types, `max_selections`, logique conditionnelle simple ; écran de confirmation ; Preview à jeton non-soumissible.
- Panneau de partage §16.4 : modes membres/web/fermé, anonymat et propriété répondant, accès du répondant à sa soumission ; interdiction globale workspace.
- **Acceptation** : CU-Form passe ; un formulaire public créé **avant** la phase 5 continue de fonctionner à l'identique (test de non-régression sur jetons existants).
### Phase 6 (optionnelle) — Durcissement et extensions
Export SVG des graphiques (§8.6) ; Gantt avancé dans Timeline (dépendances dessinées) ; widgets Feed dans les Dashboards ; heatmap Map ; conditions de formulaire en chaîne ; génération de Dashboard par skill d'agent packagée.
---
## 22. Décisions d'architecture (ADR — résumé)
| # | Décision | Alternative écartée | Motif |
|---|---|---|---|
| D1 | Promouvoir les rendus SSR en modules clients d'un **registre unique**, plutôt que réécrire six vues autonomes | Six chantiers séparés partageant `database_table.js` en dur | E1 est la cause racine ; le registre rend toute vue future triviale (principe 1) |
| D2 | Agrégation **côté serveur** avec contrat normalisé unique (Chart, KPI, widgets, API v2) | Agrégation cliente comme le groupement actuel de Table | Permissions avant calcul (§17.1), grosses bases, réutilisation par les widgets et l'API |
| D3 | Les configurations restent en `config_json` de `collection_views` ; tables nouvelles **uniquement** pour les objets référentiels (widgets, questions) | Tout mettre en tables / tout mettre en JSON | Suit le critère déjà en vigueur dans le modèle Flowdeck (§11.1, §10.3) |
| D4 | Dashboard = **vue** référençant des vues ; l'ancien Dashboard-page coexiste, conversion par outil | Remplacer `collection_dashboards` par migration | Aucune perte pour les pages existantes ; les deux objets répondent à des usages différents (page libre vs vue stable — distinction que Notion fait aussi, §3.2) |
| D5 | Géocodage **désactivé par défaut**, fournisseur branchable, cache obligatoire | Adresser un fournisseur par défaut codé en dur | Instance auto-hébergée : aucune donnée ne sort sans choix explicite de l'admin (§17.3) |
| D6 | Form builder projeté vers le `form_config_json` existant | Faire consommer `form_questions` directement par le pipeline public | Zéro régression sur les liens publics déjà diffusés (§16.5) |
| D7 | Migrations numérotées 44–47, **après** les 39–43 des Agents & Skills | Renuméroter depuis 39 | Le moteur n'applique que les versions supérieures à la courante (§10.1) |
| D8 | Timeline absorbe l'usage Gantt courant ; pas de vue Gantt séparée promue | Promouvoir Timeline **et** Gantt comme deux vues clientes | Même données, mêmes interactions à 90 % ; Notion lui-même n'a qu'une Timeline |
---
## 23. Risques et mitigations
| Risque | Impact | Mitigation |
|---|---|---|
| Régression sur les 5 vues clientes existantes lors de l'extraction du View Query Service | Élevé — Table/Board sont le cœur quotidien | Phase 1 commence par une extraction **sans changement de comportement**, couverte par la suite existante + tests de parité de réponses (mêmes lignes rendues avant/après) |
| `database_table.js` devient un goulot (registre + 11 modules) | Moyen | Modules par vue en fichiers séparés chargés paresseusement ; le fichier central ne garde que le registre et le cycle de vie |
| Coût SQLite des agrégations sur formules dans de très grandes bases | Moyen | Plafonds de scan, cache, marquage `truncated` ; index `json_extract` ciblés seulement si les métriques §20 le justifient (pas d'index spéculatif) |
| Fournisseur de géocodage gratuit saturé ou dégradé (couverture inégale selon les régions — limite que Notion signale aussi) | Moyen | Cache agressif, file de résolution en lot limitée en débit, saisie manuelle toujours possible, `place` utile même sans fournisseur |
| Fuite d'information par agrégats ou filtres globaux (§17.1–17.2) | Élevé | Filtrage ACL avant calcul, empreinte d'ACL dans les clés de cache, tests dédiés « utilisateur restreint » dans chaque phase |
| Le builder de Form casse un formulaire public en production | Élevé | Projection §16.5 (le public ne lit jamais les tables du builder), test de parité, feature flag séparé du pipeline public |
| Scope creep : vouloir la parité Notion complète dès la v1 (scatter, conditions en chaîne, widgets Feed…) | Moyen | §2.2 et phase 6 explicites ; chaque exclusion est nommée pour être un choix, pas un oubli |
---
## 24. Questions ouvertes pour Flowdeck
- **Q1 — Fournisseur de géocodage par défaut à documenter** : Nominatim public (limites d'usage strictes), instance Nominatim auto-hébergée, ou fournisseur commercial sous clé de l'admin ? Le code supporte les trois par l'interface §13.4 ; le choix du **défaut documenté** dans l'aide reste à trancher.
- **Q2 — Export SVG** : le générer côté serveur pour barres/lignes/donut (phase 6) ou s'en tenir au PNG client ? Dépend de l'usage réel (présentations imprimées ?).
- **Q3 — Réactions « de ligne »** : `text_reactions` suffit-il pour les cartes Feed, ou faut-il le mini-mécanisme polymorphe de §14.2 ? À trancher sur le code réel avant la phase 2 — c'est le seul point du plan qui pourrait ajouter une petite migration non numérotée ici.
- **Q4 — Lien Timeline ↔ calendrier externe** : le bouton *Manage in Calendar* de Notion a-t-il un équivalent souhaité via la sync calendrier (§18.2 de l'architecture), ou le renvoi interne à la vue Calendar (§12.6) suffit-il ?
- **Q5 — Logique conditionnelle des formulaires** : la v1 « une condition » suffit-elle aux usages de Bruno, ou les formulaires actuels utilisent-ils déjà des enchaînements que la projection v6.8 gérait autrement *(à vérifier dans les `form_config_json` existants)* ?
- **Q6 — Usage réel du Scatter SSR et des KPI isolés** : y a-t-il des `config_json` existants qui les utilisent ? Leur sort (§9.2 : compatibilité sans promotion) dépend de cette réponse.
- **Q7 — Dashboards personnels** : une vue Dashboard **personnelle** (`created_by` non NULL) a-t-elle un sens pour Bruno (son « cockpit du matin »), ou les Dashboards doivent-ils être toujours partagés ? Le modèle le permet sans surcoût ; c'est un choix produit.
---
## Annexe A — Correspondance Notion → Flowdeck, vue par vue
| Notion | Flowdeck cible | Écart résiduel assumé |
|---|---|---|
| Chart : 5 types, axes X/Y, donut « Données/Parts », styles, cumul | §9 : mêmes types et réglages, clés unifiées | Scatter non promu ; SVG en phase 6 ; pas de limite « 1 graphique gratuit » |
| Drilldown de Chart en tableau restreint, enregistrable comme vue | §8.4 : Table restreinte + « enregistrer comme vue » | — |
| Dashboard : widgets multi-sources, 4/ligne, 12 max, modes Lecture/Édition, filtres globaux | §15 : identique en modèle | Pas de génération par un agent **externe** ; génération par l'agent Flowdeck via §11.5 ; pas de paywall |
| Timeline : échelles heure→année, drag des bords, table latérale calculée, seau sans date | §12 : identique | Pas d'intégration à un calendrier tiers depuis la vue (§12.6) |
| Feed : cartes, commentaires, réactions, vues | §14 : identique en anatomie | Compteur de vues restreint (auteur + éditeurs) par choix de vie privée §17.4 |
| Map : propriété `place`, saisie nom/adresse/position, 100 épingles, *Map by* | §13 : identique + fournisseur configurable et mode sans fournisseur | Fournisseur par défaut non imposé (Q1) ; position courante jamais sans clic explicite |
| Form : builder, questions ↔ propriétés synchronisées, logique conditionnelle, écran de soumission, partage gradué, accès du répondant à sa réponse | §16 : identique en modèle | Conditions limitées à une règle en v1 (Q5) ; pas de copie courriel native au-delà des notifications existantes étendues au destinataire désigné |
| Sélecteur de création à 11 types + nouvelle source | §7.3 : grille générée par le registre | « Nouvelle source » = création de base liée existante (v4.1), inchangée |
## Annexe B — Schémas `config_json` de référence par vue
Chart : §9.3. Timeline : §12.2. Les trois autres, en synthèse :
```json
// feed
{"version": 2, "order": "last_edited_desc", "show_view_count": true,
"excerpt": {"enabled": true, "max_chars": 280, "cover_property": null},
"visible_properties": ["Status", "Due", "Assignee"]}
// map
{"version": 2, "place_property": "Place", "cluster": true,
"default_style": "dark", "visible_properties": ["Status", "Assignee"]}
// dashboard (le reste vit en table dashboard_widgets, mig. 46)
{"version": 2,
"global_filters": [
{"property": "Assignee", "operator": "is", "value": ["bruno"],
"excluded_widget_ids": []}],
"row_heights": {"0": 2, "1": 3}}
// form (les questions vivent en table form_questions, mig. 47)
{"version": 2, "title": "Demande", "description": "",
"sharing": {"audience": "workspace", "anonymous": false,
"respondent_access": "view", "branding": true},
"submit_screen": {"button_text": "Envoyer", "button_color": "blue",
"confirmation_title": "Merci !",
"confirmation_body": "Votre demande a été enregistrée."},
"notify_email": null}
```
## Annexe C — Glossaire
- **Vue cliente / vue promue** : vue rendue par un module du registre `FDViews` dans `DBInstance`, sauvegardée en `collection_views`, par opposition aux anciens rendus SSR.
- **Drilldown** : ouverture, depuis un élément de graphique, du tableau restreint des lignes qui le composent.
- **Widget** : emplacement d'un Dashboard affichant une vue source, existante ou propre au Dashboard.
- **Filtre global** : filtre de Dashboard appliqué, par nom de propriété, à tous les widgets dont la source possède cette propriété.
- **Place** : type de propriété géographique (nom, adresse, coordonnées, provenance de résolution).
- **Question** : présentation d'une propriété dans un formulaire ; en mode synchronisé, son libellé est le nom de la propriété.
- **Projection (de formulaire)** : régénération du `form_config_json` consommé par le pipeline public à partir des tables du builder.
- **Seau sans date** : ensemble des lignes dépourvues de la propriété de tracé d'une Timeline, affiché comme compteur accessible.
## Sources
Documentation publique de Notion (centre d'aide), consultée le 10 octobre 2026 — résumée avec mes mots en section 3 :
- *Dashboard view* — notion.com/help/dashboards
- *Chart view* — notion.com/help/charts
- *Timeline view* — notion.com/help/timelines
- *Feed view* — notion.com/help/feeds
- *Map view* — notion.com/help/maps
- *Build forms in Notion* — notion.com/help/forms
Matériaux de Bruno :
- 8 captures d'écran de sa base Notion « Todo » (10 octobre 2026) — `vues-notion-reference/01` à `08`, analysées en section 3A.
- `ARCHITECTURE.md` de Flowdeck v7.69.8 (fourni le 9 octobre 2026) — notamment §5 (modèle de données et migrations), §11 (databases), §12 (système de vues), §13 (pages et page-ombre), §15 (partage, sites et formulaires), §19 (automatisations), §22 (frontend).
- Documents compagnons : `architecture-meeting-notion-flowdeck.md` v1.2 ; `architecture-agents-skills-notion-flowdeck.md` v1.1 (réservation des migrations 39 à 43, §10.1 du présent document).
---
*Fin du document — version 1.0, 10 octobre 2026.*