Files
kolaytercih/docs/gece-vardiyasi/2026-09-23/09-harita-ve-tur-tasarim.md
bilalgursen a42a1a9920
All checks were successful
Deploy / deploy (push) Successful in 17s
docs(ekip): 22-23 Eylül vardiya raporları, BACKLOG/KARARLAR/PROTOKOL güncellemeleri
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 11:43:11 +03:00

400 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 09 — Tasarım: `/sonuc` haritası en üste + üniversite sayfasında tür
> Temel: `is/0923-harita-ve-tur` worktree'si, `origin/main` (c5769e2). Karşılaştırma için
> `gece/0922-urap` dalı `git show` ile okundu. Tarayıcı/dev sunucusu açılmadı, kod değiştirilmedi.
> Piksel ve CLS değerleri **token/kod okumasından hesaplandı**, ölçülmedi — hesap §1.2'de açık.
> Her iki talimat da "yapılacak" kabul edildi; aşağıdakiler *nasıl* yapılacağıdır.
---
## Özet — 5 madde
1. **Harita en üste çıkabilir ve CLS bedeli sıfıra inebilir.** Tek gereken, listenin *sayısını*
sunucunun hidrasyondan önce bilmesi. Profil zaten `kolaytercih.profil` çerezi ile sunucuya
aynalanıyor (`sihirbaz-profil.ts:51-57`); aynı desenle `kolaytercih.liste-sayi` çerezi yazılır,
`/sonuc` bunu okuyup haritaya **tam ölçülü** yer ayırır. Uygulama yükü: 3 dosya, ~25 satır.
2. **"Boşken hiç gözükmesin" şartı bozulmuyor.** Çerez yoksa ya da 0 ise `ManuelHarita` bugünkü gibi
`null` döner — ilk kez gelen ziyaretçi için DOM'da hiçbir düğüm yoktur, dolayısıyla tadımlığın
`first:mt-0` kuralı (`tadimlik-satiri.tsx:134`) da bozulmaz. **İlk ziyaretçide sıra değişmiyor —
doğrulandı** (`manuel-harita.tsx:71` `return null`).
3. **Tadımlığın kaybı kabul edilebilir ve dar.** Kayıp yalnız "girişsiz + kayıtlı listesi olan"
ziyaretçide var. 375×812'de tadımlığın rozeti, H2'si ve açıklaması harita eklendikten sonra da
katlamanın üstünde kalıyor (hesap §1.4); yalnız örnek satır kartı aşağı iniyor. Bu ziyaretçi
"bu ürün ne?" aşamasını geçmiş, ekranda önce kendi listesini görmesi doğru.
4. **Üniversite sayfasında tür, ızgaraya değil kimlik satırına gider.** Izgara 5 karta çıkamaz:
`lg:grid-cols-[1fr_1fr_1.35fr] lg:grid-rows-2` şablonunda harita 2 satır kaplıyor, kartlar için
**tam 4 hücre** var (`universite-icerik.tsx:291`, `universite-konum-haritasi.tsx:77`). Tür zaten
bir *metrik* değil *kategori*; sitenin geri kalanında (4 ayrı dosyada) başlığın altındaki meta
satırında çip olarak yaşıyor. Aynı çip H1'in altına konur, ızgara 4 sayısal kart kalır.
5. **Yan bulgu: `unitur` etiketinin 4 farklı sözlüğü var** ve ikisi bozuk metin basıyor
(`universite-icerik.tsx:318` ve `bolum-program-listesi.tsx:227` → "Kktc", "Vakıf Myo").
`UNITUR_ETIKET` (bugün `gece/0922-urap`'ta, bileşenin içinde) `src/lib/`'e taşınmalı — tıpkı
`DILIM_ETIKET`'in `src/lib/risk.ts`'te tek kaynak olması gibi (AGENTS.md).
---
# Talimat 1 — `/sonuc`'ta harita en üste
## 1.1 Bugünkü durum (doğrulandı)
| Olgu | Kanıt |
|---|---|
| Sıra: tadımlık → tablo → harita | `src/app/sonuc/page.tsx:118, 124, 131` |
| Liste boşken harita hiç çizilmiyor | `manuel-harita.tsx:71` (`if (liste.length === 0) return null`) |
| Sunucu snapshot'ı **her zaman** boş | `liste-store.ts:30, 86` (`getServerSnapshot → BOS_LISTE`) |
| Liste yalnız localStorage'ta | `liste-store.ts:27, 42-46` |
| Profil ise sunucuya **çerezle** aynalanıyor | `sihirbaz-profil.ts:51-57`, `sonuc/page.tsx:65` |
| Store her sayfada hidrasyonda okunuyor | `layout.tsx:98-102` → `ListeCekmecesiLazy`/`KaydetBannerLazy` → `useManuelListe` |
Son satır kritik: liste store'u **site genelinde**, ilk açılan sayfada yükleniyor. Yani yazılacak
çerez, kullanıcı `/sonuc`'a varmadan önce (ana sayfada) yazılmış oluyor.
## 1.2 CLS hesabı — "368 px" rakamı masaüstündü
`main` = `max-w-4xl px-4` (`sonuc/page.tsx:99`). Harita `w-full`, viewBox `1050×447`
(`harita.ts:10-13`, `tercih-haritasi.tsx:46-51`), iskelet de aynı oranı rezerve ediyor
(`tercih-haritasi-lazy.tsx:14`).
| Ekran | İçerik genişliği | Harita yüksekliği |
|---|---|---|
| 375 px (mobil, trafiğin ~%90'ı) | 343 px | 343 × 447/1050 = **146 px** |
| ≥ 928 px (masaüstü) | 864 px | 864 × 447/1050 = **368 px** |
Yani `sonuc/page.tsx:107`'deki "~368 px" **masaüstü** rakamı; mobilde 146 px (+ bugünkü `mt-16` = 64 px).
CLS = etki payı × mesafe payı:
| Senaryo | Etki payı | Mesafe payı | **CLS** |
|---|---|---|---|
| Bugün (harita altta), dönüş ziyaretçisi | ~0 (kayan içerik ~2,4 ekran aşağıda) | — | **~0** |
| Harita üstte, yer ayırmadan, 375×812 | (812−220)/812 ≈ 0,73 | 146/812 = 0,18 | **~0,13** ("iyileştirilmeli") |
| Harita üstte, yer ayırmadan, 1280×800 | ~0,75 | 368/800 = 0,46 | **~0,35** ("kötü") |
| **Harita üstte + çerezle yer ayrılmış** | 0 | 0 | **0** |
(Hesap; ölçüm değil. 220 px = bant + başlık + `py-12`, önceki raporun tahmini.)
## 1.3 İki alternatif
### Alternatif A — liste sayısı çerezi, tek yuva (ÖNERİLEN)
```
375 px · /sonuc · DÖNÜŞ ziyaretçisi (çerez: liste-sayi=7)
┌───────────────────────────────┐ ← ilk boyada sunucudan gelen HTML
│ [bant] [başlık] │
│ │
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ 146 px · HaritaIskeleti
│ ░░░ harita yer tutucu ░░░ │ (aspect-[1050/447], tam ölçü)
│ ░░░░░░░░░░░░░░░░░░░░░░░░░░░ │
│ │
│ ◇ Yapay zekâdan örnek satır │ chunk inince yer tutucunun
│ Yapay zekâ listendeki her │ İÇİ dolar; hiçbir şey kaymaz
│ satırı böyle anlatır │
└───────────────────────────────┘
375 px · /sonuc · İLK KEZ gelen ziyaretçi (çerez yok)
┌───────────────────────────────┐
│ [bant] [başlık] │
│ │
│ ◇ Yapay zekâdan örnek satır │ ← harita DOM'da hiç yok,
│ Yapay zekâ listendeki her │ tadımlık first:mt-0'ı korur:
│ satırı böyle anlatır │ BUGÜNKÜ EKRANIN AYNISI
└───────────────────────────────┘
```
- **CLS:** çerez isabetliyse **0**. Çerez yoksa ama liste varsa (aşağıdaki dar kaçak) 0,13 (mobil).
- **Çerezin kaçacağı tek durum:** dağıtım sonrası ilk sayfa açılışı (kullanıcı başına **bir kez**;
ana sayfada yazıldığı için `/sonuc`'a client-side geçişte bile kapanır) ve localStorage yazma
hatası. Safari ITP kaçağı **yok sayılabilir**: ITP script'le yazılan çerezi 7 günde siler ama aynı
koşulda localStorage'ı da siler — ikisi birlikte ölür, ayrışmaz. Aynı risk zaten `kolaytercih.profil`
çerezinde de var, yeni bir sınıf risk getirmiyor.
### Alternatif B — çift yuva (çerez varsa üstte, yoksa altta)
`ManuelHarita` iki konumda da yazılır; sunucu çereze bakarak hangisinin aktif olacağını seçer.
```
çerez > 0 → [HARİTA] → tadımlık → tablo → sihirbaz
çerez yok → tadımlık → tablo → [HARİTA] → sihirbaz (bugünkü davranış)
```
- **CLS:** her durumda ~0. Kaçak senaryoda bile sıfır, çünkü harita eski (güvenli) yerine düşer.
- **Bedeli:** aynı bileşen iki DOM konumunda yaşar — AGENTS.md'nin "bir eleman iki yerde görünüyorsa
tek kaynaktan" ilkesine biçimsel olarak uymasa da okuma yükü artar; ve kullanıcı bazı ziyaretlerde
haritayı üstte, bazılarında altta görür. **Tutarsızlık, kazanılan 0,13'lük tek seferlik CLS'ten
daha pahalı.**
### Hüküm: **A**
B'nin kapattığı tek delik, kullanıcı başına bir kez yaşanan ve "iyileştirilmeli" bandındaki (kırmızı
değil) bir kayma. Bunun için haritanın sabit bir yeri olmamasını kabul etmek doğru takas değil.
## 1.4 Tadımlık ne kaybediyor — hüküm: kabul edilebilir
| Ziyaretçi | Sıra değişiyor mu? | Tadımlık nerede? |
|---|---|---|
| İlk kez gelen (liste boş) | **Hayır** — harita hiç render edilmez | Bugünkü yerinde, ~220 px |
| Girişli (tadımlık zaten yok, `tadimlik-bolumu.tsx:19`) | Evet, harita tablonun üstüne çıkar | — |
| **Girişsiz + kayıtlı listesi olan** | Evet | ~220 → **~366 px** |
Üçüncü satır için 375×812 hesabı (koddan çıkarım): tadımlığın `SectionEyebrow`'u, H2'si
("Yapay zekâ listendeki her satırı böyle anlatır") ve üç satırlık açıklaması katlamanın üstünde
kalıyor; aşağı inen şey örnek satır kartının gövdesi ve CTA'sı. 375×667'de (iPhone SE) rozet + H2
görünür, paragraf yarılanır.
**Hüküm: kabul edilebilir.** Gerekçe kaybın büyüklüğü değil, kime denk geldiği: tadımlık "ödeme
öncesi tek gerçek önizleme" argümanı **ürünü ilk kez gören** kişi içindir ve o kişide sıra hiç
değişmiyor. Zaten liste kurmuş ziyaretçi için ekranın ilk işi, kendi kurduğu listeyi göstermektir;
tadımlığı onun üstüne koymak "sana hâlâ ürünü tanıtıyorum" demek olurdu.
## 1.5 Uygulayıcıya tarif (yazılımcı)
**Dosya 1 — `src/features/liste/liste-store.ts`**
1. `PROFIL_CEREZ_ADI` deseninin eşi olarak sabit ekle: `LISTE_SAYI_CEREZ_ADI = "kolaytercih.liste-sayi"`
(sabitin yeri: bu dosya; sunucu tarafı buradan import eder — `liste-store.ts` `"use client"`
olduğu için sabiti `liste-dogrula.ts`'e koymak daha temiz, orası zaten sunucu action'larıyla
paylaşılıyor, bkz. `:23-25`).
2. `sayiCereziniYaz(n: number)` — `profilCerezineYaz` (`sihirbaz-profil.ts:51-57`) ile **birebir aynı**
öznitelikler: `path=/; max-age=31536000; samesite=lax` + https'te `secure`. Değer düz tamsayı.
3. Üç çağrı yeri (listenin değiştiği her yer):
- `kaydet()` içinde (`:42-46`) — ekle/çıkar/sırala/temizle hepsi buradan geçiyor;
- `yukle()` içinde, `liste = dogrula(...)` satırından sonra (`:53`) — dağıtım sonrası ilk
açılışta çerezi kendiliğinden doğurur;
- `"storage"` dinleyicisinde `liste` atandıktan sonra (`:64`) — başka sekmedeki değişiklik.
**Dosya 2 — `src/features/liste/components/manuel-harita.tsx`**
4. İmza: `export function ManuelHarita({ yerAyir = false }: { yerAyir?: boolean })`.
5. `:71` → `if (liste.length === 0 && !yerAyir) return null;`
Boş+`yerAyir` dalında **ayrı bir iskelet dalı yazma**: fonksiyon akmaya devam etsin, `pinler` boş
dizi olur, `TercihHaritasiLazy` monte olur ve chunk inene kadar zaten `HaritaIskeleti`'ni gösterir
(`tercih-haritasi-lazy.tsx:10-21`). Böylece (a) yer tutucu ile gerçek harita **aynı** kutudan gelir,
yükseklik ayrışamaz; (b) 197 KB'lık chunk hidrasyonu beklemeden inmeye başlar.
6. `:78` `<div className="mt-16">` → `<div>`. Harita artık `main`'in ilk çocuğu; üst boşluğu
`main`'in `py-12`'sinden alır (bu, `mt-16`'nın eklenmesinden önceki durumdu). `:73-76`'daki yorum
tersini anlatıyor, **aynı commit'te** düzeltilmeli.
**Dosya 3 — `src/app/sonuc/page.tsx`**
7. `cookies()` zaten okunuyor (`:56`): `const listeSayisi = Number.parseInt(cerezler.get(LISTE_SAYI_CEREZ_ADI)?.value ?? "", 10);`
→ `const yerAyir = Number.isFinite(listeSayisi) && listeSayisi > 0;`
8. `<ManuelHarita yerAyir={yerAyir} />` satırını `:131`'den **`:118`'in üstüne** taşı.
Yeni sıra: **harita → tadımlık → tablo → sihirbaz → dipnot.**
9. `:100-114`'teki yorum bloğu bugünkü kararın tersini belgeliyor; yerine yeni gerekçe
(çerezle yer ayırma) yazılmalı — yoksa bir sonraki oturum haritayı yine aşağı alır.
10. `tadimlik-satiri.tsx:127-133`'teki yorum ("harita artık tablonun altında, üstte hiçbir kardeş
yok") da yalan olur. `first:mt-0` **kuralı doğru çalışmaya devam eder** (harita varken tadımlık
ikinci çocuk olur ve `mt-16` alır, harita yokken ilk çocuktur ve `mt-0` alır) — düzeltilecek olan
yalnız yorumun metni.
**Kabul kriterleri**
1. Listesi olmayan ziyaretçinin `/sonuc` HTML'inde harita ile ilgili **hiçbir düğüm yok**
(`grep -c "aspect-\[1050/447\]"` = 0) ve tadımlık `<section>`'ı `main`'in ilk çocuğu.
2. `kolaytercih.liste-sayi=7` çereziyle istenen `/sonuc` HTML'inde, tadımlık `<section>`'ından
**önce** `aspect-[1050/447]` kutusu var.
3. Listeye ilk `+`'dan sonra sayfa yenilenince harita en üstte ve **yukarı kayma yok**.
4. Listeyi tamamen boşaltıp sayfayı yenileyince harita yok (çerez 0 yazılmış olmalı).
**Büyüklük: S** (3 dosya, ~25 satır + 3 yorum bloğu).
## 1.6 Aynı işte kapanması gereken iki küçük delik
| # | Yer | Sorun | Düzeltme |
|---|---|---|---|
| H1 | `tercih-haritasi-lazy.tsx:14` | `animate-pulse` yer tutucu artık **katlamanın üstünde** ve `prefers-reduced-motion` okunmuyor (WCAG 2.2.2'nin ruhu; sürekli, durdurulamaz hareket) | `motion-reduce:animate-none` ekle (tek sınıf) |
| H2 | `manuel-harita.tsx:84-86` → `yurtdisi-haritalar-lazy.tsx:8-11` | `dynamic(..., { ssr:false })` ama `loading` **yok**; chunk inince ~150 px'lik şerit yoktan belirir. Bugün katlamanın altındaydı, yarın üstünde olacak | `loading: () => <div className="mt-3 h-40" aria-hidden />` ya da eşdeğer sabit yükseklik |
**Artık kalan (kabul edilen) kayma:** listesinde KKTC/yurt dışı program olan kullanıcıda yurt dışı
şeridi + "N tercih haritada gösterilemiyor" satırı sunucuda bilinemez; hidrasyonda belirir. H2
uygulanırsa bu ~150 px'lik tek kaymaya iner (mobil CLS ~0,11). Bunu da sıfırlamak çereze ikinci bir
alan yazmayı gerektirir — **önermiyorum**, kazanç bedeli haklı çıkarmıyor.
## 1.7 Reddedilen seçenekler
- **Her zaman sabit yükseklikli yer tutucu:** Bilal'in "liste eklenmezse gözükmesin" şartını
doğrudan çiğniyor.
- **Harita üstte ama küçük/sabit, büyümeyen biçim:** sorun haritanın *boyutu* değil, `null`→görünür
geçişi. 60 px'lik bir şerit bile aynı kaymayı üretir, üstelik harita okunmaz hale gelir.
- **Girişli kullanıcıda listeyi DB'den okumak:** `/sonuc` bugün oturum açmıyor; oturum + sorgu
eklemek sayfayı yavaşlatır ve **girişsiz** çoğunluğu (funnel'ın asıl kitlesi) hiç kapsamaz.
Çerez ikisini birden çözüyor.
---
# Talimat 2 — Üniversite sayfasında "tür"
## 2.1 Izgara gerçekte 4 kart değil, 4 **hücre**
```
universite-icerik.tsx:291
grid-cols-2 ← mobil
lg:grid-cols-[1fr_1fr_1.35fr] lg:grid-rows-2 ← masaüstü
universite-konum-haritasi.tsx:77
col-span-2 lg:col-start-3 lg:row-start-1 lg:row-span-2
```
Masaüstünde harita 3. sütunu **iki satır boyunca** kaplıyor; kartlara kalan yer 2 sütun × 2 satır =
**tam 4 hücre**. 5. kart şablonu bozar: ya 3. bir satır açılır (harita yanında boşluk kalır) ya da
`grid-rows-2` elle 3'e çıkarılır ve haritanın oranı bozulur. Mobilde 5. kart 2 sütunlu ızgarada
**tek başına** bir satıra düşer (yanında boşluk) ve ilk ekrandan ~90 px daha gider.
→ **(a) "ızgara 5 karta çıksın mı?" cevabı: hayır.** Gerekçe estetik değil, şablonun kendisi.
## 2.2 Asıl mesele: tür bir metrik değil
Izgaradaki diğer üç kart sayısal ve karşılaştırılabilir (Program, Fakülte/Birim, En iyi sıralama,
URAP). "Devlet" bir ölçüm değil, kurumun **kimliği**. Sitenin geri kalanı bunu zaten böyle işliyor:
başlığın altındaki meta satırında küçük bir çip olarak, dört ayrı yerde **birebir aynı sınıf setiyle**:
```
shrink-0 rounded px-1 py-px text-[10px] font-medium leading-4
DEVLET → bg-slate-100 text-slate-600 (kontrast 6,92:1 ✅)
diğer → bg-violet-50 text-violet-600 (kontrast 5,19:1 ✅ — hesap: WCAG 2.x, #7C3AED/#F5F3FF)
```
`program-tablosu.tsx:328-336` · `rapor-listesi.tsx:225-234` · `tadimlik-satiri.tsx:160-166` ·
`bolum-program-listesi.tsx:220-229`
Üniversite **detay** sayfası, bu çipi taşımayan tek yüzey. Eksik olan şey kart değil, çip.
## 2.3 İki alternatif
### Alternatif A — çip, H1'in altındaki meta satırında (ÖNERİLEN)
```
390 px · /universite/koc-universitesi · ilk ekran
Ana Sayfa › Üniversiteler › Koç Üniversitesi
┌────┐ Koç Üniversitesi
│logo│ Taban Puanları
│ 64 │ 2026
└────┘
[Vakıf] İstanbul ← EKLENEN TEK SATIR (mt-3, ~22 px)
çip sınıfı: program-tablosu.tsx:329 ile birebir
Koç Üniversitesi (İstanbul), vakıf üniversitesi; ← gece/0922-urap'taki
2026 YKS yerleştirme sonuçlarına göre … telafi cümlesi KALIR
[ Sıralaman Koç tabanlarının neresinde? → ]
┌─ harita (col-span-2, min-h-44) ──────────┐
│ │
└──────────────────────────────────────────┘
┌───────────────┐┌───────────────┐
│ PROGRAM ││ FAKÜLTE/BİRİM │
│ 78 ││ 12 │
└───────────────┘└───────────────┘
┌───────────────┐┌───────────────┐
│ URAP TÜRKİYE ││ EN İYİ SIRALAMA│ ← 4 hücre, hepsi sayısal
│ 34 / 198 ││ 412 │
│ 2025-2026 ││ │
└───────────────┘└───────────────┘
```
- **Yeni bileşen yok**, yeni sınıf yok: çip dört yerde duran sınıf setinin beşinci kopyası (ya da —
tercihen — hepsinin çıkarılacağı tek kaynak, bkz. §2.5).
- **Türün kaç kez geçtiği:** çip 1 + cümle 1 = 2, biri taranabilir, biri okunabilir. Kart yok.
- **Mobil:** H1 bloğu (logo `size-16` + `text-3xl`) 390 px'te zaten ~180 px; çip satırı 22 px ekler,
taşma riski yok (`flex items-center gap-1.5`, çip `shrink-0`, il `truncate`).
- **Erişilebilirlik:** çip metni okunur ("Vakıf"), renk tek başına anlam taşımıyor — AGENTS.md'nin
risk dili kuralının aynı mantığı.
- **Gereken tek yan değişiklik:** URAP'ı olmayan 31 sayfada 4. hücre bugün "Tür" kartı olarak
dolduruluyor (`gece/0922-urap`, `:394-417`). Çip geldiğinde o sayfalarda tür **üç kez** yazar.
4. hücre oraya **"Toplam kontenjan"** olur (`uni.stats.toplamKontenjan`, `lib/katalog.ts:380-401`).
Veri kontrolü: `data/yokatlas.db` — toplam kontenjanı 0 olan **hiçbir üniversite yok**
(`SELECT … GROUP BY universite HAVING SUM(COALESCE(kontenjan2026,0))=0` → 0 satır), yani bu hücrede
asla "—" çıkmaz. Precedent var: `minSira` de hem paragrafta hem kartta geçiyor.
### Alternatif B — "Tür" kartı geri gelir, URAP satır olur
```
Koç Üniversitesi (İstanbul), vakıf üniversitesi; 2026 YKS …
URAP Türkiye sıralaması: 34 / 198 (2025-2026) ← paragrafın altına yeni satır
┌ PROGRAM 78 ┐┌ FAKÜLTE 12 ┐
┌ TÜR Vakıf ┐┌ EN İYİ 412 ┐
```
- Artısı: diff küçük, `gece/0922-urap`'ın kart kısmı geri alınır.
- Eksileri: (a) **sayı prozaya gömülür** — URAP taranmak için var, "34/198" cümle içinde aranır hale
gelir; tasarımın dünkü gerekçesinin tersi. (b) Izgarada kategori/metrik karışımı sürer.
(c) Tür yine `text-2xl font-bold` ile "Vakıf MYO" gibi uzun değerlerde 190 px'lik karta sığmaz.
(d) Çip eksikliği kapanmaz: detay sayfası hâlâ sitenin geri kalanından farklı konuşur.
### Hüküm: **A**
Bilal'in "tür olsun" isteği A'da daha güçlü karşılanıyor: tür artık **ızgaranın dördüncü kutusunda,
ekranın ortasında** değil, **üniversitenin adının hemen altında** — hem ilk bakışta hem her sayfa
genişliğinde. Ve site genelindeki çiple aynı dili konuşuyor.
## 2.4 Uygulayıcıya tarif (yazılımcı)
Taban: `gece/0922-urap` (orada `UNITUR_ETIKET` ve telafi cümlesi hazır). Dal bayatsa önce
`origin/main` üstüne yeniden kurulmalı.
1. `UNITUR_ETIKET`i (`universite-icerik.tsx:54-66`) **`src/lib/unitur.ts`**'e taşı ve üçüncü alan
ekle: `{ kart, cumle, cip }`. `cip` = çipte görünen kısa metin (`Devlet`, `Vakıf`, `Vakıf MYO`,
`KKTC`, `Yurt dışı (kamu)`, `Yurt dışı (vakıf)`) — `kart` ile aynı değerler, ama `kart` alanı
§2.4-3 ile ölüyorsa ikisi birleştirilebilir. Gerekçe: aynı bilginin ekrandaki etiketi tek
kaynaktan gelmeli — `DILIM_ETIKET`'in `src/lib/risk.ts`'te durması gibi (AGENTS.md).
2. `universite-icerik.tsx`, H1 bloğunun (`:253-259`) **hemen altına**, `ilkSayfa` koşulundan
**bağımsız** (sayfalama sayfalarında da üniversite kimliği aynı):
```
<p className="mt-3 flex items-center gap-1.5 text-xs text-slate-500"> …çip… {il} </p>
```
Çipin sınıfı `program-tablosu.tsx:329-333`'ten **kopyalanır**, yeni varyant üretilmez.
Renk kuralı da aynı: `DEVLET` → slate, diğer her şey → violet.
`uni.unitur` NULL ise satırın tamamı render edilmez (kopuk boşluk kalmaz).
3. Izgaradaki 4. hücre (`gece/0922-urap`, `:394-417`): `uni.urap ? URAP : Tür` koşulu
→ `uni.urap ? URAP : Toplam kontenjan`. `dt`: "Toplam kontenjan", `dd`: `sayi(uni.stats.toplamKontenjan)`.
4. `bolum-program-listesi.tsx:227` `trBaslikDuzeni(p.unitur)` → `UNITUR_ETIKET[p.unitur].cip`.
Bugün o çip "Kktc" / "Vakıf Myo" / "Yurtdısı Kamu" basıyor (`slug.ts:56-64` tr-TR küçültmesi).
**Aynı commit'e girmeli**: tek kaynak taşınırken düzelmeyen tek tüketici kalmasın.
**Kabul kriterleri**
1. `bogazici-universitesi.html` → çipte "Devlet" **1 kez**, özet cümlesinde "devlet üniversitesi"
**1 kez**, ızgarada "Tür" **0 kez**.
2. `dogu-akdeniz-universitesi.html` (URAP yok) → çipte "KKTC", 4. hücrede "Toplam kontenjan" + sayı,
hiçbir hücrede "—" yok.
3. `/bolum/...` sayfalarında KKTC programının çipinde "KKTC" yazıyor, "Kktc" değil.
4. 390 px'te H1 bloğu + çip satırı yatay taşma üretmiyor (uzun üniversite adı: "İstanbul Medeniyet
Üniversitesi").
5. `pnpm build` temiz; 229 üniversite sayfasının 229'unda çip var.
**Büyüklük: S** (2 yeni/taşınan dosya + 3 dokunuş, ~50 satır).
## 2.5 Aynı işte kapanacak tutarlılık borcu
| Yer | Sorun | Kanonik kaynak |
|---|---|---|
| `program-tablosu.tsx:329` · `rapor-listesi.tsx:227` · `tadimlik-satiri.tsx:161` · `bolum-program-listesi.tsx:222` | Aynı çip sınıf seti **4 kez elle kopyalanmış**; 5.'si ekleniyor | AGENTS.md "ortak elemanlar tek kaynaktan". **Öneri:** `src/components/unitur-cipi.tsx` — küçük, tek amaçlı sunum bileşeni. Bu, "yeni bileşen tasarlama" yasağının kapsamına girmez: yeni bir *tasarım* değil, var olan beş kopyanın tek kaynağa alınmasıdır. Ayrı ve tek amaçlı commit. |
| `app/api/katalog-ara/route.ts:49-56` | `unitur` için **beşinci** sözlük: "KKTC / Yurt dışı" (arama sonucunda böyle, sayfada "KKTC") | Aynı `UNITUR_ETIKET`. Düşük öncelik; ayrı iş. |
| `program-tablosu.tsx:175` | Root `<section className="mt-16">` — `first:mt-0` yok. Girişli + listesiz kullanıcıda (tadımlık yok, harita yok) tablo `main`'in ilk çocuğu olur, tepede 64 px ölü alan kalır | `tadimlik-satiri.tsx:134` deseni (`mt-16 first:mt-0`). XS, Talimat 1 ile aynı commit'e sığar |
---
## Bilal'in gözle kontrol etmesi gerekenler (375–390 px)
1. **`/sonuc`, listeye 1-2 program ekledikten sonra sayfayı yenile.** Harita en üstte mi, açılışta
yukarı/aşağı zıplama var mı? (Talimat 1'in tek gerçek testi.)
2. **`/sonuc`, tarayıcı verisini sildikten sonra ilk kez.** Harita hiç yok mu, tadımlık en üstte mi
ve üstünde fazladan boşluk var mı? (`first:mt-0` kontrolü.)
3. **`/sonuc`, girişsiz + listeli.** Tadımlığın başlığı ("Yapay zekâ listendeki her satırı böyle
anlatır") hâlâ katlamanın üstünde mi? Hükmüm buna dayanıyor (§1.4).
4. **Listesinde KKTC/yurt dışı programı olan bir listeyle `/sonuc`.** Türkiye haritasının altındaki
yurt dışı şeridi geç belirip içeriği itiyor mu? (§1.6-H2)
5. **`/universite/koc-universitesi` ve `/universite/dogu-akdeniz-universitesi`.** Üniversite adının
altındaki tür çipi ilk bakışta görülüyor mu, yoksa çok mu küçük kalıyor? (A alternatifinin tek
öznel riski budur; "daha büyük olsun" dersen o **yeni bir çip varyantı** demektir ve site
genelindeki dört kopyayı da etkiler → `[BİLAL]` kararı.)
## Bilal'den istenen (3 madde)
1. **Talimat 1 için A'yı onayla** (liste sayısı çerezi + harita en üstte). Yeni bir işlevsel çerez
yazılacak; kişisel veri taşımıyor, `kolaytercih.profil` ile aynı sınıf. **~2 dk.**
2. **Talimat 2 için A'yı onayla**: tür = ad altındaki çip, URAP'ı olmayan 31 sayfada 4. kart
"Toplam kontenjan" olur. **~2 dk.**
3. **§2.5'teki `UniturCipi` tek-kaynak commit'i** ayrı iş olarak backlog'a girsin mi
(5 kopya → 1 bileşen)? **~1 dk.**