# Tarayıcı turu — bulgu kataloğu

**Tarih:** 03.08.2026 · **Suite:** `tests/Browser/{panel,office,backoffice}-health.spec.js`
**Kapsam:** üç host, 104 ekran, 375 ve 1440 px · **Giriş:** gerçek ekranlardan, gerçek TOTP ile

Turun sorduğu altı soru her ekran için aynı: HTTP durumu · konsol hatası ve Vue uyarısı · yatay taşma · boş render · kırık interpolasyon (`undefined`, `NaN`, `[object Object]`) · **karşılığı olmayan token sınıfı**.

⚠ **Turun kendisi de bir bulgu üretti ve o en önemlisiydi:** ilk koşuda müşteri girişi sessizce başarısız oldu, sonraki her `goto` giriş ekranına dönüyordu ve tur *"20 ekran gezildi, 18'i sağlıklı"* diye rapor etti — giriş ekranı hakkında, on sekiz kez. Yanlış sayfayı ölçüp "her şey yolunda" diyen bir tur, turu olmamasından **kötüdür**. `assertSignedIn` bunun için var ve her kimlik adımını kontrol eder.

---

## 1 · Sessiz hatalar — hiçbir testin göremediği sınıf

### 1.1 · Var olmayan renk sınıfları · **254 + 20 kullanım, 66 dosya** ⬛ DÜZELTİLDİ

| Sınıf | Kullanım | Gerçek |
|---|---|---|
| `ink-500` | **254** | Ölçek 950·800·600·400·300·200·100·50 — 500 hiç olmadı |
| `ink-700` | 19 | yok |
| `ink-900` | 4 | yok |
| `warning-700` | 1 | Dörtlü `dot/fg/bg/border`, `700` yok |
| `text-link` | 3 | Bağlantı rengi `info-fg` |

Tailwind tanımadığı sınıfı **sessizce atar**: uyarı yok, hata yok, ekran görüntüsünde fark yok — metin ebeveyninin rengini miras alır. O ekranlar okunmuş, gözden geçirilmiş ve yanlış renkle yayınlanmış; kimse göremez çünkü yanlış renk *"üstteki paragrafla aynı"*dır.

**Kapatan mekanizma:** `FrontendTest` → *"names no colour the token layer does not define"*. `tokens.css`'te tanımlı olmayan renk adı derlemeyi kırar. Tarayıcı turu aynı soruyu render edilmiş sayfaya sorar; ikisi birbirinin yerine geçmez (statik tarama çalışma anında kurulan sınıfı göremez).

### 1.2 · `PasswordInput` id'yi içeri geçirmiyordu · **7 ekran, 21 alan** ⬛ DÜZELTİLDİ

Kontrolün kök elemanı, göster düğmesini konumlandıran `div`. Vue'nun fallthrough'u `id="password"`i **div'e** koyuyordu; ürünün her `<FieldLabel for="password">`'ü bir div'i gösteriyordu. Etiket çizili, alan çizili, yazmak çalışıyor — kaybolan tek şey **ilişki**: etikete tıklamak hiçbir yeri odaklamıyor, ekran okuyucu etiketsiz bir şifre kutusu duyuruyordu.

⚠ Diğer bütün sarmalayıcı kontroller (`SensitiveInput`, `MoneyInput`, `PhoneInput`, `DatePicker`, `MultiSelect`…) `id` prop'unu zaten alıyordu. Tek boşluk buydu.

### 1.3 · `DataTable`'a zorunlu prop'lar geçilmemişti ⬛ DÜZELTİLDİ

`Office/Instructors/Index` üç zorunlu prop'u geçmiyordu ve biri **fonksiyondu**: `:empty-title` diye bir prop yok (adı `empty-heading`), `clear-label` ve `showing-label` hiç yoktu. Sonuç: `e.showingLabel is not a function` — eğitmen listesi çiziliyor, sayfalayıcıya gelince tamamı çöküyordu. Vue eksik prop'u uyarır; **yanlış isimli prop'u uyaramaz.**

---

## 2 · Sunucu hataları — gerçek 500'ler

