umbau(boxwart): neue Oberflaeche im Cockpit-Stil (Richtung A), Werkzeuge angehoben

Drei Seiten statt zehn: Start, Updates, Modelle — am PC mit Kopfnavigation, am Handy
mit Leiste unten. Umsetzung von Mockup A („Cockpit“), vom User am 23.09. gewaehlt.

- Start: Hauptwarnleuchte + 8 Warnlampen, Rundinstrumente mit Live-Werten aus dem
  Strom (Speicher, Temperatur, Platte) und Laufzeit-Zaehlwerk, Checkliste der
  Waechter-Hinweise mit ihren Knoepfen, Flugplan (heute gelaufen / geplant), Radar-Kasten.
- Updates: Bausteine mit „Laeuft → Neu“ und Zusammenfassung, laufende Auftraege,
  Verlauf der Sonntagslaeufe (neu: GET /api/updates/verlauf), Sicherungen samt
  Zurueckspielen mit Rueckfrage.
- Modelle: Speicherbalken, Rollen Hirn/Coder/Dritte Rolle, wer die Modelle nutzt
  (7 Tage + 24 h je Stunde), Modell-Radar, weitere Eintraege, Modelle selbst suchen.
- Schubladen: Dienste mit Protokoll und Neustart, Einstellungen (HF-Zugang), Hermes-Link.
- Werkzeuge: Vite 8, React 19.3 mit React Compiler 1.0 (Babel), vitest 5, Tailwind 4.3,
  shadcn 4 (Radix) fuer Dialog/Schublade/Knopf, Schriften Barlow/Barlow Condensed/
  JetBrains Mono. Entfernt: recharts, cmdk, Kraftgraph, zustand, Inter, Space Grotesk.
- Startbuendel 115 KB gzip (Budget 140); Updates/Modelle/Schubladen laden bei Bedarf.
- 16 Oberflaechen-Tests (Instrument-Bogen, Hauptleuchte, Zeitformate, Versionen).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Hitonabi
2026-09-23 21:39:07 +02:00
co-authored by Claude Opus 5.5
parent 3d2c9549fb
commit 907289d7dc
223 changed files with 8572 additions and 15393 deletions
+64
View File
@@ -0,0 +1,64 @@
import { flugplanZeit, gbText, laufzeit, seit, vor } from "./zeit"
import { rolleVon, temperaturFarbe, versionen } from "./anzeige"
import type { Modell } from "./typen"
// Fester Bezugspunkt: Mittwoch, 23.09.2026, 20:45 Uhr Berliner Zeit.
const JETZT = new Date("2026-09-23T20:45:00+02:00")
describe("flugplanZeit", () => {
it("benennt heute, gestern und morgen", () => {
expect(flugplanZeit("2026-09-23T03:00:00+02:00", JETZT)).toBe("HEUTE 03:00")
expect(flugplanZeit("2026-09-22T23:10:00+02:00", JETZT)).toBe("GESTERN 23:10")
expect(flugplanZeit("2026-09-24T03:00:00+02:00", JETZT)).toBe("MORGEN 03:00")
})
it("nimmt für die nächsten Tage den Wochentag", () => {
expect(flugplanZeit("2026-09-27T04:30:00+02:00", JETZT)).toBe("SO 04:30")
})
it("rechnet UTC-Zeiten in Berliner Zeit um", () => {
expect(flugplanZeit("2026-09-23T05:00:00Z", JETZT)).toBe("HEUTE 07:00")
})
})
describe("kleine Helfer", () => {
it("seit und vor", () => {
expect(seit(new Date("2026-09-07T11:03:00+02:00").getTime() / 1000, JETZT)).toBe("seit 07.09., 11:03")
expect(vor(JETZT.getTime() / 1000 - 40, JETZT.getTime())).toBe("vor 40 s")
expect(vor(JETZT.getTime() / 1000 - 2 * 86_400, JETZT.getTime())).toBe("vor 2 Tagen")
})
it("laufzeit und gbText", () => {
expect(laufzeit(10 * 86_400 + 14 * 3600 + 59)).toEqual([10, 14])
expect(gbText(55 * 1024 ** 3)).toBe("55 GB")
expect(gbText(1.9 * 1024 ** 4, 1)).toBe("1,9 TB")
})
it("Temperaturfarbe wechselt bei 70 und 85 Grad", () => {
expect(temperaturFarbe(37)).toBe("var(--gruen)")
expect(temperaturFarbe(70)).toBe("var(--bernstein)")
expect(temperaturFarbe(85)).toBe("var(--rot)")
})
})
describe("versionen", () => {
it("zeigt Motor-Builds mit b und nur echte Neuerungen", () => {
expect(versionen("engine", { kind: "engine", installed_build: 11057, latest_build: 11139 })).toEqual({ laeuft: "b11057", neu: "b11139" })
expect(versionen("engine", { kind: "engine", installed_build: 11139, latest_build: 11139 })).toEqual({ laeuft: "b11139", neu: null })
})
it("zählt beim Betriebssystem die Pakete", () => {
expect(versionen("os", { kind: "os", count: 24 })).toEqual({ laeuft: "Ubuntu", neu: "24 Pakete" })
})
})
describe("rolleVon", () => {
const m = (aliases: string[]): Modell => ({
name: "x", role: null, aliases, ctx: null, ttl: null, filename: "", quant: "", size_bytes: null, spec_active: false, parallel_slots: 1,
})
it("erkennt Hirn und Coder an den Aliasen", () => {
expect(rolleVon(m(["hermes", "fast"]))).toBe("hirn")
expect(rolleVon(m(["coder", "heavy"]))).toBe("coder")
expect(rolleVon(m(["debugger"]))).toBeNull()
})
})