shinkan-jinkendo/.claude/rules/CODING_RULES.md
Lars cbb783222c
Some checks failed
Deploy Development / deploy (push) Successful in 36s
Test Suite / lint-backend (push) Successful in 0s
Test Suite / build-frontend (push) Successful in 5s
Test Suite / playwright-tests (push) Failing after 14s
refactor: Standardisiere Formular-Layout (Label oben, volle Breite, linksbündig)
CSS (frontend/src/app.css):
- .form-row: flex-direction column, volle Breite, margin-bottom 16px
- .form-label: display block, font-weight 600, linksbündig
- .form-input: width 100%, text-align left (statt right 90px), padding 10px 12px
- textarea.form-input: resize vertical, min-height 80px
- select.form-input: cursor pointer
- .form-row--inline: Neue Variante für Ausnahmen (kurze Werte + Einheit)

Dokumentation (.claude/rules/CODING_RULES.md):
- Neue Regel Frontend §6: Formular-Standard (VERBINDLICH ab 2026-04-22)
- Code-Beispiele für Standard-Layout (input, textarea, select)
- Klare Regeln: Label = <label>, Pflichtfelder mit *, volle Breite, linksbündig
- Inline-Variante nur für Ausnahmen (Zahlen mit Einheit)

WICHTIG: Bestehende Formulare (Exercises, Clubs, Skills, TrainingPlanning)
nutzen bereits größtenteils diese Struktur und profitieren automatisch
von den CSS-Änderungen.

Vorteile:
- Konsistente UX über alle Formulare
- Mobile-friendly (volle Breite, kein horizontal scrolling)
- Bessere Lesbarkeit (Label als Überschrift)
- Einfacher wartbar (ein Standard für alle)

Nächster Schritt: Bestehende Formulare testen, ggf. kleine Anpassungen
2026-04-22 17:11:40 +02:00

4.1 KiB
Raw Blame History

Coding Rules Mitai Jinkendo

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

Backend

1. Auth auf jeden Endpoint

# Jeder neue Endpoint braucht Auth:
@router.get("/neuer-endpoint")
def neuer_endpoint(session: dict = Depends(require_auth)):
    pid = session['profile_id']

2. Profile-ID aus Session nie aus Header

pid = session['profile_id']   # ✅
# Nicht: request.headers.get('X-Profile-Id')  ❌

3. bcrypt für Passwörter

from auth import hash_pin, verify_pin
hashed = hash_pin(plain_password)    # ✅
# Nicht: hashlib.sha256(...)          ❌

4. PostgreSQL-Syntax

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

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

await api.listWeight()        // ✅
await fetch('/api/weight')    // ❌ kein Token

2. Fehlerbehandlung in async Funktionen

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

// ✅ Richtig:
style={{color: 'var(--accent)'}}

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

6. Formular-Standard (VERBINDLICH ab 2026-04-22)

Alle neuen Formulare verwenden den Standard-Stil:

// ✅ 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