-
-
+
+
@@ -178,7 +187,14 @@
const $ = id => document.getElementById(id);
const ns = 'http://www.w3.org/2000/svg';
const errors = id => data.issues.filter(i => i.regionId === id && i.severity === 'error');
- const problemRegions = data.regions.filter(r => errors(r.id).length);
+ // A partial exclusion needs visual inspection even when all boxes are valid.
+ // Do not claim it is a failed asset or let zero hard errors hide it.
+ const partialMasks = data.regions.filter(r => r.reference?.excludedPixels > 0 && !r.reference.fullyExcluded);
+ const problemRegions = data.regions.filter(r => errors(r.id).length || r.reference?.excludedPixels > 0)
+ .sort((a, b) => Number(!!errors(b.id).length) - Number(!!errors(a.id).length)
+ || (b.reference?.excludedFraction || 0) - (a.reference?.excludedFraction || 0));
+ const affectedBy = id => data.regions.filter(r => r.reference?.regions.some(m => m.id === id));
+ const percent = ref => (100 * ref.excludedFraction).toFixed(1).replace(/\.0$/, '');
let selected = null;
let filter = problemRegions.length ? 'problems' : 'all';
let wholeComp = false;
@@ -199,23 +215,31 @@
}
function renderList() {
$('list').replaceChildren();
- $('problems').textContent = `Problems (${problemRegions.length})`;
+ $('problems').textContent = `Inspect (${problemRegions.length})`;
$('all').textContent = `All (${data.regions.length})`;
for (const key of ['problems', 'all']) $(key).setAttribute('aria-pressed', String(filter === key));
for (const r of filter === 'problems' ? problemRegions : data.regions) {
- const button = element('button', '', `region ${errors(r.id).length ? 'error' : ''}`);
+ const button = element('button', '', `region ${errors(r.id).length ? 'error' : r.reference?.excludedPixels ? 'masked' : ''}`);
+ button.setAttribute('aria-label', name(r));
button.setAttribute('aria-current', String(selected?.id === r.id));
- button.append(element('span', '', 'dot'), element('span', name(r)));
+ button.append(element('span', '', 'dot'), element('span', name(r), 'name'));
+ if (errors(r.id).length || r.reference?.excludedPixels) {
+ const label = errors(r.id).length ? 'Error' : `${percent(r.reference)}%`;
+ const badge = element('span', label, 'status');
+ badge.title = errors(r.id).length ? 'Map error' : `${label} excluded — inspect foreground bounds`;
+ button.setAttribute('aria-description', badge.title);
+ button.append(badge);
+ }
button.onclick = () => select(r);
$('list').append(button);
}
- if (!$('list').children.length) $('list').append(element('p', 'No drawable regions have errors. Other map findings lists rejected entries.', 'empty'));
+ if (!$('list').children.length) $('list').append(element('p', 'No drawable regions have errors or masked pixels. Crop accuracy still needs visual inspection; rejected entries are in Other map findings.', 'empty'));
}
function renderContext() {
if (!selected) return;
const svg = $('context');
svg.replaceChildren();
- const covering = (selected.reference?.regions || []).map(m => data.regions.find(r => r.id === m.id)).filter(Boolean);
+ const covering = selected.reference ? selected.reference.regions.map(m => data.regions.find(r => r.id === m.id)).filter(Boolean) : affectedBy(selected.id);
const boxes = [selected, ...covering];
const left = Math.min(...boxes.map(r => r.px.x));
const top = Math.min(...boxes.map(r => r.px.y));
@@ -232,6 +256,7 @@
}
svg.append(svgElement('rect', { x: selected.px.x, y: selected.px.y, width: selected.px.w, height: selected.px.h, fill: 'none', stroke: '#00685f', 'stroke-width': 3, 'vector-effect': 'non-scaling-stroke' }));
$('covering-key').hidden = covering.length === 0;
+ $('covering-key').lastChild.textContent = selected.reference ? ' Overlapping foreground' : ' Affected artwork';
$('context-toggle').textContent = wholeComp ? 'Show nearby context' : 'Show whole comp';
$('context-toggle').setAttribute('aria-pressed', String(wholeComp));
}
@@ -262,20 +287,35 @@
selected = r;
$('title').textContent = name(r);
const ref = r.reference;
- const percent = ref ? (100 * ref.excludedFraction).toFixed(1).replace(/\.0$/, '') : '0';
- const covering = ref?.regions.map(m => name(data.regions.find(r => r.id === m.id))).join(', ');
+ const percentage = ref ? percent(ref) : '0';
+ const sources = (ref?.regions || []).map(m => data.regions.find(r => r.id === m.id)).filter(Boolean);
+ const affected = affectedBy(r.id);
+ const covering = sources.map(name).join(', ');
$('explanation').className = errors(r.id).length ? 'error' : '';
if (ref?.fullyExcluded) {
$('explanation').textContent = `The “${covering}” box hides this entire asset from the checker.`;
$('impact').textContent = 'Every hatched pixel is ignored. Nothing remains to compare, even if the generated asset is perfect. The covering box needs correcting.';
} else if (ref?.excludedPixels) {
- $('explanation').textContent = `${percent}% of this asset is excluded by overlapping text or controls.`;
- $('impact').textContent = 'Hatched pixels are ignored; the rest is compared. This is expected only where text or controls actually sit in front of the artwork.';
+ $('explanation').textContent = `${percentage}% excluded — check the foreground bounds.`;
+ $('impact').textContent = 'Hatching should cover foreground text or controls only. If it covers artwork in the gaps, split the foreground into tighter boxes. Keep every text element.';
+ } else if (affected.length) {
+ $('explanation').textContent = `This box excludes pixels from ${affected.length} ${affected.length === 1 ? 'asset' : 'assets'}.`;
+ $('impact').textContent = 'The entire overlap is masked, including empty space. Bound separate text elements separately; use a container only to describe their layout, retaining every child.';
} else {
$('explanation').textContent = ref ? 'No overlapping text or controls hide this asset from the checker.' : 'This region is built in code. Check that its box names one element.';
$('impact').textContent = ref ? 'All pixels in this crop remain available for comparison. Still check that the crop contains the right artwork.' : 'Code regions that overlap artwork can exclude those pixels from asset comparison. Containers are treated separately.';
}
- $('ignored-label').textContent = ref ? `Ignored pixels · ${percent}%` : 'Same region';
+ $('related').replaceChildren();
+ const related = ref ? sources : affected;
+ if (related.length) {
+ $('related').append(element('span', ref ? 'Excluding boxes:' : 'Inspect affected assets:'));
+ for (const other of related) {
+ const button = element('button', name(other));
+ button.onclick = () => select(other);
+ $('related').append(button);
+ }
+ }
+ $('ignored-label').textContent = ref ? `Ignored pixels · ${percentage}%` : 'Same region';
$('asset-key').textContent = ref ? 'Asset boundary' : 'Region boundary';
$('details').replaceChildren();
for (const text of [r.id, r.note, `${r.kind} · ${r.px.w} × ${r.px.h} original pixels · x ${r.px.x}, y ${r.px.y}`, r.parentId && `Parent: ${r.parentId}`, r.reviewGroup && `Review group: ${r.reviewGroup} (every member retained)`].filter(Boolean)) $('details').append(element('p', text));
@@ -287,14 +327,16 @@
}
$('details').append(element('p', 'Reference only. These images cannot ship as page assets. Inspection does not change build state or approvals.'));
$('next').hidden = problemRegions.length < 2;
- $('next').textContent = `Next problem (${Math.max(0, problemRegions.findIndex(p => p.id === r.id)) + 1}/${problemRegions.length}) →`;
+ const queueIndex = problemRegions.findIndex(p => p.id === r.id);
+ $('next').textContent = queueIndex >= 0 ? `Next check (${queueIndex + 1}/${problemRegions.length}) →` : 'Next check →';
+ history.replaceState(null, '', `#region=${encodeURIComponent(r.id)}`);
renderList(); renderContext(); renderCrop($('original'), false); renderCrop($('ignored'), true);
}
for (const key of ['all', 'problems']) $(key).onclick = () => { filter = key; renderList(); };
$('next').onclick = () => select(problemRegions[(problemRegions.findIndex(r => r.id === selected.id) + 1) % problemRegions.length]);
$('context-toggle').onclick = () => { wholeComp = !wholeComp; renderContext(); };
$('zoom').onchange = () => { if (selected) { renderCrop($('original'), false); renderCrop($('ignored'), true); } };
- $('summary').textContent = `${data.inputRegionCount} regions · ${data.issues.filter(i => i.severity === 'error').length} errors`;
+ $('summary').textContent = `${data.inputRegionCount} regions · ${data.issues.filter(i => i.severity === 'error').length} errors · ${partialMasks.length} partial masks to inspect`;
const otherIssues = data.issues.filter(i => i.code !== 'fully-masked' && i.code !== 'foreground-mask');
$('diagnostics-label').textContent = `Other map findings (${otherIssues.length})`;
for (const i of otherIssues) {
@@ -302,7 +344,8 @@
item.append(element('b', i.regionId || i.code.replaceAll('-', ' ')), element('span', i.message));
$('issues').append(item);
}
- if (data.regions.length) select(problemRegions[0] || data.regions[0]);
+ const requestedRegion = new URLSearchParams(location.hash.slice(1)).get('region');
+ if (data.regions.length) select(data.regions.find(r => r.id === requestedRegion) || problemRegions[0] || data.regions[0]);
else { $('inspection').hidden = true; $('next').hidden = true; $('diagnostics').open = true; renderList(); }