Que fait Design by Story ?
Construisez un prototype de produit en un seul fichier HTML, visualisable sur plusieurs appareils, et décomposé par user story de la même manière qu'il l'est par appareil.
La barre supérieure comporte deux sélecteurs :
- Appareil : p. ex. Site web · Android · iPhone. Même produit, format différent.
- Story : "Toutes les stories" plus une entrée par user story. Choisir une story réinitialise les données partagées avec la seed de cette story, envoie chaque appareil vers l'écran de démarrage de la story et ouvre un panneau affichant la story, sa priorité, son statut, ses critères d'acceptation, son walkthrough et ses notes.
Les deux sélections sont conservées dans le hash de l'URL (#story=S2&device=iphone), de sorte qu'un lien vers une story sur un appareil donné peut être collé dans une revue ou un ticket.
Workflow
-
Recueillir les entrées (ne demander que ce qui manque, en un seul message) :
- Quel est le produit et qui l'utilise.
- Quels appareils afficher. Par défaut : Site web + Android + iPhone. Tablette si utilisation sur le terrain ou en kiosque.
- Les user stories, sous n'importe quelle forme (une liste, un export de backlog, des documents fonctionnels).
- Format des AC : Given/When/Then, ou règles simples ? Par défaut : Given/When/Then.
- Priorité (MoSCoW) et statut, s'ils existent. Sinon, utiliser
should/draftpar défaut. - Le modèle de données principal (enregistrement principal + champs clés), ou l'inférer à partir des stories.
Normaliser chaque story selon le format de story. Si l'utilisateur n'a pas de stories, en rédiger 3 à 6 à partir de la description du produit et les confirmer avant de construire.
-
Vérifier les stories par rapport à la liste de contrôle qualité. Signaler les problèmes en une courte liste. Ne pas réécrire silencieusement le texte de l'utilisateur.
-
Planifier les écrans par story. Pour chaque story, lister les écrans qu'elle touche sur chaque appareil et les données de seed dont elle a besoin. Une story ignore un appareil uniquement si le produit fonctionne vraiment ainsi (par ex. administration uniquement sur le site web). Sa liste
devicesl'indique, et le sélecteur désactive les autres appareils lorsqu'elle est sélectionnée. -
Copier
assets/shell.htmlvers le chemin choisi par l'utilisateur (par défautdocs/reference/prototype.html) et le remplir. Lirereferences/prototype-spec.mdpour les règles visuelles (cadres, tokens, typographie, cartes, pastilles, toasts, tiroirs, icônes, ton du copier). -
Construire des écrans, pas des stories. Les écrans sont partagés ; les stories sont des points d'entrée et des seeds vers ceux-ci. Ne jamais dupliquer un écran par story. Si une story a besoin d'une variante, la piloter à partir des données de seed.
-
Vérifier dans un navigateur. Utiliser un outil navigateur si disponible. Sinon, utiliser Chrome headless :
--dump-dom "file://…/prototype.html#story=S2&device=iphone"pour vérifier ce qui a été rendu,--screenshotpour le visualiser, et--enable-logging=stderrpour capturer les erreurs console. Vérifier que :- chaque story sur chaque appareil qu'elle liste s'ouvre sur le bon écran ;
- chaque AC peut être démontrée, et le walkthrough peut être parcouru en cliquant ;
- changer d'appareil en cours de story conserve l'état ;
- les contrôles d'une story n'apparaissent pas sous une autre story, et "Toutes les stories" fait fonctionner ensemble les contrôles de toutes les stories ;
- la console est propre.
-
Maintenir la synchronisation. Lorsqu'une story est ajoutée ou modifiée ultérieurement, mettre à jour son entrée
#story-text, son entréeBEHAVIOURet chaque appareil qu'elle liste dans la même édition.
Format de story
Une story vit à deux endroits dans le shell, tous deux indexés par son identifiant :
- son texte dans le bloc JSON
#story-text, affiché en lecture seule dans le panneau de story ; - son comportement dans
BEHAVIOUR, qui est uniquement du code.
<script type="application/json" id="story-text">
{
"S1": {
"title": "Enregistrer une recherche",
"story": "En tant que visiteur, je peux enregistrer une recherche, afin d'être informé des nouvelles correspondances sans avoir à rechercher à nouveau.",
"priority": "must",
"status": "draft",
"ac": [
{ "id": "S1-AC1", "text": "Given j'ai recherché \"table en chêne\", quand j'appuie sur Enregistrer la recherche, alors elle apparaît sous Enregistrées" },
{ "id": "S1-AC2", "text": "Given cette recherche est déjà enregistrée, quand je l'enregistre à nouveau, alors Enregistrées l'affiche toujours une fois" }
],
"acNext": 2,
"walkthrough": ["Rechercher \"table en chêne\"", "Appuyer sur Enregistrer la recherche", "Ouvrir Enregistrées"],
"outOfScope": ["Alertes e-mail"],
"assumptions": ["Les visiteurs doivent être connectés pour enregistrer"],
"questions": ["Y a-t-il une limite au nombre de recherches enregistrées ?"]
}
}
</script>
const BEHAVIOUR = {
S1: {
devices: ['web', 'android', 'iphone'], // à quels cadres cette story s'applique
start: { web: 'search', android: 'search', iphone: 'search' }, // écran par appareil
features: ['save-search'], // ce que cette story active
seed: () => ({ user: SIGNED_IN, saved: [] }), // données fraîches pour cette story
},
};
| Champ | Règles |
|---|---|
story | En tant que <persona>, je peux <action>, afin de <bénéfice>. Le bénéfice est ce que l'utilisateur gagne, pas une reformulation de l'action. |
priority | must · should · could · won't (MoSCoW). |
status | draft · in review · agreed. |
ac | Objets { id, text }. Les IDs sont <story>-AC<n>, jamais renumérotés, jamais réutilisés. Voir ci-dessous. |
acNext | Le numéro AC le plus élevé jamais émis pour cette story. Un nouvel AC prend acNext + 1, et acNext est mis à jour en conséquence. Le mettre à jour à chaque ajout d'AC. |
walkthrough | Le script de démonstration que l'examinateur parcourt en cliquant. Pas des critères d'acceptation ; rester court. |
outOfScope, assumptions, questions | Listes simples. Laisser vides plutôt que d'inventer du remplissage. Noter tout élément dont l'utilisateur n'est pas sûr comme une question. |
seed() renvoie un objet frais à chaque appel, de sorte que la re-sélection d'une story la redémarre toujours proprement. "Toutes les stories" utilise DEFAULT_SEED et l'écran d'accueil de chaque appareil.
Critères d'acceptation
- Un comportement testable par AC, dans le format choisi par l'utilisateur. Given/When/Then est une ligne :
Given <contexte>, quand <action>, alors <résultat observable>. - Copie exacte : quand un AC concerne un message, citer le message (
alors je vois "Rien d'ouvert pour le moment."), jamais "un message amical". - Résultats, pas de style : "il s'affiche comme Terminé", pas "une pastille verte apparaît". Un détail visuel n'est un AC que s'il est l'exigence.
- Testable sur un seul appareil. Ne pas écrire d'AC concernant d'autres appareils ("apparaît sur tous les appareils", "web uniquement", "les téléphones ne peuvent pas le modifier"). Les appareils couverts par une story sont dans
devices, et la synchronisation entre appareils est la responsabilité du backend. - Les IDs sont permanents.
S1-AC3conserve son ID lorsqu'il est édité, déplacé ou divisé en une story enfant, afin de pouvoir être tracé jusqu'aux tests et tickets. Les nouveaux ACs prennentacNext + 1. Les IDs supprimés ne sont jamais réattribués, et rien n'est renuméroté.
Liste de contrôle qualité
Vérifier chaque story avant de la construire, et à chaque modification :
- Le persona est-il un vrai rôle utilisateur, et le bénéfice est-il quelque chose qu'il dirait ?
- Apporte-t-elle de la valeur par elle-même (indépendante), et est-elle suffisamment petite pour être démontrée en moins d'une minute ? Sinon, la diviser.
- Chaque AC a-t-il un ID, un seul comportement et un résultat observable ?
- Les cas vides, d'erreur et de limite sont-ils couverts, ou listés dans
questions? - Les dépendances envers d'autres stories sont-elles visibles ? (Une clé de feature partagée, plus une mention dans
assumptions, par ex. "Construit sur S2".)
La fonctionnalité de la story est désactivée jusqu'à ce que la story soit sélectionnée
Chaque interaction qu'une story introduit reçoit une clé de feature, listée dans features de cette story. can('save-search') est vrai uniquement lorsqu'une story listant cette clé est sélectionnée.
- Rendu : encapsuler les contrôles de la story dans
can(...)afin qu'ils n'existent pas autrement (pas de bouton, pas de formulaire, pas d'élément de menu). Ne pas les afficher désactivés. Les examinateurs doivent voir uniquement ce que la story sélectionnée ajoute. - Gestionnaires : commencer chaque gestionnaire de story par
if (!can('save-search')) return;afin que rien ne se déclenche en dehors de la story, même à partir d'un DOM obsolète ou de la console. - "Toutes les stories" active toutes les features de toutes les stories à la fois : le produit complet tel qu'il est, sur
DEFAULT_SEED. Vérifier que les stories n'entrent pas en conflit (par ex. deux contrôles au même endroit). - Une story qui dépend de l'interaction d'une autre story liste les deux clés (par ex. "Filtrer par statut" liste
['filter', 'complete']afin que le fait de cocher une tâche puisse être montré comme quittant la liste filtrée). - Les données qu'une story se contente d'afficher (par ex. les initiales d'un assigné) ne sont pas protégées ; seule la capacité à les modifier l'est.
Modifier les stories
Le prototype affiche uniquement les stories ; il ne les édite ni ne les enregistre jamais. Toutes les modifications de stories passent par Claude, dans le chat :
- Nouvelle story : rédiger la story, les AC et les questions, exécuter la liste de contrôle qualité avec l'utilisateur, puis écrire l'entrée
#story-textet l'entréeBEHAVIOURet construire les écrans en une seule édition. Une story n'existe jamais uniquement sous forme de texte. Si c'était le cas, le shell l'omettrait du sélecteur et enregistrerait une erreur dans la console. - Story modifiée (formulation, AC, priorité, statut, notes) : éditer
#story-text. Si la modification altère ce que fait la story (nouvel AC, walkthrough différent), mettre à jour les écrans etBEHAVIOURen conséquence dans la même édition. - Retours de revue : lorsque l'utilisateur transmet les commentaires des examinateurs, transformer chacun en l'une des modifications ci-dessus, ou en une entrée dans
questionssi la question reste ouverte. - Toujours dire ce qui a été construit ou modifié, par story et ID d'AC.
Si l'utilisateur conserve les stories dans un outil de suivi (Jira ou similaire), l'outil est la copie maîtresse. Refléter sa formulation ici plutôt que de modifier les stories uniquement dans le prototype.
Diviser une story
Lorsque l'utilisateur demande de diviser une story, ou qu'une story a plus d'environ 5 AC, plus d'une clé de feature principale, ou des AC qui pourraient être livrés séparément, la diviser.
-
Choisir un modèle de division et dire lequel a été utilisé :
- étapes de workflow (créer → réviser → publier)
- opérations (ajouter / éditer / supprimer)
- d'abord le chemin heureux, puis les cas limites (vide, doublon, limite atteinte)
- par appareil ou rôle (capture téléphone vs. gestion site web)
- par variation de données (un élément → plusieurs, une devise → plusieurs)
- d'abord simple, puis les extras (tri simple → préférences de tri sauvegardées)
Chaque enfant doit toujours être un
En tant que… je peux… afin de…complet, avec sa propre valeur. -
Numéroter les enfants à partir du parent :
S3→S3.1,S3.2, … Supprimer le parent à la fois de#story-textet deBEHAVIOURet mettre les enfants à sa place. LereadHashdu shell envoie un ancien lien#story=S3versS3.1, afin que les liens partagés continuent de fonctionner. -
Partitionner, ne pas dupliquer :
- Critères d'acceptation. Chaque AC parent est déplacé vers exactement un enfant avec son ID d'origine (
S3-AC2resteS3-AC2à l'intérieur deS3.1). Si un AC chevauche deux enfants, le réécrire en deux nouveaux AC (S3.1-AC1,S3.2-AC1) et noter quel ID parent ils remplacent. - Clés de feature. Diviser une clé si son interaction est divisée (
filter→filter-status,filter-counts). Un enfant peut également lister la clé d'un frère lorsqu'il s'appuie dessus, comme dans la règle de dépendance ci-dessus. - Appareils. Chaque enfant liste uniquement les appareils qu'il touche vraiment.
- Seeds. La
seed()d'un enfant ultérieur part de l'état que les enfants précédents laisseraient (par ex. S3.2 seede certains éléments déjà enregistrés), afin que chaque enfant puisse être examiné indépendamment. - Notes. Donner à chaque enfant les éléments hors périmètre, les hypothèses et les questions qui s'y appliquent. Les enfants héritent de la priorité du parent sauf indication contraire de l'utilisateur.
- Critères d'acceptation. Chaque AC parent est déplacé vers exactement un enfant avec son ID d'origine (
-
Mettre à jour les écrans : re-protéger tout contrôle qui a été déplacé vers une clé différente. Ensuite, vérifier chaque enfant sur chaque appareil qu'il liste, et vérifier que l'identifiant du parent redirige.
-
Signaler la division sous forme d'un court tableau (ID enfant, story, priorité, appareils, ID AC) afin que l'utilisateur puisse vérifier que chaque AC parent a atterri quelque part. Répliquer la division dans son backlog ou ses documents fonctionnels s'il conserve les stories à cet endroit.
Règles
- Un seul fichier HTML, CSS/JS en ligne, pas d'étape de build, pas de framework, pas de backend. L'ouvrir directement depuis le disque.
- Toutes les données dans un seul
statepartagé en mémoire ; chaque appareil le lit et l'écrit. - Les fonctions de rendu prennent l'identifiant de l'appareil et redessinent à partir de
state(voir le shell). - Utiliser des données d'exemple inventées et neutres. Ne jamais copier de noms, marques ou contenus d'autres projets dans le prototype.
- Les cadres mobiles rendent le même balisage d'application ; les différences entre Android et iPhone sont du chrome (barre d'état, encoche/îlot, barre de gestes), pas des applications séparées, sauf si l'utilisateur demande une IU spécifique à la plateforme.