# Kapsama matrisi — `38 Kenar Haller` (B5–B7 · C1–C5)

*02.08.2026 · Tasarım: `design/38 Kenar Haller.dc.html` (293 satır)*

Tasarım dosyası **implementasyondan sonra tekrar açıldı** ve satır satır çıkarıldı (Tasarım Uygulama Protokolü §3).
Bellekten üretilmedi; her satırın kanıtı dosya + sembol olarak yazıldı.

## B5 · Teklif isteği sihirbazı — 1. ve 3. adım

| # | Tasarım öğesi (satır) | ✓/⚠/❌ | Kanıt |
|---|---|---|---|
| 1 | Üç adımlı şerit: Kapsam → Gruplar → Özet (l.33–39) | ✓ | `Portal/QuoteRequest.vue` `<WizardSteps :steps="[…]" :current="step">` |
| 2 | Başlık "Neye ihtiyacınız var?" + alt cümle (l.40–41) | ✓ | `portal.quotes.request.what_do_you_need` / `scope_intro` |
| 3 | Üç radyo kartı, **alt alta** (l.42–56) | ✓ | `RadioCardGroup :columns="1"` — atom `columns` ile genişletildi |
| 4 | "Sistemin tespit ettiklerinden başla" + açıklama (l.44–45) | ✓ | `start_detected` / `start_detected_hint` (eşik + kural ayardan gelir) |
| 5 | Çipler: "Sertifikası dolan · N kişi", "3 ay kuralı · N kişi" (l.47–49) | ✓ | `#extra` slot → `chip_expiring` / `chip_newcomers`; veri `RequestStartingPoints::detected()` |
| 6 | "Kendim seçeyim" (l.52) | ✓ | `start_blank` → `applyStart()` boş grup |
| 7 | "Önceki bir istekten kopyala · TKF-… · N eğitim · N kişi" (l.55–56) | ✓ | `RequestStartingPoints::previous()`; kart yalnız önceki istek varsa çizilir |
| 8 | "Adım 1/3 · seçim sonraki adımı doldurur" + "Devam" (l.59–61) | ✓ | `step_one_of_three` + `applyStart()` |
| 9 | 3. adım şeridi: ✓ Kapsam → ✓ Gruplar → 3 Özet (l.71–77) | ✓ | `WizardSteps` `done/current/pending` durumları |
| 10 | "Göndermeden önce kontrol edin" (l.78) | ✓ | `check_before_send` |
| 11 | Grup satırları: ad · yer · N kişi · eğitimler · dönem (l.80–88) | ✓ | `trainingsOf()` / `packagesOf()` / `locationOf()` |
| 12 | "Toplam · N grup · N kişi · N eğitim" (l.89) | ✓ | `totals` computed + `summary_total` |
| 13 | KVKK rozeti + "bu talep N kişinin adını taşıyor" (l.91–94) | ✓ | `namedPeople` + `kvkk_summary` |
| 14 | "Gönderince: … referans kodu verilir …" bilgi kutusu (l.95) | ✓ | `Banner tone="info"` + `when_sent` |
| 15 | "Geri" · "Gönderdikten sonra düzenlenemez" · "Talebi Gönder" (l.96–100) | ✓ | 3. adım alt şeridi |
| 16 | Referans kodunun **önceden** gösterilmesi (l.95, `TKF-2026-0918`) | ⚠ | kod **gönderimde** üretilir; ekranda "size bir referans kodu verilir" denir — B5-a |

## B6 · Çoklu alıcı — kayıt tek, alıcılar liste

| # | Tasarım öğesi (satır) | ✓/⚠/❌ | Kanıt |
|---|---|---|---|
| 17 | "Kime *" alanı, çip kutusu (l.109–115) | ✓ | `molecules/RecipientInput.vue` (yeni atom) → `Quotes/Show.vue` gönderme penceresi |
| 18 | İlk çip "· birincil", diğerleri "· CC" (l.112–113) | ✓ | `RecipientInput` ilk çip `info` tonunda, `recipient_primary` / `recipient_cc` |
| 19 | "kişi ekle…" ipucu (l.114) | ✓ | `recipient_add` placeholder |
| 20 | Çipten ✕ ile çıkarma | ✓ | `drop()` + boş kutuda backspace |
| 21 | "Yazışma kaydı tektir, alıcılar listedir" açıklaması (l.117) | ✓ | `recipients_are_a_list` gönderme penceresinde |
| 22 | Akışta **tek** giden satır (l.119–120) | ✓ | `SendRecordMail` bir `correspondence_messages` satırı + N `correspondence_recipients` |
| 23 | Alıcı bazında DELIVERED rozeti (l.121–122) | ✓ | `QuoteTimeline::letters()` `recipients[]` → `StatusBadge` (`queued/sent/failed/suppressed`) |
| 24 | "açıldı 14:31 · 2×" / "açılmadı" (l.121–122) | ✓ | `opened_at` + `opened_count`; `RecordLetterOpened` (Postmark `Open` webhook, alanlar resmî dokümandan doğrulandı) |
| 25 | "Gelen yanıt hangi alıcıdan geldiyse o adla akışa düşer" (l.126) | ✓ | mevcut `FileInboundMail` + `address_hash` dizini (yanıt aynı konuşmaya düşer) |
| 26 | Kişi seçiciden ad ile ekleme ("Deniz Aksu") (l.112) | ⚠ | adres yazılır, **rehberden ad seçimi yok** — B6-a |

