Skip to main content
Les notes sont une extension JMAP d’OxiMail (urn:oximail:params:jmap:notes, ADR-144) : la place de ce qui n’est pas une tâche. La règle qui sépare les deux tient en une phrase : si ça a un état « fait », c’est une tâche ; sinon, c’est une note. Une note génère des tâches et garde le lien ; une tâche pointe vers la page dont elle vient. Le compte rendu d’appel, l’idée, les points vus en réunion se casaient dans la description d’une tâche, faute d’autre chose.

L’objet de capability

transcriptionAvailable est un fait d’exécution écrit dans la Session par instance : le binaire communautaire ne porte aucun moteur de transcription, et une extension peut en brancher un par le seam du serveur. maxBodyOctets (1 Mio) borne le corps riche d’une page.

Objets et méthodes

Une Note porte id, notebookId (exactement un, déplaçable), title (vide permis, le client montre la première ligne), body, bodyText, preview (les premiers caractères du texte, toujours servis), tags (libres, normalisées en minuscules), links, attachments, source (manual, dictation, voicemail, meet, un vocabulaire fermé qui dit d’où vient la page et ne change jamais le comportement sauf à la création), transcription et ses quatre champs de provenance, isPinned, isTrashed et trashedAt, createdBy (qui a un sens dans un bloc-notes partagé), createdAt, updatedAt. Le corps n’est servi que s’il est nommé. body est le document riche canonique que l’éditeur produit (ProseMirror), stocké sans perte ; bodyText est le texte brut que le serveur en dérive, ce que l’index et un assistant lisent, jamais écrit par un client. Les deux sont dans le vocabulaire projetable et hors de la projection par défaut, l’idiome bodyValues d’Email/get : une liste de deux cents pages ne doit pas porter deux cents documents. Demandez-les dans properties. Les liens pointent vers d’autres objets du workspace, {type, id} avec type parmi task, email, contact, event, file, note ; la forme est vérifiée à l’écriture et la cible n’est pas résolue, si bien qu’un lien mort s’affiche et ne casse jamais une page. Les pièces jointes sont des blobs (blobId, name, type), l’audio dicté parmi elles. Note/query filtre sur inNotebook, hasTag, linkedTo, text (répondu depuis l’index plein texte, qui porte le titre, le texte dérivé et chaque transcription de pièce jointe, jamais depuis un balayage LIKE), source, hasAttachment et isTrashed ; il trie sur updatedAt, createdAt, title et isPinned, pages épinglées d’abord puis les plus récemment modifiées par défaut. Une propriété qu’un client ne peut pas écrire (bodyText, preview, les champs de transcription, trashedAt, createdBy, les horodatages) est refusée nommément sur une création ou une modification, jamais abandonnée en silence ; Note déclare son vocabulaire d’écriture au répartiteur.

Le partage

Un bloc-notes se partage comme une liste de tâches ou un agenda, par la carte shareWith de la RFC 9670 (partage) et avec un vocabulaire propre à OxiMail, puisqu’une note n’a ni assigné, ni invitation, ni axe de confidentialité sur lesquels reporter les paliers de l’agenda. Trois bits, avec les deux implications qu’un humain supposerait, imposées : mayReadItems (lire les pages), mayWriteItems (créer, modifier, déplacer, mettre à la corbeille, restaurer et supprimer définitivement les pages, un rédacteur possède le cycle de vie de la page, et exige mayReadItems), mayAdmin (renommer, recolorer, modifier shareWith, détruire le bloc-notes, et exige mayWriteItems). Un bénéficiaire écrit par le compte shared:<propriétaire> et createdBy dit qui a écrit chaque page. Le premier partage d’un bloc-notes ouvre un compte dans la Session du bénéficiaire. Il fait donc changer sessionState (RFC 8620 §2), comme le partage d’une boîte aux lettres, d’un agenda, d’une liste de tâches ou d’un nœud du Drive : le client du bénéficiaire recharge la Session, et le compte partagé apparaît. Avant, le partage d’un bloc-notes changeait la liste des comptes sans changer l’état, et le client ne le voyait jamais.

