70 instructions: "Open every pair at original size. Add identity-affecting visual findings, then set status to reviewed. Do not fail harmless antialiasing, video-frame, or small crop differences.",
110 addFinding(findings, "high", "layout", sectionScope, "Source section has no corresponding result section.", { sourceComposition: sourceSection.composition }, "Implement the section with its original position and composition.");
111 continue;
112 }
113 if (match.sourceIndex !== match.resultIndex) {
114 addFinding(findings, "high", "layout", sectionScope, `Section order differs: source position ${match.sourceIndex + 1}, result position ${match.resultIndex + 1}.`, { sourceIndex: match.sourceIndex, resultIndex: match.resultIndex }, "Restore the source section order unless the user explicitly approved the change.");
115 }
116 if (sourceSection.composition !== resultSection.composition) {
117 addFinding(findings, "high", "layout", sectionScope, `Major composition changed from ${sourceSection.composition} to ${resultSection.composition}.`, { source: sourceSection.composition, result: resultSection.composition }, "Rebuild canvas/layers/regions using the source layout blueprint.");
118 }
119 if (sourceSection.background?.kind !== resultSection.background?.kind && sourceSection.background?.kind !== "none") {
120 addFinding(findings, "high", "images", sectionScope, `Background kind changed from ${sourceSection.background?.kind} to ${resultSection.background?.kind}.`, { source: sourceSection.background, result: resultSection.background }, "Restore the source background media/layer model.");
121 }
122 if (sourceSection.identityLocks?.mediaRole !== resultSection.identityLocks?.mediaRole) {
123 addFinding(findings, "high", "layout", sectionScope, `Media role changed from ${sourceSection.identityLocks?.mediaRole} to ${resultSection.identityLocks?.mediaRole}.`, { source: sourceSection.identityLocks?.mediaRole, result: resultSection.identityLocks?.mediaRole }, "Keep background media layered and content media as a peer region exactly as captured.");
144 const best = candidates.sort((a, b) => regionMatchScore(sourceRegion, b.region) - regionMatchScore(sourceRegion, a.region))[0];
145 if (!best) {
146 if (["heading", "action", "media", "tabs"].includes(sourceRegion.role)) addFinding(findings, "high", "layout", { ...scope, region: sourceRegion.role }, `Important ${sourceRegion.role} region is missing.`, { sourceText: sourceRegion.text }, "Restore the semantic region in this section.");
147 continue;
148 }
149 used.add(best.index);
150 const resultRegion = best.region;
151 const important = ["heading", "action", "media", "tabs"].includes(sourceRegion.role);
152 const positionLimit = important ? limits.importantRegionPosition : limits.supportingRegionPosition;
153 const sizeLimit = important ? limits.importantRegionSize : limits.supportingRegionSize;
160 if (sourceGeometry?.lineCount && resultGeometry?.lineCount && sourceGeometry.lineCount !== resultGeometry.lineCount) {
161 addFinding(findings, sourceRegion.role === "heading" ? "high" : "medium", "text", { ...scope, region: sourceRegion.role }, `Line count differs: source ${sourceGeometry.lineCount}, result ${resultGeometry.lineCount}.`, { source: sourceGeometry, result: resultGeometry }, "Restore the measured inline size/wrap policy before changing font size.");
162 }
163 if (sourceGeometry?.inlineSize && resultGeometry?.inlineSize) {
164 const ratio = resultGeometry.inlineSize / sourceGeometry.inlineSize;
165 if (!withinRatio(ratio, limits.importantTextInlineSizeRatio) && important) {
166 addFinding(findings, "high", "dimensions", { ...scope, region: sourceRegion.role }, `Important text inline size is ${formatRatio(ratio)} of source.`, { source: sourceGeometry.inlineSize, result: resultGeometry.inlineSize, ratio }, "Apply the captured width/max-width at this viewport.", limits.importantTextInlineSizeRatio);
188 if (sourceMedia.length > resultMedia.length) {
189 addFinding(findings, "high", "images", scope, `Result exposes fewer meaningful media surfaces (${resultMedia.length}) than source (${sourceMedia.length}).`, { source: sourceMedia, result: resultMedia }, "Restore missing source images/video and their background/content roles.");
190 }
191 for (const sourceItem of sourceMedia) {
192 const best = resultMedia.map((item) => ({ item, score: mediaMatchScore(sourceItem, item) })).sort((a, b) => b.score - a.score)[0];
193 if (!best || best.score < 2) {
194 addFinding(findings, sourceItem.kind === "logo" ? "critical" : "high", "images", scope, `Source ${sourceItem.kind} is not confidently matched in the result: ${sourceItem.label || sourceItem.identity || "unnamed media"}.`, { source: sourceItem, nearest: best || null }, "Use the exact source asset; do not recreate brand media.");
195 continue;
196 }
197 if (sourceItem.kind === "logo" && sourceItem.width && best.item.width) {
198 const ratio = best.item.width / sourceItem.width;
199 if (!withinRatio(ratio, limits.logoSizeRatio)) addFinding(findings, "high", "dimensions", scope, `Logo width is ${formatRatio(ratio)} of source.`, { source: sourceItem, result: best.item, ratio }, "Apply the captured logo variant and target size.", limits.logoSizeRatio);
214 addFinding(findings, "medium", "images", { page: "site-wide" }, `${missing.length} of ${sourceImages.length} source image/media filenames were not observed in result asset extraction.`, { examples: missing.slice(0, 12).map((item) => item.sourceUrl) }, "Review the screenshot pairs and media inventory; restore assets that belong to in-scope visible sections.");
221 for (const item of result.interactionMap?.interactions || []) resultKinds.set(item.kind, (resultKinds.get(item.kind) || 0) + 1);
222 for (const [kind, count] of frequencies(sourceCore.map((item) => item.kind))) {
223 const actual = resultKinds.get(kind) || 0;
224 if (actual < count) addFinding(findings, "high", "interactions", { page: "site-wide" }, `Core interaction coverage for ${kind} is lower in result (${actual}) than source (${count}).`, { source: count, result: actual }, "Restore the missing stateful scene/control behavior and rerun interaction extraction.");
388 return `# Post-build gap analysis — iteration ${String(report.iteration).padStart(3, "0")}\n\nScore: **${summary.score}/100**. Blocking findings: **${summary.blockingCount}**. Visual review: **${report.visualReview.status}**.\n\nThis is an identity comparison with measured tolerances, not a universal pixel-perfect test.\n\n## Findings\n\n${findings}\n\n## Screenshot review queue\n\nOpen every pair at original size and record only identity-affecting differences. Ignore harmless antialiasing, dynamic video frames, consent UI, and small crop differences within tolerance.\n\n${pairs}\n\n## Fix order\n\n${report.fixOrder.map((item, index) => `${index + 1}. ${item}`).join("\n")}\n`;
395 return `# Gap fix plan — iteration ${String(report.iteration).padStart(3, "0")}\n\nDo not begin fixes until the screenshot queue in \`gap-analysis.json\` is reviewed. Fix identity and structure before polish.\n\n## Blocking\n\n${rows(blocking)}\n\n## Supporting\n\n${rows(supporting)}\n\n## Completion\n\n- [ ] Build succeeds.\n- [ ] Result extraction rerun as a new iteration.\n- [ ] No open critical/high findings.\n- [ ] Screenshot review completed.\n`;