### 2.1 · Müşteri paneli **ikinci istekte** ölüyordu ⬛ DÜZELTİLDİ · 🔴 en ağırı

Session guard her istekte hesabı veritabanından okur (`select * from customer_users where id = ?`). `customer_users` satır bazlı güvenli, o sorgu tenant kapsamı dışında ve koruma reddediyor → 500. Üstelik `EnsureTenantContext` daha kapsamı hesaplayamadan oluyordu, çünkü o middleware guard'a *"kim giriş yapmış"* diye soruyor — ölen sorgunun ta kendisi.

**Neden hiçbir test yakalamadı:** feature testleri `actingAs` ile giriyor, o da modeli doğrudan guard'a koyup **hiç okumuyor**. Yani her gerçek isteğin geçtiği tek yol, hiçbir testin geçmediği yoldu — ve bugüne kadar tarayıcıdan da girilemiyordu (§1.5, giriş ekranı yoktu).

**Çözüm:** `App\Support\Auth\CustomerUserProvider` — `retrieveById`/`retrieveByToken`/`updateRememberToken` oturumdaki `tenant_id` ile kapsam açar.

### 2.2 · Operasyon gündemi (`/yonetim`) her açılışta 500 ⬛ DÜZELTİLDİ

`OperationsAgenda` **hiç tenant context açmıyordu**. Vadesi geçmiş tahsilat kartı `customer` ilişkisini eager load ediyor, takım yükü şirketler arası okuyor — ikisi de satır bazlı güvenli. Sorgu sınıfı kendi sınırını açar (chunk3 §3.6); bu açmıyordu.

### 2.3 · Teklif detayı 500 ⬛ DÜZELTİLDİ

`QuoteController::contactsOf` controller'ın içinden `Customer::query()->find(...)` çağırıyordu — hem kapsamsız hem kod yerleşimi ihlali (AD-25: sorgu yapan controller karar vermeye başlamış controller'dır). `CustomerPeople::forCustomerId()`'ye taşındı.

---

## 3 · Ekranı olan ama ulaşılamayan yerler

### 3.1 · Eğitim detayına **hiçbir yerden** gidilemiyordu ⬛ DÜZELTİLDİ

Tasarım `15` her satırın sonuna `›` çiziyor; o kolon hiç yazılmamıştı. Route var, sorgu var, `Portal/TrainingShow` var — ekran ölüydü.

### 3.2 · Teklif "Görüntüle" düğmeydi, tasarım bağlantı çiziyor ⬛ DÜZELTİLDİ

`13`'te *"İncele ve Onayla"* düğme, *"Görüntüle"* anchor. Fark süs değil: `<button @click>` yeni sekmede açılmaz, klavyeye cevap vermez, ekran okuyucuya bağlantı diye okunmaz.

### 3.3 · İçerik paketi kataloğu **tanımlanamıyordu** ⬛ DÜZELTİLDİ

`ContentPackagePolicy` yalnız **ofis** kimliğini tanıyor (`$actor instanceof User`, `quote.view`) ve `create()` düz `false` döndürüyor. Ama kaynak **backoffice**'te yaşıyor ve oradaki aktör `BackofficeUser`. Backoffice yetki ailesinde `content_package.*` diye bir izin de yok.

Politikanın kendi dokümantasyonu *"paket backoffice'te TANIMLANIR"* diyor — politika bunu imkânsız kılıyor. Kaynak var, üç ekran var, üçü de kalıcı **403**. Bugün içerik paketi yalnız seeder ile var olabiliyor.

✅ **KARAR (Sets, 03.08.2026):** *"Backoffice her şeyin üstünde ve tüm dataya sorunsuz şekilde erişebilecek yapı. Orası backbone, tüm core onun üzerinden şekilleniyor ve her şeye erişebiliyor — müşterilere de, ofise de, ayarlara da. **Paketler backoffice'ten tanımlanacak.**"*

