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>
This commit is contained in:
369
docs/ekip-raporlari/2026-10-07/cto-geri-bildirim-sartname.md
Normal file
369
docs/ekip-raporlari/2026-10-07/cto-geri-bildirim-sartname.md
Normal file
@@ -0,0 +1,369 @@
|
||||
# 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):
|
||||
|
||||
```ts
|
||||
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` `oncelikli`leri 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`/`oncelikli`yi 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:
|
||||
|
||||
```ts
|
||||
/**
|
||||
* 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 `dilim`inde, uymayan satırlar arasından `uyum`u 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`; `tani`ye `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):**
|
||||
```ts
|
||||
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 `isim`de 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:
|
||||
```ts
|
||||
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`) `boy`u 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.
|
||||
Reference in New Issue
Block a user