# 🧠 Proje Hafızası ## ▶️ Sıradaki Adım **Faz 7 – Adım 3:** Grafikler (Recharts — yeni bağımlılık, gerekçe ver): 1) harcanabilir (günlere eşit dağıtılmış, kümülatif) vs gerçekleşen (kümülatif), 2) 4 grup kümülatif + harcanabilir çizgisi; sekmeler Günlük (`/periods/{id}/daily`) / Haftalık (aynı veriden `period.weeks` ile) / Aylık (`useStats` → son 12 dönem, harcanabilir vs harcanan). Saf seri hesapları `lib/` + test; koyu tema renkleri CSS token'larından; grafik altında kısa metin özeti (erişilebilirlik). **Bekleyen soru:** kategori yönetimi ekranı Faz 7'den hemen sonra mı (önerilen), Faz 10'da mı? **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 - **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 `touch`lanı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` 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 `