diff --git a/CLAUDE.md b/CLAUDE.md index f2ed962..854655d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -68,7 +68,8 @@ npm run db:restore -- [--force] - Frontend: Tailwind v4 (`src/index.css` token'ları), shadcn/ui (style `radix-nova`, `cn` paketi), `@/` → `src/`. Renkler: `bg-needs`/`text-needs-fg`/`bg-needs-soft` (wants, culture, unexpected aynı), `text-income`/`text-expense`/`text-warning`. SVG/JS'te (Recharts vb.) ham değişken kullan: `var(--needs)`, `var(--primary)` — `@theme inline` yüzünden `--color-*` derlenmiş CSS'te olmayabilir. Tutarlarda `money` sınıfı. Butonlar ≥44 px (shadcn boyutları büyütüldü). - API: `lib/api.ts` (`api.get/post/...`, hata → `ApiError` Türkçe `message`), `lib/query.ts` (QueryClient + `queryKeys`), `lib/auth.ts` (`useMe` → undefined/null/MeResponse, `useLogin`/`useRegister`/`useLogout`). - Tipler: `frontend/src/types/` **elle düzenlenmez**; backend'de DTO'ya `#[derive(TS)] #[ts(export)]` ekle → `npm run types`. İstek yapılarında `optional_fields`. -- Sayfalar: `src/router.tsx`; adresler `components/layout/nav-items.ts` (`paths`); sayfa bileşenleri `features//page.tsx`; iskelet `components/layout/app-layout.tsx`. Sayfa başlığı `PageHeader` (sekme başlığını da ayarlar). Metinler `lib/strings.ts` (`t`). +- Sayfalar: `src/router.tsx` — her sayfa **ayrı JS parçası** (`lazy: page(() => import(...), 'XPage')`; yeni sayfa ekleyince statik import etme). `main.tsx` `prefetchMe` (oturum isteği sayfa koduyla paralel), `AppLayout` alt menü sayfalarını boşta önden indirir + gezinmede ince ilerleme çizgisi. İlk yükleme yedeği `Splash`. +- Sayfa adresleri: adresler `components/layout/nav-items.ts` (`paths`); sayfa bileşenleri `features//page.tsx`; iskelet `components/layout/app-layout.tsx`. Sayfa başlığı `PageHeader` (sekme başlığını da ayarlar). Metinler `lib/strings.ts` (`t`). - Yönlendirme: `RequireAuth` (oturum yok → `/giris?donus=`), `RequireGuest` (oturum var → `donus` ya da panel; formlar ayrıca navigate etmez). `donus` daima `safeReturnPath` ile. - Geliştirmede tasarım önizlemesi: `/dev/tasarim`. - Formlar: react-hook-form + zod (`mode: 'onTouched'`), alanlar `components/form-field.tsx` (`FormField`, `PasswordInput`, `FormError`); sunucu hatası `setError` ile ilgili alana. Bildirim: `toast` (sonner, `components/toaster.tsx`). diff --git a/docs/MEMORY.md b/docs/MEMORY.md index c7beedf..b57ad62 100644 --- a/docs/MEMORY.md +++ b/docs/MEMORY.md @@ -1,7 +1,7 @@ # 🧠 Proje Hafızası ## ▶️ Sıradaki Adım -**Faz 10 – Adım 4:** Performans — `npm run build` çıktısında paket boyutlarını ölç (ana JS, Recharts/SheetJS zaten lazy), sayfa bazlı kod bölme (`React.lazy` route), Axum statik dosyalarda `/assets/*` (hash'li) için `Cache-Control: public, max-age=31536000, immutable`, `index.html` için `no-cache`; sıkıştırma zaten var. Önce/sonra ölçümü raporla. +**Faz 10 – Adım 5:** PWA — `vite-plugin-pwa` (gerekçe: manifest + Workbox service worker üretimi; CLAUDE.md yığınında var): manifest (ad, kısa ad, tema rengi, `display: standalone`, `start_url: /`, ikonlar `public/icon-192/512/maskable` zaten var), service worker yalnız uygulama kabuğu + hash'li `/assets/*` (precache), **`/api` asla önbelleğe alınmaz** (NetworkOnly), SPA gezinme `index.html` (navigateFallback, `/api` hariç), güncelleme bildirimi (`registerType: 'prompt'` → sonner "Yeni sürüm var — Yenile"), çevrimdışıyken anlaşılır mesaj. Axum: `sw.js` ve `manifest.webmanifest` `no-cache`. iOS: apple-touch-icon zaten var. Önce kullanıcıya kısa özet. **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 @@ -151,3 +151,4 @@ - **Faz 10 – Adım 1 (2026-11-13):** Ayarlar menüsü (`features/settings/page.tsx`: Kategoriler, Ödeme türleri, Veri ve yedek + tema) ve alt sayfalar (`subpage.tsx`, `catalog-pages.tsx`, `data-page.tsx`); ekstre sayfası "‹ Veri ve yedek"e döner. `catalog-editor.tsx`: satırda ↑ ↓ ✎ (44 px, `aria-label` adlı), düzenleme formu ad + emoji (hızlı seçim paleti, `aria-pressed`), Pasifleştir/Yeniden etkinleştir + toast "Geri al" (sürümsüz PATCH), aynı ad hatası backend'den. `lib/catalog.ts` mutasyonları `['categories'|'payment-methods']` + rapor/dönem önbelleğini yeniler. Telefonda grup başlığı bölünüyordu → açıklama alta. Frontend 136 test; e2e 25 kontrol (DB silindi). - **Faz 10 – Adım 2 (2026-11-13):** Backend: `PATCH /auth/me` (ad), `POST /auth/me/password` (mevcut şifre + limiter `password:`, `sessions_valid_from` → diğer oturumlar 401, bu cihaza yeni çerez, "beni hatırla" eski çerezin `exp-iat`'ından), `PATCH /household` (sahip: ad, `budget_split_pct` toplam 100, `budget_suggestion`), `DELETE /household/members/{id}` (sahip; kendini/sahibi çıkaramaz; e-posta `#removed-` → yeniden davet edilebilir). `GET /household` artık `settings` döner. `services/account.rs`. Testler `tests/account.rs` (4) + 2 bilinçli bozma yakalandı. Frontend: `/ayarlar/hane` (ad, üyeler + çıkar onaylı, davet oluştur → panoya kopyala / paylaş / iptal), `/ayarlar/hesap` (ad, e-posta salt okunur, şifre formu: zod tekrar + farklı, sunucu hatası alana), `/ayarlar/butce` (4 yüzde canlı toplam, 50/30/10/10 düğmesi, yöntem radyo; üye salt okunur). Öneri seçici ve sihirbaz hanenin yöntemiyle açılır (veri yoksa yüzdelere düşer). E2E 28 kontrol: **davet bağlantısıyla gerçek kayıt** dahil (`REGISTRATION=invite_only`). Not: başsız Chrome'da pano için `Emulation.setFocusEmulationEnabled`. Backend 205, frontend 140 test. - **Faz 10 – Adım 3 (2026-11-13):** Tarama betiği (scratchpad `e2e/scan.mjs`): 21 sayfa × telefon açık/koyu + masaüstü (+ boş hane, giriş/kayıt); sayfa içi denetim (adsız denetim, <44 px hedef, canvas ile oklch→sRGB kontrast, tek h1, alt, lang, taşma) + Tab ile odak halkası. İlk sonuç 89 kontrast / 75 küçük hedef / 27 adsız / 3 h1 → benzersiz: muted yazı 4,35 (→ token 0.52), yeşil 4,37 (→ #166534), `sm` düğmeler 36 px (→ pointer-coarse 44), GFM görev kutusu adsız (→ "Tamamlandı/Yapılacak"), rapor 2 h1 (→ `shiftHeadings`). **Ekran görüntüsünden:** telefonda Defter formu taşıyordu (ızgara öğesi `min-width:auto` 425 px) — otomatik kontrol kaçırdı çünkü mobil öykünmede `innerWidth` da büyür → kontrol `screen.width`/390 ile; Raporlar grafik kartları da taşıyordu → `Section` + rapor bölümüne `min-w-0`. Eklenen: %80 bütçe uyarısı (`lib/budget-level.ts`), Raporlar boş durumu (dönem yok → Dönemi başlat / Ekstreden aktar; filtre boş → Filtreleri temizle), tutarlarda bölünmez boşluk. Sonra: kontrast/adsız/h1/telefon hedef 0. Frontend 142 test. +- **Faz 10 – Adım 4 (2026-11-13):** Önbellek başlıkları zaten doğruydu (Faz 5: `/assets/*` 1 yıl immutable, HTML no-cache, sıkıştırma). Ana JS 950 KB (gz 284) tek paket → sayfa bazlı `lazy` route: ana 402 KB (gz 128), sayfalar 2–23 KB; Markdown (49 KB gz) ve zod/form (40 KB gz) yalnız kullanan sayfada. İlk sürümde Defter yavaşlamıştı (lazy route → `/auth/me` sıralı) → `prefetchMe` ile paralel; `HydrateFallback: Splash`. Boşta alt menü sayfalarını önden indirme + `useNavigation` ilerleme çizgisi. Ölçüm (scratchpad `e2e/perf.mjs`: yavaş 4G 1,6 Mbps/150 ms + CPU 4×, soğuk; eski sürüm `git worktree` ile ayrı derlenip aynı koşulda, 2 tekrar ort.): Giriş 2,97→2,23 sn, Panel 2,42→2,03, Raporlar 2,36→2,11, Defter 2,33→2,37 (aynı); hazırda JS 295→171–221 KB. Regresyon: katalog (25) + rapor (21) e2e geçti. Not: e2e rapor başlığı artık `.md-h1`. diff --git a/docs/PHASES.md b/docs/PHASES.md index 60de982..17ad28f 100644 --- a/docs/PHASES.md +++ b/docs/PHASES.md @@ -90,5 +90,5 @@ - ✅ 1. Ayarlar yeniden düzeni (`/ayarlar` menü + tema) · **Kategori yönetimi** (grup grup ekle, ad/emoji düzenle, sırala, pasifleştir) · **Ödeme türü yönetimi** · Veri sayfası `/ayarlar/veri` - ✅ 2. Hane ve hesap: hane adı, üyeler, **davet bağlantısı** (oluştur/kopyala/iptal), üye çıkarma; profil adı, **şifre değiştirme**; varsayılan bütçe yüzdeleri + öneri yöntemi - ✅ 3. Arayüz cilası ve erişilebilirlik: tüm sayfaların telefon/masaüstü/koyu tema taraması, boş durumlar, skeleton, %80 bütçe uyarısı, odak/klavye, kontrast, onay diyalogları -- 🔄 4. Performans: sayfa bazlı kod bölme (lazy route), statik dosyalarda uzun önbellek, paket boyutu -- ⏳ 5. PWA: manifest + service worker (uygulama kabuğu; `/api` asla önbelleğe alınmaz), kurulum, güncelleme bildirimi +- ✅ 4. Performans: sayfa bazlı kod bölme (lazy route), statik dosyalarda uzun önbellek, paket boyutu +- 🔄 5. PWA: manifest + service worker (uygulama kabuğu; `/api` asla önbelleğe alınmaz), kurulum, güncelleme bildirimi diff --git a/frontend/src/components/layout/app-layout.tsx b/frontend/src/components/layout/app-layout.tsx index 4ea061d..ce7880f 100644 --- a/frontend/src/components/layout/app-layout.tsx +++ b/frontend/src/components/layout/app-layout.tsx @@ -1,5 +1,6 @@ +import { useEffect } from 'react' import { LogOut } from 'lucide-react' -import { NavLink, Outlet, ScrollRestoration, useLocation } from 'react-router' +import { NavLink, Outlet, ScrollRestoration, useLocation, useNavigation } from 'react-router' import { Logo } from '@/components/logo' import { ThemeToggle } from '@/components/theme-toggle' import { Button } from '@/components/ui/button' @@ -14,8 +15,10 @@ import { bottomNav, moreNav, paths, sideNav } from './nav-items' * - Masaüstü (≥ md): solda kenar çubuğu */ export function AppLayout() { + usePrefetchMainPages() return (
+ {/* Mobil üst çubuk */} @@ -37,6 +40,45 @@ export function AppLayout() { ) } +/** + * Alt menüdeki ana sayfaların kodu, tarayıcı boşta kalınca önceden indirilir + * (ilk açılışı yavaşlatmaz; menüde gezinirken bekleme olmaz). + */ +function usePrefetchMainPages() { + useEffect(() => { + const load = () => { + void import('@/features/dashboard/page') + void import('@/features/ledger/page') + void import('@/features/planning/page') + void import('@/features/savings/page') + } + if ('requestIdleCallback' in window) { + const id = window.requestIdleCallback(load, { timeout: 5000 }) + return () => window.cancelIdleCallback(id) + } + const id = setTimeout(load, 2000) + return () => clearTimeout(id) + }, []) +} + +/** Sayfa kodu inerken (yavaş bağlantı) üstte ince ilerleme çizgisi; anlık geçişte görünmez */ +function NavigationProgress() { + const loading = useNavigation().state === 'loading' + return ( +
+ ) +} + function Sidebar() { return (