Files
kolaytercih/docs/ekip-raporlari/2026-10-07/cto-geri-bildirim-sartname.md
bilalgursen da0e9dff7a docs(ekip): 7–10 Ekim raporları, eval sonuçları, seslendirme betiği
- docs/ekip-raporlari/2026-10-07, 2026-10-09, 2026-10-10 raporları ve ekran görüntüleri
  (ses/ altındaki mp3 dosyaları depoya alınmadı, 38 MB)
- BACKLOG #38: rehber yazılarına "Dinle" düğmesi adayı
- scripts/seslendir.py (Türkçe TTS) ve .gitignore'a .venv-tts/
- scripts/eval/sonuc: 8–10 Ekim revizyon eval çıktıları

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-11 00:33:39 +03:00

46 KiB
Raw Blame History

CTO şartnamesi — Bilal'in 10 maddelik geri bildirimi (7 Eki 2026)

Taban: dal feat/arc-secim-sayi, HEAD 4d30dd1. Tüm dosya:satır atıfları bu commit üzerinde doğrulandı. Çalışma ağacında yalnız data/app.db kirli (Bilal'in); dokunulmaz.

Özet (5 madde)

  1. Beş paket, dosya kümeleri ayrık: A (sohbet sunucusu: rehber linki doğrulama + revizyon sınırının kaldırılması), B (revizyon "ilk N" garantisi), C (program varyant etiketi: burs/dil/öğretim türü), D1 (sohbet UI: shimmer, logo varyantı, soru geçişi), D2 (program ayrıntı çekmecesi: "Yıllara göre" kaldırma, açılış bug'ı, logo→içerik koreografisi). Madde 7 için iş yok.
  2. Üç kök neden koddan doğrulandı: (1) rehber linki için kod tarafında hiçbir doğrulama yok — denetim yalnız vaat/kayıt/revizyon beyanına bakıyor (src/lib/ai/cikti-denetimi.ts), prompt "adres uydurma" diyor ama model uyduruyor; (2-3) programs.isim sütunu burs/dil varyantını taşımıyor (Başkent Tıp ×6 satır: Burslu/%50/Ücretli × Türkçe/İngilizce; ayırt edici alanlar program_ozellik.burs_orani / ogrenim_dili / ogrenim_turu), hiçbir liste bu alanı göstermiyor; (4) "ilk N" kuralı modelin seçimine bırakılmış — secimiOnar yalnız eksik satırı tamamlarken +4 öncelik veriyor, model 24 satırı İstanbul'suz doldurursa ilkTercihleriOneAl öne alacak satır bulamıyor; dev mock yolu (mockRapor) kuralı hiç uygulamıyor.
  3. İtiraz (madde 8-9): "Drawer'daki info" liste çekmecesinde değil; liste-cekmecesi.tsx'te bilgi paneli yok. Info düğmesi ve "Yıllara göre" bölümü src/features/katalog/components/program-ayrinti.tsx (ProgramAyrintiButonu; rapor-listesi.tsx:364 ve program-tablosu.tsx:443'ten açılıyor). Açılış bug'ının kök nedeni çift durak (0,6 → 0,92) + veri geldikten sonra boyun değişmesi (alt-sayfa.tsx ResizeObserver yeniden hedefliyor, kısık durakta gövde kaydırmıyor).
  4. Test altyapısı: vitest/jest yok; mekanik testler scripts/eval/*.ts içinde, pnpm tsx --conditions=react-server <betik> ile koşuyor ve çıkış kodu 1 ile düşüyor (akis-denetim.ts deseni: sahte OpenAI ucu + geçici app.db kopyası). Yeni testler bu desene uyar: baglanti-denetim.ts, revizyon-denetim.ts, varyant-denetim.ts.
  5. Merge sırası: paketler bağımsız; tek bilinçli çakışma A↔D1 (sohbet-client.tsx'in revizyonHakki prop'u) — A prop'u 0/1 bayrak olarak besler, D1 dokunmaz, entegrasyonda orkestratör 3 satırlık yeniden adlandırma yapar. Build'i yalnız orkestratör alır; paketler typegen + tsc + lint ile kapanır.

Dosya sahipliği

Paket Madde Dosyalar (Y = yeni)
A sohbet sunucusu 1, 6 src/lib/ai/baglanti-denetimi.ts (Y), src/lib/ai/sohbet-akisi.ts, src/app/api/soru/route.ts, src/lib/ai/danisman-prompt.ts, src/lib/ai/araclar.ts, src/lib/ai/sohbet-meta.ts, src/lib/credits.ts, src/features/rapor/liste-revizyon.ts, src/features/rapor/components/listem-icerik.tsx, src/features/rapor/components/listem-govde.tsx, src/features/rapor/components/sohbet-senaryo-verisi.ts, src/app/page.tsx, src/features/odeme/components/paket-satinal.tsx, scripts/eval/akis-denetim.ts, scripts/eval/baglanti-denetim.ts (Y)
B revizyon "ilk N" 4 src/lib/revizyon-kurali.ts, src/lib/ai/rapor.ts, scripts/eval/revizyon-denetim.ts (Y)
C program varyantı 2, 3 src/types/yokatlas.ts, src/lib/db.ts, src/lib/program-varyant.ts (Y), src/lib/rapor-havuzu.ts, src/components/liste-satiri.ts, src/features/liste/liste-queries.ts, src/features/liste/liste-dogrula.ts, src/features/liste/hooks/use-tercih-profili.ts, src/features/liste/components/program-ekle-butonu.tsx, src/features/liste/components/program-tablosu.tsx, src/features/liste/components/liste-cekmecesi.tsx, src/features/rapor/components/rapor-listesi.tsx, src/features/rapor/rapor-queries.ts, src/features/katalog/components/universite-program-tablosu.tsx, src/features/katalog/components/bolum-program-listesi.tsx, scripts/eval/varyant-denetim.ts (Y)
D1 sohbet UI 5, 10 src/features/rapor/components/sohbet-client.tsx, src/features/rapor/components/sohbet-anketi.tsx, src/lib/adim-gecisi.ts (Y), src/features/sihirbaz/components/sihirbaz-adimlar.tsx, src/components/ui/loader.tsx (yalnız gerekirse, bkz. D1-5)
D2 program ayrıntı çekmecesi 8, 9 src/features/katalog/components/program-ayrinti.tsx, src/features/katalog/components/akademik-ayrinti.tsx, src/components/ui/acilis-sahnesi.tsx (Y), docs/tasarim/DESIGN.md (yalnız §4.19)
— 7 iş yok

Dokunulmayanlar (bilerek): src/lib/ai/cikti-denetimi.ts (link denetimi ayrı saf modülde; vaat/kayıt denetimleriyle karışmasın), src/features/rapor/rapor-actions.ts (revisionCount sayaç olarak kalır), src/lib/appdb/schema.ts (göç yok), src/features/rapor/components/arac-pilleri.tsx, src/lib/ai/cikti-denetimi.ts, src/components/animasyonlu-mark.tsx, src/components/ui/alt-sayfa.tsx.

Çakışma kararları

  1. sohbet-client.tsx — A ve D1. Sahibi D1. A, listem-govde.tsx:450'de revizyonHakki={kalanHak} yerine revizyonHakki={hasPaket ? 1 : 0} geçer ve yanına // 0/1 bayrak; prop adı entegrasyonda revizyonAcik olur yorumu koyar. D1 prop'a dokunmaz. Entegrasyonda orkestratör revizyonHakki?: number → revizyonAcik?: boolean yeniden adlandırmasını (sohbet-client.tsx:352,385,944,1075 + listem-govde.tsx:450) tek commit'te yapar.
  2. src/lib/ai/rapor.ts — B ve C. Sahibi B. C, programBilgisi (rapor.ts:∼140) fonksiyonuna dokunmaz; varyant etiketi kayıtlı rapora yazılmaz, okuma anında rapor-queries.ts'te zenginleştirilir (eski kayıtlara da işler — bkz. C-4).
  3. src/lib/ai/danisman-prompt.ts, araclar.ts, route.ts — A tek sahip. Madde 1'in prompt tarafı ve madde 6'nın prompt/araç/route değişiklikleri aynı pakette olduğu için çakışma yok.
  4. docs/tasarim/DESIGN.md — D2 tek sahip (yalnız §4.19). D1'in §4.20'ye eklemesi gereken bir not varsa raporuna yazar, tasarımcı/orkestratör entegrasyonda işler.
  5. rapor-havuzu.ts — C. B bu dosyaya dokunmaz (B'nin işi rapor.ts + revizyon-kurali.ts).

Tüm paketler için ortak kurallar

  • Yalnız verilen worktree + dalda lokal commit; push/merge yok. git add -A / git add . yasak; dosyalar yol vererek eklenir. Yıkıcı git komutları (reset --hard, checkout --, restore, clean, stash) yasak (AGENTS.md).
  • Worktree'de node_modules symlink değil klon (cp -Rc node_modules <worktree>/node_modules); Turbopack symlink'te patlıyor.
  • Doğrulama sırası, her commit'ten önce: pnpm exec next typegen && pnpm exec tsc --noEmit && pnpm lint. (package.json'da typegen script'i yok; pnpm exec next typegen kullanılır. Taze worktree'de typegen koşulmadan tsc baseline'da kırıktır — PageProps tipi .next/types'tan gelir.) git status temiz kalmalı; typegen yalnız gitignore'lu çıktı üretir.
  • pnpm build koşulmaz (paralel build RAM'i tüketir); tam build'i entegrasyonda orkestratör alır. next build env'siz koşar: modül yüklenirken env zorunlu kılan kod yazılmaz; guard çağrı anında atar.
  • Dev sunucusu / tarayıcı yok. Next'e özgü bir API'ye dokunmadan önce node_modules/next/dist/docs/ içindeki sayfa okunur; A için 01-app/03-api-reference/03-file-conventions/route.md (Route Handler) ve 01-app/01-getting-started/15-route-handlers.md.
  • AGENTS.md / DESIGN.md: rozet = SectionEyebrow; CTA'da yıldız yok, ArrowRight sonda; kapatma çarpısı DialogContent sınıf seti; dark: yazılmaz, yüzey bg-card; pnpm tasarim:denetim sıfır hata (lint koşuyor). Sihirbaz/liste mimarisi değişmiyor → /meraklisina şeması bu turda değişmez (B'nin "ilk N" garantisi havuz kuralını değil seçim onarımını değiştirir; şema zaten "aday havuzu dışına çıkılmaz" diyor).
  • Mekanik testler scripts/eval/ altında, pnpm tsx --conditions=react-server <betik> ile koşar, beklenti tutmazsa process.exit(1); desen scripts/eval/akis-denetim.ts. Testler data/app.db'yi doğrudan kullanmaz — geçici kopya (copyFileSync + APP_DB_URL=file:… + node scripts/db-goc.mjs).
  • Her paket raporu: değişen dosyalar, komut çıktıları (typegen/tsc/lint + paket testi), "koddan çıkarım" işaretli kalanlar, backlog önerisi.

Paket A — Sohbet sunucusu: rehber linki doğrulama (madde 1) + revizyon sınırı kaldırma (madde 6)

A-0 Kök neden (doğrulandı)

  • Madde 1. danisman-prompt.ts:247-251 modele "adres araç sonucundan gelir, adres uydurma" diyor; rehber_ara/rehber_oku (araclar.ts:304, 378, 1288-1314, 1476-1492) gerçek slug'ları döndürüyor. Ama sohbet-akisi.ts:207-247 çıktı denetimi yalnız vaat dili, kayıt beyanı, revizyon beyanı ve şema sızıntısına bakıyor; /rehber/<slug> için hiçbir kontrol yok. Model araç çağırmadan ezberden [Yatay geçiş](/rehber/yatay-gecis-rehberi) yazabiliyor; istemci (sohbet-baglanti.tsx:58,78) her /rehber/[a-z0-9-]+ adresini "Rehber yazısı" hapı yapıyor → 404'e giden hap. Gerçek slug kaynağı content/rehber/*.md (50 dosya) → getAllRehberSlugs() / getRehberAramaKayitlari() (src/lib/rehber.ts:224-264, server-only).
  • Madde 6. MAX_REVIZYON = 2 (credits.ts:18); kapı liste-revizyon.ts:82-88; prompt danisman-prompt.ts:75-82; araç araclar.ts:472 ("en fazla 2 hak"), araclar.ts:1764,1770; route route.ts:25,199,210; UI listem-icerik.tsx:12,180, listem-govde.tsx:78,102,219,335,450, sohbet-client.tsx:352,385,944,1075; pazarlama page.tsx:31,247, paket-satinal.tsx:37; demo sohbet-senaryo-verisi.ts:78,90; tip sohbet-meta.ts:50 (kalanHak). DB sütunu reports.revision_count (schema.ts:165, drizzle/0000_baslangic.sql:62) — göç gerekmez: sütun sayaç olarak kalır (kaç kez kuruldu, istatistik), yalnız kapı olmaktan çıkar.

A-1 Rehber linki doğrulama — tasarım

Akış text-delta parçalarıyla istemciye anında gidiyor (sohbet-akisi.ts:189-194); cevap bitince eklenen "düzeltme notu" yanlış hapı ekrandan geri alamaz. Bu yüzden düzeltme akış sırasında yapılır: küçük bir tampon, bir bağlantı adayı başladığında metni tutar, bağlantı kapanınca doğrulayıp bırakır. Bağlantılar kısadır (≤ ~120 karakter), gecikme hissedilmez.

Yeni saf modül src/lib/ai/baglanti-denetimi.ts (DB/ağ/Next yok; server-only import etmez — sohbet-akisi.ts gibi betiklerden koşar):

export type RehberKaydi = { slug: string; baslik: string };
export type BaglantiDuzeltme = { slug: string; sonuc: "yakin" | "duz"; yeniSlug?: string };

/** Metindeki /rehber/<slug> adreslerini (markdown bağlantısı, ters tırnak ve çıplak adres) gerçek kayıtlarla eşler. */
export function rehberBaglantilariniDuzelt(metin: string, rehberler: readonly RehberKaydi[]): { metin: string; duzeltmeler: BaglantiDuzeltme[] };

/** Akış tamponu: delta verilir, güvenle bırakılabilen metin döner; bitir() kalanı boşaltır. */
export class BaglantiTamponu {
  constructor(rehberler: readonly RehberKaydi[]);
  besle(delta: string): string;
  bitir(): string;
  readonly duzeltmeler: BaglantiDuzeltme[];
}

Kurallar:

  • Eşleme: slug content/rehber kaydıysa dokunma. Değilse en yakın gerçek slug: slug'lar - ile parçalanır, Dice/Jaccard benzerliği hesaplanır; eşik ≥ 0,5 ve tek aday açık ara önde (ikinciden ≥ 0,15 fazla) ise [başlık](/rehber/yeni-slug) olarak yeniden yazılır (başlık kayıttan). Aksi hâlde markdown bağlantısı düz metne iner ([Yatay geçiş](/rehber/x) → Yatay geçiş), çıplak /rehber/x adresi silinir (öncesindeki "bkz." gibi bağlaç kalır; aşırı mühendislik yok).
  • Tampon: metinde [ ya da /rehber/ görülünce o noktadan itibaren tutulur; markdown bağlantısı ) ile, çıplak adres slug dışı bir karakterle (boşluk, nokta, satır sonu, )) kapanınca doğrulanıp bırakılır. [ sonrası 160 karakter içinde ]( gelmezse tutulan metin olduğu gibi bırakılır (sıradan köşeli parantez). bitir() kalanı doğrulayıp boşaltır.
  • /universite/…, /bolum/… bu turda doğrulanmaz (backlog).

sohbet-akisi.ts değişikliği:

  • danismanAkisi seçeneklerine rehberler?: readonly RehberKaydi[] eklenir (verilmezse tampon pasif: betikler/evaller eski davranışı korur).
  • Okuma döngüsünde (:181-195): text-start → yeni BaglantiTamponu; text-delta → cikti = tampon.besle(parca.delta); metin += cikti; cikti boş değilse writer.write({ ...parca, delta: cikti }); text-end → kalan = tampon.bitir(); kalan varsa önce text-delta olarak yazılır, sonra text-end. Diğer parçalar olduğu gibi.
  • CiktiDenetimi'ne baglantiDuzeltme: number eklenir; console.info("[soru] rehber bağlantısı düzeltildi:", n) loglanır (slug'lar loglanabilir, kişisel veri değil).
  • Kayıt (onTamam(metin)) düzeltilmiş metni alır — ekran ve DB aynı.

