Files
CRAPP/CLAUDE.md
T
davideandClaude Sonnet 5 84313b1955 Aggiorno documentazione e memoria di progetto per il CMS Strapi
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>
2026-08-28 15:40:08 +02:00

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) wrapping supabase.from(...) or, for Strapi-backed data, strapiFetch(...) from src/lib/strapi-client.ts (see rosa-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: staleTime 5 min, gcTime 30 min, refetchOnWindowFocus/Mount/Reconnect all off, retry: 1. Don't override this per-query without a reason.
  • After a mutation, update the query cache with setQueryData (see useAssegnaTurno in src/lib/palloni.ts for the pattern) rather than invalidateQueries, 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 via src/lib/rosa-base.ts / classifica-csi.ts / storico-match.ts (long staleTime, no polling — same cloud-efficiency rules apply). Player identity is the stable codice field (g1, g2, …) on the Strapi giocatore content 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, from scout.tsx / scout-store.ts) is saved to the scoring device's localStorage (key crapp-scout-v1, still the source useRosa/ giocatoriConScout read for stats) and pushed best-effort to Strapi's scout-match-finale content type via /api/public/scout-finale at 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 the scout_live Supabase 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/* (see tsconfig.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.ts is intentionally minimal: @lovable.dev/vite-tanstack-config already 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.