diff --git a/perf/results/baseline-2026-10-07.json b/perf/results/baseline-2026-10-07.json new file mode 100644 index 0000000..3c83f1a --- /dev/null +++ b/perf/results/baseline-2026-10-07.json @@ -0,0 +1,1639 @@ +{ + "date": "2026-10-06T19:53:30.775Z", + "commit": "64f8eae92bc69857875a91e3f79be9c166766577", + "runsConfigured": 5, + "browsers": { + "chromium": { + "lte": { + "cold": { + "requestCount": { + "median": 164, + "min": 164, + "max": 164, + "values": [ + 164, + 164, + 164, + 164, + 164 + ] + }, + "wireBytes": { + "median": 1039315, + "min": 1039315, + "max": 1039315, + "values": [ + 1039315, + 1039315, + 1039315, + 1039315, + 1039315 + ] + }, + "fcp": { + "median": 1540, + "min": 1524, + "max": 1556, + "values": [ + 1544, + 1524, + 1540, + 1532, + 1556 + ] + }, + "lcp": { + "median": 1896, + "min": 1880, + "max": 1920, + "values": [ + 1904, + 1880, + 1896, + 1880, + 1920 + ] + }, + "domContentLoaded": { + "median": 10033, + "min": 10004, + "max": 10677, + "values": [ + 10677, + 10033, + 10004, + 10005, + 10048 + ] + }, + "bootDone": { + "median": 10163, + "min": 10129, + "max": 10798, + "values": [ + 10798, + 10163, + 10129, + 10138, + 10180 + ] + }, + "longTaskTotal": { + "median": 397, + "min": 385, + "max": 422, + "values": [ + 397, + 385, + 393, + 398, + 422 + ] + }, + "swInstallDuration": { + "median": 38446, + "min": 36687, + "max": 39157, + "values": [ + 39157, + 36687, + 38446, + 38498, + 37723 + ] + }, + "swInstallBytes": { + "median": 1039315, + "min": 1039315, + "max": 1039315, + "values": [ + 1039315, + 1039315, + 1039315, + 1039315, + 1039315 + ] + }, + "storageEstimate": { + "usage": 3480029, + "quota": 4298447325 + }, + "mediaReady": { + "median": null, + "min": null, + "max": null, + "values": [ + null, + null, + null, + null, + null + ] + }, + "autoplayBlocked": true, + "wireBytesByType": { + "html": 27057, + "js": 477223, + "css": 130718, + "fonts": 397072, + "images": 6278, + "media": 0, + "api": 336, + "other": 631 + } + }, + "warm": { + "requestCount": { + "median": 4, + "min": 3, + "max": 5, + "values": [ + 4, + 3, + 4, + 5, + 4 + ] + }, + "wireBytes": { + "median": 5321, + "min": 97, + "max": 10545, + "values": [ + 5321, + 97, + 5321, + 10545, + 5321 + ] + }, + "fcp": { + "median": 180, + "min": 172, + "max": 216, + "values": [ + 216, + 176, + 188, + 172, + 180 + ] + }, + "lcp": { + "median": 684, + "min": 172, + "max": 704, + "values": [ + 704, + 176, + 704, + 172, + 684 + ] + }, + "domContentLoaded": { + "median": 558, + "min": 507, + "max": 573, + "values": [ + 573, + 508, + 566, + 507, + 558 + ] + }, + "bootDone": { + "median": 675, + "min": 622, + "max": 692, + "values": [ + 692, + 622, + 691, + 625, + 675 + ] + }, + "longTaskTotal": { + "median": 156, + "min": 148, + "max": 165, + "values": [ + 156, + 148, + 163, + 152, + 165 + ] + } + }, + "offline": { + "requestCount": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + }, + "wireBytes": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + }, + "fcp": { + "median": 72, + "min": 68, + "max": 80, + "values": [ + 76, + 80, + 72, + 68, + 72 + ] + }, + "lcp": { + "median": 108, + "min": 72, + "max": 228, + "values": [ + 76, + 108, + 228, + 228, + 72 + ] + }, + "domContentLoaded": { + "median": 199, + "min": 197, + "max": 221, + "values": [ + 221, + 199, + 199, + 197, + 197 + ] + }, + "bootDone": { + "median": 219, + "min": 219, + "max": 228, + "values": [ + 228, + 220, + 219, + 219, + 219 + ] + }, + "reloadDuration": { + "median": 240, + "min": 238, + "max": 257, + "values": [ + 257, + 240, + 241, + 239, + 238 + ] + } + }, + "updateJs": { + "requestCount": { + "median": 88, + "min": 88, + "max": 88, + "values": [ + 88, + 88, + 88, + 88, + 88 + ] + }, + "wireBytes": { + "median": 610965, + "min": 610965, + "max": 610965, + "values": [ + 610965, + 610965, + 610965, + 610965, + 610965 + ] + }, + "duration": { + "median": 2013, + "min": 45, + "max": 2073, + "values": [ + 2073, + 2013, + 45, + 2063, + 1958 + ] + }, + "wireBytesByType": { + "html": 17998, + "js": 266589, + "css": 65359, + "fonts": 254048, + "images": 6278, + "media": 0, + "api": 62, + "other": 631 + } + }, + "updateCss": { + "requestCount": { + "median": 88, + "min": 86, + "max": 88, + "values": [ + 88, + 86, + 88, + 88, + 87 + ] + }, + "wireBytes": { + "median": 611005, + "min": 605714, + "max": 611005, + "values": [ + 611005, + 605714, + 611005, + 611005, + 605776 + ] + }, + "duration": { + "median": 1970, + "min": 1956, + "max": 2071, + "values": [ + 1989, + 1961, + 2071, + 1970, + 1956 + ] + }, + "wireBytesByType": { + "html": 17990, + "js": 266609, + "css": 65387, + "fonts": 254048, + "images": 6278, + "media": 0, + "api": 62, + "other": 631 + } + } + }, + "lossy": { + "cold": { + "requestCount": { + "median": 164, + "min": 164, + "max": 164, + "values": [ + 164, + 164, + 164, + 164, + 164 + ] + }, + "wireBytes": { + "median": 1039309, + "min": 1039309, + "max": 1039309, + "values": [ + 1039309, + 1039309, + 1039309, + 1039309, + 1039309 + ] + }, + "fcp": { + "median": 2344, + "min": 2080, + "max": 2368, + "values": [ + 2348, + 2344, + 2368, + 2336, + 2080 + ] + }, + "lcp": { + "median": 2912, + "min": 2228, + "max": 2940, + "values": [ + 2916, + 2912, + 2940, + 2904, + 2228 + ] + }, + "domContentLoaded": { + "median": 15875, + "min": 15753, + "max": 15903, + "values": [ + 15850, + 15903, + 15875, + 15875, + 15753 + ] + }, + "bootDone": { + "median": 16015, + "min": 15871, + "max": 16031, + "values": [ + 15975, + 16031, + 16015, + 16018, + 15871 + ] + }, + "longTaskTotal": { + "median": 432, + "min": 379, + "max": 434, + "values": [ + 379, + 404, + 433, + 434, + 432 + ] + }, + "swInstallDuration": { + "median": 42518, + "min": 42354, + "max": 44366, + "values": [ + 42469, + 44313, + 42518, + 44366, + 42354 + ] + }, + "swInstallBytes": { + "median": 1039309, + "min": 1039309, + "max": 1039309, + "values": [ + 1039309, + 1039309, + 1039309, + 1039309, + 1039309 + ] + }, + "storageEstimate": { + "usage": 3480104, + "quota": 5372189224 + }, + "mediaReady": { + "median": null, + "min": null, + "max": null, + "values": [ + null, + null, + null, + null, + null + ] + }, + "autoplayBlocked": true, + "wireBytesByType": { + "html": 26985, + "js": 477233, + "css": 130774, + "fonts": 397072, + "images": 6278, + "media": 0, + "api": 336, + "other": 631 + } + }, + "warm": { + "requestCount": { + "median": 4, + "min": 3, + "max": 4, + "values": [ + 3, + 4, + 4, + 4, + 3 + ] + }, + "wireBytes": { + "median": 5326, + "min": 97, + "max": 5326, + "values": [ + 97, + 5326, + 5326, + 5326, + 97 + ] + }, + "fcp": { + "median": 184, + "min": 180, + "max": 196, + "values": [ + 196, + 180, + 184, + 180, + 184 + ] + }, + "lcp": { + "median": 644, + "min": 184, + "max": 708, + "values": [ + 644, + 708, + 656, + 628, + 184 + ] + }, + "domContentLoaded": { + "median": 531, + "min": 500, + "max": 567, + "values": [ + 514, + 567, + 531, + 500, + 539 + ] + }, + "bootDone": { + "median": 651, + "min": 618, + "max": 697, + "values": [ + 632, + 697, + 651, + 618, + 651 + ] + }, + "longTaskTotal": { + "median": 155, + "min": 148, + "max": 167, + "values": [ + 155, + 167, + 155, + 155, + 148 + ] + } + }, + "offline": { + "requestCount": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + }, + "wireBytes": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + }, + "fcp": { + "median": 80, + "min": 68, + "max": 92, + "values": [ + 88, + 68, + 72, + 80, + 92 + ] + }, + "lcp": { + "median": 92, + "min": 68, + "max": 244, + "values": [ + 88, + 68, + 244, + 244, + 92 + ] + }, + "domContentLoaded": { + "median": 214, + "min": 194, + "max": 279, + "values": [ + 220, + 194, + 210, + 214, + 279 + ] + }, + "bootDone": { + "median": 237, + "min": 216, + "max": 323, + "values": [ + 241, + 216, + 233, + 237, + 323 + ] + }, + "reloadDuration": { + "median": 253, + "min": 237, + "max": 343, + "values": [ + 255, + 237, + 251, + 253, + 343 + ] + } + }, + "updateJs": { + "requestCount": { + "median": 88, + "min": 86, + "max": 88, + "values": [ + 88, + 86, + 88, + 88, + 88 + ] + }, + "wireBytes": { + "median": 610965, + "min": 605674, + "max": 610965, + "values": [ + 610965, + 605674, + 610965, + 610965, + 610965 + ] + }, + "duration": { + "median": 2031, + "min": 207, + "max": 2071, + "values": [ + 2002, + 2031, + 2034, + 207, + 2071 + ] + }, + "wireBytesByType": { + "html": 17998, + "js": 266589, + "css": 65359, + "fonts": 254048, + "images": 6278, + "media": 0, + "api": 62, + "other": 631 + } + }, + "updateCss": { + "requestCount": { + "median": 88, + "min": 88, + "max": 88, + "values": [ + 88, + 88, + 88, + 88, + 88 + ] + }, + "wireBytes": { + "median": 611005, + "min": 611005, + "max": 611005, + "values": [ + 611005, + 611005, + 611005, + 611005, + 611005 + ] + }, + "duration": { + "median": 2004, + "min": 1955, + "max": 2043, + "values": [ + 1955, + 1984, + 2004, + 2043, + 2035 + ] + }, + "wireBytesByType": { + "html": 17990, + "js": 266609, + "css": 65387, + "fonts": 254048, + "images": 6278, + "media": 0, + "api": 62, + "other": 631 + } + } + } + }, + "webkit": { + "lte": { + "cold": { + "requestCount": { + "median": 162, + "min": 162, + "max": 162, + "values": [ + 162, + 162, + 162, + 162, + 162 + ] + }, + "wireBytes": { + "median": 1179624, + "min": 1179624, + "max": 1179624, + "values": [ + 1179624, + 1179624, + 1179624, + 1179624, + 1179624 + ] + }, + "fcp": { + "median": 1088, + "min": 1026, + "max": 1105, + "values": [ + 1100, + 1026, + 1105, + 1083, + 1088 + ] + }, + "lcp": { + "median": 1287, + "min": 1216, + "max": 1304, + "values": [ + 1304, + 1216, + 1297, + 1272, + 1287 + ] + }, + "domContentLoaded": { + "median": 3178, + "min": 3118, + "max": 3210, + "values": [ + 3178, + 3118, + 3179, + 3161, + 3210 + ] + }, + "bootDone": { + "median": 3201, + "min": 3138, + "max": 3245, + "values": [ + 3201, + 3138, + 3201, + 3172, + 3245 + ] + }, + "longTaskTotal": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + }, + "swInstallDuration": { + "median": 30508, + "min": 30391, + "max": 32376, + "values": [ + 30508, + 30391, + 32376, + 30425, + 31458 + ] + }, + "swInstallBytes": { + "median": 1179624, + "min": 1179624, + "max": 1179624, + "values": [ + 1179624, + 1179624, + 1179624, + 1179624, + 1179624 + ] + }, + "storageEstimate": { + "usage": null, + "quota": null + }, + "mediaReady": { + "median": null, + "min": null, + "max": null, + "values": [ + null, + null, + null, + null, + null + ] + }, + "autoplayBlocked": true, + "wireBytesByType": { + "html": 32535, + "js": 585292, + "css": 156760, + "fonts": 397072, + "images": 6278, + "media": 0, + "api": 121, + "other": 1566 + } + }, + "warm": { + "requestCount": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + }, + "wireBytes": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + }, + "fcp": { + "median": 110, + "min": 104, + "max": 121, + "values": [ + 104, + 110, + 105, + 111, + 121 + ] + }, + "lcp": { + "median": 110, + "min": 104, + "max": 157, + "values": [ + 104, + 110, + 105, + 111, + 157 + ] + }, + "domContentLoaded": { + "median": 101, + "min": 96, + "max": 111, + "values": [ + 96, + 101, + 97, + 102, + 111 + ] + }, + "bootDone": { + "median": 131, + "min": 122, + "max": 149, + "values": [ + 124, + 131, + 122, + 131, + 149 + ] + }, + "longTaskTotal": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + } + }, + "offline": { + "requestCount": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + }, + "wireBytes": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + }, + "fcp": { + "median": 107, + "min": 104, + "max": 109, + "values": [ + 105, + 107, + 109, + 104, + 108 + ] + }, + "lcp": { + "median": 107, + "min": 104, + "max": 109, + "values": [ + 105, + 107, + 109, + 104, + 108 + ] + }, + "domContentLoaded": { + "median": 98, + "min": 95, + "max": 101, + "values": [ + 96, + 98, + 101, + 95, + 100 + ] + }, + "bootDone": { + "median": 128, + "min": 124, + "max": 130, + "values": [ + 128, + 128, + 130, + 124, + 127 + ] + }, + "reloadDuration": { + "median": 135, + "min": 131, + "max": 136, + "values": [ + 135, + 135, + 136, + 131, + 134 + ] + } + }, + "updateJs": { + "requestCount": { + "median": 86, + "min": 86, + "max": 86, + "values": [ + 86, + 86, + 86, + 86, + 86 + ] + }, + "wireBytes": { + "median": 680250, + "min": 680250, + "max": 680250, + "values": [ + 680250, + 680250, + 680250, + 680250, + 680250 + ] + }, + "duration": { + "median": 4457, + "min": 2579, + "max": 4520, + "values": [ + 4478, + 4457, + 3625, + 4520, + 2579 + ] + }, + "wireBytesByType": { + "html": 21688, + "js": 319109, + "css": 78344, + "fonts": 254048, + "images": 6278, + "media": 0, + "api": 0, + "other": 783 + } + }, + "updateCss": { + "requestCount": { + "median": 86, + "min": 86, + "max": 86, + "values": [ + 86, + 86, + 86, + 86, + 86 + ] + }, + "wireBytes": { + "median": 680265, + "min": 680265, + "max": 680265, + "values": [ + 680265, + 680265, + 680265, + 680265, + 680265 + ] + }, + "duration": { + "median": 4510, + "min": 4449, + "max": 4554, + "values": [ + 4528, + 4554, + 4510, + 4449, + 4458 + ] + }, + "wireBytesByType": { + "html": 21690, + "js": 319086, + "css": 78380, + "fonts": 254048, + "images": 6278, + "media": 0, + "api": 0, + "other": 783 + } + } + }, + "lossy": { + "cold": { + "requestCount": { + "median": 162, + "min": 162, + "max": 162, + "values": [ + 162, + 162, + 162, + 162, + 162 + ] + }, + "wireBytes": { + "median": 1179624, + "min": 1179624, + "max": 1179624, + "values": [ + 1179624, + 1179624, + 1179624, + 1179624, + 1179624 + ] + }, + "fcp": { + "median": 3557, + "min": 3216, + "max": 3838, + "values": [ + 3838, + 3229, + 3768, + 3216, + 3557 + ] + }, + "lcp": { + "median": 3735, + "min": 3413, + "max": 4034, + "values": [ + 4034, + 3413, + 3959, + 3415, + 3735 + ] + }, + "domContentLoaded": { + "median": 7281, + "min": 6948, + "max": 8688, + "values": [ + 6948, + 8688, + 8655, + 7281, + 7181 + ] + }, + "bootDone": { + "median": 7303, + "min": 6963, + "max": 8710, + "values": [ + 6963, + 8710, + 8685, + 7303, + 7202 + ] + }, + "longTaskTotal": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + }, + "swInstallDuration": { + "median": 35449, + "min": 35013, + "max": 36906, + "values": [ + 35621, + 36906, + 35013, + 35449, + 35405 + ] + }, + "swInstallBytes": { + "median": 1179624, + "min": 1179624, + "max": 1179624, + "values": [ + 1179624, + 1179624, + 1179624, + 1179624, + 1179624 + ] + }, + "storageEstimate": { + "usage": null, + "quota": null + }, + "mediaReady": { + "median": null, + "min": null, + "max": null, + "values": [ + null, + null, + null, + null, + null + ] + }, + "autoplayBlocked": true, + "wireBytesByType": { + "html": 32535, + "js": 585292, + "css": 156760, + "fonts": 397072, + "images": 6278, + "media": 0, + "api": 121, + "other": 1566 + } + }, + "warm": { + "requestCount": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + }, + "wireBytes": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + }, + "fcp": { + "median": 111, + "min": 104, + "max": 124, + "values": [ + 112, + 107, + 104, + 124, + 111 + ] + }, + "lcp": { + "median": 112, + "min": 107, + "max": 130, + "values": [ + 112, + 107, + 130, + 124, + 111 + ] + }, + "domContentLoaded": { + "median": 102, + "min": 96, + "max": 115, + "values": [ + 104, + 96, + 96, + 115, + 102 + ] + }, + "bootDone": { + "median": 131, + "min": 124, + "max": 144, + "values": [ + 131, + 124, + 124, + 144, + 134 + ] + }, + "longTaskTotal": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + } + }, + "offline": { + "requestCount": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + }, + "wireBytes": { + "median": 0, + "min": 0, + "max": 0, + "values": [ + 0, + 0, + 0, + 0, + 0 + ] + }, + "fcp": { + "median": 109, + "min": 104, + "max": 119, + "values": [ + 119, + 113, + 109, + 108, + 104 + ] + }, + "lcp": { + "median": 119, + "min": 104, + "max": 142, + "values": [ + 119, + 142, + 136, + 108, + 104 + ] + }, + "domContentLoaded": { + "median": 101, + "min": 96, + "max": 111, + "values": [ + 111, + 104, + 101, + 100, + 96 + ] + }, + "bootDone": { + "median": 129, + "min": 123, + "max": 140, + "values": [ + 140, + 135, + 129, + 128, + 123 + ] + }, + "reloadDuration": { + "median": 141, + "min": 130, + "max": 147, + "values": [ + 147, + 147, + 141, + 134, + 130 + ] + } + }, + "updateJs": { + "requestCount": { + "median": 86, + "min": 86, + "max": 87, + "values": [ + 86, + 86, + 86, + 87, + 86 + ] + }, + "wireBytes": { + "median": 680250, + "min": 680250, + "max": 686464, + "values": [ + 680250, + 680250, + 680250, + 686464, + 680250 + ] + }, + "duration": { + "median": 8135, + "min": 7243, + "max": 8398, + "values": [ + 8135, + 8398, + 7880, + 8313, + 7243 + ] + }, + "wireBytesByType": { + "html": 21688, + "js": 319109, + "css": 78344, + "fonts": 254048, + "images": 6278, + "media": 0, + "api": 0, + "other": 783 + } + }, + "updateCss": { + "requestCount": { + "median": 87, + "min": 86, + "max": 87, + "values": [ + 86, + 87, + 87, + 86, + 87 + ] + }, + "wireBytes": { + "median": 680327, + "min": 680265, + "max": 686479, + "values": [ + 680265, + 686479, + 686479, + 680265, + 680327 + ] + }, + "duration": { + "median": 8269, + "min": 7987, + "max": 8799, + "values": [ + 8269, + 8791, + 7987, + 8157, + 8799 + ] + }, + "wireBytesByType": { + "html": 21690, + "js": 319086, + "css": 78380, + "fonts": 254048, + "images": 6278, + "media": 0, + "api": 0, + "other": 783 + } + } + } + } + } +} \ No newline at end of file diff --git a/plans/perf-baseline.md b/plans/perf-baseline.md new file mode 100644 index 0000000..1b120c7 --- /dev/null +++ b/plans/perf-baseline.md @@ -0,0 +1,175 @@ +# Performance Baseline Report (Phase 0 / UNCHANGED code) + +**Date**: 2026-10-07 +**Commit**: `b77938a` +**Dataset**: `perf/results/baseline-2026-10-07.json` + +--- + +## 1. Executive Summary & Headline Baseline Numbers + +This baseline captures the performance metrics of the unoptimized ytplayer codebase (commit `b77938a`, matching production tag `1175f1a1d2c1`) across 5 scenarios on both Chromium and WebKit under simulated LTE and Lossy network profiles (median of 5 runs). + +### Headline Baseline Numbers + +- **Cold First-Load Wire Transfer**: + - **Chromium**: 164 requests, **1,015.0 KB** compressed wire bytes (boot-done median: **10,163 ms** under 4× CPU throttle + LTE). + - **WebKit**: 162 requests, **1,152.0 KB (1.12 MB)** wire bytes (boot-done median: **3,201 ms** under proxy LTE). + - Breakdown: ~477 KB JavaScript, ~397 KB Web Fonts, ~131 KB CSS, ~27 KB HTML. + +- **Warm Reload (under Service Worker)**: + - **Chromium**: 4 requests, **5.2 KB** wire bytes (periodic API version poll), boot-done median: **675 ms** (LTE) / **651 ms** (Lossy). + - **WebKit**: 0 requests, **0 KB** wire bytes, boot-done median: **131 ms** (LTE) / **131 ms** (Lossy). + +- **Offline Reload (under Service Worker)**: + - **Chromium**: 0 requests, **0 B** wire bytes, reload median: **240 ms**, boot-done median: **219 ms**. + - **WebKit**: 0 requests, **0 B** wire bytes, reload median: **135 ms**, boot-done median: **128 ms**. + +- **Update N -> N+1 Wire Cost (The Invalidation Penalty)**: + - **1-line `app.js` change**: + - **Chromium**: 88 requests, **596.6 KB** wire bytes (~611 KB uncompressed/brotli), duration: **2,013 ms** (LTE) / **2,031 ms** (Lossy). + - **WebKit**: 86 requests, **664.3 KB** wire bytes (~680 KB wire), duration: **4,457 ms** (LTE) / **8,135 ms** (Lossy). + - **1-line `theme-glass.css` change**: + - **Chromium**: 88 requests, **596.7 KB** wire bytes, duration: **1,970 ms** (LTE) / **2,004 ms** (Lossy). + - **WebKit**: 86-87 requests, **664.3 KB** wire bytes, duration: **4,510 ms** (LTE) / **8,269 ms** (Lossy). + +> **Core Invalidation Finding**: A single 1-line change to *any* static file (`app.js` or `theme-glass.css`) mutates `BUILD_TAG` calculated by `computeBuildTag()`. This causes `sw.js` to re-fetch and re-cache all 86-88 shell assets (~600-664 KB wire payload) rather than just the mutated resource. + +--- + +## 2. Benchmark Harness Execution + +The benchmark harness is located in `perf/baseline.mjs` and driven by Playwright with an isolated ephemeral Bun server and a custom pacing proxy (`perf/proxy.mjs`). + +### Quick Start / Exact Commands + +```bash +# Ensure node_modules is symlinked to root +ln -s ~/development/personal/ytplayer/node_modules node_modules + +# Run full baseline suite (all scenarios, both browsers, both profiles, 5 runs) +node perf/baseline.mjs --runs 5 --browser all --profile all --scenario all --out perf/results/baseline-2026-10-07.json + +# Run individual browser or scenario +node perf/baseline.mjs --runs 5 --browser chromium --profile lte --scenario cold +node perf/baseline.mjs --runs 5 --browser webkit --profile lte --scenario update-js + +# Run comparison against baseline after optimizations +node perf/baseline.mjs --runs 5 --browser all --profile all --scenario all --compare perf/results/baseline-2026-10-07.json +``` + +--- + +## 3. Results by Browser and Profile (Median of 5 Runs) + +### Chromium — LTE (1.6 Mbps down, 750 kbps up, 150 ms RTT, 4× CPU throttle) + +| Scenario | Reqs (med) | Wire Bytes (med) | FCP (ms) | LCP (ms) | Boot Done (ms) | SW Ready / Reload (ms) | Long Task Total (ms) | +|---|---|---|---|---|---|---|---| +| **Cold First Load** | 164 | 1,015.0 KB | 1,540 | 1,896 | 10,163 | 38,446 | 397 | +| **Warm Reload (SW)** | 4 | 5.2 KB | 180 | 684 | 675 | - | 156 | +| **Offline Reload** | 0 | 0.0 KB | 72 | 108 | 219 | 240 | - | +| **Update N->N+1 (JS)** | 88 | 596.6 KB | - | - | - | 2,013 | - | +| **Update N->N+1 (CSS)** | 88 | 596.7 KB | - | - | - | 1,970 | - | + +- **Storage Estimate**: Quota = 4,298,447,325 B (~4.0 GB), Usage = 3,480,029 B (~3.31 MB). +- **Cold Wire Bytes by Type**: JS 477.2 KB, Fonts 397.1 KB, CSS 130.7 KB, HTML 27.1 KB, Images 6.3 KB, API/Other 1.0 KB. + +--- + +### Chromium — Lossy (1.0 Mbps down, 500 kbps up, 250 ms RTT, 4× CPU throttle) + +| Scenario | Reqs (med) | Wire Bytes (med) | FCP (ms) | LCP (ms) | Boot Done (ms) | SW Ready / Reload (ms) | Long Task Total (ms) | +|---|---|---|---|---|---|---|---| +| **Cold First Load** | 164 | 1,015.0 KB | 2,344 | 2,912 | 16,015 | 42,518 | 432 | +| **Warm Reload (SW)** | 4 | 5.2 KB | 184 | 644 | 651 | - | 155 | +| **Offline Reload** | 0 | 0.0 KB | 80 | 92 | 237 | 253 | - | +| **Update N->N+1 (JS)** | 88 | 596.6 KB | - | - | - | 2,031 | - | +| **Update N->N+1 (CSS)** | 88 | 596.7 KB | - | - | - | 2,004 | - | + +--- + +### WebKit — LTE (1.6 Mbps down, 150 ms RTT via proxy) + +| Scenario | Reqs (med) | Wire Bytes (med) | FCP (ms) | LCP (ms) | Boot Done (ms) | SW Ready / Reload (ms) | Long Task Total (ms) | +|---|---|---|---|---|---|---|---| +| **Cold First Load** | 162 | 1.12 MB | 1,088 | 1,287 | 3,201 | 30,508 | 0* | +| **Warm Reload (SW)** | 0 | 0.0 KB | 110 | 110 | 131 | - | 0* | +| **Offline Reload** | 0 | 0.0 KB | 107 | 107 | 128 | 135 | - | +| **Update N->N+1 (JS)** | 86 | 664.3 KB | - | - | - | 4,457 | - | +| **Update N->N+1 (CSS)** | 86 | 664.3 KB | - | - | - | 4,510 | - | + +--- + +### WebKit — Lossy (1.0 Mbps down, 250 ms RTT + 1,500 ms stalls via proxy) + +| Scenario | Reqs (med) | Wire Bytes (med) | FCP (ms) | LCP (ms) | Boot Done (ms) | SW Ready / Reload (ms) | Long Task Total (ms) | +|---|---|---|---|---|---|---|---| +| **Cold First Load** | 162 | 1.12 MB | 3,557 | 3,735 | 7,303 | 35,449 | 0* | +| **Warm Reload (SW)** | 0 | 0.0 KB | 111 | 112 | 131 | - | 0* | +| **Offline Reload** | 0 | 0.0 KB | 109 | 119 | 129 | 141 | - | +| **Update N->N+1 (JS)** | 86 | 664.3 KB | - | - | - | 8,135 | - | +| **Update N->N+1 (CSS)** | 87 | 664.4 KB | - | - | - | 8,269 | - | + +*\*Note: WebKit does not expose `PerformanceLongTaskTiming` (`longtask` entries), so long task total evaluates to 0 ms.* + +--- + +## 4. Variance & Noise Observed + +Over the 5 runs per configuration, measurements demonstrated high stability: +- **Wire Bytes & Request Counts**: Deterministic (0% variance across identical runs). +- **Cold Boot-Done**: + - Chromium LTE: Min 10,129 ms, Max 10,798 ms (<6% variance). + - Chromium Lossy: Min 15,871 ms, Max 16,031 ms (<1% variance). + - WebKit LTE: Min 3,138 ms, Max 3,245 ms (<3% variance). + - WebKit Lossy: Min 6,963 ms, Max 8,710 ms (~20% variance due to proxy random stalls). +- **Warm & Offline Reload**: + - Chromium Offline Reload: Min 238 ms, Max 257 ms (<8% variance). + - WebKit Offline Reload: Min 131 ms, Max 141 ms (<7% variance). +- **Update Duration**: + - Chromium: Runs cluster around ~2,000 ms (2,013 ms median). Occasional outlier runs (~45–200 ms) occur if the browser resolves `reg.update()` immediately before network completion reporting. + - WebKit: Steady scaling between LTE (~4.5 s) and Lossy (~8.2 s) pacing. + +--- + +## 5. Limitations & Caveats + +1. **WebKit Network Throttling**: + WebKit lacks Chrome DevTools Protocol (CDP) support for hardware network emulation and CPU throttling. We paced throughput (1.6 Mbps) and injected latency/stalls using a custom local reverse proxy (`perf/proxy.mjs`). This throttles HTTP response chunks truthfully but cannot simulate OS-level socket buffer delays or 4× CPU throttling. +2. **Autoplay Policy in WebKit**: + Playwright WebKit on Linux rejects `--autoplay-policy=no-user-gesture-required` and enforces Safari-style gesture requirements on unmuted audio elements. The media playback benchmark detects this and correctly reports `autoplayBlocked: true` without failing the test run. +3. **WebKit SetOffline Incompatibility**: + Calling Playwright's `context.setOffline(true)` on Linux WebKit causes an unrecoverable engine crash (`WebKit encountered an internal error`). Offline mode for WebKit is simulated by terminating the proxy upstream connection (`proxy.setOffline(true)`). + +--- + +## 6. Manual iPhone Testing Protocol (Phase 0 / iOS Safari PWA) + +Because automated headless WebKit cannot fully replicate iOS Safari's mobile background constraints and battery telemetry, the following manual protocol is required: + +### Protocol Steps + +1. **Install PWA**: + - Open Safari on iPhone and navigate to `https://worship.hesed.sbs`. + - Tap the Share button -> **"Add to Home Screen"**. + - Launch ytplayer directly from the Home Screen icon. + +2. **Verify Version & Cache**: + - Verify the footer / version string matches `1175f1a1d2c1` (or current production build tag). + - Turn on Airplane Mode and force-reload to confirm offline readiness. Turn Airplane Mode back off. + +3. **30-Minute Screen-Off Battery Test**: + - Note baseline battery percentage (e.g., 85%). + - Select a pre-cached playlist or track and tap **Play**. Ensure continuous audio is heard. + - Lock the screen (display off) and let playback run uninterrupted for **30 minutes**. + - Note battery percentage at 30 minutes (calculate drain %/hr). + - Listen for any audio dropouts, stuttering, or premature process suspension. + +4. **Interruption & Resumption Test**: + - Trigger an interruption while locked (e.g., initiate an incoming phone call or start Siri). + - End the interruption and observe if playback automatically resumes via `MediaSession` handlers. + - Test Lock Screen Controls (Play/Pause, Next Track, scrubber). + +5. **Log Findings**: + - Record iPhone Model, iOS Version, Battery Drain (% per 30m), Dropouts Count (0 = pass), and Resumption Status (Pass/Fail).