CLAUDE.md, docs/PORTABILITA.md e mem/features/cloud-efficienza.md riflettono che rosa/classifica/storico partite vengono ora da Strapi (non più hardcoded) e che lo scout finalizzato viene anche inviato a Strapi, oltre al localStorage. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
7.7 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
What this is
CrAPP — a mobile-first web app for managing an amateur volleyball team (CRAP Volley): attendance,
matches/trainings/events, live match scouting, player stats/badges, CSI league standings, push
notifications. Italian-language codebase (routes, variables, comments). Built with Lovable
(lovable.dev); pushes to main sync back into the Lovable editor — avoid rewriting published git
history (force-push, rebase/amend/squash of pushed commits).
Commands
Package manager is bun (bun.lock is the lockfile; package-lock.json also exists but bun is
primary — see bunfig.toml).
bun run dev # vite dev server
bun run build # production build (nitro/vite)
bun run build:dev # development-mode build
bun run preview # preview a build
bun run lint # eslint .
bun run format # prettier --write .
There is no test runner configured in this repo.
Adding a dependency: bunfig.toml enforces a 24h supply-chain guard (minimumReleaseAge) on new
packages; only pre-listed @lovable.dev/* packages bypass it. Confirm with the user before adding
new bypass entries.
Architecture
Stack: TanStack Start (React 19, file-based router) + Vite, styled with Tailwind v4 +
shadcn/radix components, data via @supabase/supabase-js, TanStack Query for client cache.
Routing
File-based routing under src/routes/ (see src/routes/README.md). One root layout,
src/routes/__root.tsx, wraps every page — preserve its <Outlet />. $id.tsx = dynamic segment,
{-$category}.tsx = optional segment, $.tsx = splat (_splat param). Do not hand-create
src/pages/ or Next/Remix-style layout files. src/routeTree.gen.ts is auto-generated — never
edit it directly.
Server-only HTTP endpoints live under src/routes/api/public/*.ts (e.g. push subscription,
palloni/presenze reminders) — plain HTTP handlers, not proprietary edge functions, so they can run
under any Node host or scheduler (system cron, pg_cron, etc).
Server entry / SSR error handling
src/start.ts registers global middleware: attachSupabaseAuth (client-side function middleware
that attaches the Supabase bearer token to every server-fn RPC — see
src/integrations/supabase/auth-attacher.ts, which is auto-generated, do not hand-edit) and an
error middleware + explicit CSRF middleware (createCsrfMiddleware) for server functions. Defining
src/start.ts opts out of Start's automatic CSRF middleware, so this file must keep re-adding it.
src/server.ts wraps the generated TanStack Start server entry and normalizes a specific h3
failure mode: h3 swallows in-handler throws into a 500 JSON body
({"unhandled":true,"message":"HTTPError"}) that a plain try/catch never sees, so it's detected by
inspecting the response body and converted into renderErrorPage()'s HTML error page.
Data layer — portability rule
The project must remain deployable on plain Node.js + PostgreSQL, not locked into Lovable Cloud
(see docs/PORTABILITA.md). Concretely:
- Never query the database (or the Strapi CMS) from components. All data access goes through
modules in
src/lib/*.ts(e.g.palloni.ts,mvp-voti.ts,eventi.ts,presenze.ts,rosa.ts,scout-*.ts) — each exports TanStack Query hooks (useX) wrappingsupabase.from(...)or, for Strapi-backed data,strapiFetch(...)fromsrc/lib/strapi-client.ts(seerosa-base.ts,classifica-csi.ts,storico-match.ts). This keeps the backend swappable in one place. src/integrations/lovable/*(social login) is optional and unused by any screen — safe to remove without impact.- No provider-exclusive features: no edge functions, no Lovable-only auth as the sole login method,
no proprietary storage. Config only via standard env vars (
DATABASE_URL/SUPABASE_URL+ keys,VAPID_*), never hardcoded. - SQL migrations in
supabase/migrations/must use standard PostgreSQL, no provider-exclusive extensions.
Cloud-efficiency rules (small paid-tier budget)
The Supabase/Lovable Cloud plan is metered (~20 credits/month, ~17 users), so these constraints are load-bearing, not style preferences:
- No polling (
refetchInterval) against the database; prefer local sync via BroadcastChannel/storage. - Global QueryClient (
src/router.tsx) is deliberately configured for infrequent refetching:staleTime5 min,gcTime30 min,refetchOnWindowFocus/Mount/Reconnectall off,retry: 1. Don't override this per-query without a reason. - After a mutation, update the query cache with
setQueryData(seeuseAssegnaTurnoinsrc/lib/palloni.tsfor the pattern) rather thaninvalidateQueries, to avoid an extra read. - Stats/badges/standings are "write once, read many": computed and persisted once (e.g. at match end), never recomputed on every page open.
- Live match scouting: only the scoring user writes; everyone else reads already-saved data.
- Player roster, CSI league standings and match history come from a self-hosted Strapi CMS
(
infra/strapi/, its own Postgres database on the same cluster as Supabase) — editing happens only in Strapi's own admin panel (/cms/admin), never in app UI. The app reads them read-only viasrc/lib/rosa-base.ts/classifica-csi.ts/storico-match.ts(longstaleTime, no polling — same cloud-efficiency rules apply). Player identity is the stablecodicefield (g1,g2, …) on the Strapigiocatorecontent type, not Strapi's own numeric id — every Supabase table that references a player by id (pagelle_voti,cacche_partita, MVP votes, ball-duty turns, presenze, scout actions) keys on that string, so it must never change for an existing player. - A scouted match result (
ScoutMatch, fromscout.tsx/scout-store.ts) is saved to the scoring device'slocalStorage(keycrapp-scout-v1, still the sourceuseRosa/giocatoriConScoutread for stats) and pushed best-effort to Strapi'sscout-match-finalecontent type via/api/public/scout-finaleat match end, so it becomes visible cross-device in the admin panel — no retry if that POST fails, the local save already succeeded either way. The in-progress resume state still goes only to thescout_liveSupabase table, unrelated to Strapi. - Push notifications only for high-value events (convocations, training/match reminders, ball-duty turn, final result) — no chat/photo/video features.
Auth
Supabase Auth (GoTrue), attached via middleware rather than per-request boilerplate — see
auth-attacher.ts / auth-middleware.ts above. Self-hostable; not tied to Lovable-proprietary
auth.
Project memory (mem/)
mem/index.md indexes feature-level design notes (mem/features/*.md) — currently the portability
rule and the cloud-efficiency rules summarized above. Check there before adding a feature that
might conflict with either constraint.
Conventions
- Language: Italian. Code comments and git commit messages must be written in Italian, matching the rest of the codebase (routes, identifiers, existing comments/commits are already Italian).
- Path alias
@/*→src/*(seetsconfig.json). - Prettier: 100-char width, double quotes (
singleQuote: false), trailing commas everywhere. - TypeScript strict mode plus extra strictness:
noUncheckedIndexedAccess,exactOptionalPropertyTypes,noImplicitReturns,noImplicitOverride,noPropertyAccessFromIndexSignature. vite.config.tsis intentionally minimal:@lovable.dev/vite-tanstack-configalready bundles TanStack devtools,tanstackStart,viteReact,tailwindcss,tsConfigPaths, nitro, env injection, and the@alias — do not re-add any of those plugins manually or the app breaks with duplicate plugins.