Files
rippy/docker/ui/src/components/ui/Badge.tsx
T
HitonabiandClaude Opus 5 27c9d9a4bb fix(ui): Leerer Bildschirm nach „Rippen starten" — ein halber Job riss alles mit
Commander: „wenn man auf rippen starten klickt passiert irgendwas, was das
Programm nicht mag. Der Hintergrund ist einfach leer und er startet nix. Es
gibt auch keine fehlermeldung."

Nachgestellt: Dienst lokal gestartet, Disc-Karte geoeffnet, „Rippen starten"
geklickt. Browser-Konsole:

    TypeError: Cannot read properties of undefined (reading 'toUpperCase')

## Er startet sehr wohl — man sieht es nur nie

Waehrend die Seite leer war, lief der Job (per API gemessen: status
`processing`, progress 12). Das „er startet nix" ist also der zweite Teil
desselben Fehlers: Die Oberflaeche war weg, bevor sie ihn zeigen konnte.

## Die Ursache

`_job_kurz` in `bus/waechter.py` trug nur `status`, `progress`, `title` und
`error` — **keinen `type`**. Das UI fuegt aus `job.created` einen halben Job
in seine Liste ein, `LiveLogSection` liest `job.type.toUpperCase()`, und React
baut bei einem Fehler im Zeichnen den GESAMTEN Baum ab. Es gab in diesem
Projekt keine einzige Fehlergrenze — also blieb ein leerer Bildschirm ohne
jede Meldung.

## Drei Reparaturen, weil es drei Fehler waren

1. **Das Ereignis traegt den Job.** `type`, `device` und `startTime` fahren
   mit. Sie aendern sich ueber die Lebenszeit eines Jobs nie, kosten also kein
   zusaetzliches Ereignis — und ohne `startTime` stand in der Jobliste
   sekundenlang „Invalid Date".
2. **Das UI vertraegt sein Fehlen.** `LiveLogSection` und `TypeBadge` nahmen
   einen vollstaendigen Job an. Zeile 108 derselben Datei hatte das
   Fragezeichen laengst, Zeile 48 nicht.
3. **Eine Fehlergrenze.** Ein Fehler in einer Karte darf nicht den ganzen
   Bildschirm mitnehmen — und schon gar nicht schweigend. Jetzt steht da, was
   los ist, die Navigation bleibt bedienbar, und der Hinweis sagt das
   Wichtigste: laufende Rips gehen weiter.

## Warum es niemand gefunden hat

`unterschiede()` bekommt die Kurzform schon fertig, und alle Tests reichen
ihre eigenen Woerterbuecher herein. **`_job_kurz` selbst war nie geprueft.**
Jetzt bewacht `UI_PFLICHTFELDER` den Vertrag mechanisch — es gibt kein
gemeinsames Typsystem zwischen Python und dem UI.

Gegengeprueft im Browser: derselbe Klick, keine Konsolenfehler, Job erscheint
als „Alle (1)".

833 Tests gruen, ruff sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 14:10:12 +02:00

43 lines
1.3 KiB
TypeScript

import { Disc } from 'lucide-react'
import { STATUS_STYLES, DISC_TYPE_STYLES } from '../../lib/design'
interface StatusBadgeProps {
status: string
className?: string
}
export function StatusBadge({ status, className = '' }: StatusBadgeProps) {
const config = STATUS_STYLES[status as keyof typeof STATUS_STYLES] || {
badge: 'bg-slate-500/15 text-slate-400 border-slate-500/30',
label: status,
}
return (
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium border ${config.badge} ${className}`}>
{config.label}
</span>
)
}
interface TypeBadgeProps {
type: string
className?: string
}
export function TypeBadge({ type, className = '' }: TypeBadgeProps) {
// Ein fehlender Typ darf kein Absturz sein. Am 29.08.2026 hat genau diese
// Annahme (an anderer Stelle) die ganze Oberflaeche geleert: Ein frisch
// angelegter Job kommt ueber den Ereignisstrom ohne `type` an.
const config = DISC_TYPE_STYLES[type as keyof typeof DISC_TYPE_STYLES] || {
badge: 'bg-slate-500/15 text-slate-400 border-slate-500/30',
label: type ? type.toUpperCase() : 'DISC',
}
return (
<span className={`inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-xs font-medium border ${config.badge} ${className}`}>
<Disc size={13} />
{config.label}
</span>
)
}