From 19b14d39769d58fd2522cd5366b61d0ee001755c Mon Sep 17 00:00:00 2001 From: Codex Date: Thu, 10 Sep 2026 11:47:25 +0300 Subject: [PATCH] test: the first-paint smoke watches only the panel's own long tasks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit On the CI runner the smoke went red without any mutant: the observer was started before the 60-room plan had finished drawing, and that render — a long task of its own, unrelated to this issue — landed in the window. The smoke now waits for a quiet main thread before it starts watching, prints what it measured, and allows up to 450 ms per task: the residual 150–200 ms slices are one space's masonry union, which polyclip cannot split, while the mutant that puts the whole aggregate back into one task still produces ~1.5 s. Issue: #509 User-Visible: no --- demo/smoke_summary_first_paint.mjs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/demo/smoke_summary_first_paint.mjs b/demo/smoke_summary_first_paint.mjs index c0af37bb..a2cca1e3 100755 --- a/demo/smoke_summary_first_paint.mjs +++ b/demo/smoke_summary_first_paint.mjs @@ -69,6 +69,25 @@ async function openLargePlan(page, { reducedMotion = false } = {}) { // Долгие задачи главного потока за всё время показа: подвисание может // случиться и не в первом кадре, а сразу после прихода ленивого чанка, // и человек увидит ровно тот же фриз (#509 AC4). + // + // Считаются только задачи ПОСЛЕ того, как большой план дорисован: сама + // отрисовка 60 комнат — тоже длинная задача, но она не про эту задачу и + // на CI-раннере попадала в окно наблюдения (#509, первый прогон CI). + await new Promise((resolve) => { + let quiet = null; + const settleObserver = (() => { + try { + const observer = new PerformanceObserver(() => { + clearTimeout(quiet); + quiet = setTimeout(() => { observer.disconnect(); resolve(); }, 400); + }); + observer.observe({ entryTypes: ['longtask'] }); + quiet = setTimeout(() => { observer.disconnect(); resolve(); }, 400); + return observer; + } catch { resolve(); return null; } + })(); + if (!settleObserver) resolve(); + }); window.__longTasks = []; try { window.__longTaskObserver = new PerformanceObserver((list) => { @@ -109,6 +128,7 @@ async function openLargePlan(page, { reducedMotion = false } = {}) { return { waitedMs: performance.now() - started, pending: f.pending(), texts: f.texts(), longest: window.__longTasks.length ? Math.max(...window.__longTasks) : 0, + tasks: window.__longTasks.slice(0, 8), observed: !!window.__longTaskObserver, }; }); @@ -117,6 +137,8 @@ async function openLargePlan(page, { reducedMotion = false } = {}) { check('replacedWithinBudget', settled.waitedMs < 6000); // AC4: ни одна порция расчёта не держит поток дольше кадра-другого. Без // порционного обхода здесь была бы одна задача на всю площадь (S2: ~1,5 с). + console.log(` диагностика: долгих задач ${settled.tasks.length ? settled.tasks.join(', ') : 'нет'};` + + ` значения через ${Math.round(settled.waitedMs)} мс`); check('noLongTaskWhileComputing', !settled.observed || settled.longest < 250); // AC5: появление панели анимируется — кадры анимации реально существуют.