Yapılan: `content_package.view/create/update/deactivate` backoffice yetki ailesine eklendi (`config/authorization.php` + `PermissionMatrix`), `AuthorizationSeeder` super-admin rolüne otomatik veriyor, `ContentPackagePolicy` artık `BackofficeUser`'ı tanıyor. `delete` hâlâ **hiç kimseye** açık değil: satılmış paketin kaydı silinmez, `deactivate` ile kapatılır (AD-11) — süresi dolana kadar erişimi olan müşteri onu hâlâ okuyor.

⚠ Ofis satırı olduğu gibi kaldı: orada `view` var, `create` yok. Aynı kelime, iki aile, iki anlam (chunk6 §6.1) — satan kişi sattığı şeyi değiştiremez.

---

## 4 · 375px — taşan ekranlar ⬛ TAMAMI DÜZELTİLDİ

`HANDOFF` §11 ve §81: *"hiçbir sayfa salt mobil değildir"* ve 375px çizilmemiş tablolar `02`'deki **tablo→kart dönüşümüyle** türetilir. Üç panel ekranı bunu yapmıyordu:

| Ekran | Genişlik | Sebep |
|---|---|---|
| `/egitimler` | 648px | Ham `<table>`, kart görünümü yok |
| `/personel` | 560px | Ham `<table>`, kart görünümü yok |
| Üçü de | 406–441px | `ml-auto flex shrink-0` toolbar sarılmıyordu |

### 4.1 · Kırpılan tablolar — sayfa kaymıyor ama kolonlar yok ⬛ DÜZELTİLDİ

⛔ **Sorunun sessiz yarısı ve sayfa taşması kontrolünün göremediği şey.** `overflow-hidden` taşıyan bir kart tablosunu **kırpar**: belge 375px kalır, hiçbir şey kaymaz, son kolonlar orada değildir. Ekran sağlam görünür ve veri eksiktir.

Tura yeni bir ölçüm eklendi — *"tablo, içinde durduğu kutudan geniş mi ve kutu kaymıyor mu"* — ve **dokuz tablo** buldu:

| Ekran | Ölçü | Kaybolan |
|---|---|---|
| Backoffice erişim logu | **5142px tablo, 343px kutu** | dört kolon |
| Backoffice yazma logu | 736px / 343px | kolonlar |
| Ofis faturalar | 1113px / 317px | tutar, kalan, statü, **bütün aksiyonlar** |
| Ofis teklifler | 548px / 317px | fiyat, geçerlilik, statü |
| Ofis teklif detayı (kalemler) | 460px / 317px | adet, birim fiyat, tahsis kontrolü |
| Ofis fatura detayı (tahsilatlar) | 544px / 317px | yöntem, **dekont**, ters kayıt kontrolü |
| Portal faturalar | 421px / 317px | kalan tutar, statü |
| Portal lokasyonlar | 440px / 317px | kişi sayısı, kapatma |
| Portal teklifler | 569px / 317px | fiyat, geçerlilik, **onay düğmesi** |

Yedisi `02`'nin tablo→kart dönüşümüyle kapatıldı; iki backoffice logu kendi kutusunda kayan tabloya çevrildi (Filament'in kendi sözlüğü — AD-25 — ve bir log satır satır değil zaman çizgisi olarak okunur).

⚠ **Bir kez daha yanlış yoldan gidildi ve kaydı duruyor:** ilk düzeltme `overflow-x-auto` sınıfını blade'e yazdı. Bu panelin stylesheet'i **Filament'in kendi build'i** ve yalnız Filament'in kullandığı utility'leri taşıyor — sınıf hiçbir şeye derlenmedi, kırpma aynen kaldı. İnline stille çözüldü.

### 4.2 · Bileşen VAR, sayfalar kullanmıyor

**Soru soruldu:** *"Tasarımcı katalog ve form elemanları ile ilgili çalışma yapmıştı, orada zaten veri tablosu hazırlamıştı — atoma/componente çevrilmemiş mi?"*

**Cevap: çevrilmiş.** `02 Veri Gosterimi` açıkça *"Tablo bu ürünün kalbidir; her tablonun mobil kart karşılığı tasarlanır"* diyor ve `organisms/DataTable.vue` bunu birebir uyguluyor: arama kutusu, filtre çipleri, sıralama, sayfalama, boş hal ve **`hidden md:block` tablo + `md:hidden` kart listesi**.

