Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 7 additions & 6 deletions docs/visual-parity.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,10 @@ This baseline accepts neither icon presentation nor the overflow affordances and
composition. Those remain part of the full navigation family and both application-adoption
reviews. A matching text-tab measurement or passing functional/coverage check does not complete
that family or the library. The first candidate exposed a different bottom track row and the native button label
0.796875px below the reference. Both were corrected in the shared renderer. Read-only decoding
of all16 final local samples finds zero changed pixels against their original references.
This is scoped to the pinned local Chromium/macOS captures; the hosted cross-platform gate
checks exact styles and geometry, rather than requiring platform-specific raster identity.
0.796875px below the reference. Both were corrected in the shared renderer. The compiled
catalog now checks exact agreement of every decoded pixel against the matching Chromium
reference on macOS and Linux. All 16 text-only candidate captures per platform, covering
link and panel navigation, must also match the measured styles and geometry.
No full navigation or human UX acceptance is claimed.


Expand Down Expand Up @@ -138,8 +138,9 @@ vertical bound. Original failure diagnostics remain evidence; no tolerance was i

The compiled catalog asserts exact measured state/style values and less-than-one-pixel geometry,
with a 0.1px vertical bound, against all three icon/text presentations. Original images are verified
by digest and dimensions before each comparison. Scoped raster inspection and current package
identities are recorded separately in the [implementation evidence](evidence/shared-ui-foundation.json).
by digest and dimensions before each comparison. The catalog also requires zero changed
pixels in the 48 icon/text captures per platform and records the pixel agreement for each
candidate screenshot.
Custom font/theme breadth, overflow affordances, other component families and both application
adoptions remain required under the unchanged 64 foundation and 6 delivery cases.

