Spunto CMS

Ce qu'il ne fait pas

Les manques, les pièges et les refus tranchés de Spunto CMS, énoncés ici pour que votre premier appel à l'API REST n'ait pas à vous les apprendre.

Une page qui ne liste que des fonctionnalités coûte plus cher que pas de page du tout : elle fait perdre son temps à qui la croit, et le premier curl la corrige de toute façon.

Voici donc l'autre liste. Elle a trois parties, et elles ne sont pas de même nature.

Refus tranchés

Ce sont des décisions, pas des manques. Elles ne changeront pas.

Pas de GraphQL. REST uniquement, maintenant comme plus tard.

Pas d'URL de fichier partageable. Ni bucket public, ni lien signé. Pourquoi.

Pas de tri ni de groupement sur une propriété tableau. multi_select et multi_person refusent les deux, avec un 400 qui le dit, plutôt que de trier selon l'ordre des documents d'un tableau JSON. Pourquoi.

Pas de pagination par offset. Des curseurs keyset uniquement, partout. Pourquoi.

Pas de 403 pour un espace de travail dont vous n'êtes pas membre. Vous recevez un 404, et l'API ne vous dira pas lequel des deux cas il recouvre. Pourquoi.

Pas de HTML dans le corps des cartes. Le format en blocs ne peut pas l'exprimer. Pourquoi.

Pas encore construit

Ce sont des absences, et elles peuvent changer. Elles sont listées parce que bâtir sur leur absence, ou sur leur arrivée imminente, est un choix qu'il vaut mieux faire en connaissance de cause.

Publier, c'est une carte, une version, maintenant. Une carte a un brouillon et au plus une version publiée. Il n'y a ni publication programmée, ni historique de versions vers lequel revenir, ni route qui publie en masse : un script publie carte par carte.

Le publié est un choix de lecture, pas un droit. version: "published", c'est le lecteur qui le demande. Une clé qui peut lire un espace peut aussi en lire les brouillons — gardez-la côté serveur, comme de toute façon.

Tout ne lit pas la version publiée. Les requêtes de vue, les lectures groupées, les compteurs de groupe et GET …/items/{itemId} prennent une version. La recherche, les sous-tâches, le journal d'activité et l'accueil montrent toujours les brouillons.

Pas de recherche plein texte dans le corps des cartes. La recherche ne porte que sur les titres des cartes et les noms des listes. Il n'existe aucun index sur le corps, et les vues ne peuvent pas non plus filtrer ni trier dessus.

Pas de rétroliens, et un signet ne se rafraîchit pas. Une mention suit la carte qu'elle cite, mais rien ne liste les cartes qui mentionnent une carte donnée. Le titre d'un signet est lu une fois, au moment du coller ; il ne change pas quand la page change. L'import Notion ne convertit pas les mentions de pages Notion. Les embeds sont une liste blanche — un autre fournisseur est un changement de code, pas un réglage.

Pas de calendrier dans l'app iOS. L'application web affiche les vues calendrier (mois, semaine, chronologie) ; l'app iOS montre une vue calendrier en tableau.

Les limites du calendrier. Les cases d'un calendrier et les barres d'une chronologie viennent de propriétés date : pas d'heure, pas de récurrence, et pas de durée sur plusieurs jours dans la grille du mois — les durées sont l'affaire de la chronologie, entre deux propriétés. Les jours se lisent en UTC, comme toutes les dates que l'app affiche.

Pas de sprints automatiques. Un sprint se crée par quelqu'un — sur le bandeau, Terminer le sprint propose de créer le suivant —, jamais parce qu'une date est passée : il n'y a pas de planificateur. Sprints.

Pas de création automatique d'index. POST …/index-hints vous dit quels index une vue réclame, et s'arrête là — la tâche qui les appliquerait n'existe pas. Détails.

Pas d'édition en masse au-delà de 5 000 cartes. L'API refuse plutôt que de prétendre être atomique ou de mettre du travail en file en silence. Entre 200 et 5 000, vous devez passer expectedCount. Détails.

Pas de duplication d'un sous-arbre au-delà de 200 cartes. La duplication est un geste synchrone et atomique qui vous rend la copie ; au-delà, ce serait un import, et elle refuse plutôt que de faire semblant. Détails.

