bmad-plus 0.17.1 → 0.19.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.
@@ -87,6 +87,60 @@ Deliver in the way the host allows, and say so plainly:
87
87
  Then tell the tester four things: the link or file, how long it takes, **which steps write for
88
88
  real**, and the play order when several recipes share an environment (`bmad-plus uat order`).
89
89
 
90
+ ### What the page guarantees — and what you check before a person opens it
91
+
92
+ A run takes tens of minutes. The tester answers in passing, opens the product in another
93
+ tab, comes back, reloads. On 2026-09-25 a page built outside this command showed its steps
94
+ before any run existed: every tick was displayed and none was kept, a reload emptied the
95
+ form, and the status read "saved" on nobody's word. What follows exists so that this
96
+ cannot happen again, on any project.
97
+
98
+ **The page is built, never written.** Persistence lives in one place, the template that
99
+ `bmad-plus uat build` fills. Do not hand-write an acceptance page, do not re-implement
100
+ saving in a project, do not edit the produced HTML. A page that needs something the
101
+ template lacks is a change to the template, made in BMAD+ with its tests.
102
+
103
+ **What every built page guarantees, by construction** (`uat build --json` lists them under
104
+ `guarantees`, and the build refuses a template that lost one):
105
+
106
+ | Guarantee | What the tester gets |
107
+ |---|---|
108
+ | `hidden-before-start` · `no-answer-before-run` | no answer field before a run exists, whatever a stylesheet says; a tick before that is refused and explained, never dropped |
109
+ | `verified-local-write` · `honest-save-status` | a write counts only once it reads back; the status names a refused save ("Could not save in this browser — save or copy the JSON") and never says "saved" otherwise |
110
+ | `save-on-every-change` | every tick and every keystroke in a note is kept at once, no blur needed, and the cursor never moves |
111
+ | `restore-before-capabilities` · `newer-copy-wins` | the browser's copy comes back synchronously on reload, before any optional capability answers; an older remote copy never overwrites a newer local one |
112
+ | `revision-guard` | a run that answered another revision of the recipe is offered, not poured in: identical lines keep their answers, changed lines are asked again, the earlier run stays untouched and exportable (`carriedFrom` in the results) |
113
+ | `other-tab-notice` | two tabs on one run converge on the latest change, and the page says so |
114
+ | `unreadable-draft-kept` | a saved run that cannot be read is reported and exportable, never deleted |
115
+ | `progress-accessible` | the share of lines *answered* — not passed — as a `progressbar` with its value, next to the seen / not seen / blocked counts |
116
+ | `storage-explained` | where the answers live and what makes them disappear (site data cleared, private window closed), and that nobody receives them until the JSON is handed over |
117
+ | `finished-is-not-accepted` · `unload-guard` | unanswered lines are said on the page before finishing (a second click finishes anyway); finishing keeps the date and says it is not an acceptance; leaving with an unsaved run is questioned |
118
+ | `utf8-and-escaped-diacritics` · `export-is-the-run` | UTF-8 declared, diacritics handled as escapes, and the exported JSON is the run as answered |
119
+
120
+ **Before a person opens the page**, in this order, and say in the delivery which ones ran:
121
+
122
+ 1. `bmad-plus uat lint <id> --src <dir>` passes.
123
+ 2. `bmad-plus uat build <id>` writes the page and lists its guarantees.
124
+ 3. Open the built page in a real browser and play the essential pass yourself: no answer
125
+ field before starting → enter a name and start → tick one *Seen* → tick one *Not seen* →
126
+ type a remark without leaving the field → type an overall remark → **reload at once,
127
+ without clicking anywhere** → everything is back and the progress says two lines are
128
+ answered. `tools/qa/uat-page-browser-check.js` in the BMAD+ repository is this pass,
129
+ automated; the framework's own suite (`tests/unit/uat-page.test.js`) replays every
130
+ scenario of the incident, storage refusal and revision change included.
131
+ 4. Run these where the project's execution policy allows: if tests do not run on this
132
+ machine, run them on the project's remote environment. Never skip them to save time.
133
+
134
+ If any of these fail, the page is **not ready** — say so, and do not hand it over. A page
135
+ handed over without step 3 is handed over with that fact written next to it.
136
+
137
+ **Tell the tester where the answers live**, in one sentence, before the link: in the artifact
138
+ database (saved on every tick, nothing to send), in the project through `uat serve`, or in
139
+ their browser — on that device only, until they save the JSON. The page says it too.
140
+
141
+ **When the recipe is amended** after a run started, tell the tester their earlier run will be
142
+ offered on the new page and that only unchanged lines keep their answers.
143
+
90
144
  ## 3. Read, triage, gate
91
145
 
92
146
  ```bash
@@ -144,7 +198,10 @@ keeps acceptance out of reach; the fix task carries the run and the triage entry
144
198
  - It does not invent expectations: what the spec announces comes from the code and the
145
199
  measurement, never from the plan alone.
146
200
  - It does not play the recipe. An agent that "plays" it in a headless browser has written a test,
147
- not a recipe.
201
+ not a recipe. Checking that the *page* keeps answers (the pass above) is not playing the recipe:
202
+ it proves the form, not the product.
203
+ - It does not write pages by hand. Persistence is the template's, tested in BMAD+; a project that
204
+ needs more changes the template, not its copy of the page.
148
205
 
149
206
  ## Pitfalls already paid for
150
207
 
@@ -156,3 +213,6 @@ keeps acceptance out of reach; the fix task carries the run and the triage entry
156
213
  - One step reading two screens: totals were hunted on a page that only shows subtotals. One step, one screen.
157
214
  - Two recipes on one environment destroying each other's witnesses through indirect writes — a save
158
215
  that triggers a recomputation. Declare witnesses, run `uat order`, and re-measure after each pass.
216
+ - A page written outside `uat build`, with its own persistence: the steps showed before the run
217
+ existed, ticks were dropped, a reload lost everything, and "saved" was printed unverified
218
+ (FormaPro, 2026-09-25). Build the page; check it in a browser; say which checks ran.
@@ -20,6 +20,17 @@
20
20
  "updatedAt": { "type": "string", "format": "date-time" },
21
21
  "finishedAt": { "type": ["string", "null"], "format": "date-time" },
22
22
  "overallNote": { "type": "string" },
23
+ "carriedFrom": {
24
+ "type": "object",
25
+ "description": "Present when the page continued a run that answered another revision of the recipe. The rule is fixed: an answer travelled only when its line was identical (same step, same letter, same text); every other line was asked again. The earlier run is left untouched.",
26
+ "required": ["runId", "kept", "toAnswer"],
27
+ "properties": {
28
+ "runId": { "type": "string", "description": "The run the answers came from." },
29
+ "specSha256": { "type": ["string", "null"], "description": "The revision that run answered; null when it carried no fingerprint." },
30
+ "kept": { "type": "integer", "description": "Answers that travelled." },
31
+ "toAnswer": { "type": "integer", "description": "Lines of this revision left unanswered by the carry." }
32
+ }
33
+ },
23
34
  "summary": {
24
35
  "type": "object",
25
36
  "required": ["passed", "failed", "blocked", "skipped", "unanswered"],