## B7 · Gelen e-postadan "Görev oluştur" *(önceki turda)*

| # | Tasarım öğesi (satır) | ✓/⚠/❌ | Kanıt |
|---|---|---|---|
| 27 | "Kaynak: gelen e-posta · kişi · tarih" (l.134) | ✓ | `OpenQuoteTask($messageId)` + `Quotes/Show.vue` görev penceresi |
| 28 | Başlık konudan üretilir, düzenlenebilir (l.140–142) | ✓ | pencere açılırken konu doldurulur |
| 29 | E-postayı göreve bağlama onayı (l.148–149) | ✓ | `correspondence_message_id` kolonu |
| 30 | Alıntının nota otomatik eklenmesi (l.151–153) | ✓ | alıntı **kopyalanır** (mesaj silinse de görev okunur kalır) |
| 31 | Termin metinden okunur ("Perşembe dönüş") (l.145) | ❌ | **yok** — B7-a (metinden tarih çıkarımı yapılmıyor) |

## C1 · İmzalı nüsha — yüklendi hâli ve hata *(önceki turda)*

| # | Tasarım öğesi | ✓/⚠/❌ | Kanıt |
|---|---|---|---|
| 32 | Yüklendi hâli: dosya adı, boyut, kaldır | ✓ | `FileDrop` |
| 33 | Reddedilen dosyanın **sebebi** | ✓ | `FileDrop` hata metni |
| 34 | Sonuç cümlesi: "bu hâlde onay gönderilemez" | ✓ | `Portal/QuoteShow.vue` `signed_copy_blocking` |
| 35 | Netlik (bulanıklık) kontrolü | ✓ | `lib/sharpness.js` — OCR yok (Sets kararı, 01.08.2026) |

## C2 · Scroll-to-accept — 375px