route.ts: import { getRehberAramaKayitlari } from "@/lib/rehber" ve danismanAkisi({ ..., rehberler: getRehberAramaKayitlari() }). (Prod'da cache'li, rehber.ts:52-60.)

danisman-prompt.ts BAĞLANTI maddesi (:247-251): bir cümle eklenir: "Rehber adresi yalnız bu cevapta rehber_ara / rehber_oku sonucunda gördüğün sayfa alanından gelir; görmediğin bir yazıya bağlantı verme, önce rehber_ara çağır." (Kod koruması asıl savunma; prompt yalnız araç çağrısını teşvik eder.)

A-2 Revizyon sınırının kaldırılması

  • credits.ts: MAX_REVIZYON silinir (yorumuyla).
  • liste-revizyon.ts: :82-88 HAK kapısı silinir; RevizyonSonucu.kod birliğinden "HAK" çıkar; ozetCikar imzasından revisionCount ve dönen kalanHak çıkar (:46-57); :40,43,126,160 metinlerindeki "revizyon hakkın kullanılmadı" → "kredin/paketin etkilenmedi" (dürüst: paketlide bedel 0). revisionCount: +1 güncellemesi kalır (:155).
  • sohbet-meta.ts:50: kalanHak tipten çıkar (eski kayıtlardaki fazla alan çalışma zamanında zararsız).
  • araclar.ts: :472 açıklamasında "(paketli adayda, en fazla 2 hak)" → "(paketli adayda; sınır yok)"; :1764 HAK dalı silinir; :1770 "kalan revizyon hakkını belirt" ibaresi silinir; AracBaglami.kalanRevizyon (:103-104) silinir.
  • danisman-prompt.ts:75-82 revizyonBolumu(r: { paketli: boolean }, params): kalanHak dalları silinir; paketli durum metni: "Revizyon sınırsız; her kurulum 20-60 sn sürdüğü için isteği yine tek seferde doğru kur." 3. maddedeki "kalan hakkı belirt" silinir. sistemPromptu seçeneği revizyon?: { paketli: boolean } olur.
  • route.ts: MAX_REVIZYON import'u ve :199,210 hesapları silinir; revizyon: { paketli }; aracBaglami.kalanRevizyon kalkar.
  • listem-icerik.tsx:12,180: MAX_REVIZYON import'u ve kalanHak prop'u kalkar. listem-govde.tsx: kalanHak prop'u (:78,102) silinir; :219 "(kalan hak: N)" ifadesi silinir; :335 koşulu hasPaket olur; :450 → revizyonHakki={hasPaket ? 1 : 0} + yorum (çakışma kararı 1).
  • sohbet-senaryo-verisi.ts:78 kalanHak: 1 ve :90 "Kalan revizyon hakkın: 1." silinir.
  • page.tsx:31,247: MAX_REVIZYON import'u kalkar; madde metni "Sınırsız liste revizyonu (danışmanla sohbetten)". paket-satinal.tsx:37: aynı metin. paket/page.tsx:13 ve page.tsx:185 zaten "liste revizyonları" diyor, dokunulmaz.
  • sohbet-akisi.ts:134-137 "bu cevapta ikinci revizyon çalıştırılmaz" korunur — bu bir hak değil, cevap başına döngü koruması.