Expand Down
8 changes: 6 additions & 2 deletions tests/storybook/navigation-icons-visual.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {captureSettledElement} from './screenshot.ts';
import {waitForCatalogStory} from './lifecycle.ts';
import {readOriginalRaster} from './reference-images.ts';
import {compareVisualPng} from './raster-comparison.ts';
import assert from 'node:assert/strict';
import {readFile,writeFile} from 'node:fs/promises';
import {resolve} from 'node:path';
Expand Down Expand Up @@ -43,8 +44,11 @@ export async function exerciseNavigationIconsVisual(page:Page,origin:string,outp
}
if(row.state==='focus'&&n===0){assert.equal(item.focusVisible,true);assert.equal(item.base.outline,expected.base.outline);assert.equal(item.base.outlineOffset,expected.base.outlineOffset);}
}
const raster=await captureSettledElement(page,'#icon-proof',resolve(output,'ha-icons-'+kind+'-'+theme+'-'+row.presentation+'-'+row.state+'.png'));
results.push({platform,kind,theme,presentation:row.presentation,state:row.state,geometryAndStateMatch:true,layoutToleranceCssPx:1,verticalToleranceCssPx:0.1,actual,referenceImage:row.image,raster});
const path=resolve(output,`ha-icons-${kind}-${theme}-${row.presentation}-${row.state}.png`);
const raster=await captureSettledElement(page,'#icon-proof',path);
const {agreement}=await compareVisualPng(page,original,await readFile(path));
assert.equal(agreement.changedPixels,0,`Original HA navigation icon pixels differ for ${platform} ${kind} ${theme} ${row.presentation} ${row.state}`);
results.push({platform,kind,theme,presentation:row.presentation,state:row.state,geometryAndStateMatch:true,layoutToleranceCssPx:1,verticalToleranceCssPx:0.1,actual,referenceImage:row.image,raster,rasterAgreement:agreement});
}
}
await writeFile(resolve(output,'ha-navigation-icons-parity.json'),JSON.stringify({reference:'docs/evidence/ha-navigation-icons-reference.json',scope:'Three icon/text presentations; overflow, other visual families and installed app/human acceptance remain pending.',results},null,2)+'\n');return results;
Expand Down
10 changes: 7 additions & 3 deletions tests/storybook/navigation-visual.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {captureSettledElement} from './screenshot.ts';
import {waitForCatalogStory} from './lifecycle.ts';
import {readOriginalRaster} from './reference-images.ts';
import {compareVisualPng} from './raster-comparison.ts';
import assert from 'node:assert/strict';
import {readFile,writeFile} from 'node:fs/promises';
import {resolve} from 'node:path';
Expand All @@ -22,7 +23,7 @@ export async function exerciseNavigationVisual(page:Page,origin:string,output:st
await page.evaluate(()=>{const holder=document.createElement('div');holder.id='navigation-proof';holder.style.cssText='position:fixed;left:32px;top:32px;width:600px;height:56px;overflow:hidden;z-index:9999';const host=document.createElement('vyp-ha-tabs');host.id='visual-navigation';holder.append(host);document.querySelector('main.catalog-frame')!.append(holder);});
const states:ReferenceState[]=reference.states.filter(row=>row.platform===platform&&row.theme===theme);assert.equal(states.length,4,'All measured platform states are required');
for(const row of states){
const bytes=await readOriginalRaster(row.image);assert.equal(bytes.readUInt32BE(16),row.image.width);assert.equal(bytes.readUInt32BE(20),row.image.height);
const original=await readOriginalRaster(row.image);assert.equal(original.readUInt32BE(16),row.image.width);assert.equal(original.readUInt32BE(20),row.image.height);
await page.locator('#visual-navigation').evaluate(async(el,{kind,state})=>{const host=el as HaTabs;host.state={kind,label:'Household sections',selected:'overview',items:['Overview','Tasks','Inventory'].map((label,n)=>({id:['overview','tasks','inventory'][n],label,href:kind==='navigation'?'#section-'+n:null,disabled:n===1&&state==='disabled'}))};await host.updateComplete;host.shadowRoot!.querySelector<HTMLElement>('[part=item]')!.blur();},{kind,state:row.state});
if(row.state==='hover')await page.locator('#visual-navigation a,#visual-navigation button').nth(1).hover();else await page.mouse.move(1100,800);
if(row.state==='focus'){await page.locator('#visual-navigation a,#visual-navigation button').first().focus();await page.keyboard.press('Home');}
Expand All @@ -33,8 +34,11 @@ export async function exerciseNavigationVisual(page:Page,origin:string,output:st
for(const[key,value]of Object.entries(item.geometry))assert.ok(Math.abs(value-expected.geometry[key as keyof typeof expected.geometry])<(key==='textY'?0.1:1),kind+' '+theme+' '+row.state+' '+key);
if(row.state==='focus'&&index===0){assert.equal(item.focusVisible,true);assert.equal(item.base.outline,expected.base.outline);assert.equal(item.base.outlineOffset,expected.base.outlineOffset);}
}
const raster=await captureSettledElement(page,'#navigation-proof',resolve(output,'ha-navigation-'+kind+'-'+theme+'-'+row.state+'.png'));
results.push({platform,kind,theme,state:row.state,typographyStateAndGeometryMatch:true,layoutToleranceCssPx:1,textVerticalToleranceCssPx:0.1,actual,referenceImage:row.image,raster});
const path=resolve(output,`ha-navigation-${kind}-${theme}-${row.state}.png`);
const raster=await captureSettledElement(page,'#navigation-proof',path);
const {agreement}=await compareVisualPng(page,original,await readFile(path));
assert.equal(agreement.changedPixels,0,`Original HA navigation pixels differ for ${platform} ${kind} ${theme} ${row.state}`);
results.push({platform,kind,theme,state:row.state,typographyStateAndGeometryMatch:true,layoutToleranceCssPx:1,textVerticalToleranceCssPx:0.1,actual,referenceImage:row.image,raster,rasterAgreement:agreement});
}
}
await writeFile(resolve(output,'ha-navigation-parity.json'),JSON.stringify({reference:'docs/evidence/ha-navigation-reference.json',scope:'Measured text-only controls; icons, overflow affordances and installed-application/human acceptance remain required',results},null,2)+'\n');return results;
Expand Down
Loading