Bu siteyi yapay zekâ agent'larıyla geliştiriyoruz, stüdyo sayfamızda da bunu açıkça söylüyoruz. Site iki dilli, Next.js 16 ve React 19 ile çalışıyor. Tarayıcı kontrollerini Playwright 1.62 ile yapıyoruz. Agent işini bitirince rapor verir: lint temiz, tip kontrolü sorunsuz, production build'i geçti. Hepsi doğrudur ama hiçbiri sayfanın ekranda nasıl göründüğünü söylemez.
Ağustos 2026'da siteyi bir agent yeniden yazarken lint'in, tip kontrolünün ve build'in göremeyeceği üç front-end hatası çıktı. Sayfayı gerçek tarayıcıda açan script ikisini aynı gün yakaladı, üçü de canlıya çıkmadı. Eylülde dördüncü bir hatayı yakalayacak kontrol hazırdı ama çalıştırılamadı. Biz yine de yayımladık ve bir yazı sayfası on gün kadar telefon ekranından 482 piksel geniş kaldı. Bu yazıda kontrollerimizin nelere baktığını ve çalıştırılmamış bir kontrol için raporda ne yazması gerektiğini anlatıyoruz.
Lint'in, tip kontrolünün ve build'in göremediği front-end hataları
Üçü de 8 Ağustos 2026'da, sitenin yeniden yazıldığı gün ortaya çıktı. Her biri, geldiği değişikliğin içinde düzeltildi, yani hiçbiri deploy edilmedi.
“Hareketi azalt” ayarını dinlemeyen şerit. Ana sayfada ürün ekran görüntülerinden oluşan, yana doğru kayan bir şerit vardı. Sistemde “hareketi azalt” ayarı açıkken de kaymayı sürdürdü. Stil dosyasında prefers-reduced-motion için animasyon sürelerini kısaltan genel bir kural vardı ama bu kural şeridi durdurmadı. Düzeltmede animasyonu yalnızca @media (prefers-reduced-motion: no-preference) içinde tanımladık.
Dokunmatik ekranın hiç göstermediği ok. Ürün kartındaki ok opacity 0'da duruyor, imleç kartın üzerine gelince görünüyordu. Dokunmatik ekranda hover yok, o yüzden ok orada hiç görünmedi. Düzeltmeden sonra ok hep görünüyor, yalnızca soluk. Hover'da değişen tek şey rengi.
Sıfır piksel yüksekliğindeki mobil menü. Mobil menü, header'ın altında kalan ekranı dolduracak sabit konumlu bir paneldi. Header'da backdrop-filter var ve backdrop filter kullanan bir eleman, sabit konumlu alt elemanları için containing block olur. Panelin top ve bottom değerleri ekrana göre değil, 64 piksellik header'a göre hesaplandı. Panel sıfır piksel yüksekliğinde çıktı, arka planı boyanmadı, bağlantıları sayfanın üstüne döküldü. İlk düzeltmede paneli <body> içine render ettik.
Lint, tip kontrolü ve build geçti, ekrana gelen sayfa yine de bozuktu.
Şeridi ve oku, aşağıda anlattığımız kontroller yakaladı. Menünün nasıl fark edildiğini not etmedik.
Üçünün de arkasında, kodun kontrol edildiği yer ile sayfanın çalıştığı yer arasındaki fark var: başka bir hareket ayarı, başka bir girdi aygıtı, başka bir containing block. Aynı gün tam tersi türden, gürültülü bir hata da yaşadık: image'daki npm sürümü dizüstündekinden farklı olduğu için bütün deploy'lar npm ci adımında durdu. O hikâyeyi npm ci, lockfile ve Docker image'ı üzerine yazımızda anlattık.
Playwright kontrolleri
Siteyi yeniden yazarken scripts/verify.mjs dosyasını ekledik ve en başına tek bir kural yazdık: yerleşim, hareket maliyeti ve içerik güvenliği politikası (CSP) hakkında ne iddia ediyorsak ya bu script kanıtlar ya da o iddiada hiç bulunmayız. Script, Playwright ile Chromium'u başlatıyor ve sayfaları çalışan bir production build'inden açıyor. Taşma kontrolü iki dilde ve 320'den 1280 piksele kadar yedi genişlikte çalışıyor. Script'te on kontrol var, eşikleri de biz belirledik:
| Kontrol | Başarısız olduğu durum |
|---|---|
| Yatay taşma | sayfa herhangi bir genişlikte ekrandan genişse |
| Layout shift (CLS) | CLS 0,01'i aşarsa |
| Hareket maliyeti | long task oluşursa ya da sayfa baştan sona kaydırılırken kare hızı 55 fps'nin altına düşerse |
| “Hareketi azalt” | 1,2 saniye arayla alınan iki ekran görüntüsü farklıysa ya da bir eleman opacity 0'da duruyorsa |
| CSP | kendi politikamız bir isteği engellerse |
| Route'lar | bir sayfa 200 dönmezse, lang ya da hreflang yanlışsa |
| Bölüm animasyonları | sayfa baştan sona kaydırıldıktan sonra bir bölüm hâlâ gizliyse |
| Geri ve ileri | geri düğmesine basıldıktan sonra bölümler gizli kalırsa |
| Çerez izni | reklam kütüphanesi yoksa, bir sinyal varsayılan olarak reddedilmemişse ya da kabul edildiğinde güncelleme gönderilmiyorsa |
| Çerezler | ziyaretçi karar vermeden önce herhangi bir üçüncü taraf çerezi varsa |
“Hareketi azalt” ayarını test etmenin akla ilk gelen yolu yanlış. Computed style'ları karşılaştırmak yetmiyor: JavaScript ile çalışan bir animasyon kütüphanesi animationName değerini none bırakıp her karede piksel oynatabiliyor. Bu yüzden kontrol iki ekran görüntüsü alıp baytlarını karşılaştırıyor. Örnekteki url adresi oluşturuyor, fail ile ok da sonucu kaydediyor:
const ctx = await browser.newContext({
viewport: { width: 1280, height: 900 },
reducedMotion: "reduce",
});
const page = await ctx.newPage();
await page.goto(url("en", "/"), { waitUntil: "networkidle" });
await page.evaluate(() => new Promise((r) => setTimeout(r, 1200)));
const a = await page.screenshot();
await page.evaluate(() => new Promise((r) => setTimeout(r, 1200)));
const b = await page.screenshot();
if (!a.equals(b))
fail(
"reduced-motion",
`page still animating (${a.length} vs ${b.length} bytes)`,
);
else ok("reduced-motion (pixel-identical)");
Bayt bayt karşılaştırma, ancak iki ekran görüntüsü arasında başka hiçbir şey değişmiyorsa işe yarar. Burada sayfa network idle durumuna gelmiş ve ilk ekran görüntüsünden önce 1,2 saniye daha beklemiş oluyor. page.screenshot() varsayılan olarak animasyonları olduğu gibi bırakıyor, kayan şerit bu yüzden fark olarak görünüyor. Metin imlecini ise gizliyor, yani yanıp sönen imleç fark yaratmıyor.
Şeridi bu karşılaştırma yakaladı. Aynı kontrolün devamındaki satırlar, yerleşimde yer kaplayan ama opacity 0 ile render edilen elemanları arıyor. Ürün kartındaki ok da bu satırlara takıldı.
Kontrollerden biri başarısız olursa script sıfırdan farklı bir kodla çıkıyor ve rapor yazıyor. Eylül 2026'da aynı türden script'ler ekledik. İçlerinden biri Chromium ve WebKit'te yedi genişliği tarıyor.
Hiçbiri kendiliğinden çalışmıyor: repo'da CI yok, deploy da hiçbir kontrol çalıştırmıyor. Her biri ancak biz ya da o işi yapan agent onu production build'i üzerinde başlatınca çalışıyor. Her push'tan önce çalıştırılmış da değiller. Tablodaki on kontrolün son raporu 8 Ağustos 2026 tarihli.
Düzeltmeler için dersler dosyası
Script yalnızca birinin aklına geleni kontrol eder. Geri kalanı, işi inceleyen kişiden düzeltme olarak gelir. Sohbette verilen düzeltme ise oturum bitince kaybolur.
Bu yüzden her düzeltmeyi tasks/lessons.md dosyasına tarihli bir kural olarak yazıyoruz. Sonraki oturuma da her şeyden önce bu dosyayı okumasını söylüyoruz. Dosyadaki ilk kayıt 10 Eylül 2026 tarihli. İki kayıt, kısaltarak:
- 10 Eylül. Başarılı bir build, amaçlanan tipografinin ya da kompozisyonun ekrana geldiğini göstermez. Yeniden tasarıma “hazır” demeden önce gerçekten yüklenen font ailesine, mobil yerleşime ve ekran görüntülerine bakılır.
- 20 Eylül. Masaüstü ve telefon genişlikleri, sayfanın tablette de çalıştığını göstermez. 768, 820, 1024 ve 1180 piksel iki dilde de kontrol edilir.
İlk kural bir sayfadan çıktı. 10 Eylül'de sitenin fontları değiştirilmiş, build de geçmişti. Tarayıcıda ise sayfa varsayılan bir serif fontla açıldı, çünkü otomatik üretilen font değişkenleri eski kalmıştı. Hiçbir şey hata vermedi, işi inceleyen kişi de tasarımı reddetti.
Bu kural artık assertion olarak da duruyor. İkinci bir script, listesindeki route'ları iki dilde, dört genişlikte açıyor ve document.fonts.ready promise'ini bekliyor. Body'nin computed font-family değerinde sitenin fontu Instrument geçmiyorsa başarısız oluyor.
Neyin kontrol edilmediğini söyleyen raporlar
Agent'ın özeti, kontrol çalışmış olsa da olmasa da aynı kendinden emin dille yazılır. Eylülden beri raporlarımız bu ikisini ayıran bir bölümle bitiyor. 10 Eylül 2026'daki yeniden tasarım turunun raporunda bu bölümün başlığı ve dört satırından biri şöyleydi:
Bu turun kanıtı ve sınırları
- Chromium, sandbox içinde macOS MachPortRendezvous izniyle engellendi. Sandbox dışı görsel test isteği bekliyor; bu tur için başarılı desktop/mobile browser sonucu henüz yok. Güncellenen verify script'leri çalıştırılmadan görsel doğrulama kanıtı sayılmaz.
Çevresindeki satırlarda neyin geçtiği sayılarıyla yazıyor. Neyin yapılmadığı da yazıyor: gerçek e-posta gönderilmedi, o turun değişiklikleri henüz commit ya da deploy edilmedi.
Çalıştırılamayan script'lerden biri, yazılar dâhil bütün route'larda hiçbir sayfanın 320 piksellik ekrandan geniş olmadığını kontrol ediyor. O geceki yeniden tasarımda bir yazı yerleşimi de vardı: uzun kod satırları sayfayı bu genişliğin 482 piksel dışına taşırıyordu.
Bu yerleşim aynı gece, daha önceki bir turun ardından push edilmişti. O turun raporunda da tarayıcı kontrolü için aynı şey yazıyordu. O turda ana sayfaya ve ürün arşivine masaüstü tarayıcıda elle bakmıştık. İşi inceleyen kişi kontrollerin çabuk bitirilmesini istemiş, commit ve push için de onay vermişti, yani agent kendi başına yayımlamadı.
Yazı, canlı sitede 20 Eylül'e kadar o genişlikte kaldı. O gün işi inceleyen kişi, tablet genişliğinde bozulan bir ürün seçiciyi bildirdi. Biz de her tür sayfayı Chromium ve WebKit'te yedi genişlikte ilk kez taradık.
Taramada seçiciyi bulduk: 768 pikselde bir ürün adı, düğmesinin 28 piksel dışına çıkıyordu. Yazıyı da bulduk. Düzeltmelerden sonra 680 kontrol geçti.
Rapor, çalıştırılmayan kontrol konusunda doğruydu ama biz ona göre davranmadan yayımladık.
Kontrollerin gösteremedikleri
Script'ler, çalıştırıldıklarında yerleşimin taşmadığını, hareketin, CSP'nin ve çerez izninin beklendiği gibi davrandığını gösteriyor. Sayfanın iyi olduğunu göstermiyor.
Taşma yok, sayfa yine bozuk. 13 Eylül'de bir sayfada 320 pikselde taşma yoktu, yani kontrol yeşildi. Ekran görüntülerinde ise hizmet etiketleri kelimenin ortasından bölünüyordu. 9 Ekim'de bu yazının Türkçesindeki kontrol tablosu, telefonda kelimeleri aynı şekilde ortasından böldü. Sebep, 482 piksellik taşmanın düzeltmesiydi: yazı gövdesindeki overflow-wrap: anywhere.
Menü. On kontrolün hiçbiri mobil menüyü açmıyor. Sonradan yazdığımız bir script açıyor: menünün açıldığına, Escape ile kapandığına, focus'un menü düğmesine döndüğüne ve menüdeki bir bağlantının çalıştığına bakıyor. Panelin yüksekliğini ise hiç ölçmüyor. Menüyü bugün koruyan şey, nasıl kurulduğu. Menü eylülden beri showModal() ile açılan bir <dialog>: tarayıcı onu top layer'da gösteriyor, yüksekliği de top ve bottom ile değil, viewport birimleriyle veriliyor.
Kimsenin istek atmadığı adres. Ağustostaki yeniden yazımda siteye, paylaşılan bağlantıların önizlemesi için üretilen bir görsel eklemiştik. 9 Ekim 2026'da canlı sitede bu görselin adresine istek attık ve 502 aldık: görseli üreten kütüphane, kendisine verilen değişken font dosyasını okuyamıyor. Ağustostan beri ne font değişmişti ne de route'un onu yükleme biçimi. Yani görsel iki aydır hata veriyordu.
Fontun sabit ağırlıklı bir kopyası sorunu çözdü. O adrese istek atan bir script yok.
Stüdyo sayfasındaki “Yapay zekâ hızı getirir; yön ve son kontrol bizde kalır” cümlesiyle kastettiğimiz iş bölümü bu. Pratikte son kontrol, bir insanın sayfayı masaüstünde de telefonda da açıp bakmasıdır.
Agent'ın yazdığı front-end için kontrol listesi
- Diff'i değil, ziyaretçinin gördüğünü kontrol edin: gerçek bir tarayıcıda, gerçek genişliklerde bir production build'i.
- Her kontrol başarısız olabilmeli. Ekran görüntülerini bayt bayt karşılaştırın, sıfırdan farklı kodla çıkın, elemanın adını verin.
- Yalnızca sayfaları değil, durumları da test edin: “hareketi azalt”, dokunmatik ekran, hover ve focus, geri ve ileri, çerez izni öncesi ve sonrası.
- Sayfanın duyurduğu her adrese istek atın: paylaşım görseli, ikonlar, feed.
- Her düzeltmeyi sonraki oturumun okuyacağı tarihli bir kurala, assertion'a çevrilebilen her kuralı da assertion'a dönüştürün.
- Her raporu, neyin kontrol edilmediğiyle bitirin. Çalıştırılmamış bir kontrole güvenerek yayımlamayın.