La corbeille

La destruction est en deux temps (ADR-035). Note/set destroy sur une page vivante la met à la corbeille (la page répond Updated avec isTrashed: true) ; destroy sur une page à la corbeille la supprime pour de bon (Destroyed) ; Note/set update {isTrashed: true} met aussi à la corbeille, et {isTrashed: false} restaure. Le balayage des corbeilles détruit ce qui est à la corbeille depuis plus longtemps que l’horizon du locataire, sa surcharge notes.trash_retention_days ou à défaut [notes] trash_retention_days (30 par défaut, plancher 1 ; une surcharge à 0 ne purge rien, voir les horizons par locataire), par la même sortie que la corbeille du Drive, en respectant les séquestres et en annonçant ce qu’il a retiré aux clients synchronisés (stockage).

La dictée et la transcription

La dictée n’est pas une méthode : c’est un Note/set create avec source: "dictation" et une pièce jointe audio (type audio/*, refusé sinon). La page est créée immédiatement, transcription: "pending", et après validation l’audio est remis au moteur de transcription ; quand le travail rend compte, le corps, le texte brut et le transcript par pièce jointe (des segments datés {start, end, text}, pour qu’un client saute l’audio à un paragraphe et surligne le segment en lecture) sont remplis et la page répond done. Sans moteur, la page est acceptée quand même et répond transcription: "failed" avec un transcriptionError qui nomme la raison : honnête, jamais silencieux, jamais un refus de la page. Note/transcribe {accountId, id, blobId} est l’autre geste, ajouté ensuite : transcrire un enregistrement ajouté à une page qui a déjà du contenu, et relancer une transcription en échec. Un Note/set update ordinaire (réordonner les pièces, renommer une pièce jointe) ne lance jamais de travail par effet de bord ; le client demande. L’appel vérifie que la pièce est une pièce jointe audio de cette page, passe la page en pending en effaçant la transcription et la provenance de cette pièce (les autres gardent les leurs), et envoie une seule requête pour cette pièce seule en mode ajout : le texte s’ajoute au corps tel qu’il est quand le travail rend compte, fusionné par le magasin dans sa propre transaction, si bien qu’une modification validée entre-temps n’est jamais perdue. La méthode existe sur toute instance, jamais unknownMethod ; sans moteur elle échoue de la même façon honnête. Une page dit où sa transcription a eu lieu. Moteur local ou prestataire tiers sont deux choix légitimes de l’exploitant, mais ils ne disent pas la même chose de l’audio. Chaque pièce jointe audio porte la provenance de sa propre transcription, écrite par le travail sur la pièce qu’il a traitée, en cas de succès et d’échec (l’audio est parti quand même) : transcriptionProvider (un vocabulaire ouvert qui nomme une implémentation, local_whisper, openai_compat, …, affiché sans qu’on décide rien dessus), transcriptionEndpointHost (un hôte, jamais une URL qui pourrait porter une clé ; null pour un moteur local), et transcriptionResidencyTier, le champ sur lequel un client branche : local (l’audio n’a jamais quitté l’instance), ch, eu, non_eu. null veut dire inconnu, jamais local. Les trois champs de la page elle-même sont dérivés de ses pièces : deux enregistrements peuvent avoir été traités à des moments différents par des moteurs différents, et la page ne peut pas affirmer davantage que la moins souveraine d’entre elles. Une phrase qui sur-affirme la souveraineté est le défaut que cette conception interdit. Le moteur de transcription est un seam d’extension ; le binaire communautaire n’en livre aucun, ce que transcriptionAvailable: false dit exactement. Les règles de rigueur (identifiants inconnus dans notFound, invalidProperties plutôt qu’une coercition, stateMismatch sur ifInState) sont identiques à celles de la surface courrier.