feat(example): une app d'exemple, écrite comme un consommateur

Le harnais e2e parlait à un sac de méthodes posé sur `window.__sdk`. Il prouvait
que les fonctions s'exécutaient, jamais qu'on pouvait écrire une application avec
— et cet écart a livré un vrai défaut : l'inbox d'un document était verte en test
et inutilisable en vrai, parce que le harnais faisait traverser une adresse d'une
identité à l'autre par une variable, ce qu'aucune application ne peut faire.

`examples/notebook` est une application minimale en DOM natif, qui résout
`@ng-eventually/client` comme un consommateur externe (workspace, dépendance
déclarée, aucun import privilégié). Elle ne peut faire que ce qu'une application
peut faire.

Elle s'est déjà payée deux fois pendant son écriture :

- `UnionSubject.subject` et `.graph` étaient typés `string` alors que ce sont
  toujours des références de document. Un consommateur devait donc caster ce
  qu'il venait de lire avant de le repasser — un cast à cet endroit précis
  rouvre la confusion que les types template literal existent pour fermer.
- l'écran d'accès normalisait ce que l'utilisateur SAISIT mais pas ce que l'URL
  porte, si bien qu'un lien `?ng-id=@Erin` ouvrait un espace différent de celui
  de la même personne tapant `erin`. Une seule normalisation désormais, celle
  du registre.

Le domaine est volontairement mince — des notes — mais suffit à exercer le
placement par scope, la possession de caps, le partage dirigé, les inbox par
document et la lecture réactive.

170 tests unitaires, typecheck vert sur la lib, l'exemple et le harnais.
This commit is contained in:
Sylvain Duchesne
2026-08-05 18:25:47 +02:00
parent 66a40fbb89
commit d35e735c8b
12 changed files with 814 additions and 19 deletions
+12 -3
View File
@@ -57,10 +57,19 @@ void sparqlUpdate;
/** One subject read from a doc, with its properties (predicate → values). */
export interface UnionSubject {
/** The subject IRI (`?s`) — in the polyfill, the doc's own NURI. */
subject: string;
/**
* The subject IRI (`?s`) — in the polyfill, the doc's own NURI.
*
* Typed `Nuri`, not `string`: both fields are always document references here (the
* read is anchored per document and the subject is pinned to the anchor), and typing
* them loosely forced a consumer to cast whatever it had just read before it could
* pass it back — `shareNote(note.doc)`, `leaveMessage(note.doc)`. A cast at that
* boundary re-opens exactly the confusion the template literal types exist to close.
* Found by writing the example application (`examples/notebook`).
*/
subject: Nuri;
/** The graph (doc NURI) the subject was read from. */
graph: string;
graph: Nuri;
/** predicate IRI → the list of object values (literals or IRIs) for it. */
props: Record<string, string[]>;
}