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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,21 @@ published-wheel identity, complete inventory and original file manifest before
copying PDFs and previews into the built site. Large evidence files stay in
release storage. Pass `--archive /path/to/verified.zip` to use a local copy.
After another MkDocs build, run the preparation step again before serving `site`.
Earlier published manifests live in `docs/assets/css-corpus/history/`. The same
preparation command verifies and materializes them too, preserving their versioned
PDF, image, and index URLs when the current explorer advances. Keep the historical
manifest bytes unchanged; the browser check verifies old PASS, FAIL, and disputed
PDF URLs alongside the current interactive results.
The package selector uses the generated `versions.json` inventory. Shared case
URLs include `version=...`, and changing versions preserves the selected case
and filters so readers can inspect the same fixture across retained releases.
Its markup loads `explorer-v2.js` and `explorer-v2.css`; keep the original
`explorer.js` and `explorer.css` unchanged so cached earlier pages retain a
compatible script and stylesheet during deployment.
The new script loads after its markup and before the theme script. This lets it
migrate legacy `q` fixture links to `query` before the theme can open its global
search overlay. Keep the mobile shared-link and download checks when changing
this loading order.

With Playwright 1.63.0, its Chrome browser, and pypdf 6.19.0 installed, run
`python tools/check_css_explorer.py --site site --out output/css-explorer-ui`.
Expand Down
46 changes: 46 additions & 0 deletions docs/assets/css-corpus/explorer-v2.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
.css-explorer { margin: 1.5rem 0; }
.css-explorer [hidden] { display: none !important; }
.css-explorer input, .css-explorer select, .css-explorer button {
font: inherit; color: var(--md-default-fg-color); background: var(--md-default-bg-color);
border: 1px solid var(--md-default-fg-color--light); border-radius: .2rem; padding: .5rem;
}
.css-explorer button { cursor: pointer; font-weight: 600; }
.css-explorer button:disabled { opacity: .45; cursor: default; }
.css-explorer :focus-visible { outline: 3px solid var(--md-accent-fg-color); outline-offset: 3px; }
.ce-version { max-width: 18rem; margin: .75rem 0; }
.ce-version label { display: block; font-size: .72rem; font-weight: 600; }
.ce-version select { display: block; width: 100%; min-width: 0; margin-top: .3rem; min-height: 2.3rem; }
.ce-filters { display: grid; grid-template-columns: 2fr 1fr 1fr; gap: .7rem; }
.ce-filters label { min-width: 0; font-size: .72rem; font-weight: 600; }
.ce-filters input, .ce-filters select { display: block; width: 100%; margin-top: .3rem; min-height: 2.3rem; }
.ce-counts { display: flex; flex-wrap: wrap; gap: .5rem 1rem; padding: .6rem 0; border-bottom: 1px solid var(--md-default-fg-color--lightest); }
.ce-result { padding: .8rem 0; border-bottom: 1px solid var(--md-default-fg-color--lightest); }
.ce-result button { text-align: left; padding: 0; color: var(--md-typeset-a-color); border: 0; background: transparent; overflow-wrap: anywhere; }
.ce-result p { margin: .3rem 0; font-size: .75rem; }
.ce-meta { display: flex; flex-wrap: wrap; gap: .4rem .8rem; font-size: .65rem; }
.ce-verdict { font-weight: 700; }
.ce-verdict[data-verdict="FAIL"] { color: #b52338; }
.ce-verdict[data-verdict="PASS"] { color: #166454; }
.ce-verdict[data-verdict="REFERENCE-DISPUTED"] { color: #845000; }
[data-md-color-scheme="slate"] .ce-verdict[data-verdict="FAIL"] { color: #ff9da9; }
[data-md-color-scheme="slate"] .ce-verdict[data-verdict="PASS"] { color: #8ed9be; }
[data-md-color-scheme="slate"] .ce-verdict[data-verdict="REFERENCE-DISPUTED"] { color: #f2c27b; }
.ce-pagination { display: flex; align-items: center; justify-content: space-between; gap: .7rem; margin: 1rem 0; font-size: .75rem; }
.ce-detail { margin: 1.2rem 0; padding: 1rem; border: 1px solid var(--md-default-fg-color--light); border-radius: .3rem; background: var(--md-code-bg-color); overflow-wrap: anywhere; scroll-margin-top: 4rem; }
.ce-detail h2 { margin-top: 0; font-size: 1.1rem; }
.ce-detail h3 { font-size: .8rem; }
.ce-detail p { font-size: .75rem; }
.ce-detail details { border-color: var(--md-default-fg-color--lightest); box-shadow: none; }
.ce-detail summary { background: transparent; }
.ce-detail summary::before { background-color: var(--md-typeset-a-color); }
.ce-links { display: flex; flex-wrap: wrap; gap: .6rem 1rem; }
.ce-images { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: .7rem; }
.ce-images figure { min-width: 0; margin: 0; }
.ce-images figcaption { font-size: .65rem; text-align: left; margin: .4rem 0; }
.ce-images img { display: block; width: 100%; height: auto; border: 1px solid var(--md-default-fg-color--light); background: white; }
.ce-error { border-left: 3px solid #b52338; padding-left: .7rem; }
@media (max-width: 700px) {
.ce-filters { grid-template-columns: 1fr; }
.ce-images { grid-template-columns: 1fr; }
.ce-detail { padding: .6rem; scroll-margin-top: 6rem; }
}
207 changes: 207 additions & 0 deletions docs/assets/css-corpus/explorer-v2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
(() => {
'use strict';
const root = document.getElementById('css-explorer');
if (!root) return;
// Old fixture links used the theme's global-search parameter. Migrate it
// before the theme starts so a shared mobile link leaves the PDF accessible.
const initialUrl = new URL(location.href);
if (initialUrl.searchParams.has('q')) {
if (!initialUrl.searchParams.has('query')) initialUrl.searchParams.set('query', initialUrl.searchParams.get('q'));
initialUrl.searchParams.delete('q');
history.replaceState(null, '', initialUrl);
}
const find = (id) => document.getElementById(`ce-${id}`);
const create = (tag, text, className) => {
const element = document.createElement(tag);
if (text !== undefined) element.textContent = text;
if (className) element.className = className;
return element;
};
const versionsUrl = new URL(root.dataset.versions, location.href);
let indexUrl;
const perPage = 12;
const verdicts = ['PASS', 'FAIL', 'REFERENCE-DISPUTED'];
let data, fixtures, selected, pageNumber = 0;
const badge = (status) => {
const element = create('span', status, 'ce-verdict');
element.dataset.verdict = status;
return element;
};
const assetUrl = (path) => {
if (!/^(?:pdfs|refs|out)\/[a-z0-9_-]+\/[a-z0-9_.-]+\.(?:pdf|png)$/.test(path)) throw new Error('Invalid evidence path');
return new URL(path, indexUrl).href;
};
const link = (title, href, download = false) => {
const element = create('a', title);
element.href = href;
if (download) element.download = '';
return element;
};
const saveUrl = (push = false) => {
const url = new URL(location.href);
for (const [key, value] of [['version', data.version], ['query', find('search').value], ['category', find('category').value],
['verdict', find('verdict').value], ['case', selected || '']]) {
if (value) url.searchParams.set(key, value); else url.searchParams.delete(key);
}
history[push ? 'pushState' : 'replaceState'](null, '', url);
};
function showDetail(key, focus = false) {
const detail = find('detail');
detail.replaceChildren();
const fixture = fixtures.get(key);
if (!fixture) { detail.hidden = true; selected = null; return; }
selected = key;
detail.hidden = false;
const title = create('h2', fixture.id);
title.id = 'ce-detail-title';
detail.append(title, badge(fixture.status), create('p', fixture.description));
const difference = Number(fixture.differencePercent).toFixed(4);
detail.append(create('p', `${fixture.category} · ${fixture.feature} · Above-floor page difference: ${difference}%`));
detail.append(create('p', `Reference renderer: ${fixture.referenceRenderer}. Reference metadata: ${fixture.referenceStatus}.`));
if (fixture.diagnosis) detail.append(create('p', `Comparator diagnosis: ${fixture.diagnosis}`));
const links = create('p', undefined, 'ce-links');
links.append(link('Download Fullbleed PDF', assetUrl(fixture.pdf), true), link('View HTML/CSS fixture', fixture.sourceUrl));
const share = link('Link to this case', location.href);
const shareUrl = new URL(location.href);
shareUrl.searchParams.set('case', key);
share.href = shareUrl.href;
links.append(share);
detail.append(links);
if (fixture.referenceNote) {
const note = create('details');
note.append(create('summary', 'Upstream reference review note'), create('p', fixture.referenceNote));
detail.append(note);
}
const digest = create('details');
digest.append(create('summary', 'PDF checksum'), create('p', `SHA-256: ${fixture.pdfSha256}`));
detail.append(digest);
fixture.pages.forEach((page, index) => {
detail.append(create('h3', `Page ${index + 1}`));
const images = create('div', undefined, 'ce-images');
for (const [key, label] of [['reference', `${fixture.referenceRenderer} reference`], ['candidate', `Fullbleed ${data.version}`]]) {
const figure = create('figure');
figure.append(create('figcaption', label));
if (page[key]) {
const url = assetUrl(page[key]);
const anchor = link('', url);
anchor.setAttribute('aria-label', `Open full-size ${label}, page ${index + 1}`);
const image = document.createElement('img');
image.src = url;
image.alt = `${label}, ${fixture.id}, page ${index + 1}`;
image.loading = 'lazy';
image.addEventListener('error', () => { image.replaceWith(create('p', 'This preview could not load. Use the PDF or complete report.')); });
anchor.append(image);
figure.append(anchor);
} else figure.append(create('p', 'No page on this side of the comparison.'));
images.append(figure);
}
detail.append(images);
});
if (focus) { detail.focus({preventScroll: true}); detail.scrollIntoView({block: 'start', behavior: 'auto'}); }
}
function renderResults() {
const query = find('search').value.trim().toLowerCase();
const category = find('category').value;
const verdict = find('verdict').value;
const filtered = data.fixtures.filter((f) => (!category || f.category === category) && (!verdict || f.status === verdict)
&& `${f.key} ${f.feature} ${f.description}`.toLowerCase().includes(query));
const pages = Math.max(1, Math.ceil(filtered.length / perPage));
pageNumber = Math.min(pageNumber, pages - 1);
const start = pageNumber * perPage;
find('results-status').textContent = filtered.length ? `${filtered.length.toLocaleString()} matching fixtures. Showing ${start + 1}–${Math.min(start + perPage, filtered.length)}.` : 'No fixtures match these filters. Try a broader term or another verdict.';
const results = find('results');
results.replaceChildren();
for (const fixture of filtered.slice(start, start + perPage)) {
const article = create('article', undefined, 'ce-result');
const button = create('button', fixture.id);
button.type = 'button';
button.setAttribute('aria-label', `Inspect ${fixture.id}`);
button.addEventListener('click', () => { selected = fixture.key; saveUrl(true); showDetail(fixture.key, true); });
const metadata = create('p', undefined, 'ce-meta');
metadata.append(badge(fixture.status), create('span', fixture.category), create('span', `${fixture.pages.length} page${fixture.pages.length === 1 ? '' : 's'}`));
article.append(button, metadata, create('p', fixture.description));
results.append(article);
}
find('previous').disabled = pageNumber === 0;
find('next').disabled = pageNumber >= pages - 1;
find('page').textContent = `Page ${pageNumber + 1} of ${pages}`;
}
function restoreUrl() {
const params = new URL(location.href).searchParams;
if (params.get('version') !== data.version) { location.reload(); return; }
find('search').value = params.get('query') || '';
find('category').value = params.get('category') || '';
find('verdict').value = params.get('verdict') || '';
if (find('category').selectedIndex < 0) find('category').value = '';
if (find('verdict').selectedIndex < 0) find('verdict').value = '';
pageNumber = 0;
renderResults();
showDetail(params.get('case'));
}
async function start() {
const versionsResponse = await fetch(versionsUrl);
if (!versionsResponse.ok) throw new Error('Package versions unavailable');
const versions = await versionsResponse.json();
if (versions.schema !== 'fullbleed.css-explorer-versions.v1' || !Array.isArray(versions.versions)
|| !versions.versions.length || versions.versions.some(v => typeof v !== 'string' || !/^\d+\.\d+\.\d+$/.test(v))
|| new Set(versions.versions).size !== versions.versions.length || !versions.versions.includes(versions.current)) {
throw new Error('Invalid package version inventory');
}
const requested = new URL(location.href).searchParams.get('version');
const version = versions.versions.includes(requested) ? requested : versions.current;
indexUrl = new URL(`${version}/index.json`, versionsUrl);
for (const value of versions.versions) {
const option = create('option', `${value}${value === versions.current ? ' (current)' : ' (historical)'}`);
option.value = value;
find('version').append(option);
}
find('version').value = version;
const response = await fetch(indexUrl);
if (!response.ok) throw new Error('Fixture index unavailable');
data = await response.json();
if (data.schema !== 'fullbleed.css-explorer.v1' || data.packageSource !== 'published' || data.version !== version
|| !Array.isArray(data.fixtures) || data.fixtures.length !== data.total) throw new Error('Invalid fixture index');
fixtures = new Map(data.fixtures.map((f) => [f.key, f]));
if (fixtures.size !== data.total || data.fixtures.some((f) => !verdicts.includes(f.status))) throw new Error('Invalid verdict inventory');
for (const status of verdicts) {
const count = data.fixtures.filter((f) => f.status === status).length;
if (data.counts[status] !== count) throw new Error('Inconsistent verdict counts');
const span = create('span');
span.append(badge(status), document.createTextNode(` ${count.toLocaleString()}`));
find('counts').append(span);
}
for (const category of [...new Set(data.fixtures.map((f) => f.category))].sort()) {
const option = create('option', category);
option.value = category;
find('category').append(option);
}
find('provenance').append(link('Complete evidence ZIP', data.archiveUrl),
link('Raw comparison JSON', new URL('report.json', indexUrl).href),
link('Run and wheel record', new URL('fullbleed-run.json', indexUrl).href));
const changeFilter = () => { pageNumber = 0; selected = null; saveUrl(); showDetail(null); renderResults(); };
find('search').addEventListener('input', changeFilter);
find('category').addEventListener('change', changeFilter);
find('verdict').addEventListener('change', changeFilter);
find('version').addEventListener('change', () => {
const url = new URL(location.href);
url.searchParams.set('version', find('version').value);
location.assign(url.href);
});
for (const [name, direction] of [['previous', -1], ['next', 1]]) find(name).addEventListener('click', () => {
pageNumber += direction; renderResults(); find('results-status').scrollIntoView({block: 'center'});
});
addEventListener('popstate', restoreUrl);
find('loading').textContent = `${data.total.toLocaleString()} fixtures · published Fullbleed ${data.version} · full comparison gate ${data.gatePassed ? 'passed' : 'failed'}`;
find('controls').hidden = false;
const normalized = new URL(location.href);
normalized.searchParams.set('version', data.version);
history.replaceState(null, '', normalized);
restoreUrl();
saveUrl();
}
start().catch(() => {
find('loading').textContent = 'The fixture browser could not load. Follow the evidence guide link above to download the complete report.';
find('loading').classList.add('ce-error');
find('controls').hidden = true;
});
})();
21 changes: 21 additions & 0 deletions docs/assets/css-corpus/history/2.5.22.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"schema": "fullbleed.css-corpus-publication.v1",
"packageSource": "published",
"version": "2.5.22",
"archiveUrl": "https://github.com/fullbleed-engine/fullbleed-official/releases/download/v2.5.22/fullbleed-2.5.22-published-css-corpus.zip",
"archiveSha256": "31e0072d40c77bcbb56dd73da91f0c9ab614d11d44a5ab5f8b6873f72da5c1e7",
"archiveBytes": 60212323,
"wheelSha256": "b48954edd2c1adbb17011e07f4ad4e6cc416f4d88ff7f072dcd9bc31a8b1b0e9",
"wheelFilename": "fullbleed-2.5.22-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"upstreamCommit": "0d1e53b6d8174d0a5059a8696c24e62759381f6d",
"invocationId": "fullbleed-f76772e29a594e7884800598a61c2410",
"overall": {
"score_pct": 98.48,
"pass": 1619,
"fail": 25,
"reference_disputed": 18,
"total": 1662
},
"gatePassed": false,
"runUrl": "https://github.com/fullbleed-engine/fullbleed-official/actions/runs/38600917475"
}
22 changes: 11 additions & 11 deletions docs/assets/css-corpus/published.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
{
"schema": "fullbleed.css-corpus-publication.v1",
"packageSource": "published",
"version": "2.5.22",
"archiveUrl": "https://github.com/fullbleed-engine/fullbleed-official/releases/download/v2.5.22/fullbleed-2.5.22-published-css-corpus.zip",
"archiveSha256": "31e0072d40c77bcbb56dd73da91f0c9ab614d11d44a5ab5f8b6873f72da5c1e7",
"archiveBytes": 60212323,
"wheelSha256": "b48954edd2c1adbb17011e07f4ad4e6cc416f4d88ff7f072dcd9bc31a8b1b0e9",
"wheelFilename": "fullbleed-2.5.22-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"version": "2.5.34",
"archiveUrl": "https://github.com/fullbleed-engine/fullbleed-official/releases/download/v2.5.34/fullbleed-2.5.34-published-css-corpus.zip",
"archiveSha256": "86a6ead53b6e0ec947ea3cc3c8a4d2fd1d371756749293bd23cfd80cea3e34bc",
"archiveBytes": 60230681,
"wheelSha256": "17b75f0448fa731a0d5e875e1f40f14652579962aeb23f43a85d2fc554a5b28f",
"wheelFilename": "fullbleed-2.5.34-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
"upstreamCommit": "0d1e53b6d8174d0a5059a8696c24e62759381f6d",
"invocationId": "fullbleed-f76772e29a594e7884800598a61c2410",
"invocationId": "fullbleed-ec9e3de34cbd470db971777862a68680",
"overall": {
"score_pct": 98.48,
"pass": 1619,
"fail": 25,
"score_pct": 98.54,
"pass": 1620,
"fail": 24,
"reference_disputed": 18,
"total": 1662
},
"gatePassed": false,
"runUrl": "https://github.com/fullbleed-engine/fullbleed-official/actions/runs/38600917475"
"runUrl": "https://github.com/fullbleed-engine/fullbleed-official/actions/runs/39337511855"
}
Loading
Loading