Çok ajanlı gece vardiyasının çıktıları: analitik (Rybbit) bulguları, SEO/GEO konu planı, marka stratejisi, canlı mail testi, CEO kararları (2 tur), CTO şartnamesi, yazar/yazılımcı raporları, bağımsız kod denetimi ve sabah özeti (yazılı + sesli). Kod değişiklikleri gece/* dallarında. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
33 KiB
06 — CTO planı — 2026-09-21 gece vardiyası (Tur 3)
Hazırlayan: CTO ajanı · Girdi: 05-ceo-kararlari.md §3 + 01/02/03/04 · Kod değiştirilmedi; yalnızca bu dosya yazıldı. Tüm dosya:satır atıfları main HEAD 56925e1 üzerinde okunarak doğrulandı. (Tek yan etki: p4-eposta worktree'sinde doğrulama için next typegen + tsc + lint koşuldu; yalnızca gitignore'lu .next/ ve next-env.d.ts oluştu, git status temiz.)
1. Özet (5 madde)
- Dört paket de uygulanabilir; dosya kümeleri ayrık. CEO'nun listesindeki üç gizli çakışma çözüldü: (a) P3'ün "tek sabit"i
seo.tsx'e (P1'in) giremez → yeni dosya; (b) P2'ninpaket_goruntulendiolayı/paketsayfasına (P3'ün) dokunmadan yenisrc/app/paket/layout.tsxile bağlanır; (c) P2src/app/layout.tsx'e dokunmaz, erken olay kaybıanalitik.tsiçinde kuyrukla çözülür. - P2 kök nedeni doğrulandı ve bir tane daha bulundu. Hook
useRef+[ad, anahtarEki]deps'li effect kullanıyor; eleman ilk commit'te yoksa (banner) ya da sökülüp yeniden takılıyorsa (tadımlık) gözlem hiç kurulmuyor → callback ref. Ek bulgu: Rybbit script'iafterInteractive;olay()script yokken sessizce düşürüyor → mount anında atılacak yeni olaylar (sonuc_/paket_goruntulendi) sert yüklemede kaybolur → küçük kuyruk şart. - P3'te CEO'nun kapsamı eksik (CTO itirazı #1). "İhtimal dili" yalnız
riskNotuUret'te değil: tadımlık havuzununriskNotu'su LLM üretimi ve DB'de kayıtlı, prompt'u açıkça "yüksek/orta/düşük ihtimal dili kullan" diyor (scripts/tadimlik-uret.ts:70-71); danışman sohbetinin sistem prompt'u da aynı (src/app/api/soru/route.ts:48). Girişsiz ziyaretçinin /sonuc'ta gördüğü tek yapay zekâ satırı eski dille kalırdı. Çözüm: risk cümlesi tek saf fonksiyona (riskNotuMetni) alınır, tadımlıkta okuma anında deterministik üretilir — prod'da LLM yeniden koşusu gerekmez. - P4'te soğuma kaynağı değiştirildi (CTO itirazı #2). better-auth 1.6.23'te
verification.identifier= token (e-posta değil); e-posta yalnızvalueJSON'unda ve satırsendMagicLinkçağrılmadan önce yazılıyor → tablo tabanlı soğuma kendi satırına takılır ve kayan pencere üretir. Yerine süreç içiMap(tek konteyner; şema/DB yazımı yok). Ayrıca prod guard modül yüklenirken değil gönderim anında atmalı; aksi hâldenext build(NODE_ENV=production, env'siz) kırılır. - Doğrulama tuzağı: taze worktree'de
pnpm exec tsc --noEmitbaseline'da kırık (PagePropsglobal tipi.next/types'tan geliyor). Öncepnpm exec next typegenkoşulmalı — denendi: typegen → tsc 0 hata,pnpm linttemiz. Tampnpm build'i yalnız P1 koşar (HTML kabul kriteri); dört paralel build ~8 GB RAM ister, diğerleri tsc+lint ile kapanır, build'i Tur 4'te orkestratör sırayla alır.
Dosya sahipliği (ayrık; listede olmayan dosyaya dokunulmaz)
| Paket | Dosyalar (Y = yeni) |
|---|---|
P1 gece/p1-rehber-altyapi |
src/features/rehber/components/el-cizimi-sema.tsx · src/features/rehber/components/rehber-makale.tsx · src/lib/rehber.ts · src/lib/tarih.ts Y · src/app/sitemap.ts · src/lib/seo.tsx (yalnız articleJsonLd) |
P2 gece/p2-olcum |
src/components/use-gorunum-olayi.ts · src/lib/analitik.ts · src/features/sihirbaz/components/{hero-form,cta-sira-form,sihirbaz-cagri-karti}.tsx · src/features/liste/components/tercih-profili-kapisi.tsx · src/features/kullanici/components/giris-form.tsx · src/features/rapor/components/{liste-uretici,listem-govde}.tsx · src/app/paket/layout.tsx Y · src/features/odeme/components/paket-gorunum-olayi.tsx Y · src/components/rybbit-identify.tsx · src/features/kullanici/components/user-nav.tsx · (salt-okunur doğrulama, değişiklik beklenmiyor: kaydet-banner.tsx, tadimlik-satiri.tsx) |
P3 gece/p3-vaat-dili |
src/app/page.tsx · src/app/paket/page.tsx · src/features/odeme/components/paket-satinal.tsx · src/lib/ai/rapor.ts (yalnız riskNotuUret) · src/app/layout.tsx (yalnız description) · src/lib/veri-kapsami.ts Y · CTO eki: src/lib/risk.ts · src/lib/tadimlik-havuzu.ts · scripts/tadimlik-uret.ts · src/app/api/soru/route.ts (yalnız 48. satır) · src/components/arama-funnel-karti.tsx (yalnız 38. satır) · src/features/katalog/components/veri-kaynagi.tsx (yalnız export) |
P4 gece/p4-eposta |
src/lib/eposta-gonder.ts · src/lib/eposta.ts · src/lib/auth.ts · src/lib/analitik-sunucu.ts · isteğe bağlı ayrı commit: .gitea/workflows/ci.yaml |
Çakışma kararları: seo.tsx → P1 (P3 oraya sabit eklemez). src/app/paket/page.tsx + paket-satinal.tsx → P3 (P2 yeni layout dosyası kullanır). src/app/layout.tsx → P3 (P2 script'e dokunmaz). tadimlik-satiri.tsx → P2, tadimlik-havuzu.ts → P3. analitik.ts → P2, analitik-sunucu.ts → P4. giris-form.tsx → P2, auth.ts → P4.
Tüm yazılımcılar için ortak kurallar
- Yalnız kendi worktree + dalında lokal commit; push/merge yok.
git add -A/git add .yasak — yalnız sahip olunan dosyalar yol vererek eklenir (builddata/*.db-shmgibi izlenen dosyaları kirletebilir;data/app.db,docker-compose.yml,docs/geo/geo-raporu.mdasla). - Doğrulama sırası (worktree kökünde):
pnpm exec next typegen && pnpm exec tsc --noEmit && pnpm lint. Dev sunucusu/tarayıcı yok (AGENTS.md). - Next'e özgü API'den önce
node_modules/next/dist/docs/(P1:01-app/03-api-reference/03-file-conventions/01-metadata/sitemap.md,04-functions/generate-metadata.md; P2:03-file-conventions/layout.md). Doğrulananlar:openGraph.modifiedTimetipte var (opengraph-types.d.ts:28); sitemaplastModified: string | Date;next typegen(06-cli/next.md:175). - Commit mesajı sonu:
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>. Rapor:07-yazilimci-raporlari.md'ye kendi başlığı.
2. P1 · Rehber altyapısı
Amaç: Şema metni JS'siz botlara görünsün; mermaid hatasında sonsuz iskelet bitsin; guncelleme tarihi uçtan uca aksın; frontmatter tırnağı ve tablo taşması düzelsin.
Kök neden (kanıt):
el-cizimi-sema.tsx:85-97:aria-labelyalnızsvgstate'i dolunca render ediliyor; SSR çıktısı =aria-hiddeniskelet + figcaption. Bileşen client ama SSR edilir → içine konan<details>sunucu HTML'ine girer.el-cizimi-sema.tsx:55-74:import("mermaid").then(async …)zincirindecatchyok;mermaid.renderatarsa (getBBox is not a function, 2 oturum — tarayıcıya özgü, kaynağı saptanamadı)svghiç dolmaz, iskelet kalır, promise yakalanmaz. Mermaid ayrıca#d<id>geçici div'inibody'de bırakabilir (mermaid.core.mjs:1248).rehber.ts:71-74:.replace(/^["']|["']$/g, "")iki ucu bağımsız siliyor. Etkilenen yalnız 2 dosya (merkezi-yerlestirme…md:3,taban-siralamalar…md:3); 24 dosyanın hiçbirinde tam tırnaklı değer yok.rehber.ts:20-26RehberOzet'teguncellemeyok;sitemap.ts:22/rehbergirdisindelastModifiedyok,:47y.tarih;seo.tsx:149-169dateModifiedyok;rehber-makale.tsx:32yalnızpublishedTime, gövdede tarih yok.rehber-makale.tsx:79:[&_table]:w-full, sarmalayıcı yok.
Değişiklikler:
src/lib/tarih.ts(yeni, saf,server-onlyyok):export function trTarih(iso: string): string—/^(\d{4})-(\d{2})-(\d{2})$/ile ayrıştır, sabitAYLAR = ["Ocak",…,"Aralık"]dizisinden"21 Eylül 2026"üret; biçim uymazsathrow.Date/toLocale*/saat dilimi kullanma.src/lib/rehber.ts:- Tırnak: değer uzunluğu ≥ 2, ilk ve son karakter aynı tırnak (
"ya da') ve aradaki metin o tırnağı içermiyorsa soy; aksi hâlde olduğu gibi bırak. RehberOzet'eguncelleme?: string.metaDogrula:tarihve (varsa)guncellemeYYYY-MM-DDdeğilse ya daguncelleme < tarihise build'i kıranError(mevcut hata mesajı kalıbıyla).oku()veozetOku()(cache'ten kırpma dalı dahil,:169-170) alanı taşır. Sıralamatarih'e göre kalır.RehberAramaKaydideğişmez.- Tablolar:
parcala()içindeki ikimarked.parseçıktısını tek yardımcıdan geçir:<table>→<div class="rehber-tablo" role="region" aria-label="Tablo" tabindex="0"><table>,</table>→</table></div>(marked 18<table>'ı özniteliksiz basar; globalmarked.usekullanma).:12yorumundaki "10 makale"yi düzelt.
- Tırnak: değer uzunluğu ≥ 2, ilk ve son karakter aynı tırnak (
src/lib/seo.tsx— yalnızarticleJsonLd:dateModified?: stringparametresi; çıktıdadateModified: opts.dateModified ?? opts.datePublished.src/features/rehber/components/rehber-makale.tsx: metadata'daopenGraph: { type: "article", publishedTime: yazi.tarih, modifiedTime: yazi.guncelleme ?? yazi.tarih };articleJsonLd'yedateModified;aciklamaparagrafının altına<p className="mt-3 text-xs text-slate-500">Yayın: <time dateTime={yazi.tarih}>{trTarih(yazi.tarih)}</time>+ (yalnızguncellemevarsa)· Güncelleme: <time …>— düz metin satırı, badge değil (SectionEyebrow kuralı tetiklenmez). Sınıf listesinde[&_table]:mt-4→[&_.rehber-tablo]:mt-4 [&_.rehber-tablo]:overflow-x-auto.src/app/sitemap.ts: rehber girdilerindelastModified: y.guncelleme ?? y.tarih;/rehbergirdisinistaticEntries'ten fonksiyon içine al,lastModified= tüm yazılarınguncelleme ?? tarihdeğerlerinin en büyüğü (ISO string karşılaştırması yeter).src/features/rehber/components/el-cizimi-sema.tsx:const [hata, setHata] = useState(false).cizdir()→import("mermaid").then(async …).catch(() => { document.getElementById("d" + semaId)?.remove(); if (!iptal) setHata(true); });mermaid.initialize'asuppressErrorRendering: true(11.16'da var,config.type.d.ts:237).- Render:
svgvarsa mevcutrole="img" aria-labeldiv'i (korunur); yoksa vehatadeğilse iskelet;hataise iskelet çizilmez. figcaption'dan sonra,<figure>içinde:<details open={hata || undefined} className="mt-2 text-xs text-slate-500"><summary className="cursor-pointer font-medium text-slate-600">Şemayı metin olarak oku</summary><p className="mt-2 text-sm leading-6 text-slate-600">{ariaLabel}</p></details>. Gizli metin (sr-only,display:none) yok.
Commit bölünmesi: (1) fix(rehber): frontmatter tırnak + tablo sarmalayıcı · (2) feat(rehber): guncelleme alanı → sitemap/JSON-LD/OG/görünür tarih · (3) fix(rehber): şema metni SSR'da + mermaid hata toparlama. Hepsi [KARAR].
Kabul kriterleri / doğrulama: typegen+tsc+lint; ardından yalnız bu pakette pnpm build, sonra (ana repodaki mevcut build'de baseline ölçüldü: ilk iki grep 0, description Üst sıraya… diye tırnaksız başlıyor — yani kriterler ayırt edici):
grep -c "Şemayı metin olarak oku" .next/server/app/rehber/tercih-listesi-nasil-yapilir.html≥ 1 ve aynı dosyadagrep -c "Tercih listesi dilim şeması"≥ 1;.next/server/app/meraklisina.html'degrep -c "Sihirbaz akış şeması"≥ 1.grep -o 'name="description" content="[^"]\{0,30\}' .next/server/app/rehber/merkezi-yerlestirme-nasil-calisir.html→"Üst sırayaile başlar (aynısıtaban-siralamalar-nasil-degisir).- Aynı HTML'de
rehber-tablosınıfı veYayın:+<time dateTime="2026-bulunur;.next/server/app/sitemap.xml.body'de/rehber</loc>girdisinin<lastmod>'u var. Geçici olarak bir yazıyaguncelleme: 2026-09-22ekleyipdateModified/modifiedTime/Güncelleme:çıktısını gör, commit'lemeden geri al (content/yazar dalının). - Kod incelemesi:
.catchvar, hata dalında iskelet yok,detailsaçık.
Kapsam dışı: kume/ilgili (T4), FAQPage, llms.txt, IndexNow, flowchart dışı mermaid, font, yazar alanı, content/rehber/* (yazar dalı), rehber-listesi.tsx.
Riskler / AGENTS.md: ElCizimiSema /meraklisina hunisiyle ortak → <details> orada da çıkar; metin kaynağı yine tek ariaLabel prop'u olduğundan şema senkron kuralına yeni yük binmez, huni-semasi.tsx'e dokunulmaz; Bilal görsel bakmalı. Ekran okuyucuda metin iki kez okunabilir (CEO kabul etti). metaDogrula sıkılaşması: 24 dosyanın hepsi YYYY-MM-DD (doğrulandı), yazar dalındaki yeni dosyalar bozuk tarihle gelirse build kırılır — istenen davranış.
3. P2 · Ölçüm onarımı
Amaç: Görünüm hook'u sonradan mount olan elemanda da çalışsın; 6 yeni olay; 429 mesajı; PII ayrı commit.
Kök neden (kanıt):
use-gorunum-olayi.ts:20,28-30,51:useRef+ deps[ad, anahtarEki].ref.currenteffect anındanullise erken dönüyor ve deps değişmediği için bir daha çalışmıyor.kaydet-banner.tsx:33,47,57:kapalibaşlangıçtatrue→ ilk rendernull; üstelik içerikViewportPortaliçinde (viewport-portal.tsx— ilk client render'da danull). Eleman hiçbir zaman ilk commit'te yok → olay 0.tadimlik-satiri.tsx:75-77,87,104: tip uymuyorsasetSatir(null)→<section>sökülür, fetch sonrası yeni DOM düğümü takılır; gözlemci eski (kopuk) düğümde kalır.- Çalışanlar (
listem-govde.tsx:139-148,sihirbaz-cagri-karti.tsx:232) eleman ilk commit'te var olduğu için etkilenmiyor.
- İkinci neden (raporlarda yok):
layout.tsx:129-136script'iafterInteractive;analitik.ts:52-58window.rybbityoksa sessiz no-op. Sert sayfa yüklemesinde mount effect'i script'ten önce koşar → olay kaybolur. Mevcut olaylar kullanıcı etkileşimiyle atıldığı için görünmedi; yeni "sayfa görüntülendi" olaylarını doğrudan vurur. - Uyarı:
tadimlik_goruntulendi= 0'ın tek nedeni hook olmayabilir — prodtadimlik_havuzuboşsa bileşen hiç render edilmez (tadimlik-bolumu.tsx:20, bellek notu "deploy sonrası prod'dapnpm tadimlik") ve bölüm uzun tablonun altında. Düzeltmeden sonra hâlâ 0 ise önce havuz kontrol edilir (Bilal).
Değişiklikler:
src/lib/analitik.ts:OlayAdi'na tam bu adlarla ekle:cta_form_goruntulendi,sira_form_hata,sonuc_goruntulendi,paket_goruntulendi,liste_uretim_hata,giris_hata.olay():window.rybbityoksa olayı modül içi kuyruğa (en çok 20) koy; 500 ms aralıkla en çok 20 deneme (10 sn) — script gelince sırayla boşalt, gelmezse kuyruğu at (reklam engelleyici). Yeniexport function olayBirKez(ad, ozellikler?, anahtarEki?): hook'takisessionStorageanahtar mantığının (kolaytercih.olay.<ad>[.<ek>]) aynısı — anahtar varsa çık, yoksa yaz +olay(); storage hatasında yine gönder.src/components/use-gorunum-olayi.ts: dönüş tipiRefCallback<T>;useCallback((el: T | null) => { … }, [ad, anahtarEki]).elyoksa /IntersectionObserveryoksa / anahtar zaten yazılıysa çık; yoksa gözlemci kur, kesişimdeio.disconnect()+olayBirKez. React 19 ref temizleme fonksiyonu döndür (return () => io.disconnect()).ozelliklerRefdeseni korunur. Çağıranların hepsi yalnızref={…}veriyor (.currentokuyan yok — doğrulandı) → 5 kullanım yerinde değişiklik gerekmez.cta-sira-form.tsx: yalnız profilsiz daldaki<section>'a (:162)ref={useGorunumOlayi<HTMLElement>("cta_form_goruntulendi")}(hook çağrısı koşulsuz, üstte). Özellik yok — Rybbit pathname'i zaten yazar.catchdalına (:110)olay("sira_form_hata", { kaynak: "cta" }).hero-form.tsx:87catch →{ kaynak: "hero" };tercih-profili-kapisi.tsxfacetYuklecatch →{ kaynak: duzenle ? "duzenle" : "kapi" }. Doğrulama hatası (geçersiz sayı) sayılmaz, yalnız istek hatası.sihirbaz-cagri-karti.tsxSihirbazBolumu: mount effect'indeolayBirKez("sonuc_goruntulendi", { girisli: girisliMi ? "evet" : "hayir", sira_kovasi: siraKovasi(sira) }, girisliMi ? "girisli" : "anonim")./paket:src/features/odeme/components/paket-gorunum-olayi.tsx(yeni, client,nulldöner) — effect'tenew URLSearchParams(window.location.search).get("kaynak"), allowlistkilit | kredi_bitti | nav, değilse"diger";olayBirKez("paket_goruntulendi", { kaynak }, kaynak).useSearchParamskullanma (cacheComponents: true→ Suspense/CSR bailout derdi).src/app/paket/layout.tsx(yeni):{children}+<PaketGorunumOlayi />. Linkler:listem-govde.tsx:288,369→/paket?kaynak=kilit;liste-uretici.tsx:134→?kaynak=kredi_bitti;user-nav.tsx:51→?kaynak=nav.liste-uretici.tsx:elsedalı (:87)olay("liste_uretim_hata", { kod: sonuc.code });catch(:89){ kod: "ISTISNA" }.AUTH/KREDI/PAKETdalları bu olayı atmaz.giris-form.tsx:linkGonderhata dalı →olay("giris_hata", { yontem: "eposta", status: error.status ?? 0 });status === 429ise toast "Çok sık denedin, bir dakika sonra tekrar dene", değilse mevcut mesaj. Google hata dalı →{ yontem: "google", status }.geriSayimBaslat30 → 60 sn (P4'ün sunucu soğumasıyla hizalı; P4 merge edilmese de zararsız).- Ayrı, son commit [BİLAL]:
rybbit-identify.tsxeposta/isimprop'ları ve trait'ler kalkar,kullaniciTanit(kullaniciId);user-nav.tsx:47çağrısı sadeleşir.analitik.ts'e dokunmaz.
Commit bölünmesi: (1) fix(analitik): görünüm hook'u callback ref + erken olay kuyruğu · (2) feat(analitik): huni olayları (3–7 + 8'in olay kısmı) · (3) fix(giris): 429 mesajı + 60 sn geri sayım · (4) [BİLAL] chore(gizlilik): rybbit identify yalnız kullanıcı ID. (4) en sonda durmalı: user-nav.tsx'te 47. ve 51. satırlar yakın; Bilal reddederse git merge gece/p2-olcum~1.
Kabul kriterleri: typegen+tsc+lint temiz. grep -rn "useGorunumOlayi" src → mevcut 5 + cta-sira-form. grep -rnE "olay(BirKez)?\(" src | grep -iE "email|eposta:|sira:" → boş (ham sıra/e-posta yok; sıra yalnız siraKovasi()). Olay adları yukarıdaki listeyle birebir (kalıcı sözleşme). Kod incelemesi: hook eleman null→düğüm geçişinde gözlem kuruyor, düğüm sökülünce disconnect ediyor.
Kapsam dışı: giris_basarili (P4), odeme_hata, sihirbaz_kapatildi, rapor_yazdirildi, /sonuc pageview şişmesi, iç trafik filtresi, src/app/layout.tsx, page.tsx/sohbet-client.tsx/tercih-degisti-modali.tsx içindeki /paket linkleri (kaynak = diger düşer), rapor-actions.ts (hata alt nedeni).
Riskler / AGENTS.md: Yeni CTA butonu eklenmiyor; var olan ArrowRight desenine dokunma. ?kaynak= /paket canonical'ını etkilemez (sayfaMetadata path: "/paket"). /giris?callback=/paket dönüşünde kaynak kaybolur (kabul). Kuyruk sayesinde dev'de (script yok) 10 sn sonra sessizce boşalır — hata yok.
4. P3 · Vaat dili ve güven temizliği
Amaç: Olmayan özelliği ("yerleşme olasılığı") satan dili kaldırmak; 4↔5 yıl tutarsızlığını bitirmek; iki [BİLAL] değişikliğini bağımsız commit olarak hazırlamak.
Kök neden (kanıt): page.tsx:180, paket-satinal.tsx:31 "yerleşme olasılığı"; rapor.ts:166,168,170,172 dört cümlenin dördü de "ihtimal". "4 yıl": page.tsx:107,122,148,180,365 + paket-satinal.tsx:31; DB sira2021…sira2025 = 5 (rapor.ts programBilgisi, tablolar "Son 5 yıl"). CEO listesinde olmayanlar: scripts/tadimlik-uret.ts:70-71 (prompt "ihtimal dili kullan"), :72 ("son 4 yılın"), :84-87 (mock); src/app/api/soru/route.ts:48; src/components/arama-funnel-karti.tsx:38 "Yerleşme ihtimalini görerek seç." Tadımlık riskNotu DB'de (schema.ts:187) ve tadimlik-havuzu.ts:83'ten olduğu gibi servis ediliyor; oysa rozet aynı satırda canlı hesaplanıyor (tadimlik-satiri.tsx:107) → metinle rozet çelişebiliyor. CEO'nun grep'i (olasılı|ihtimalle yerleş) "ihtimalini/ihtimal dili/ihtimali düşük" kalıplarını yakalamıyor.
Commit 1 — [KARAR] fix(metin): olasılık dili + 5 yıl:
src/lib/veri-kapsami.ts(yeni):export const VERI_YIL_SAYISI = 5;(+ yorum: DBsira2021–sira2025; veri yılı eklenince güncelle).seo.tsx'e koyma (P1'in).src/lib/risk.ts:export function riskNotuMetni(risk: RiskSeviyesi | null): string— güvenli: "Son taban sıralaması senin sıranın belirgin altında; geçmiş veriye göre güvenli aralıkta." · az-riskli: "Son taban sıralaması sırana yakın; küçük bir kayma sonucu değiştirebilir." · riskli: "Son taban sıralaması senin sıranın üstünde kapandı; hayal tercihi olarak düşün." ·null: "Güncel taban verisi yok; bu satırı temkinli değerlendir." ("Geçen yıl" deme: kodsira2025 ?? sira2024'e düşüyor.)src/lib/ai/rapor.tsriskNotuUret: gövde →return riskNotuMetni(riskHesapla(p.sira2025 ?? p.sira2024, adaySira));. Başka hiçbir şeye dokunma (5/13/6 şeması, risk eşiği, prompt aynı).page.tsx:180vepaket-satinal.tsx:31→`Her tercih için yerleşme riski ve trend analizi (son ${VERI_YIL_SAYISI} yıl)`;page.tsx:107,122,148,365sabitten.page.tsx'te import'u 32. satırın (@/lib/credits) hemen altına koy (commit 3'ün dokunacağı 15. ve 55-59. satırlardan uzak).arama-funnel-karti.tsx:38→ "Yerleşme riskini görerek seç."
Commit 1b — [KARAR · CTO eki] fix(ai): tadımlık risk notu deterministik + prompt dili:
tadimlik-havuzu.ts:83:riskNotu: riskNotuMetni(riskHesapla(program.efektifSira, sira) ?? dilimdenRisk(satir.dilim))(program= satırın snapshot'ı; üç fonksiyon@/lib/risk). Böylece/api/tadimlikde kapsanır; DB satırları ve şema değişmez.scripts/tadimlik-uret.ts:70-72prompt: "riskNotu: geçmiş taban verisine dayalı risk değerlendirmesi (TEK kısa cümle). ASLA garanti/kesin/yüzde/olasılık/ihtimal deme; 'güvenli / az riskli / riskli' etiketleriyle konuş." + "son 5 yılın"; mock metinleri aynı dile. Script koşulmaz.api/soru/route.ts:48→ "ASLA yerleşme garantisi verme; olasılık, yüzde ya da 'ihtimal' dili kullanma — güvenli / az riskli / riskli etiketleri ve taban sıralarıyla konuş."
Commit 2 — [BİLAL] chore(metin): danışman fiyat kıyası: "2.000–10.000 TL" → "1.000–5.000 TL" (page.tsx:90,168,484, paket/page.tsx:52); "onda bir fiyatına" → "çok daha düşük fiyata" (paket/page.tsx:49, layout.tsx:55; page.tsx:228 yorum satırı içinde ölü kod — yorum bloğunu sil). Fiyat bölümü paragrafının (page.tsx:484-487) altına text-xs text-slate-500 kaynak satırı: "Danışman fiyat aralığı: Armut, 2026." Sabit kullanma, düz metin (commit 1'den bağımsız kalsın). Rakamı ajanlar doğrulamadı → Bilal Armut sayfasını açmadan almasın.
Commit 3 — [BİLAL] chore(guven): hero'da YÖK logosu yerine tarihli veri satırı: veri-kaynagi.tsx:4 const TARIH_METNI → export const KATALOG_TARIH_METNI (dosya içi kullanım güncellenir; tek biçim kaynağı). page.tsx:236-251: <Image> + dikey ayraç kalkar; aynı kutu (inline-flex … rounded-lg border … sınıfları aynen) içinde <strong>Veri: YÖK Atlas</strong> · son güncelleme <time dateTime={KATALOG_VERI_TARIHI}>{KATALOG_TARIH_METNI}</time>. import Image (15) silinir; KATALOG_VERI_TARIHI 55-59'daki seo import'una eklenir. public/yok-logo.svg silinmez.
Kabul kriterleri: typegen+tsc+lint. Commit 1+1b sonrası: grep -rniE "olasılı|ihtimal" src scripts/tadimlik-uret.ts → yalnız page.tsx:76 ("binlerce ihtimal" — kombinasyon anlamı, vaat değil), page.tsx:132 ("boşta kalma ihtimalini" — SSS, kapsam dışı), program-tablosu.tsx:176 (kod yorumu) ve prompt'lardaki yasaklayıcı cümleler. grep -rn "4 yıl" src/app/page.tsx src/features/odeme → boş. Commit 2 sonrası grep -rn "onda bir\|2\.000–10\.000" src → boş. Bağımsızlık testi: main'den geçici dal açıp commit 2'yi ve 3'ü tek başlarına git cherry-pick et → çakışmasız + tsc geçer; geçici dalı sil.
Kapsam dışı: Garanti→Güvenli (H2) — cta-sira-form.tsx'teki "garanti dilimleri / nereye yerleşirsin" dahil (dosya P2'nin); /hakkinda, "Sınırlarımız", hero/kapanış tonu, SSS metinleri, vizyon.md.
Riskler / AGENTS.md: Davranış değişmiyor (eşikler, 5/13/6, havuz kuralları aynı) → huni-semasi.tsx SEMA güncellenmez; şemadaki "risk notu gerçek sıralama geçmişinden hesaplanır, yapay zekâ yorumu değildir" iddiası 1b ile tadımlık için de doğru hâle gelir. Kayıtlı raporlardaki eski cümleler kalır (göç yok). Commit 3 yeni badge tasarlamaz (SectionEyebrow kuralı: mevcut bilgi kutusu, pill değil). CTA butonlarına dokunulmuyor.
5. P4 · Giriş e-postası sağlamlaştırma
Amaç: Reply-To, prod'da sessiz fallback'in bitmesi, adres başına soğuma, şablon temizliği, giris_basarili.
Kök neden (kanıt): eposta-gonder.ts:13-18 ve auth.ts:33-37 NODE_ENV'e bakmadan konsola yazıp başarı dönüyor (token'lı link loga düşer); eposta-gonder.ts:22 sessiz onboarding@resend.dev; :21-29 replyTo yok; eposta.ts:79-80 Google Fonts <link>, :96-101 footer'da iletişim yok; auth.ts'te adres bazlı sınır yok (yalnız eklentinin IP limiti 5/60 sn, magic-link/index.mjs:157-163); analitik-sunucu.ts:17 tek olay.
Değişiklikler:
eposta-gonder.ts:export function epostaKonsolModu(): boolean=!process.env.RESEND_API_KEY && (process.env.NODE_ENV !== "production" || process.env.EPOSTA_KONSOL_FALLBACK === "1").epostaGonderbaşında, çağrı anında (modül seviyesinde asla —next buildNODE_ENV=production + env'siz koşar, modül yüklenirken atmak build'i kırar): konsol modu → mevcutconsole.log; anahtar yok ve konsol modu değil →throw new Error("[eposta] RESEND_API_KEY tanımlı değil");EMAIL_FROMyok → prod'da (kaçış bayrağı yoksa)throw, aksi hâlde mevcutresend.dev.send'ereplyTo: process.env.EMAIL_REPLY_TO ?? "destek@kolaytercih.com"(Resend 6 alan adıreplyTo).auth.tssendMagicLinkEmail::33-37bloğu →if (epostaKonsolModu()) { console.log(link); return; }; aksi hâldeepostaGonder(yanlış yapılandırmada atar → better-auth 500 → formda hata toast'ı; P2giris_hata{status:500}yazar).__sonMagicLinksatırı yerinde kalır.- Soğuma —
auth.ts, yalnızNODE_ENV === "production"(dev paneli "anında giriş" art arda istek atıyor):globalThis.__girisEpostaSon: Map<string, number>; anahtaremail.trim().toLowerCase(); her çağrıda 60 sn'den eski kayıtları buda; kayıt varsa sessizcereturn(mail yok, istek 200); yoksa zamanı yaz → gönder; gönderim atarsa kaydı sil (kullanıcı hemen yeniden deneyebilsin) ve hatayı yeniden fırlat. Konsol modunda da çalışır (lokal docker testi görsün). eposta.ts::79-80iki<link>satırı silinir; footer'a (:98-99) üçüncü satır:<a href="https://kolaytercih.com" style="color:#94a3b8;">kolaytercih.com</a> · <a href="mailto:destek@kolaytercih.com" style="color:#94a3b8;">destek@kolaytercih.com</a>; düz metin (text) gövdelerinin sonuna aynı iki bilgi. Satış dili ekleme.analitik-sunucu.ts:17:"odeme_tamamlandi" | "giris_basarili".auth.tsdatabaseHooks'asession: { create: { after: async (session, ctx) => { void sunucuOlayi("giris_basarili", { kullaniciId: session.userId, pathname: "/giris", ozellikler: { yontem: ctx?.path?.startsWith("/magic-link") ? "eposta" : ctx?.path?.startsWith("/callback") ? "google" : "diger" } }); } } }—awaityok; imza(session, context | null)1.6.23 tiplerinde doğrulandı (@better-auth/core … init-options.d.mts). E-posta özelliğe girmez.- İsteğe bağlı commit [BİLAL]
.gitea/workflows/ci.yaml:.env.productionbloğunaRYBBIT_HOST,RYBBIT_SITE_ID,RYBBIT_API_KEY,EMAIL_REPLY_TOsatırları (secret yoksa boş gelir → kod no-op/varsayılan; zararsız). Gerekçe aşağıda, itiraz #4.
Commit bölünmesi: (1) chore(eposta): şablondan harici font + footer iletişim · (2) fix(eposta): Reply-To + prod'da sessiz fallback yok · (3) feat(giris): adres başına 60 sn soğuma · (4) feat(analitik): giris_basarili sunucu olayı · (5) [BİLAL] ci.yaml. Tamamı "Bilal görmeden merge edilmez"; (2) ve (3) canlı giriş yolunu değiştirir, tek tek atılabilir.
Kabul kriterleri: typegen+tsc+lint. grep -n "fonts.googleapis\|fonts.gstatic" src/lib/eposta.ts → boş. grep -n "process.env" src/lib/eposta-gonder.ts → tüm okumalar fonksiyon gövdesinde. grep -n "await sunucuOlayi" src/lib/auth.ts → boş. Kod incelemesi: prod + anahtarsız + bayraksız yolda hiçbir console.log URL basmıyor. Bilal'in merge öncesi lokal docker testi: EPOSTA_KONSOL_FALLBACK=1 ile link konsola düşer, 60 sn içinde ikinci istek link üretmez; bayraksız/anahtarsız → formda hata.
Kapsam dışı: disposable filtre, vazgeçme linki/List-Unsubscribe, eski token iptali, Resend webhook, Traefik IP başlığı / better-auth rateLimit ayarı, DNS, kredi-hatirlatma.ts.
Riskler: Hata = kimse giriş yapamaz → (2)-(3) için lokal docker testi şart (bellek notu "testler lokal docker'la"). Süreç içi Map yeniden başlatmada sıfırlanır ve çoklu kopyada paylaşılmaz — bugün tek konteyner (docker-compose.yml), ikinci katman IP limiti duruyor. Soğumada düşen isteğin verification satırı yetim kalır (15 dk'da ölür, hiç postalanmadı — zararsız). session.create Google girişinde ve dev panelde de tetiklenir (dev'de env yok → no-op).
6. CTO itirazları (CEO kararına)
- P3 kabul kriteri eksik ve grep'i yetersiz. Yalnız
riskNotuUretdüzeltilirse girişsiz /sonuc tadımlığı (LLM üretimi, DB'de kayıtlı) ve danışman sohbeti "ihtimal" diliyle kalır; prompt'lar bu dili emrediyor. Alternatif: commit 1b (okuma anında deterministik risk notu + iki prompt satırı). Prod'dapnpm tadimlik --forcegerekmez. - P4-3 "kaynak:
verificationtablosu" kırılgan.identifier= token (magic-link/index.mjs:61-68), e-posta yalnızvalueJSON'unda (indeks yok,nameile biçim değişir), satırsendMagicLink'ten önce yazılıyor (kendine takılır), düşürülen her istek yeni satır bırakıp pencereyi kaydırır; düzeltmesi tabloyaDELETEgerektirir. Alternatif: süreç içiMap— şema yok, DB yazımı yok, better-auth iç yapısına bağımlılık yok. - P4-2 "net hata" modül seviyesinde atılamaz —
next buildkırılır; yalnız gönderim anında. - P4-5
giris_basariliprod'da büyük olasılıkla hiç düşmeyecek:sunucuOlayiRYBBIT_HOST/SITE_ID/API_KEYister (analitik-sunucu.ts:13-15,31); CI'ın yazdığı.env.production'da bu üçü yok (.gitea/workflows/ci.yaml:17-34). Rybbit'teki 3odeme_tamamlandikaydının kaynağı bu yüzden şüpheli (lokal docker testi olabilir). Bilal: Gitea secret'larını ekle + P4 commit 5'i al; yoksa huniningiris_basariliadımı boş kalır. - P2
paket_goruntulendi {kaynak}CEO'nun dosya listesiyle yapılamaz (/paketsayfası vepaket-satinal.tsxP3'ün;SatinAlFormyalnız girişliye render oluyor). Alternatif: yenipaket/layout.tsx. Ana sayfadaki iki/paketlinki (page.tsx:535,565, P3'ün) kaynak taşıyamaz →diger. - P2'ye erken-olay kuyruğu eklenmezse yeni görüntülenme olayları sert yüklemede (OAuth dönüşü, e-postadan gelen, yer imi) sistematik eksik sayar; CEO listesinde yoktu, eklendi.
- İstemci 30 sn ↔ sunucu 60 sn: kullanıcı 30. sn'de "Yeniden gönder"e basınca sunucu sessizce yutar, kullanıcı mail bekler. P2 geri sayımı 60'a çeker.
- "Her paket
pnpm build": dört paralel build ≈ 8 GB (bellek notu: build ~2 GB). Yalnız P1 build eder; diğerleri typegen+tsc+lint; build Tur 4'te sırayla.
7. Backlog (bu gece yapılmayan teknik maddeler, öncelik sırasıyla)
- Prod tadımlık havuzu kontrolü (
pnpm tadimlik) — P2 yayına giriptadimlik_goruntulendihâlâ 0 ise ilk şüpheli. [Bilal] - Gitea secret'ları:
RYBBIT_*,EMAIL_REPLY_TO(itiraz #4). [Bilal] - Garanti→Güvenli (H2) tek paket: ~10 dosya +
SEMA+ aria + figcaption aynı commit;cta-sira-form.tsx"garanti dilimleri / nereye yerleşirsin" vepage.tsx:132dahil. Karar bekliyor. - Rehber T4 (
kume/ilgili+/rehberküme başlıkları,SectionEyebrowile) — P1 merge'inden sonra, aynı dosyalar. liste_uretim_hataalt nedeni:rapor-actions.tssonuç tipineneden: "ai" | "dogrulama" | "tekrar"; ardındanodeme_hata,sihirbaz_kapatildi,rapor_yazdirildi; kalan/paketlinklerinekaynak(page.tsx,sohbet-client.tsx,tercih-degisti-modali.tsx).- Tarih biçimi tekilleştirme:
veri-kaynagi.tsx'intoLocaleDateString'i P1'intrTarih'ine geçsin (P1+P3 merge sonrası); hydration #418 şüphesini de kapatır. - better-auth
rateLimit+advanced.ipAddress.ipAddressHeadersTraefik'e göre sabitlenmesi (IP başlığı yanlışsa limit ya herkese ortak ya hiç işlemiyor) — canlı ayar, lokal docker + Bilal. error.tsx'e ChunkLoadError için tek seferlik reload;/sonucpageview şişmesi (replaceState).el-cizimi-sema.tsx:14-15font lisansı — alınmadıysa font değişimi işi.- Eski token iptali, Resend webhook/alarm, hatırlatma retry, disposable filtre (ürün/hukuk kararı sonrası).