diff --git a/CHANGELOG.md b/CHANGELOG.md index b5a253d7..a374ac49 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ Sections dated before 2026-09-19 predate the cycle and stay as they are. Named m ## Unreleased +- fix(page-fidelity): **a popup wireframe is scored against its dialog, not the page drawn behind it** (#471). A popup wireframe draws the caller's page behind a scrim (`.popup-stage > .shell-behind + .dialog-scrim > .dialog`); `page-fidelity.js` looked for `div.main` first and landed in the shell, so a popup whose fields and bindings all matched scored 29% in the field, every miss being the caller's page head, h1 and card. With popup evidence (a `.popup-stage` or `.dialog-scrim` in the wireframe, or a page whose MDL names a popup layout) the scorer now scores the `.dialog` only (class word, so `dialog-head` does not count), or the stage minus `.shell-behind` when there is no `.dialog`. Without that evidence nothing changes, so a full-page wireframe that draws an inline `.dialog` confirm (the design system's own component) scores exactly as before, and a bare-`.dialog` popup wireframe still reads ``. `test-page-fidelity-mocks.sh` gains the popup cases on a verbatim v0.23.0 describe of a real popup page and a wireframe drawn in the issue's shape, plus layout-only evidence and the full-page-plus-inline-confirm regression case: 18% and 8 FAIL on master, 100% and 78/78 with the fix. — jopterhorst - fix(progress-report): **the build plan is shown once: the phase cards are the plan.** Expanding a card now shows its full rows (#, Kind, Step, Produces/Proves, Depends on, State, Scripts) and Claims, which only the duplicate "Full plan tables" block carried; that block is gone and views A (script folders) and B (module proof) are their own section. The `now` phase renders open with a selected style, opening another phase closes the previous one (accordion; `#phase-N` links still open their card), and a viewer's saved open/closed state survives the 30 s reload and wins over the default (#474) — field feedback from a requirements-driven project at Stage 5 — MendixMau (field feedback 2026-10-20) ## v2026.10.19 diff --git a/project-bin/page-fidelity.js b/project-bin/page-fidelity.js index 804d202f..acda5f49 100755 --- a/project-bin/page-fidelity.js +++ b/project-bin/page-fidelity.js @@ -143,7 +143,7 @@ function innerBalanced(html, openRe) { return html.slice(open, end); } -function contentOf(html) { +function contentOf(html, popupLayout) { // Boundary preference, most-specific first: //
ToeicBuddy shape — content-only wireframes // div.main the topbar portal full-page shape — the wireframe mocks the WHOLE app @@ -161,10 +161,34 @@ function contentOf(html) { // 06/07 .mockup-frame. Measured, not assumed — every one of them // fell through to before this list existed. // div.content same shape when the wireframe has no .main wrapper - // popup/dialog wireframes with annotation siblings - // then strip annotation chrome either way. - const m = html.match(/]*>([\s\S]*?)<\/main>/i); - let s = m ? m[1] + // popup/dialog wireframes with annotation siblings — a bare `.dialog` + // popup with no stage and no scrim still lands here, as it always did + // then strip annotation chrome either way. Ahead of the whole list, only with popup + // evidence: the dialog itself (see POPUP below). + // + // POPUP, ONLY WITH POPUP EVIDENCE (#471). A popup wireframe draws the CALLER's page behind a + // scrim for context: `.popup-stage > .shell-behind + .dialog-scrim > .dialog`. The shell + // behind carries its own div.main, so the list above landed on the caller's page and scored + // the popup against it: 29% in the field for a popup whose fields and bindings all matched, + // every miss in the shell. So when the wireframe is a popup, the dialog is the page. + // + // But `.dialog` alone is not evidence: it is the toolkit's own design-system component, and + // a full-page wireframe may draw an inline confirm with it. Switching on any `.dialog` scored + // a grouped overview plus a confirm as the confirm alone (39% on master, 5% on the first cut + // of this fix; review of #482). Evidence is a `.popup-stage` or `.dialog-scrim` in the + // wireframe, or a page whose MDL names a popup layout (`Layout: …Popup…`, the same test the + // chrome set uses). Matched on the class WORD, so `dialog-head`/`dialog-foot` never count. + const cls = name => new RegExp(`<(div|section|dialog|aside)\\b[^>]*class="(?:[^"]*\\s)?${name}(?:\\s[^"]*)?"`, 'i'); + const popupEvidence = !!popupLayout || cls('popup-stage').test(html) || cls('dialog-scrim').test(html); + const popup = !popupEvidence ? null + : innerBalanced(html, cls('dialog')) + || (() => { // a stage with no .dialog: everything but the shell + const stage = innerBalanced(html, cls('popup-stage')); + return stage === null ? null : dropBalanced(stage, cls('shell-behind')); + })(); + const m = popup === null ? html.match(/]*>([\s\S]*?)<\/main>/i) : null; + let s = popup !== null ? popup + : m ? m[1] : innerBalanced(html, /<(div)[^>]*class="[^"]*\bwf-screen\b[^"]*"/i) || innerBalanced(html, /<(div)[^>]*class="[^"]*\bwf-shell\b[^"]*"/i) || innerBalanced(html, /<(div)[^>]*class="[^"]*\bmockup-frame\b[^"]*"/i) @@ -318,7 +342,7 @@ function bindRows(html) { return rows; } -function wfFacts(html) { +function wfFacts(html, popupLayout) { // HTML COMMENTS ARE NOT PAGE. contentOf() picks its boundary with a regex, and a template // whose header comment says "put the screen inside
… the bind table goes AFTER //
" handed it the comment text as the page. Field case, 2026-10-02, two builds @@ -327,7 +351,7 @@ function wfFacts(html) { // neither describing the page. Strip comments before anything reads the document. html = html.replace(//g, ' '); const mock = localMockClasses(html); - let main = contentOf(html); + let main = contentOf(html, popupLayout); const mockUsed = []; const structural = []; for (const c of mock) { @@ -739,9 +763,10 @@ function score(wf, mdl, chromeExtra) { return { pct: den ? Math.round(100 * num / den) : null, h, b, k, c, bd }; } -const wf = wfFacts(readWireframe()); const ownMdl = pageMdl(); if (!ownMdl.trim()) { console.error('page-fidelity: no declaration of page "' + PAGE + '" found in input'); process.exit(2); } +// Read first: a popup layout is evidence that a `.dialog` in the wireframe is the page (#471). +const wf = wfFacts(readWireframe(), /Layout:\s*[^,)\n]*Popup/i.test(ownMdl)); const snips = expandSnippets(ownMdl); const mdl = ownMdl + snips.extra; const lay = layoutChrome(SRCS, ownMdl); diff --git a/tests/wave2/fixtures/page-fidelity-popup/CAPTURE.md b/tests/wave2/fixtures/page-fidelity-popup/CAPTURE.md new file mode 100644 index 00000000..2cae4adc --- /dev/null +++ b/tests/wave2/fixtures/page-fidelity-popup/CAPTURE.md @@ -0,0 +1,19 @@ +# page-fidelity-popup — a popup wireframe and the popup page it draws (#471) + +`product-newedit-describe-v023.mdl` is **verbatim** `mxcli describe page Sales.Product_NewEdit` +output (mxcli v0.23.0, Mendix 11.14.0, captured 2026-10-05), copied unchanged from +`tests/wave2/fixtures/context-pack/capture/describe/PAGE__Sales.Product_NewEdit.out`. It is a +real popup page: `Layout: Atlas_Core.PopupLayout`, a form grid, Cancel and Save in a footer. + +`product-newedit-popup.html` is **hand-drawn**, because issue #471 quotes the shape of the field +wireframe, not the file: `.popup-stage > .shell-behind + .dialog-scrim > .dialog`. The shell +behind holds the caller's page as such wireframes draw it (a sidebar, a `div.main` with a page +head, its `

