devmethod-ai 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/decision-architecture/SKILL.md +10 -0
- package/.agents/skills/decision-architecture/assets/ADR.md +6 -3
- package/.agents/skills/design-to-code/SKILL.md +7 -1
- package/.agents/skills/design-to-code/references/visual-creation.md +37 -0
- package/.agents/skills/project-foundation/SKILL.md +3 -1
- package/.agents/skills/project-foundation/assets/CADRAGE.md +11 -0
- package/.agents/skills/project-foundation/assets/EXISTANT.md +14 -0
- package/.agents/skills/project-foundation/assets/OPPORTUNITES.md +10 -0
- package/.agents/skills/project-foundation/assets/PROJECT_PROFILE.md +4 -2
- package/.agents/skills/project-foundation/assets/REGLES.md +6 -0
- package/.agents/skills/project-foundation/assets/START_HERE.md +2 -0
- package/.agents/skills/project-foundation/references/delivery-planning.md +11 -0
- package/.agents/skills/project-foundation/references/exploration.md +11 -0
- package/.agents/skills/project-foundation/references/mission-context.md +37 -0
- package/.agents/skills/project-foundation/references/operating-commands.md +26 -6
- package/.agents/skills/project-foundation/references/work-sizing.md +1 -1
- package/.agents/skills/scoped-delivery/SKILL.md +6 -0
- package/.agents/skills/scoped-delivery/assets/MISSION.md +18 -1
- package/.agents/skills/scoped-delivery/assets/PLAN.md +20 -0
- package/.agents/skills/scoped-delivery/assets/REPRISE.md +10 -0
- package/.agents/skills/scoped-delivery/assets/REVIEW.md +34 -0
- package/.agents/skills/scoped-delivery/assets/SLICE.md +5 -0
- package/.agents/skills/scoped-delivery/assets/TICKET.md +21 -0
- package/.agents/skills/scoped-delivery/references/review-workflow.md +29 -0
- package/COMPATIBILITY.md +1 -1
- package/README.md +46 -17
- package/START_HERE.md +2 -0
- package/dist/cli.js +122 -90
- package/dist/review-app.js +462 -0
- package/dist/review-browser.js +567 -0
- package/dist/review-cli.js +68 -0
- package/dist/review-model.js +101 -0
- package/dist/review-open.js +19 -0
- package/dist/review-ui.css +782 -0
- package/dist/review.js +13 -0
- package/docs/ADR-007-conversation-and-mission-ownership.md +13 -0
- package/docs/ADR-008-review-presentation.md +13 -0
- package/docs/MISSIONS.md +2 -0
- package/docs/RELEASE-0.1.0.md +14 -3
- package/docs/RELEASE-0.2.0.md +48 -0
- package/docs/RELEASE-0.3.0.md +24 -0
- package/docs/REVIEW-SOURCES.md +13 -0
- package/docs/REVIEW-VALIDATION.md +34 -0
- package/docs/REVIEWS.md +75 -0
- package/docs/ROADMAP.md +2 -0
- package/docs/VISUAL-CREATION-PROPOSAL.md +21 -0
- package/docs/VISUAL-WORKFLOW.md +75 -0
- package/docs/WORKFLOW-0.3-VALIDATION.md +32 -0
- package/docs/WORKFLOW-0.3.md +34 -0
- package/docs/images/devmethod-delivery.svg +10 -0
- package/docs/images/devmethod-flow.svg +13 -26
- package/docs/images/review-interface-desktop.jpg +0 -0
- package/docs/images/review-interface-mobile.jpg +0 -0
- package/docs/media/from-zero/README.md +17 -0
- package/docs/media/from-zero/devmethod-demo.fr.srt +59 -0
- package/docs/media/from-zero/scenes.json +50 -0
- package/docs/media/full-chain-4k/README.md +37 -0
- package/docs/media/full-chain-4k/devmethod-chaine-complete.fr.srt +435 -0
- package/docs/media/full-chain-4k/scenes.json +173 -0
- package/docs/media/full-chain-4k/transcripts.fr.md +315 -0
- package/docs/media/visual-chain/README.md +44 -0
- package/docs/media/visual-chain/assets/add-book-v1.png +0 -0
- package/docs/media/visual-chain/assets/completed-v1.png +0 -0
- package/docs/media/visual-chain/devmethod-du-besoin-au-produit.fr.srt +147 -0
- package/docs/media/visual-chain/execution.fr.md +30 -0
- package/docs/media/visual-chain/image-prompts.json +9 -0
- package/docs/media/visual-chain/reference-hashes.json +5 -0
- package/docs/media/visual-chain/scenes.json +232 -0
- package/docs/media/visual-chain/video-preview.jpg +0 -0
- package/docs/missions/visual-workflow.md +46 -0
- package/docs/missions/workflow-0.3-reviews/interface/REVIEW.md +109 -0
- package/docs/missions/workflow-0.3-reviews/interface/review.json +255 -0
- package/docs/missions/workflow-0.3.md +17 -0
- package/examples/clair-from-zero/AGENT-EVALUATION.md +15 -0
- package/examples/clair-from-zero/MISSION.md +41 -0
- package/examples/clair-from-zero/README.md +24 -0
- package/examples/clair-from-zero/app/app.mjs +69 -0
- package/examples/clair-from-zero/app/domain.mjs +30 -0
- package/examples/clair-from-zero/app/index.html +20 -0
- package/examples/clair-from-zero/app/storage.mjs +4 -0
- package/examples/clair-from-zero/app/styles.css +128 -0
- package/examples/clair-from-zero/browser-check.cjs +2 -0
- package/examples/clair-from-zero/tests/domain.test.mjs +37 -0
- package/examples/mission-dialogue/PROJECT_PROFILE.md +9 -0
- package/examples/mission-dialogue/architecture/decisions/001-storage.md +7 -0
- package/examples/mission-dialogue/docs/missions/first-save/PLAN.md +13 -0
- package/examples/mission-dialogue/docs/missions/first-save/REPRISE.md +6 -0
- package/examples/mission-dialogue/docs/missions/first-save/tickets/SAVE-1.md +14 -0
- package/examples/mission-dialogue/docs/missions/legacy-copy.md +9 -0
- package/examples/mission-dialogue/docs/produit/REGLES.md +5 -0
- package/examples/review/review.json +199 -0
- package/examples/visual-pilot/README.md +11 -0
- package/examples/visual-pilot/app/app.js +10 -0
- package/examples/visual-pilot/app/index.html +1 -0
- package/examples/visual-pilot/app/reference.png +0 -0
- package/examples/visual-pilot/app/style.css +3 -0
- package/examples/visual-pilot/browser-check.cjs +3 -0
- package/examples/visual-pilot/desktop-actual.png +0 -0
- package/examples/visual-pilot/directions-prompt.txt +1 -0
- package/examples/visual-pilot/directions-v1.png +0 -0
- package/examples/visual-pilot/editorial-mockup-prompt.txt +1 -0
- package/examples/visual-pilot/editorial-mockup-v1.png +0 -0
- package/examples/visual-pilot/mismatch-probe.png +0 -0
- package/examples/visual-pilot/mobile-actual.png +0 -0
- package/examples/visual-pilot/quick-filter/AGENT-RESULT.md +25 -0
- package/examples/visual-pilot/quick-filter/baseline.log +42 -0
- package/examples/visual-pilot/quick-filter/filter.mjs +3 -0
- package/examples/visual-pilot/quick-filter/filter.test.mjs +9 -0
- package/package.json +3 -3
- package/scripts/build-review.mjs +8 -0
- package/scripts/media/demo-actions.cjs +16 -0
- package/scripts/media/encode-demo.py +22 -0
- package/scripts/media/record-demo.cjs +30 -0
- package/scripts/media/visual-short/check.cjs +1 -0
- package/scripts/media/visual-short/encode.py +16 -0
- package/scripts/media/visual-short/record.cjs +4 -0
- package/scripts/media/visual-short/story.py +34 -0
- package/scripts/package-smoke.mjs +16 -3
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": 1,
|
|
3
|
+
"id": "REVIEW-UI-1",
|
|
4
|
+
"title": "Review de l’interface DevMethod",
|
|
5
|
+
"project": "DevMethod",
|
|
6
|
+
"mission": "workflow-0.3",
|
|
7
|
+
"tickets": [
|
|
8
|
+
{
|
|
9
|
+
"id": "PR-19",
|
|
10
|
+
"title": "Livraison du workflow et de la review",
|
|
11
|
+
"url": "https://github.com/montassarkhalloufi/DevMethod/pull/19"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"date": "2026-09-13",
|
|
15
|
+
"scope": [
|
|
16
|
+
"Format de review, rendu sécurisé, navigation clavier/mobile et exports du candidat en cours."
|
|
17
|
+
],
|
|
18
|
+
"exclusions": [
|
|
19
|
+
"Audit exhaustif WCAG avec lecteur d’écran",
|
|
20
|
+
"Exécution native du workflow conversationnel sur les trois hôtes"
|
|
21
|
+
],
|
|
22
|
+
"revision": {
|
|
23
|
+
"commit": "8d7cd3584c891c04d983f85279e14a2070ab621d + working changes",
|
|
24
|
+
"dirty": [
|
|
25
|
+
"src/review-app.ts",
|
|
26
|
+
"src/review-model.ts",
|
|
27
|
+
"src/review-cli.ts",
|
|
28
|
+
"src/review.ts",
|
|
29
|
+
"src/review-ui.css",
|
|
30
|
+
"src/cli.ts"
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"technologies": [
|
|
34
|
+
{
|
|
35
|
+
"name": "TypeScript",
|
|
36
|
+
"version": "5.9.3",
|
|
37
|
+
"detectedFrom": "package-lock.json"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"name": "Node.js",
|
|
41
|
+
"version": "24.18.0 local / >=22 package",
|
|
42
|
+
"detectedFrom": "node --version / package.json"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"sources": [
|
|
46
|
+
{
|
|
47
|
+
"id": "WAI-TABS",
|
|
48
|
+
"title": "Tabs Pattern",
|
|
49
|
+
"kind": "documentation",
|
|
50
|
+
"publisher": "W3C WAI",
|
|
51
|
+
"technology": "ARIA / DOM",
|
|
52
|
+
"version": "APG consulted 2026-09-13",
|
|
53
|
+
"url": "https://www.w3.org/WAI/ARIA/apg/patterns/tabs/",
|
|
54
|
+
"consultedAt": "2026-09-13",
|
|
55
|
+
"access": "consulted",
|
|
56
|
+
"usage": "Clavier, focus et association des onglets/panneaux.",
|
|
57
|
+
"compatibility": "Guide de conception ; ne certifie pas une conformité WCAG complète.",
|
|
58
|
+
"provenance": "Page du site officiel W3C consultée pendant cette session."
|
|
59
|
+
}
|
|
60
|
+
],
|
|
61
|
+
"checks": [
|
|
62
|
+
{
|
|
63
|
+
"id": "UI-KEYBOARD",
|
|
64
|
+
"title": "Clavier et retour mobile",
|
|
65
|
+
"domain": "Interface",
|
|
66
|
+
"kind": "manual",
|
|
67
|
+
"status": "passed",
|
|
68
|
+
"result": "Focus après correction et conservation des filtres observés.",
|
|
69
|
+
"reason": null,
|
|
70
|
+
"evidenceIds": [
|
|
71
|
+
"E-FOCUS-AFTER"
|
|
72
|
+
],
|
|
73
|
+
"revision": "8d7cd3584c891c04d983f85279e14a2070ab621d + working changes",
|
|
74
|
+
"targets": [
|
|
75
|
+
"finding-selection",
|
|
76
|
+
"tabs"
|
|
77
|
+
]
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": "UI-EXPORT",
|
|
81
|
+
"title": "Contenu de l’export HTML",
|
|
82
|
+
"domain": "Interface",
|
|
83
|
+
"kind": "manual",
|
|
84
|
+
"status": "passed",
|
|
85
|
+
"result": "Résultats, état de sélection et empreinte CSP vérifiés dans le dernier export Chrome.",
|
|
86
|
+
"reason": null,
|
|
87
|
+
"evidenceIds": [
|
|
88
|
+
"E-EXPORT"
|
|
89
|
+
],
|
|
90
|
+
"revision": "8d7cd3584c891c04d983f85279e14a2070ab621d + working changes",
|
|
91
|
+
"targets": [
|
|
92
|
+
"export"
|
|
93
|
+
]
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": "UI-REOPEN",
|
|
97
|
+
"title": "Réouverture file://",
|
|
98
|
+
"domain": "Interface",
|
|
99
|
+
"kind": "manual",
|
|
100
|
+
"status": "blocked",
|
|
101
|
+
"result": "Non vérifiée dans le navigateur automatisé.",
|
|
102
|
+
"reason": "La politique de l’outil interdit cette navigation ; aucun contournement effectué.",
|
|
103
|
+
"evidenceIds": [],
|
|
104
|
+
"revision": "8d7cd3584c891c04d983f85279e14a2070ab621d + working changes",
|
|
105
|
+
"targets": [
|
|
106
|
+
"export"
|
|
107
|
+
]
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"id": "SEC-REDACTION",
|
|
111
|
+
"title": "Masquage des formats courants",
|
|
112
|
+
"domain": "Confidentialité",
|
|
113
|
+
"kind": "automated",
|
|
114
|
+
"status": "passed",
|
|
115
|
+
"result": "Régression Bearer, variable préfixée et valeur entre guillemets réussie.",
|
|
116
|
+
"reason": null,
|
|
117
|
+
"evidenceIds": [
|
|
118
|
+
"E-REDACTION"
|
|
119
|
+
],
|
|
120
|
+
"revision": "fa84208 + redaction working changes",
|
|
121
|
+
"targets": [
|
|
122
|
+
"review-redaction"
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
],
|
|
126
|
+
"findings": [
|
|
127
|
+
{
|
|
128
|
+
"id": "FOCUS-01",
|
|
129
|
+
"title": "La sélection d’un constat perdait le focus clavier",
|
|
130
|
+
"domain": "Accessibilité",
|
|
131
|
+
"severity": "moderate",
|
|
132
|
+
"severityReason": "La navigation clavier quittait le contenu attendu et rendait la poursuite du parcours difficile.",
|
|
133
|
+
"confidence": "confirmed",
|
|
134
|
+
"resolution": "resolved",
|
|
135
|
+
"location": {
|
|
136
|
+
"path": "src/review-app.ts",
|
|
137
|
+
"line": null,
|
|
138
|
+
"component": "Sélection du constat et gestion de hashchange"
|
|
139
|
+
},
|
|
140
|
+
"trigger": "Activer un constat avec Enter quand le fragment de lien doit changer.",
|
|
141
|
+
"expected": "Le focus arrive sur le titre du détail et permet de poursuivre au clavier.",
|
|
142
|
+
"observed": "Avant correction, le rendu immédiat était suivi d’un deuxième rendu lors de hashchange ; le titre focalisé était remplacé.",
|
|
143
|
+
"impact": "L’utilisateur au clavier perdait son point de navigation.",
|
|
144
|
+
"reproduction": [
|
|
145
|
+
"Activer R-01 avec Enter depuis la liste.",
|
|
146
|
+
"Inspecter le focus après le changement du fragment."
|
|
147
|
+
],
|
|
148
|
+
"evidenceIds": [
|
|
149
|
+
"E-FOCUS-BEFORE"
|
|
150
|
+
],
|
|
151
|
+
"correction": "Mettre à jour le fragment avec history.pushState, rendre une seule fois, puis focaliser le titre. Restaurer aussi le focus après un changement d’onglet.",
|
|
152
|
+
"tradeoffs": "Écouter popstate séparément pour les navigations historiques.",
|
|
153
|
+
"sourceIds": [
|
|
154
|
+
"WAI-TABS"
|
|
155
|
+
],
|
|
156
|
+
"ticketIds": [
|
|
157
|
+
"PR-19"
|
|
158
|
+
],
|
|
159
|
+
"verification": "Enter conserve le focus sur detail-title ; ArrowRight cible Correction ; le retour mobile restaure la liste filtrée.",
|
|
160
|
+
"resolutionEvidenceIds": [
|
|
161
|
+
"E-FOCUS-AFTER"
|
|
162
|
+
],
|
|
163
|
+
"targets": [
|
|
164
|
+
"finding-selection",
|
|
165
|
+
"tabs"
|
|
166
|
+
]
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"id": "REDACT-01",
|
|
170
|
+
"title": "Certains formats courants d’identifiants étaient partiellement masqués",
|
|
171
|
+
"domain": "Confidentialité",
|
|
172
|
+
"severity": "major",
|
|
173
|
+
"severityReason": "Un rapport distribué pouvait conserver une valeur sensible dans un header standard ou un champ entre guillemets. Aucun identifiant réel n’a été exposé dans ce test.",
|
|
174
|
+
"confidence": "confirmed",
|
|
175
|
+
"resolution": "resolved",
|
|
176
|
+
"location": {
|
|
177
|
+
"path": "src/review-model.ts",
|
|
178
|
+
"line": null,
|
|
179
|
+
"component": "redactReviewText"
|
|
180
|
+
},
|
|
181
|
+
"trigger": "Importer une preuve contenant un header Bearer ou une valeur sensible avec espaces.",
|
|
182
|
+
"expected": "Masquer la valeur complète des formats courants pris en charge.",
|
|
183
|
+
"observed": "La première règle arrêtait le masquage au premier espace et ne reconnaissait pas certains préfixes de variables.",
|
|
184
|
+
"impact": "Risque de divulguer une valeur sensible dans une copie de rapport si la revue de confidentialité ne la détecte pas.",
|
|
185
|
+
"reproduction": [
|
|
186
|
+
"Utiliser exclusivement les cas synthétiques du test de régression du masquage."
|
|
187
|
+
],
|
|
188
|
+
"evidenceIds": [
|
|
189
|
+
"E-REDACTION"
|
|
190
|
+
],
|
|
191
|
+
"correction": "Reconnaître les schémas Bearer/Basic, les valeurs entre guillemets et les clés préfixées avant de produire la copie distribuée.",
|
|
192
|
+
"tradeoffs": "Le masquage reste heuristique ; la confidentialité des images et des formats inconnus exige une inspection avant partage.",
|
|
193
|
+
"sourceIds": [],
|
|
194
|
+
"ticketIds": [
|
|
195
|
+
"PR-19"
|
|
196
|
+
],
|
|
197
|
+
"verification": "Les valeurs synthétiques de ces trois formats sont absentes de sanitizedReview ; le document original reste inchangé.",
|
|
198
|
+
"resolutionEvidenceIds": [
|
|
199
|
+
"E-REDACTION"
|
|
200
|
+
],
|
|
201
|
+
"targets": [
|
|
202
|
+
"review-redaction"
|
|
203
|
+
]
|
|
204
|
+
}
|
|
205
|
+
],
|
|
206
|
+
"evidence": [
|
|
207
|
+
{
|
|
208
|
+
"id": "E-FOCUS-BEFORE",
|
|
209
|
+
"title": "Observation réelle avant correction",
|
|
210
|
+
"kind": "log",
|
|
211
|
+
"content": "Navigateur intégré, 2026-09-13 : activation clavier du constat R-01 puis lecture du focus. Résultat observé : document.activeElement.id vide après le second rendu déclenché par hashchange. Le lien direct devenait #finding=R-01. Observation manuelle de cette session, pas un journal de test CI.",
|
|
212
|
+
"url": null,
|
|
213
|
+
"image": null
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
"id": "E-FOCUS-AFTER",
|
|
217
|
+
"title": "Vérification réelle après correction",
|
|
218
|
+
"kind": "log",
|
|
219
|
+
"content": "Navigateur intégré, 2026-09-13 : Enter sur le constat => focus detail-title. ArrowRight sur Preuve => focus detail-content-correction. En mobile 390x844, retour => focus sur le constat choisi ; query=silencieux et confidence=confirmed conservés ; aucun débordement horizontal.",
|
|
220
|
+
"url": null,
|
|
221
|
+
"image": null
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
"id": "E-EXPORT",
|
|
225
|
+
"title": "Export Chrome inspecté sur disque",
|
|
226
|
+
"kind": "text",
|
|
227
|
+
"content": "Le dernier export Chrome possède review format 1, uiState.selected=R-01, filters.query=silencieux et section=correction. Les données ont été validées et l’empreinte SHA-256 du programme correspond à la CSP. Réouverture navigateur file:// non exécutée : politique de l’outil.",
|
|
228
|
+
"url": null,
|
|
229
|
+
"image": null
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"id": "E-REDACTION",
|
|
233
|
+
"title": "Régression automatisée du masquage des identifiants",
|
|
234
|
+
"kind": "log",
|
|
235
|
+
"content": "Cas synthétiques : header Authorization avec Bearer, variable NPM_TOKEN et mot de passe entre guillemets contenant des espaces. Avant correction, la règle masquait seulement une partie de certaines valeurs. Le test dédié vérifie maintenant que les trois valeurs synthétiques sont totalement absentes de la source distribuée. Exécuté dans npm test : réussite.",
|
|
236
|
+
"url": null,
|
|
237
|
+
"image": null
|
|
238
|
+
}
|
|
239
|
+
],
|
|
240
|
+
"limits": [
|
|
241
|
+
"La réouverture directe file:// est bloquée par la politique du navigateur de test ; elle n’est pas annoncée comme vérifiée.",
|
|
242
|
+
"Les tests de format ne prouvent ni la vérité des preuves fournies ni le comportement d’un agent de review.",
|
|
243
|
+
"Le mode d’ouverture final reste à choisir avant publication."
|
|
244
|
+
],
|
|
245
|
+
"policy": {
|
|
246
|
+
"blockingSeverities": [
|
|
247
|
+
"critical",
|
|
248
|
+
"major",
|
|
249
|
+
"moderate"
|
|
250
|
+
],
|
|
251
|
+
"requireAllChecks": true,
|
|
252
|
+
"rationale": "Corriger les défauts confirmés affectant le parcours et vérifier les contrôles de livraison avant publication."
|
|
253
|
+
},
|
|
254
|
+
"summary": "Le défaut de focus observé a été corrigé et revérifié. La validation de livraison reste incomplète, avec ses limites explicites."
|
|
255
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Workflow and review delivery — 0.3.0
|
|
2
|
+
|
|
3
|
+
Owner: current implementation session. Authorized scope: improve explore, architecture, plan, mission ownership and review; deliver a functional browser review interface, package, PR, merge after checks, GitHub release and npm publication. Preserve approved design behavior, existing project conventions and user files. The user supplied a validated visual direction for the review screen; its example findings are not real evidence.
|
|
4
|
+
|
|
5
|
+
This repository already uses single-file mission records, so retain that convention for this delivery. The user added the review scope before publication; PR #19 remains the same delivery. Existing checks for the earlier commit are historical, not validation of the added interface.
|
|
6
|
+
|
|
7
|
+
## Executable scope and ownership
|
|
8
|
+
|
|
9
|
+
- Workflow guidance and mission templates: implemented; evidence and migration details in [workflow validation](../WORKFLOW-0.3-VALIDATION.md) and [migration](../WORKFLOW-0.3.md).
|
|
10
|
+
- Review record, derived report, browser interface and CLI: active. Acceptance: validated versioned records; consistent counters/filters; finding/check separation; evidence and official-source provenance; safe rendering; list/detail and direct links; export/reopen; explicit empty/invalid/partial/historical states; responsive keyboard use; distribution independent of the source checkout.
|
|
11
|
+
- Delivery: [PR #19](https://github.com/montassarkhalloufi/DevMethod/pull/19). CI/package proofs belong in the PR and release record. The actual review results are owned by [review.json](workflow-0.3-reviews/interface/review.json), with a generated report; do not duplicate finding status here.
|
|
12
|
+
|
|
13
|
+
Exclusions: whole-project dashboard, remote hosting, automatic discovery/installation/execution of third-party skills, automatic code review execution, arbitrary filesystem browsing, destructive migration, changes to the approved design workflow.
|
|
14
|
+
|
|
15
|
+
Current architecture question: static self-contained HTML versus loopback server; recommended static report under the dependency-free offline contract, presented to the user before dependent implementation. Preserve their forthcoming choice/delegation in the relevant ADR.
|
|
16
|
+
|
|
17
|
+
Next action: verify the final delegated offline-opening integration, merge and publish under current authorization. The user explicitly delegated the opening-mode choice on 2026-09-13; ADR 008 records the selected standalone HTML and optional OS opening command. Shared model, report/export, CLI presentation and browser UI are implemented. The file:// browser reopening limitation remains documented; do not bypass the browser tool policy or claim that check passed.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Relevé de l’essai Clair
|
|
2
|
+
|
|
3
|
+
Date : 2026-09-13. Dossier initial : kit fraîchement installé, aucune application. Source du produit : brief transmis par l’agent parent, dont la sélection détaillée des écrans est déléguée. Le relevé est une observation de cette exécution, pas une comparaison de méthodes.
|
|
4
|
+
|
|
5
|
+
Les lectures initiales et décisions ont précédé le code. Exploration/cadrage/design/architecture/plan/readiness ont été groupés en une tranche standard CLAIR-1, puis implémentation, tests et revue locale. Le seul enregistrement courant est `docs/missions/clair.md`. Ce rapport est un instantané ; aucun document par étape. Aucun imagegen, réseau, dépendance ou sous-agent utilisé. Le kit est resté intact ; le profil adopté est dans la mission pour respecter cette contrainte.
|
|
6
|
+
|
|
7
|
+
Fichiers lus : START_HERE.md, AGENTS.foundation.md, PROJECT_PROFILE.md, ENGINEERING_POLICY.template.md ; SKILL.md de project-foundation, design-to-code, scoped-delivery et decision-architecture ; références work-sizing, operating-commands, mission-context, visual-creation, ux-contract, verification-and-cost ; modèle scoped-delivery/assets/MISSION.md ; kit-manifest.json. CONTRIBUTING.md et AGENTS.md étaient absents. Les autres modules ont été découverts par inventaire, sans être chargés.
|
|
8
|
+
|
|
9
|
+
Fichiers créés : app/index.html, app/styles.css, app/app.mjs, app/domain.mjs, app/storage.mjs, tests/domain.test.mjs, docs/missions/clair.md et ce rapport. Livré : page française responsive ivoire/encre/rouille, ajout titre/note, validation, toggle, trois filtres, suppression/annulation, localStorage versionné et validé, alerte d’échec persistante/réessai, réinitialisation explicite après erreur de lecture, départ vide et ajout de trois tâches de démonstration marquées fictives.
|
|
10
|
+
|
|
11
|
+
Exécuté : `node --test tests/*.test.mjs` — 6 tests passés, 0 échec ; `node --check app/app.mjs` — passé. Vérification des hashes SHA-256 des fichiers du manifeste installé — aucune différence. Revue locale du code et des limites ; aucune installation ni ressource distante.
|
|
12
|
+
|
|
13
|
+
Limites : cet agent n’a pas lancé de navigateur, capturé d’écran, testé l’interaction réelle ou vérifié le rendu mobile. Le parent en est chargé indépendamment. Les vérifications DOM/CSS effectuées sont des inspections du source. Aucune preuve d’intégration, de déploiement ou d’usage production. L’application conserve les priorités jusqu’à suppression ; aucune remise à zéro quotidienne ni synchronisation entre onglets. L’annulation porte sur la dernière suppression et disparaît après rechargement. La démo peut être ajoutée plusieurs fois volontairement.
|
|
14
|
+
|
|
15
|
+
Lancer : `python3 -m http.server 8766 --bind 127.0.0.1 --directory app`. Prochaine commande méthode : `$project-foundation verify CLAIR-1`.
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Clair — mission canonique
|
|
2
|
+
|
|
3
|
+
2026-09-13, livraison standard, propriétaire : agent de réalisation.
|
|
4
|
+
|
|
5
|
+
## Sources et profil adopté
|
|
6
|
+
Le brief transmis par le parent fait autorité : application française locale, HTML/CSS/JS sans dépendances ni comptes, direction ivoire/encre/rouille et sérif ; choix détaillés délégués. Les fichiers du kit restent intacts, notamment PROJECT_PROFILE.md : ce paragraphe porte le profil adopté conformément à la contrainte de préservation. CONTRIBUTING.md et AGENTS.md sont absents. ENGINEERING_POLICY.template.md est un modèle ; le JavaScript demandé prend priorité sur sa recommandation TypeScript.
|
|
7
|
+
|
|
8
|
+
Stack : modules JavaScript natifs, CSS, HTML ; Node pour les tests purs ; Python pour le serveur local. Commandes : `node --test tests/*.test.mjs` ; `python3 -m http.server 8766 --bind 127.0.0.1 --directory app`. Aucun déploiement, réseau, installation ou génération d’images autorisé. Données : titres, notes et état d’achèvement stockés sous clair.tasks.v1 dans localStorage, conservés jusqu’à suppression ou effacement du navigateur ; aucune synchronisation ni télémétrie.
|
|
9
|
+
|
|
10
|
+
## Framing, design et architecture — décisions avant code
|
|
11
|
+
Valeur : choisir et suivre les priorités du jour sans configuration. Direction existante explicite, choix d’écran délégué ; aucune image ou maquette séparée nécessaire. Prototype HTML final choisi comme support. Grand titre éditorial, date, compteur, liste principale et formulaire latéral ; mobile en une colonne avec formulaire avant la liste. Palette #f7f3eb, #292923, #97472f ; Georgia système et sans-sérif système. États : vide, filtres vides, tâches actives/terminées, validation, suppression/annulation, stockage indisponible/corrompu.
|
|
12
|
+
|
|
13
|
+
Choix structurel dans le cadre délégué : module pur pour validation/transitions/schéma, adaptateur localStorage, contrôleur DOM. Une page monolithique serait plus courte mais rendrait les invariants difficiles à tester isolément. Aucun framework utile au périmètre. Les titres/notes ne deviennent jamais du HTML. Échec d’écriture : garder la session en mémoire et afficher une alerte persistante avec réessai ; erreur de lecture : ne pas écraser les données sans action explicite de réinitialisation. Suppression réversible de la dernière tâche, sans expiration temporelle. Pas de réinitialisation automatique à minuit.
|
|
14
|
+
|
|
15
|
+
## Plan et readiness (avant implémentation)
|
|
16
|
+
Une tranche CLAIR-1 couvre AC1 ajout titre/note et validation ; AC2 toggle/filtres ; AC3 suppression/annulation ; AC4 persistance/schema/erreurs ; AC5 vide + démo explicitement fictive ; AC6 responsive/clavier/focus. Contrats prêts, dépendances résolues. Tests prévus sur invariants et stockage ; vérification navigateur déléguée au parent et non revendiquée par cet agent. Limites de titre 160 caractères et note 500, données versionnées et validées à la lecture. Fin de périmètre après réalisation locale et tests ; pas de fonctionnalités hors brief.
|
|
17
|
+
|
|
18
|
+
## Evidence et état
|
|
19
|
+
État courant après vérification parent : CLAIR-1 réalisé et vérifié dans Chrome desktop/mobile. Les étapes explorations/cadrage/design/architecture/plan/readiness ont été groupées, pas de document par étape.
|
|
20
|
+
|
|
21
|
+
## Résultat local et evidence — 2026-09-13
|
|
22
|
+
CLAIR-1 réalisé dans `app/`. État lors du retour de l’agent : implémentation locale terminée, acceptation navigateur alors en attente. Aucun commit, PR ou déploiement.
|
|
23
|
+
|
|
24
|
+
| Critère | Preuve exécutée | Résultat |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| AC1 | `node --test tests/*.test.mjs`, cas validation | passé : blanc, trim, bornes titre/note |
|
|
27
|
+
| AC2 | même commande, toggle/filtres | passé : immutabilité et ordre |
|
|
28
|
+
| AC3 | même commande, suppression/annulation | passé : position, ajout intermédiaire, absence et doublon |
|
|
29
|
+
| AC4 | même commande, schéma/adapter | passé : round-trip Unicode, JSON invalide, version, types, IDs dupliqués, exceptions lecture/écriture |
|
|
30
|
+
| AC5 | revue du contrôleur et HTML | démarrage vide sans données ; bouton de démo explicitement fictif, notes marquées ; navigateur non exécuté |
|
|
31
|
+
| AC6 | revue CSS/DOM | labels, focus visible, annonces, règles 700/1000 px et réduction de mouvement présentes ; comportement/rendu navigateur non vérifiés |
|
|
32
|
+
|
|
33
|
+
Commande `node --check app/app.mjs` passée. Six tests, zéro échec. Revue locale de ses propres fichiers : contenu utilisateur inséré via textContent, stockage traité au bord, aucune ressource distante, aucun secret, actions couvertes dans le contrôleur. Cette inspection ne démontre pas l'accessibilité complète. Vérification SHA-256 des fichiers installés contre kit-manifest.json : aucun changement. Aucun test navigateur réalisé par cet agent, aucune comparaison pixel ou audit automatique revendiqué.
|
|
34
|
+
|
|
35
|
+
Prochaine action exacte : parent, exécuter la vérification desktop/mobile et des parcours de CLAIR-1 sur le serveur local. Commande méthode recommandée : `$project-foundation verify CLAIR-1`.
|
|
36
|
+
|
|
37
|
+
## Vérification indépendante du parent
|
|
38
|
+
|
|
39
|
+
Interactions Chrome vérifiées : vide, validation, ajout/note, toggle/filtres, rechargement, suppression/annulation, démo, saisie clavier, absence de débordement à 390px, échec de stockage avec conservation en mémoire et alerte. Captures desktop/mobile inspectées. Les animations sont désactivées pour les captures afin de ne pas photographier une barre de progression en transition. Aucun audit complet d’accessibilité.
|
|
40
|
+
|
|
41
|
+
Constat méthode : l’agent avait conservé une ancienne ligne « implémentation en cours » avant son résultat final. Le parent l’a réconciliée ici ; ce besoin de correction empêche de présenter la tenue du statut comme entièrement autonome. Le rapport agent est un instantané historique. Prochaine action : aucune implémentation restante dans ce périmètre ; intégration et vidéo suivies dans le dépôt DevMethod.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Clair — an actual from-zero DevMethod pilot
|
|
2
|
+
|
|
3
|
+
Clair is a fictional French personal-priorities app created by a separate Codex subagent in a new directory containing only the installed DevMethod kit. The parent provided a bounded brief and delegated the detailed editorial visual choices. No application code or previous implementation was supplied. This is a same-host subagent test, not a fresh standalone Codex/Claude session or a comparative benchmark.
|
|
4
|
+
|
|
5
|
+
## Run
|
|
6
|
+
|
|
7
|
+
From this directory:
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
python3 -m http.server 8766 --bind 127.0.0.1 --directory app
|
|
11
|
+
node --test tests/*.test.mjs
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Open http://127.0.0.1:8766. Stop the server with Ctrl-C. No npm dependencies, backend, external fonts, account or provider key. Browser storage uses `clair.tasks.v1`. The application starts empty; its demo button explicitly adds fictional tasks. Delete/undo covers the last deletion and does not survive reload. There is no cross-tab synchronization or daily reset.
|
|
15
|
+
|
|
16
|
+
## What was observed
|
|
17
|
+
|
|
18
|
+
The implementing agent grouped framing, design, architecture, planning and readiness in one mission. It created five app files, a test file, one mission and the requested evaluation snapshot; all installed kit files retained their hashes. Six logic/storage tests passed. The parent then exercised the actual Chrome UI: blank validation, add/note, completion/filters, persistence, delete/undo, demo, keyboard submission, 390px overflow and write-failure recovery.
|
|
19
|
+
|
|
20
|
+
[Agent report](AGENT-EVALUATION.md) is a historical observation. [Mission](MISSION.md) includes parent checks and disclosed limitations. A stale status sentence left by the implementing agent was reconciled by the parent; this was not perfect autonomous bookkeeping. The design was delegated, so this test does not repeat the user-choice/image steps of the separate [Lisière pilot](../visual-pilot/README.md).
|
|
21
|
+
|
|
22
|
+
Browser check from the DevMethod repository root: `node examples/clair-from-zero/browser-check.cjs`, with Playwright and Chrome available. Set PLAYWRIGHT_MODULE to an installed Playwright module path when needed. It writes captures under docs/media/from-zero/captures and uses a temporary browser profile.
|
|
23
|
+
|
|
24
|
+
[French video and transcript](../../docs/media/from-zero/README.md) show this app's actual interactions and separately identify the Lisière visual pilot. Neither demonstration establishes general code quality, production readiness, lower costs or superiority over BMAD.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {createTask,toggleTask,removeTask,restoreTask,filterTasks} from './domain.mjs';
|
|
2
|
+
import {readTasks,writeTasks} from './storage.mjs';
|
|
3
|
+
const $ = selector => document.querySelector(selector);
|
|
4
|
+
let tasks = [], filter = 'all', removed = null, readBlocked = false;
|
|
5
|
+
const announce = message => { $('#announcement').textContent = message; };
|
|
6
|
+
function showStorageError(message, blocked = false) {
|
|
7
|
+
$('#storage-alert').hidden = false; $('#storage-message').textContent = message;
|
|
8
|
+
$('#retry-storage').hidden = blocked; $('#reset-storage').hidden = !blocked;
|
|
9
|
+
}
|
|
10
|
+
try { tasks = readTasks(window.localStorage); }
|
|
11
|
+
catch { readBlocked = true; showStorageError('Les données enregistrées ne peuvent pas être lues. Vos changements restent uniquement en mémoire. Pour protéger les données existantes, la sauvegarde est suspendue. Vous pouvez les effacer explicitement pour repartir avec cette liste.', true); }
|
|
12
|
+
function save() {
|
|
13
|
+
if (readBlocked) return;
|
|
14
|
+
try { writeTasks(window.localStorage, tasks); $('#storage-alert').hidden = true; }
|
|
15
|
+
catch { showStorageError('La sauvegarde a échoué. Vos changements restent uniquement en mémoire et peuvent être perdus à la fermeture ou au rechargement. Libérez de l’espace ou autorisez le stockage, puis réessayez.'); }
|
|
16
|
+
}
|
|
17
|
+
function commit(message) { save(); render(); announce(message); }
|
|
18
|
+
function setFilter(value) { filter = value; render(); }
|
|
19
|
+
function render() {
|
|
20
|
+
const done = tasks.filter(task => task.done).length;
|
|
21
|
+
$('#total-count').textContent = String(tasks.length).padStart(2,'0');
|
|
22
|
+
$('#all-count').textContent = tasks.length; $('#active-count').textContent = tasks.length-done; $('#done-count').textContent = done;
|
|
23
|
+
$('#progress-label').textContent = tasks.length ? `${done} sur ${tasks.length} terminée${tasks.length > 1 ? 's' : ''}` : 'Un nouveau départ';
|
|
24
|
+
$('#progress-bar').style.width = `${tasks.length ? done/tasks.length*100 : 0}%`;
|
|
25
|
+
document.querySelectorAll('[data-filter]').forEach(button => button.setAttribute('aria-pressed', String(button.dataset.filter === filter)));
|
|
26
|
+
const visible = filterTasks(tasks, filter), list = $('#task-list'); list.replaceChildren();
|
|
27
|
+
for (const task of visible) {
|
|
28
|
+
const row = document.createElement('li'); row.className = `task${task.done?' done':''}`; row.dataset.id = task.id;
|
|
29
|
+
const check = document.createElement('button'); check.type = 'button'; check.className = 'task-check'; check.setAttribute('role','checkbox'); check.setAttribute('aria-checked', String(task.done)); check.setAttribute('aria-label', `${task.done?'Marquer à faire':'Terminer'} : ${task.title}`); check.textContent = task.done ? '✓' : '';
|
|
30
|
+
check.addEventListener('click', () => {
|
|
31
|
+
const position = visible.findIndex(item => item.id === task.id);
|
|
32
|
+
tasks = toggleTask(tasks, task.id); commit(task.done ? 'Priorité marquée à faire.' : 'Priorité terminée.');
|
|
33
|
+
const newRows = [...list.children], same = newRows.find(item => item.dataset.id === task.id);
|
|
34
|
+
(same?.querySelector('.task-check') || newRows[Math.min(position,newRows.length-1)]?.querySelector('.task-check') || $('#empty-add')).focus();
|
|
35
|
+
});
|
|
36
|
+
const content = document.createElement('div'), title = document.createElement('h3'); title.textContent = task.title; content.append(title);
|
|
37
|
+
if (task.note) { const note = document.createElement('p'); note.textContent = task.note; content.append(note); }
|
|
38
|
+
if (task.done) { const state = document.createElement('span'); state.className = 'task-state'; state.textContent = 'TERMINÉE'; content.append(state); }
|
|
39
|
+
const del = document.createElement('button'); del.type = 'button'; del.className = 'task-delete'; del.setAttribute('aria-label', `Supprimer : ${task.title}`);
|
|
40
|
+
del.innerHTML = '<svg viewBox="0 0 20 22" aria-hidden="true"><path d="M3 6h14M7 6V3h6v3M5 6l1 14h8l1-14M8 9v8M12 9v8"/></svg>';
|
|
41
|
+
del.addEventListener('click', () => { const result = removeTask(tasks, task.id); tasks = result.tasks; removed = result.removed; commit('Priorité supprimée. Vous pouvez annuler.'); $('#undo-button').focus(); });
|
|
42
|
+
row.append(check,content,del); list.append(row);
|
|
43
|
+
}
|
|
44
|
+
$('#empty-state').hidden = visible.length > 0;
|
|
45
|
+
if (!tasks.length) { $('#empty-title').textContent = 'Tout commence par une chose.'; $('#empty-copy').textContent = 'Qu’aimeriez-vous faire avancer aujourd’hui ? Ajoutez votre première priorité, même petite.'; $('#empty-add').textContent = 'Écrire ma première priorité ↗'; }
|
|
46
|
+
else if (filter === 'done') { $('#empty-title').textContent = 'Chaque chose en son temps.'; $('#empty-copy').textContent = 'Vos priorités terminées apparaîtront ici. Un petit pas suffit pour commencer.'; $('#empty-add').textContent = 'Voir les priorités à faire ↗'; }
|
|
47
|
+
else { $('#empty-title').textContent = 'De la place pour souffler.'; $('#empty-copy').textContent = 'Toutes vos priorités sont terminées. Savourez ce que vous avez fait aujourd’hui.'; $('#empty-add').textContent = 'Ajouter une nouvelle priorité ↗'; }
|
|
48
|
+
$('#undo-bar').hidden = !removed;
|
|
49
|
+
}
|
|
50
|
+
$('#task-form').addEventListener('submit', event => {
|
|
51
|
+
event.preventDefault();
|
|
52
|
+
try {
|
|
53
|
+
const task = createTask($('#task-title').value, $('#task-note').value, crypto.randomUUID());
|
|
54
|
+
tasks = [...tasks,task]; filter = 'all'; $('#task-form').reset(); $('#form-error').hidden = true; $('#task-title').removeAttribute('aria-invalid'); commit('Priorité ajoutée.'); $('#task-title').focus();
|
|
55
|
+
} catch (error) { $('#form-error').textContent = error.message; $('#form-error').hidden = false; $('#task-title').setAttribute('aria-invalid','true'); $('#task-title').focus(); }
|
|
56
|
+
});
|
|
57
|
+
$('#task-title').addEventListener('input', () => { if ($('#task-title').value.trim()) { $('#form-error').hidden = true; $('#task-title').removeAttribute('aria-invalid'); } });
|
|
58
|
+
document.querySelectorAll('[data-filter]').forEach(button => button.addEventListener('click', () => { setFilter(button.dataset.filter); announce(`${filterTasks(tasks,filter).length} priorité(s) affichée(s).`); }));
|
|
59
|
+
$('#empty-add').addEventListener('click', () => { if (tasks.length && filter === 'done') { setFilter('active'); $('[data-filter="active"]').focus(); } else { $('#task-title').focus(); $('#task-title').scrollIntoView({block:'center',behavior:'auto'}); } });
|
|
60
|
+
$('#undo-button').addEventListener('click', () => { const id = removed?.task.id; tasks = restoreTask(tasks,removed); removed = null; filter = 'all'; commit('Suppression annulée.'); [...$('#task-list').children].find(row => row.dataset.id === id)?.querySelector('.task-check').focus(); });
|
|
61
|
+
$('#dismiss-undo').addEventListener('click', () => { removed = null; render(); $('#priorities').focus(); });
|
|
62
|
+
$('#demo-button').addEventListener('click', () => {
|
|
63
|
+
const examples = [['Lire quelques pages','Démo fictive · Un chapitre, un thé et le téléphone de côté.'],['Faire une promenade','Démo fictive · Vingt minutes sans itinéraire.'],['Écrire à une personne chère','Démo fictive · Juste quelques mots pour prendre des nouvelles.']];
|
|
64
|
+
tasks = [...tasks,...examples.map(([title,note]) => createTask(title,note,crypto.randomUUID()))]; filter = 'all'; commit('Trois priorités fictives ajoutées à la liste.');
|
|
65
|
+
});
|
|
66
|
+
$('#retry-storage').addEventListener('click', () => { save(); if ($('#storage-alert').hidden) { announce('Liste sauvegardée dans ce navigateur.'); $('#task-title').focus(); } });
|
|
67
|
+
$('#reset-storage').addEventListener('click', () => { readBlocked = false; save(); if ($('#storage-alert').hidden) { announce('Les anciennes données ont été remplacées par la liste actuelle.'); $('#task-title').focus(); } });
|
|
68
|
+
$('#today').textContent = new Intl.DateTimeFormat('fr-FR',{weekday:'long',day:'numeric',month:'long'}).format(new Date()).toLocaleUpperCase('fr-FR');
|
|
69
|
+
render();
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export const TITLE_LIMIT = 160;
|
|
2
|
+
export const NOTE_LIMIT = 500;
|
|
3
|
+
export function createTask(title, note, id) {
|
|
4
|
+
if (typeof title !== 'string' || !title.trim()) throw new Error('Donnez un titre à votre priorité.');
|
|
5
|
+
if (title.trim().length > TITLE_LIMIT) throw new Error('Le titre est limité à 160 caractères.');
|
|
6
|
+
if (typeof note !== 'string' || note.trim().length > NOTE_LIMIT) throw new Error('La note est limitée à 500 caractères.');
|
|
7
|
+
return { id, title: title.trim(), note: note.trim(), done: false };
|
|
8
|
+
}
|
|
9
|
+
export function toggleTask(tasks, id) { return tasks.map(task => task.id === id ? {...task, done: !task.done} : task); }
|
|
10
|
+
export function removeTask(tasks, id) {
|
|
11
|
+
const index = tasks.findIndex(task => task.id === id);
|
|
12
|
+
return { tasks: tasks.filter(task => task.id !== id), removed: index < 0 ? null : { task: tasks[index], index } };
|
|
13
|
+
}
|
|
14
|
+
export function restoreTask(tasks, removed) {
|
|
15
|
+
if (!removed || tasks.some(task => task.id === removed.task.id)) return tasks;
|
|
16
|
+
const result = [...tasks]; result.splice(Math.min(removed.index, result.length), 0, removed.task); return result;
|
|
17
|
+
}
|
|
18
|
+
export function filterTasks(tasks, filter) { return tasks.filter(task => filter === 'done' ? task.done : filter === 'active' ? !task.done : true); }
|
|
19
|
+
export function decodeTasks(raw) {
|
|
20
|
+
if (raw === null) return [];
|
|
21
|
+
const data = JSON.parse(raw);
|
|
22
|
+
if (!data || data.version !== 1 || !Array.isArray(data.tasks)) throw new Error('Format de données inconnu.');
|
|
23
|
+
const ids = new Set();
|
|
24
|
+
for (const task of data.tasks) {
|
|
25
|
+
if (!task || typeof task.id !== 'string' || !task.id || ids.has(task.id) || typeof task.title !== 'string' || !task.title.trim() || task.title.length > TITLE_LIMIT || typeof task.note !== 'string' || task.note.length > NOTE_LIMIT || typeof task.done !== 'boolean') throw new Error('Données enregistrées illisibles.');
|
|
26
|
+
ids.add(task.id);
|
|
27
|
+
}
|
|
28
|
+
return data.tasks.map(({id, title, note, done}) => ({id, title, note, done}));
|
|
29
|
+
}
|
|
30
|
+
export function encodeTasks(tasks) { return JSON.stringify({version: 1, tasks}); }
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="fr">
|
|
3
|
+
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><meta name="theme-color" content="#f7f3eb"><title>Clair — Place à l’essentiel</title><link rel="stylesheet" href="styles.css"><script type="module" src="app.mjs"></script></head>
|
|
4
|
+
<body>
|
|
5
|
+
<a class="skip" href="#priorities">Aller aux priorités</a>
|
|
6
|
+
<div class="page">
|
|
7
|
+
<header class="masthead"><a class="brand" href="./" aria-label="Clair, accueil">clair<span class="brand-dot">✳</span></a><span class="masthead-note">UN PEU MOINS. UN PEU MIEUX.</span><span class="edition">VOTRE ESPACE PERSONNEL</span></header>
|
|
8
|
+
<main>
|
|
9
|
+
<section class="hero" aria-labelledby="hero-title"><div><p class="eyebrow" id="today">LE FIL DU JOUR</p><h1 id="hero-title">Faire place<br>à <em>l’essentiel.</em></h1><p class="intro">Une journée, quelques priorités.<br>Gardez de la place pour ce qui compte vraiment.</p></div><div class="hero-aside" aria-hidden="true"><div class="sun"><i></i><i></i><i></i><i></i></div><p>Moins de bruit.<br>Plus de présence.</p><span>À VOTRE RYTHME</span></div></section>
|
|
10
|
+
<div id="storage-alert" class="storage-alert" role="alert" hidden><p id="storage-message"></p><button type="button" id="retry-storage">Réessayer la sauvegarde</button><button type="button" id="reset-storage" hidden>Effacer les données illisibles</button></div>
|
|
11
|
+
<div class="workspace">
|
|
12
|
+
<section class="priorities" id="priorities" aria-labelledby="list-heading" tabindex="-1"><div class="section-heading"><h2 id="list-heading">Vos priorités</h2><span class="count" id="total-count">00</span></div><div class="list-toolbar"><div class="filters" role="group" aria-label="Filtrer les priorités"><button type="button" data-filter="all" aria-pressed="true">Toutes <span id="all-count">0</span></button><button type="button" data-filter="active" aria-pressed="false">À faire <span id="active-count">0</span></button><button type="button" data-filter="done" aria-pressed="false">Terminées <span id="done-count">0</span></button></div><span id="progress-label" class="progress-label">Un nouveau départ</span></div><div class="progress-track" aria-hidden="true"><div id="progress-bar"></div></div><ul id="task-list" aria-label="Liste des priorités"></ul><div id="empty-state" class="empty-state"><div class="empty-mark" aria-hidden="true">✳</div><h3 id="empty-title">Tout commence par une chose.</h3><p id="empty-copy">Qu’aimeriez-vous faire avancer aujourd’hui ?<br>Ajoutez votre première priorité, même petite.</p><button type="button" id="empty-add" class="text-button">Écrire ma première priorité <span aria-hidden="true">↗</span></button></div><div class="list-foot"><span id="list-note">Une petite liste. Une intention claire.</span><button type="button" id="demo-button">Charger une démo fictive <span aria-hidden="true">↗</span></button></div></section>
|
|
13
|
+
<aside class="composer" aria-labelledby="form-heading"><p class="eyebrow">UNE CHOSE À LA FOIS</p><h2 id="form-heading">Qu’est-ce qui<br>compte <em>aujourd’hui ?</em></h2><form id="task-form" novalidate><label for="task-title">Votre priorité <span aria-hidden="true">*</span></label><input id="task-title" name="title" type="text" maxlength="160" placeholder="Par exemple, prendre le temps de lire" required aria-describedby="form-error" autocomplete="off"><label for="task-note">Une note <span class="optional">— facultatif</span></label><textarea id="task-note" name="note" maxlength="500" rows="3" placeholder="Un détail, une idée, un premier pas…"></textarea><p id="form-error" class="form-error" role="alert" hidden></p><button class="add-button" type="submit">Ajouter une priorité <span aria-hidden="true">+</span></button></form><p class="form-tip">Pas besoin de tout prévoir.<br>Commencez par ce qui vous tient à cœur.</p></aside>
|
|
14
|
+
</div>
|
|
15
|
+
</main>
|
|
16
|
+
<footer><span class="footer-brand">clair</span><p>Vos priorités restent dans ce navigateur.<br>Aucun compte. Aucune synchronisation.</p><span class="footer-note">CHAQUE PETIT PAS COMPTE.</span></footer>
|
|
17
|
+
</div>
|
|
18
|
+
<div id="undo-bar" class="undo-bar" role="status" hidden><span>Priorité supprimée.</span><button type="button" id="undo-button">Annuler</button><button type="button" id="dismiss-undo" aria-label="Fermer la notification de suppression">×</button></div><div class="sr-only" id="announcement" role="status" aria-live="polite"></div>
|
|
19
|
+
<noscript><p class="noscript">Activez JavaScript pour utiliser Clair. Aucune donnée n’est envoyée à un serveur.</p></noscript>
|
|
20
|
+
</body></html>
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { decodeTasks, encodeTasks } from './domain.mjs';
|
|
2
|
+
export const STORAGE_KEY = 'clair.tasks.v1';
|
|
3
|
+
export function readTasks(storage) { return decodeTasks(storage.getItem(STORAGE_KEY)); }
|
|
4
|
+
export function writeTasks(storage, tasks) { storage.setItem(STORAGE_KEY, encodeTasks(tasks)); }
|