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,128 @@
|
|
|
1
|
+
:root{--paper:#f7f3eb;--ink:#292923;--muted:#726e63;--rust:#97472f;--line:#d9d3c7;--panel:#eee9df;--serif:Georgia,'Times New Roman',serif;--sans:Arial,Helvetica,sans-serif}
|
|
2
|
+
*{box-sizing:border-box}
|
|
3
|
+
body{margin:0;background:var(--paper);color:var(--ink);font-family:var(--sans);font-size:14px}
|
|
4
|
+
button,input,textarea{font:inherit}
|
|
5
|
+
button,a,input,textarea{-webkit-tap-highlight-color:transparent}
|
|
6
|
+
button{cursor:pointer}
|
|
7
|
+
button{color:inherit}
|
|
8
|
+
button:focus-visible,a:focus-visible,input:focus-visible,textarea:focus-visible,[tabindex]:focus-visible{outline:3px solid var(--rust);outline-offset:5px}
|
|
9
|
+
button:hover{filter:brightness(.86)}
|
|
10
|
+
[hidden]{display:none!important}
|
|
11
|
+
.page{max-width:1480px;padding:0 64px;margin:auto}
|
|
12
|
+
.masthead{height:100px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--line);gap:24px}
|
|
13
|
+
.brand{font-family:var(--serif);font-size:44px;letter-spacing:-3px;color:var(--ink);text-decoration:none}
|
|
14
|
+
.brand-dot{color:var(--rust);font-size:28px;display:inline-block;vertical-align:top;margin:5px 0 0 7px}
|
|
15
|
+
.masthead-note,.edition,.eyebrow,.hero-aside>span,.footer-note{font-size:10px;letter-spacing:1.8px;font-weight:600}
|
|
16
|
+
.edition{color:var(--muted)}
|
|
17
|
+
.hero{display:flex;align-items:center;justify-content:space-between;padding:57px 0 50px}
|
|
18
|
+
.eyebrow{color:var(--rust);margin:0 0 23px}
|
|
19
|
+
.hero h1{font:normal clamp(58px,5.7vw,82px)/1.04 var(--serif);letter-spacing:-3.3px;margin:0}
|
|
20
|
+
.hero h1 em{font-weight:normal;color:var(--rust)}
|
|
21
|
+
.intro{font-size:15px;line-height:1.7;color:var(--muted);margin:23px 0 0}
|
|
22
|
+
.hero-aside{width:28%;text-align:center;padding-top:8px}
|
|
23
|
+
.hero-aside p{font:italic 24px/1.4 var(--serif);margin:18px 0;color:var(--muted)}
|
|
24
|
+
.hero-aside>span{font-size:9px;color:var(--muted)}
|
|
25
|
+
.sun{position:relative;width:90px;height:90px;margin:0 auto}
|
|
26
|
+
.sun i{position:absolute;top:44px;left:0;width:90px;height:1px;background:var(--rust)}
|
|
27
|
+
.sun i:nth-child(2){transform:rotate(45deg)}
|
|
28
|
+
.sun i:nth-child(3){transform:rotate(90deg)}
|
|
29
|
+
.sun i:nth-child(4){transform:rotate(135deg)}
|
|
30
|
+
.sun:after{content:'';position:absolute;inset:30px;border:1px solid var(--rust);border-radius:50%;background:var(--paper)}
|
|
31
|
+
.workspace{display:grid;grid-template-columns:minmax(0,1.8fr) minmax(290px,1fr);gap:48px;border-top:1px solid var(--ink);padding-top:32px;padding-bottom:55px}
|
|
32
|
+
.section-heading{display:flex;justify-content:space-between;align-items:center;margin-bottom:25px}
|
|
33
|
+
.section-heading h2{font:normal 30px var(--serif);letter-spacing:-.6px;margin:0}
|
|
34
|
+
.count{font:normal 27px var(--serif);color:var(--muted)}
|
|
35
|
+
.list-toolbar{display:flex;justify-content:space-between;align-items:center;gap:16px}
|
|
36
|
+
.filters{display:flex;gap:19px}
|
|
37
|
+
.filters button{font-size:12px;border:0;background:transparent;padding:9px 0;color:var(--muted);border-bottom:2px solid transparent;white-space:nowrap}
|
|
38
|
+
.filters button[aria-pressed=true]{color:var(--rust);border-bottom-color:var(--rust);font-weight:bold}
|
|
39
|
+
.filters span{font-size:10px;margin-left:3px}
|
|
40
|
+
.progress-label{font-size:10px;color:var(--muted);white-space:nowrap}
|
|
41
|
+
.progress-track{height:2px;background:var(--line);margin:10px 0 0}
|
|
42
|
+
.progress-track div{height:100%;background:var(--rust);transition:width .2s ease}
|
|
43
|
+
.empty-state{min-height:285px;display:flex;align-items:center;justify-content:center;flex-direction:column;text-align:center;padding:32px 10px}
|
|
44
|
+
.empty-mark{font-size:43px;color:var(--rust);margin:0 0 15px}
|
|
45
|
+
.empty-state h3{font:normal 25px var(--serif);margin:0 0 12px;letter-spacing:-.3px}
|
|
46
|
+
.empty-state p{line-height:1.7;color:var(--muted);font-size:13px;margin:0}
|
|
47
|
+
.text-button{border:0;background:none;color:var(--rust);font-size:12px;border-bottom:1px solid var(--rust);padding:0 0 5px;margin-top:22px}
|
|
48
|
+
.text-button span{margin-left:14px}
|
|
49
|
+
.list-foot{display:flex;justify-content:space-between;gap:15px;border-top:1px solid var(--line);padding-top:19px;font-size:10px;color:var(--muted)}
|
|
50
|
+
.list-foot button{border:0;padding:0 0 2px;background:transparent;color:var(--rust);font-size:10px;border-bottom:1px solid var(--rust)}
|
|
51
|
+
.composer{background:var(--panel);padding:29px 29px 23px;align-self:start}
|
|
52
|
+
.composer .eyebrow{font-size:9px;margin-bottom:16px}
|
|
53
|
+
.composer h2{font:normal 29px/1.15 var(--serif);letter-spacing:-.7px;margin:0 0 27px}
|
|
54
|
+
.composer h2 em{color:var(--rust)}
|
|
55
|
+
label{display:block;font-size:11px;font-weight:bold;margin:0 0 10px}
|
|
56
|
+
label>span:not(.optional){color:var(--rust)}
|
|
57
|
+
.optional{font-weight:normal;color:var(--muted)}
|
|
58
|
+
input,textarea{width:100%;border:1px solid #c9c1b2;border-radius:0;color:var(--ink);background:#faf7f1;padding:13px 12px;font-size:12px;line-height:1.5}
|
|
59
|
+
input{margin-bottom:20px}
|
|
60
|
+
textarea{resize:vertical;min-height:91px}
|
|
61
|
+
input::placeholder,textarea::placeholder{color:#817b6f}
|
|
62
|
+
input[aria-invalid=true]{border-color:var(--rust)}
|
|
63
|
+
.add-button{margin-top:17px;display:flex;align-items:center;justify-content:space-between;background:var(--rust);color:white;border:0;padding:14px 17px;width:100%;font-size:12px}
|
|
64
|
+
.add-button span{font-size:21px;line-height:1}
|
|
65
|
+
.form-tip{font-size:11px;line-height:1.6;color:var(--muted);margin:20px 0 0}
|
|
66
|
+
.form-error{color:#8a2c20;font-size:12px;line-height:1.5;margin:12px 0 0}
|
|
67
|
+
#task-list{list-style:none;padding:0;margin:0}
|
|
68
|
+
.task{display:grid;grid-template-columns:24px minmax(0,1fr) 30px;align-items:start;gap:14px;border-bottom:1px solid var(--line);padding:23px 0}
|
|
69
|
+
.task:last-child{border-bottom:0}
|
|
70
|
+
.task-check{width:22px;height:22px;padding:0;border:1px solid #9c9586;background:transparent;margin-top:2px;display:grid;place-items:center}
|
|
71
|
+
.task.done .task-check{background:var(--rust);border-color:var(--rust);color:white}
|
|
72
|
+
.task h3{font:normal 21px/1.3 var(--serif);margin:0;overflow-wrap:anywhere}
|
|
73
|
+
.task p{margin:8px 0 0;color:var(--muted);font-size:12px;line-height:1.6;white-space:pre-wrap;overflow-wrap:anywhere}
|
|
74
|
+
.task.done h3{text-decoration:line-through;color:var(--muted)}
|
|
75
|
+
.task-delete{background:none;border:0;padding:3px;color:var(--muted);width:30px;height:30px}
|
|
76
|
+
.task-delete svg{width:16px;height:18px;fill:none;stroke:currentColor;stroke-width:1.4}
|
|
77
|
+
.task-state{font-size:9px;color:var(--rust);letter-spacing:1px;display:block;margin-top:9px}
|
|
78
|
+
.undo-bar{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);display:flex;align-items:center;gap:25px;max-width:calc(100% - 32px);width:max-content;background:var(--ink);color:var(--paper);padding:15px 20px;box-shadow:0 8px 25px #29292325;font-size:12px}
|
|
79
|
+
.undo-bar button{background:none;color:var(--paper);border:0;padding:5px;text-decoration:underline}
|
|
80
|
+
.undo-bar #dismiss-undo{text-decoration:none;font-size:22px;padding:0 3px}
|
|
81
|
+
.storage-alert{border:1px solid var(--rust);padding:18px;margin-bottom:24px;color:var(--rust);font-size:13px;line-height:1.5}
|
|
82
|
+
.storage-alert p{margin:0 0 12px}
|
|
83
|
+
.storage-alert button{background:none;border:1px solid var(--rust);color:var(--rust);padding:8px 12px}
|
|
84
|
+
footer{border-top:1px solid var(--line);display:flex;align-items:center;gap:24px;padding:25px 0 30px;color:var(--muted)}
|
|
85
|
+
.footer-brand{font:normal 27px var(--serif);letter-spacing:-1.5px}
|
|
86
|
+
footer p{font-size:10px;line-height:1.6;margin:0}
|
|
87
|
+
.footer-note{margin-left:auto;font-size:9px}
|
|
88
|
+
.sr-only{position:absolute;width:1px;height:1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap}
|
|
89
|
+
.skip{position:fixed;top:-100px;left:20px;background:var(--ink);color:white;padding:12px;z-index:10}
|
|
90
|
+
.skip:focus{top:12px}
|
|
91
|
+
.noscript{padding:20px;color:var(--rust)}
|
|
92
|
+
@media(min-width:1500px){.hero{padding-top:70px;padding-bottom:65px}
|
|
93
|
+
}
|
|
94
|
+
@media(max-width:1000px){.page{padding:0 35px}
|
|
95
|
+
.workspace{gap:28px;grid-template-columns:minmax(0,1.4fr) minmax(275px,1fr)}
|
|
96
|
+
.progress-label{display:none}
|
|
97
|
+
.composer{padding:25px 22px}
|
|
98
|
+
.masthead-note{display:none}
|
|
99
|
+
}
|
|
100
|
+
@media(max-width:700px){.page{padding:0 22px}
|
|
101
|
+
.masthead{height:80px}
|
|
102
|
+
.brand{font-size:39px}
|
|
103
|
+
.edition{font-size:8px;letter-spacing:1px}
|
|
104
|
+
.hero{padding:35px 0}
|
|
105
|
+
.hero h1{font-size:55px;letter-spacing:-2.5px}
|
|
106
|
+
.hero .eyebrow{font-size:9px;margin-bottom:19px}
|
|
107
|
+
.intro{font-size:13px;line-height:1.7;margin-top:20px}
|
|
108
|
+
.hero-aside{display:none}
|
|
109
|
+
.workspace{display:flex;flex-direction:column;gap:32px;padding-top:24px;padding-bottom:35px}
|
|
110
|
+
.composer{order:-1;width:100%;padding:24px}
|
|
111
|
+
.composer h2{font-size:27px}
|
|
112
|
+
.composer h2 br{display:none}
|
|
113
|
+
.composer .eyebrow{margin-bottom:10px}
|
|
114
|
+
.composer h2{margin-bottom:24px}
|
|
115
|
+
.form-tip{display:none}
|
|
116
|
+
.section-heading{margin-bottom:16px}
|
|
117
|
+
.section-heading h2{font-size:28px}
|
|
118
|
+
.empty-state{min-height:280px}
|
|
119
|
+
.empty-state h3{font-size:23px}
|
|
120
|
+
.list-foot{font-size:9px;line-height:1.7}
|
|
121
|
+
.list-foot button{font-size:10px;align-self:start}
|
|
122
|
+
.footer-note{display:none}
|
|
123
|
+
footer{gap:20px;padding-bottom:90px}
|
|
124
|
+
.task h3{font-size:20px}
|
|
125
|
+
.undo-bar{gap:13px;font-size:11px;padding:12px 15px}
|
|
126
|
+
}
|
|
127
|
+
@media(prefers-reduced-motion:reduce){*{transition:none!important;scroll-behavior:auto!important}
|
|
128
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const {chromium}=require(process.env.PLAYWRIGHT_MODULE || 'playwright');const assert=require('node:assert/strict');const fs=require('node:fs');
|
|
2
|
+
(async()=>{const browser=await chromium.launch({channel:'chrome',headless:true});const context=await browser.newContext({viewport:{width:1280,height:900},reducedMotion:'reduce'});const p=await context.newPage();const errors=[];p.on('pageerror',e=>errors.push(e.message));await p.goto('http://127.0.0.1:8766');assert.equal(await p.locator('.task').count(),0);await p.locator('.add-button').click();assert.equal(await p.locator('#form-error').isVisible(),true);await p.locator('#task-title').fill('Vérifier le parcours');await p.locator('#task-note').fill('Une note de démonstration.');await p.locator('.add-button').click();assert.equal(await p.locator('.task').count(),1);await p.locator('.task-check').first().click();await p.locator('[data-filter="active"]').click();assert.equal(await p.locator('.task').count(),0);await p.locator('[data-filter="done"]').click();assert.equal(await p.locator('.task').count(),1);await p.reload();await p.locator('[data-filter="all"]').click();assert.equal(await p.locator('.task').count(),1);await p.locator('.task-delete').first().click();assert.equal(await p.locator('.task').count(),0);await p.locator('#undo-button').click();assert.equal(await p.locator('.task').count(),1);await p.locator('#demo-button').click();assert.ok(await p.locator('.task').count()>=1);fs.mkdirSync('docs/media/from-zero/captures',{recursive:true});await p.screenshot({path:'docs/media/from-zero/captures/clair-desktop.png',fullPage:true});await p.setViewportSize({width:390,height:844});await p.screenshot({path:'docs/media/from-zero/captures/clair-mobile.png',fullPage:true});assert.equal(await p.evaluate(()=>document.documentElement.scrollWidth<=innerWidth),true);await p.locator('#task-title').focus();await p.keyboard.type('Ajout clavier');await p.keyboard.press('Tab');await p.keyboard.press('Tab');assert.equal(await p.locator('.add-button').evaluate(e=>e===document.activeElement),true);await p.keyboard.press('Enter');assert.ok(await p.locator('.task').filter({hasText:'Ajout clavier'}).count());const blocked=await browser.newContext();await blocked.addInitScript(()=>{Storage.prototype.setItem=()=>{throw new DOMException('Blocked','QuotaExceededError');};});const b=await blocked.newPage();await b.goto('http://127.0.0.1:8766');await b.locator('#task-title').fill('Conserver ma saisie');await b.locator('.add-button').click();assert.equal(await b.locator('#storage-alert').isVisible(),true);assert.equal(await b.locator('.task').count(),1);assert.deepEqual(errors,[]);console.log('PASS: empty state, blank validation, add/note, complete/filter, reload persistence, delete/undo, demo, keyboard submit, mobile overflow, storage failure retains task and alerts.');await browser.close();})();
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import test from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import {createTask,toggleTask,removeTask,restoreTask,filterTasks,decodeTasks,encodeTasks} from '../app/domain.mjs';
|
|
4
|
+
import {readTasks,writeTasks,STORAGE_KEY} from '../app/storage.mjs';
|
|
5
|
+
const a = createTask('Lire','Une note','a'), b = createTask('Marcher','','b');
|
|
6
|
+
test('creation trims inputs; rejects blank and over-limit values', () => {
|
|
7
|
+
assert.deepEqual(createTask(' Titre ',' Note ','id'),{id:'id',title:'Titre',note:'Note',done:false});
|
|
8
|
+
for (const title of ['',' ','\n\t','x'.repeat(161)]) assert.throws(() => createTask(title,'','id'));
|
|
9
|
+
assert.throws(() => createTask('Titre','x'.repeat(501),'id'));
|
|
10
|
+
assert.equal(createTask('x'.repeat(160),'x'.repeat(500),'id').note.length,500);
|
|
11
|
+
});
|
|
12
|
+
test('toggle is immutable and filters preserve order', () => {
|
|
13
|
+
const initial = [a,b], changed = toggleTask(initial,'a');
|
|
14
|
+
assert.equal(a.done,false); assert.equal(changed[0].done,true);
|
|
15
|
+
assert.deepEqual(filterTasks(changed,'done'),[changed[0]]); assert.deepEqual(filterTasks(changed,'active'),[b]);
|
|
16
|
+
assert.deepEqual(filterTasks(changed,'all'),changed); assert.deepEqual(toggleTask(changed,'a'),initial);
|
|
17
|
+
});
|
|
18
|
+
test('delete and undo restore original position even after adding another task', () => {
|
|
19
|
+
const initial=[a,b], {tasks,removed}=removeTask(initial,'a'), c=createTask('Écrire','','c');
|
|
20
|
+
assert.deepEqual(tasks,[b]); assert.deepEqual(restoreTask([...tasks,c],removed),[a,b,c]);
|
|
21
|
+
assert.deepEqual(restoreTask(initial,removed),initial); assert.equal(removeTask(initial,'missing').removed,null);
|
|
22
|
+
assert.deepEqual(restoreTask([],null),[]);
|
|
23
|
+
});
|
|
24
|
+
test('empty persistence and Unicode round-trip; HTML remains literal text', () => {
|
|
25
|
+
assert.deepEqual(decodeTasks(null),[]);
|
|
26
|
+
const task=createTask('<img src=x onerror=alert(1)>','Été\n日本語','x');
|
|
27
|
+
assert.deepEqual(decodeTasks(encodeTasks([task])),[task]);
|
|
28
|
+
});
|
|
29
|
+
test('untrusted storage schema rejects invalid data and duplicate IDs', () => {
|
|
30
|
+
for(const raw of ['not JSON','null','{}','[]',JSON.stringify({version:2,tasks:[]}),JSON.stringify({version:1,tasks:[a,a]}),JSON.stringify({version:1,tasks:[{...a,done:'true'}]}),JSON.stringify({version:1,tasks:[{...a,title:' '}]}),JSON.stringify({version:1,tasks:[{...a,note:null}]})]) assert.throws(()=>decodeTasks(raw));
|
|
31
|
+
});
|
|
32
|
+
test('storage adapter uses versioned key; failures propagate rather than fake success', () => {
|
|
33
|
+
const map=new Map(), storage={getItem:key=>map.get(key)??null,setItem:(key,value)=>map.set(key,value)};
|
|
34
|
+
writeTasks(storage,[a,b]); assert.ok(map.has(STORAGE_KEY)); assert.deepEqual(readTasks(storage),[a,b]);
|
|
35
|
+
assert.throws(()=>writeTasks({setItem(){throw new Error('quota');}},[a]),/quota/);
|
|
36
|
+
assert.throws(()=>readTasks({getItem(){throw new Error('blocked');}}),/blocked/);
|
|
37
|
+
});
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Reading list — fictional workflow fixture
|
|
2
|
+
|
|
3
|
+
Outcome: let a reader save a title and retrieve it later. This is an illustrative authored fixture, not a delivered product or a real conversation transcript.
|
|
4
|
+
Cross-cutting constraints: one maintainer, modest operating budget, hosting undecided.
|
|
5
|
+
Active mission: [first save](docs/missions/first-save/PLAN.md).
|
|
6
|
+
Product rules: [rules](docs/produit/REGLES.md).
|
|
7
|
+
Open architecture: [storage](architecture/decisions/001-storage.md).
|
|
8
|
+
Research: not executed; no market or price claims are made.
|
|
9
|
+
Read the mission, then the active ticket and only its linked rules/decision. A legacy record is also [illustrated](docs/missions/legacy-copy.md); it is a separate mission, not another tracker of first-save.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Storage decision
|
|
2
|
+
|
|
3
|
+
Status: PROPOSED. No choice or delegation has been received in this fixture.
|
|
4
|
+
Constraints: single maintainer; hosting and multi-device requirement unresolved.
|
|
5
|
+
Conversation brief to present: local storage offers a small single-device slice; server persistence can support multiple devices but adds hosting and operation. Recommendation depends on the multi-device requirement and data-loss tolerance. No current price claim.
|
|
6
|
+
Ask the user to challenge, offer an alternative, choose or delegate. Evaluate any alternative against the same durability, operations, cost and access criteria.
|
|
7
|
+
Dependent architecture stays open. Revisit after the requirement or budget changes. Record the actual choice/delegation and rationale here before detailing dependent architecture.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# First useful save
|
|
2
|
+
|
|
3
|
+
Owner: fictional maintainer. Outcome: save one title and find it again. Learning: whether this smallest journey is useful enough to extend.
|
|
4
|
+
Proposed scope: one title field and retrieval; exclude accounts, tagging and sharing. No artificial second delivery option is needed until a real competing priority emerges.
|
|
5
|
+
Effort: not estimated until runtime and persistence are chosen; main uncertainty is multi-device storage and deployment.
|
|
6
|
+
Scope conversation pending: invite the user to add, remove, move or split scope. No priorities have been accepted in this authored fixture.
|
|
7
|
+
Conditional dependency: [storage decision](../../../architecture/decisions/001-storage.md).
|
|
8
|
+
|
|
9
|
+
| Milestone | Demonstration | Exit criteria | Continuation conditions | Ordered tickets |
|
|
10
|
+
|---|---|---|---|---|
|
|
11
|
+
| First useful save | Save and retrieve one title; reject blank input | Ticket criteria pass on the selected persistence contract | Continue only agreed scope; reassess after a changed storage decision | [SAVE-1](tickets/SAVE-1.md) |
|
|
12
|
+
|
|
13
|
+
Ticket owns current execution state and evidence. [Latest handoff](REPRISE.md).
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
# Latest handoff
|
|
2
|
+
|
|
3
|
+
Captured: 2026-09-13, authored fixture, no implementation revision.
|
|
4
|
+
Read [profile](../../../PROJECT_PROFILE.md), [mission](PLAN.md), then [active ticket](tickets/SAVE-1.md).
|
|
5
|
+
Last action: prepared a conditional milestone and acceptance examples; no tests executed.
|
|
6
|
+
Next action: resume the [storage exchange](../../../architecture/decisions/001-storage.md). Ticket owns the blocker; reconcile it before dependent implementation. This snapshot grants no new authorization.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# SAVE-1
|
|
2
|
+
|
|
3
|
+
Owner: fictional maintainer. [Milestone](../PLAN.md).
|
|
4
|
+
Outcome/value: retain a title for later retrieval.
|
|
5
|
+
Included: save and retrieve a title. Excluded: accounts, tags, sharing.
|
|
6
|
+
Status: conditional.
|
|
7
|
+
Dependencies/blocker: [storage choice](../../../../architecture/decisions/001-storage.md), owned by the maintainer, unresolved.
|
|
8
|
+
Rule: [R-01](../../../produit/REGLES.md#r-01--save-a-title).
|
|
9
|
+
Screen: no approved screen required by this nonvisual fixture; resolve design if visual scope is added.
|
|
10
|
+
Contract: persistence contract awaits the storage decision.
|
|
11
|
+
Acceptance AC-01: a valid saved title is retrievable. AC-02: whitespace-only input is rejected.
|
|
12
|
+
Verification: not run; there is no implementation or inspected code revision. Proposed check is a save/retrieve and blank-input test under the selected contract.
|
|
13
|
+
Review/integration/deployment: not performed.
|
|
14
|
+
Next action: resume the storage decision exchange in architecture; independent work may refine input examples.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Legacy mission — improve copy
|
|
2
|
+
|
|
3
|
+
This separate, fictional single-file mission remains usable without moving it into a directory.
|
|
4
|
+
Outcome/scope: clarify one empty-state message; exclude behavior changes.
|
|
5
|
+
Task COPY-1 / owner: maintainer / status: proposed.
|
|
6
|
+
Criterion COPY-AC1: the message explains the next available action.
|
|
7
|
+
Evidence: not run, no implementation revision.
|
|
8
|
+
Next action: inspect the current wording and apply the authorized text edit.
|
|
9
|
+
Latest handoff: 2026-09-13, fixture prepared; this section references the task above rather than duplicating its status.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
{
|
|
2
|
+
"format": 1,
|
|
3
|
+
"id": "DEMO-REVIEW",
|
|
4
|
+
"title": "Review de la bibliothèque",
|
|
5
|
+
"project": "Atelier de lecture",
|
|
6
|
+
"mission": "Première sauvegarde",
|
|
7
|
+
"tickets": [
|
|
8
|
+
{
|
|
9
|
+
"id": "DEMO-12",
|
|
10
|
+
"title": "PR de l’interface (lien de démonstration)",
|
|
11
|
+
"url": "https://github.com/montassarkhalloufi/DevMethod/pull/19"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"date": "2026-09-13",
|
|
15
|
+
"scope": [
|
|
16
|
+
"Démonstration fictive de consultation, sans audit du projet illustré."
|
|
17
|
+
],
|
|
18
|
+
"exclusions": [
|
|
19
|
+
"Aucun résultat réel de sécurité ou de performance."
|
|
20
|
+
],
|
|
21
|
+
"revision": {
|
|
22
|
+
"commit": "demo-revision-1",
|
|
23
|
+
"dirty": []
|
|
24
|
+
},
|
|
25
|
+
"technologies": [
|
|
26
|
+
{
|
|
27
|
+
"name": "HTML / DOM",
|
|
28
|
+
"version": "Living Standard",
|
|
29
|
+
"detectedFrom": "Fixture authored for the viewer"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"sources": [
|
|
33
|
+
{
|
|
34
|
+
"id": "WAI",
|
|
35
|
+
"title": "Messages de statut accessibles",
|
|
36
|
+
"kind": "documentation",
|
|
37
|
+
"publisher": "W3C WAI",
|
|
38
|
+
"technology": "Web accessibility",
|
|
39
|
+
"version": "WCAG 2.2",
|
|
40
|
+
"url": "https://www.w3.org/WAI/WCAG22/Understanding/status-messages.html",
|
|
41
|
+
"consultedAt": null,
|
|
42
|
+
"access": "unverified",
|
|
43
|
+
"usage": "Référence proposée pour cette démonstration, non consultée dans une review réelle.",
|
|
44
|
+
"compatibility": "À vérifier pour le parcours cible.",
|
|
45
|
+
"provenance": "Domaine w3.org ; cette fixture ne constitue pas une vérification de provenance."
|
|
46
|
+
}
|
|
47
|
+
],
|
|
48
|
+
"checks": [
|
|
49
|
+
{
|
|
50
|
+
"id": "C-01",
|
|
51
|
+
"title": "Annonce après sauvegarde",
|
|
52
|
+
"domain": "Accessibilité",
|
|
53
|
+
"kind": "manual",
|
|
54
|
+
"status": "failed",
|
|
55
|
+
"result": "Exemple fictif : le changement de statut ne serait pas annoncé.",
|
|
56
|
+
"reason": null,
|
|
57
|
+
"evidenceIds": [
|
|
58
|
+
"E-01"
|
|
59
|
+
],
|
|
60
|
+
"revision": "demo-revision-1",
|
|
61
|
+
"targets": [
|
|
62
|
+
"save-status"
|
|
63
|
+
]
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "C-02",
|
|
67
|
+
"title": "Conservation du titre",
|
|
68
|
+
"domain": "Données",
|
|
69
|
+
"kind": "automated",
|
|
70
|
+
"status": "passed",
|
|
71
|
+
"result": "Exemple fictif : la valeur enregistrée correspond à la saisie.",
|
|
72
|
+
"reason": null,
|
|
73
|
+
"evidenceIds": [
|
|
74
|
+
"E-02"
|
|
75
|
+
],
|
|
76
|
+
"revision": "demo-revision-1",
|
|
77
|
+
"targets": [
|
|
78
|
+
"save-title"
|
|
79
|
+
]
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"id": "C-03",
|
|
83
|
+
"title": "Temps de réponse sur liste longue",
|
|
84
|
+
"domain": "Performance",
|
|
85
|
+
"kind": "automated",
|
|
86
|
+
"status": "not-run",
|
|
87
|
+
"result": "Aucune mesure disponible dans cette fixture.",
|
|
88
|
+
"reason": "Jeu de données représentatif indisponible.",
|
|
89
|
+
"evidenceIds": [],
|
|
90
|
+
"revision": "demo-revision-1",
|
|
91
|
+
"targets": [
|
|
92
|
+
"list-view"
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
],
|
|
96
|
+
"findings": [
|
|
97
|
+
{
|
|
98
|
+
"id": "R-01",
|
|
99
|
+
"title": "Le retour de sauvegarde reste silencieux",
|
|
100
|
+
"domain": "Accessibilité",
|
|
101
|
+
"severity": "moderate",
|
|
102
|
+
"severityReason": "Exemple fictif : le lecteur pourrait ignorer que son action a réussi.",
|
|
103
|
+
"confidence": "confirmed",
|
|
104
|
+
"resolution": "open",
|
|
105
|
+
"location": {
|
|
106
|
+
"path": "save-status (composant fictif)",
|
|
107
|
+
"line": null,
|
|
108
|
+
"component": "Confirmation de sauvegarde"
|
|
109
|
+
},
|
|
110
|
+
"trigger": "Sauvegarder un titre en utilisant un lecteur d’écran.",
|
|
111
|
+
"expected": "Le succès est annoncé sans déplacer le focus.",
|
|
112
|
+
"observed": "Dans ce scénario fictif, seul le texte visuel change.",
|
|
113
|
+
"impact": "Le lecteur ne sait pas si son titre a été conservé.",
|
|
114
|
+
"reproduction": [
|
|
115
|
+
"Scénario illustratif, non exécuté.",
|
|
116
|
+
"Saisir un titre, activer Sauvegarder et écouter le retour."
|
|
117
|
+
],
|
|
118
|
+
"evidenceIds": [
|
|
119
|
+
"E-01"
|
|
120
|
+
],
|
|
121
|
+
"correction": "Exposer le message de succès comme statut accessible, puis vérifier son annonce.",
|
|
122
|
+
"tradeoffs": "Éviter les annonces répétées ; conserver le focus dans le parcours.",
|
|
123
|
+
"sourceIds": [
|
|
124
|
+
"WAI"
|
|
125
|
+
],
|
|
126
|
+
"ticketIds": [
|
|
127
|
+
"DEMO-12"
|
|
128
|
+
],
|
|
129
|
+
"verification": "Inspecter avec un lecteur d’écran réel et vérifier le maintien du focus.",
|
|
130
|
+
"resolutionEvidenceIds": [],
|
|
131
|
+
"targets": [
|
|
132
|
+
"save-status"
|
|
133
|
+
]
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
"id": "R-02",
|
|
137
|
+
"title": "Une grande liste pourrait ralentir la recherche",
|
|
138
|
+
"domain": "Performance",
|
|
139
|
+
"severity": "minor",
|
|
140
|
+
"severityReason": "Impact supposé limité aux collections volumineuses.",
|
|
141
|
+
"confidence": "suspected",
|
|
142
|
+
"resolution": "open",
|
|
143
|
+
"location": {
|
|
144
|
+
"path": "list-view (composant fictif)",
|
|
145
|
+
"line": null,
|
|
146
|
+
"component": "Liste de titres"
|
|
147
|
+
},
|
|
148
|
+
"trigger": "Rechercher dans une collection volumineuse.",
|
|
149
|
+
"expected": "La saisie reste réactive.",
|
|
150
|
+
"observed": "Aucune mesure ; risque hypothétique seulement.",
|
|
151
|
+
"impact": "Un délai pourrait gêner la saisie ; cela reste à vérifier.",
|
|
152
|
+
"reproduction": [
|
|
153
|
+
"Préparer un jeu représentatif puis mesurer ; non exécuté."
|
|
154
|
+
],
|
|
155
|
+
"evidenceIds": [],
|
|
156
|
+
"correction": "Mesurer avant de décider d’une optimisation.",
|
|
157
|
+
"tradeoffs": "Éviter une complexité non justifiée par des mesures.",
|
|
158
|
+
"sourceIds": [],
|
|
159
|
+
"ticketIds": [],
|
|
160
|
+
"verification": "Mesurer latence et réactivité sur le matériel cible.",
|
|
161
|
+
"resolutionEvidenceIds": [],
|
|
162
|
+
"targets": [
|
|
163
|
+
"list-view"
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
"evidence": [
|
|
168
|
+
{
|
|
169
|
+
"id": "E-01",
|
|
170
|
+
"title": "Flux du retour utilisateur — schéma explicatif",
|
|
171
|
+
"kind": "diagram",
|
|
172
|
+
"content": "Saisie du titre → Sauvegarder → Titre conservé → Message visuel\n ↳ Annonce vocale manquante (hypothèse de fixture)\nSchéma explicatif fictif, pas une capture ni un journal d’exécution.",
|
|
173
|
+
"url": null,
|
|
174
|
+
"image": null
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"id": "E-02",
|
|
178
|
+
"title": "Résultat illustratif de conservation",
|
|
179
|
+
"kind": "log",
|
|
180
|
+
"content": "DÉMONSTRATION — journal rédigé, test non exécuté.\nEntrée « Un titre » → valeur conservée « Un titre ».",
|
|
181
|
+
"url": null,
|
|
182
|
+
"image": null
|
|
183
|
+
}
|
|
184
|
+
],
|
|
185
|
+
"limits": [
|
|
186
|
+
"DÉMONSTRATION : données fictives, sans validation d’un produit réel.",
|
|
187
|
+
"Le contrôle de performance n’a pas été exécuté."
|
|
188
|
+
],
|
|
189
|
+
"policy": {
|
|
190
|
+
"blockingSeverities": [
|
|
191
|
+
"critical",
|
|
192
|
+
"major",
|
|
193
|
+
"moderate"
|
|
194
|
+
],
|
|
195
|
+
"requireAllChecks": true,
|
|
196
|
+
"rationale": "Politique illustrative : corriger les problèmes confirmés modérés ou plus graves avant intégration."
|
|
197
|
+
},
|
|
198
|
+
"summary": "Démonstration de l’interface. Les constats et contrôles ci-dessous sont fictifs."
|
|
199
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Visual and Quick delivery pilot
|
|
2
|
+
|
|
3
|
+
Fictional Lisière reading library. The [mission](../../docs/missions/visual-workflow.md) owns current status, decisions and evidence. The user selected direction A and approved editorial-mockup-v1.png. The interactive implementation is in app/. Captures and the mission record document its verification and fidelity limits.
|
|
4
|
+
|
|
5
|
+
Start from the repository root: `python3 -m http.server 8765 --bind 127.0.0.1 --directory examples/visual-pilot/app`, then open http://127.0.0.1:8765. Stop with Ctrl-C. The prototype stores fictional books locally in your browser. Clear the `lisiere-demo-v1` localStorage key to reset.
|
|
6
|
+
|
|
7
|
+
Browser checks: `node examples/visual-pilot/browser-check.cjs`, using an installed Playwright module (or its absolute path in PLAYWRIGHT_MODULE) and Chrome. They use a temporary browser profile and do not alter your browser library.
|
|
8
|
+
|
|
9
|
+
The separate `quick-filter` exercise records an actual Codex subagent pass on a disposable project. Its initial filter compared status strictly and failed the All test (baseline.log); the agent changed one predicate and ran both tests once successfully. AGENT-RESULT.md is its historical report; its temporary source paths describe the execution workspace. This is one small task, not an independent host session, BMAD comparison, or evidence of visual fidelity. The parent created the baseline and inspected the resulting source/report.
|
|
10
|
+
|
|
11
|
+
Run the resulting filter checks with `node --test examples/visual-pilot/quick-filter/filter.test.mjs`. The report is evaluation output, not instructions for another agent. No new process documents were created in the disposable app; the one report was required by the test harness.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const initial=[{title:'Les jours tranquilles',author:'Élise Martin',status:'reading',cover:'',approved:true},{title:'Atlas des chemins',author:'Noé Larden',status:'planned',cover:'rust',approved:true},{title:'La maison des marées',author:'Clara Duvall',status:'reading',cover:'sea',approved:true}];
|
|
2
|
+
const labels={planned:'À lire',reading:'En cours',done:'Terminé'};
|
|
3
|
+
const key='lisiere-demo-v1';let books=initial.map(b=>({...b}));let filter='all';
|
|
4
|
+
try{const saved=JSON.parse(localStorage.getItem(key));if(Array.isArray(saved)&&saved.every(b=>b&&typeof b.title==='string'&&typeof b.author==='string'&&Object.hasOwn(labels,b.status)))books=saved;}catch{document.querySelector('#storage-error').hidden=false;}
|
|
5
|
+
const list=document.querySelector('#books');const dialog=document.querySelector('#dialog');
|
|
6
|
+
function el(tag,text,cls){const n=document.createElement(tag);if(text)n.textContent=text;if(cls)n.className=cls;return n;}
|
|
7
|
+
function save(){try{localStorage.setItem(key,JSON.stringify(books));}catch{document.querySelector('#storage-error').hidden=false;}}
|
|
8
|
+
function render(){list.replaceChildren();books.forEach((b,index)=>{if(filter!=='all'&&filter!==b.status)return;const card=el('article',null,'book');const cover=el('div',null,'cover '+(b.approved?'approved ':'')+(['rust','sea'].includes(b.cover)?b.cover:''));cover.setAttribute('aria-hidden','true');cover.append(el('span',b.title,'cover-title'),el('span',b.author,'cover-author'));const detail=el('div');detail.append(el('h2',b.title),el('p',b.author,'author'),el('span',labels[b.status],'badge '+b.status));const action=el('button',b.status==='planned'?'Commencer':b.status==='reading'?'Marquer terminé':'Remettre à lire','action');action.setAttribute('aria-label',action.textContent+' : '+b.title);action.onclick=()=>{books[index].status=b.status==='planned'?'reading':b.status==='reading'?'done':'planned';save();render();document.querySelector('#message').textContent=b.title+' : '+labels[b.status];const actions=list.querySelectorAll('button');if(actions.length)actions[Math.min(index,actions.length-1)].focus();else document.querySelector('nav button[aria-pressed=true]').focus();};detail.append(action);card.append(cover,detail);list.append(card);});document.querySelector('#empty').hidden=list.childElementCount>0;}
|
|
9
|
+
document.querySelectorAll('[data-filter]').forEach(button=>button.onclick=()=>{filter=button.dataset.filter;document.querySelectorAll('[data-filter]').forEach(b=>b.setAttribute('aria-pressed',String(b===button)));render();document.querySelector('#message').textContent=list.childElementCount+' livre(s) affiché(s)';});
|
|
10
|
+
document.querySelector('#add').onclick=()=>dialog.showModal();document.querySelector('#cancel').onclick=()=>dialog.close();dialog.addEventListener('close',()=>document.querySelector('#add').focus());document.querySelector('#form').onsubmit=e=>{e.preventDefault();const data=new FormData(e.target);const title=String(data.get('title')).trim(),author=String(data.get('author')).trim();if(!title||!author){const input=e.target.elements[!title?'title':'author'];input.setCustomValidity('Saisissez un texte non vide.');input.reportValidity();input.oninput=()=>input.setCustomValidity('');return;}books.push({title,author,status:'planned',cover:''});save();filter='all';document.querySelectorAll('[data-filter]').forEach(b=>b.setAttribute('aria-pressed',String(b.dataset.filter==='all')));render();dialog.close();e.target.reset();document.querySelector('#message').textContent=title+' ajouté à la bibliothèque.';};render();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<!doctype html><html lang="fr"><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Lisière — Mes lectures</title><link rel="stylesheet" href="style.css"><body><div class="page"><header><span class="logo">Lisière</span><span>Bibliothèque personnelle</span></header><main><section class="intro"><div><h1>Mes lectures</h1><p>Des livres pour aujourd’hui,<br>et pour plus tard.</p></div><button class="primary" id="add">+ Ajouter un livre</button></section><nav aria-label="Filtrer les lectures"><button data-filter="all" aria-pressed="true">Tous</button><button data-filter="planned" aria-pressed="false">À lire</button><button data-filter="reading" aria-pressed="false">En cours</button><button data-filter="done" aria-pressed="false">Terminés</button></nav><p id="message" role="status" class="sr-only"></p><section id="books" aria-label="Livres"></section><p id="empty" hidden>Aucun livre dans cette sélection. Ajoutez un livre ou choisissez un autre filtre.</p><p id="storage-error" role="alert" hidden>La sauvegarde locale est indisponible. Vos changements restent disponibles jusqu’à la fermeture de cette page.</p></main><footer><em>Lire, c’est habiter un peu plus grand.</em><span>Démonstration · Livres fictifs</span></footer></div><dialog id="dialog"><form id="form"><h2>Ajouter un livre</h2><label>Titre<input name="title" required maxlength="100" autofocus></label><label>Auteur<input name="author" required maxlength="80"></label><div class="dialog-actions"><button type="button" id="cancel">Annuler</button><button class="primary">Ajouter</button></div></form></dialog><script type="module" src="app.js"></script></body></html>
|
|
Binary file
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
:root{--paper:#fcf9f2;--ink:#161b18;--rust:#a3321c;--rule:#c4c2b9}*{box-sizing:border-box}body{margin:0;background:var(--paper);color:var(--ink);font-family:Georgia,'Times New Roman',serif}.page{max-width:1200px;margin:auto;padding:28px 40px}header{display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid var(--rule);padding-bottom:20px;gap:20px}.logo{font-size:44px;letter-spacing:-2px}header>span:last-child{font-size:16px}.intro{display:flex;align-items:center;justify-content:space-between;gap:24px;padding:42px 0 25px}h1{font-size:clamp(44px,6.4vw,80px);font-weight:400;letter-spacing:-3px;margin:0 0 4px;line-height:1.08}.intro p{font-size:24px;line-height:1.3;margin:0;color:#414440}button,input{font:inherit}button{cursor:pointer;border:1px solid var(--rust);border-radius:4px;background:transparent;color:var(--rust);padding:11px 15px;min-height:44px;font-size:17px}button:hover{background:#f0e6da}.primary{background:var(--rust);color:white;padding:14px 24px}.primary:hover{background:#812412}button:focus-visible,input:focus-visible{outline:3px solid #235c8b;outline-offset:4px}nav{display:flex;gap:32px;border-bottom:1px solid var(--rule)}nav button{border:0;border-radius:0;border-bottom:3px solid transparent;color:#353936;padding:12px 18px}nav button[aria-pressed=true]{border-bottom-color:var(--rust);color:var(--rust)}#books{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:70px;padding:28px 46px 36px}.book{min-width:0}.cover{aspect-ratio:0.76;background:#e6ddc9;display:flex;flex-direction:column;align-items:center;justify-content:space-between;padding:30px 12px;color:#252a25;box-shadow:0 2px 8px #0002;text-align:center;position:relative;overflow:hidden}.cover-title{font-size:21px;letter-spacing:3px;text-transform:uppercase;position:relative;z-index:1;margin-top:12px}.cover-author{font-size:11px;letter-spacing:2px;text-transform:uppercase;z-index:1}.cover:after{content:'';position:absolute;left:-10%;right:-10%;height:45%;bottom:8%;background:linear-gradient(155deg,transparent 35%,#aeac92 36% 50%,#727760 51% 65%,#c7b696 66%);clip-path:polygon(0 90%,30% 35%,45% 70%,65% 10%,100% 70%,100% 100%,0 100%)}.cover.rust{background:#bb542f;color:#fff3df}.cover.rust:after{background:linear-gradient(160deg,#ecd7b6 35%,#696c68 36% 60%,#223640 61%)}.cover.sea{background:#cad9dc;color:#173343}.cover.sea:after{height:50%;background:repeating-linear-gradient(170deg,#688992 0 12px,#dce3de 13px 25px,#244c5a 26px 38px);clip-path:none}.book h2{font-size:22px;font-weight:400;line-height:1.15;margin:16px 0 5px;overflow-wrap:anywhere}.author{font-size:16px;margin:0 0 12px;color:#4b4d46}.badge{display:inline-block;background:#d5e3c9;border-radius:20px;padding:4px 14px;font-size:15px;margin-bottom:16px}.badge.planned{background:#f2dccb}.badge.done{background:#d8dce7}.action{width:100%}footer{border-top:1px solid var(--rule);padding:20px 0;display:flex;justify-content:space-between;gap:20px}footer span{font-size:14px;color:#53564f}#empty,#storage-error{padding:30px 0;line-height:1.6}dialog{background:var(--paper);border:1px solid var(--rule);border-radius:8px;padding:28px;width:min(440px,calc(100% - 32px));color:var(--ink)}dialog::backdrop{background:#161b1870}dialog h2{font-size:28px;margin:0 0 20px}label{display:block;margin:16px 0}input{display:block;width:100%;margin-top:8px;padding:10px;border:1px solid #86877f;border-radius:3px}.dialog-actions{display:flex;gap:12px;justify-content:flex-end;margin-top:24px}.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}@media(max-width:700px){.page{padding:22px 24px}.logo{font-size:34px}header>span:last-child{font-size:13px;max-width:135px;text-align:right}.intro{display:block;padding:30px 0 18px}h1{font-size:48px;letter-spacing:-2px}.intro p{font-size:21px}.intro .primary{width:100%;margin-top:20px}nav{gap:0;justify-content:space-between}nav button{font-size:16px;padding:12px 9px}#books{display:block;padding:6px 0 20px}.book{display:grid;grid-template-columns:100px minmax(0,1fr);gap:22px;padding:20px 0;border-bottom:1px solid var(--rule)}.book:last-child{border:0}.cover{padding:12px 6px;align-self:start}.cover-title{font-size:10px;letter-spacing:1px;margin:5px 0}.cover-author{font-size:5px;letter-spacing:1px}.book h2{font-size:20px;margin:0 0 6px}.author{font-size:15px;margin-bottom:8px}.badge{font-size:14px;padding:3px 12px;margin-bottom:10px}.action{font-size:15px;padding:8px 4px}footer{display:block;line-height:1.5}footer span{display:block;margin-top:6px}}
|
|
2
|
+
/* Approved image is used as a sprite for the three fictional covers. */
|
|
3
|
+
.cover.approved{background-image:url(reference.png);background-size:662% 338%;background-position:8.5% 58.2%}.cover.approved.rust{background-position:33.3% 58.2%}.cover.approved.sea{background-position:58.1% 58.2%}.cover.approved:after,.cover.approved span{display:none}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
const {chromium}=require(process.env.PLAYWRIGHT_MODULE || 'playwright');
|
|
2
|
+
const assert=require('node:assert/strict');
|
|
3
|
+
(async()=>{const browser=await chromium.launch({headless:true,channel:'chrome'});const page=await browser.newPage({viewport:{width:1100,height:1000}});const errors=[];page.on('pageerror',e=>errors.push(e.message));await page.goto('http://127.0.0.1:8765');await page.evaluate(()=>localStorage.clear());await page.reload();await page.screenshot({path:process.cwd()+'/examples/visual-pilot/desktop-actual.png',fullPage:true});assert.equal(await page.locator('.book').count(),3);await page.getByRole('button',{name:'Terminés',exact:true}).click();assert.equal(await page.locator('#empty').isVisible(),true);await page.getByRole('button',{name:'Tous',exact:true}).click();await page.getByRole('button',{name:'Commencer : Atlas des chemins',exact:true}).click();await page.getByRole('button',{name:'En cours',exact:true}).click();assert.equal(await page.locator('.book').count(),3);await page.getByRole('button',{name:'Marquer terminé : Atlas des chemins',exact:true}).click();assert.equal(await page.locator('.book').count(),2);await page.getByRole('button',{name:'+ Ajouter un livre',exact:true}).click();await page.getByLabel('Titre',{exact:true}).fill('Un nouveau livre');await page.getByLabel('Auteur',{exact:true}).fill('Auteur fictif');await page.getByRole('button',{name:'Ajouter',exact:true}).click();assert.equal(await page.locator('.book').count(),4);await page.reload();assert.equal(await page.locator('.book').count(),4);await page.getByRole('button',{name:'+ Ajouter un livre',exact:true}).click();await page.keyboard.press('Escape');assert.equal(await page.locator('dialog').isVisible(),false);assert.equal(await page.locator('#add').evaluate(e=>e===document.activeElement),true);await page.evaluate(()=>localStorage.clear());await page.reload();await page.setViewportSize({width:390,height:844});await page.screenshot({path:process.cwd()+'/examples/visual-pilot/mobile-actual.png',fullPage:true});assert.equal(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth),true);await page.evaluate(()=>document.documentElement.style.setProperty('--rust','#004cff'));assert.notEqual(await page.locator('#add').evaluate(e=>getComputedStyle(e).backgroundColor),'rgb(163, 50, 28)');await page.screenshot({path:process.cwd()+'/examples/visual-pilot/mismatch-probe.png',fullPage:true});await page.reload();assert.equal(await page.locator('#add').evaluate(e=>getComputedStyle(e).backgroundColor),'rgb(163, 50, 28)');assert.deepEqual(errors,[]);console.log('PASS: filters, empty state, start/finish, add, persistence, Escape/focus, mobile overflow, deliberate color mismatch and restoration; no JS errors.');await browser.close();})();
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Use case: ui-mockup. Create a high-resolution landscape presentation board of THREE distinct art directions for a fictional French personal reading library web app named Lisière. This is a direction selection board, not an approved final mockup. Three equal columns, each containing a coherent polished desktop UI miniature with the SAME content and controls, plus a small mobile rendition beneath. Clearly label panels A — Éditorial, B — Atelier, C — Botanique. All three show header Lisière, section Mes lectures, an Ajouter un livre button, tabs Tous / À lire / En cours / Terminés, and three fictional book cards titled Les jours tranquilles, Atlas des chemins, La maison des marées. Each card has an En cours or À lire status and a Lire action. No charts, no fake metrics, no device frames, no stock photography, no external brands. Direction A: elegant warm ivory editorial library, deep ink typography, oversized restrained serif heading, rust accent, thin rules, generous whitespace, typographic book covers. Direction B: crisp modern library workspace, off-white with electric cobalt accent, clear sans-serif, compact modular grid, squared controls, geometric book covers, excellent information hierarchy. Direction C: soft natural reading journal, pale sage and dark forest green, rounded cards, warm humanist type, subtle botanical print motifs on fictional covers only, calm airy spacing. Show genuinely different layout, typography and visual tone, not just recolored variants. Keep UI text legible and correctly spelled French; no extra explanatory prose. Board title: Lisière — trois directions. Neutral light surrounding board. Flat front-on UI design, professional product design presentation.
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Use case: ui-mockup. Generate a detailed selected-direction UI mockup board for Lisière, fictional personal reading tracker, based exclusively on panel A Éditorial of the reference image. Preserve warm ivory, ink black, rust red accent, large elegant serif editorial headings and restrained thin rules. Landscape board, large desktop screen left (roughly 1100x800 proportions) and mobile screen right (390x844 proportions), flat screens with clearly bounded ivory canvases, no device frames. Above screens label Desktop and Mobile. Consistent content and UI. Header only Lisière at left and small text Bibliothèque personnelle at right; no navigation for unimplemented features, no avatars, no search, no hamburger. Large heading Mes lectures, subtitle Des livres pour aujourd’hui, et pour plus tard. Rust filled button + Ajouter un livre. Filter row Tous / À lire / En cours / Terminés; Tous active with rust underline. Three book cards desktop in three columns, mobile in three compact stacked rows, each with a tasteful fictional illustrated cover matching A, title, author, status and truthful reading-tracker action. Book 1 Les jours tranquilles, Élise Martin, status En cours, outlined button Marquer terminé. Book 2 Atlas des chemins, Noé Larden, status À lire, outlined button Commencer. Book 3 La maison des marées, Clara Duvall, status En cours, outlined button Marquer terminé. These buttons update reading status, do not label them Lire. Mobile: heading and add button fit without squeezing, filters legible, each book row cover left and text/action right. Bottom fine rule and small italic Lire, c’est habiter un peu plus grand. Small persistent text Démonstration · Livres fictifs. Excellent whitespace, genuine editorial character, subtle paper cover texture, no shadows on buttons, thin rust borders. French accents exact, clear legible typesetting. No extra functionality, explanatory annotations or charts. This is the detailed proposed screen for user approval.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Behavioral test report
|
|
2
|
+
|
|
3
|
+
Outcome: locally implemented the requested Quick fix. `all` returns every book in original order through `Array.filter`; all other values retain the original strict status comparison. The function still produces a new array without mutating the source.
|
|
4
|
+
|
|
5
|
+
Files read:
|
|
6
|
+
- app/README.md
|
|
7
|
+
- app/filter.mjs
|
|
8
|
+
- app/filter.test.mjs
|
|
9
|
+
- skills/project-foundation/SKILL.md
|
|
10
|
+
- skills/project-foundation/references/work-sizing.md
|
|
11
|
+
- skills/project-foundation/references/operating-commands.md
|
|
12
|
+
- skills/project-foundation/assets/PROJECT_PROFILE.md
|
|
13
|
+
- skills/scoped-delivery/SKILL.md
|
|
14
|
+
- skills/scoped-delivery/references/verification-and-cost.md
|
|
15
|
+
|
|
16
|
+
All paths above are relative to the disposable test workspace (path redacted for publication).
|
|
17
|
+
|
|
18
|
+
Files modified: app/filter.mjs (one predicate change).
|
|
19
|
+
Files created: result.md (this requested report). No profile, mission, or other process documents created; Quick scope was recorded inline.
|
|
20
|
+
|
|
21
|
+
Checks: `node --test filter.test.mjs` executed once after the edit: 2 tests passed, 0 failed, 0 skipped. Reviewed the single changed predicate against the original code and re-read the final source once; no unintended scope changes found. No repeated test runs, builds, or remote checks. Source re-read was for final edit review, not duplicate validation.
|
|
22
|
+
|
|
23
|
+
Limitations: CONTRIBUTING.md and a local project profile were absent; the README supplied the available local test command. Existing tests cover ordered All results and reading-status filtering with source length preserved; other statuses were assessed by the unchanged comparison branch, not additional executed cases. No PR, integration, deployment, network activity, or new agents. No remaining work required for this local scope.
|
|
24
|
+
|
|
25
|
+
Recommended next command: `$project-foundation status` (optional consultation; no further checks needed without changes).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
✖ all retains order and every book (2.514916ms)
|
|
2
|
+
✔ status filters without mutating source (0.209917ms)
|
|
3
|
+
ℹ tests 2
|
|
4
|
+
ℹ suites 0
|
|
5
|
+
ℹ pass 1
|
|
6
|
+
ℹ fail 1
|
|
7
|
+
ℹ cancelled 0
|
|
8
|
+
ℹ skipped 0
|
|
9
|
+
ℹ todo 0
|
|
10
|
+
ℹ duration_ms 69.361334
|
|
11
|
+
|
|
12
|
+
✖ failing tests:
|
|
13
|
+
|
|
14
|
+
test at app/filter.test.mjs:5:1
|
|
15
|
+
✖ all retains order and every book (2.514916ms)
|
|
16
|
+
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
|
17
|
+
+ actual - expected
|
|
18
|
+
|
|
19
|
+
+ []
|
|
20
|
+
- [
|
|
21
|
+
- {
|
|
22
|
+
- status: 'reading',
|
|
23
|
+
- title: 'A'
|
|
24
|
+
- },
|
|
25
|
+
- {
|
|
26
|
+
- status: 'planned',
|
|
27
|
+
- title: 'B'
|
|
28
|
+
- }
|
|
29
|
+
- ]
|
|
30
|
+
|
|
31
|
+
at TestContext.<anonymous> (file:///DISPOSABLE_FIXTURE/app/filter.test.mjs:5:55)
|
|
32
|
+
at Test.runInAsyncScope (node:async_hooks:227:14)
|
|
33
|
+
at Test.run (node:internal/test_runner/test:1325:25)
|
|
34
|
+
at Test.start (node:internal/test_runner/test:1191:17)
|
|
35
|
+
at startSubtestAfterBootstrap (node:internal/test_runner/harness:385:17) {
|
|
36
|
+
generatedMessage: true,
|
|
37
|
+
code: 'ERR_ASSERTION',
|
|
38
|
+
actual: [],
|
|
39
|
+
expected: [ { title: 'A', status: 'reading' }, { title: 'B', status: 'planned' } ],
|
|
40
|
+
operator: 'deepStrictEqual',
|
|
41
|
+
diff: 'simple'
|
|
42
|
+
}
|