Files
flowdeck/docs/architecture-templates-notion-flowdeck.md
T
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

94 KiB
Raw Blame History

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 :

  1. 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.
  2. 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.
  3. 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 :

  1. 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.
  2. 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 registre templates (§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.
  3. 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.
  4. 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.data dans base.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 Jinja fd_icon, cache-busting ?v=VERSION, navigation partielle maison (fdNavigate) plutôt que des rechargements complets.
  5. 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

  1. Résumé exécutif
  2. Périmètre, personas et cas d'usage
  3. Analyse fonctionnelle : les templates dans Notion
  4. État des lieux Flowdeck v7.69.8, capture de Bruno et analyse d'écart
  5. Principes directeurs
  6. Vue d'ensemble du système (C4)
  7. Interface : sélecteur, gestionnaire, menus de base et éditeur de template
  8. Back-end : TemplateService, instanciation, variables dynamiques
  9. Modèle de données et migrations 48 à 49
  10. API et événements
  11. Templates de base de données : défaut, récurrence, bases liées
  12. Galerie système et templates fournis
  13. Intégrations : agent, automatisations, réunions, recherche
  14. Sécurité, permissions et vie privée
  15. Plan d'implémentation par phases et tickets
  16. Exigences non fonctionnelles, résilience, déploiement
  17. Tests et critères d'acceptation
  18. Décisions d'architecture (ADR — résumé)
  19. Risques et mitigations
  20. Questions ouvertes pour Bruno

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_templates et, à 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 button de l'éditeur (déclenche une automatisation) et la propriété button des 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_templates n'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 templates avec un kind. 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 un schema_json de collection ; les valeurs de propriétés sont un property_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-templates et templates_io continuent 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 contexte template_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)

  1. Charger et autoriser : template visible par l'acteur (P6) ; cible inscriptible par l'acteur ; sinon erreur RFC 7807 explicite.
  2. Résoudre le manifeste (§9.2) depuis la source du template (page-support ou silo legacy via adaptateur §9.5) ; vérifier manifest.version.
  3. 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.
  4. 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).
  5. 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.
  6. Journaliser dans template_runs (§9.4) : source (ui, api, agent, automation, recurrence), acteur, cible créée, statut, avertissements, clé d'idempotence.
  7. Émettre l'événement template.applied (webhooks sortants, catalogue existant) et, pour une ligne, les événements collection.* ordinaires de la création — les automatisations existantes se déclenchent donc normalement.
  8. Renvoi : {created_type, created_id, url, warnings[]}. L'undo du toast (§7.1) supprime l'objet créé (soft-delete) et marque le run undone.

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 :

  1. 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_excluded pour la recherche ; le ticket T3-2 ajoute le filtre aux listes) ;
  2. elle n'a pas de parent_id de 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 ;
  3. ses versions (page_versions) donnent gratuitement l'historique du template ; la restauration d'une version = nouvelle version du template ;
  4. à 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

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_default est 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_page sur 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 par recurrence.py, instanciation avec run_source='recurrence' et occurrence_key dédupliqué, recalcul de next_run_at. Un échec journalise template.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_templates du seed actuel deviennent les premiers templates scope='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 (en kind='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=1 jamais 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 workspace exige le rôle éditeur du workspace ; pour un kind='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 : gardien url_fetch existant). Un template importé arrive en scope='personal', jamais system.
  • Audit : créations/modifications via l'API interne et v2 suivent les journaux existants (api_audit_log en v2) ; template_runs couvre 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_runs en error.
  • Idempotence : double instantiate mê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é)

  1. Page vide → Templates → le sélecteur s'ouvre (le bug d'origine, refermé).
  2. Créer un template de page depuis la page courante ; l'appliquer à une nouvelle page ; vérifier titre daté résolu et blocs.
  3. Dans une base de tâches : créer deux templates de ligne, en définir un comme défaut, vérifier New vs New ▾.
  4. Rendre un template récurrent (quotidien) ; forcer le passage du scheduler ; vérifier une ligne créée, datée du jour.
  5. Ouvrir le gestionnaire /templates et l'onglet Library : mêmes données, compteurs à jour.
  6. 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

  1. 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) ?
  2. Portée par défaut d'un nouveau template : personal (sûr, mais invisible aux autres membres) ou workspace (pratique en équipe) ? Proposition du document : personal, promotion explicite — à confirmer vu que tu es souvent seul sur l'instance.
  3. 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 ?
  4. 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 les view_hints et le mapping existant, sans réglage supplémentaire.
  5. Nom de la route du gestionnaire : /templates est proposé ; si une route legacy page-templates sous /board doit 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 /templates et 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 :

É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).