Files
kakeibo/docs/MEMORY.md
T
2026-11-13 01:29:30 +03:00

37 KiB
Raw Blame History

🧠 Proje Hafızası

▶️ Sıradaki Adım

Faz 9 – Adım 1 (plan docs/PHASES.md Faz 9, onay bekleniyor): rapor uç noktaları. Kullanıcıya sorulanlar: aylık rapor biçimi (templates/monthly_report.md yok, kullanıcı verecek ya da taslak), JSON geri yükleme anlamı (önerilen: tüm hane verisini değiştir + önce otomatik yedek), banka ekstresi içe aktarma isteniyor mu (kapsam dışı, Adım 3'e eklenebilir). Bekleyen (kullanıcıda): Faz 5 – Adım 5 canlı yedek kurulumu (docs/DEPLOY.md §6.1–6.3) → çıktılar gelince Faz 5 ✅.

📌 Alınan Kararlar

  • Yeniden kapatmada birikim (kullanıcı "a", 2026-11-13): bağlı TL alımı yeni tutara güncellenir (0 → silinir).
  • Faz 8 kararları (kullanıcı, 2026-11-13): kur kaynakları TCMB (döviz) + xaus.com ons×TCMB USD (altın; çeyrek/yarım/tam has gram ile) + manuel; değerleme alış kuru; kapanış birikimi onayla TL varlığına alım (tekrar eklenmez), geçmiş kapanışlar "bekleyen birikimler".
  • Kategori yönetimi ekranı (kullanıcı, 2026-11-13): Faz 10'da (seçenek b). Faz 8 önce.
  • Kur kaynağı araştırması (2026-11-13): TCMB today.xml döviz var, altın yok (USD ForexBuying/Selling, 22 kod). xaus.com /api/v1/spot anahtarsız çalışıyor (spot_usd_oz, per_gram_usd), resmi değil → yedek: son kur + manuel. Truncgil doğrulanamadı.
  • Kategoriler (kullanıcı, 2026-11-12): 3. kat alt kategori yok (grup → kategori). Şartı: raporlarda tüm kategorilerin dağılımı görünmeli (yalnızca ilk 5 değil). Amaç "market ne için?" gibi kırılım → kategoriyi bölerek (ör. Market · Gıda / Temizlik); bunun için Ayarlar'da kategori yönetimi ekranı gerekli.
  • Canlı sürüm eski (2026-11-12 kontrol): push'lar Gitea'da ama Dokploy yeniden derlemiyor (Faz 4+ yok, /api/stats/periods 404). Auto Deploy için Gitea webhook'u gerekiyor → kullanıcıya adım adım anlatıldı.
  • Yerel demo hesapları (kakeibo_dev): demo@ornek.com / demo12345 (owner, "Demo Hanesi") · es@ornek.com / demo12345 (member, aynı hane). npm run db:reset silerse API ile yeniden oluştur (ileride db:seed).
  • Sürüm kontrolü: Gitea (https://gitea.ta1sph.info/suphicakir/kakeibo.git), dal main.
  • Her adım sonunda Claude commit atar ve haber verir; push'u kullanıcı kendi uygulamasıyla yapar (Claude git push çalıştırmaz).
  • Node v26 (LTS) sistemde mevcut; ayrı kurulum gerekmedi.
  • Yerel MongoDB host portu 27020 (27017 sistem mongod, 27018/27019 başka projeler).
  • Frontend lint aracı: oxlint (Vite şablonu varsayılanı), ESLint yerine.
  • Veri modeli v1 onaylandı (2026-11-11): ölçekli tam sayı (_e6), tarih YYYY-MM-DD metin, dönem bitişi hariç, kullanıcı = tek hane, ayarlar haneye gömülü, haftalık bütçe hesaplanır.
  • Model yaklaşımı: #[serde(flatten)] yerine ortak alanlar models/common.rs yardımcılarıyla (new_meta, scope, touch). Modeller ilgili adımda eklenir.
  • households.household_id = _id (scope filtresi her yerde aynı).
  • Auth: JWT (HS256, jsonwebtoken rust_crypto) kakeibo_session cookie'de; HttpOnly, SameSite=Lax, Secure yalnızca APP_URL https ise. Beni hatırla → 30 gün Max-Age; değilse 12 saat JWT + oturum cookie'si. Kayıttan sonra 30 gün.
  • Giriş sınırı: e-posta başına 15 dk'da 5 hatalı deneme (bellek içi, auth/limiter.rs). Olmayan kullanıcıda da sahte argon2 doğrulaması (zamanlama sızıntısı yok).
  • Hata kodları: {error:{code,message}}; invalid_credentials, unauthorized, bad_request, conflict, too_many_requests.
  • Hane izolasyonu kuralı: hane kimliği her zaman AuthUser.household_id'den alınır, asla URL/gövdeden. Başka hanenin kaydı → 404 (403 değil; varlığı sızdırılmaz).
  • AuthUser her istekte DB'den kullanıcıyı doğrular (silinen kullanıcı → 401, rol DB'den). Owner-only işlemler: auth.require_owner()?.
  • Davet: 8 karakter (31 harflik okunaklı alfabe), 7 gün, tek kullanım; find_one_and_update ile atomik claim, kayıt transaction'ı içinde.
  • Kategoriler/ödeme türleri: tüm üyeler yönetir; silme yok (pasife alma); grup sonradan değişmez; ad grup içinde benzersiz (büyük/küçük harf duyarsız, pasifler dahil); PATCH'te version → iyimser kilit (409); sıralama tam liste ister.
  • Tasarım: ana renk indigo (oklch(0.42 0.13 268)), grup renkleri Okabe-Ito (needs #0072b2, wants #cc79a7, culture #009e73, unexpected #d55e00), *-fg tonları AA (açık ≥5.0, koyu ≥9) hesaplanarak doğrulandı. Gelir #15803d, gider #c62828, uyarı #b45309.
  • Tema seçimi localStorage (kakeibo-theme), yalnızca cihaz kolaylığı; ileride users.preferences.theme ile senkron olabilir.
  • parseMoney: virgül varsa ondalık; virgül yoksa son noktadan sonra 1–2 rakam → ondalık ("1.25" = 1,25 ₺; "1.250" = 1250 ₺).
  • Logo: Taslak A (indigo zemin, spiral krem defter, ¥) seçildi. Kaynak frontend/brand/logo.svg + logo-fullbleed.svg (maskable/Apple, içerik %82). Uygulama içi components/logo.tsx aynı çizim (elle senkron tutulmalı). Manifest Faz 10'da (vite-plugin-pwa).
  • ts-rs 12: backend/.cargo/config.toml → TS_RS_EXPORT_DIR=../frontend/src/types. TS adları: User, HouseholdSummary, Household, Member, Invite, CatalogItem, Catalog{Create,Update,Order}Request, RegisterRequest, LoginRequest, MeResponse, HealthResponse, ApiErrorBody, Group, Role.
  • QueryClient: 4xx tekrar denenmez; herhangi bir sorgu 401 alırsa me = null. Çıkışta qc.clear().
  • Adresler Türkçe: / (Panel), /defter, /plan, /birikim, /diger (mobil), /degerlendirme, /raporlar, /ayarlar, /giris, /kayit. Mobil alt menü: Panel, Defter, Plan, Birikim, Diğer.
  • Çıkış: qc.clear() KULLANMA (açık ekranlar silinen sorguya bağlı kalıyor) → önce me=null, sonra removeQueries (me hariç).
  • Canlıda SPA yönlendirmesi (Axum: bilinmeyen yol → index.html) Faz 3'te yapılmalı.
  • Kayıt ekranı: "Yeni hane kur" / "Davetle katıl"; davet bağlantısı /kayit?davet=KOD. Sunucu 409 → e-posta alanı, "Davet…" 400 → kod alanı.
  • Yönlendirme sonrası toast için mutateAsync + await kullanıldı (mutate() callback'leri bileşen kalkınca çalışmayabilir).
  • Kayıt politikası (kullanıcı kararı, 2026-11-12): Seçenek 1 → REGISTRATION=invite_only varsayılan; sistemde hiç hane yokken ilk kayıt yeni hane kurar (sunucu sahibi), sonra yalnızca davetle. open isteğe bağlı. Canlıda ilk hesabı kullanıcı /kayit'tan açar; demo hesaplar canlıya taşınmaz.
  • Prettier ayarı eklendi (önceden yoktu → VS Code kaydedince çift tırnağa çevirebilirdi).
  • Canlı imaj: node:26-alpine → rust:1.99-slim-trixie → debian:trixie-slim (ca-certificates + curl, uid 10001 kakeibo), 174 MB. Docker katman önbelleği için sahte lib/main ile bağımlılık derlemesi; kalıntılar *kakeibo* + .fingerprint silinir, gerçek kaynak touchlanır (yoksa sahte lib kullanılıyordu).
  • Önbellek: HTML no-cache, /assets/* 1 yıl immutable, diğer statik 1 saat. Bilinmeyen /api/* → JSON 404.
  • Canlı: domain kakeibo.ta1sph.website (DNS → 159.146.61.151, ev dış IP'si olmalı), Gitea Dokploy'a bağlı. Canlı MongoDB = ayrı Dokploy servisi kakeibo-kakeibomongo-fibzhc (rs0), kullanıcı kakeibo_app (readWrite/kakeibo).
  • Dönem: bitiş hariç, en fazla 62 gün, hane başına tek açık, çakışma yok (metin karşılaştırması), kapalı salt okunur, daraltmada dışarıda harcama kalırsa 409. Ad boşsa otomatik (calendar::default_period_name). Tüm üyeler açabilir/düzenleyebilir.
  • ts-rs: i64 tutarlar #[ts(type = "number")]; isteklerde Option<i64> için #[ts(type = "number", optional)] (yoksa ? kayboluyor).
  • Plan kalemleri: tarih isteğe bağlı (dönem içinde), tutar > 0, kopyalama göreli gün + dönem sonuna kırpma, aynı tür+ad (büyük/küçük harf duyarsız) atlanır. Notlar: başlık 1–120, metin ≤ 20.000. Kapalı dönemde hepsi 409 (period::get_open). Silme = soft delete.
  • İzolasyon iki katmanlı: kayıt okuma scope() + dönem get_open(hid, period_id) + yazma filtresi scope(). Tek katmanı bozmak açık oluşturmuyor (mutasyon denemesiyle görüldü).
  • Bütçe: aylık = budgets{scope:"month"}; haftalık otomatik = (aylık − elle girilenler) gün oranında, money::split_proportional (en büyük kalan, toplam tam eşit). Elle hafta = scope:"week", week_start; null → soft delete. Aylık değişince elle haftalar korunur; elle toplam > aylık → weekly_over.
  • Özet: spendable = gelir − sabit gider − birikim − borç + artı (carryovers.to_period_id). unallocated = spendable − bütçe toplamı (negatif = fazla dağıtım).
  • Öneri: default = hane settings.budget_split_pct × spendable (≤0 → available:false); previous/avg3 = önceki kapalı dönem(ler)in entries toplamı / dönem sayısı (yuvarlayarak).
  • Sürümler: Rust 1.99.1 (edition 2024), Axum 0.8, React 19, Vite 8, TypeScript 7, Tailwind 4.3, shadcn 4.23, Vitest 5, react-router 8, TanStack Query 5.

❓ Açık Konular

  • Geliştirme makinesi: CachyOS nodejs 26.13.0-2 ↔ simdjson .so.36 uyumsuzluğu → simdjson 2:5.0.5-1'e düşürüldü (2026-11-12). Düzeltilmiş nodejs gelene kadar pacman -Syu --ignore simdjson. node: libsimdjson.so.34 hatası görülürse sebep budur.
  • Canlı yedekleme kurulumu bekliyor (betikler hazır, docs/DEPLOY.md §6) → gerçek veri girişinden önce.
  • Canlı MongoDB auth kapalı (Dokploy replica set betiği keyFile kullanmıyor, özel Command'ı yok sayıyor). İç ağda, port kapalı. Ayrıntı: docs/DEPLOY.md §4.
  • Üye çıkarma / rol değiştirme / sahipliği devretme henüz yok (Ayarlar ekranıyla birlikte, Faz 2+).
  • Kapalı dönem yeniden açma → çözüldü: düzeltme modu (correcting). Arayüzde düzeltmedeki dönem için Defter/Plan dönem seçimi Faz 6 Adım 5'te.
  • Altın kuru için veri kaynağı seçimi (Faz 8).
  • Aylık değerlendirme çıktı formatı kullanıcıdan gelecek (Faz 9).
  • Canlı MongoDB kurulum şekli (konteyner mi, doğrudan mı) Faz 3'te birlikte kontrol edilecek.

📝 İlerleme Günlüğü

2026-11-11 — Faz 0

  • Adım 1: Ortam kontrol edildi. CachyOS, fish. Node 26, npm 12, Docker 29 + Compose 5, Git, VS Code, gcc mevcut. Rust ve cargo-watch eksik; kurulum komutları verildi.
  • Adım 2: .vscode/ (extensions, settings, tasks, launch), .gitignore, .env.example ve monorepo klasörleri oluşturuldu. git init -b main.
  • Adım 3: CLAUDE.md, docs/MEMORY.md, docs/PHASES.md oluşturuldu. Gitea remote eklendi, ilk commit push edildi.
  • Adım 4: Rust kuruldu. docker-compose.dev.yml (mongo:7, rs0, healthcheck ile otomatik rs.initiate). Backend: Axum, yalnızca GET /api/health. Frontend: Vite react-ts şablonu, /api proxy → 8080, geçici durum ekranı. Kök package.json: concurrently ile npm run dev. Uçtan uca test edildi (Mongo PRIMARY, proxy çalışıyor).
  • Adım 5: docs/DATA_MODEL.md hazırlandı ve onaylandı. Faz 0 tamamlandı.

2026-11-11 — Faz 1

  • Adım 1: config.rs (env + doğrulama, JWT_SECRET ≥ 32), db.rs (5 sn timeout + ping), error.rs (AppError → {error:{code,message}} Türkçe; 5xx detayı yalnızca logda), state.rs, routes/ (health: DB yoksa 503). Açılışta DB/ayar hatasında anlaşılır log + çıkış kodu 1. Elle test: 4 senaryo geçti.
  • Adım 2: db/ modülü: col sabitleri, migrations.rs (schema_migrations, v1 = koleksiyonları oluştur), indexes.rs (DATA_MODEL §4 + tüm hane koleksiyonlarına (household_id, updated_at)). models/common.rs: UUID v7, new_meta, scope, touch. Açılışta db::prepare. 4 birim testi; idempotency ve tek açık dönem indeksi elle doğrulandı. uuid (v7) eklendi.
  • Adım 3: Kimlik doğrulama: auth/ (password argon2id + spawn_blocking, jwt, limiter, session cookie + AuthUser extractor), extract.rs (ValidJson = JSON + validator, Türkçe 400), services/account.rs (kayıt tek transaction: hane + owner + 13 kategori + 4 ödeme türü), services/seed.rs, routes/auth.rs (register/login/logout/me). Yeni crate'ler: argon2, jsonwebtoken, axum-extra(cookie), validator, time. 11 birim testi; 13 senaryo curl ile, rollback DB'de doğrulandı.
  • Adım 4: AuthUser DB doğrulaması + require_owner, services/invite.rs (create/list_active/revoke/claim), kayıt davet koduyla member olarak katılma (services/account.rs yeniden düzenlendi), routes/household.rs (GET /household, invites GET/POST/DELETE). 13 birim testi; 15 senaryo e2e (izolasyon 404, member 403, kod tekrar kullanımı, rollback) geçti.
  • Adım 5: models/group.rs, services/catalog.rs (kategori + ödeme türü ortak: list/create/update/reorder), routes/catalog.rs. Backend lib+bin'e ayrıldı (AppState::new). Entegrasyon testleri: tests/isolation.rs (3), tests/catalog.rs (4) — toplam 22 test. İzolasyon testi, kasıtlı açık eklenerek doğrulandı (yakaladı). db:reset artık test DB'lerini de siler. Dev-deps: tower, http-body-util. Faz 1 tamamlandı.

2026-11-12 — Faz 2

  • Adım 1: Tailwind v4 (@tailwindcss/vite), shadcn init (radix, preset nova; cn paketi = shadcn'in clsx+tailwind-merge yerine geçeni, incelendi), Inter (@fontsource-variable, çevrimdışı). index.css: grup + durum token'ları, odak halkası, money utility, reduced-motion. lib/format.ts (+22 vitest), lib/theme.tsx, lib/groups.ts, components/theme-toggle.tsx. Butonlar 44/48 px'e büyütüldü. Geçici önizleme ekranı (App.tsx), 375 px headless ekran görüntüsüyle iki temada kontrol edildi.
  • Adım 2: 3 logo taslağı (A indigo defter, B açık defter + para, C hanko) 512/64/32/16 px önizlemeyle sunuldu; kullanıcı A'yı seçti. scripts/icons.sh (npm run icons): favicon.svg/.ico, icon-192/512, icon-maskable-512, apple-touch-icon (180), brand/icon-1024 (Tauri). Maskable güvenli alan görsel olarak doğrulandı. index.html: ikon linkleri, theme-color, description.
  • Adım 3: ts-rs eklendi (17 tip), health ve hata gövdesi struct'a çevrildi (ApiErrorBody). Frontend: @tanstack/react-query, lib/api.ts (+7 vitest), lib/query.ts, lib/auth.ts, main.tsx'te QueryClientProvider. Geçici oturum paneli. Tarayıcı e2e (CDP betiği): yanlış şifre mesajı, giriş, yenilemede kalıcılık, httpOnly (document.cookie'de yok), çıkış — hepsi geçti.
  • Adım 4: react-router 8 (createBrowserRouter), RequireAuth/RequireGuest/Splash, AppLayout (masaüstü kenar çubuğu + mobil üst çubuk + 5'li alt menü, safe-area), Diğer sayfası, 7 yer tutucu sayfa, 404, lib/strings.ts, useDocumentTitle, lib/return-path.ts (+10 test, açık yönlendirme koruması), ThemeToggle compact. Tasarım önizlemesi → /dev/tasarim (yalnızca DEV). CDP e2e (10 kontrol) iki hata buldu, düzeltildi: giriş sonrası donus yarışı, çıkışta qc.clear(). Masaüstü içerik boşluğu düzeltildi.
  • Adım 5: react-hook-form, zod 4, @hookform/resolvers, sonner; shadcn input/label/checkbox/card (44 px / 20 px'e büyütüldü). components/form-field.tsx, components/toaster.tsx, features/auth/ (auth-shell, login-page, register-page, schemas + 8 vitest). Metinler t.auth. CDP e2e 11 kontrol geçti (boş form, yanlış şifre, giriş + toast, davet linki, kayıtlı e-posta → alan hatası, geçersiz davet → alan hatası, yeni hane). Frontend 47 test. Faz 2 tamamlandı.

2026-11-12 — Faz 3 öncesi

  • Kullanıcı api.http'deki kayıt örneğini (yeni@ornek.com) hesap sandı → dosya başlığına hazır hesaplar kutusu ve "bu bir KAYIT isteğidir" uyarısı eklendi.
  • Kayıt politikası: config.rs RegistrationMode, AppError::RegistrationClosed (403), account::new_household_allowed, GET /api/auth/registration (RegistrationInfo TS tipi). tests/registration.rs (2). Kayıt ekranı kapalıyken yalnızca davet modu + açıklama. .env/.env.example: REGISTRATION=invite_only. Prettier (.prettierrc.json, .prettierignore, format / format:check), tüm src biçimlendirildi (yalnızca boşluk).

2026-11-12 — Faz 3

  • Adım 1: tower-http (fs, compression gzip/br, trace). routes::app() + routes/spa.rs (ServeDir + index.html fallback + cache middleware), API fallback JSON 404, STATIC_DIR config, graceful shutdown (SIGTERM/Ctrl+C). Dockerfile, .dockerignore, docs/DEPLOY.md başlatıldı. İmaj yerelde çalıştırıldı (host network, 8090): 10 kontrol + tarayıcı ekran görüntüsü geçti. İlk derlemede Docker önbellek hilesi hatası (sahte lib.rs) bulundu, düzeltildi.
  • Adım 2: Mevcut tek MongoDB (butcem-mongo) Bütçem'e ait → kullanıcı ayrı MongoDB seçti. Dokploy MongoDB (replica set) kuruldu, kakeibo_app oluşturuldu, port yayını yok (iki servis de null). Sorunlar: kullanıcı şifreyi komuta düz yazıp paylaştı; Dokploy betiği root şifresini servis Args'ına düz yazıyor → iki şifre de mongosh içinde üretilerek yenilendi. passwordPrompt yapıştırma hatası (U_STRINGPREP). Auth açma denemesi: Dokploy özel Command'ı uygulamadı → auth kapalı kabul edildi (açık konu).
  • Adım 3–4: Dokploy Application kakeibo-app (iç ad kakeibo-kakeiboapp-nd5kek). Sorunlar: push yapılmamıştı (Dockerfile yok); Provider GitHub sekmesinde kalmıştı (Github Provider not found, kullanıcı çözdü); Traefik 404 (kullanıcı çözdü, büyük olasılıkla domain sonrası redeploy). Yayında: https://kakeibo.ta1sph.website — health ok, Let's Encrypt, ilk hesap açıldı. Faz 3 tamamlandı (yedekleme hariç).

2026-11-12 — Faz 4

  • Adım 1: services/calendar.rs (parse/format, weeks Pazartesi başlangıçlı + kısmi haftalar, Türkçe ay adları; 4 test), models/period.rs, services/period.rs (2 test), routes/period.rs (list/current/create/get/patch; DTO'da last_date, days, weeks). tests/periods.rs (5) — çakışma kontrolü bilerek bozulunca test yakaladı. Backend toplam 58 test.
  • Adım 2: models/plan_item.rs, models/note.rs, services/plan.rs (list/create/update/delete/copy_previous), services/note.rs, routes/plan.rs, routes/note.rs, period::get_open + closed(). tests/plan_notes.rs (5): CRUD + doğrulama, kopyalama (göreli gün, 28 Şubat'a kırpma, tekrar yok), kapalı dönem salt okunur, not filtresi, izolasyon. Backend 72 test.
  • Adım 3: services/money.rs (split_proportional + 3 test; beklenen değer hatası testteydi, elle hesapla düzeltildi), services/budget.rs (summary, view, set_monthly, set_week, suggest), routes/budget.rs (5 uç nokta, 9 TS tipi). tests/budget.rs (6) — özet formülü bilerek bozulunca yakaladı. Backend 88 test.
  • Adım 4: İlk dönem sihirbazı /plan/donem-ac?adim=1..5 (features/period-wizard/): Dönem → Gelirler → Giderler → Birikim → Bütçe. Dönem 1. adımda açılır, her adım hemen kaydedilir; adım adreste (yenileme/geri tuşu çalışır). lib/periods.ts (Query kancaları; değişince queryKeys.period(id) öneki yenilenir), lib/period-dates.ts (son gün dahil ↔ backend bitiş hariç), lib/money-schema.ts (moneyText, toKurus), components/money-input.tsx (inputMode=decimal, blur'da 1.250,50), PageLoading/PageError, SummaryTiles, NoPeriodCard (Panel + Plan). Frontend 60 test; CDP e2e 43 kontrol geçti, test verisi demo hanesinden silindi.
  • Not: üretim JS paketi ~620 kB (gzip 195 kB) → Faz 10'da sayfa bazlı bölme (lazy route).
  • Adım 5: Plan ekranı (features/planning/): plan-read.tsx (salt okunur: kalemler, 4 grup + haftalık döküm, "elle" etiketi), plan-edit.tsx (taslak formu, Kaydet/Vazgeç, useBlocker + beforeunload ile kaydedilmemiş değişiklik onayı). lib/plan-draft.ts (toDraft/diffPlan: yalnızca değişenler gönderilir), lib/budget-preview.ts (backend split_proportional + view ile birebir; aynı test örnekleri), useSavePlan (sıralı istekler; hata olursa yeniden yükler, transaction değil). Ortak: DistributionBar, SuggestionPicker (sihirbaz da kullanıyor). Frontend 69 test.
  • E2E artık ayrı sunucuda: REGISTRATION=open PORT=8091 STATIC_DIR=frontend/dist MONGODB_URI=…/kakeibo_test_e2e (kullanıcının dev verisine dokunulmaz; bitince DB silinir). 31 kontrol geçti; ekran görüntüsüyle 3 düzen sorunu bulundu/düzeltildi (dar satırlar, mobil tutar/tarih, hafta etiketi).
  • Adım 6: Notlar: features/notes/ (NotesSection — Plan'da context="planning", Değerlendirme'de review ile yeniden kullanılacak; NoteEditor Yaz/Önizle sekmeleri; zod şeması karakteri backend gibi sayar), lib/notes.ts (+ useHousehold yazar adı için), components/markdown.tsx (ham HTML yok, bağlantılar yeni sekme), components/ui/textarea.tsx, index.css .markdown stilleri. Yeni bağımlılık: react-markdown 10, remark-gfm 4. E2E 16 kontrol (XSS, 409 çakışma dahil). DistributionBar 0/0 durumunda "tamamı dağıtıldı" demiyor. Faz 4 tamamlandı.
  • Not: JS paketi 820 kB (gzip 254 kB; markdown ~200 kB) → Faz 10'da route bazlı lazy + Markdown'u ayrı parçaya al. npm audit uyarıları shadcn CLI bağımlılıklarından (deepmerge, minimatch), uygulamaya girmiyor; --force shadcn'i 1.0'a düşürür, yapılmadı.

2026-11-12 — Faz 5

  • Adım 1: Harcamalar backend: models/entry.rs, services/entry.rs (list + from/to filtresi, sıra date ↓ _id ↓; create/update/delete soft/restore), routes/entry.rs (5 uç nokta), catalog::find. Grup kategoriden türetilir (istemci göndermez). Pasif kategori/ödeme türü yeni seçimde reddedilir, kaydın mevcut değeri olarak kalabilir. Ödeme türü isteğe bağlı; düzenlemede "" → kaldır (projedeki "boş metin = kaldır" kuralı; ts-rs deserialize_with desteklemediği için çift-Option yerine). tests/entries.rs (6) — previous önerisi artık gerçek kayıtla test edildi. Mutasyon: grup türetme bozulunca yakalandı; restore'da hane filtresi tek başına kaldırılınca get_open ikinci katman olarak korudu, ikisi birden kaldırılınca 2 test yakaladı. Backend 97 test.
  • Adım 2: Gerçekleşen harcama (saklanmaz, her istekte aggregation): GET /summary → spent_total, remaining (= harcanabilir − harcanan, negatifse aşılmış). GET /budget → grupta spent (dönem), haftada spent. spent_by_group_and_date (grup×gün toplanır, Rust'ta haftalara dağıtılır; tarih metin karşılaştırması). Silinen kayıt sayılmaz, kategori değişince harcama yeni gruba geçer. Test actual_spending_in_summary_and_budget (hafta sınırları 6/7 Aralık, dönem son günü, önceki dönem hariç, geri al). Mutasyon: hafta sınırı <=→< ve silinenleri sayma → ikisi de yakalandı. Backend 98 test.
  • Adım 3: Defter ekranı (features/ledger/): WeekPicker (◀ ▶, "Bu hafta", haftanın harcanan/bütçe toplamı), EntryForm (büyük MoneyInput, 4 renkli grup düğmesi, kategori/ödeme türü çipleri, tarih, açıklama; kayıttan sonra tutar+açıklama temizlenir, odak tutara döner), schema.ts (entrySchema(start,last), fieldForError: sunucu mesajı → alan). Hafta adreste: /defter?hafta=N (bugünün haftasında parametre yok); başka haftaya tarihli kayıt → o haftaya geçer; hafta değişince tarih o haftaya çekilir. lib/ledger.ts (weekIndexFor, defaultEntryDate, parseWeekParam, son seçim localStorage kakeibo.ledger.last: grup + her grubun son kategorisi + ödeme türü; pasifleşen seçim düşer). lib/catalog.ts (useCategories, usePaymentMethods, 5 dk staleTime), lib/entries.ts (useEntries, useCreateEntry), queryKeys.entries. Frontend 79 test; e2e 31 kontrol (ayrı 8091 sunucu, DB silindi). Mobilde "Beklenmeyen" taşması düzeltildi.
  • Adım 4: Defter listesi ve özet: entry-list.tsx (gün gün groupByDay, gün adı formatWeekday = Intl tr-TR UTC; satır: kategori emojisi grup renginde, grup · ödeme · açıklama; düzenle/sil 44 px), sil → sonner action "↩️ Geri al" (8 sn) → useRestoreEntry. Düzenleme aynı EntryForm (entry + key, Güncelle/Vazgeç; version gönderilir, 409 Türkçe root hatası; '' → ödeme/açıklama kaldır). Katalog pasifler dahil okunur (eski kayıtların adı; seçenekte yalnız aktifler + kaydın mevcut değeri). week-summary.tsx grup bazlı bütçe/harcanan/kalan + çubuk, aşımda "Aşıldı" kırmızı. Masaüstü iki sütun (form solda yapışkan); dar sütunda taşmaya karşı container query (@container, @lg:grid-cols-4). Frontend 81 test; e2e 29 kontrol (+ Adım 3'ün 31'i yeniden geçti). Not: e2e betikleri aynı DB'yi paylaşırsa birbirini bozar → her biri temiz DB ile.
  • Adım 5: Yedekleme betikleri: scripts/backup.sh (Swarm etiketiyle konteyneri bulur, mongodump --archive --gzip → .partial → gzip -t + mongorestore --dryRun → ad verilir; son KEEP=14, BACKUP_GROUP ile okuma izni), scripts/restore.sh (dosya adından kaynak DB, --nsFrom/--nsTo ile başka adla; mevcut hedefe yalnızca --force + --drop; COMPARE_DB ile sayım karşılaştırma), scripts/backup-pull.sh (rsync SSH BatchMode, ~/Yedekler/kakeibo, son 60, 48 saatten eski → notify-send). deploy/systemd/ (sunucu 03:30, bu bilgisayar 09:00 + açılış, Persistent=true). Kullanıcı kararı: sunucu dışı kopya bu bilgisayara (S3 değil). npm run db:backup / db:restore (yerel, backups/ gitignore). Yerel test: yedek → geri yükleme sayıları kakeibo_dev ile aynı, koruma, bozuk dosya hatası, saklama ✅.

2026-11-12 — Faz 6

  • Adım 1: Değerlendirme backend: services/review.rs (budget::summary + budget::view + kategori aggregation), routes/review.rs → GET /periods/{id}/review (PeriodReview: available = gelir − sabit − borç + artı, savings_goal, spent_total, realized_savings = available − harcanan, savings_gap; groups planned/spent, categories çoktan aza (+count), weeks 4 grubun toplamı). Kapalı dönemde okunur. periods.reflection_md (PATCH, ≤ 20.000 karakter, "" → null, yalnızca açık dönem). tests/review.rs (3). Mutasyon: 4. soruya hedef düşmek / silinenleri saymak → yakalandı. Not: TestApp::register adı e-postaya çevirir → Türkçe karakterli ad kullanma.
  • Adım 2: Kapatma backend: services/closing.rs — settle(realized, to_savings) (< 0 → borç, birikim istenemez; ≥ 0 → varsayılan tamamı birikime, kalan artı), close (tek transaction: dönem closed + closed_at/by + to_savings, bu dönemden eski devirler soft delete, yeni devir; to_period_id = sonraki dönem ya da null), reopen (sahip; closed → correcting; ardından kapalı/düzeltmede dönem varsa 409). PeriodStatus::Correcting + is_writable() → get_open/update buna bakar; current() yalnız open. period::create artık transaction: önceki dönemin bağsız devrini bağlar. POST /periods/{id}/close (PeriodCloseRequest/PeriodCloseResult/Carryover), POST /periods/{id}/reopen. Period DTO: to_savings, closed_at. tests/closing.rs (4) + settle birim testi. Mutasyon 3/3 yakalandı; hata zorlanınca transaction geri aldı (dönem açık, sürüm aynı). Backend 113 test. Karar: yeniden açma = düzeltme modu (kullanıcı "a").
  • Adım 3: Değerlendirme sayfası (features/review/): Questions (4 kart; 1. kart hesap açıklaması, 3. bütçe yüzdesi, 4. hedefe göre durum lib/review.ts → savingsStatus: above/below/deficit), breakdowns.tsx (GroupComparison, TopCategories ilk 5 + "Tümünü göster", WeeklyDistribution; ortak Bar, ratio), Reflection (görüntüle/düzenle, useUpdatePeriod + version, 409 formda; şema features/review/schema.ts), NotesSection collapsed (yalnız başlık, aria-expanded ile açılır). Ortak components/markdown-tabs.tsx (Yaz/Önizle, useId kimlikler; NoteEditor da kullanıyor). useReview + queryKeys.review (dönem önekinde → kayıt değişince yenilenir). Dönem sürüyorsa bilgi satırı. Frontend 84 test; e2e 24 kontrol (Ekim kapatılıp Kasım'a artı devri dahil). Mobil haftalık satır sıkıştırıldı.
  • Adım 4: Kapatma sihirbazı /degerlendirme/kapat (features/review/close-page.tsx): Özet (4 soru, erken kapatma uyarısı, "Nasıl daha iyi" boşsa uyarı + bağlantı) → Birikim (artıda MoneyInput varsayılan tamamı, Tamamı/Hiçbiri, canlı önizleme; açıkta/sıfırda yalnız bilgi) → Onay (useClosePeriod, version; 409 formda, onSettled tüm ['period'] + liste yenilenir → ikinci deneme güncel sürümle) → Sonuç (sayfa durumunda; kapanınca current null). lib/review.ts → settlePreview (backend settle ile aynı tutulmalı), şema close-schema.ts (0..gerçekleşen). Stepper artık steps/label alıyor. Dönem açma sihirbazı tarih önerisi: suggestRanges(today, prevEnd) / rangeAfter — son dönemin bitişinden başlar, çakışan ay önerilmez (erken kapatılan ay tekrar önerilmezdi hatası önlendi). Not: moneyToText(0) → '' (plan taslağı için); 0 göstermek gerekiyorsa formatNumber. Frontend 89 test; e2e 33 kontrol (DB silindi, 8091 kapatıldı).
  • Adım 5: Dönem seçimi: lib/period-select.ts (useSelectedPeriod: ?donem= → listeden; yoksa açık dönem, o da yoksa düzeltmedeki; pickPeriod, isWritable, canReopen = backend kuralı, periodPath), features/periods/ (PeriodPicker başlıkta <select>, açık döneme dönünce parametre kalkar; SelectedPeriod yükleniyor/hata/bulunamadı/yok kapısı). Defter, Plan, Değerlendirme, kapatma sihirbazı buna geçti (Panel ve dönem sihirbazı hâlâ useCurrentPeriod). Defter kapalı dönemde salt okunur (form yok, EntryList readOnly); hafta değişimi donem'i korur. Plan: düzenleme isWritable, kopyalama yalnız açık. PeriodHeading durum rozeti. Değerlendirme sonu ClosingSection (açık → Kapat · düzeltmede → Defter'de/Plan'da düzelt + Yeniden Kapat · kapalı → kapanış bilgisi + sahip için satır içi onaylı "Düzeltme için yeniden aç") + PeriodHistory. useReopenPeriod; useUpdatePeriod liste önbelleğini de günceller (seçili dönem listeden → art arda kayıtta 409 olmasın). Ders: başarıda bileşen kalkıyorsa mutate(…, {onSuccess}) çalışmaz → mutateAsync kullan. Frontend 92 test; e2e 36 + kapatma 33 yeniden (DB silindi). Faz 6 ✅.

2026-11-12 — Faz 7

  • Kararlar (kullanıcı onayı): toplam birikim Faz 8'e kadar = kapanışlarda birikime aktarılanların toplamı; sekmeler Günlük/Haftalık = bu dönem (kümülatif, harcanabilir günlere eşit dağıtılır), Aylık = son 12 dönem; açık dönem yoksa son dönem salt okunur.
  • Adım 1: services/stats.rs + routes/stats.rs: GET /periods/{id}/daily (DaySpent[]: dönemin her günü, spent = GroupAmounts, harcama yoksa 0; budget::spent_by_group_and_date artık pub(crate)), GET /stats/periods?limit=12 (PeriodStats: total_saved tüm dönemlerin to_savings toplamı — limitten bağımsız, yeniden açılan dönemin eski tutarı sayılır; periods eskiden yeniye, her biri budget::summary ile → dönem başına ~5 sorgu, en fazla 24). tests/stats.rs (2). Mutasyon 2/2 yakalandı. Backend 118 test.
  • Adım 2: Panel (features/dashboard/): page.tsx (açık dönem; yoksa NoPeriodCard + son dönem salt okunur, lib/dashboard.ts → dashboardPeriod), overview.tsx (kartlar: gelir, sabit, toplam birikim useStats().total_saved, borç yalnızca varsa — 3 kartta ızgara 3 sütun; Anlık durum DOM'da önce/mobilde üstte, masaüstünde sağda: kalan harcanabilir + dailyAllowance = kalan ÷ kalan gün (bugün dahil, kuruş aşağı); gruplar WeekSummary artık index: number | 'month', Haftalık/Aylık geçişi; boş durumlar: bütçe dağıtılmamış → Plan, harcama yok → Defter). "Harcama Ekle": masaüstünde başlıkta, mobilde yüzen (alt menünün üstü) → /defter state.focus → EntryForm autoFocus. components/skeleton.tsx. queryKeys.stats = ['periods','stats'] (kapatma/plan ile yenilenir), kayıt mutasyonları da stats'ı yeniler. Frontend 95 test; e2e 22 kontrol (DB silindi). Canlı yayın 2026-11-12 güncellendi (kullanıcı Deploy yaptı; /api/stats/periods 401).
  • Adım 3: Grafikler (features/dashboard/charts.tsx, Recharts 3, lazy ayrı parça ~110 KB gz): Günlük/Haftalık/Aylık (Segmented — components/segmented.tsx, Panel grup kartı da kullanıyor). Seriler lib/charts.ts (dailySeries: harcanabilir günlere eşit + kümülatif harcanan, gelecek gün null; weeklySeries: hafta sonu planı, içinde bulunulan hafta bugüne kadar; monthlySeries: useStats dönemleri; lastPoint) + test. Backend: budget::Summary.spent_by_group → PeriodStat.spent_by_group (aylık grup grafiği). Grup grafiğinde harcanabilir çizgisi varsayılan kapalı (ölçeği ezip grup çizgilerini sıfıra yapıştırıyordu), aria-pressed düğmeyle açılır. Lejant/ipucu sırası itemSorter ile sabit (Recharts 3 varsayılanı alfabetik). Grafik aria-hidden, altında metin özeti (figcaption). formatCompact (eksen, bölünmez boşluk). Ders: --color-* değişkenleri @theme inline yüzünden derlenmiş CSS'te yok → SVG'de ham var(--needs) (CLAUDE.md'ye eklendi); e2e'de çizgi rengi getComputedStyle ile denetlenir. Tam sayfa ekran görüntüsü (captureBeyondViewport) ResponsiveContainer'ı küçültebilir → grafik için görünen alan görüntüsü al. Frontend 101, backend 118 test; e2e 18 kontrol. npm audit uyarıları shadcn CLI'dan (geliştirme aracı; --force shadcn'i 1.0'a düşürür → uygulanmadı). Faz 7 ✅.

2026-11-13 — Faz 8

  • Adım 1: Varlıklar backend: models/asset.rs (AssetKind cash/gold/fx, TxType buy/sell), services/holding.rs (saf: ağırlıklı ortalama maliyet, tam satışta artık maliyet sıfırlanır, Oversold, i128 mul_div; 5 birim test), services/asset.rs (liste + hesaplanmış durum, döviz ekleme 3 harf/tekil, ad/aktiflik, işlem ekle/düzenle/sil; her yazmada tarih sıralı yeniden denetim; TL: miktar = tutar), routes/asset.rs (7 uç nokta). Varsayılan 8 varlık seed::assets (kayıtta) + migration v2 (eski haneler, idempotent → canlıdaki hane uygulama yeniden başlayınca alır). tests/assets.rs (4). Mutasyon 3/3 yakalandı. Backend 135 test.
  • Adım 2: Kurlar: reqwest 0.12 (rustls-tls → MongoDB ile aynı ring; 0.13 aws-lc-rs getiriyordu), quick-xml (serialize). services/rate_sources.rs (saf: parse_tcmb ForexBuying ÷ Unit, boş alış atlanır, decimal_e6 kayan noktasız; parse_gold xaus per_gram_usd; gold_rates has gram katsayıları; testler örnek metinle), services/rates.rs (refresh: iki kaynak bağımsız, TCMB düşerse DB'deki son USD; gün başına upsert; spawn_refresher main'de, RATES_REFRESH_MINUTES varsayılan 60 / 0 kapalı; current: aggregation kod×(oto|hane) en yeni → en yeni tarih, eşitlikte manuel; set_manual/delete_manual; portfolio + value_of yarım yukarı). GET /portfolio, POST /rates, DELETE /rates/{id}. calendar::today_tr (UTC+3). Testler sahte axum sunucusuyla (internet yok): tests/rates.rs (2) + birim (4). Mutasyon: öncelik, USD yedeği yakalandı; yuvarlama kaçtı → value_of birim testi eklendi, artık yakalıyor. Gerçek kaynak denemesi (test DB): TCMB 22 döviz, altın 5 kod (gram 6.253,82, çeyrek 10.024,88 ₺). Backend 145 test. DEPLOY §2 + .env.example: RATES_REFRESH_MINUTES, konteynerin internete çıkışı gerekir.
  • Adım 3: Birikim sayfası (features/savings/): page.tsx (özet: toplam değer + son kur zamanı, maliyet, kâr/zarar stacked, gerçekleşen; uyarılar: kur hiç yok / 6 saattir çekilemedi / kursuz varlık; dağılım halka grafiği allocation-chart.tsx lazy 6 KB, renkli liste = metin özeti; varlık listesi: elde/geçmişi olanlar + <details> "Diğer varlıklar"; add-currency.tsx), asset-page.tsx (/birikim/:id: durum kartları, RateBox elle kur gir/kaldır, TxFormView tx-form.tsx alım/satım: miktar × birim fiyat → tutar önerisi, elle değişen tutar korunur, yeni alımda birim fiyat = güncel kur (kuruşa yuvarlı), TL'de yalnız tutar, satışta eldeki sınırı; geçmiş: düzenle + iki adımlı sil; pasife al/etkinleştir). lib/decimal.ts (parseE6: virgül ondalık, tek nokta + 3 rakam binlik, formatE6, formatRate, valueKurus BigInt = backend), lib/savings.ts (plRatio, formatPlPercent -%0,6 ve ≈0 gizli, isFetchStale 6 saat, rateAgeDays, splitAssets, allocation, kindEmoji, qty), lib/assets.ts (kancalar, hepsi ['portfolio'] yeniler), components/decimal-input.tsx, formatDateTime. Frontend 114 test; e2e 37 kontrol (kurlar DB'ye elle, RATES_REFRESH_MINUTES=0). Düzeltilen: %-0,0, TL satırında tekrar, mobil özet taşması; Tailwind keyfi alt seçici ([&>*]) lg: ile ezilemedi → sınıflar öğenin kendisine.
  • Adım 4: Kapanış → birikim. Backend (services/asset.rs): add_period_savings (yalnız closed, to_savings > 0, dönem başına tek → 409; TL varlığı pasif olsa da; not " kapanışı", tarih son gün), pending_period_savings, plan_savings_sync (transaction öncesi: bağlı kayıt + TL geçmişi denetimi → SavingsSync::Update/Delete) + apply_savings_sync (kapatma transaction'ı içinde). PeriodCloseResult.savings_linked. GET /savings/pending, POST /periods/{id}/to-savings. tests/savings_link.rs (3), mutasyon 3/3. Frontend: kapatma sonucunda AddToSavings (onay düğmesi → "Birikime eklendi" + bağlantı; yeniden kapatmada "kayıt güncellendi"), features/savings/pending.tsx (Birikimler üstünde), Panel "Toplam birikim" = portfolio.value_total (stats.total_saved artık arayüzde kullanılmıyor; backend'de duruyor). useClosePeriod portföyü de yeniler. Backend 149, frontend 114 test; e2e 14 kontrol. Faz 8 ✅.