Added a new router and middleware for handling www to apex domain redirection, ensuring that Let's Encrypt certificates are obtained for both domains. Updated the docker-compose.yml file to include necessary rules and middleware for proper redirection and TLS handling. Also, updated the CEO agent description to remove redundant details and clarified the QA engineer's responsibilities regarding testing critical flows and error reporting. Adjusted the CTO agent's section for consistency in expert guidance and clarified the designer's tools to include 'Skill' for better design resource management.
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ı).