(params: ValidationLoopParams)
| 196 | } |
| 197 | |
| 198 | export async function validationLoop(params: ValidationLoopParams): Promise<ValidationLoopResult> { |
| 199 | // Ensure dependencies are installed before starting validation loop |
| 200 | ensureValidationDependencies(); |
| 201 | |
| 202 | const { protocol, figmaThumbnailUrl, outputDir, workspace } = params; |
| 203 | |
| 204 | if (!protocol || !figmaThumbnailUrl || !outputDir || !workspace) { |
| 205 | throw new Error('Something wrong in validation loop, missing required parameters...'); |
| 206 | } |
| 207 | |
| 208 | const config: ValidationLoopConfig = { |
| 209 | ...DEFAULT_VALIDATION_LOOP_CONFIG, |
| 210 | ...params.config, |
| 211 | }; |
| 212 | const mode: ValidationMode.ReportOnly | ValidationMode.Full = config.mode ?? ValidationMode.Full; |
| 213 | const maxIterations = mode === ValidationMode.ReportOnly ? 1 : config.maxIterations; |
| 214 | const visualizationTool = new VisualizationTool(); |
| 215 | |
| 216 | // Variables to track server state (will be initialized in iteration 1) |
| 217 | let currentServerUrl: string | undefined; |
| 218 | let serverKey: string | undefined; |
| 219 | |
| 220 | try { |
| 221 | // Extract unified validation context from protocol (single traversal) |
| 222 | const validationContext = extractValidationContext(protocol); |
| 223 | const designOffset: [number, number] = [validationContext.offset.x, validationContext.offset.y]; |
| 224 | if (Math.abs(designOffset[0]) >= 1 || Math.abs(designOffset[1]) >= 1) { |
| 225 | logger.printInfoLog(`Design offset: (${designOffset[0].toFixed(0)}, ${designOffset[1].toFixed(0)} px)`); |
| 226 | } |
| 227 | |
| 228 | // Extract component paths from context (already resolved to absolute filesystem paths) |
| 229 | const resolvedComponentPaths = extractComponentPaths(validationContext, workspace); |
| 230 | |
| 231 | // Build element registry for compatibility with existing APIs |
| 232 | const elementRegistry = toElementMetadataRegistry(validationContext); |
| 233 | |
| 234 | // Download and cache Figma thumbnail once to avoid redundant downloads in each iteration |
| 235 | logger.printInfoLog('Downloading Figma thumbnail (will be cached for all iterations)...'); |
| 236 | const cachedFigmaThumbnailBase64 = await downloadImage(figmaThumbnailUrl, undefined, undefined, true); |
| 237 | logger.printSuccessLog('Figma thumbnail cached successfully'); |
| 238 | |
| 239 | const iterations: IterationLog[] = []; |
| 240 | const componentHistory: ComponentHistory = {}; |
| 241 | let previousScreenshotPath: string | undefined; |
| 242 | let currentMae = -1; // Sentinel value: -1 indicates no measurement yet |
| 243 | let currentSae = 0; |
| 244 | let lastMisalignedCount = 0; |
| 245 | let lastValidationResult: ValidationIterationResult | undefined; |
| 246 | // Track paths to last iteration's individual screenshots for report reuse |
| 247 | let lastRenderMarkedPath: string | undefined; |
| 248 | let lastTargetMarkedPath: string | undefined; |
| 249 | |
| 250 | for (let iteration = 1; iteration <= maxIterations; iteration++) { |
| 251 | // Use different logging for reportOnly mode |
| 252 | if (mode === ValidationMode.ReportOnly) { |
| 253 | logger.printLog(`\n${'='.repeat(60)}`); |
| 254 | logger.printLog(`Running validation (report-only mode)`); |
| 255 | logger.printLog(`${'='.repeat(60)}`); |
no test coverage detected