diff --git a/.changepacks/changepack_log_notice_layout.json b/.changepacks/changepack_log_notice_layout.json new file mode 100644 index 0000000..9730616 --- /dev/null +++ b/.changepacks/changepack_log_notice_layout.json @@ -0,0 +1,7 @@ +{ + "changes": { + "crates/devup-mcp-devup-ui/Cargo.toml": "Minor" + }, + "note": "Two layout defects on the notice screens, found the week the harness stopped hiding them. A horizontal row can overflow collectively even when every child fits on its own, and CSS min-width:auto then refuses to let a FILL sibling shrink to its share: a navigation item Figma draws at 95px expanded to 316px, pushed an address block onto three extra lines, and made the tablet page 72px taller than the design. The existing minW=0 rule already covered the case where a single child is wider than its parent; it now also covers the row that only overflows once its children are added together, still measured from the snapshot rather than assumed, still skipped when the design states its own minWidth, and applied only to non-wrapping horizontal rows because a wrapping row distributes that content over more lines instead. Separately, an SVG mask with an export offset was refused a folded layout size outright, so a social icon the design gives 82x32 was generated at zero width and everything below it shifted. The refusal exists because a FILL axis would scale the layout box while the pixel mask offsets stay where they are, and because a positioned or free-layout asset is placed from its export box; none of that applies to an in-flow HUG or FIXED axis, which keeps its measured box while maskSize and maskPos go on describing the larger painted bounds. Both conditions read node fields only - layout mode, wrap, sizing, positioning, and measured widths - so neither carries a viewport, a breakpoint, a node id, or a constant fitted to the current capture. Measured against Figma's own PNG, the three notice screens improve from 5.54 / 3.35 / 2.22 to 5.25 / 3.22 / 2.20 percent, the tablet page height falls from 72 pixels over the design to 3, and the other twelve screens are byte-identical. All 268 plugin byte-parity goldens are unchanged.", + "date": "2026-09-13T13:05:00+09:00" +} diff --git a/crates/devup-mcp-devup-ui/src/codegen/layout.rs b/crates/devup-mcp-devup-ui/src/codegen/layout.rs index 344aeb3..dce56f3 100644 --- a/crates/devup-mcp-devup-ui/src/codegen/layout.rs +++ b/crates/devup-mcp-devup-ui/src/codegen/layout.rs @@ -591,11 +591,11 @@ pub(super) fn push_layout_props( // the share to 132 and took the 9px from the hugging sibling, whose // `Devup-ui` then broke across two lines. `minW="0"` lets the share // be the share, and the content spill as Figma draws it. Written - // only where the snapshot shows content wider than the box, which is - // the only place the two layouts part. - if let Some(own) = view.number("width") - && view - .child_ids() + // only where the snapshot shows content wider than the box. A row + // can overflow collectively even when each child fits individually. + // Wrapping rows can distribute that content over multiple lines. + let child_overflows = view.number("width").is_some_and(|own| { + view.child_ids() .filter_map(|id| snapshot.nodes.get(id)) .any(|child| { let child = child.typed_view(); @@ -603,7 +603,11 @@ pub(super) fn push_layout_props( && child.string("layoutPositioning") != Some("ABSOLUTE") && child.number("width").is_some_and(|width| width > own + 0.5) }) - { + }); + let row_overflows = view.string("layoutMode") == Some("HORIZONTAL") + && view.string("layoutWrap") != Some("WRAP") + && line_overflows(snapshot, node, "width", "paddingLeft", "paddingRight"); + if view.number("minWidth").is_none() && (child_overflows || row_overflows) { string_prop(props, "minW", "0"); } } @@ -1583,15 +1587,29 @@ pub(crate) fn folded_mask_dimensions(snapshot: &Snapshot, node: &RawNode) -> Opt let view = node.typed_view(); if super::style::asset_kind(snapshot, node) != Some(super::style::AssetKind::SvgMask) || view.child_ids().next().is_none() - // Rotated exports and pixel mask offsets need a different transform - // proof. Leave them unresolved instead of inventing responsive sizes. + // Rotated exports need a separate transform proof. || view.number("rotation").is_some_and(|rotation| rotation.abs() > 0.01) - || export_offset(node).is_some() || !(view.string("layoutSizingHorizontal") == Some("HUG") || view.string("layoutSizingVertical") == Some("HUG")) { return None; } + if export_offset(node).is_some() { + // In-flow HUG/FIXED axes retain the measured layout box. The export's + // larger painted bounds are independently preserved by maskSize and + // maskPos. A FILL axis would scale that box while leaving pixel mask + // offsets unchanged; positioned assets instead use the export box. + let parent = view + .string("parentId") + .and_then(|id| snapshot.nodes.get(id)); + if !matches!(view.string("layoutSizingHorizontal"), Some("HUG" | "FIXED")) + || !matches!(view.string("layoutSizingVertical"), Some("HUG" | "FIXED")) + || view.string("layoutPositioning") == Some("ABSOLUTE") + || placed_by_a_free_layout(snapshot, node, parent, false) + { + return None; + } + } let measured = |axis| { view.number(axis) .or_else(|| view.value("absoluteBoundingBox")?.get(axis)?.as_f64()) diff --git a/crates/devup-mcp-devup-ui/src/provenance.rs b/crates/devup-mcp-devup-ui/src/provenance.rs index 530036c..48c8508 100644 --- a/crates/devup-mcp-devup-ui/src/provenance.rs +++ b/crates/devup-mcp-devup-ui/src/provenance.rs @@ -1585,7 +1585,13 @@ pub(crate) fn finalize_tsx( } else { "raw-fallback" }; - let source_property = if *prop == "fontWeight" + let source_property = if *prop == "minW" + && value == "0" + && node.typed_view().number("minWidth").is_none() + && node.typed_view().string("layoutSizingHorizontal") == Some("FILL") + { + "layoutSizingHorizontal" + } else if *prop == "fontWeight" && node.node_type == "TEXT" && node.typed_view().number(property).is_none() { diff --git a/crates/devup-mcp-devup-ui/tests/folded_asset_size.rs b/crates/devup-mcp-devup-ui/tests/folded_asset_size.rs index c051f3b..406eda1 100644 --- a/crates/devup-mcp-devup-ui/tests/folded_asset_size.rs +++ b/crates/devup-mcp-devup-ui/tests/folded_asset_size.rs @@ -64,6 +64,159 @@ fn wquw119() -> devup_mcp_figma::Snapshot { serde_json::from_str(include_str!("../../../fixtures/wquw-119-snapshot.json")).unwrap() } +fn offset_hug_mask(horizontal: &str, vertical: &str) -> devup_mcp_figma::Snapshot { + let mut snapshot = wquw119(); + let node = snapshot.nodes.get_mut("3997:46317").unwrap(); + for (field, value) in [ + ("layoutSizingHorizontal", json!(horizontal)), + ("layoutSizingVertical", json!(vertical)), + ("width", json!(137)), + ("height", json!(29)), + ( + "absoluteBoundingBox", + json!({"x":10,"y":20,"width":137,"height":29}), + ), + ( + "absoluteRenderBounds", + json!({"x":7,"y":18,"width":143,"height":33}), + ), + ] { + node.fields.insert(field.into(), value); + } + snapshot +} + +#[test] +fn offset_hug_mask_restores_layout_size_without_replacing_painted_bounds() { + for (horizontal, vertical) in [("HUG", "FIXED"), ("FIXED", "HUG"), ("HUG", "HUG")] { + let snapshot = offset_hug_mask(horizontal, vertical); + let output = + generate_component(&snapshot, "3997:46315", &CodegenOptions::default()).unwrap(); + for expected in [ + "w=\"137px\"", + "h=\"29px\"", + "maskSize=\"143px 33px\"", + "maskPos=\"-3px -2px\"", + ] { + assert!( + output.tsx.contains(expected), + "missing {expected}: {}", + output.tsx + ); + } + assert!(!output.tsx.contains("aspectRatio=")); + } +} + +#[test] +fn offset_hug_mask_maps_restored_width_and_rejects_wrong_size() { + let snapshot = offset_hug_mask("HUG", "FIXED"); + let output = generate_component(&snapshot, "3997:46315", &CodegenOptions::default()).unwrap(); + for field in ["width", "layoutSizingHorizontal", "childrenIds"] { + assert!( + output.source_map.entries.iter().any(|entry| { + entry.node_id.as_deref() == Some("3997:46317") + && entry.property.as_deref() == Some(field) + && entry.resolution == "restored-hug-after-mask-child-folding" + && entry + .generated_range + .as_ref() + .is_some_and(|r| output.tsx.get(r.start..r.end) == Some("w=\"137px\"")) + }), + "missing {field} restoration evidence" + ); + } + assert!( + !output + .fidelity_report + .uncovered_layout + .contains(&"3997:46317#width".to_owned()) + ); + let mut wrong = output.clone(); + wrong.tsx = wrong.tsx.replace("w=\"137px\"", "w=\"143px\""); + let report = + devup_mcp_devup_ui::provenance::validate_fidelity(&snapshot, "3997:46315", &wrong).unwrap(); + assert!( + report + .uncovered_layout + .contains(&"3997:46317#width".to_owned()) + ); +} + +#[test] +fn offset_hug_mask_can_restore_layout_bounding_box_fallback() { + let mut snapshot = offset_hug_mask("HUG", "HUG"); + let node = snapshot.nodes.get_mut("3997:46317").unwrap(); + node.fields.remove("width"); + node.fields.remove("height"); + let output = generate_component(&snapshot, "3997:46315", &CodegenOptions::default()).unwrap(); + assert!(output.tsx.contains("w=\"137px\""), "{}", output.tsx); + assert!(output.tsx.contains("h=\"29px\"")); + assert!( + output + .source_map + .entries + .iter() + .any(|entry| entry.node_id.as_deref() == Some("3997:46317") + && entry.property.as_deref() == Some("width") + && entry.resolution + == "restored-hug-after-mask-child-folding-from-absoluteBoundingBox") + ); +} + +#[test] +fn offset_mask_restoration_does_not_claim_fluid_rotated_or_positioned_geometry() { + for excluded in [ + "horizontal-fill", + "vertical-fill", + "rotation", + "absolute", + "free-layout", + "missing-size", + ] { + let mut snapshot = offset_hug_mask("HUG", "HUG"); + let node = snapshot.nodes.get_mut("3997:46317").unwrap(); + match excluded { + "horizontal-fill" => { + node.fields + .insert("layoutSizingHorizontal".into(), json!("FILL")); + } + "vertical-fill" => { + node.fields + .insert("layoutSizingVertical".into(), json!("FILL")); + } + "rotation" => { + node.fields.insert("rotation".into(), json!(30)); + } + "absolute" => { + node.fields + .insert("layoutPositioning".into(), json!("ABSOLUTE")); + } + "missing-size" => { + node.fields.remove("width"); + node.fields.remove("absoluteBoundingBox"); + } + "free-layout" => { + let parent = snapshot.nodes.get_mut("3997:46316").unwrap(); + parent.fields.insert("layoutMode".into(), json!("NONE")); + parent.fields.remove("inferredAutoLayout"); + } + _ => unreachable!(), + } + let output = + generate_component(&snapshot, "3997:46315", &CodegenOptions::default()).unwrap(); + assert!( + !output + .source_map + .entries + .iter() + .any(|entry| entry.node_id.as_deref() == Some("3997:46317") + && entry.resolution.starts_with("restored-hug")), + "unexpected restoration for {excluded}" + ); + } +} + #[test] fn r5_wquw119_hug_mask_preserves_render_size_and_source_map() { let snapshot = wquw119(); diff --git a/crates/devup-mcp-devup-ui/tests/horizontal_fill_overflow.rs b/crates/devup-mcp-devup-ui/tests/horizontal_fill_overflow.rs new file mode 100644 index 0000000..b4799f1 --- /dev/null +++ b/crates/devup-mcp-devup-ui/tests/horizontal_fill_overflow.rs @@ -0,0 +1,140 @@ +use devup_mcp_devup_ui::codegen::{CodegenOptions, CodegenOutput, generate_component}; +use devup_mcp_figma::Snapshot; +use serde_json::json; + +fn scene() -> Snapshot { + serde_json::from_value(json!({ + "fileKey": "fill-overflow", "roots": ["root"], "diagnostics": [], + "nodes": { + "root": {"id":"root", "type":"FRAME", "fields":{ + "layoutMode":"HORIZONTAL", "layoutSizingHorizontal":"FIXED", + "layoutSizingVertical":"HUG", "width":320, "height":40, + "itemSpacing":40, "childrenIds":["hug","fill"] + }}, + "hug": {"id":"hug", "type":"FRAME", "fields":{ + "parentId":"root", "layoutMode":"VERTICAL", + "layoutSizingHorizontal":"HUG", "layoutSizingVertical":"HUG", + "width":160, "height":40, "childrenIds":[] + }}, + "fill": {"id":"fill", "type":"FRAME", "fields":{ + "parentId":"root", "layoutMode":"HORIZONTAL", + "layoutSizingHorizontal":"FILL", "layoutSizingVertical":"HUG", + "width":120, "height":40, "itemSpacing":20, + "primaryAxisAlignItems":"MAX", "childrenIds":["a","b"] + }}, + "a": {"id":"a", "type":"RECTANGLE", "fields":{ + "parentId":"fill", "width":70, "height":40, + "layoutSizingHorizontal":"FIXED", "layoutSizingVertical":"FIXED" + }}, + "b": {"id":"b", "type":"RECTANGLE", "fields":{ + "parentId":"fill", "width":70, "height":40, + "layoutSizingHorizontal":"FIXED", "layoutSizingVertical":"FIXED" + }} + } + })) + .unwrap() +} + +fn generate(snapshot: &Snapshot) -> CodegenOutput { + let mut snapshot = snapshot.clone(); + for node in snapshot.nodes.values_mut() { + if node.fields.contains_key("layoutMode") { + node.fields + .insert("inferredAutoLayout".into(), json!(node.fields)); + } + } + generate_component(&snapshot, "root", &CodegenOptions::default()).unwrap() +} + +fn fill_tag(output: &CodegenOutput) -> &str { + let entry = output + .source_map + .entries + .iter() + .find(|entry| entry.node_id.as_deref() == Some("fill") && entry.property.is_none()) + .unwrap(); + let range = entry.generated_range.as_ref().unwrap(); + output.tsx[range.start..range.end] + .split('>') + .next() + .unwrap() +} + +#[test] +fn collectively_overflowing_row_keeps_its_fill_allocation() { + let output = generate(&scene()); + // Neither 70px child exceeds the 120px allocation; together with the + // 20px gap they do. CSS must allow the FILL box to remain 120px wide. + assert!(fill_tag(&output).contains("minW=\"0\""), "{}", output.tsx); + assert!(fill_tag(&output).contains("flex=\"1\"")); + assert!(!fill_tag(&output).contains("120px")); +} + +#[test] +fn derived_minimum_maps_to_fill_intent_not_an_absent_min_width() { + let output = generate(&scene()); + let mappings: Vec<_> = output + .source_map + .entries + .iter() + .filter(|entry| { + entry.node_id.as_deref() == Some("fill") + && entry.generated_range.as_ref().is_some_and(|range| { + output.tsx.get(range.start..range.end) == Some("minW=\"0\"") + }) + }) + .collect(); + assert!( + mappings + .iter() + .any(|entry| entry.property.as_deref() == Some("layoutSizingHorizontal")) + ); + assert!( + !mappings + .iter() + .any(|entry| entry.property.as_deref() == Some("minWidth")) + ); +} + +#[test] +fn fitting_wrapping_and_out_of_flow_children_do_not_force_a_reset() { + for (field, value) in [ + ("width", json!(180)), + ("layoutWrap", json!("WRAP")), + ("layoutMode", json!("VERTICAL")), + ] { + let mut snapshot = scene(); + snapshot + .nodes + .get_mut("fill") + .unwrap() + .fields + .insert(field.into(), value); + assert!(!fill_tag(&generate(&snapshot)).contains("minW="), "{field}"); + } + for (field, value) in [ + ("visible", json!(false)), + ("layoutPositioning", json!("ABSOLUTE")), + ] { + let mut snapshot = scene(); + snapshot + .nodes + .get_mut("b") + .unwrap() + .fields + .insert(field.into(), value); + assert!(!fill_tag(&generate(&snapshot)).contains("minW="), "{field}"); + } +} + +#[test] +fn an_explicit_minimum_is_preserved() { + let mut snapshot = scene(); + snapshot + .nodes + .get_mut("fill") + .unwrap() + .fields + .insert("minWidth".into(), json!(100)); + assert!(fill_tag(&generate(&snapshot)).contains("minW=\"100px\"")); +} diff --git a/docs/notice-layout-divergence.md b/docs/notice-layout-divergence.md new file mode 100644 index 0000000..0933e7f --- /dev/null +++ b/docs/notice-layout-divergence.md @@ -0,0 +1,287 @@ +# Notice layout divergence + +Base: `9c27de0301ff5eb9dd9f746bd9d05f5c9e102154`, measured on 2026-09-13. + +## Localisation before implementation + +Read `line-box-displacement.md` and `about-vertical-geometry.md` before +investigation. Built the unmodified base in this worktree's own target, +acquired notice with that binary, and rendered it with the unchanged harness. +The fresh baseline reproduces the dispatch's values exactly: + +| Screen | Design height | Baseline rendered height | Baseline divergence | +| --- | ---: | ---: | ---: | +| notice-422-6914 | 1215 | 1217 | 5.54% | +| notice-422-7088 | 1142 | 1214 | 3.35% | +| notice-422-6865 | 1142 | 1145 | 2.22% | + +`bands.mjs` localises tablet's worst band to y=900–1000 (9.46%). +`drift.mjs` finds the preceding y=700–900 content at approximately +3px; +the footer does not match through a uniform translation. The footer crop +shows extra address lines and a wrapped SWING EZ label. `boxes.mjs` and +the collected snapshot identify the exact source of the page's +72px: + +* Address text `I422:7136;147:985`: Figma **397×115**, browser **176×184**. + It gains **69px**, exactly three of its already-correct 23px line advances. +* Its company-details column `I422:7136;147:983`: 143px becomes 212px. +* Footer `422:7136`: design y=879/h=263, browser y=882/h=332. + The 69px internal growth plus the existing upstream 3px gives +72px. +* Navigation sibling `I422:7136;147:986`: Figma allocates **95px** of the + footer's 872px content width, but CSS gives it **316px**. Its four HUG + children total 256px, with three 30px gaps: **346px** of content. Every + individual child is narrower than 95px, so the existing single-child + overflow test misses the row's collective overflow. + +A browser-only probe changes just that navigation's `min-width` to zero. +The address returns to 115px, the company column to 143px, and the page to +**1145px**. No font, line advance, weight, border, or Korean wrapping policy +changes. This rules out recurrence of the three previously fixed causes for +the 69px growth. The remaining upstream 3px is not corrected here. + +## Mobile is independent + +Mobile's worst band is y=900–1000 (14.64%). The crop shows two missing +social icons and a displaced theme toggle, not a vertically growing footer. +The social row `I422:6963;265:2564` has a collected 82×32 layout box and an +82×36 export beginning at y=-2 relative to that box. Codegen correctly emits +`maskSize="82px 36px"` and `maskPos="0px -2px"`, but omits the HUG width. +`boxes.mjs` measures the mask as **0×32** and its parent as 80×32 instead +of 162×32. Centering that narrower parent moves the toggle from x=99 to x=140. + +The width is lost because `folded_mask_dimensions` excludes every node with +an export offset. Child folding removes the intrinsic width, and CSS masks +provide no intrinsic layout size. `elements.mjs` reports the 100×95 search +illustration correctly; it does not enumerate mask images, so the zero-width +mask finding comes from DOM boxes and the crop rather than that script. + +A separate browser-only probe restores the collected 82px mask width, keeping +the generated module unchanged. The mask becomes 82×32, its parent becomes +162×32 at x=99, and the toggle returns to x=99; page height stays 1217px. +`w16-mask-probe.log` records the intervention. This is a localisation probe, +not a generator candidate or a claimed mobile divergence improvement. + +The tablet navigation correction cannot affect this mobile mask. A scope +question was sent to the coordinator because the dispatch requires mobile +improvement but permits a mobile fix only through the same change. + +## Tests and measurement records + +The main checkout's ignored `harness/render/out/` holds `w16-*` logs, +baseline PNGs in `w16-baseline/`, and the browser-only probe. Harness scripts +were not edited. The initial unfiltered render correctly refused other +groups acquired by a different binary; subsequent runs acquired and rendered +each group separately with the explicit worktree binary. + +Baseline binary SHA-256: +`f657817d22e026ea81cad892f46c903c68ba5517c62a69e0058ab8c86fc16dea`. + +The tablet tests first recorded **2 passed / 2 failed** in +`w16-fill-red.log`: missing `minW="0"` for collective overflow and missing +source mapping for that derived minimum. After implementation all four pass. +The other tests preserve explicit minima and exclude fitting rows, wrapping +rows, vertical columns, hidden children and absolutely positioned children. + +All 268 plugin goldens remain unchanged in the tablet-only candidate; +`compat_fixtures` and `compat_manifest` pass. No manifest checksum changes. + +Fresh non-notice baseline measurements: + +| Group | Baseline divergence, narrow to wide | +| --- | --- | +| about | 7.46 / 4.06 / 2.41% | +| landing | 4.99 / 2.47 / 1.50% | +| popup | 3.64 / 2.06 / 0.85% | +| grid | 2.96% | +| keyframes | 6.71% | +| report | 1.28% | + +## Tablet-only candidate + +Candidate binary SHA-256: +`009214264ff45012c331c04f4257f2c8a62cc3ce494b02a406560e5734c1e3e1`. + +The change extends the existing FILL minimum reset to non-wrapping horizontal +rows whose visible, in-flow child widths plus gaps exceed their inner width. +It keeps explicit minimum widths. The new `minW="0"` mapping names the +source's `layoutSizingHorizontal`, not a `minWidth` field that does not exist. +No frame ID, viewport width, breakpoint, or capture-specific constant enters +the condition. The existing 0.5px geometry comparison tolerance is reused. + +| Notice width | Baseline | Candidate | Fresh repeated candidate | Candidate height | +| --- | ---: | ---: | ---: | ---: | +| 360 | 5.54% | 5.54% | 5.54% | 1217 | +| 992 | 3.35% | 3.26% | 3.26% | 1145 | +| 1920 | 2.22% | 2.22% | 2.22% | 1145 | + +Every non-notice group was freshly acquired and rendered with the candidate. +All 14 other actual PNGs, including mobile and desktop notice, are +**byte-identical** to their respective fresh baseline PNGs. Thus no unchanged +percentage conceals a smaller regression. The non-notice table above applies +equally to the candidate. Only the tablet notice threshold is lowered, to 3.26, +after the second fresh acquire/render confirms it. + +The mobile improvement acceptance condition is **not met** by this candidate. +Its independent folded-mask width defect is identified above and is untouched +under the original same-change-only scope. The coordinator scope question +received no reply during the work, so no expansion was assumed. This is not +a claim that every acceptance criterion has passed. + +The documented negative is specific: the mobile mask is HUG horizontally, +whereas the corrected mechanism allocates a FILL child of a horizontal flex +parent. It cannot restore this mask's erased child-based intrinsic width. +The next cycle should address the export-offset exclusion in +`folded_mask_dimensions`, proving layout dimensions separately from the +export's larger painted bounds and preserving the existing mask offset. +Removing that guard indiscriminately would also admit FILL/ratio cases whose +pixel offsets need a separate scaling proof. Neither Korean line breaking +nor the three already-fixed text/stroke causes explains the missing mask. + +## Verification + +| Required gate | Result | +| --- | --- | +| `cargo fmt --all -- --check` | Pass | +| `cargo clippy --locked --workspace --all-targets --all-features -j 2 -- -D warnings` | Pass, zero warnings | +| `cargo test --workspace -j 2 --no-fail-fast` | 1063 passed, 0 failed, 2 ignored | +| `cargo insta test --workspace --all-features --check` | 1063 passed, 0 failed, 2 ignored; no snapshots to review | +| `cargo test --locked -p devup-mcp --test stdio_smoke -j 2` | 2 passed, 0 failed | + +The UI-only run separately passed 327 tests. All commands use +`CARGO_PROFILE_DEV_DEBUG=0`, `CARGO_PROFILE_TEST_DEBUG=0`, +`CARGO_INCREMENTAL=0`, and two build jobs (`CARGO_BUILD_JOBS=2` for the +insta wrapper). `CARGO_TARGET_DIR` was never set. Ordinary MSVC build/test +linking prints its existing localized library-creation warning; the clippy +invocation itself is clean. Logs are `w16-workspace.log`, `w16-insta.log`, +`w16-clippy.log`, and `w16-smoke.log` in the main harness's ignored `out/`. + +The workspace test build changed executable identity without production +source changes. Its binary was preserved as +`target/debug/devup-mcp-w16-final.exe`, SHA-256 +`06707befc5571f73030459e86493bf5ae959fac84149f1b79f85a1b46f823c92`, +and final measurements were reacquired from that exact immutable copy. +The later isolated smoke build cannot overwrite the measured copy. +All 15 final actual PNGs match the first candidate, and all final reference +PNGs match baseline. Final notice was acquired and rendered twice, again +confirming 5.54 / 3.26 / 2.22% and 1217 / 1145 / 1145px. The hashes are recorded +in [notice-layout-evidence.json](notice-layout-evidence.json). + +No plugin golden, manifest checksum, forbidden Rust file, harness script, +Korean `wordBreak` rule, or corpus consistency assertion changed. + +The change is committed locally without pushing. Build output is cleaned +after the commit as requested; acquisition and diagnostic browser processes +were closed, and no worktree devup-mcp process is retained. + +## Second fix: offset masks retain their HUG layout size + +The follow-up explicitly authorizes the independent mobile defect. This work +starts on `d4b2056427ab`, preserving the tablet commit without amending or +squashing it. The scope limitation in the earlier sections records the first +dispatch's history; it does not apply to this authorized second fix. + +The mechanism was independently rechecked using a fresh build of the HEAD +production sources, fresh acquisition, the generated TSX, collected snapshot, +and `boxes.mjs`. The baseline mask is **0×32** at x=220, its parent **80×32** +at x=140, and the toggle x=140. The collected node is **82×32**, with horizontal +HUG and vertical FIXED sizing. Its exported painted bounds are **82×36**, +offset **0,-2**. The generated opening has `h="32px"`, `maskSize="82px 36px"` +and `maskPos="0px -2px"`, but no width. Folding removes the children that +would supply an intrinsic HUG width; the unconditional `export_offset` +exclusion prevents restoring it. The baseline PNG visibly lacks both social +icons. The baseline production binary was preserved before implementation; +only regression-test additions were present during its build/acquisition. + +The fix separates layout geometry from painted export geometry. An offset +mask can restore measured HUG axes when both axes are HUG/FIXED and it stays +in normal flow. Existing `maskSize` and `maskPos` continue to describe the +export. FILL axes, rotation, explicit absolute positioning and placement by a +free-layout parent remain excluded: their scaling or positioning needs a +different proof. There is no viewport, breakpoint, node ID or capture-specific +constant in the production condition. The earlier tablet `minW` logic and +Korean `wordBreak="keep-all"` are unchanged. + +The same defect exists in the wider notice social masks +`I422:7136;148:1436` and `I422:6913;148:1436`: both are HUG/FIXED 82×32 with +82×36 painted bounds at y=-2. Their width restoration is the same data-driven +operation, not a separate tablet or desktop path. + +### Regression and provenance evidence + +Before implementation, `w16-mask-red.log` records **19 passed / 3 failed**. +The failures are missing HUG layout dimensions, missing restored-width source +mapping, and missing bounding-box fallback dimensions. The tests use a +different **137×29** layout and **143×33** export at **-3,-2**, exercising +HUG/FIXED, FIXED/HUG and HUG/HUG combinations rather than copying the notice +capture's values. Negative cases cover both FILL axes, rotation, explicit +absolute placement, free-layout placement and missing layout measurements. +All **22** folded-asset tests subsequently pass. + +The existing shared restoration predicate also drives provenance, so the new +width maps to the measured width, HUG sizing and folded children through +`restored-hug-after-mask-child-folding`. Bounding-box fallback retains the +existing fallback resolution. A test replaces the correct 137px width with +the export's 143px width and verifies fidelity rejects it. Mobile's actual +layout coverage changes from **118/122 to 119/122**, removing exactly +`I422:6963;265:2564#width` from `uncoveredLayout`. Its other three uncovered +layout properties remain reported; the screen is still lossy overall. + +All **268 plugin goldens** pass byte parity unchanged, and the corpus manifest +and coverage registry pass. No golden, manifest checksum, corpus assertion, +forbidden Rust file or `harness/render/scripts/` file changes. + +### Fresh measurements and full gate for the second fix + +| Screen | HEAD baseline | Repeated baseline | Mask candidate | Post-test final binary | Rendered height | +| --- | ---: | ---: | ---: | ---: | ---: | +| notice-422-6914 | 5.54% | 5.54% | 5.25% | 5.25% | 1217 | +| notice-422-7088 | 3.26% | 3.26% | 3.22% | 3.22% | 1145 | +| notice-422-6865 | 2.22% | 2.22% | 2.20% | 2.20% | 1145 | + +The exact mobile changed ratio is **0.05541838134430727 → +0.05254229538180155**, a decrease of approximately **0.288 percentage points**. +Restoring the mask does not change page height. The first candidate's other +12 actual PNGs are byte-identical to the fresh HEAD baseline, and all 15 +reference hashes match. Their percentages remain about **7.46 / 4.06 / 2.41**, +landing **4.99 / 2.47 / 1.50**, popup **3.64 / 2.06 / 0.85**, grid **2.96**, +keyframes **6.71**, and report **1.28**. + +| Required gate | Second-fix result | +| --- | --- | +| `cargo fmt --all -- --check` | Pass | +| `cargo clippy --locked --workspace --all-targets --all-features -j 2 -- -D warnings` | Pass, zero warnings | +| `cargo test --workspace -j 2 --no-fail-fast` | 1067 passed, 0 failed, 2 ignored | +| `cargo insta test --workspace --all-features --check` | 1067 passed, 0 failed, 2 ignored; no snapshots to review | +| `cargo test --locked -p devup-mcp --test stdio_smoke -j 2` | 2 passed, 0 failed | + +All gates run in this worktree's own target with debug information and +incremental compilation disabled and two build jobs, including +`CARGO_BUILD_JOBS=2` for insta. `CARGO_TARGET_DIR` is never set. The ordinary +MSVC link steps retain their existing localized library-creation warning; +the Clippy command itself reports zero warnings. Logs use the +`w16-mask-{red,green,fmt,clippy,workspace,insta,smoke}.log` names in the main +checkout's ignored harness `out/` directory. Measurement logs and preserved +reports/PNGs use `w16-mask-baseline`, `w16-mask-candidate` and `w16-mask-final`. + +The preserved binaries have distinct SHA-256 identities: + +* HEAD production baseline: `1c9dda74c8503ab40364751bc58a1db422819a91c267cd3aa40380431aa9c76d`. +* First mask candidate: `516476ac9fad7b511e62811517d2a1bfbd66145e96c2e11bbad844710ba4d253`. +* Post-test final: `28d4c01357b324d7521d18a145ac232871ed1c9853036d9f28f6f36963fe0d06`. + +Every measurement is freshly acquired with the exact binary passed through +`DEVUP_MCP_BIN`. The post-test executable is copied after the final smoke +gate, so no subsequent test build can replace its measured bytes. No binary +identity check is bypassed, and the harness scripts remain unmodified. + +Final repeated acquisition confirms **5.25 / 3.22 / 2.20%** again. All 15 final +actual PNGs, exact ratios and heights match the first candidate; all reference +hashes match baseline. The final DOM measures the mask **82×32** at x=179, +its parent **162×32** at x=99, and the toggle x=99. The social icons are visible +again. Only the three measured notice thresholds are lowered, after repetition, +to **5.25 / 3.22 / 2.20**. The `maskFix` section of +[notice-layout-evidence.json](notice-layout-evidence.json) preserves the exact +ratios, repeated notice ratios, actual/reference hashes and binary identities. + +The second fix is committed separately on top of `d4b2056`, without pushing. +Build output is cleaned after that commit, and the acquisition/diagnostic +processes started for this task are closed. diff --git a/docs/notice-layout-evidence.json b/docs/notice-layout-evidence.json new file mode 100644 index 0000000..f678454 --- /dev/null +++ b/docs/notice-layout-evidence.json @@ -0,0 +1,532 @@ +{ + "base": "9c27de0301ff5eb9dd9f746bd9d05f5c9e102154", + "baselineBinarySha256": "f657817d22e026ea81cad892f46c903c68ba5517c62a69e0058ab8c86fc16dea", + "candidateBinarySha256": "009214264ff45012c331c04f4257f2c8a62cc3ce494b02a406560e5734c1e3e1", + "screens": [ + { + "screen": "about-422-2987", + "baselineActualSha256": "69f144c1ebe9ea6eb5c3ae056fe8db709f7b95f5cdedaf73e169a2b41ae0d47b", + "candidateActualSha256": "69f144c1ebe9ea6eb5c3ae056fe8db709f7b95f5cdedaf73e169a2b41ae0d47b", + "actualIdentical": true, + "referenceIdentical": true, + "referenceSha256": "6dc173242afb2bc5ad5d7262d4d23acd1ec7fcc468db32c413cc5356b3dd9120", + "finalActualSha256": "69f144c1ebe9ea6eb5c3ae056fe8db709f7b95f5cdedaf73e169a2b41ae0d47b", + "finalMatchesCandidate": true, + "finalReferenceMatchesBaseline": true + }, + { + "screen": "about-422-3180", + "baselineActualSha256": "bb18213011cc6ebd7cbe384a2a40720d4966437dffc51de957cf0c9e29b9d271", + "candidateActualSha256": "bb18213011cc6ebd7cbe384a2a40720d4966437dffc51de957cf0c9e29b9d271", + "actualIdentical": true, + "referenceIdentical": true, + "referenceSha256": "9487389f99ba9d199262ddf0c0e8acaed0679b0aadceeb535cecb99f6b3e1faa", + "finalActualSha256": "bb18213011cc6ebd7cbe384a2a40720d4966437dffc51de957cf0c9e29b9d271", + "finalMatchesCandidate": true, + "finalReferenceMatchesBaseline": true + }, + { + "screen": "about-422-3376", + "baselineActualSha256": "ac23b6b562c1b09ffcd1491487c7c8e353c8d274d61f0360edb14934c17efef9", + "candidateActualSha256": "ac23b6b562c1b09ffcd1491487c7c8e353c8d274d61f0360edb14934c17efef9", + "actualIdentical": true, + "referenceIdentical": true, + "referenceSha256": "183b29572ab786dbe588e0bfed57db8210920e4f070dfb22226065a6545c17f8", + "finalActualSha256": "ac23b6b562c1b09ffcd1491487c7c8e353c8d274d61f0360edb14934c17efef9", + "finalMatchesCandidate": true, + "finalReferenceMatchesBaseline": true + }, + { + "screen": "grid-429-1966", + "baselineActualSha256": "a7ea4c4023061e9851972a8ad633af22fa160a0d610621d98e00529959cab69f", + "candidateActualSha256": "a7ea4c4023061e9851972a8ad633af22fa160a0d610621d98e00529959cab69f", + "actualIdentical": true, + "referenceIdentical": true, + "referenceSha256": "251925cfa907bcb24ff2077cbf81a778a0be9a9df62a9532359bea231f468e10", + "finalActualSha256": "a7ea4c4023061e9851972a8ad633af22fa160a0d610621d98e00529959cab69f", + "finalMatchesCandidate": true, + "finalReferenceMatchesBaseline": true + }, + { + "screen": "keyframes-458-2021", + "baselineActualSha256": "49551a58d3283ba4cb5e1277893db55014aecc0b05d2752d8f081c2ee48ab225", + "candidateActualSha256": "49551a58d3283ba4cb5e1277893db55014aecc0b05d2752d8f081c2ee48ab225", + "actualIdentical": true, + "referenceIdentical": true, + "referenceSha256": "94ff08ecd5dac40feeb9ba154e40f0f527dbcaa31c02f5cbf0c03dae25ede191", + "finalActualSha256": "49551a58d3283ba4cb5e1277893db55014aecc0b05d2752d8f081c2ee48ab225", + "finalMatchesCandidate": true, + "finalReferenceMatchesBaseline": true + }, + { + "screen": "landing-832-2975", + "baselineActualSha256": "1bf31cea5d1dbf5d6a5c2b4eb35fab39b6800f56bf1905a9278621e08c2787d3", + "candidateActualSha256": "1bf31cea5d1dbf5d6a5c2b4eb35fab39b6800f56bf1905a9278621e08c2787d3", + "actualIdentical": true, + "referenceIdentical": true, + "referenceSha256": "68b0743556479681e642b389f89fd2a082d86c6d634884739bd8725522cf8c6b", + "finalActualSha256": "1bf31cea5d1dbf5d6a5c2b4eb35fab39b6800f56bf1905a9278621e08c2787d3", + "finalMatchesCandidate": true, + "finalReferenceMatchesBaseline": true + }, + { + "screen": "landing-833-3322", + "baselineActualSha256": "619165013992405a3bf47132235f2e735ff2a1ec39686b79d4c61bfa1d0bf279", + "candidateActualSha256": "619165013992405a3bf47132235f2e735ff2a1ec39686b79d4c61bfa1d0bf279", + "actualIdentical": true, + "referenceIdentical": true, + "referenceSha256": "f3eebec1a9e5f574922b4f616d08abae92f9cada366213d4ac4d748553bd96a9", + "finalActualSha256": "619165013992405a3bf47132235f2e735ff2a1ec39686b79d4c61bfa1d0bf279", + "finalMatchesCandidate": true, + "finalReferenceMatchesBaseline": true + }, + { + "screen": "landing-833-3640", + "baselineActualSha256": "f35490cf62a1d7f3622a210624366fadb6e81f5415d5b03a5979b23dae372cdd", + "candidateActualSha256": "f35490cf62a1d7f3622a210624366fadb6e81f5415d5b03a5979b23dae372cdd", + "actualIdentical": true, + "referenceIdentical": true, + "referenceSha256": "d66ec480cec7ae66c6e2ffe03161b763dc7d446c064fd82318a4d0f3d3e9bb92", + "finalActualSha256": "f35490cf62a1d7f3622a210624366fadb6e81f5415d5b03a5979b23dae372cdd", + "finalMatchesCandidate": true, + "finalReferenceMatchesBaseline": true + }, + { + "screen": "notice-422-6865", + "baselineActualSha256": "ff48e72f467543b6b058d835c1309990f975e2339cceb15e9e3dccce634bda70", + "candidateActualSha256": "ff48e72f467543b6b058d835c1309990f975e2339cceb15e9e3dccce634bda70", + "actualIdentical": true, + "referenceIdentical": true, + "referenceSha256": "73049f14e3be6c308652418bf69f7d53af44f0df650241b29e0e7aff16c3a1b4", + "finalActualSha256": "ff48e72f467543b6b058d835c1309990f975e2339cceb15e9e3dccce634bda70", + "finalMatchesCandidate": true, + "finalReferenceMatchesBaseline": true + }, + { + "screen": "notice-422-6914", + "baselineActualSha256": "6e105a63de769172198b74b4b552b8b22e8d5c8e2252aba399cb42abcef0fc83", + "candidateActualSha256": "6e105a63de769172198b74b4b552b8b22e8d5c8e2252aba399cb42abcef0fc83", + "actualIdentical": true, + "referenceIdentical": true, + "referenceSha256": "421d8a6fdda27079f183c6927c0672568648be3b23e37526938eb58c1276764a", + "finalActualSha256": "6e105a63de769172198b74b4b552b8b22e8d5c8e2252aba399cb42abcef0fc83", + "finalMatchesCandidate": true, + "finalReferenceMatchesBaseline": true + }, + { + "screen": "notice-422-7088", + "baselineActualSha256": "1affdd88ad4e564b486b7cba678985c3e7b8f0149a396b6d90ec643351841726", + "candidateActualSha256": "b24d538b465d7a7f87a6bd1509be216518fc05fd84993e3826b624fa6e4e3207", + "actualIdentical": false, + "referenceIdentical": true, + "referenceSha256": "7f8ab78a60ef7f6adac3e4f8a93bfd767ead06841030a2647490f9ded53d6386", + "finalActualSha256": "b24d538b465d7a7f87a6bd1509be216518fc05fd84993e3826b624fa6e4e3207", + "finalMatchesCandidate": true, + "finalReferenceMatchesBaseline": true + }, + { + "screen": "popup-422-5682", + "baselineActualSha256": "059c502c8c28229cd099ecc1202461d347c069cadd2ba2efee1a37aa0baace2f", + "candidateActualSha256": "059c502c8c28229cd099ecc1202461d347c069cadd2ba2efee1a37aa0baace2f", + "actualIdentical": true, + "referenceIdentical": true, + "referenceSha256": "b70f8364a24570367868ce0e52c9f64643640a9e5dad9aa589d67633e7fcc155", + "finalActualSha256": "059c502c8c28229cd099ecc1202461d347c069cadd2ba2efee1a37aa0baace2f", + "finalMatchesCandidate": true, + "finalReferenceMatchesBaseline": true + }, + { + "screen": "popup-422-5705", + "baselineActualSha256": "ee43d4428b94262fc87460a413da67350f4a3446e8122a3bb53ae96a25ea71fe", + "candidateActualSha256": "ee43d4428b94262fc87460a413da67350f4a3446e8122a3bb53ae96a25ea71fe", + "actualIdentical": true, + "referenceIdentical": true, + "referenceSha256": "c4e30d646e39234ef40f09e8e196baf2d3e72addbc4271d0b069193f8f0dc4ad", + "finalActualSha256": "ee43d4428b94262fc87460a413da67350f4a3446e8122a3bb53ae96a25ea71fe", + "finalMatchesCandidate": true, + "finalReferenceMatchesBaseline": true + }, + { + "screen": "popup-422-5728", + "baselineActualSha256": "38ed76ac08098116a37b0c511fbf9b2a61c7b1ec5a7d89bd69aec2e428abcd00", + "candidateActualSha256": "38ed76ac08098116a37b0c511fbf9b2a61c7b1ec5a7d89bd69aec2e428abcd00", + "actualIdentical": true, + "referenceIdentical": true, + "referenceSha256": "82ab13e4a18136875449cae8e1d34c13bbe0e74350e249d2674ec45dfad86444", + "finalActualSha256": "38ed76ac08098116a37b0c511fbf9b2a61c7b1ec5a7d89bd69aec2e428abcd00", + "finalMatchesCandidate": true, + "finalReferenceMatchesBaseline": true + }, + { + "screen": "report-446-1971", + "baselineActualSha256": "de0a90f9c933f64944e5d991640b4eca24e47f6c5ee90521d527fcc109ca961d", + "candidateActualSha256": "de0a90f9c933f64944e5d991640b4eca24e47f6c5ee90521d527fcc109ca961d", + "actualIdentical": true, + "referenceIdentical": true, + "referenceSha256": "0cf71b751e361e59966634f18a787d22d794c1648c15d3ab91977090f1b18efd", + "finalActualSha256": "de0a90f9c933f64944e5d991640b4eca24e47f6c5ee90521d527fcc109ca961d", + "finalMatchesCandidate": true, + "finalReferenceMatchesBaseline": true + } + ], + "finalBinarySha256": "06707befc5571f73030459e86493bf5ae959fac84149f1b79f85a1b46f823c92", + "maskFix": { + "base": "d4b2056427ab579a011e500b7016a3867ea071da", + "baselineBinarySha256": "1c9dda74c8503ab40364751bc58a1db422819a91c267cd3aa40380431aa9c76d", + "candidateBinarySha256": "516476ac9fad7b511e62811517d2a1bfbd66145e96c2e11bbad844710ba4d253", + "finalBinarySha256": "28d4c01357b324d7521d18a145ac232871ed1c9853036d9f28f6f36963fe0d06", + "screens": [ + { + "screen": "notice-422-6914", + "baselineChangedRatio": 0.05541838134430727, + "candidateChangedRatio": 0.05254229538180155, + "finalChangedRatio": 0.05254229538180155, + "baselineRendered": { + "width": 360, + "height": 1217 + }, + "finalRendered": { + "width": 360, + "height": 1217 + }, + "baselineActualSha256": "6e105a63de769172198b74b4b552b8b22e8d5c8e2252aba399cb42abcef0fc83", + "candidateActualSha256": "0f2cdb2303f39bf1edd65eb0b983b39374b4c938354b149c41ec9e769a238eec", + "finalActualSha256": "0f2cdb2303f39bf1edd65eb0b983b39374b4c938354b149c41ec9e769a238eec", + "actualIdenticalToBaseline": false, + "finalMatchesCandidate": true, + "referenceSha256": "421d8a6fdda27079f183c6927c0672568648be3b23e37526938eb58c1276764a", + "referenceIdentical": true, + "baselineModuleSha256": "bc1389f98a63bc8e228cb1de9e4687357cf05e8a6e9df29680d889c005684416", + "finalModuleSha256": "5887a5c57cb9e882afac67b0cfe7062ac43194f067fb9bed625e041596ef46e3", + "baselineRepeatedChangedRatio": 0.05541838134430727, + "finalRepeatedChangedRatio": 0.05254229538180155 + }, + { + "screen": "notice-422-7088", + "baselineChangedRatio": 0.03256083695836393, + "candidateChangedRatio": 0.03222010762103836, + "finalChangedRatio": 0.03222010762103836, + "baselineRendered": { + "width": 992, + "height": 1145 + }, + "finalRendered": { + "width": 992, + "height": 1145 + }, + "baselineActualSha256": "b24d538b465d7a7f87a6bd1509be216518fc05fd84993e3826b624fa6e4e3207", + "candidateActualSha256": "625143077b60ee5f4857e1336a03ef96f582070fbfe607e6fb4ed2a429545c55", + "finalActualSha256": "625143077b60ee5f4857e1336a03ef96f582070fbfe607e6fb4ed2a429545c55", + "actualIdenticalToBaseline": false, + "finalMatchesCandidate": true, + "referenceSha256": "7f8ab78a60ef7f6adac3e4f8a93bfd767ead06841030a2647490f9ded53d6386", + "referenceIdentical": true, + "baselineModuleSha256": "b12bda05062087bb61737befeb3166ef63e722abb269b516383d3ba8ff75e972", + "finalModuleSha256": "3484aa6a3ef64642d7aa5a5539355e3b70a989f7d561c8f8cc7db3703c2791f1", + "baselineRepeatedChangedRatio": 0.03256083695836393, + "finalRepeatedChangedRatio": 0.03222010762103836 + }, + { + "screen": "notice-422-6865", + "baselineChangedRatio": 0.022197898423817863, + "candidateChangedRatio": 0.022021854932866316, + "finalChangedRatio": 0.022021854932866316, + "baselineRendered": { + "width": 1920, + "height": 1145 + }, + "finalRendered": { + "width": 1920, + "height": 1145 + }, + "baselineActualSha256": "ff48e72f467543b6b058d835c1309990f975e2339cceb15e9e3dccce634bda70", + "candidateActualSha256": "8fd58f7f3428c90f51712865db2ef1514124bfeb237a1288344f88483aa1387a", + "finalActualSha256": "8fd58f7f3428c90f51712865db2ef1514124bfeb237a1288344f88483aa1387a", + "actualIdenticalToBaseline": false, + "finalMatchesCandidate": true, + "referenceSha256": "73049f14e3be6c308652418bf69f7d53af44f0df650241b29e0e7aff16c3a1b4", + "referenceIdentical": true, + "baselineModuleSha256": "c8697576763e467b17233156a10ac9372219edc7c8bd6f4ce42a2289503bdf98", + "finalModuleSha256": "c2f8fef6deae6070934268d9d5003bf3008d96d8e81a04735e2c3f6185d1150b", + "baselineRepeatedChangedRatio": 0.022197898423817863, + "finalRepeatedChangedRatio": 0.022021854932866316 + }, + { + "screen": "about-422-3376", + "baselineChangedRatio": 0.07457565991405771, + "candidateChangedRatio": 0.07457565991405771, + "finalChangedRatio": 0.07457565991405771, + "baselineRendered": { + "width": 360, + "height": 7240 + }, + "finalRendered": { + "width": 360, + "height": 7240 + }, + "baselineActualSha256": "ac23b6b562c1b09ffcd1491487c7c8e353c8d274d61f0360edb14934c17efef9", + "candidateActualSha256": "ac23b6b562c1b09ffcd1491487c7c8e353c8d274d61f0360edb14934c17efef9", + "finalActualSha256": "ac23b6b562c1b09ffcd1491487c7c8e353c8d274d61f0360edb14934c17efef9", + "actualIdenticalToBaseline": true, + "finalMatchesCandidate": true, + "referenceSha256": "183b29572ab786dbe588e0bfed57db8210920e4f070dfb22226065a6545c17f8", + "referenceIdentical": true, + "baselineModuleSha256": "3857a227ad1934a0c58b1cc40e999f87b43010195a80613d20ea7415feb75264", + "finalModuleSha256": "3857a227ad1934a0c58b1cc40e999f87b43010195a80613d20ea7415feb75264" + }, + { + "screen": "about-422-3180", + "baselineChangedRatio": 0.04063815022762631, + "candidateChangedRatio": 0.04063815022762631, + "finalChangedRatio": 0.04063815022762631, + "baselineRendered": { + "width": 992, + "height": 5620 + }, + "finalRendered": { + "width": 992, + "height": 5620 + }, + "baselineActualSha256": "bb18213011cc6ebd7cbe384a2a40720d4966437dffc51de957cf0c9e29b9d271", + "candidateActualSha256": "bb18213011cc6ebd7cbe384a2a40720d4966437dffc51de957cf0c9e29b9d271", + "finalActualSha256": "bb18213011cc6ebd7cbe384a2a40720d4966437dffc51de957cf0c9e29b9d271", + "actualIdenticalToBaseline": true, + "finalMatchesCandidate": true, + "referenceSha256": "9487389f99ba9d199262ddf0c0e8acaed0679b0aadceeb535cecb99f6b3e1faa", + "referenceIdentical": true, + "baselineModuleSha256": "9b9ca99252737850948c296515f24c2e5cc6112d005a963278b50730fa43589a", + "finalModuleSha256": "9b9ca99252737850948c296515f24c2e5cc6112d005a963278b50730fa43589a" + }, + { + "screen": "about-422-2987", + "baselineChangedRatio": 0.024149936935043095, + "candidateChangedRatio": 0.024149936935043095, + "finalChangedRatio": 0.024149936935043095, + "baselineRendered": { + "width": 1920, + "height": 4758 + }, + "finalRendered": { + "width": 1920, + "height": 4758 + }, + "baselineActualSha256": "69f144c1ebe9ea6eb5c3ae056fe8db709f7b95f5cdedaf73e169a2b41ae0d47b", + "candidateActualSha256": "69f144c1ebe9ea6eb5c3ae056fe8db709f7b95f5cdedaf73e169a2b41ae0d47b", + "finalActualSha256": "69f144c1ebe9ea6eb5c3ae056fe8db709f7b95f5cdedaf73e169a2b41ae0d47b", + "actualIdenticalToBaseline": true, + "finalMatchesCandidate": true, + "referenceSha256": "6dc173242afb2bc5ad5d7262d4d23acd1ec7fcc468db32c413cc5356b3dd9120", + "referenceIdentical": true, + "baselineModuleSha256": "178477e195f004d39734a8fdd36e001607db7665d7b4e79488c146dcedc5b527", + "finalModuleSha256": "178477e195f004d39734a8fdd36e001607db7665d7b4e79488c146dcedc5b527" + }, + { + "screen": "landing-833-3640", + "baselineChangedRatio": 0.049872156420379773, + "candidateChangedRatio": 0.049872156420379773, + "finalChangedRatio": 0.049872156420379773, + "baselineRendered": { + "width": 360, + "height": 2955 + }, + "finalRendered": { + "width": 360, + "height": 2955 + }, + "baselineActualSha256": "f35490cf62a1d7f3622a210624366fadb6e81f5415d5b03a5979b23dae372cdd", + "candidateActualSha256": "f35490cf62a1d7f3622a210624366fadb6e81f5415d5b03a5979b23dae372cdd", + "finalActualSha256": "f35490cf62a1d7f3622a210624366fadb6e81f5415d5b03a5979b23dae372cdd", + "actualIdenticalToBaseline": true, + "finalMatchesCandidate": true, + "referenceSha256": "d66ec480cec7ae66c6e2ffe03161b763dc7d446c064fd82318a4d0f3d3e9bb92", + "referenceIdentical": true, + "baselineModuleSha256": "a55acb9a08d98c5da824bab33993287b503d5bc4a1672bc2866119f0866a7125", + "finalModuleSha256": "a55acb9a08d98c5da824bab33993287b503d5bc4a1672bc2866119f0866a7125" + }, + { + "screen": "landing-833-3322", + "baselineChangedRatio": 0.024662550063123068, + "candidateChangedRatio": 0.024662550063123068, + "finalChangedRatio": 0.024662550063123068, + "baselineRendered": { + "width": 992, + "height": 2964 + }, + "finalRendered": { + "width": 992, + "height": 2964 + }, + "baselineActualSha256": "619165013992405a3bf47132235f2e735ff2a1ec39686b79d4c61bfa1d0bf279", + "candidateActualSha256": "619165013992405a3bf47132235f2e735ff2a1ec39686b79d4c61bfa1d0bf279", + "finalActualSha256": "619165013992405a3bf47132235f2e735ff2a1ec39686b79d4c61bfa1d0bf279", + "actualIdenticalToBaseline": true, + "finalMatchesCandidate": true, + "referenceSha256": "f3eebec1a9e5f574922b4f616d08abae92f9cada366213d4ac4d748553bd96a9", + "referenceIdentical": true, + "baselineModuleSha256": "f6f4e52ec734fab91ad859de0d755a10a5e2633743956d407f45dc9d458684f8", + "finalModuleSha256": "f6f4e52ec734fab91ad859de0d755a10a5e2633743956d407f45dc9d458684f8" + }, + { + "screen": "landing-832-2975", + "baselineChangedRatio": 0.015037458117163857, + "candidateChangedRatio": 0.015037458117163857, + "finalChangedRatio": 0.015037458117163857, + "baselineRendered": { + "width": 1920, + "height": 3084 + }, + "finalRendered": { + "width": 1920, + "height": 3084 + }, + "baselineActualSha256": "1bf31cea5d1dbf5d6a5c2b4eb35fab39b6800f56bf1905a9278621e08c2787d3", + "candidateActualSha256": "1bf31cea5d1dbf5d6a5c2b4eb35fab39b6800f56bf1905a9278621e08c2787d3", + "finalActualSha256": "1bf31cea5d1dbf5d6a5c2b4eb35fab39b6800f56bf1905a9278621e08c2787d3", + "actualIdenticalToBaseline": true, + "finalMatchesCandidate": true, + "referenceSha256": "68b0743556479681e642b389f89fd2a082d86c6d634884739bd8725522cf8c6b", + "referenceIdentical": true, + "baselineModuleSha256": "8cc5656a6e2b9c238e7beee366f25ed2cd1a9c2957375483d090fed05cad7144", + "finalModuleSha256": "8cc5656a6e2b9c238e7beee366f25ed2cd1a9c2957375483d090fed05cad7144" + }, + { + "screen": "popup-422-5682", + "baselineChangedRatio": 0.03644230769230769, + "candidateChangedRatio": 0.03644230769230769, + "finalChangedRatio": 0.03644230769230769, + "baselineRendered": { + "width": 390, + "height": 800 + }, + "finalRendered": { + "width": 390, + "height": 800 + }, + "baselineActualSha256": "059c502c8c28229cd099ecc1202461d347c069cadd2ba2efee1a37aa0baace2f", + "candidateActualSha256": "059c502c8c28229cd099ecc1202461d347c069cadd2ba2efee1a37aa0baace2f", + "finalActualSha256": "059c502c8c28229cd099ecc1202461d347c069cadd2ba2efee1a37aa0baace2f", + "actualIdenticalToBaseline": true, + "finalMatchesCandidate": true, + "referenceSha256": "b70f8364a24570367868ce0e52c9f64643640a9e5dad9aa589d67633e7fcc155", + "referenceIdentical": true, + "baselineModuleSha256": "413fa6750729f3fe1c66ca7fc5d149e94bfb73a4d5d6f176b43bff6a467450e7", + "finalModuleSha256": "413fa6750729f3fe1c66ca7fc5d149e94bfb73a4d5d6f176b43bff6a467450e7" + }, + { + "screen": "popup-422-5705", + "baselineChangedRatio": 0.020626068115234375, + "candidateChangedRatio": 0.020626068115234375, + "finalChangedRatio": 0.020626068115234375, + "baselineRendered": { + "width": 768, + "height": 1024 + }, + "finalRendered": { + "width": 768, + "height": 1024 + }, + "baselineActualSha256": "ee43d4428b94262fc87460a413da67350f4a3446e8122a3bb53ae96a25ea71fe", + "candidateActualSha256": "ee43d4428b94262fc87460a413da67350f4a3446e8122a3bb53ae96a25ea71fe", + "finalActualSha256": "ee43d4428b94262fc87460a413da67350f4a3446e8122a3bb53ae96a25ea71fe", + "actualIdenticalToBaseline": true, + "finalMatchesCandidate": true, + "referenceSha256": "c4e30d646e39234ef40f09e8e196baf2d3e72addbc4271d0b069193f8f0dc4ad", + "referenceIdentical": true, + "baselineModuleSha256": "413fa6750729f3fe1c66ca7fc5d149e94bfb73a4d5d6f176b43bff6a467450e7", + "finalModuleSha256": "413fa6750729f3fe1c66ca7fc5d149e94bfb73a4d5d6f176b43bff6a467450e7" + }, + { + "screen": "popup-422-5728", + "baselineChangedRatio": 0.00853539737654321, + "candidateChangedRatio": 0.00853539737654321, + "finalChangedRatio": 0.00853539737654321, + "baselineRendered": { + "width": 1920, + "height": 1080 + }, + "finalRendered": { + "width": 1920, + "height": 1080 + }, + "baselineActualSha256": "38ed76ac08098116a37b0c511fbf9b2a61c7b1ec5a7d89bd69aec2e428abcd00", + "candidateActualSha256": "38ed76ac08098116a37b0c511fbf9b2a61c7b1ec5a7d89bd69aec2e428abcd00", + "finalActualSha256": "38ed76ac08098116a37b0c511fbf9b2a61c7b1ec5a7d89bd69aec2e428abcd00", + "actualIdenticalToBaseline": true, + "finalMatchesCandidate": true, + "referenceSha256": "82ab13e4a18136875449cae8e1d34c13bbe0e74350e249d2674ec45dfad86444", + "referenceIdentical": true, + "baselineModuleSha256": "413fa6750729f3fe1c66ca7fc5d149e94bfb73a4d5d6f176b43bff6a467450e7", + "finalModuleSha256": "413fa6750729f3fe1c66ca7fc5d149e94bfb73a4d5d6f176b43bff6a467450e7" + }, + { + "screen": "grid-429-1966", + "baselineChangedRatio": 0.029639884816046395, + "candidateChangedRatio": 0.029639884816046395, + "finalChangedRatio": 0.029639884816046395, + "baselineRendered": { + "width": 1281, + "height": 1086 + }, + "finalRendered": { + "width": 1281, + "height": 1086 + }, + "baselineActualSha256": "a7ea4c4023061e9851972a8ad633af22fa160a0d610621d98e00529959cab69f", + "candidateActualSha256": "a7ea4c4023061e9851972a8ad633af22fa160a0d610621d98e00529959cab69f", + "finalActualSha256": "a7ea4c4023061e9851972a8ad633af22fa160a0d610621d98e00529959cab69f", + "actualIdenticalToBaseline": true, + "finalMatchesCandidate": true, + "referenceSha256": "251925cfa907bcb24ff2077cbf81a778a0be9a9df62a9532359bea231f468e10", + "referenceIdentical": true, + "baselineModuleSha256": "6b16b8f79f4ecf590a24a1c13af67b488afc08a2d513d156e000c20728c1b0d2", + "finalModuleSha256": "6b16b8f79f4ecf590a24a1c13af67b488afc08a2d513d156e000c20728c1b0d2" + }, + { + "screen": "keyframes-458-2021", + "baselineChangedRatio": 0.067138671875, + "candidateChangedRatio": 0.067138671875, + "finalChangedRatio": 0.067138671875, + "baselineRendered": { + "width": 64, + "height": 64 + }, + "finalRendered": { + "width": 64, + "height": 64 + }, + "baselineActualSha256": "49551a58d3283ba4cb5e1277893db55014aecc0b05d2752d8f081c2ee48ab225", + "candidateActualSha256": "49551a58d3283ba4cb5e1277893db55014aecc0b05d2752d8f081c2ee48ab225", + "finalActualSha256": "49551a58d3283ba4cb5e1277893db55014aecc0b05d2752d8f081c2ee48ab225", + "actualIdenticalToBaseline": true, + "finalMatchesCandidate": true, + "referenceSha256": "94ff08ecd5dac40feeb9ba154e40f0f527dbcaa31c02f5cbf0c03dae25ede191", + "referenceIdentical": true, + "baselineModuleSha256": "873d3092340f7008f37b6e023d84cebe28eabb2a28bdd4fb14440bc4236dccff", + "finalModuleSha256": "873d3092340f7008f37b6e023d84cebe28eabb2a28bdd4fb14440bc4236dccff" + }, + { + "screen": "report-446-1971", + "baselineChangedRatio": 0.012764674159854678, + "candidateChangedRatio": 0.012764674159854678, + "finalChangedRatio": 0.012764674159854678, + "baselineRendered": { + "width": 1920, + "height": 734 + }, + "finalRendered": { + "width": 1920, + "height": 734 + }, + "baselineActualSha256": "de0a90f9c933f64944e5d991640b4eca24e47f6c5ee90521d527fcc109ca961d", + "candidateActualSha256": "de0a90f9c933f64944e5d991640b4eca24e47f6c5ee90521d527fcc109ca961d", + "finalActualSha256": "de0a90f9c933f64944e5d991640b4eca24e47f6c5ee90521d527fcc109ca961d", + "actualIdenticalToBaseline": true, + "finalMatchesCandidate": true, + "referenceSha256": "0cf71b751e361e59966634f18a787d22d794c1648c15d3ab91977090f1b18efd", + "referenceIdentical": true, + "baselineModuleSha256": "23b8eb8ea5d6bd41ce8a77b69ccee3497a68dc7408b1fd50bbb2007b89199959", + "finalModuleSha256": "23b8eb8ea5d6bd41ce8a77b69ccee3497a68dc7408b1fd50bbb2007b89199959" + } + ] + } +} diff --git a/harness/render/thresholds.json b/harness/render/thresholds.json index 8069601..87091ea 100644 --- a/harness/render/thresholds.json +++ b/harness/render/thresholds.json @@ -1,5 +1,5 @@ { - "note": "Current generated-screen baselines are backed by two fresh-process acquire/render measurements on d039623, with identical input hashes and exact divergence values; see docs/harness-determinism.md and its evidence JSON. Values are percentages; render.mjs fails above each value plus tolerance. The unchanged popup-answer entries are historical plugin-answer comparisons and were not remeasured in this task.", + "note": "Current generated-screen baselines are backed by two fresh-process acquire/render measurements on d039623, with identical input hashes and exact divergence values; see docs/harness-determinism.md and its evidence JSON. Notice tablet was subsequently tightened for collective row overflow, then all three notice screens were tightened after repeated fresh acquire/render measurements of the independent offset-mask HUG repair; see docs/notice-layout-divergence.md. Values are percentages; render.mjs fails above each value plus tolerance. The unchanged popup-answer entries are historical plugin-answer comparisons and were not remeasured in this task.", "tolerance": 0.05, "screens": { "popup-422-5682": 3.64, @@ -11,9 +11,9 @@ "keyframes-458-2021": 6.71, "grid-429-1966": 2.96, "report-446-1971": 1.28, - "notice-422-6914": 5.54, - "notice-422-7088": 3.35, - "notice-422-6865": 2.22, + "notice-422-6914": 5.25, + "notice-422-7088": 3.22, + "notice-422-6865": 2.2, "about-422-3376": 7.46, "about-422-3180": 4.06, "about-422-2987": 2.41,