Pas de mode hors ligne. L'application web s'installe comme une application autonome (Chrome sur Android et sur ordinateur, « Sur l'écran d'accueil » sur iOS), mais rien de ce que vous y lisez n'est conservé sur l'appareil : sans réseau, chaque écran affiche la même page « pas de connexion ». C'est voulu — une page en cache montrerait une liste périmée, ou celle de la personne connectée avant vous sur le même appareil.

Pas de revue des fiches de la marketplace. N'importe quel compte peut publier une app dans la marketplace ; le badge Vérifiée, posé par l'exploitant de l'instance, est le seul signal de confiance. Une fiche non vérifiée le dit. Regardez où mène le bouton d'installation avant d'accorder quoi que ce soit.

Pas de limite de débit. Aucune aujourd'hui. C'est la description de l'implémentation actuelle, pas une garantie — ne construisez rien qui dépende de leur absence.

Pas d'email sortant. Ce déploiement n'en envoie aucun. Les invitations sont des jetons que quelqu'un transmet ; rien ne vérifie qu'une adresse appartient à la personne qui l'a tapée. Ce dernier point compte si vous raisonnez sur la sécurité des comptes : c'est précisément pour cela qu'une identité externe n'est jamais rattachée à un compte sur la foi d'une adresse email identique.

Pièges

Ce sont de vrais comportements, qui surprennent. Ils sont documentés plutôt que corrigés, parce que dans chaque cas l'alternative était pire.

Un select se trie alphabétiquement, pas dans l'ordre de ses options. Une vue « par priorité » rend critical, high, low, normal. Encodez l'ordre dans les valeurs s'il vous le faut. Détails.

L'ordre manuel est global à la liste, pas propre à chaque colonne du kanban. Déposer une carte en tête d'une colonne en fait aussi la première ligne de la vue tableau. Détails.

display se remplace en bloc. Enregistrer les colonnes d'un tableau sans renvoyer groupOrder efface l'ordre des colonnes du kanban.

config se remplace en bloc, lui aussi. Enregistrer les options d'une propriété sans renvoyer optionColors efface les couleurs. Pour ajouter ou repeindre une seule option, POST …/fields/{fieldId}/options fusionne au lieu de remplacer.

Les propriétés sont indexées par key, les vues référencent fieldId. Deux noms pour le même champ, employés à des endroits différents : les confondre vous vaut un 400 « propriété inconnue ». Détails.

Le type sous lequel un fichier est servi n'est pas celui sous lequel il a été envoyé. Un SVG revient en téléchargement application/octet-stream, avec image: false. Pourquoi.

icon: null retire l'icône ; omettre icon n'y touche pas. C'est la même distinction que dans le reste de PATCH, et elle mérite d'être connue avant de construire un client qui renvoie toute la carte à chaque frappe.

coverFileId est un identifiant de fichier, pas une URL, et seule une image passe. Envoyez d'abord le fichier (POST …/files), puis écrivez son identifiant. Un fichier d'un autre espace de travail, ou qui n'est pas servi comme image (un SVG, un PDF), vaut un 400. null — ou "" — retire la couverture, l'omettre n'y touche pas, et la retirer ne supprime pas le fichier. Fichiers.

Un burndown oublie ce que le journal a oublié ; la vélocité, non. Le burndown se relit dans le journal, qu'une instance purge par mois : un vieux sprint montre des jours inconnus (historyFrom). La vélocité lit ce que chaque sprint contient aujourd'hui — donc une carte rouverte plus tard réécrit la vélocité du sprint qui l'avait livrée. Sprints.

createdBy vient de l'appelant, jamais du corps de la requête. En envoyer un n'est pas une erreur — les clés inconnues sont ignorées, vous obtenez donc un 201 et un auteur qui est vous. Il vaut null pour les imports, les données d'amorçage et tout ce qui a été créé avant l'existence de la colonne.

Quand cette page se trompe

Elle est écrite à la main, et la référence de l'API est générée depuis le code. S'ils se contredisent, croyez la référence — et l'écart est un bug de cette page.

Sur cette page