⛔ **Sorun bileşende değil, çağıranda:** ürün genelinde **32 sayfa ham `<table>` yazıyor**, yalnız **6'sı** `DataTable` kullanıyor. Yani dönüşüm bir kez yazılmış, sonra 26 sayfa onu atlamış — telefon kontrolünün başına gelenin aynısı (kırmızı çizgi 17): dosya tekti, ayrışan şey her sayfanın onu kullanmayı hatırlamasıydı.

Panelin üçü elle düzeltildi. Ofis tarafında turun ölçtüğü taşmalar da kapatıldı ve **hiçbiri ham tablo değildi** — dördü de aynı iki CSS kalıbıydı:

| Kalıp | Nerede | Neden |
|---|---|---|
| Grid çocuğunda `min-w-0` yok | `AgendaShell` (gündem + satış kokpiti), `Quotes/Show` | Grid çocuğunun varsayılan `min-width` değeri `auto` — *"içeriğimden dar olamam"*. Uzun bir müşteri adı kolonu 500px'e itiyor, sayfa yana kayıyordu. Desktop'ta görünmez, çünkü yer var |
| `ml-auto flex shrink-0` sarılmıyor | `PageHeader` + 5 sayfa | Geniş ekranda doğru (aksiyonlar sağa yaslanır, ezilmez), telefonda yanlış. `md:` altında sarılıyor artık |

⚠ **Yani "32 sayfa ham tablo yazıyor" bulgusu duruyor ama acil değil:** turun ölçtüğü 104 ekranın hiçbirinde ham tablo taşmaya sebep olmadı. Taşınma gerekçesi estetik/bakım — panelin üçünde olduğu gibi kart karşılığı olmayan liste ekranları — ve o iş kendi kaleminde yapılır.

### 4.2 · Tedarikçi kartında detaya giriş yoktu ⬛ DÜZELTİLDİ

375px'te tablo yerine kart çiziliyor ve kartta **hiçbir kontrol yoktu**. Desktop satırında "Detay" ve "Düzenle" var; telefonda tedarikçi okunabiliyor ama **açılamıyordu**. Karta gerçek bir `Link` eklendi.

---

## 5 · Demo veri — turun ortaya çıkardıkları ⬛ DÜZELTİLDİ

- **Demo şirket yöneticilerinin şifresi yoktu.** Portal kişisi davet akışı için yaratılıyor ve o akış şifre koymuyor: her demo firmanın yöneticisinde ikinci faktör vardı, birinci faktör yoktu. Ne test ekibi ne tur giriş yapabilirdi.
- **Yedek kodlar düz metindi.** `Hash::check` bcrypt olmayan bir saklı değerde `false` dönmez, **istisna fırlatır** — yani yanlış kod 500 üretiyordu. Seeder'lar artık `ConfirmAppAuthentication` gibi hash'liyor; iki action da hash olmayan satırı atlıyor.
- **Backoffice'e girilemiyordu.** Veritabanında tek gerçek kimlik vardı: şifresi bilinmiyor, ikinci faktörü yok — ve bu alan her hesaptan faktör istiyor. `DemoBackofficeUserSeeder` eklendi.

---

## 5.1 · Son durum

| Host | 375px | 1440px |
|---|---|---|
| Müşteri paneli | ✅ 20/20 | ✅ 20/20 |
| Ofis | ✅ 47/47 | ✅ 47/47 |
| Backoffice | ✅ 48/49 | ✅ 48/49 |

⚠ Backoffice'in 49'a çıkması **12 düzenleme ekranının** eklenmesinden *(Sets, 03.08.2026: "açılsın tabii ki, detay sayfaları olmak zorunda")*. Listesi çizilen ama düzenleme formu hiç açılmamış bir kaynak, yarım ölçülmüş demektir. Onikisinden onbiri temiz; kalan bir tanesi §5.2.

