# Coding Rules – Mitai Jinkendo

Diese Regeln IMMER befolgen. Sie basieren auf Erfahrungen aus der Entwicklung.

## Backend

### 1. Auth und Mandantenkontext (Shinkan)

**Jeder geschützte Endpoint braucht Auth.** Sofern der Endpoint **Vereinsdaten**, **visibility/club_id** oder **mandanten-gefilterte Listen** betrifft, zusätzlich **`TenantContext`** — nicht nur `require_auth` allein.

```python
from tenant_context import TenantContext, get_tenant_context

@router.get("/beispiel")
def beispiel(tenant: TenantContext = Depends(get_tenant_context)):
    pid = tenant.profile_id
    role = tenant.global_role
    club_ctx = tenant.effective_club_id  # kann None sein (z. B. Plattform-Admin)
```

- **Bibliotheks-/Planungslisten:** Filter wie bestehende Module (`library_content_visibility_sql` oder gleiche Leseprüfung); keine vollständige Tabelle für normale Nutzer.
- **Schreiben:** `assert_valid_governance_visibility` aus `club_tenancy`, wenn `visibility` / `club_id` gesetzt werden.
- **Dokumentation:** Änderungen in `.claude/docs/working/ACCESS_LAYER_ENDPOINT_AUDIT.md` festhalten.
- **Ausnahmen** (z. B. reiner Login, globale Kataloge): Kommentar `# ACCESS_LAYER exempt: …` und ggf. Eintrag in `backend/scripts/check_access_layer_hints.py`.

Reine Plattform-Admin-Router (ohne Vereinskontext) können bei Bedarf weiter `Depends(require_auth)` nutzen — dann im Audit als „Plattform“ kennzeichnen.

### 2. Profile-ID aus TenantContext oder Session — nie aus freiem Header

```python
pid = tenant.profile_id   # ✅ bei Depends(get_tenant_context)
# oder session['profile_id'] nur wenn Endpoint ausdrücklich ohne TenantContext (Ausnahme dokumentieren)
# Nicht: request.headers.get('X-Profile-Id')  ❌
```

### 3. bcrypt für Passwörter
```python
from auth import hash_pin, verify_pin
hashed = hash_pin(plain_password)    # ✅
# Nicht: hashlib.sha256(...)          ❌
```

### 4. PostgreSQL-Syntax
```python
cur.execute("SELECT * FROM t WHERE id = %s AND active = true", (id,))
# Nicht: ?  und  active = 1  (SQLite-Syntax)
```

### 5. Rate Limiting für sensitive Endpoints
```python
from slowapi import Limiter
@router.post("/sensitive")
@limiter.limit("5/minute")
def sensitive(request: Request, ...):
```

### 6. Universal CSV Import / Admin-Vorlagen
Neues **Import-Zielmodul**, Änderungen an **`csv_parser`**, Executor, DB-`source`/`CHECK`, oder System-CSV-Vorlagen:

- Pflichtlektüre und Checkliste: **`.claude/docs/technical/UNIVERSAL_CSV_IMPORT_AGENT_GUIDE.md`**
- Keine zweite DB-Connection im Importpfad; Zeilenfehler ohne „aborted transaction“ (SAVEPOINT-Muster wo nötig)
- Admin Create/Update von Systemvorlagen: Validierung über `validate_csv_template` nicht umgehen

## Frontend

### 1. api.js für alle API-Calls
```javascript
await api.listWeight()        // ✅
await fetch('/api/weight')    // ❌ kein Token
```

### 2. Fehlerbehandlung in async Funktionen
```javascript
try {
  const data = await api.meinEndpoint()
} catch(e) {
  setError(e.message)  // api.js wirft bereits Error mit detail-Text
}
```

### 3. Kein TypeScript
Das Projekt nutzt bewusst kein TypeScript – keine .ts/.tsx Dateien erstellen.

### 4. Keine neuen npm-Pakete ohne Absprache
Erst fragen, dann installieren.

### 5. CSS-Variablen statt Hardcoded-Farben
```javascript
// ✅ Richtig:
style={{color: 'var(--accent)'}}

// ❌ Falsch:
style={{color: '#1D9E75'}}
```

### 6. Formular-Standard (VERBINDLICH ab 2026-04-22)
**Alle neuen Formulare verwenden den Standard-Stil:**

```jsx
// ✅ Standard: Label oben, volle Breite, linksbündig
<div className="form-row">
  <label className="form-label">Feldname *</label>
  <input
    type="text"
    className="form-input"
    value={formData.field}
    onChange={(e) => updateFormField('field', e.target.value)}
    required
  />
</div>

// ✅ Textarea
<div className="form-row">
  <label className="form-label">Beschreibung</label>
  <textarea
    className="form-input"
    rows={3}
    value={formData.description}
    onChange={(e) => updateFormField('description', e.target.value)}
  />
</div>

// ✅ Select
<div className="form-row">
  <label className="form-label">Status</label>
  <select
    className="form-input"
    value={formData.status}
    onChange={(e) => updateFormField('status', e.target.value)}
  >
    <option value="active">Aktiv</option>
    <option value="inactive">Inaktiv</option>
  </select>
</div>

// ❌ NICHT: Inline-Layout mit Label links
// Nur für Ausnahmen (kurze Werte mit Einheit) nutzen:
<div className="form-row--inline">
  <label className="form-label">Dauer</label>
  <input type="number" className="form-input" value={duration} />
  <span className="form-unit">min</span>
</div>
```

**Regeln:**
- Label ist `<label>` mit Klasse `.form-label` (nicht `<div>`)
- Input/Textarea/Select nutzen `.form-input`
- Volle Breite (100%), linksbündig
- Pflichtfelder mit `*` im Label kennzeichnen
- Inline-Variante (`.form-row--inline`) nur für Ausnahmen (Zahlen mit Einheit)

## Git & Deployment

### 1. Nie direkt auf main pushen
Immer über Pull Request in Gitea: develop → main

### 2. develop Branch nie löschen
Er ist permanent – nicht nach Merge löschen.

### 3. .env nie committen
Steht in .gitignore – nie entfernen.

### 4. Commit-Message Format
```
feat:     neues Feature
fix:      Bugfix
refactor: Umbau ohne Funktionsänderung
docs:     Dokumentation
ci:       CI/CD Änderungen
chore:    Maintenance
```
