Vues enregistrées
Une vue est une donnée — arbre de filtres, tris et groupement — compilée en SQL à un seul endroit. Ce qu'elle sélectionne, et ce qu'elle change à l'affichage.
Une vue est une façon enregistrée de regarder une liste. Elle se compose de deux moitiés délibérément tenues à l'écart l'une de l'autre :
definition— ce que la vue sélectionne : un arbre de filtres, des tris, un groupement facultatif, et le choix de suivre ou non les rattachements à des listes secondaires ;display— comment on la regarde : l'ordre des colonnes d'un kanban, et les champs que la vue montre (columns) — ordre, largeurs et colonnes masquées d'un tableau, et le même ordre et les mêmes champs masqués pour les cartes d'un kanban ou d'une pile. Un seul réglage par vue, quel que soit sonkind.
Déplacer, élargir ou masquer une colonne modifie display. Cela ne peut faire
entrer ni sortir une seule carte du résultat, parce que cela n'atteint jamais la
partie qui sélectionne.
`display` est remplacé en bloc
Enregistrer une moitié oblige à renvoyer l'autre. Un client qui enregistre les
largeurs de colonnes sans renvoyer groupOrder efface l'ordre des colonnes du
kanban.
Une définition est une donnée, pas du SQL
{
"filter": {
"and": [
{ "op": "eq", "field": { "fieldId": "sys:status" }, "value": "<statusId>" },
{ "op": "contains", "field": { "fieldId": "<fieldId>" }, "value": "draft" }
]
},
"sort": [{ "field": { "fieldId": "sys:order" }, "direction": "asc" }],
"groupBy": { "fieldId": "sys:status" },
"includeSecondaryLists": true
}Aucun fragment SQL ne peut entrer dans une vue — ni extrait, ni expression, ni nom de colonne. Une définition est un arbre sérialisable, et c'est ce qui permet de la stocker, de l'envoyer, d'en calculer le diff et de l'exécuter ailleurs que là où elle a été écrite.
Elle devient du SQL à un seul endroit du code. C'est une contrainte voulue : c'est elle qui permettra un jour d'aiguiller une vue vers un autre backend de lecture sans toucher à un seul écran.
Les types de vue
Une vue a un kind : table, board, list ou calendar.
Le type ne change pas ce que la vue sélectionne. Il change la mise en page et, pour les types groupés, la route à appeler.
L'interface affiche les quatre. Une vue calendar a trois mises en page —
mois, semaine et chronologie — choisies dans son display, pas comme
des types de plus.
Calendrier et chronologie
Un calendrier pose les cartes sur une propriété date (ou sur
sys:created_at, en lecture seule). Quelle date, et quelle mise en page, vivent
dans display.calendar :
{ "calendar": { "layout": "month", "dateField": "<fieldId date>", "endField": "<fieldId date>" } }Comme les réglages de colonnes, c'est une façon de regarder, pas de
sélectionner : la définition n'est pas touchée, et la même vue en tableau
sélectionne les mêmes cartes. Le mois regardé n'est enregistré nulle part —
il voyage avec chaque lecture, en range :
{
"definition": { "...": "la définition de la vue, plus groupBy sur la date" },
"groups": ["2026-10-26", "2026-10-27", "…", "2026-12-06"],
"range": { "field": { "fieldId": "<fieldId date>" }, "from": "2026-10-26", "to": "2026-12-06" }
}Un mois est donc une lecture groupée par jour : chaque case est un groupe, vides comprises, chacune avec son curseur — « +3 de plus » charge la suite de ce jour. La plage est compilée par l'API et borne les lignes comme les compteurs.
Avec un endField, une carte est une durée, et elle est rendue par toute fenêtre
qu'elle chevauche : une rédaction commencée le 28 octobre et due le
4 novembre est dans les deux mois. Une fin absente, ou antérieure au début, vaut
une durée d'un jour.
Dans l'interface, glisser une carte sur un autre jour écrit sa date, la déposer sur le panneau Sans date l'efface, et cliquer dans le vide d'une case crée une carte qui naît avec cette date. Sur la chronologie, glisser une barre décale ses deux bornes, tirer un bord n'écrit que ce bord, et cliquer sur la piste d'une carte sans date l'y pose.
Pourquoi la chronologie n'est pas un `kind`
kind fait partie de chaque vue dans chaque réponse. L'app iOS décode les
réponses strictement : une valeur qu'elle ne connaît pas l'empêcherait d'ouvrir
la liste entière. calendar est dans l'énumération depuis le début, donc la
chronologie en est une mise en page — et les apps plus anciennes l'affichent
simplement en tableau.
Le groupement, et pourquoi les colonnes viennent du domaine
Une colonne de kanban, c'est la vue, plus une valeur de groupe. Même SQL compilé, un prédicat de plus — c'est ce qui fait que la somme des colonnes vaut exactement la vue.
Deux conséquences que vous rencontrerez tout de suite :
Chaque groupe se pagine séparément. POST …/grouped-query renvoie des
groupes, chacun avec son propre curseur. Découper une page plate en colonnes
côté client remplirait trois colonnes au hasard de l'ordre de tri, et rendrait
inexprimable un « charger la suite » sur une seule colonne.
L'appelant nomme les groupes qu'il veut. Un GROUP BY ne renvoie que les
valeurs qui ont des lignes : une colonne « Terminé » vide n'existerait donc pas —
et on ne peut pas déposer une carte dans une colonne qui n'est pas affichée.
L'appelant fournit les groupes, parce que lui seul connaît les statuts de
l'espace de travail, les options déclarées d'un select ou la liste des
membres.
Un axe sur lequel on ne peut pas écrire n'accepte donc aucun dépôt : déposer une carte est une écriture, pas un geste de rangement.
Une clé de groupe doit faire l'aller-retour
Une clé de groupe sort d'une requête en ::text et rentre dans une autre pour
retrouver les lignes de ce groupe. Si elle ne se relit pas dans le type de
l'expression, la colonne affiche « 12 » en en-tête et zéro ligne dessous — sans
aucune erreur nulle part. C'est pourquoi le groupement par sys:created_at
groupe par jour de création, en UTC : lue telle quelle, la colonne est un
horodatage dont la clé ne se relirait pas en date, et chaque carte aurait son
groupe. Le jour fait l'aller-retour, et c'est ce dont un calendrier des
créations a besoin.
L'ordre manuel est global à la liste
sys:order est un ordre par liste, pas un par colonne.
Déposer une carte en tête d'une colonne de kanban la place donc aussi en première ligne de la vue tableau. Notion garde un ordre distinct par groupe ; ici, il n'y en a qu'un, et ça se voit. Si cela vous surprend, c'est le comportement documenté, pas un bug.
Une conséquence appliquée à dessein : déposer dans une colonne vide n'écrit aucun ordre. La carte y sera première de toute façon, et en écrire un l'aurait propulsée en tête de la liste entière.
La vue par défaut
Une liste sans vue enregistrée n'est pas illisible. L'API publie
CollectionDetail.defaultView : ordre manuel, groupement par statut, listes
secondaires incluses.
Elle est publiée plutôt que recopiée dans chaque client, et pour une raison — voir listes et cartes.
Tri et pagination
La pagination est keyset uniquement. Il n'y a d'OFFSET nulle part dans
l'API, et aucun moyen d'en demander un : le curseur est opaque, vous renvoyez ce
qu'on vous a donné.
C'est mesuré, pas esthétique — vers la page 12 000, la forme avec offset prenait 348 ms là où la forme keyset en prenait 3.
Un curseur est lié au tri pour lequel il a été émis. Changez le tri, repartez du début ; l'API vous le dira plutôt que de renvoyer en silence une mauvaise page.
Détails dans interroger le contenu.
Le registre des propriétés
Dix types de propriétés, déclarés une fois par espace de travail, validés à l'écriture — et ce que chacun permet de trier, filtrer ou grouper.
Sprints, points et burndown
Un sprint est une liste qui porte des dates. Ses cartes restent dans leur backlog ; les points d'effort, le burndown et la vélocité se lisent dans le journal de l'espace et dans ce que chaque sprint contient.