A-3 Kabul kriterleri

  1. grep -rn "MAX_REVIZYON\|kalanHak\|kalanRevizyon" src scripts → 0 satır.
  2. grep -rn "revizyon hakk" src → 0 satır (eval raporları ve docs hariç).
  3. pnpm tsx --conditions=react-server scripts/eval/baglanti-denetim.ts → … senaryo geçti, çıkış 0. Senaryolar (en az): (a) gerçek slug'lı markdown bağlantısı aynen kalır; (b) /rehber/yatay-gecis-rehberi → /rehber/yatay-gecis-cap-yandal (yakın eşleşme, başlık kayıttan); (c) /rehber/tamamen-uydurma-seyler → düz metin; (d) ters tırnak içi ve çıplak adres de düzeltilir; (e) tampon: aynı bağlantı 3-4 deltaya bölünerek beslenince besle çıktılarının birleşimi + bitir() tek seferlik sonuçla birebir aynı; (f) [ ile başlayıp bağlantı olmayan metin ("[2024] verisi") kayıpsız geçer.
  4. pnpm tsx --conditions=react-server scripts/eval/akis-denetim.ts → mevcut 7 senaryo + yeni 2 senaryo geçer: rehber-uydurma (model [OBP](/rehber/obp-nedir) ve [Yatay geçiş](/rehber/yatay-gecis-rehberi) yazar; rehberler seçeneği gerçek content/rehber listesinden verilir; beklenti baglantiDuzeltme === 2, kaydedilen metinde /rehber/obp-nedir-nasil-hesaplanir ve /rehber/yatay-gecis-cap-yandal var, /rehber/obp-nedir) yok) ve rehber-gercek (gerçek slug, baglantiDuzeltme === 0). Beklenen tipi baglantiDuzeltme ile genişler; eski senaryolara 0 yazılır.
  5. typegen + tsc + lint temiz.