Tam sweep **3.8 dakika**. Başlangıçta ofis turu tek başına 25 dakikaydı ve zaman aşımına uğruyordu — sebebi §6.6.

---

## 5.2 · Backoffice detay ekranlarının çıkardıkları

12 düzenleme ekranı tura eklenince üç şey çıktı:

| Bulgu | Durum |
|---|---|
| **Anket soruları tablosu boştu** — anket ekranının soracağı, sonuç ekranının ortalayacağı hiçbir şey yoktu; ikisi de yazılmış ve erişilebilirdi | ⬛ `SurveyQuestionSeeder` (production seed) — tasarım `51`'in çizdiği dördü kelimesi kelimesine |
| **Fiyat listesi düzenleme ekranının konusu yoktu** — yayımlanmış liste düzenlenemez (doğru: AD-11, teklif okunmuş bir liste değiştirilmez, yerine yenisi geçer) ve demo setinde yalnız yayımlanmış liste vardı | ⬛ Demo sete **taslak** liste eklendi (2027) |
| **Bildirim şablonu tablosu boş** | ⏸ **Karar bekliyor**, aşağıda |

⏸ **Bildirim şablonları — metin Sets'ten gelmeli.** Ekran var, kaynak var, `notification_rules` 9 kural taşıyor, olay anahtarları `config/business.php`'de belli: `certificate.expiring` · `instructor.document_expiring` · `training.ministry_deadline` · `training.instructor_missing` · `invoice.due_soon` · `training.reminder`. Eksik olan **gövde metinleri** — müşteriye giden yazı. Uydurulmadı; `KNOWN_OPEN`'a yazıldı ve her koşuda ekrana basılıyor. Metinler gelince seeder yazılır.

---

## 6 · Turun kendi hataları (kayıt için)

Aşağıdakiler üründe değil **turdaydı** ve düzeltildi. Yazılı duruyorlar çünkü hepsi *"tur yanlış şeyi ölçüyordu"* sınıfından ve tekrar edebilirler:

1. `networkidle` Inertia için yeterli değil — XHR biter, sayfa hâlâ eski; tur eski adresi okuyup üç çalışan listeyi ölü sandı.
2. Kenar çubuğu `ul > li` çiziyor; tur satır ararken menü maddesine tıklıyordu. Arama `main` içine alındı.
3. Filament'in OTP alanı altı input çiziyor ama yalnız ilki gerçek; bizim `OtpInput`'umuz için doğru olan "her kutuya bir hane" burada kodu bozuyordu.
4. `/egitmen` öneki `/egitmenler`'i de yakalıyordu — operasyonun eğitmen listesi, eğitmenin kendi paneli sanılıp üç doğru 403 hata diye raporlandı.
5. İkinci kimliğe geçmeden önce çıkış yapılmıyordu; `/giris` misafir route'u olduğu için form hiç görünmüyordu.
6. **Tur 25 dakika sürüyordu ve zaman aşımıyla ölüyordu.** İki sebep: (a) 768px altında tablo hâlâ belgede duruyor (`hidden md:block`), tur onu sayıp **görünmeyen** bir satıra tıklıyor ve iki kez actionability zaman aşımını bekliyordu — liste başına 60 saniye; (b) hiçbir adımın süre sınırı yoktu, dolayısıyla zaman aşımı yalnız *"test çok uzun sürdü"* diyordu, hangi ekran olduğunu değil. Artık satırlar **görünür** olanlardan seçiliyor, tıklamalarda 5 saniyelik sınır var ve her adımın kendi bütçesi var — aşan adım **bulgu olarak kaydediliyor** ve tur devam ediyor.
7. Rapor yalnız bulgu varken yazılıyordu; girişte ölen bir koşu bir öncekinin raporunu diskte bırakıyordu ve onu okumak taze sonuç gibi görünüyordu. Çoktan düzeltilmiş bir bulgu bu yüzden bir saat kovalandı. Artık **her koşu** raporunu yazıyor, boş olsa bile.
8. Playwright dosya filtresi alt-dizgi eşliyor: `office-health.spec.js` istemek `backoffice-health.spec.js`'i de koşturuyordu.
