Spaces:
Sleeping
Inclusive Visuals Audit — Hasarİ Web
Audit Date: 2026-05-16
Scope: apps/web + packages/ui + packages/design (READ-ONLY)
Audit Type: Renk, tipografi, ikonografi, lokalizasyon, erişilebilirlik (WCAG AA + color-blind)
Stance: Pratik fix odaklı. Kod değiştirilmedi.
1. Genel Tablo
| # | Bulgu | Severity | Kategori | Konum |
|---|---|---|---|---|
| F-01 | Severity rengi color-blind için sadece renge bağımlı (ikon yok) | HIGH | Renk / a11y | packages/ui/src/components/SeverityBadge.tsx |
| F-02 | Intl.NumberFormat/DateTimeFormat her yerde tr-TR hardcoded |
HIGH | Lokalizasyon | apps/web/app/**/*.tsx (5 dosya) |
| F-03 | Shared UI bileşenlerinde TR string hardcoded (EN locale'de TR yazı görünür) | HIGH | Lokalizasyon | packages/ui/src/components/* |
| F-04 | <html lang> doğru ama dir attribute yok (LTR varsayılan implicit) |
LOW | Yön / a11y | apps/web/app/layout.tsx:50 |
| F-05 | PartCard "hasarsız parça" sadece yeşil ringle gösteriliyor (ikon var ama küçük dot, simgesel ayırt edici zayıf) |
MEDIUM | Renk / a11y | packages/ui/src/components/PartCard.tsx:13-25 |
| F-06 | CostDisplay "doğruluk" göstergesi (high/medium/low) sadece renk noktası |
MEDIUM | Renk / a11y | packages/ui/src/components/CostDisplay.tsx:18-22 |
| F-07 | severity.hafif = amber-400 (#fbbf24) — beyaz arkaplanda 3:1 contrast sınır altında küçük metin için |
MEDIUM | Renk / WCAG | packages/design/src/colors.ts:45 |
| F-08 | home/page.tsx Hero "preview card" — kırmızı dot + amber/orange badge sadece renk ile severity ima ediyor |
MEDIUM | Renk / a11y | apps/web/app/page.tsx:88-104, 175-198 |
| F-09 | "Hasarsız parça" yeşil + "ağır hasar" kırmızı → klasik protanopia çakışması | HIGH | Renk / a11y | Genel (severity sistemi) |
| F-10 | Profil ayarlarında telefon alanı i18n'de var ama formda yok (ölü string) | LOW | Form / UX | apps/web/messages/{tr,en}.json + apps/web/app/(app)/settings/page.tsx |
| F-11 | İsim alanı min(2) — Türkçe tek heceli isimler için OK; maxLength yok (DB güvenliği zayıf) |
LOW | Form | apps/web/app/(auth)/register/page.tsx:19 |
| F-12 | E-posta validation z.string().email() — + ve . özel karakterleri Zod default destekler ✓ |
OK | Form | apps/web/app/(auth)/register/page.tsx:20 |
| F-13 | Para sembolü hep sonda 1.234 ₺ — TR doğru, EN için de aynı sembol kullanılıyor (tutarlı) |
OK | Para birimi | Tüm *Display bileşenleri |
| F-14 | EN locale'de tarih tr-TR formatında gösterilecek (16 May 2026, 14:32) yerine US format gerekirdi |
HIGH | Lokalizasyon | apps/web/app/history/page.tsx:307-320 |
| F-15 | Logo bir image değil, Lucide ShieldCheck ikon — TR/EN tutarlı, kültürel sorun yok |
OK | Brand | apps/web/components/Header.tsx:34-37 |
| F-16 | Anasayfada hero görseli yok (placeholder kart) — "lüks/ekonomik araç" temsil sorunu YOK | OK | Brand / Görsel | apps/web/public/ (boş) |
| F-17 | Plaka tipi UI'da hiçbir yerde gösterilmiyor — bölgesel önyargı YOK | OK | Brand | — |
| F-18 | Brand adı "Hasarİ" — büyük dotted İ Türkçe karakter; EN locale'de aynen kalıyor (kasıtlı, doğru) | OK | Brand | apps/web/messages/{tr,en}.json:3 |
| F-19 | Inter font subset ['latin', 'latin-ext'] — ç, ş, ğ, ı, ö, ü destekleniyor ✓ |
OK | Tipografi | apps/web/app/layout.tsx:11-15 |
| F-20 | tabular-nums kullanılıyor maliyet/sayı için — sayı hizalama doğru |
OK | Tipografi | Genel |
2. Detaylı Bulgular
F-01 — Severity ikon eşleştirmesi yok (HIGH)
Konum: packages/ui/src/components/SeverityBadge.tsx
Sorun:
Severity'yi sadece renk ile ayırt ediyor (hafif=amber, orta=orange, agir=red) + küçük renkli dot (aria-hidden). Yazılı etiket var (Hafif/Orta/Ağır) — bu kısmi OK. Ancak:
- Protanopia (kırmızı görmeyen) için orange ve red ayırt edilemez → "orta" ve "ağır" birbirinden farksız görünür.
- Renkli dot screen reader'a
aria-hidden, yani sadece görsel ayraç. Eksik kalan ikon ayraç.
Fix önerisi (kod değiştirmeden, plan):
// SeverityBadge.tsx — sadece öneri, kod değişmedi
const ICONS: Record<SeverityLevel, IconComponent> = {
hafif: Info, // ⓘ
orta: AlertTriangle, // ⚠
agir: AlertOctagon, // ⛔
};
// dot yerine <Icon className="h-3 w-3" aria-hidden /> ekle
Bu, redundant coding (renk + ikon + metin) prensibiyle WCAG 1.4.1 "Use of Color" gereksinimini karşılar.
F-02 / F-14 — Hardcoded tr-TR locale (HIGH)
Konum:
apps/web/app/history/page.tsx:268, 310apps/web/app/(app)/settings/page.tsx:360, 363apps/web/app/(admin)/users/page.tsx:116apps/web/app/(app)/dashboard/page.tsx:96, 152, 159packages/ui/src/components/CostDisplay.tsx:38, 47packages/ui/src/components/DamageBadge.tsx:53packages/ui/src/components/PartCard.tsx:87-88
Sorun:
Tüm toLocaleString('tr-TR') ve Intl.DateTimeFormat('tr-TR', …) çağrıları locale parametresini hardcoded geçiyor. EN locale aktif olduğunda kullanıcı şöyle görür:
- Beklenen (EN):
1,234.56 ₺ve5/16/2026, 2:32 PM - Gerçekleşen:
1.234,56 ₺ve16 May 2026 14:32
EN locale switching çalışır ama sayı/tarih formatları TR kalır — yarım lokalizasyon.
Fix önerisi:
// next-intl helper kullan
const t = useLocale(); // 'tr' | 'en'
const fmt = new Intl.NumberFormat(t === 'tr' ? 'tr-TR' : 'en-US');
fmt.format(value);
Veya next-intl'in built-in useFormatter() API'sini kullan (format.number(), format.dateTime()).
F-03 — Shared UI'da TR string sızıntısı (HIGH)
Konum:
packages/ui/src/components/CleanPartsBadgeRow.tsx:22, 37—Hasarsız parçalar,dahapackages/ui/src/components/InspectionSummary.tsx:11-17—Toplam parça,Hasarlı parça,Çoklu parçavb.packages/ui/src/components/PartCard.tsx:62, 84—Hasarsız,Parça toplampackages/ui/src/components/DamageBadge.tsx:41-49, 56—Çoklu parça,Düşük güven,güvenpackages/ui/src/components/CostDisplay.tsx:13-16, 35, 38, 53, 65—Yüksek doğruluk,Tahmini onarım maliyeti,Orta nokta,günpackages/ui/src/components/InspectionStatusBadge.tsx:5-10—Sırada,İşleniyor,Tamamlandı,Başarısız
Sorun:
@arac-hasar/ui package'ı Türkçe stringleri gömülü olarak ihraç ediyor. Web app'in i18n switcher'ı bu bileşenler için EN'e geçemez → kullanıcı EN seçse de "Hasarsız parça", "Tahmini onarım maliyeti" gibi yazılar TR görür.
Fix önerisi: İki seçenek:
- Bileşenleri props'la beslenebilir yap:
<CleanPartsBadgeRow parts={…} labels={{title: t('cleanParts'), more: t('more')}} /> - Bileşeni i18n-aware yap:
useTranslationsçağır, sözlüğüpackages/uiiçinde tut (ya da@arac-hasar/types'tan re-export et). MevcutSEVERITY_TR,PART_STATUS_TRzaten types'ta var; aynı pattern'i diğer label'lara uygula.
F-04 — <html dir> hard-coded değil ama eksik (LOW)
Konum: apps/web/app/layout.tsx:50
Mevcut:
<html lang={locale} className={inter.variable}>
Sorun:
dir attribute yok → tarayıcı default LTR davranır. Bu şu an TR/EN için doğru ama ileride AR/HE locale eklenirse fark edilmeden bozulur. RTL kapsamda değil, ama explicit yazmak best practice.
Fix önerisi:
<html lang={locale} dir="ltr" className={inter.variable}>
Hard-coded dir="ltr" şu an kabul edilebilir (kapsamda RTL yok); ileride RTL desteği eklenince locale-aware yapılır.
F-05 — PartCard "clean" durumu ikon eksik (MEDIUM)
Konum: packages/ui/src/components/PartCard.tsx:53, 61
Sorun:
"Hasarsız" durumu yeşil dot + yeşil pill ile gösteriliyor. Hasarlı durum siyah pill + sayı (2 hasar) ile gösteriliyor. Yeşil/kırmızı ayrımı clean vs damaged için kritik ve burada ikon yok.
Mevcut: <span className="bg-emerald-500 h-2 w-2 rounded-full" aria-hidden /> + emerald pill.
Fix önerisi: Status indicator olarak ikon ekle:
clean→<Check />(yeşil)minor_damage→<Info />(amber)moderate_damage→<AlertTriangle />(orange)severe_damage→<AlertOctagon />(red)
F-06 — CostDisplay confidence renge bağımlı (MEDIUM)
Konum: packages/ui/src/components/CostDisplay.tsx:57-62
Sorun:
high=emerald, medium=amber, low=slate. Metin etiketi var (Yüksek doğruluk vb.) → kısmen OK. Ancak amber #f59e0b ve slate #94a3b8 color-blind için ayırt edilebilir ama yeşil/sarı (high/medium) tritanopia için sorunlu.
Fix önerisi: Metin etiketi yeterli, ancak küçük bir ikon eklenebilir (<ShieldCheck /> high, <Shield /> medium, <ShieldAlert /> low).
F-07 — severity.hafif amber-400 kontrast sınırı (MEDIUM)
Konum: packages/design/src/colors.ts:45
Mevcut yorum:
amber-400 (mild) — yellow-leaning amber for contrast
Doğrulama:
#fbbf24(amber-400) beyaz üstünde: contrast ratio ~1.85:1 → AA non-text 3:1 fail.- WCAG 1.4.11 "Non-text Contrast" → UI components 3:1 ister.
SeverityBadgezatenbg-amber-100(açık zemin) +text-amber-900(koyu yazı) kullanıyor, yazı kontrastı OK (amber-900üstündeamber-100~7.5:1). Ancakseverity.hafiftoken'ı doğrudan badge zemini olarak kullanılırsa sorun olur.
Fix önerisi: Token kullanım kuralını dokümante et:
severity.hafif→ sadece dot/icon dolgu rengi.- Badge zemini için
amber-100+ yazıamber-900kombinasyonu zorunlu. - Veya
severity.hafifdeğeriniamber-500(#f59e0b) yap → ~2.8:1, hâlâ sınır ama daha iyi. Önerilen:#d97706(amber-600) → 4.5:1 AA ✓
F-08 — Anasayfa preview kartı sadece renk/dot (MEDIUM)
Konum: apps/web/app/page.tsx:88-104, 175-198
Sorun:
- Satır 90:
<span className="h-2.5 w-2.5 rounded-full bg-red-500" aria-hidden />→ "kırmızı = hasar" sadece renkle ima. Badgecomponent (satır 175-198)colorprop alıyor, severity yazısı + renkli zemin var ama ikon yok.
Fix önerisi:
Preview kart sadece marketing içerikli, ama "feature parity" göstermek için SeverityBadge'in ikon-iyileştirilmiş hâlini burada da kullan.
F-09 — Yeşil/kırmızı protanopia çakışması (HIGH, sistemik)
Konum: Genel — severity.clean (#22c55e) + severity.agir (#ef4444)
Sorun: Klasik problem: protanopia/deuteranopia kullanıcıları için yeşil ve kırmızı aynı kahverengi/sarımtırak ton olarak algılanır → "hasarsız" ve "ağır hasar" birbirinden ayırt edilemez. Bu, ürünün ana semantic mesajını (✓ vs ✕) görsel olarak yok eder.
Fix önerisi (zorunlu, redundancy):
- ✅ Metin etiketi (
Hasarsız/Ağır) — mevcut, OK. - ✅ İkon farklılaştırma (
<Check />vs<AlertOctagon />) — eksik (F-01, F-05 ile bağlantılı). - ✅ Şekil/border farklılaştırma —
cleanpill rounded-full,severerounded-md gibi shape coding. - ✅ Pattern (örn. ağır hasar için diagonal stripe overlay) — opsiyonel ama power tool.
Minimum kabul: ikon + metin + renk = 3 kanal redundancy.
F-10 — Telefon alanı i18n'de var, formda yok (LOW)
Konum:
apps/web/messages/tr.json:172—"phone": "Telefon (opsiyonel)"apps/web/messages/en.json:172—"phone": "Phone (optional)"- Hiçbir
.tsxdosyasındaphonekullanılmıyor (grep doğrulandı).
Fix önerisi: Ya ölü string'leri kaldır, ya da register/page.tsx ve settings/page.tsx'e telefon alanı ekle. Eklenecekse:
+90prefix zorlama — uluslararası destek içinlibphonenumber-jsile validate.inputMode="tel",autoComplete="tel",pattern="[0-9+\s()-]+".- TR placeholder:
+90 5xx xxx xx xx, EN placeholder:+1 (555) 123-4567.
F-11 — İsim alanı maxLength yok (LOW)
Konum: apps/web/app/(auth)/register/page.tsx:19
Mevcut: full_name: z.string().min(2)
Sorun:
- TR isimleri
min(2)OK (örn. "Ay" gibi nadir kısa adlar). maxyok → DoS/DB satır limiti riski. i18n'de zatenfullNameTooLong: "İsim en fazla 120 karakter olabilir."mesajı var ama schema'da kullanılmıyor.
Fix önerisi: z.string().min(2).max(120).
F-12 — Email validation OK
Zod .email() regex'i + ve . karakterlerini kabul eder (user+tag@example.com, first.last@domain.com.tr). Türkçe karakter yerel adında sorun olabilir (çağrı@example.com → Zod fail). Eğer .tr IDN destekliyorsa, z.string().email() yetersiz; punycode ile pre-process gerekir. Düşük öncelik — Türkçe yerel ad e-postaları nadir.
F-13 — Para sembolü pozisyonu
TR norm: 1.234,56 ₺ (sembol sonda, locale-correct). EN ne yapmalı? Endüstri pratiği: TRY için ₺1,234.56 (sembol başta) ya da 1,234.56 TRY. Mevcut tüm component'lerde sembol sonda (TR style) — EN kullanıcısı için biraz garip ama anlaşılır. Tutarlılık için kabul edilebilir, opsiyonel iyileştirme: useFormatter().number(value, { style: 'currency', currency: 'TRY' }) → locale-correct otomatik pozisyon.
F-15 / F-16 / F-17 — Brand / görsel temsil
- Logo: Lucide
ShieldCheckikon. Hem TR hem EN için aynı. Kültürel önyargı YOK. - Hero görseli: Yok (
public/boş, hero bölümü sadece text + sentetik card). Bu kapsayıcılık için iyi haber: ne lüks Mercedes ne 1995 Şahin temsil edilmemiş, sadece soyut "ön tampon" mock-up var (Ön tampon/Front bumpertext). - Plaka tipi: Hiçbir UI'da plaka örneği yok. Bölgesel önyargı sorunu yok.
Marketing önerisi (ileride hero görseli eklenirse):
- Çeşitli araç sınıfı (sedan, hatchback, SUV, ticari) gösteren bir görsel/illustration set.
- TR plakası göstermek istenirse: jenerik
34 XXX 1234(İstanbul kodu yerineXX) veya bulanık plaka. - Renk: tek renk araç değil — gri, beyaz, kırmızı mix (en yaygın TR pazarı).
- AI generated görsel kullanılacaksa:
clone carartefaktları, fake plaka karakterleri, lastik şekli bozuklukları için negative prompt zorunlu.
F-18 — Hasarİ brand kelimesi
Dotted capital İ Türkçeye özgü karakter. EN locale'de de Hasarİ olarak korunmuş — bu doğru bir brand kararı (brand adı çevrilmez). Inter font latin-ext subset'i bu karakteri render eder ✓.
F-19 — Türkçe karakter desteği
Inter font subsets: ['latin', 'latin-ext'] ile yükleniyor. latin-ext subset şunları içerir: ç, Ç, ş, Ş, ğ, Ğ, ı, İ, ö, Ö, ü, Ü — TR karakter seti tam destek. Form alanlarında bu karakterler kabul edilir (<input> Unicode-by-default).
3. Top 3 Öncelikli Düzeltme
🥇 P1 — Severity ikon eşleştirmesi ekle (F-01, F-05, F-09)
Etki: ~%8 erkek nüfusunda görülen kırmızı/yeşil körlüğü için ürünün ana semantic mesajını kurtarır. Efor: ~2 saat. Dokunulacak dosyalar:
packages/ui/src/components/SeverityBadge.tsx— Lucide ikon mapping ekle.packages/ui/src/components/PartCard.tsx— status icon mapping ekle.apps/web/app/page.tsx— preview kartıSeverityBadgekullanacak şekilde refactor.
Bonus: SeverityBadge'e shape="rounded" | "square" prop'u ekleyerek shape coding ile redundancy katmanı ekle.
🥈 P2 — Locale-aware sayı/tarih formatting (F-02, F-14)
Etki: EN locale şu an yarım çalışıyor — tüm sayılar ve tarihler TR formatında kalıyor. Efor: ~3 saat. Yaklaşım:
apps/web/lib/format.tsadında yeni helper:import { useFormatter, useLocale } from 'next-intl'; export function useNumberFmt() { … } export function useDateTimeFmt() { … }- Tüm
toLocaleString('tr-TR')çağrılarını helper ile değiştir. packages/uiiçindeki component'ler içinformatterprop ya danext-intl'i UI package'ında peer-dep yap.
🥉 P3 — Shared UI'da TR string sızıntısını temizle (F-03)
Etki: EN locale switcher gerçek anlamda çalışsın — sadece sayfa içi değil bileşen içi yazılar da çevrilsin. Efor: ~4 saat. Yaklaşım:
packages/uiiçindeki tüm hardcoded TR string'ilabelsprop'una taşı (bileşen i18n-agnostic kalsın).- Çağıran sayfalarda
useTranslationsile beslen. @arac-hasar/types'taki mevcutSEVERITY_TR,PART_STATUS_TRconstants'lara karşıSEVERITY_EN,PART_STATUS_ENekle (veya tek bir locale-resolver fonksiyon).
4. Kapsam Dışı / Önemsiz
- RTL desteği: Kapsam dışı, ama
dir="ltr"explicit yazılması önerilir (F-04). - Email IDN (
çağrı@…): Düşük öncelik, kullanım frekansı düşük (F-12). - Para birimi locale-aware format: Mevcut hâli (sembol sonda) tutarlı ve kabul edilebilir (F-13).
- Telefon validation: Şu an form yok, eklenmeden tasarım kararı bekler (F-10).
- Logo / hero görseli: Mevcut hâli kapsayıcılık açısından temiz (F-15, F-16, F-17).
5. Doğrulanmış İyi Pratikler ✓
aria-hiddendecorative ikonlarda doğru kullanılmış.tabular-numssayı hizalama için tutarlı.Inter latin-extile Türkçe karakter destek tam.- Logo brand'i hem TR hem EN'de
Hasarİ— kasıtlı, doğru. - Hero görseli yok → bölgesel/sınıfsal araç önyargısı yok.
- Plaka kullanılmıyor → bölgesel önyargı yok.
- Form
noValidate+ custom error messages +aria-invalid+aria-describedbydoğru. FormFieldlabelhtmlForile input'a bağlı.- Focus ring (
focus-visible:ring-2 ring-brand-500) keyboard navigation için belirgin. CostDisplayaria-labelile screen reader için doğal dil özet sunuyor (38. satır).
Audit Ends.