A-4 Commit planı

  1. feat(sohbet): rehber bağlantıları akışta gerçek slug'larla doğrulanıyor — baglanti-denetimi.ts, sohbet-akisi.ts, route.ts, danisman-prompt.ts (BAĞLANTI cümlesi), baglanti-denetim.ts, akis-denetim.ts.
  2. feat(revizyon): revizyon sayısı sınırı kaldırıldı — tek koşul paket + sohbet kredisi — kalan dosyalar.
  3. [Bilal] ayrı commit yok; pazarlama metni değişikliği Bilal'in açık isteği.

A-5 Uzman ve denetim

  • yapay-zeka-muhendisi: prompt cümlesini ve yakın-eşleşme eşiklerini (0,5 / 0,15) gözden geçirir; pnpm eval:sohbet'e "rehberde olmayan konu" senaryosu ekler (backlog).
  • guvenlik-uyum: merge öncesi A-2 (para/paket vaadi değişiyor; /paket metni ve koşullar sayfası tutarlı mı). Link düzeltmede log yalnız slug içerir, kişisel veri yok.

Paket B — Revizyon "ilk N" garantisi (madde 4)

B-0 Kök neden (doğrulandı)

Boru hattı var ama belirleyici değil:

  • efektifParams (revizyon-kurali.ts:45-83) İstanbul'u havuz süzgecine ekliyor ✔; havuzOlustur onceliklileri kırpmada öne alıyor (rapor-havuzu.ts:343-347) ✔; havuz JSON'unda "oncelikli": true işaretleniyor (rapor.ts:424-442) ✔; prompt "EN AZ N tane seç" diyor (rapor.ts:566-571) — ricadır.
  • secimiOnar (rapor.ts:207-270) modelin seçtiği geçerli satırları olduğu gibi alır, yalnız eksik kalan yeri uyum (+4 öncelikli) sırasıyla doldurur. Model 24 geçerli satırı öncelikli satırsız doldurursa hiçbir şey tamamlanmaz; ilkTercihleriOneAl (rapor.ts:866) öne alacak satır bulamaz → "ilk 5 İstanbul" istendi, liste değişmedi gibi görünür. (YETERLI_MODEL_SECIMI = 12 olduğundan ikinci deneme de tetiklenmez.)
  • Dev mock yolu (rapor.ts:572-575 → mockRapor :368-422) ilkTercihleriOneAl/oncelikliyi hiç uygulamaz — lokalde anahtarsız denemede kural hiç görünmez.
  • Model kuralı hiç çıkarmıyorsa (liste_revize'yi iller.ekle ile çağırıp ilkTercihler vermiyorsa) bu kod tarafı değil; pnpm eval:sohbet konusu (yapay-zeka-muhendisi, B-5).

B-1 Değişiklik

revizyon-kurali.ts — yeni saf fonksiyon:

/**
 * Dilim seçimi kurulduktan sonra "ilk N" ölçütüne uyan satır sayısı N'den
 * azsa, havuzdaki uyan ama seçilmemiş programlar, aynı dilimdeki en düşük
 * uyumlu uymayan satırların yerine konur (dilim adetleri değişmez). Havuzda
 * yeterli uyan yoksa eldeki kadarı; takas edilen satır kaynak "tamamlandi"
 * olur (gerekçe koddan şablonlanır). Dönen `eklenen` = takas sayısı.
 */
export function ilkTercihleriGarantile<S extends { programId: string; kaynak: string }>(
  secim: Record<Dilim, S[]>,
  havuz: readonly AdayProgram[],
  k: RevizyonKurali | undefined,
  uyum: (p: AdayProgram) => number,
  yeniSatir: (p: AdayProgram) => S,
): { secim: Record<Dilim, S[]>; eklenen: number };

Takas kuralı: öncelikli adaylar uyum azalan sırayla; her aday kendi diliminde, uymayan satırlar arasından uyumu en düşük olanı çıkarır (model gerekçesi olan satırlar önce korunur: kaynak === "model" olanlar en son çıkar). Dilim adetleri sabit kalır; dilim hedefi 0 olan dilimde takas yapılmaz. Havuz öncelikli-dilim dağılımı N'e yetmiyorsa eldeki kadar.

rapor.ts:

  • secimiOnar sonrası, nihai belirlendikten sonra (:703 civarı, metin denetiminden önce) ilkTercihleriGarantile(nihai.secim, havuz, kural, uyum, (p) => ({ programId: p.id, gerekce: null, kaynak: "tamamlandi" })) uygulanır; onarim.tamamlanan += eklenen; taniye ilkNEklenen alanı eklenir (RaporTani tipi genişler).
  • mockRapor (:368-422): oncelikli süzgecini alır, dilimden seçimini oncelikli olanları önce alacak şekilde sıralar ve sonunda ilkTercihleriOneAl uygular (mock prod sırasını taklit eder — kayıtlı yorum zaten "prod ile aynı hedef hesabı" diyor).
  • Kullanıcı mesajındaki "EN AZ N tane seç" cümlesi kalır (modelin gerekçe yazması için), ama sonuç koda bağlıdır.

B-2 Test — scripts/eval/revizyon-denetim.ts (Y)

akis-denetim.ts deseni: sahte OpenAI ucu; AI_BASE_URL/AI_API_KEY sahte; TYPESAFE_API_KEY silinir; data/yokatlas.db gerçek (salt okunur), app.db gerekmez. Sahte model chat/completions isteğinden response_format.json_schema adetlerini (hayal/dengeli/guvenli minItems) ve kullanıcı mesajındaki ADAY HAVUZU … : {…} JSON'unu okur, her dizi için oncelikli olmayan id'lerden tam adet seçer (gerekçe "test"), genelDegerlendirme/uyarilar sabit. Senaryolar:

  1. ilk5-istanbul: params { sira: 100000, tur: "say", iller: ["Ankara","İzmir"], kategoriler: [], revizyon: { ilkTercihler: { adet: 5, iller: ["İstanbul"] }, not: "ilk 5 İstanbul" } } → raporUretAyrintili(params, "ilk 5 İstanbul", undefined); beklenti: rapor.tercihler[0..4] her birinin programlar[id].il === "İSTANBUL" (veri büyük harf; olcuteUyar zaten tr-TR küçültüyor), tercihler.length === 24, tani.ilkNEklenen === 5, dilim adetleri kapsam.dagilim ile aynı.
  2. ilk3-vakif: ilkTercihler: { adet: 3, universiteTipi: "vakif" } → ilk 3 satır uniturDevletMi(unitur) === false.
  3. havuz-yetersiz: ilkTercihler: { adet: 10, iller: ["Ardahan"] } → hata fırlatmaz, ilk satırlar eldeki Ardahan programları (0 da olabilir), sonuç 24 satır; tani.ilkNEklenen ≤ 10.
  4. mock-yolu: AI_API_KEY boş + NODE_ENV !== "production" ile mockAktif() → senaryo 1 params; ilk 5 İstanbul.

Çıkış 0/1. Sahte ucun "kasten öncelikliyi atlaması" test koduna yorumla yazılır (gerçek model davranışını ölçmez).

B-3 Kabul kriterleri

  • pnpm tsx --conditions=react-server scripts/eval/revizyon-denetim.ts → 4/4, çıkış 0.
  • pnpm tsx --conditions=react-server scripts/eval/akis-denetim.ts hâlâ geçer (B dokunmaz, regresyon kontrolü).
  • typegen + tsc + lint temiz. eval:robot çağrı imzası değişmez (raporUretAyrintili dört parametre).

B-4 Commit planı

  1. fix(revizyon): "ilk N" ölçütü seçimde kodla garanti ediliyor; mock yolu da kuralı uyguluyor — revizyon-kurali.ts, rapor.ts.
  2. test(revizyon): mekanik ilk-N denetimi (sahte model önceliği atlar) — revizyon-denetim.ts.

B-5 Uzman

  • yapay-zeka-muhendisi: pnpm eval:sohbet'e "ilk 5'i İstanbul'a göre güncelle" / "ilk 5'i X şehrine göre güncelle" cümleleriyle araç argümanı denetimi ekler (ilkTercihler.adet === 5 && iller == ["İstanbul"]); model iller.ekle ile yetiniyorsa danisman-prompt.ts:85-89 örnekleri genişletilir (bu A'nın dosyası — bulgu rapora, değişiklik sonraki tura).

Paket C — Program varyant etiketi (madde 2, 3)

C-0 Kök neden (doğrulandı, veriden)

  • data/yokatlas.db: (universite, isim) ikilisi 4.225 grupta tekrar ediyor (SELECT universite, isim FROM programs GROUP BY 1,2 HAVING COUNT(*)>1). Örnek BAŞKENT ÜNİVERSİTESİ / Tıp: 6 satır (id 200610657, 200610666, 200611488, 200690357, 200690358, 200690359), isim hepsinde "Tıp"; fark program_ozellik'te: burs_orani ∈ {Ücretli, %50 İndirimli, Burslu} × ogrenim_dili ∈ {Türkçe, İngilizce}. İÖ/UOLP'de de aynı (Marmara Hukuk: 107210332 "Hukuk" ve 107200444 "Hukuk (UOLP-…)(%25 İndirimli)" — UOLP parantezi isimde kalmış, burs/dil kalmamış).
  • program_ozellik kapsamı tam: 21.493 programın 0'ı eksik.
  • Hiçbir liste varyantı göstermiyor: rapor-listesi.tsx:242-262, program-tablosu.tsx:352-367, liste-cekmecesi.tsx:438-455, universite-program-tablosu.tsx, bolum-program-listesi.tsx (grep: burs|ozellik → 0). Danışman/liste modelinin havuz JSON'u da varyantı görmüyor (rapor-havuzu.ts:427-452).
  • Birebir aynı kayıt (aynı id) tekrarı yok: secimiOnar tekrarları atıyor (rapor.ts:233-236). "Tekerrür" algısı yalnız etiket eksikliğinden.

C-1 Tasarım — tek kaynak Program.varyant

src/lib/program-varyant.ts (Y, saf):

export type VaryantGirdisi = { bursOrani: string | null; ogrenimDili: string | null; ogrenimTuru: string | null };
/** Satır altı etiketi: "Burslu · İngilizce", "%50 İndirimli", "İkinci Öğretim", "Ücretli · Uzaktan"; Türkçe + Örgün + burs bilgisi yoksa null. */
export function varyantEtiketi(g: VaryantGirdisi): string | null;

Sıra: burs (varsa, olduğu gibi: "Burslu" / "%50 İndirimli" / "Ücretli"), dil (yalnız "Türkçe" değilse), öğretim türü (yalnız "Örgün Öğretim" değilse; metin DB'deki hâliyle: "İkinci Öğretim", "Açıköğretim", "Uzaktan Öğretim"). Ayraç " · ".

src/types/yokatlas.ts: Program'a varyant: string | null (zorunlu alan; ProgramListeSatiri Omit olduğu için otomatik taşır; ManuelTercih ayrı tip).

src/lib/db.ts: SELECT_COLS (:40-42) üç ilişkili alt sorgu kazanır: (SELECT burs_orani FROM program_ozellik o WHERE o.id = programs.id) AS bursOrani, aynı şekilde ogrenimDili, ogrenimTuru. Tüm SELECT ${SELECT_COLS} FROM programs kullanımları takma adsız (db.ts:224,322, katalog.ts:199,519, araclar.ts:945,957) → programs.id çözümlenir. ProgramHamSatiri üç alanı ekler; programSatiri (:56) varyant: varyantEtiketi(row) üretir. programs_arsiv sorguları (ARSIV_SELECT) dokunulmaz (arşivde ozellik yok; kapanan programlar için varyant null kalır). Tablo yokluğu: ozellikTablosuVarMi koruması alt sorgu için gerekmez — data/yokatlas.db depoda ve Docker seed'inde (Dockerfile:28,75) tabloyla geliyor; yazılımcı bunu commit mesajına yazar.

Model ve danışman: rapor-havuzu.ts:427 havuzuKompaktJson satırına ...(p.varyant ? { varyant: p.varyant } : {}) eklenir (model aynı programın burslu/ücretli varyantlarını ayırt eder; gerekçede "burslu" yazabilir — metindekiSayilar(p.isim, …) sayı denetimi "%50" için rapor.ts:779 listesine p.varyant eklenmesi gerekir; bu B'nin dosyası → C raporuna not, entegrasyonda orkestratör tek satır ekler: ...metindekiSayilar(p.isim, p.universite, p.varyant ?? "")).

UI (satır altı): liste-satiri.ts'e SATIR_VARYANT = "shrink-0 rounded-full bg-slate-100 px-2.5 py-0.5 text-xs font-semibold text-slate-700" eklenmez — SATIR_CIP zaten bu (§4.15 tür çipi). Varyant SATIR_META satırında TurCipi'nin hemen ardından SATIR_CIP sınıfıyla bir çip olarak gösterilir (rapor-listesi.tsx:264 sonrası, program-tablosu.tsx aynı yer, universite-program-tablosu.tsx, bolum-program-listesi.tsx). İnteraktif değil, SectionEyebrow kuralı dışında (§4.15 çip). liste-cekmecesi.tsx:445-455 ikinci satırda üniversite adından sonra · {tercih.varyant} (text-slate-500). Null ise hiçbir şey çizilmez.

Manuel liste: liste-dogrula.ts:21-33 ManuelTercih.varyant?: string | null (opsiyonel: eski localStorage kayıtları geçer; dogrula string ise alır); use-tercih-profili.ts programEkleIste kopyalarken varyant taşır; program-ekle-butonu.tsx:22-31 ProgramOzet pick'ine "varyant" eklenir; liste-queries.ts:15-29 programOzetle varyant döner.

Kayıtlı rapor (/listem): rapor-queries.ts kullanicininRaporu (:35) sonucu dönmeden önce programOzellikleriGetir(Object.keys(result.programlar)) ile result.programlar[id].varyant = varyantEtiketi(...) yazılır (24 id, tek sorgu; eski kayıtlar da kazanır). RaporSonuc.programlar[id] tipine varyant?: string | null eklenir (src/features/rapor/types/rapor.ts — C'ye ait, B dokunmaz). rapor-listesi.tsx p.varyant okur. listeOzetiCikar (rapor.ts, B) bu turda varyantı modele yazmaz — backlog.

Tekilleştirme kuralı ([KARAR]): birebir aynı kayıt tekrarı yok; varyantlar ayrı ÖSYM kodu ve ayrı taban sırasıdır, listede ikisi de meşru. Ek kural eklenmez. Model varyantı görünce zaten "Başkent Tıp burslu + Başkent Tıp %50" seçimini gerekçelendirebilir. Üçten fazla varyantın aynı listeye girmesi sorun olursa secimiOnar'a dilim içi üst sınır (B'nin dosyası) sonraki tur.

C-2 Test — scripts/eval/varyant-denetim.ts (Y)

pnpm tsx scripts/eval/varyant-denetim.ts (react-server koşulu gerekmez; db.ts ve program-varyant.ts saf/sqlite):

  1. varyantEtiketi birim: {Burslu, İngilizce, Örgün} → "Burslu · İngilizce"; {null, Türkçe, Örgün} → null; {Ücretli, Türkçe, İkinci Öğretim} → "Ücretli · İkinci Öğretim"; {%50 İndirimli, null, null} → "%50 İndirimli".
  2. programGetir("200610657")…("200690359") 6 Başkent Tıp kaydının varyant değerleri birbirinden farklı ve hiçbiri null.
  3. dilimAra(100000, "say", "dengeli", { limit: 50 }) sonucunda her satırda varyant alanı tanımlı (string ya da null) — SELECT_COLS'un bütün yolu kapsadığının kanıtı.
  4. Devlet/Türkçe/örgün bir program (ör. 107210332 Marmara Hukuk) → varyant === null.

C-3 Kabul kriterleri

  • Test 4/4, çıkış 0; typegen + tsc + lint temiz; pnpm tasarim:denetim sıfır hata (yeni çip SATIR_CIP sınıf dizisiyle birebir).
  • grep -n "varyant" src/features/rapor/components/rapor-listesi.tsx src/features/liste/components/program-tablosu.tsx src/features/liste/components/liste-cekmecesi.tsx her dosyada ≥1.
  • sqlite3 data/yokatlas.db dosyası değişmez (git status temiz; test salt okur).

C-4 Commit planı

  1. feat(veri): Program.varyant — burs/dil/öğretim türü etiketi tek kaynaktan (program_ozellik) — types, db.ts, program-varyant.ts, rapor-havuzu.ts, varyant-denetim.ts.
  2. feat(liste): varyant çipi — sonuç tablosu, kendi listem çekmecesi, katalog tabloları — liste-queries, liste-dogrula, use-tercih-profili, program-ekle-butonu, program-tablosu, liste-cekmecesi, universite-program-tablosu, bolum-program-listesi.
  3. feat(listem): kayıtlı raporun satırlarına varyant (okuma anında zenginleştirme) — rapor-queries.ts, types/rapor.ts, rapor-listesi.tsx.

C-5 Uzman

  • veri-muhendisi: scripts/veri-kalite.ts'e "aynı (universite, isim, fakulte) grubunda varyant etiketi tekrar eden satır" denetimi (bu turda değil; backlog). UOLP/ÇAP parantezlerinin isimde kalmasının (bolumBazAdi) varyantla çakışıp çakışmadığını gözden geçirir.
  • tasarimci: çipin satırdaki yeri ve 375 px'te sarma (DESIGN §4.15 "ad sarar, çipler altına iner").

Paket D1 — Sohbet UI: shimmer, logo varyantı, soru geçişi (madde 5, 10)

D1-0 Kök neden (doğrulandı)

  • Shimmer yok: soru_sor aracı HAPSIZ_ARACLAR'da (arac-pilleri.tsx:48) → hap çizilmez; form yalnız output-available olunca gelir (sohbet-client.tsx:112-119). Sorular üretilirken (tool-soru_sor parçası input-streaming/input-available) metin boş olduğundan CevapIskeleti (gri üç çizgi, :316-324) görünür; TextShimmerLoader (loader.tsx:322-350) var ama kullanılmıyor.
  • Logo: site header AnimasyonluMark sadeceHover = varsayılan varyant rota (site-header.tsx:26, animasyonlu-mark.tsx:108,172). Sohbet avatarı araç çalışırken zincir (sohbet-client.tsx:305-307). Bilal'in istediği: header'dakiyle aynı → rota.
  • Soru geçişi: SohbetAnketi (sohbet-anketi.tsx:85-142) shadcn Questionnaire ilkeli; Item aktif değilken hidden + inert alır (node_modules @shadcn/react/dist/questionnaire/index.js, Je() → itemProps.hidden: !_), geçiş animasyonu yok. Sihirbaz (sihirbaz-adimlar.tsx:252-282) TransitionPanel (src/components/core/transition-panel.tsx) + yön (yon), x: ±100%, opacity, height (useMeasure), yay stiffness 300 / damping 30, hareketi azalt'ta çapraz solma.

D1-1 Değişiklik

Madde 5 (sohbet-client.tsx):

  • DanismanAvatar: varyant={canli && aracta ? "rota" : "nabiz"}; yorum güncellenir ("header logosuyla aynı rota koreografisi").
  • Mesaj gövdesinde (:1004-1017): const soruHazirlaniyor = m.parts.some(p => p.type === "tool-soru_sor" && "state" in p && p.state !== "output-available" && p.state !== "output-error"). metin boş ve soruHazirlaniyor ise CevapIskeleti yerine <TextShimmerLoader text="Soruları hazırlıyorum…" size="sm" className="px-0.5" /> (role="status" sarmalı, aria-live="polite"). Metin varsa shimmer satırı metnin altında, formun yerinde durur; form gelince kalkar.
  • TextShimmerLoader gradyanı --muted-foreground/--foreground token'ı kullanıyor (loader.tsx:340). pnpm tasarim:denetim ya da görsel dil bunu reddederse yalnız o zaman loader.tsx'te gradyan var(--color-slate-400) → var(--color-slate-900) → var(--color-slate-400) olur (karanlık mod token'dan döner; dark: yok).

Madde 10:

  • src/lib/adim-gecisi.ts (Y, saf, "use client" yok): sihirbazdaki varyant ve geçiş sabitleri buraya taşınır:
    export function adimGecisiVaryantlari(opts: { hareketiAzalt: boolean; yukseklik: number | "auto" }): Variants; // enter/center/exit, custom = yön
    export function adimGecisi(hareketiAzalt: boolean): Transition; // x/height spring 300/30, opacity 0.2; azaltılmışta 0.12 solma
    
    sihirbaz-adimlar.tsx:254-282 bu iki fonksiyonu kullanır; davranış birebir (diff yalnız taşınma).
  • sohbet-anketi.tsx: Questionnaire kontrollü olur: item={aktif} + onItemChange={(ad) => { setYon(indeks(ad) > indeks(aktif) ? 1 : -1); setAktif(ad); }} (ilkel item/onItemChange prop'larını destekliyor, index.d.ts:23-27). Her QuestionnaireItem içeriği (Title/Description/Choices/Error) motion.div'e sarılır: variants={adimGecisiVaryantlari(...)}, custom={yon}, initial={false}, animate={s.ad === aktif ? "center" : "enter"}, transition={adimGecisi(hareketiAzalt)}; sarmal kap relative overflow-hidden. Yükseklik: aktif içerik useMeasure ile ölçülür, sihirbazdaki gibi height varyanta geçer (ilk ölçümde "auto").
  • Çıkış animasyonu sınırı: ilkel pasif fieldset'i anında hidden yaptığı için çıkış kayması görünmez. Yazılımcı önce QuestionnaireItem'a hidden={false} ve inert={false} geçip görünürlüğü data-active ile kendisi yönetmeyi dener — bunun işe yaraması b (mergeProps) fonksiyonunda kullanıcı prop'unun kazanmasına bağlı: node_modules/.pnpm/@shadcn+react@0.3.1_*/node_modules/@shadcn/react/dist/chunk-HBS6WEDP.js içindeki b okunur. Kazanıyorsa pasif item'lar TransitionPanel mantığıyla (exit → position:absolute) tam çift yönlü geçiş alır; kazanmıyorsa yalnız giriş animasyonu kalır ve rapora "koddan çıkarım: çıkış görünmüyor" yazılır. Her iki durumda form gönderimi (FormData, :70-78) bozulmamalı: pasif item'ların input'ları DOM'da kalır.
  • Ortak bileşen kararı ([KARAR]): TransitionPanel taşınmaz; sihirbaz paneli çocukları değiştirirken anket ilkelin kendi item'larını yönetiyor. Ortak olan sabitler ve varyant üreticisi (adim-gecisi.ts); bu "birebir aynı geçiş"i sağlar, bileşeni zorlamaz.

D1-2 Kabul kriterleri

  • grep -n '"zincir"' src/features/rapor/components/sohbet-client.tsx → 0; grep -n "TextShimmerLoader" src/features/rapor/components/sohbet-client.tsx ≥ 1.
  • grep -n "stiffness: 300" src/features/sihirbaz/components/sihirbaz-adimlar.tsx src/features/rapor/components/sohbet-anketi.tsx → 0 (sabit tek kaynakta: src/lib/adim-gecisi.ts'te 1).
  • sohbet-anketi.tsx item= ve onItemChange= içerir; sihirbaz-adimlar.tsx adimGecisiVaryantlari import eder.
  • typegen + tsc + lint temiz; pnpm tasarim:denetim sıfır hata.
  • Rapor: çıkış animasyonu elde edildi mi (mergeProps bulgusu, dosya:satır).

D1-3 Commit planı

  1. feat(sohbet): soru formu hazırlanırken shimmer; araçta avatar header logosuyla aynı rota koreografisi.
  2. refactor(hareket): sihirbaz adım geçişi sabitleri lib/adim-gecisi.ts'e; soru formu aynı geçişle.

D1-4 Uzman

  • tasarimci: shimmer metni ("Soruları hazırlıyorum…") ve DESIGN §4.20/§4.33'e eklenecek iki cümle (D1 raporuna yazılır; DESIGN.md D2'nin). Dev sunucusu açabilir (AGENTS.md istisnası) — 375/768'de soru geçişinde yükseklik sıçraması ve overflow-hidden'ın odak halkasını kırpması kontrol.

Paket D2 — Program ayrıntı çekmecesi (madde 8, 9)

D2-0 Kök neden (doğrulandı) ve itiraz

  • Bilgi paneli program-ayrinti.tsx (ProgramAyrintiButonu, Info ikonu :140-170); liste-cekmecesi.tsx'te böyle bir panel yok. "Yıllara göre" = YillaraGore (:284-336), AyrintiIcerik'te :221.
  • Açılış bug'ı: AltSayfaIcerik duraklar={[0.6, 0.92]} ilkDurak={0} (:171-175). (a) İlk durak 0,6'da sayfa "kısık"tır → AltSayfaGovde overflow-hidden touch-none (alt-sayfa.tsx:631), kaydırılamaz; kullanıcı dokunup kaydırmaya çalışır, "takılmış" görür. (b) Panel iskeletle (6×h-10 ≈ 280 px) açılır, veri gelince (yerel sqlite, ~100 ms; genelde açılış yayı 0,4 s bitmeden) içerik ~3-4 ekran olur → ResizeObserver (alt-sayfa.tsx:303-327) boyu günceller, ofset(d) = boy − durakPx(d) sıçrar; yay sürüyorsa git(d) yeniden hedefler, bitmişse y.jump → gözle görülür zıplama. "Çok uzun diye" doğru sezgi; asıl neden açılırken boyun değişmesi ve çift durak.
  • Madde 9'daki "logo sonra içerik": akademik-ayrinti.tsx:120-270 AcilisSahnesi — üç renkli kare logo olarak belirir, yerine büyür, içerik data-sonra bloklarıyla bulanıktan netleşir. Bilal aynısını program ayrıntısında istiyor. Sahne [data-kare] kartlara ihtiyaç duyuyor; program çekmecesi kartsız (§4.19) → sahneye "kartsız" mod gerekir.

D2-1 Değişiklik

src/components/ui/acilis-sahnesi.tsx (Y): AcilisSahnesi akademik-ayrinti.tsx'ten buraya taşınır (sabitler LOGO, KARE_*, ICERIK_BASI, GUCLU_CIKIS, YERLESME dâhil) ve hazir?: boolean (varsayılan true; false iken sahne oynamaz, true olunca bir kez oynar — veri bekleyen çekmece için) ve kartsız mod kazanır: [data-kare] yoksa ve [data-logo] varsa, data-logo kabındaki üç kare (data-kare yerine data-logo-kare="sol|sag-ust|sag-alt", mutlak konumlu, LOGO renk/konum) BASLANGIC + slot·SLOT_ADIMI gecikmeyle belirir (GUCLU_CIKIS), KARE_SURE·0.52'ye kadar durur, sonra scale 0.6 + opacity 0 ile söner (toplam ≈ 1 s), [data-sonra] blokları netlestir ile ICERIK_BASI'ndan itibaren 0.08 s arayla gelir. Hareketi azalt: yalnız 0,25 s solma (mevcut dal). akademik-ayrinti.tsx yeni modülü import eder; davranışı değişmez.

program-ayrinti.tsx:

  • YillaraGore bileşeni ve :221 çağrısı silinir (API gecmis alanı kalır; backlog). AyrintiIcerik içindeki bölümler data-sonra alır (Kunye ul, Kontenjan türleri bloğu, "Son yerleşenin netleri" başlık+not+tablo tek sarmalda, Kosullar details, Istihdam, UniversiteOzeti, kaynak paragrafı). Başlığın altında <div data-logo className="relative mx-auto h-16 w-16" aria-hidden> (üç data-logo-kare span) — yalnız durum === "hazir" iken render; AcilisSahnesi hazir={durum === "hazir"} sarmalı :218 civarı.
  • Çekmece: duraklar ve ilkDurak prop'ları kaldırılır (varsayılan tek durak 0,92; AkademikAyrinti ile aynı). Yükleme iskeleti min-h-[70dvh] sarmalda (açılışta boy sabit; veri gelince ResizeObserver sayfayı yalnız büyütür, konum sıçramaz — alt-sayfa.tsx:11-12 "içerik daha kısaysa içeriğin boyunda kalır" kuralı korunur, iskelet kısa değildir).
  • Veri geldiğinde içerik sahne bitene kadar görünmez (sahne opacity:0 ile başlatıyor) — "logo sonra içerik" hissi buradan.

docs/tasarim/DESIGN.md §4.19: "sayfa ekranın %60'ında açılır … tam boya çıkar" cümlesi (program-ayrinti.tsx:171-173 yorumu ve §4.10:372 atfı) → tek durak; "Yıllara göre" maddesi listeden çıkar; açılış koreografisi "AcilisSahnesi kartsız mod (logo → içerik), src/components/ui/acilis-sahnesi.tsx" olarak bir cümleyle eklenir. §4.10:372'deki "(§4.19)" atfı güncellenir.

D2-2 Kabul kriterleri

  • grep -n "YillaraGore\|Yıllara göre" src/features/katalog/components/program-ayrinti.tsx → 0.
  • grep -n "duraklar=\|ilkDurak=" src/features/katalog/components/program-ayrinti.tsx → 0.
  • grep -n "function AcilisSahnesi" src/features/katalog/components/akademik-ayrinti.tsx → 0; src/components/ui/acilis-sahnesi.tsx export eder; akademik-ayrinti.tsx import eder.
  • grep -n "data-sonra" src/features/katalog/components/program-ayrinti.tsx ≥ 5; data-logo ≥ 1.
  • typegen + tsc + lint temiz; pnpm tasarim:denetim sıfır hata (kapatma çarpısı sınıf seti değişmez).
  • Tarayıcı doğrulaması yazılımcıda yok; tasarimci D2-4.

D2-3 Commit planı

  1. refactor(ui): AcilisSahnesi ortak bileşen (components/ui/acilis-sahnesi.tsx), kartsız logo→içerik modu.
  2. fix(program-ayrinti): tek durak + sabit iskelet boyu (açılış sıçraması), "Yıllara göre" kaldırıldı, logo→içerik açılışı; DESIGN §4.19.

D2-4 Uzman

  • tasarimci (merge öncesi, dev sunucusu açabilir): 375 ve 768 px'te çekmece açılışında sıçrama yok mu; sahne süresi (≈1 s) uzun mu; prefers-reduced-motion. Gerekçe ve ölçümleri raporuna yazar, sunucuyu kapatır.

Madde 7 — Rybbit tracking API: iş yok

src/lib/analitik-sunucu.ts:15-17,33-52 RYBBIT_HOST/SITE_ID/API_KEY varsa POST /api/track atıyor; bayrak/TODO yok. İstemci tarafı (analitik.ts) window.rybbit script'i, tracking API ile ilgisiz. Env adları .env.local'da mevcut (değer okunmadı), CI .gitea/workflows/ci.yaml:41-43 build'e geçiriyor, prod docker-compose.yml .env.production dosyasından alıyor. API sunucuda yeni açıldıysa odeme_tamamlandi ve giris_basarili (:19) olayları bugünden itibaren düşmeye başlamalı — doğrulama analitik ajanının işi (backlog).


İtirazlar

  1. Madde 8-9 dosya adı. "Liste çekmecesi" değil program-ayrinti.tsx; liste-cekmecesi.tsx'e yalnız C (varyant satırı) dokunur. Orkestratörün "sohbet-client.tsx ve liste-cekmecesi.tsx'e çakışmadan bölün" kısıtı bu sayede kendiliğinden sağlanıyor.
  2. Madde 8 çözümü "Yıllara göre"yi kaldırmakla bitmez. Bug tablo uzunluğundan değil çift durak + açılışta boy değişiminden; tablo kalksa da panel ~3 ekran. D2 üçünü birlikte yapar.
  3. Madde 1 için "cikti-denetimi.ts'e ekle" yetmez. Oradaki denetimler cevap bittikten sonra çalışır ve ekrandaki hapı geri alamaz; akış içi tampon gerekir (A-1). cikti-denetimi.ts dokunulmaz.
  4. Madde 2-3 için "rapor-havuzu/rapor.ts'te tekilleştir" yanlış yön. Kayıtlar birebir aynı değil; sorun etikettir ve veri katmanında çözülür (Program.varyant), böylece /sonuc, katalog, kendi listem ve rapor aynı anda düzelir.
  5. Madde 6'da DB göçü gerekmez. revision_count sayaç olarak kalır; kaldırmak iki göç ve geri dönüş maliyeti getirir, hiçbir kazanç yok.
  6. Madde 4 "test iste" — modelin kuralı çıkarıp çıkarmadığı mekanik testle ölçülemez; B'nin testi kod yolunu kilitler, model tarafı eval:sohbet'e (yapay-zeka-muhendisi) gider.

Merge sırası ve bağımlılıklar

A, B, C, D1, D2 birbirinden bağımsız; önerilen sıra B → C → A → D2 → D1 (küçükten büyüğe, UI en sona). Entegrasyonda orkestratör: (1) sohbet-client.tsx prop yeniden adlandırması (çakışma kararı 1); (2) rapor.ts:779 metindekiSayilar(... p.varyant) tek satırı (C-1 notu); (3) pnpm build; (4) scripts/eval/{akis,baglanti,revizyon,varyant}-denetim.ts dördü; (5) pnpm eval:robot --profil … isteğe bağlı (anahtar ister, Bilal).

Backlog önerileri (kapsam dışı)

  1. /universite/… ve /bolum/… sohbet bağlantılarının da gerçek slug'larla doğrulanması (katalog haritaları katalog.ts); A'nın tamponu genişletilebilir.
  2. listeOzetiCikar ve program_ara çıktısına varyant (danışman "burslu mu?" sorusuna listeden cevap versin).
  3. veri-kalite.ts: aynı (üniversite, isim, fakülte) grubunda varyant etiketi tekrarlayan satır uyarısı; UOLP/ÇAP parantezleri.
  4. api/program-ayrinti gecmis alanı artık kullanılmıyor → programGecmisiGetir sorgusu kaldırılabilir (küçük tasarruf).
  5. eval:sohbet'e "rehberde olmayan konu" ve "ilk 5'i X'e göre güncelle" senaryoları (yapay-zeka-muhendisi).
  6. analitik: tracking API açıldıktan sonra odeme_tamamlandi / giris_basarili olaylarının Rybbit'te göründüğünün teyidi; önceki dönem için huni boşluğu notu.
  7. sohbet-client.tsx 1.105 satır; soru formu/revizyon parçası ayrı dosyaya (D1 sonrası).
  8. Paketsiz ama kredili adayın liste_revize kullanıp kullanamayacağı — bugün paket şart (liste-revizyon.ts:71-77); Bilal'in "kredisi olduğu sürece" sözü bunu kapsıyorsa ürün kararı.

Bilal'den istenen

  1. Madde 6: paketsiz-kredili adayda revizyon açılsın mı, yoksa paket şart kalsın mı? (tek cümle, 1 dk) — şartname "paket şart kalır" varsayıyor.
  2. Prod .env.production içinde RYBBIT_HOST / RYBBIT_SITE_ID / RYBBIT_API_KEY tanımlı mı (yalnız varlık; madde 7'nin sunucu olayları buna bağlı) — 1 dk.
  3. D2 sahnesi için tercih: logo kareleri sönerek mi, yoksa başlığın yanına küçülerek mi kaybolsun? (varsayılan: sönerek) — 1 dk.