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/.
94 KiB
Architecture & instructions d'implémentation — Gestion et interface des Templates (modèle Notion) pour Flowdeck
| Champ | Valeur |
|---|---|
| Version | 1.0 |
| Date | 10 octobre 2026 |
| Auteur | Spark, pour Bruno — à remettre à Hermes comme cahier des charges d'implémentation |
| Statut | Proposition d'architecture et plan d'implémentation — à valider contre le code réel de Flowdeck (voir le ticket T0-1 d'audit) |
| 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 templates de Notion d'après son centre d'aide officiel : templates de base de données, templates récurrents, templates d'équipe / galerie (liens en fin de document, consultés le 10 octobre 2026) |
| Référence visuelle | 1 capture de l'état actuel fournie par Bruno le 10 octobre 2026 : page vide « Untitled » de Flowdeck, rangée « Get started with », bouton-pilule Templates sous le curseur — conservée dans templates-notion-reference/capture-etat-actuel-bouton-templates.png |
| Documents compagnons | architecture-meeting-notion-flowdeck.md (v1.2), architecture-agents-skills-notion-flowdeck.md (v1.1), architecture-vues-notion-flowdeck.md (v1.0) — même méthode, même ancrage v7.69.8 |
Historique des versions
- 1.0 — 10 octobre 2026 : création. Point de départ double, formulé par Bruno : (1) il n'existe dans Flowdeck aucune gestion ni interface digne de ce nom pour les templates — le seul point d'entrée visible est la pilule « Templates » de la rangée Get started with d'une page vide ; (2) ce bouton ne fonctionne pas. Le document traite donc les deux : un diagnostic encadré du bouton cassé (phase 0, cause à établir par la preuve, pas par hypothèse) et la conception complète d'un système de templates unifié — registre, gestionnaire, sélecteur, éditeur de template, templates de base de données avec défaut et récurrence, variables dynamiques — ancré sur les trois tables de templates existantes de la v7.69.8. Migrations proposées 48 et 49, reséquencées après les 39 à 43 (Agents & Skills) et les 44 à 47 (Vues) déjà proposées par les documents compagnons (voir §9.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 :
- Une analyse fonctionnelle (section 3) de ce que font les templates dans Notion, établie à partir de sa documentation publique officielle consultée le 10 octobre 2026. C'est le comportement observable du produit, résumé avec mes mots.
- Un état des lieux Flowdeck (section 4) tiré du document d'architecture v7.69.8 fourni par Bruno et de sa capture du 10 octobre 2026 : ce qui existe déjà, ce qui existe partiellement, ce qui manque, et ce qui est constaté cassé. La cause du bouton inopérant n'y est pas affirmée : elle fait l'objet d'un protocole de diagnostic en phase 0 (§4.3 et §15), parce qu'aucun élément disponible ne permet de la trancher à distance.
- Une architecture cible originale (sections 5 à 20) pour porter Flowdeck à parité fonctionnelle avec Notion sur les templates. 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. Template est gardé en anglais, comme dans l'UI actuelle de Flowdeck (pilule « Templates ») et dans les noms de tables (
page_templates,database_templates). On distingue quatre genres de templates, que Notion sépare et que Flowdeck mélange aujourd'hui : template de page (contenu d'une page libre), template de ligne (propre à une base : propriétés pré-remplies + corps de la page de ligne), template de base (une base entière prête à créer : schéma, vues, lignes d'exemple), template de blocs (un groupe de blocs à insérer dans la page courante). Gestionnaire désigne l'écran de gestion des templates (§7.2) ; sélecteur (picker), le panneau de choix et d'application d'un template (§7.1) ; instanciation, l'opération qui crée un objet réel à partir d'un template.
0. Brief pour Hermes — à lire en premier
Mission. Construire dans Flowdeck la gestion et l'interface des templates, au niveau d'expérience de Notion, et réparer le bouton « Templates » de la page vide. L'ordre des phases du §15 fait foi : ne pas commencer par l'interface complète avant le diagnostic (phase 0) et le registre unifié (phase 1).
Ce qui est attendu, dans l'ordre :
- Diagnostiquer avant de réparer (phase 0). Reproduire le clic mort de la pilule « Templates » dans un test Playwright qui échoue, identifier la cause racine avec des preuves (console, réseau, code du handler), la consigner, puis seulement corriger. Aucune « réparation » par contournement (masquer la pilule, la remplacer par un lien mort ailleurs) n'est acceptée.
- Unifier avant d'ajouter (phase 1). Les trois tables existantes (
database_templates,page_templates,page_global_templates) restent la source des contenus historiques ; le nouveau registretemplates(§9) les catalogue toutes derrière un seul service,TemplateService(§8). Toute nouvelle surface (UI, API v2, agent, automatisations, récurrence) passe par ce service — jamais par un accès direct à une des trois tables. - Une seule implémentation de l'instanciation. L'outil agent existant
apply_template, l'UI humaine, l'API v2 et le scheduler de récurrence doivent tous appeler la même fonction d'instanciation transactionnelle (§8.3). C'est l'invariant déjà appliqué à l'API publique agent de Flowdeck (« une seule implémentation, jamais re-développée », §16.2 de l'architecture v7.69.8) ; l'étendre aux templates. - Respecter les invariants Flowdeck : SQLite + SQL brut sans ORM, migrations versionnées une par une et transactionnelles, Alpine build CSP (tout composant Alpine doit être enregistré via
Alpine.datadansbase.html— un gestionnaire inline non enregistré est une cause classique de clic silencieusement mort, voir H3 en §4.3), aucun bundler, icônes via le global Jinjafd_icon, cache-busting?v=VERSION, navigation partielle maison (fdNavigate) plutôt que des rechargements complets. - Chaque clic produit un effet visible. Critère d'acceptation transversal, né du bug actuel : tout point d'entrée « Templates » doit ouvrir le sélecteur, ou afficher un état vide explicite avec une action de création — jamais un no-op silencieux, y compris hors ligne, sans permission, ou avec un catalogue vide.
Définition de « terminé » (par phase) : tickets de la phase livrés, tests du §17 verts (dont le test de régression du clic), migrations appliquées sur une base v7.69.8 de test sans perte des templates existants, et parcours manuel de vérification du §17.4 effectué.
Hors mandat : ne pas refondre l'éditeur de blocs, ne pas toucher aux templates Jinja de app/templates/ (le mot « templates » y désigne les gabarits HTML du serveur — voir le piège de vocabulaire en §4.4), ne pas implémenter de marketplace publique.
Table des matières
- Résumé exécutif
- Périmètre, personas et cas d'usage
- Analyse fonctionnelle : les templates dans Notion
- État des lieux Flowdeck v7.69.8, capture de Bruno et analyse d'écart
- Principes directeurs
- Vue d'ensemble du système (C4)
- Interface : sélecteur, gestionnaire, menus de base et éditeur de template
- Back-end :
TemplateService, instanciation, variables dynamiques - Modèle de données et migrations 48 à 49
- API et événements
- Templates de base de données : défaut, récurrence, bases liées
- Galerie système et templates fournis
- Intégrations : agent, automatisations, réunions, recherche
- Sécurité, permissions et vie privée
- Plan d'implémentation par phases et tickets
- Exigences non fonctionnelles, résilience, déploiement
- Tests et critères d'acceptation
- Décisions d'architecture (ADR — résumé)
- Risques et mitigations
- Questions ouvertes pour Bruno
- Annexe A — Correspondance Notion → Flowdeck
- Annexe B — Format de manifeste
flowdeck-templatev1 - Annexe C — Glossaire
- Sources
1. Résumé exécutif
Dans Notion, un template n'est pas un fichier modèle caché dans un menu : c'est un objet de première classe, visible et gérable. Une base possède ses propres templates, créés et édités comme des pages, listés dans le menu du bouton New, qu'on peut dupliquer, définir comme défaut, ou rendre récurrents. Une page vide propose des templates au démarrage, la barre latérale a une entrée Templates qui ouvre une galerie, et une page publiée peut être dupliquée comme template par d'autres. Le système est unifié : créer, gérer, appliquer et automatiser sont quatre vues du même objet.
Le constat Flowdeck est presque inverse. D'après l'architecture v7.69.8 (§10.2, §5.3), les données de templates existent — trois tables, un seed de bases prêtes, des lignes récurrentes, un outil agent apply_template, un module d'export/import templates_io dans l'API v2 — mais il n'y a aucune surface de gestion : pas d'écran qui liste les templates, pas d'endroit où les créer, les renommer, les dupliquer, choisir un défaut ou régler une récurrence autrement que par le code ou la base. Et le seul point d'entrée visible par l'utilisateur, la pilule « Templates » de la rangée Get started with d'une page vide (capture de Bruno, §4.2), ne fonctionne pas — Bruno le constate, la capture le montre figé au milieu d'entrées d'une autre nature (Ask AI, AI meeting notes, Form…).
Les sept écarts structurants (détaillés en section 4) :
| # | Écart | Nature du travail |
|---|---|---|
| E1 | Point d'entrée unique et cassé : la pilule Templates de la page vide est le seul accès visible, et son clic est sans effet | Diagnostic prouvé puis réparation (phase 0), puis multiplication des points d'entrée légitimes (sélecteur depuis page vide, menu New des bases, menu de page, gestionnaire) |
| E2 | Trois silos de données sans catalogue commun : database_templates, page_templates, page_global_templates (+ presets de réunion et canevas Design System à la marge), aucun registre, aucune recherche transverse |
Registre unifié templates (migration 48) qui catalogue les trois silos sans les détruire, backfill idempotent (§9) |
| E3 | Aucune interface de gestion : pas de liste, pas de CRUD, pas de portée (perso / workspace / teamspace), pas d'archivage, pas de compteur d'usage | Gestionnaire de templates (§7.2) : page dédiée /templates + onglet Templates dans la Library, sur les patrons UI existants |
| E4 | Aucun éditeur de template : le contenu d'un template ne s'édite pas « comme une page » ; les propriétés par défaut d'un template de ligne ne se règlent pas dans l'UI de la base | Édition par page-support (§7.4, §9.3) : le contenu du template vit dans une page Flowdeck ordinaire mais masquée, éditée avec l'éditeur existant, bandeau de mode template |
| E5 | Instanciation fragmentée : l'outil agent apply_template existe, le seed crée des bases, les lignes récurrentes ont leur mécanique — trois chemins qui ne partagent ni validation, ni journal, ni gestion d'erreur |
TemplateService.instantiate() unique (§8.3), transactionnel, idempotent, journalisé dans template_runs, consommé par l'UI, l'API v2, l'agent et le scheduler |
| E6 | Valeurs dynamiques non systématisées : Notion résout @today, @now, @me dans les titres et propriétés des templates ; Flowdeck a les briques (tokens de date [[fddate:…]], fonctions now/today du moteur de formules) mais pas de contrat de variables pour les templates |
Contrat de variables (§8.4) : variables système résolues à l'instanciation + variables déclarées demandées à l'utilisateur dans le sélecteur, validation par validate_property_value() existant |
| E7 | Défaut et récurrence invisibles : un template de ligne peut être récurrent « éventuellement » (v7.69.8) mais rien ne permet de le voir, le régler ou l'arrêter ; aucun concept de template par défaut d'une base exposé dans le menu New | Défaut + récurrence de première classe (§11) : menu New ▾ complet, éditeur RRULE réutilisant recurrence.py, scheduler et déduplication sur le patron des rappels |
Recommandation principale. Ne pas « réparer un bouton » : construire le système dont ce bouton n'est que la porte. D'abord le diagnostic (la cause du clic mort conditionne la confiance dans tout le reste de l'UI), puis le registre et le service d'instanciation, puis seulement les surfaces — sélecteur, gestionnaire, éditeur — dans cet ordre. Le plan en 5 phases (§15) rend le système utilisable dès la phase 1 (la pilule réparée ouvre un sélecteur minimal qui applique réellement un template) et complet en phase 4.
2. Périmètre, personas et cas d'usage
2.1 Dans le périmètre
Les quatre genres de templates, unifiés (§5, principe P1) :
- Template de page : titre, icône, couverture et blocs d'une page libre ; proposé sur page vide, depuis le menu New page, et applicable à une page existante (ajout ou remplacement de contenu, avec confirmation).
- Template de ligne : propre à une collection ; propriétés par défaut + corps de la page-ombre de la ligne ; géré depuis la base (menu New ▾) et depuis le gestionnaire ; peut être par défaut et/ou récurrent.
- Template de base : crée une collection complète (schéma, propriétés, vues, dashboards simples, lignes d'exemple optionnelles) ; proposé à la création d'une base et dans la galerie.
- Template de blocs : un ensemble de blocs inséré à la position du curseur dans la page courante (successeur unifié des
page_global_templateset, à terme, des canevas Design System — §13.4).
La gestion : créer (depuis zéro, depuis une page/ligne/base existante, par duplication), renommer, éditer le contenu et les réglages, changer la portée, définir/retirer le défaut, régler/arrêter la récurrence, dupliquer, archiver/restaurer, supprimer, exporter/importer, voir l'usage (nombre d'instanciations, dernière utilisation).
L'application : sélecteur avec recherche, aperçu et saisie des variables ; instanciation atomique avec retour visuel (navigation vers l'objet créé, ou insertion des blocs) ; journal des instanciations ; échecs explicites et récupérables.
Les intégrations existantes : outil agent apply_template rebranché sur le service ; action d'automatisation « créer depuis un template » ; presets de réunion enregistrés comme templates ; recherche Ctrl+K.
2.2 Hors périmètre
- Marketplace publique, templates payants, partage hors instance (le « dupliquer comme template » d'une page publiée sur la même instance est en phase 5 optionnelle, §15).
- Refonte de l'éditeur de blocs ou du moteur de vues (les documents compagnons couvrent les vues ; celui-ci consomme l'existant).
- Templates d'e-mails, de sites publiés ou de formulaires publics (objets distincts, déjà couverts par
sites/form_config_json). - Migration des gabarits HTML Jinja (
app/templates/) — voir §4.4.
2.3 Personas et cas d'usage directeurs
| Persona | Cas d'usage directeur |
|---|---|
| Bruno, seul sur son instance | « Je crée une page vide, je clique Templates, je choisis Revue hebdo, la page se remplit. La semaine suivante, la ligne Revue hebdo apparaît toute seule dans ma base de tâches, datée du jour. » |
| Bruno, gestion de projets dans Flowdeck | « Ma base Projets a un template de ligne Spécification : propriétés pré-remplies (statut, priorité) et corps structuré. New l'applique par défaut ; New ▾ me laisse choisir Bug ou Spécification. » |
| Membre d'un teamspace | « Les templates du teamspace sont visibles par les membres, invisibles dehors (404 comme les pages privées), et seuls les éditeurs de la base peuvent modifier son template par défaut. » |
| L'agent Flowdeck | « Quand on me demande de créer une note de réunion, j'utilise apply_template — le même service que l'UI — et l'action apparaît dans le journal, annulable comme mes autres actions. » |
3. Analyse fonctionnelle : les templates dans Notion
Synthèse, avec mes mots, de la documentation publique de Notion consultée le 10 octobre 2026 (liens en fin de document). Seuls les comportements qui fondent une décision de conception Flowdeck sont retenus.
3.1 Templates de base de données — le cœur du système
- Un template de base est propre à une base : il n'existe que pour elle et s'applique à ses nouvelles lignes. Chaque base peut en avoir plusieurs (par type de réunion, par type de tâche…).
- Il combine deux choses : des valeurs de propriétés pré-remplies et un corps de page structuré (titres, sections, checklists, blocs liés…) qui devient le contenu de la page de la ligne créée.
- Création et gestion se font depuis le menu déroulant accolé au bouton New de la base : + New template pour créer ; chaque template listé a un menu ••• avec Edit, Duplicate, Set as default, Repeat…, Delete.
- Éditer un template ouvre son contenu comme une page, dans l'éditeur ordinaire ; les modifications valent pour les créations futures, jamais rétroactivement pour les lignes existantes.
- Le template par défaut s'applique quand on clique New sans ouvrir le menu. Sans défaut, New crée une ligne vide.
- Un template peut être appliqué après coup à une page de base existante créée sans template.
- Les titres et propriétés acceptent des mentions dynamiques — date du jour, instant courant, utilisateur courant — résolues au moment de la création de la ligne, pas à la définition du template.
3.2 Templates récurrents
- Tout template de base peut être rendu récurrent depuis son menu (Repeat) : quotidien, hebdomadaire, mensuel, annuel, ou fréquence personnalisée (intervalle, jours de semaine, fin par date ou nombre d'occurrences).
- À l'échéance, une nouvelle ligne est créée depuis le template, avec les valeurs dynamiques résolues à cette date. On arrête la récurrence en la désactivant dans le même menu ou en supprimant le template ; les lignes déjà créées ne sont jamais touchées.
3.3 Templates de page et galerie
- À la création d'une page, Notion propose des templates de démarrage (page vide, ou partir d'un template de la galerie) ; une entrée Templates dans la barre latérale ouvre la galerie de l'espace de travail, prolongée par la galerie communautaire.
- Une page publiée peut autoriser sa duplication comme template : les visiteurs obtiennent un bouton Duplicate qui copie la page (ou le système de pages/bases) dans leur espace.
3.4 Boutons et insertion de blocs
- Notion a fondu l'ancien « template button » dans ses boutons génériques : un clic peut insérer des blocs, créer des pages ou modifier des entrées. Pour Flowdeck, l'équivalent fonctionnel existe déjà sous deux formes — le bloc
buttonde l'éditeur (déclenche une automatisation) et la propriétébuttondes bases — ce qui classe ce besoin en intégration (§13.2), pas en nouveau mécanisme de templates.
3.5 Ce que Notion ne fait pas (et que ce document ne demande pas)
- Pas de variables nommées à saisir à l'application (seules les mentions dynamiques existent) : les variables déclarées du §8.4 sont donc un choix Flowdeck délibéré, marqué comme tel — elles répondent au besoin de Bruno de paramétrer (projet, personne, échéance) sans multiplier les templates quasi identiques.
- Pas d'aperçu « diff » avant application : le sélecteur Flowdeck propose une prévisualisation du contenu résolu (§7.1), sans prétendre que Notion la fournit.
4. État des lieux Flowdeck v7.69.8, capture de Bruno et analyse d'écart
4.1 Ce qui existe déjà (d'après l'architecture v7.69.8)
| Brique | État décrit en v7.69.8 | Référence |
|---|---|---|
| Templates de bases prêtes | database_templates — bases prêtes à créer, alimentées par un seed db_templates.py |
§10.2 |
| Templates de lignes | page_templates — lignes de base, « éventuellement récurrentes » |
§10.2, §5.3 |
| Templates de blocs | page_global_templates — ensembles de blocs réutilisables |
§10.2, §5.3 |
| Route legacy | page-templates dans le package board/ |
§4 |
| Export / import | Module templates_io dans l'API publique v2 (app/routers/api_v2/) |
§3, §4 |
| Outil agent | apply_template parmi les 26 outils statiques du ToolRegistry ; chaque mutateur renvoie un snapshot d'undo |
§16.3 |
| Presets de réunion | Instructions de résumé prédéfinies (auto, standup, team, sales, 1:1, interview) et modèles de notes par type de réunion prévus par le document Meetings | §18.1, doc. compagnon |
| Canevas Design System | block_templates.py, insertion de blocs depuis le menu + de l'assistant (phase 3 du hub, v7.53) |
§16.4 |
| Briques de récurrence | recurrence.py (sous-ensemble RRULE : daily/weekly/monthly, interval, COUNT, UNTIL, BYDAY, fuseaux) ; rappels par scan périodique 60 s dédupliqué par journal |
§19.3 |
| Briques de valeurs dynamiques | Tokens de mentions de date [[fddate:YYYY-MM-DD[Thh:mm]]] résolus au rendu ; fonctions now / today du moteur de formules |
§14.1, §11.3 |
| Validation des propriétés | validate_property_value() par type + contraintes validation_json |
§11.2 |
| Patrons d'interface réutilisables | Library à onglets avec colonnes configurables et side peek ; galerie de skills à presets installables (17 presets, format portable) ; menus contextuels de page façon Notion | §15.2, §16.3, §13.3 |
Conclusion de l'inventaire : le contenu et les moteurs existent ; le catalogue, la gestion et les surfaces manquent. C'est un travail d'unification et d'exposition, pas de création ex nihilo — exactement comme pour les vues (document compagnon).
4.2 Lecture de la capture de Bruno (10 octobre 2026)
Ce que la capture montre, élément par élément — c'est l'état observable du point d'entrée actuel :
- Une page libre « Untitled » ouverte dans l'éditeur (fil d'Ariane Home / Untitled), vide : le pied indique « 1 blocks » et « Saved offline ».
- Au centre-bas de la page, la rangée « Get started with » : pilules Ask AI, AI meeting notes, Form, un bouton « ••• », et la pilule Templates (icône de page + libellé), sur laquelle le curseur en forme de main est positionné — Bruno est donc en train de cliquer dessus, ou vient de le faire.
- Un menu déroulant est ouvert au-dessus, listant Table, Board, List, Timeline, Calendar, Gallery, Import : c'est le menu de création de base (les types de vues + l'import), pas un menu de templates. Deux lectures possibles, à trancher par le diagnostic (§4.3) : soit ce menu appartient à une autre pilule de la rangée et il masque/perturbe la zone de clic de Templates, soit le clic sur Templates ouvre… ce menu-là, c'est-à-dire le mauvais.
- La barre latérale (panneau Home) ne contient aucune entrée Templates : sections test, Manage Workspaces, Meetings, Recents, Favorites, Agents, Teamspaces, Shared, Published, Library, My Tasks, Trash, Help. Dans Notion, l'entrée Templates de la barre latérale est un accès de premier niveau à la galerie ; ici, la Library est le seul endroit où un onglet Templates pourrait exister — il n'y en a pas (§15.2 : Recents, AI Meeting Notes, Favorites, Shared, Private, Published, Workspace, Repository).
- Aucun état « catalogue vide », aucun panneau, aucune notification visible : si des templates existent en base, rien ne les rend atteignables depuis cette page.
Constat de Bruno, pris comme fait : le clic sur « Templates » ne fonctionne pas. La capture corrobore au minimum ceci : même dans l'hypothèse où le clic déclencherait quelque chose, l'écran n'offre ni retour visible, ni liste, ni état vide — ce qui viole le critère transversal du brief (§0, point 5).
4.3 Diagnostic du bouton cassé — hypothèses à trancher par la preuve
Aucune cause n'est affirmée ici. Le tableau donne à Hermes les hypothèses ordonnées par probabilité compte tenu des invariants connus de Flowdeck, la vérification qui tranche chacune, et la correction attendue si elle se confirme. Le ticket T0-2 (§15) impose de toutes les passer, dans l'ordre, et de consigner le résultat.
| # | Hypothèse | Pourquoi elle est plausible chez Flowdeck | Vérification qui tranche | Correction si confirmée |
|---|---|---|---|---|
| H1 | Aucun handler effectif : la pilule est rendue (fragment de la rangée Get started with) mais son gestionnaire JS est absent, renommé ou jamais lié | La rangée mélange des actions de natures différentes (IA, réunion, formulaire, base) ; une pilule ajoutée « pour plus tard » sans câblage est le scénario le plus simple | Chercher la chaîne Templates / l'id de la pilule dans static/js/page_editor_scripts.js et les fragments _page_editor_*.html ; poser un point d'arrêt console sur le clic |
Câbler le handler sur le sélecteur (§7.1) — pas sur un comportement ad hoc |
| H2 | Composant Alpine non enregistré : le clic passe par Alpine, mais le composant n'est pas déclaré via Alpine.data dans base.html |
Invariant documenté de la build CSP : les enregistrements Alpine.data de base.html sont obligatoires ; un composant inline échoue silencieusement (pas d'erreur visible) |
Console : avertissements Alpine ; comparer avec un composant voisin fonctionnel (Ask AI) ; vérifier la présence de l'enregistrement | Enregistrer le composant ; ajouter le test de régression T0-3 |
| H3 | Endpoint absent ou en erreur : le handler appelle une route templates inexistante (404) ou en échec (500), et l'erreur est avalée sans retour UI | Il n'existe dans l'architecture aucune route de gestion des templates côté UI (seuls page-templates legacy sous /board et templates_io en API v2) |
Onglet réseau au clic : URL appelée, statut ; logs serveur corrélés | Créer la route du §10 ; et rendre toute erreur visible (toast + état d'erreur du sélecteur) |
| H4 | Interception par le menu de création de base : le menu Table…Import visible sur la capture recouvre la zone ou capte l'événement ; le clic atteint le mauvais élément | Le menu est ouvert exactement au-dessus de la pilule sur la capture ; les menus contextuels globaux (_ctx_menu) ont leur propre gestion de z-index et de fermeture |
Inspecter l'élément au point de clic (document.elementFromPoint) avec le menu ouvert/fermé ; tester le clic menu fermé |
Corriger la pile des menus (fermeture au clic extérieur, pointer-events) ; le sélecteur de templates doit être un panneau distinct, pas un menu partageant cette pile |
| H5 | JS périmé servi par le service worker : l'état « Saved offline » et les caches PWA peuvent servir une version de page_editor_scripts.js sans le handler |
PWA : statique en cache-first, bump de cache à chaque release ; un décalage version HTML/JS produit des comportements « bouton mort » typiques | Comparer la version servie et la version au dépôt ; tester en navigation privée / cache désactivé | Bump de cache ; vérifier la cohérence ?v=VERSION du fragment et du JS |
| H6 | Abandon silencieux faute de contexte : le handler exige un workspace/une collection que la page libre « Untitled » n'a pas, et sort sans rien afficher | La page est une page libre (pas une ligne de base) ; un handler pensé pour les templates de lignes n'aurait rien à appliquer ici | Lire le code du handler : conditions de sortie ; journaliser temporairement les sorties précoces | Le sélecteur doit s'ouvrir quel que soit le contexte ; le contexte filtre les genres proposés (page, blocs), il ne bloque jamais l'ouverture |
Règle de sortie de diagnostic : la phase 0 ne se termine que lorsque (a) le test Playwright de reproduction passe de rouge à vert pour la bonne raison (la cause consignée), et (b) le clic produit l'ouverture du sélecteur minimal de la phase 1 — ou, si la phase 1 n'est pas encore livrée, d'un panneau d'état explicite « catalogue » (même vide). Un bouton qui « ne fait plus d'erreur en console » mais n'ouvre rien n'est pas réparé.
4.4 Piège de vocabulaire à éviter pendant l'implémentation
app/templates/ contient les gabarits HTML Jinja de Flowdeck (~35 fichiers) ; les routes SSR les rendent. Le présent document parle des templates métier (objets réutilisables par l'utilisateur). Toute nouvelle surface doit éviter la confusion : fichiers front nommés templates_manager.html, _template_picker.html, JS templates_manager.js / template_picker.js, routes /templates et /api/templates… — jamais template.html seul, jamais de variable Python nommée template sans suffixe métier (fd_template, tpl) dans les modules partagés.
4.5 Les sept écarts, en détail
- E1 — Point d'entrée unique et cassé. Voir §4.2 et §4.3. Au-delà du bug : une fonctionnalité dont l'unique porte est une pilule contextuelle de page vide est indécouvrable (rien dans la sidebar, rien dans la Library, rien dans les bases). Notion multiplie les portes cohérentes : sidebar, page vide, menu New des bases.
- E2 — Trois silos sans catalogue. Impossible aujourd'hui de répondre à « quels templates ai-je ? » sans interroger trois tables aux sémantiques différentes, ni de chercher un template par son nom tous genres confondus. Le registre (§9) est la réponse ; la recherche du sélecteur et de Ctrl+K s'appuie dessus.
- E3 — Aucune gestion. Créer un template de ligne récurrent, aujourd'hui, suppose le seed ou la base de données. Il manque : liste, création guidée, renommage, duplication, portée, archivage, suppression, compteurs d'usage — tout ce que la galerie de skills possède déjà pour les skills (§16.3 de l'architecture), ce qui donne le patron à suivre.
- E4 — Aucun éditeur de template. Dans Notion, éditer un template = éditer une page. Flowdeck a un excellent éditeur de pages ; il faut un moyen d'y brancher le contenu d'un template sans créer une vraie page visible dans l'arbre, la recherche et les récents — d'où la page-support masquée (§9.3), qui réutilise aussi versions, temps réel et blocs synchronisés.
- E5 — Instanciation fragmentée. Trois chemins (seed, outil agent, récurrence des lignes) = trois comportements d'erreur et aucune journalisation commune. Le service unique (§8) factorise validation des propriétés, résolution des variables, création transactionnelle et journal
template_runs. - E6 — Variables non systématisées. Les mentions dynamiques existent comme tokens de rendu, les formules savent
now()/today(), mais rien ne définit ce qui est résolu à l'instanciation (et figé dans l'objet créé) vs au rendu (et vivant). Le contrat du §8.4 tranche : un template fige ses valeurs à la création de l'objet — sinon une note de réunion « du 3 octobre » changerait de date en la rouvrant. - E7 — Défaut et récurrence invisibles. Le menu New d'une base Flowdeck ne montre ni le défaut ni les templates disponibles ; la récurrence des
page_templatesn'a ni écran de réglage, ni prochain passage visible, ni bouton d'arrêt. §11 spécifie les trois surfaces manquantes.
5. Principes directeurs
- P1 — Un objet, quatre genres, un registre. Tout template, quel que soit son genre, est une ligne du registre
templatesavec unkind. Les surfaces ne connaissent que le registre ; les différences de genre vivent dans le manifeste (§9.2) et dans le service. - P2 — Le contenu reste dans les modèles existants. Le corps d'un template de page/ligne est un contenu de page Flowdeck (blocs JSON / markdown legacy,
content_format) porté par une page-support ; le schéma d'un template de base est unschema_jsonde collection ; les valeurs de propriétés sont unproperty_values_json. Aucun nouveau format de contenu. - P3 — Figer à l'instanciation. Variables, dates dynamiques et valeurs par défaut sont résolues au moment de créer l'objet, dans la transaction d'instanciation. L'objet créé est ensuite un objet ordinaire, indépendant du template (le modifier ou supprimer le template ne l'affecte jamais).
- P4 — Une instanciation, quatre appelants. UI, API v2, agent, automatisations/récurrence : même fonction, même validation, même journal, mêmes erreurs (§8.3).
- P5 — Aucun clic sans effet visible. Ouverture, état vide, erreur explicite ou progression : jamais de no-op (§0 point 5, §4.3 règle de sortie).
- P6 — Les permissions du template et de la cible se composent. Lire le template + écrire la cible ; un template ne donne jamais accès à un contenu que son lecteur ne pourrait pas voir, et sa page-support n'apparaît dans aucune surface de navigation (§14).
- P7 — Réutiliser les patrons d'UI existants. Gestionnaire = patron Library (onglets, colonnes, side peek) ; galerie système = patron galerie de skills ; éditeur RRULE = patron des récurrences de propriétés ; sélecteur = patron des menus contextuels de l'éditeur. Aucun nouveau langage visuel.
- P8 — L'historique ne casse pas. Backfill idempotent des trois tables legacy vers le registre ; les objets créés avant la refonte restent valides ; les routes legacy
page-templatesettemplates_iocontinuent de fonctionner (adaptateurs sur le service).
6. Vue d'ensemble du système (C4)
graph TD
subgraph Surfaces["Surfaces (SSR + JS existants étendus)"]
PICKER["Sélecteur de templates<br/>(page vide, menu de page, New ▾ de base)"]
MANAGER["Gestionnaire /templates<br/>+ onglet Library"]
TEDITOR["Éditeur de template<br/>(éditeur de page + bandeau)"]
DBMENU["Menu New ▾ d'une base<br/>(DBInstance)"]
end
subgraph Core["Noyau (nouveau)"]
REG["Registre templates<br/>(migration 48)"]
SVC["TemplateService<br/>preview / instantiate / duplicate<br/>create_from_* / resolve_variables"]
RUNS["template_runs<br/>(journal + dédup récurrence)"]
end
subgraph Existing["Existant Flowdeck réutilisé"]
HOLDER["Pages-support masquées<br/>(pages, search_excluded)"]
LEGACY["database_templates<br/>page_templates<br/>page_global_templates"]
VAL["validate_property_value()"]
REC["recurrence.py + scheduler 60 s"]
AGENT["Outil agent apply_template"]
AUTO["Automatisations (action create_from_template)"]
end
PICKER --> SVC
MANAGER --> REG
TEDITOR --> HOLDER
DBMENU --> PICKER
SVC --> REG
SVC --> HOLDER
SVC --> LEGACY
SVC --> VAL
SVC --> RUNS
REC --> SVC
AGENT --> SVC
AUTO --> SVC
Flux directeur (application depuis une page vide) : clic Templates → le sélecteur interroge le registre (portées visibles, genre page) → l'utilisateur choisit, l'aperçu résout les variables système et demande les variables déclarées → POST /api/templates/{id}/instantiate → TemplateService crée/met à jour la page dans une transaction, journalise dans template_runs, renvoie l'URL → le front navigue (ou reste, pour les blocs) avec un toast « Créé depuis le template X — Annuler » (l'annulation supprime l'objet créé, sur le patron des snapshots d'undo de l'agent).
7. Interface : sélecteur, gestionnaire, menus de base et éditeur de template
Quatre surfaces, toutes sur les patrons existants (P7). Les maquettes ASCII donnent la disposition attendue ; le style suit le Design System et les composants actuels (pilules de la rangée Get started with, menus contextuels de l'éditeur, tableaux de la Library).
7.1 Le sélecteur de templates (picker)
Déclencheurs (tous ouvrent le même composant, filtré par le contexte) :
| Contexte | Déclencheur | Filtre initial |
|---|---|---|
| Page vide | Pilule Templates de la rangée Get started with (le bouton à réparer) | kind=page + kind=blocks |
| Page quelconque | Menu ••• de la page → Appliquer un template… ; et slash command /template dans l'éditeur |
kind=page (mode ajout/remplacement) + kind=blocks (insertion au curseur) |
| Base (pleine page ou inline) | Bouton New ▾ (§7.3) → nom d'un template, ou Tous les templates… | kind=row, target_collection_id = la base |
| Création de base | Menu de création de base → Partir d'un template | kind=database |
| Palette Ctrl+K | Action « Appliquer un template… » | selon la page courante |
Disposition (panneau modal centré, ou side peek sur écran étroit ; recherche autofocus, navigation clavier ↑/↓/Entrée/Échap) :
┌─ Appliquer un template ──────────────────────────────────────── ✕ ─┐
│ 🔍 Rechercher un template… │
│ [ Tous ] [ Pages ] [ Lignes ] [ Bases ] [ Blocs ] ☐ Portée: ▾ │
├──────────────────────────────┬─────────────────────────────────────┤
│ RÉCENTS │ 📄 Revue hebdo │
│ 📄 Revue hebdo ★ │ Template de page · Workspace │
│ 🐞 Rapport de bug │ ───────────────────────────────── │
│ FAVORIS │ APERÇU (valeurs résolues) │
│ 📄 Note de réunion 1:1 ★ │ # Revue du {{date}} │
│ MON WORKSPACE │ ## Fait / En cours / Blocages │
│ 📄 Spécification produit │ ☐ Actions de la semaine │
│ 🗂 Base Projets (base) │ ───────────────────────────────── │
│ SYSTÈME │ VARIABLES │
│ 📄 Journal quotidien │ Semaine : [ 2026-W42 ] │
│ 📄 Compte rendu (système) │ Projet : [ Flowdeck ▾ ] │
│ │ │
│ + Nouveau template │ [ Annuler ] [ Utiliser ] │
├──────────────────────────────┴─────────────────────────────────────┤
│ Entrée : appliquer · ★ : favori · ••• : éditer / dupliquer / … │
└────────────────────────────────────────────────────────────────────┘
Comportements obligatoires :
- Aperçu résolu à droite : le contenu du template avec les variables système déjà résolues pour maintenant (dates, utilisateur) et les variables déclarées éditables inline ; les propriétés par défaut d'un template de ligne s'affichent en chips sous l'aperçu.
- État vide par section : si aucun template n'existe pour le genre filtré, la section Système reste proposée (galerie §12) et un bouton + Créer un template est visible. Le panneau s'ouvre toujours (P5).
- État d'erreur : si l'API échoue, le panneau affiche l'erreur et un bouton Réessayer — c'est l'antidote direct de H3.
- Actions secondaires par ligne (menu •••, selon permissions) : Éditer, Dupliquer, Renommer, Définir comme défaut (lignes), Répéter… (lignes), Archiver, Exporter.
- Après application : toast avec Annuler (10 s) ; pour un template de page appliqué à la page courante vide, pas de navigation (la page se remplit en place, temps réel synchronisé) ; pour une création (ligne, base, nouvelle page), navigation vers l'objet créé via
fdNavigate. - Mobile (≤768 px) : le sélecteur devient la feuille plein écran « Insert block » déjà utilisée par l'éditeur, aperçu en second onglet.
7.2 Le gestionnaire de templates
Accès : nouvelle page /templates (SSR, templates_manager.html + templates_manager.js), atteinte par (a) une entrée Templates dans la sidebar, panneau Home, sous Library ; (b) un onglet Templates dans la Library (même données, patron Library : colonnes configurables par Show columns, sélection multiple, side peek d'aperçu) ; (c) le lien Gérer les templates… du sélecteur et du menu New ▾ des bases.
┌─ Templates ───────────────────────────────────── [ + Nouveau ▾ ] ─┐
│ 🔍 Rechercher… Genre: [Tous ▾] Portée: [Toutes ▾] │
│ Base: [Toutes ▾] ☐ Archivés Tri: [Dernière utilisation ▾]│
├────────────────────────────────────────────────────────────────────┤
│ Icône Nom Genre Portée Base Défaut/Répète │
│ 📄 Revue hebdo Page Workspace — — │
│ 🐞 Rapport de bug Ligne Workspace Support ★ Défaut │
│ 📅 Tâche hebdo Ligne Workspace Tâches ↻ Lun. 9 h │
│ 🗂 Projets (starter) Base Système — — │
│ ▦ Sommaire + actions Blocs Personnel — — │
├────────────────────────────────────────────────────────────────────┤
│ Ligne sélectionnée → aperçu en side peek : contenu, propriétés │
│ par défaut, variables, usage (42 utilisations, dernière le …), │
│ [ Utiliser ] [ Éditer ] [ Dupliquer ] [ Archiver ] [ Exporter ] │
└────────────────────────────────────────────────────────────────────┘
Menu + Nouveau : Template de page vierge, Template de ligne pour une base… (choix de la base), Template de base vierge, Template de blocs (depuis la sélection), Depuis une page existante…, Depuis une ligne existante…, Importer un template (.json).
Règles : les templates système (§12) sont listés avec un badge, non éditables directement (Dupliquer pour modifier) ; l'archivage retire le template des sélecteurs sans le supprimer (restauration depuis le filtre Archivés) ; la suppression est un soft-delete aligné sur la corbeille unifiée (deleted_at, §15.4 de l'architecture) ; les compteurs d'usage viennent de template_runs (§9.4), jamais d'un compteur dénormalisé à maintenir à la main — une vue SQL d'agrégation suffit.
7.3 Le menu New ▾ d'une base
Dans DBInstance (base pleine page, inline et bloc database) :
[ New ] [ ▾ ]
┌──────────────────────────────┐
│ + Nouvelle ligne vide │
│ ──────────────────────────── │
│ 🐞 Rapport de bug ★ │ ★ = défaut (appliqué par [New])
│ 📄 Spécification │
│ 📅 Tâche hebdo ↻ Lun. │
│ ──────────────────────────── │
│ + Nouveau template… │
│ Gérer les templates… │
└──────────────────────────────┘
Sur chaque template, au survol : [ ••• ] → Éditer / Dupliquer /
Définir comme défaut / Répéter… / Supprimer
- Le bouton principal New applique le défaut s'il existe, sinon crée une ligne vide (comportement actuel préservé).
- Le même menu, ouvert depuis une base liée (
collection_data_sources), liste les templates de la base source (§11.3). - Dans la vue Board/Calendar/Gallery/List, le New d'une colonne/d'un jour pré-remplit en plus la propriété de regroupement ou de date — qui prime sur la valeur par défaut du template en cas de conflit, et le sélecteur l'affiche (« Date : imposée par la vue »).
7.4 L'éditeur de template
Éditer le contenu d'un template de page ou de ligne ouvre la page-support (§9.3) dans l'éditeur ordinaire, avec trois différences, et trois seulement :
┌────────────────────────────────────────────────────────────────────┐
│ 🧩 Vous modifiez le template « Rapport de bug » — les changements │
│ s'appliquent aux prochaines créations. [ Terminé ] │
├────────────────────────────────────────────────────────────────────┤
│ 🐞 Rapport de {{titre}} │
│ ## Reproduction … (blocs éditables, éditeur inchangé) │
│ │
│ ── Panneau latéral « Réglages du template » ── │
│ Nom / Icône / Description / Portée : Workspace │
│ Propriétés par défaut (ligne) : Statut=[À trier] Priorité=[P2] │
│ Variables : {{titre}} texte requis · {{date}} = aujourd'hui │
│ ☐ Template par défaut de la base ↻ Répéter : [ Lun. 9 h ▾ ] │
└────────────────────────────────────────────────────────────────────┘
- Bandeau de mode permanent (composant du fragment
_page_editor_content.html, conditionné par le contextetemplate_id), bouton Terminé qui ramène au gestionnaire ou à la base d'origine. - Panneau de réglages (tiroir droit, même patron que les réglages de page) : métadonnées, portée, propriétés par défaut éditées avec les éditeurs de cellule existants de
DBInstance(pour les lignes), variables (§8.4), défaut et récurrence (§11). - La page-support est exclue de l'arbre, de la recherche (
search_excluded=1), des récents et de la Library ; son URL directe renvoie un non-membre de la portée vers 404 (patron teamspace privé). - Pour un template de base, l'édition ouvre un assistant en 3 étapes (schéma et propriétés — sur l'éditeur de schéma existant ; vues par défaut ; lignes d'exemple optionnelles), pas l'éditeur de page.
- Pour un template de blocs, l'édition ouvre la page-support en mode « blocs seuls » (pas de titre de page éditable).
7.5 La rangée Get started with, corrigée
La pilule Templates reste à sa place (c'est un bon réflexe produit, aligné sur Notion) mais son contrat change : clic → sélecteur §7.1 filtré page/blocs, toujours. Si la rangée affiche aussi le menu de création de base (Table…Import de la capture), les deux piles de menus doivent être mutuellement exclusives (ouvrir l'un ferme l'autre — antidote H4). La pilule reçoit un état de chargement bref si le registre met plus de 300 ms à répondre, jamais un silence.
8. Back-end : TemplateService, instanciation, variables dynamiques
8.1 Module et responsabilités
Nouveau service app/services/templates.py (logique métier, accès données — patron de l'architecture : les routeurs authentifient et rendent, les services portent la logique), routeurs :
app/routers/templates.py— pages SSR (/templates, édition) et API interne cookie (/api/templates…, §10.1) ;- extension de
app/routers/api_v2/templates_io.py— l'import/export existant devient le transport du format de manifeste (§10.2, annexe B) ; - le package
collections/ne gagne qu'un endpoint mince pour le menu New ▾ (liste des templates d'une base), qui délègue au service.
8.2 Fonctions du service (contrat)
class TemplateService:
def list_templates(actor, workspace_id, *, kind=None, scope=None,
target_collection_id=None, query=None,
include_archived=False) -> list[TemplateDTO]
def get_template(actor, template_id) -> TemplateDTO # 404 si hors portée (P6)
def create_template(actor, spec) -> TemplateDTO # vierge ou depuis source
def create_from_page(actor, page_id, spec) -> TemplateDTO
def create_from_row(actor, collection_id, row_id, spec) -> TemplateDTO
def create_from_collection(actor, collection_id, spec) -> TemplateDTO
def update_template(actor, template_id, patch) -> TemplateDTO
def duplicate_template(actor, template_id, spec) -> TemplateDTO
def set_default(actor, template_id) -> None # exclusif par base (§11.1)
def set_recurrence(actor, template_id, rrule | None) -> None # lignes uniquement
def archive_template(actor, template_id) / restore / delete # soft-delete
def preview(actor, template_id, variables, context) -> PreviewDTO
def instantiate(actor, template_id, variables, context, *,
idempotency_key=None, run_source) -> InstantiateResult
def run_due_recurrences(now) -> list[InstantiateResult] # appelé par le scheduler
context porte la cible : {target: {type: current_page|new_page|row|collection|insertion}, page_id?, collection_id?, parent_id?, view_hints?: {group_property, date_property}} — les view_hints matérialisent la règle du §7.3 (la vue prime sur le défaut).
8.3 Instanciation — algorithme unique (P4)
- Charger et autoriser : template visible par l'acteur (P6) ; cible inscriptible par l'acteur ; sinon erreur RFC 7807 explicite.
- Résoudre le manifeste (§9.2) depuis la source du template (page-support ou silo legacy via adaptateur §9.5) ; vérifier
manifest.version. - Résoudre les variables (§8.4) : système d'abord, déclarées ensuite (valeurs fournies ou défauts) ; toute variable requise manquante → erreur de validation listant les champs, avant toute écriture.
- Valider les propriétés par défaut avec
validate_property_value()contre le schéma actuel de la base cible ; une propriété disparue ou renommée depuis la création du template produit un avertissement dans le résultat (et dans l'aperçu), pas un échec — le reste s'applique (règle de dégradation §16). - Créer dans une transaction : page / ligne (+ sa page-ombre) / base (+ vues et lignes d'exemple) / insertion de blocs, avec les valeurs figées (P3). Les blocs copiés reçoivent de nouveaux identifiants ; les blocs synchronisés sont copiés en blocs ordinaires (jamais référencés — sinon éditer la copie modifierait la source synchronisée partout) ; les mentions de pages restent des tokens
[[fdpage:…]]si la cible est lisible par l'acteur, sinon dégradées en texte simple. - Journaliser dans
template_runs(§9.4) : source (ui,api,agent,automation,recurrence), acteur, cible créée, statut, avertissements, clé d'idempotence. - Émettre l'événement
template.applied(webhooks sortants, catalogue existant) et, pour une ligne, les événementscollection.*ordinaires de la création — les automatisations existantes se déclenchent donc normalement. - Renvoi :
{created_type, created_id, url, warnings[]}. L'undo du toast (§7.1) supprime l'objet créé (soft-delete) et marque le runundone.
L'idempotence suit le patron API v2 existant (en-tête Idempotency-Key, table idempotency_keys, réponse rejouée) — indispensable pour le scheduler et les retries réseau.
8.4 Variables et valeurs dynamiques — le contrat
Deux familles, résolues à l'instanciation (P3), jamais au rendu :
Variables système (syntaxe dans les titres, valeurs de propriétés et textes de blocs) :
| Token | Résolution | Équivalent existant |
|---|---|---|
{{today}} / {{now}} |
Date / horodatage de l'instanciation, fuseau de l'utilisateur | today() / now() des formules |
{{me}} |
Utilisateur courant (propriétés person, texte « @Prénom ») |
mentions de personnes |
{{date:+7d}}, {{date:-1w}}, {{date:+1mo}} |
Date décalée (jour/semaine/mois), format de sortie selon le type de la propriété cible | dateAdd des formules |
{{workspace}}, {{collection}} |
Nom du workspace / de la base cible | — |
{{title}} |
Titre saisi à la création (pour une ligne : le titre demandé dans le sélecteur) | — |
Variables déclarées (définies dans le panneau de réglages du template, stockées dans templates.variables_json, §9.2) : {name, label, type: text|number|date|person|select, required, default?, options?, prompt?}. Le sélecteur les rend en formulaire (§7.1) ; une variable déclarée s'utilise avec la même syntaxe {{nom}} dans le titre, les propriétés par défaut (par référence, pas par texte, pour les types non textuels) et les blocs.
Frontière avec les tokens existants : les tokens de rendu ([[fdpage:…]], [[fddate:…]]) restent vivants dans l'objet créé ; les tokens {{…}} de template n'y survivent jamais — un {{…}} non résolu à l'instanciation est un échec de validation, pas un texte copié. Cette règle doit être testée (§17) car c'est la confusion la plus probable à l'implémentation.
9. Modèle de données et migrations 48 à 49
9.1 Séquencement des migrations
La version courante décrite par la v7.69.8 est 38. Les documents compagnons proposent déjà : 39 à 43 (Agents & Skills), 44 à 47 (Vues). Les templates prennent donc 48 (registre et réglages) et 49 (journal des runs et récurrences), dans l'ordre des phases du §15 : le registre (48) en phase 1, le journal (49) dès la phase 1 aussi — il est le socle des compteurs et de la déduplication — mais sa partie récurrence n'est consommée qu'en phase 4 ; les deux migrations sont donc écrites en phase 1, dans cet ordre, pour ne jamais rééditer une migration inférieure (règle déjà établie par le document Agents & Skills v1.1). Comme toujours : @register(version, name), une migration = une transaction, backfill séparé et idempotent (le backfill du §9.5 est une fonction du service appelée au boot si le registre est vide, pas du DDL).
9.2 Table templates — le registre (migration 48)
CREATE TABLE templates (
id TEXT PRIMARY KEY, -- tpl_<uuid court>
workspace_id INTEGER NOT NULL REFERENCES workspaces(id),
teamspace_id INTEGER REFERENCES teamspaces(id),
kind TEXT NOT NULL CHECK (kind IN ('page','row','database','blocks')),
name TEXT NOT NULL,
description TEXT NOT NULL DEFAULT '',
icon TEXT,
cover_url TEXT,
category TEXT NOT NULL DEFAULT '', -- libre : Réunion, Projet, Perso…
scope TEXT NOT NULL DEFAULT 'workspace'
CHECK (scope IN ('personal','workspace','teamspace','system')),
owner_id INTEGER REFERENCES users(id), -- requis si scope='personal'
target_collection_id TEXT, -- requis si kind='row' ; la base propriétaire
content_page_id INTEGER REFERENCES pages(id), -- page-support (§9.3), kinds page/row/blocks
source_kind TEXT NOT NULL DEFAULT 'native'
CHECK (source_kind IN ('native','legacy_database','legacy_page','legacy_global')),
source_id TEXT, -- id dans le silo legacy, si source_kind != 'native'
manifest_json TEXT NOT NULL DEFAULT '{}', -- schémas/valeurs par genre (§9.2.1)
variables_json TEXT NOT NULL DEFAULT '[]', -- variables déclarées (§8.4)
is_default INTEGER NOT NULL DEFAULT 0, -- kind='row' uniquement, exclusif par base
is_system INTEGER NOT NULL DEFAULT 0, -- galerie fournie (§12), non éditable
position INTEGER NOT NULL DEFAULT 0, -- ordre dans les menus
created_by INTEGER REFERENCES users(id),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
archived_at TEXT,
deleted_at TEXT, -- soft-delete, corbeille unifiée
sync_version INTEGER NOT NULL DEFAULT 1
);
CREATE INDEX idx_templates_ws_kind ON templates(workspace_id, kind) WHERE deleted_at IS NULL;
CREATE INDEX idx_templates_target ON templates(target_collection_id) WHERE kind='row';
CREATE UNIQUE INDEX uq_templates_default
ON templates(target_collection_id) WHERE is_default=1 AND deleted_at IS NULL AND archived_at IS NULL;
9.2.1 Contenu de manifest_json, par genre
Le manifeste décrit tout ce qui n'est pas le corps de blocs (le corps vit dans la page-support, P2) :
// kind = 'row'
{
"format": "flowdeck-template", "version": 1,
"property_defaults": { "Status": "À trier", "Priority": "P2",
"DueDate": "{{date:+7d}}", "Assignee": ["{{me}}"] },
"title_template": "Bug — {{titre}}"
}
// kind = 'page'
{ "format": "flowdeck-template", "version": 1,
"title_template": "Revue du {{today}}", "full_width": false, "font_small": false }
// kind = 'database'
{ "format": "flowdeck-template", "version": 1,
"collection": { "name": "Projets", "icon": "🗂",
"schema_json": { /* schéma déclaratif existant */ },
"properties": [ /* définitions collection_properties */ ],
"views": [ { "view_type": "board", "config_json": { } } ],
"is_task": true, "task_mapping": { } },
"seed_rows": [ { "template_ref": "tpl_…", "variables": { } } ], // lignes d'exemple, optionnel
"child_pages": [ { "title": "Guide", "content_page_id": 0 } ] } // pages compagnes, optionnel
// kind = 'blocks'
{ "format": "flowdeck-template", "version": 1, "insert_mode": "at_cursor" }
9.3 La page-support — éditer un template comme une page (E4)
Chaque template natif de genre page, row ou blocks possède une page-support : une ligne ordinaire de pages, créée par le service, qui porte son contenu (blocs/markdown, content_format, icône, couverture). Quatre garde-fous la rendent invisible et sûre :
pages.search_excluded = 1(drapeau existant) et exclusion explicite de l'arbre sidebar, des récents, de la Library et de la FTS (les requêtes de ces surfaces filtrent déjàsearch_excludedpour la recherche ; le ticket T3-2 ajoute le filtre aux listes) ;- elle n'a pas de
parent_idde navigation (racine technique du workspace), son accès passe uniquement par le registre : la route d'édition vérifie la visibilité du template (P6) avant de servir l'éditeur, l'URL directe de la page renvoie 404 aux non-ayants droit ; - ses versions (
page_versions) donnent gratuitement l'historique du template ; la restauration d'une version = nouvelle version du template ; - à la suppression du template, la page-support suit le même soft-delete (et la même restauration).
Pour kind='database', pas de page-support unique : le schéma vit dans manifest_json ; les éventuelles pages compagnes ont chacune leur page-support référencée dans le manifeste.
Alternative écartée : stocker les blocs directement dans manifest_json. Elle aurait imposé un éditeur de blocs dédié ou une sérialisation parallèle — contraire à P2/P7 — et perdu versions, temps réel et blocs synchronisés.
9.4 Tables template_runs et récurrences (migration 49)
CREATE TABLE template_recurrences (
template_id TEXT PRIMARY KEY REFERENCES templates(id),
rrule TEXT NOT NULL, -- sous-ensemble RRULE de recurrence.py
timezone TEXT NOT NULL, -- zoneinfo de l'utilisateur propriétaire
enabled INTEGER NOT NULL DEFAULT 1,
next_run_at TEXT, -- UTC, recalculé après chaque passage
last_run_at TEXT,
end_count INTEGER, -- miroir COUNT/UNTIL pour l'affichage
updated_by INTEGER REFERENCES users(id),
updated_at TEXT NOT NULL
);
CREATE TABLE template_runs (
id TEXT PRIMARY KEY,
template_id TEXT NOT NULL REFERENCES templates(id),
run_source TEXT NOT NULL CHECK (run_source IN
('ui','api','agent','automation','recurrence')),
actor_id INTEGER REFERENCES users(id), -- NULL = scheduler
occurrence_key TEXT, -- récurrence : '<template_id>@<occurrence ISO>'
created_type TEXT, -- page | row | collection | insertion
created_id TEXT,
status TEXT NOT NULL CHECK (status IN ('ok','error','undone')),
warnings_json TEXT NOT NULL DEFAULT '[]',
error TEXT,
idempotency_key TEXT,
created_at TEXT NOT NULL
);
CREATE UNIQUE INDEX uq_template_runs_occurrence
ON template_runs(occurrence_key) WHERE occurrence_key IS NOT NULL;
CREATE UNIQUE INDEX uq_template_runs_idem
ON template_runs(idempotency_key) WHERE idempotency_key IS NOT NULL;
occurrence_key unique = la déduplication des récurrences (même patron que reminder_log pour les rappels) ; template_runs sert aussi les compteurs du gestionnaire (§7.2) et l'audit.
9.5 Backfill des silos legacy (P8)
Fonction TemplateService.backfill_registry(), idempotente (clé : source_kind + source_id), exécutée au premier boot après la migration 48 et rejouable depuis l'admin :
| Source legacy | Ligne de registre créée |
|---|---|
database_templates (seed db_templates.py) |
kind='database', scope='system' si issue du seed, source_kind='legacy_database', manifeste construit depuis la définition du seed |
page_templates (par base, récurrentes ou non) |
kind='row', target_collection_id de la base propriétaire, source_kind='legacy_page' ; la récurrence existante est transcrite dans template_recurrences (RRULE) et next_run_at recalculé |
page_global_templates |
kind='blocks', source_kind='legacy_global', page-support créée par copie des blocs |
| Presets de réunion (document Meetings) | kind='page', category='Réunion', scope='system' — simple enregistrement, le pipeline de réunion reste propriétaire de son exécution (§13.3) |
Tant qu'un adaptateur legacy n'a pas été remplacé par le natif (phase 2), instantiate() lit le contenu depuis le silo d'origine pour les lignes source_kind != 'native' : le registre catalogue, il ne déplace pas. La convergence des contenus vers le natif (pages-supports) se fait à la première édition du template via le nouvel éditeur, jamais par une migration silencieuse.
10. API et événements
10.1 API interne (cookie, consommée par le front)
GET /api/templates?kind=&scope=&target_collection_id=&q=&include_archived=
GET /api/templates/{id} → DTO + aperçu du contenu (blocs résumés)
POST /api/templates → créer (vierge ou {from_page_id|from_row|from_collection})
POST /api/templates/{id}/duplicate
PATCH /api/templates/{id} → nom, description, icône, catégorie, portée, position
DELETE /api/templates/{id} → soft-delete (corbeille)
POST /api/templates/{id}/archive | /restore
PUT /api/templates/{id}/property-defaults → (row) manifeste des valeurs par défaut
PUT /api/templates/{id}/variables → variables déclarées
POST /api/templates/{id}/set-default → (row) défaut exclusif de la base
PUT /api/templates/{id}/recurrence → {rrule, timezone, enabled} ou null
POST /api/templates/{id}/preview → {variables, context} → contenu résolu, sans écrire
POST /api/templates/{id}/instantiate → {variables, context, Idempotency-Key} → §8.3 pas 8
GET /api/templates/{id}/runs → journal (gestionnaire)
GET /api/collections/{id}/templates → menu New ▾ (mince, délègue au service)
Erreurs au format des API internes existantes ; l'aperçu et l'instanciation renvoient les warnings[] (propriétés disparues, mentions dégradées) en plus du résultat.
10.2 API publique v2 (Bearer, parité avec l'interne)
Même contrat sous /api/v2/templates… (scopes read/write), construit sur templates_io existant : liste/détail en read ; instantiate/duplicate en write ; export GET /api/v2/templates/{id}/export et import POST /api/v2/templates/import au format de l'annexe B (RFC 7807, idempotence, audit api_audit_log, rate limiting par jeton — tout le contrat §4.1 de l'architecture s'applique sans exception). L'outil agent et les automatisations n'utilisent pas HTTP : ils appellent le service en processus (P4).
10.3 Événements
Nouveaux événements au catalogue des webhooks sortants : template.created, template.updated, template.applied, template.deleted, template.recurrence_fired, template.recurrence_failed. Le bus d'automatisations reçoit template.applied via fire_event() (patron form.submitted, meeting.summarized).
11. Templates de base de données : défaut, récurrence, bases liées
11.1 Template par défaut
- Au plus un défaut actif par base (index unique partiel §9.2).
set_defaultest transactionnel : il retire l'ancien défaut et pose le nouveau dans la même transaction. - Le défaut s'applique : au bouton New principal, à la création par l'agent sans template précisé uniquement si l'utilisateur l'a demandé ainsi (l'agent ne doit pas changer de comportement silencieusement — par défaut l'outil garde son comportement actuel : pas de template sauf demande), et aux créations par automatisation
create_pagesur la base si l'étape coche « appliquer le template par défaut » (défaut : non coché). Ces deux règles évitent le piège classique du défaut qui se met à créer du contenu partout. - Retirer le défaut : menu ••• du template → Retirer le défaut, ou réglage dans le panneau du template.
11.2 Récurrence
- Réglage depuis le menu ••• du template (base ou gestionnaire) → éditeur RRULE reprenant les contrôles des récurrences de propriétés existantes (fréquence, intervalle, jours, fin par date/nombre), stocké dans
template_recurrences, fuseau de l'utilisateur qui règle. - Exécution : le scheduler mutualisé (60 s, celui des rappels et des agents planifiés) appelle
run_due_recurrences(): pour chaque récurrence due et activée, calcul de l'occurrence parrecurrence.py, instanciation avecrun_source='recurrence'etoccurrence_keydédupliqué, recalcul denext_run_at. Un échec journalisetemplate.recurrence_failed, notifie le propriétaire (notification in-app, patron rappels) et ne boucle pas : le prochain passage est la prochaine occurrence, pas un retry immédiat. - Le gestionnaire et le menu de base affichent l'état (« ↻ Lun. 9 h — prochain : 12 oct. ») et permettent pause/reprise/suppression de la récurrence sans toucher au template.
- Une récurrence dont la base cible est archivée/supprimée se désactive elle-même (et le journal le dit).
11.3 Bases liées et vues
Les templates appartiennent à la base source ; une base liée les expose en lecture/application (création dans la source), jamais en édition depuis la liée. Les view_hints (§8.2) couvrent Board (propriété de regroupement), Calendar (propriété de date), Gallery/List (aucun hint) ; en vue Table sans hint, défaut et valeurs du template s'appliquent tels quels.
12. Galerie système et templates fournis
Sur le patron de la galerie de skills (presets installables, §16.3 de l'architecture) :
- Les
database_templatesdu seed actuel deviennent les premiers templatesscope='system',kind='database', sans changement de contenu. - Nouveaux presets
kind='page'fournis (contenus courts, en français comme l'UI) : Note de réunion (alignée sur les presets du document Meetings), Revue hebdo, Spécification produit, Rapport de bug (enkind='row'de démonstration sur une base créée par le preset Projets), Journal quotidien (avec{{today}}dans le titre), Décision (ADR), 1:1 — la liste exacte est un livrable du ticket T4-1, validée par Bruno. - Un template système n'est jamais édité en place : Dupliquer pour modifier crée une copie
scope='personal'(ou workspace selon le rôle). « Installer » un preset de page = le dupliquer dans la portée de l'utilisateur ; les presets de base s'utilisent directement (l'instanciation crée une base neuve, elle ne modifie pas le preset). - Les mises à jour de presets entre versions de Flowdeck ne touchent que les lignes
is_system=1jamais dupliquées.
13. Intégrations : agent, automatisations, réunions, recherche
13.1 Agent Flowdeck
L'outil apply_template du ToolRegistry est rebranché sur TemplateService.instantiate() (même validation, même journal template_runs avec run_source='agent', en plus du journal agent_actions et de son snapshot d'undo existants). Ajouter un outil de lecture list_templates (lecture seule, filtré par les permissions de l'utilisateur appelant — l'agent n'agit jamais au-delà, §6.1 de l'architecture) pour que l'agent puisse proposer le bon template au lieu de l'exiger nommé. AgentPolicies.check_tool() s'applique aux deux, comme à tout outil.
13.2 Automatisations
Nouveau type d'action de step : create_from_template (9e type, après les 8 existants §19.1 de l'architecture) : paramètres {template_id, variables figées ou mappées depuis le trigger, cible}. Elle hérite de tout le moteur (conditions, délais, journal de run). Le bloc button de l'éditeur et la propriété button des bases peuvent la déclencher via leurs automatisations existantes — c'est la réponse Flowdeck au « template button » de Notion (§3.4), sans nouveau mécanisme.
13.3 Réunions
Les presets de notes de réunion existants (standup, 1:1…) sont enregistrés au registre (§9.5) pour être découvrables dans le sélecteur et le gestionnaire, mais le pipeline AI Meeting Notes (bloc meeting, run_processing()) reste le propriétaire de leur exécution : le template de réunion, appliqué depuis le sélecteur, crée la page avec le bloc meeting pré-configuré du bon preset — pas de second moteur de résumé.
13.4 Design System et canevas
Les canevas design_system (block_templates.py) sont des templates de blocs spécialisés (mise en page). Phase 5 : les enregistrer au registre (kind='blocks', category='Design System') pour les rendre trouvables dans le sélecteur, en gardant leur service d'insertion actuel comme adaptateur — même patron que les silos legacy.
13.5 Recherche et palette
Ctrl+K : les templates visibles par l'utilisateur deviennent cherchables par nom (requête sur le registre, pas d'index FTS dédié nécessaire à cette échelle), avec l'action Appliquer / Ouvrir dans le gestionnaire. La recherche sémantique/Ask AI n'indexe pas les pages-supports (cohérent avec leur exclusion, §9.3).
14. Sécurité, permissions et vie privée
- Visibilité par portée :
personal→ propriétaire seul ;workspace→ membres du workspace ;teamspace→ membres du teamspace (hors teamspace privé : 404, patron existant) ;system→ tous les utilisateurs de l'instance, lecture seule. - Écriture : créer/éditer un template
workspaceexige le rôle éditeur du workspace ; pour unkind='row', éditer le template (et surtout changer le défaut ou la récurrence) exige le droit d'édition de la base cible — pas seulement du workspace. - Application : double contrôle (lire le template, écrire la cible), §8.3 pas 1. Un template ne sert jamais de cheval de Troie : les propriétés par défaut qui référencent des personnes/pages inaccessibles à l'applicateur sont dégradées en avertissements (§8.3 pas 4-5).
- Page-support : jamais exposée par les API de navigation/recherche/Library ; les endpoints de l'éditeur vérifient l'appartenance au registre avant de servir son contenu (ticket T3-2, test dédié §17).
- Contenu importé : l'import de manifeste (annexe B) valide le schéma, borne la taille et la profondeur (sous-pages/blocs imbriqués), et neutralise les blocs à risque comme le fait l'import existant (SSRF sur
embed/bookmark: gardienurl_fetchexistant). Un template importé arrive enscope='personal', jamaissystem. - Audit : créations/modifications via l'API interne et v2 suivent les journaux existants (
api_audit_logen v2) ;template_runscouvre les instanciations, y compris celles du scheduler (acteur système explicite).
15. Plan d'implémentation par phases et tickets
Ordre contraignant : 0 diagnostic → 1 registre + sélecteur minimal → 2 gestion → 3 éditeur → 4 galerie/récurrences/intégrations, phase 5 optionnelle. Chaque ticket est livrable et testable séparément ; les critères d'acceptation sont cumulatifs avec §17.
Phase 0 — Diagnostic du bouton cassé (prérequis, petit)
| Ticket | Contenu | Acceptation |
|---|---|---|
| T0-1 | Audit de l'existant réel : inventorier dans le code les handlers de la rangée Get started with, les routes page-templates (board) et templates_io (v2), et les schémas exacts des trois tables legacy (colonnes réelles, via docs/DATA_MODEL.md et la base) ; consigner les écarts avec §4.1 |
Note d'audit jointe au dépôt ; tout écart avec ce document est signalé avant la phase 1 |
| T0-2 | Diagnostic H1→H6 (§4.3) dans l'ordre, avec preuves (console, réseau, elementFromPoint, comparaison de version SW) |
Cause racine consignée par écrit ; si aucune hypothèse ne se confirme, escalade à Bruno avec les traces — pas de correction spéculative |
| T0-3 | Test de reproduction Playwright : ouvrir une page vide, cliquer la pilule Templates, attendre un panneau visible ; le test échoue avant correctif, passe après | Test rouge→vert pour la cause du T0-2 ; intégré à la suite E2E existante |
| T0-4 | Correctif minimal de la cause + garde-fou P5 : en attendant la phase 1, le clic ouvre un panneau d'état du catalogue (même vide, avec Créer un template désactivé proprement si la création n'existe pas encore — jamais un no-op) | Parcours manuel §17.4, étape 1 |
Phase 1 — Registre unifié, service d'instanciation, sélecteur minimal (MVP utilisable)
| Ticket | Contenu | Acceptation |
|---|---|---|
| T1-1 | Migrations 48 (templates) et 49 (template_recurrences, template_runs) — §9 |
Migrations appliquées sur une copie de base v7.69.8 ; idempotentes ; rollback documenté |
| T1-2 | TemplateService : list/get/create/update/duplicate/archive, adaptateurs legacy (§9.5), backfill idempotent rejouable |
Backfill sur données du seed : registre complet, zéro doublon au second passage |
| T1-3 | instantiate() transactionnel pour page et row (§8.3), résolution des variables système (§8.4), validation des propriétés, journal template_runs, idempotence |
Tests unitaires §17.2 ; un échec en cours de création ne laisse aucun objet partiel |
| T1-4 | API interne §10.1 (liste, détail, preview, instantiate) ; API v2 : liste/détail/instantiate | Contrat testé ; erreurs RFC 7807 en v2 |
| T1-5 | Sélecteur minimal (§7.1, sans l'aperçu riche : liste + recherche + variables déclarées en formulaire simple) branché sur : pilule Templates de page vide (remplace le panneau d'état du T0-4), menu ••• de page, New ▾ de base en lecture | Le parcours directeur de Bruno (§2.3, persona 1) fonctionne de bout en bout |
| T1-6 | Rebranchement de l'outil agent apply_template sur le service + outil list_templates (§13.1) |
Un run agent créant depuis un template apparaît dans template_runs et dans agent_actions |
Phase 2 — Gestionnaire et gestion par base
| Ticket | Contenu | Acceptation |
|---|---|---|
| T2-1 | Page /templates (§7.2) : liste filtrable (genre, portée, base, archivés), recherche, tri, sélection, side peek d'aperçu, compteurs depuis template_runs |
Toutes les opérations de gestion (§2.1) faisables sans SQL ni API manuelle |
| T2-2 | Onglet Templates dans la Library (mêmes données, patron Library) + entrée Templates dans la sidebar (panneau Home, sous Library, respectant sidebar_config) |
L'onglet et la page affichent des données identiques |
| T2-3 | Création depuis le gestionnaire : vierge (page/row/blocks), depuis une page/ligne/base existante (create_from_*), import JSON (annexe B) |
Un template créé depuis une page existante reproduit fidèlement titre/icône/blocs à l'instanciation |
| T2-4 | Menu New ▾ complet (§7.3) : défaut marqué, ••• par template, + Nouveau template… (crée et ouvre l'éditeur phase 3 en mode minimal : nom + propriétés par défaut), Gérer les templates… filtré sur la base | Le défaut s'applique par New ; l'exclusivité du défaut tient en concurrence (deux réglages simultanés) |
| T2-5 | Portées et partage : réglage de portée dans le gestionnaire, contrôles §14 (dont 404 hors teamspace privé) | Matrice de tests §17.3 verte |
Phase 3 — Éditeur de template complet
| Ticket | Contenu | Acceptation |
|---|---|---|
| T3-1 | Page-support à la création des templates natifs (§9.3) + route d'édition avec bandeau de mode et bouton Terminé (§7.4) | Éditer un template = éditer une page ; historique des versions visible et restaurable |
| T3-2 | Masquage complet de la page-support : arbre, récents, Library, recherche, Ask AI, backlinks ; 404 direct hors ayants droit | Tests dédiés §17.3 ; aucune fuite du contenu d'un template personnel via la recherche d'un autre utilisateur |
| T3-3 | Panneau de réglages : métadonnées, portée, propriétés par défaut avec les éditeurs de cellule de DBInstance (row), variables déclarées (CRUD + aperçu de résolution), défaut |
L'aperçu du sélecteur (T3-5) reflète exactement ce qui sera créé |
| T3-4 | Assistant d'édition kind='database' (schéma, vues, lignes d'exemple) ; édition kind='blocks' en mode blocs seuls |
Un template de base édité crée une base conforme au schéma affiché |
| T3-5 | Aperçu résolu dans le sélecteur (§7.1) : contenu résolu, chips de propriétés, avertissements ; modes ajouter / remplacer sur page existante avec confirmation explicite pour remplacer | Aucun {{token}} non résolu ne peut atteindre l'objet créé (§8.4) |
| T3-6 | Application après coup à une ligne existante (§3.1) : depuis la page de ligne, Appliquer un template… (propriétés : ne remplit que les vides par défaut, case « écraser » explicite ; corps : ajouté à la fin) | Comportement par défaut non destructif, testé |
Phase 4 — Récurrences, galerie système, automatisations
| Ticket | Contenu | Acceptation |
|---|---|---|
| T4-1 | Galerie système (§12) : presets convertis + nouveaux presets validés par Bruno, badges, Dupliquer pour modifier | Un nouvel utilisateur trouve ≥ 5 templates utilisables dès l'installation |
| T4-2 | Récurrences complètes (§11.2) : éditeur RRULE, affichage « prochain passage », pause/reprise, branchement scheduler + déduplication + notification d'échec | Une tâche hebdo de test se crée à l'heure dite, une seule fois, même si le scheduler redémarre entre-temps |
| T4-3 | Action d'automatisation create_from_template (§13.2) + option « template par défaut » des actions create_page (§11.1) |
Un bouton de page et un bouton de base déclenchent chacun une création depuis template, journalisée |
| T4-4 | Enregistrement des presets de réunion au registre (§13.3) ; export/import v2 complets (§10.2) ; événements webhooks §10.3 | Export d'un template → import sur une autre instance → instanciation identique |
Phase 5 — Extensions (optionnelle)
| Chantier | Contenu |
|---|---|
| A | Dupliquer comme template sur les pages publiées de l'instance (réglage dans Share → Publish, bouton Duplicate sur la page publique, copie vers le workspace du visiteur connecté) |
| B | Canevas Design System enregistrés au registre (§13.4) |
| C | Templates de dashboard (une mise en page collection_dashboards comme contenu de template de base) — à coordonner avec le document Vues (E4 Dashboard) |
| D | Suggestions de templates par l'agent (« cette page ressemble à un format récurrent, l'enregistrer comme template ? ») — proposition uniquement, jamais de création silencieuse |
16. Exigences non fonctionnelles, résilience, déploiement
- Performance : liste du registre filtrée < 100 ms à 1 000 templates (index §9.2) ; le sélecteur ne charge jamais les corps de blocs en liste (aperçu à la demande, un template à la fois) ; instanciation d'un template de page < 500 ms hors pièces jointes.
- Dégradation (§8.3 pas 4) : schéma de base ayant dérivé depuis la création du template → appliquer ce qui reste valide + avertissements nommés ; jamais d'échec total pour une propriété renommée, jamais d'application silencieusement partielle sans avertissement.
- Hors ligne : le sélecteur en mode offline affiche les métadonnées mises en cache mais désactive l'instanciation avec un message explicite (les écritures restent à la file de sync existante seulement pour les objets ordinaires ; une instanciation n'est pas rejouable telle quelle hors ligne). Le bouton ne doit en aucun cas redevenir « mort » hors ligne — c'est un cas de test (§17).
- Fichiers et médias : un template référençant des uploads (images, pièces jointes) les copie à l'instanciation (nouveaux fichiers), sur le patron de la duplication de page existante ; jamais de référence partagée dont la suppression du template casserait les objets créés.
- Déploiement : aucun nouveau service, aucune dépendance ; migrations dans le flux de boot existant ; le backfill est journalisé (nombre de lignes par
source_kind) et son échec ne bloque pas le démarrage (le registre reste vide, les silos legacy continuent de servir leurs anciennes surfaces — dégradation, pas panne).
17. Tests et critères d'acceptation
17.1 Régression du bug d'origine
- Le test Playwright du T0-3 reste dans la suite : page vide → clic Templates → sélecteur visible. Variantes : catalogue vide, hors ligne, utilisateur sans aucun template personnel. Dans tous les cas, un panneau visible.
17.2 Tests unitaires / d'intégration du service
- Résolution des variables : système (dates au fuseau de l'utilisateur), déclarées (requise manquante → erreur avant écriture ; défaut appliqué), offsets de dates,
{{me}}dans une propriétéperson. - Aucun token
{{…}}survivant dans un objet créé (assertion sur titre, propriétés et blocs). - Propriétés par défaut : valide, renommée (avertissement), type changé (avertissement), relation vers cible inaccessible (dégradée).
- Transaction : panne simulée au milieu de la création d'une base depuis template → ni base ni lignes orphelines ;
template_runsenerror. - Idempotence : double
instantiatemême clé → un seul objet, réponse rejouée. - Défaut : exclusivité sous concurrence ; New applique le défaut ; bases liées : lecture des templates de la source.
- Récurrence : occurrence calculée au bon fuseau ; redémarrage du scheduler entre deux passages → pas de doublon (
occurrence_key).
17.3 Tests de permissions
Matrice portée × rôle (propriétaire / membre workspace / membre teamspace / extérieur / agent au nom de chacun) sur : voir dans le sélecteur, éditer, changer le défaut et la récurrence, appliquer, accéder à l'URL directe de la page-support, chercher le contenu d'un template personnel d'autrui.
17.4 Parcours manuel de vérification (à exécuter par Hermes, résultat consigné)
- Page vide → Templates → le sélecteur s'ouvre (le bug d'origine, refermé).
- Créer un template de page depuis la page courante ; l'appliquer à une nouvelle page ; vérifier titre daté résolu et blocs.
- Dans une base de tâches : créer deux templates de ligne, en définir un comme défaut, vérifier New vs New ▾.
- Rendre un template récurrent (quotidien) ; forcer le passage du scheduler ; vérifier une ligne créée, datée du jour.
- Ouvrir le gestionnaire
/templateset l'onglet Library : mêmes données, compteurs à jour. - Demander à l'agent de créer une note depuis un template nommé ; vérifier
template_runs(run_source='agent') et l'undo.
18. Décisions d'architecture (ADR — résumé)
| # | Décision | Alternative écartée | Motif |
|---|---|---|---|
| ADR-T1 | Registre templates fédérant les silos, contenus conservés à leur place puis convergés à l'édition |
Fusion immédiate des trois tables en une seule | Zéro perte, livrable en phase 1, conforme à P8 |
| ADR-T2 | Contenu des templates de page/ligne dans une page-support masquée | Blocs sérialisés dans manifest_json |
Réutilise éditeur, versions, temps réel ; aucun éditeur parallèle (P2, P7) |
| ADR-T3 | Instanciation unique dans TemplateService, HTTP seulement pour les surfaces externes |
Logique par surface (UI, agent, scheduler) | L'invariant « une seule implémentation » déjà appliqué à l'API agent de Flowdeck |
| ADR-T4 | Variables {{…}} figées à l'instanciation, distinctes des tokens de rendu [[…]] |
Résolution au rendu | Un objet créé depuis un template doit être stable dans le temps (P3) |
| ADR-T5 | Récurrence portée par le template (table dédiée + scheduler 60 s + dédup par occurrence_key) |
Une automatisation générée par récurrence | Le réglage « Repeat » de Notion est une propriété du template ; le patron rappels prouve le mécanisme dans Flowdeck |
| ADR-T6 | Gestionnaire = page /templates + onglet Library, pas un écran Settings |
Réglages dans Settings | Les templates sont des objets de travail quotidiens, pas de la configuration ; Library est le patron « toutes mes ressources » |
| ADR-T7 | Le défaut ne s'applique pas implicitement à l'agent ni aux automatisations existantes (opt-in explicite, §11.1) | Défaut global à toute création | Éviter qu'un réglage d'UI change silencieusement le comportement de systèmes déjà en production chez Bruno |
19. Risques et mitigations
| Risque | Mitigation |
|---|---|
| La cause du bouton cassé est plus profonde (fragment partagé par d'autres pilules de la rangée) | Phase 0 avant tout ; le test T0-3 couvre la rangée entière, pas seulement Templates |
| Fuite de contenu via la page-support (recherche, backlinks, partage) | Garde-fous §9.3 + matrice §17.3 ; revue spécifique du ticket T3-2 |
| Dérive des schémas de bases rendant les templates de ligne partiellement inapplicables | Règle de dégradation §16 + avertissements dans l'aperçu et le résultat ; le gestionnaire signale les templates « à revoir » (propriété par défaut introuvable) |
| Doublons de récurrence après redémarrage ou double instance | occurrence_key unique + scheduler mono-processus existant |
| Confusion des deux sens du mot « template » (Jinja vs métier) chez les contributeurs | Convention de nommage §4.4, à ajouter à AGENTS.md du dépôt Flowdeck |
| Le périmètre gonfle vers un marketplace | Hors périmètre §2.2 ; la phase 5 est explicitement optionnelle et fermée |
20. Questions ouvertes pour Bruno
- Presets système : la liste de départ du §12 (7 presets) te convient-elle, ou veux-tu tes propres formats dès la phase 4 (par ex. tes formats de notes Flowdeck/ObsiGate) ?
- Portée par défaut d'un nouveau template :
personal(sûr, mais invisible aux autres membres) ouworkspace(pratique en équipe) ? Proposition du document :personal, promotion explicite — à confirmer vu que tu es souvent seul sur l'instance. - Application sur page non vide : le mode par défaut du sélecteur doit-il être ajouter à la fin (proposition, non destructif) ou demander à chaque fois ?
- Récurrence et défaut des bases de tâches liées à My Tasks : un template récurrent sur une base de tâches doit-il hériter du mapping
is_task(assigné/statut/échéance) automatiquement ? Proposition : oui, via lesview_hintset le mapping existant, sans réglage supplémentaire. - Nom de la route du gestionnaire :
/templatesest proposé ; si une route legacypage-templatessous/boarddoit rester l'URL canonique pour compatibilité de liens existants, le signaler avant la phase 2.
Annexe A — Correspondance Notion → Flowdeck
| Notion | Flowdeck (cible) |
|---|---|
| Template de base (propre à la base) | Template kind='row' + target_collection_id (§9.2) |
| Menu New ▾ : + New template, ••• (Edit / Duplicate / Set as default / Repeat / Delete) | Menu New ▾ §7.3, mêmes actions, mêmes libellés |
| Édition du template comme une page | Page-support + bandeau de mode (§7.4, §9.3) |
| Template par défaut | is_default exclusif par base (§11.1) |
| Repeat (daily/week/month/year/custom) | template_recurrences RRULE + scheduler (§11.2) |
Mentions dynamiques @today/@now/@me dans le titre |
Variables système {{today}}/{{now}}/{{me}} (§8.4) |
| Galerie Templates de la sidebar | Gestionnaire /templates + onglet Library (§7.2) + galerie système (§12) |
| Page publiée « Allow duplicate as template » | Phase 5, chantier A (§15) |
| Boutons (ex-template button) | Bloc/propriété button + action d'automatisation create_from_template (§13.2) |
| Appliquer un template à une page existante | Sélecteur en mode ajout/remplacement (§7.1) ; lignes : T3-6 |
Annexe B — Format de manifeste flowdeck-template v1
Transport d'export/import (API v2 §10.2) — un objet JSON autonome :
{
"format": "flowdeck-template",
"version": 1,
"kind": "row",
"name": "Rapport de bug",
"description": "Structure standard d'un rapport de bug",
"icon": "🐞",
"category": "Support",
"target": { "match_by": "collection_name", "collection_name": "Support" },
// à l'import, la base cible est résolue par nom dans le workspace choisi,
// jamais par id externe ; si absente, l'import crée le template « orphelin »
// en scope personnel et le signale.
"variables": [ { "name": "titre", "type": "text", "required": true } ],
"manifest": { "property_defaults": { "Status": "À trier" },
"title_template": "Bug — {{titre}}" },
"content": { "content_format": "blocks", "blocks": [ /* blocs de la page-support */ ] },
"recurrence": { "rrule": "FREQ=WEEKLY;BYDAY=MO", "timezone": "America/Toronto" } // optionnel
}
Règles : version supérieure non reconnue → refus explicite ; les content_page_id et identifiants internes ne voyagent jamais ; les blocs synchronisés sont exportés matérialisés (contenu copié).
Annexe C — Glossaire
- Template : objet réutilisable du registre, de genre page, ligne, base ou blocs.
- Page-support : page technique masquée portant le contenu d'un template (§9.3).
- Instanciation : création d'un objet réel depuis un template, par
TemplateService.instantiate()(§8.3). - Sélecteur : panneau de choix/aperçu/application d'un template (§7.1).
- Gestionnaire : écran de gestion des templates, page
/templateset onglet Library (§7.2). - Variable système / déclarée : valeur résolue automatiquement / demandée à l'utilisateur, à l'instanciation (§8.4).
- Run : une instanciation journalisée dans
template_runs(§9.4).
Sources
Analyse fonctionnelle de Notion (section 3), documentation publique officielle consultée le 10 octobre 2026 :
- Using database templates — https://www.notion.com/help/guides/using-database-templates
- Using database templates to help cement your team's process — https://www.notion.com/help/guides/using-database-templates-for-teams
- Automate work with repeating database templates — https://www.notion.com/help/guides/automate-work-repeating-database-templates
- Getting started with templates for your team — https://www.notion.com/help/guides/getting-started-with-templates-for-your-team
État Flowdeck : ARCHITECTURE.md v7.69.8 fourni par Bruno (§§ cités dans le texte) et capture de l'état actuel fournie le 10 octobre 2026 (templates-notion-reference/capture-etat-actuel-bouton-templates.png).