` and a card), and the dialog holds the popup page's own title, fields, help +texts and buttons, taken from the describe above. The `wf-bar` and `wf-note` annotation +siblings are the toolkit's own convention. + +On master the scorer chose `div.main` inside `.shell-behind` and printed 18%, every miss from +the shell (the issue measured 29% on the field popup and 13% on a throwaway one, the same way). +With the fix it scores the dialog: 100%. + +Re-capture the wireframe from a real popup wireframe when one can be shared. diff --git a/tests/wave2/fixtures/page-fidelity-popup/product-newedit-describe-v023.mdl b/tests/wave2/fixtures/page-fidelity-popup/product-newedit-describe-v023.mdl new file mode 100644 index 00000000..34fcd84d --- /dev/null +++ b/tests/wave2/fixtures/page-fidelity-popup/product-newedit-describe-v023.mdl @@ -0,0 +1,63 @@ +create or modify page Sales.Product_NewEdit ( + Title: 'Catalog item', + Layout: Atlas_Core.PopupLayout, + Folder: 'Catalog/Pages', + Params: { $Product: Sales.Product } +) { + layoutgrid g { + row row1 { + column col1 (DesktopWidth: AutoFill) { + dataview dv (DataSource: $Product, ReadOnlyStyle: Control) { + -- Context: $currentObject (Product) + container formGrid (Class: 'form-grid') { + container fgSku (Class: 'form-group') { + textbox tbSku (Label: 'SKU *', Attribute: Sku) + dynamictext helpSku ( + Content: 'Set when the item is created. It cannot change afterwards.', + Class: 'form-help' + ) + } + container fgName (Class: 'form-group') { + textbox tbName (Label: 'Name *', Attribute: Name) + } + container fgDescription (Class: 'form-group span-2') { + textarea taDescription (Label: 'Description', Attribute: Description) + } + container fgCategory (Class: 'form-group') { + textbox tbCategory (Label: 'Category', Attribute: Category) + } + container fgPrice (Class: 'form-group') { + textbox tbPrice (Label: 'Unit price *', Attribute: UnitPrice) + } + container fgBrand (Class: 'form-group') { + combobox cbBrand ( + Label: 'Brand *', + Attribute: Product_Brand, + DataSource: database from Sales.Brand, + CaptionAttribute: Name + ) + } + container fgActive (Class: 'form-group') { + checkbox cbActive (Label: 'Active', Attribute: Active) + dynamictext helpActive ( + Content: 'Inactive items are not offered on new sales orders.', + Class: 'form-help' + ) + } + } + footer footer1 { + actionbutton btnCancel (Caption: 'Cancel', Action: cancel_changes close_page, Class: 'btn') + actionbutton btnSave ( + Caption: 'Save', + Action: microflow Sales.ACT_Product_Save(Product: $Product), + ButtonStyle: Primary, + Class: 'btn btn-primary' + ) + } + } + } + } + } +} + +grant view on page Sales.Product_NewEdit to Sales.Admin, Sales.SalesOfficer; diff --git a/tests/wave2/fixtures/page-fidelity-popup/product-newedit-popup.html b/tests/wave2/fixtures/page-fidelity-popup/product-newedit-popup.html new file mode 100644 index 00000000..d9675926 --- /dev/null +++ b/tests/wave2/fixtures/page-fidelity-popup/product-newedit-popup.html @@ -0,0 +1,48 @@ + + + + +Wireframe: Sales.Product_NewEdit (popup) + + + +
Wireframe · Sales.Product_NewEdit · popup over Product_Overview
+ +
The overview behind the scrim is the caller's page, drawn for context only.
+ + diff --git a/tests/wave2/test-page-fidelity-mocks.sh b/tests/wave2/test-page-fidelity-mocks.sh index abf768e7..a4676d87 100755 --- a/tests/wave2/test-page-fidelity-mocks.sh +++ b/tests/wave2/test-page-fidelity-mocks.sh @@ -247,6 +247,40 @@ hasnt "no layout hint when no missed class looks like chrome" "$OUT19" "is not i hasnt "no layout hint for page classes (seg-tabs, dg-*)" "$OUT6" "is not in the input" has "the hint names the layout when region-* is missed" "$OUT16" "layout LayoutTest.App_Default is not in the input" +echo " -- a popup wireframe scores the dialog, not the caller's page behind it (#471)" +# fixtures/page-fidelity-popup/CAPTURE.md: the MDL is a verbatim mxcli v0.23.0 describe of a +# popup page; the wireframe draws it in the issue's shape, .popup-stage > .shell-behind + +# .dialog-scrim > .dialog, with the caller's overview (div.main, h1, card) behind the scrim. +PF="$TOOLKIT/tests/wave2/fixtures/page-fidelity-popup" +OUT20=$(cd "$TMP" && node "$SUT" --no-log "$PF/product-newedit-popup.html" Product_NewEdit "$PF/product-newedit-describe-v023.mdl" 2>&1) +echo "$OUT20" | sed 's/^/ | /' +hasnt "the caller's h1 behind the scrim is not scored" "$OUT20" "heading: Product catalog" +hasnt "the caller's card behind the scrim is not scored" "$OUT20" "heading: Active items" +hasnt "the caller's button is not an owed action" "$OUT20" "action: New item" +has "the dialog's fields and buttons all match" "$OUT20" "text-match 100%" +sed 's/class="dialog" /class="sheet" /' "$PF/product-newedit-popup.html" > "$TMP/stage-only.html" +OUT21=$(cd "$TMP" && node "$SUT" --no-log "$TMP/stage-only.html" Product_NewEdit "$PF/product-newedit-describe-v023.mdl" 2>&1) +hasnt "a .popup-stage with no .dialog still drops the shell" "$OUT21" "heading: Product catalog" +has "...and still scores what is in front of it" "$OUT21" "headings 1/1" +sed 's/class="dialog" /class="panel" /; s/class="popup-stage"/class="stage"/' "$PF/product-newedit-popup.html" > "$TMP/no-popup.html" +OUT22=$(cd "$TMP" && node "$SUT" --no-log "$TMP/no-popup.html" Product_NewEdit "$PF/product-newedit-describe-v023.mdl" 2>&1) +has "popup evidence but no .dialog and no stage: the old div.main boundary, as before" "$OUT22" "heading: Product catalog" +# Popup evidence from the page alone: no stage, no scrim, but the MDL names Atlas_Core.PopupLayout. +sed 's/class="popup-stage"/class="stage"/; s/class="dialog-scrim"/class="scrim"/' "$PF/product-newedit-popup.html" > "$TMP/layout-only.html" +OUT23=$(cd "$TMP" && node "$SUT" --no-log "$TMP/layout-only.html" Product_NewEdit "$PF/product-newedit-describe-v023.mdl" 2>&1) +hasnt "a popup layout in the MDL is evidence enough: the shell is not scored" "$OUT23" "heading: Product catalog" +has "...and the dialog is" "$OUT23" "text-match 100%" +echo " -- a full-page wireframe with an inline .dialog scores as before (review of #482)" +# .dialog is the design system's component; a full page may draw an open confirm with it. On +# master this wireframe scored 39% (the confirm counted as page content); the first cut of #471 +# scored the confirm alone, 5%. No stage, no scrim, a full-page layout: no popup evidence. +sed 's#
#&#' \ + "$FIX/grouped-overview.html" > "$TMP/inline-dialog.html" +grep -q 'Delete project' "$TMP/inline-dialog.html" || bad "inline-dialog setup: the sed did not insert the confirm" "" +OUT24=$(cd "$TMP" && node "$SUT" --no-log "$TMP/inline-dialog.html" Demo_Overview "$FIX/demo-overview.mdl" 2>&1) +echo "$OUT24" | sed 's/^/ | /' +has "an inline confirm does not become the boundary: the master score, unchanged" "$OUT24" "text-match 39% headings 1/2 actions 1/3 content 1/2 classes 1/5" + echo printf 'test-page-fidelity-mocks: %d passed, %d failed\n' "$PASS" "$FAIL" [ "$FAIL" -eq 0 ]