| # | Tasarım öğesi (satır) | ✓/⚠/❌ | Kanıt |
|---|---|---|---|
| 36 | **Mobilde iç kaydırma yok** — belge sayfanın kendisi (l.228) | ✓ | `ScrollToAccept.vue`: pane `md:max-h-96 md:overflow-y-auto`; **ölçüldü** → `tests/Browser/scroll-to-accept.spec.js` "ONE scrolling surface" (375px'te `overflow-y: visible`, sayfa kaydırılır) |
| 37 | İlerleme üstte **sabit** şerit + "%74 okundu" (l.209) | ✓ | `sticky top-0 … md:hidden`; ölçüldü → "progress strip stays on screen" |
| 38 | "Okudum" ölçümü **sayfa kaydırmasından** gelir (l.228) | ✓ | `measurePage()` (getBoundingClientRect); ölçüldü → "button opens only after the page reaches the end" |
| 39 | Onay paneli altta **sticky** (l.215–222) | ✓ | `QuoteShow.vue` `fixed inset-x-0 bottom-0 … md:static` + gövdede `pb-44 md:pb-0` |
| 40 | "✓ Sonuna kadar okundu" satırı (l.216) | ✓ | `portal.quotes.show.read_done` (yalnız mobil şeritte) |
| 41 | "Teklifi Onayla" tam genişlik, altında "Revizyon İste" + "Reddet" yan yana (l.217–221) | ✓ | mobilde `flex`, `md:flex-col` |
| 42 | Masaüstünde pane'in kendi kaydırması korunur | ✓ | ölçüldü → "the pane keeps its own scrolling from md up" (1024px) |

## C3 · Teklif detayında limit aşımı özeti *(önceki turda)*

| # | Tasarım öğesi (satır) | ✓/⚠/❌ | Kanıt |
|---|---|---|---|
| 43 | "ONAY BEKLİYOR" rozeti (l.235) | ✓ | `Quotes/Show.vue` `quote.pending_approval` bölümü |
| 44 | Genel toplam / İndirim (limit aşıldı) / Marj (eşik) satırları (l.237–240) | ✓ | `QuoteDetail` + `DiscountCeiling`; eşikler **ayardan** |
| 45 | Bekleme süresi ve SLA | ✓ | `QuoteDetail::pendingApproval()` `waited_hours` / `sla_hours` / `sla_breached` |
| 46 | "Gönderilemez" / "indirimi düşür" çıkışları | ✓ | `cannot_send_yet` / `or_reduce` |

## C4 · Uyum kartında eşik

| # | Tasarım öğesi (satır) | ✓/⚠/❌ | Kanıt |
|---|---|---|---|
| 47 | "SERTİFİKASI DOLACAK — N KİŞİ" + "90 GÜN EŞİĞİ" rozeti (l.249) | ⚠ | rozet **sertifika ekranında** sayaç + eşik cümlesi olarak; ayrı rozet değil — C4-a |
| 48 | Kişi satırları: ad · tarih · "N gün" (l.251–252) | ✓ | `Portal/Certificates.vue` `CertificateRow` (`days_left`, `warning_days`) |
| 49 | "Eşik firmanız için N gün olarak ayarlı" (l.254) | ✓ | `portal.certificates.threshold_set` |
| 50 | "organizasyon ayarlarından değiştirilebilir (30/60/90/120)" (l.254) | ✓ | `threshold_change` bağlantısı + `Portal/Organisation.vue` "Uyum uyarı eşiği" alanı → `SetComplianceWarningWindow` (DB CHECK 30/60/90/120) |
| 51 | "Bu N kişiden grup oluştur →" (l.255) | ✓ | `group_from_these` → `/portal/teklif-istegi` (sihirbazın 1. adımı tespit edilenlerle açılır) |
| 52 | Eşiğin **firma** ayarı olması (l.257 gerekçesi) | ✓ | `customers.compliance_warning_days`; boş = 3E varsayılanı (`Settings::complianceWarningDays()`) |

## C5 · Filtre + arama + sayfalama birlikte

| # | Tasarım öğesi (satır) | ✓/⚠/❌ | Kanıt |
|---|---|---|---|
| 53 | Arama kutusu, temizlenebilir (l.266) | ✓ | `Quotes/Show.vue` zaman akışı `TextInput v-model="trailSearch"` |
| 54 | Sekmelerde **aramadan bağımsız** tür sayaçları (l.268–271, 277) | ✓ | `trailCounts` — arama uygulanmadan hesaplanır |
| 55 | Başlıkta filtre+arama sonrası sonuç sayısı (l.276) | ✓ | `timeline_found_in` (":count sonuç · ':term' araması :kind filtresi içinde") |
| 56 | Filtre değişince **arama korunur**, sayfa 1'e döner (l.288) | ✓ | `pickTrailKind()` yalnız `trailPage` sıfırlar; `watch(trailSearch)` da sayfayı sıfırlar |
| 57 | Sayfalama **filtrelenmiş + aranmış** küme üzerinde (l.288) | ✓ | `trailFound` → `trailPages` → `shownTrail` |
| 58 | "‹ 1 ›" sayfa göstergesi (l.285) | ✓ | `timeline_page` + Önceki/Sonraki |
| 59 | Sonuç yoksa: "… Not içinde sonuç yok — Tümü'nde N sonuç var" + tek tık genişletme (l.288) | ✓ | `timeline_none_here` + `timeline_widen` → `pickTrailKind('all')` |

## Kapanmamış satırlar — karar gerekiyor (Onay Kuralı 6)

| Kod | Satır | Durum | Soru |
|---|---|---|---|
| **B5-a** | #16 referans kodu 3. adımda gösteriliyor | ⚠ | Kod gönderimde üretiliyor; önceden rezerve edilsin mi, yoksa "kod verilecek" cümlesi yeterli mi? |
| **B6-a** | #26 alıcıyı rehberden **ad** ile seçme | ⚠ | Müşteri kişileri seçiciden gelsin mi (portal kullanıcıları + firma kişileri), yoksa adres yazımı yeterli mi? |
| **B7-a** | #31 termini metinden okuma ("Perşembe dönüş") | ❌ | Doğal dil tarih çıkarımı kapsam dışı mı, yoksa ayrı ticket mı? |
| **C4-a** | #47 "90 GÜN EŞİĞİ" ayrı rozet | ⚠ | Eşik cümlesi yeterli mi, kart başlığına rozet de eklensin mi? |
