Files
ApiCore/docs/superpowers/specs/2026-10-15-beute-3d-design.md
T

50 KiB
Raw Blame History

Beute in 3D (Ist-Zustand, Umbau, Rähmchen) — Design

Stand: 2026-10-15, Umsetzung: L1–L5 fertig und auf main (2026-10-16), nicht ausgerollt (#28) · Betrifft: ApiCore-Backend, ApiCore-Frontend · Teilprojekt 1 und 2 von 4 des Inhaber-Wunschs „3D-Beute“ vom 2026-10-15 · Grundlage: Gespräch mit dem Inhaber (Entscheidungen in Abschnitt 1), Holtermann-Produktseiten der eigenen Ausrüstung (Abschnitt 4), Muster aus T3 (Offline-Warteschlange, Stand-Paket), T5 (Tokens, Sonne, Bewegungsstufe) und T6 (Bestandsbuch, Vorbelegung nur sichtbar)

1. Ziel

Wer ein Volk öffnet, sieht seine Beute als 3D-Modell: durchsichtige Kästen mit blau leuchtenden Kanten, von unten nach oben so gestapelt, wie sie am Stand steht. Das Modell ist der Ist-Zustand. Es ändert sich durch Umbau in 3D und durch die Durchsicht, und jede Änderung bleibt als Historie erhalten.

  • Fährt die Maus über ein Teil, leuchten seine Kanten stärker und es hebt sich leicht. Ein Klick auf eine Zarge lässt sie nach vorn fliegen und größer werden, die anderen Teile weichen zurück und verblassen. Jetzt sieht man die Rähmchen, die vorher nur angedeutet waren. Fährt die Maus über ein Rähmchen, steigt es ein Stück aus der Zarge und zeigt Platz, Art und Namen („R4 · Brutwabe · Königinwabe“).
  • Rähmchen bekommen eine Art (Brutwabe, Drohnenwabe, Honigwabe, Pollenwabe, Futterwabe, Mittelwand, Leerwabe, Baurahmen, Schied) und optional einen Namen.
  • Ausrüstung mit Zustand wird sichtbar: „Noch 5 l Zuckerwasser in der Futterzarge“ steht als leuchtender, sanft schwappender Pegel im Modell. Der Ameisensäure-Verdunster zeigt seinen Säurestand.
  • Neue Zargen, Zwischenböden, Gitter, Folien, Verdunster und Deckel kommen per Drag & Drop hinein; die Bausperre wird für den Winter in den Boden gelegt und wieder entfernt.
  • Das System kennt Beutensysteme als Daten. Der Inhaber hat heute nur Liebig-Zander mit 10 Rähmchen; Dadant, Deutsch Normalmaß oder Langstroth sind später neue Katalogzeilen, kein neuer Code.

Messbar fertig ist dieses Teilprojekt, wenn:

  1. Auf der Volk-Seite steht die 3D-Karte oben rechts über „Durchsicht“ und „Varroa“, genau so hoch wie die Karte „Stammdaten“, und das Modell füllt sie ohne Abschnitt aus, egal ob eine Zarge oder sechs.
  2. Ein Rähmchen lässt sich in der Karte in drei Schritten umbenennen (Zarge antippen, Rähmchen antippen, Art wählen und speichern). Die Änderung steht in der Historie mit Quelle und Zeit.
  3. „Noch 5 l drin, 3 l nachgefüllt“ an der Futterzarge ändert den Pegel im Modell und bietet eine vorbelegte Fütterung an, die erst mit „Speichern“ im Fütterungs-Formular entsteht.
  4. Eine Durchsicht kann den Aufbau ändern, auch offline; nach dem Sync sind Durchsicht und Aufbau-Änderung gemeinsam gespeichert oder gemeinsam abgelehnt.
  5. Alles, was in 3D geht, geht auch per Tastatur und Screenreader über die Aufbau-Liste. Ohne WebGL erscheint dieselbe Beute als SVG.
  6. Bei Bewegungsstufe „ruhig“ oder „aus“ und bei prefers-reduced-motion fliegt nichts; Zustände wechseln mit kurzer Überblendung oder sofort.

Entscheidungen

Frage Entscheidung
Was zeigt das Modell? Den Ist-Zustand. Er ändert sich durch Umbau (3D oder Liste) und durch die Durchsicht. Jede Änderung ist ein Eintrag in der Historie (Inhaber, 2026-10-15).
Stil Technisch-holografisch nach Vorgabe des Inhabers: durchsichtige Kästen, blau leuchtende Kanten, Rähmchen zuerst als feine Linien angedeutet, starke Bewegung bei Hover und Klick. Kein Foto-Realismus.
Platz auf der Seite Rechte Spalte ganz oben, über Durchsicht und Varroa, in derselben Rasterzeile wie „Stammdaten“, damit beide Karten immer gleich hoch sind (Inhaber).
Größe des Modells Automatisch eingepasst: Die Kamera rahmt das ganze Modell mit Rand. Wenige Zargen erscheinen groß, ein hoher Turm kleiner, nichts wird abgeschnitten (Inhaber).
Wo wird bearbeitet? Gemischt (Inhaber: „C“): Kleine Änderungen direkt in der Karte (Zarge öffnen, Rähmchen-Art und Name, Füllstände, Bausperre rein/raus). Umbau mit Drag & Drop neuer Teile in der Vollbild-Ansicht „Umbauen“.
Technik three.js mit react-three-fiber, drei und postprocessing (Bloom) (Inhaber: „A“). Nur damit leuchten Kanten echt, lassen sich Teile in 3D fliegen und Beutensysteme aus Maßen erzeugen.
Teilprojekte Diese Spec: Teil 1 (Beuten-Daten) und Teil 2 (3D-Ansicht). Teil 3 (KI: Rähmchen-Art aus Foto, Aufbau-Vorschlag beim Anlegen) und Teil 4 (KI-Gespräch in der Durchsicht, baut auf T7) bekommen eigene Specs. Diese Spec legt nur die Schnittstelle an, die sie brauchen: Änderungen mit Quelle AI_SUGGESTION, die ein Mensch bestätigt.
Speicherform Aktueller Zustand als geprüftes JSON je Volk plus Änderungs-Protokoll mit Zustand danach. Ein Aufbau ist ein kleiner Baum (Stapel, Einsätze, Rähmchen); Zeilen je Rähmchen brächten bei jeder Änderung viele Schreibzugriffe und keinen Mehrwert, weil niemand Rähmchen über Völker hinweg relational abfragt. Die Rückverfolgbarkeitskette (CLAUDE.md) berührt das nicht.
Änderungen Als Operationen (Teil hinzufügen, entfernen, verschieben, Zustand setzen, Rähmchen setzen). Neue Teile bekommen ihre Id im Client, damit offline erstellte Operationen sich aufeinander beziehen können.
Gleichzeitige Änderungen Operationen werden gegen den aktuellen Stand angewandt, nicht gegen die Basis. Fehlt ein Teil, auf das sich eine Operation bezieht, oder bricht sie eine Regel, wird die ganze Anfrage mit 409 und dem aktuellen Stand abgelehnt. Zustandswerte gelten „wer zuletzt schreibt“.
Fütterung und Behandlung Vorschlag, nie still gebucht. Ein höherer Futterpegel bietet eine vorbelegte Fütterung an, ein eingesetzter Verdunster mit Säure eine vorbelegte Behandlung (Bestandsbuch T6). Gebucht wird nur mit „Speichern“ im jeweiligen Formular, wie bei T6 und T7.
Bewegung Abweichung von T5 §1 „Keine neue Animation“, vom Inhaber am 2026-10-15 ausdrücklich gewünscht („extrem animiert“, „moderne Motions“). Gilt hier für die Beute-Ansicht; die übrige App wird in T5 nachgezogen (Abschnitt 12.4). Die Bewegungsstufe des Geräts (full/subtle/off) und prefers-reduced-motion gelten immer.
Altbestand Bestehende Völker bekommen keinen Aufbau per Migration. Beim ersten Öffnen zeigt die Karte „Aufbau anlegen“ mit einem Startaufbau aus der Vorlage des Beutensystems. Erst „Übernehmen“ legt ihn an.
Bestehende Felder Colony.hiveType bleibt als Freitext für Völker ohne Aufbau. Colony.occupiedFrames bleibt die Volksstärke (von Bienen besetzte Waben, aus Durchsichten) und wird vom Aufbau nie geschrieben. Rähmchen-Zahlen der Beute kommen nur aus deriveHiveFacts (Abschnitt 5.3).
Rechte Lesen mit colonies:read, Aufbau ändern mit colonies:update, Katalog pflegen mit der neuen Berechtigung hive-systems:manage (nur Systemrolle ADMIN). Folge: Die Rolle STAFF darf heute Durchsichten anlegen, aber kein colonies:update, also auch die Beute nicht ändern; der Inhaber kann das in der Rollenverwaltung freischalten.

2. Umfang

2.1 Im Umfang

  • Katalog: Beutensysteme, Teile-Arten mit Maßen, Platzregeln und Zustandsfeldern, Vorlagen. Startdaten für Liebig-Zander mit der Ausrüstung des Inhabers (Abschnitt 4).
  • Aufbau je Volk: anlegen, ändern, Historie, Stand an einem Tag.
  • API unter /api/v1/... für alles davon, mit Rollenprüfung.
  • Durchsicht: Block „Beute“ im Formular, Änderungen gehen mit der Durchsicht in einer Transaktion, auch über die Offline-Warteschlange.
  • Frontend: 3D-Karte auf der Volk-Seite, Vollbild „Umbauen“, Aufbau-Liste (Tastatur, Screenreader), SVG-Rückfall, Historie-Ansicht.
  • Vorschläge für Fütterung und Behandlung aus Zustandsänderungen.
  • Ereignis HiveChanged im Domain-Event-Outbox (ohne Handler in dieser Spec).

2.2 Nicht im Umfang

  • KI-Bewertung von Rähmchen aus Fotos, KI-Vorschlag beim Anlegen (Teil 3).
  • KI-Gespräch während der Durchsicht (Teil 4).
  • Bauteil-Inventar (wie viele Zargen liegen im Lager). Das bestehende Modul equipment verwaltet Geräte und bleibt unverändert.
  • Fotos der echten Beuten als Texturen. Der Inhaber schickt Fotos; sie dienen zum Abgleich der Maße und Proportionen, nicht als Bild im Modell.
  • Mehrere Völker in einer Beute (Zwischenboden mit zwei Völkern). Der Zwischenboden ist ein Teil im Stapel; ein zweites Volk darüber ist ein eigenes Volk mit eigener Beute.

3. Begriffe

Begriff Bedeutung
Beutensystem Maßsystem einer Beute, z. B. „Zander nach Dr. Liebig, 10 Rähmchen“. Bestimmt Grundfläche, Rähmchenmaß und Rähmchen je Zarge.
Teile-Art Eine Katalogzeile, z. B. „Liebig Zander Ganzzarge“ oder „Nassenheider Professional“.
Teil Ein Exemplar einer Teile-Art im Aufbau eines Volks, mit eigener Id und eigenem Zustand.
Stapel Die Teile, die übereinander liegen, von unten (Boden) nach oben (Dach).
Einlage Ein Teil, das in einem Boden liegt (Bausperre).
Einsatz Ein Teil, das in einer Zarge steht oder liegt (Verdunster).
Aufbau Stapel plus Einlagen, Einsätze und Rähmchen eines Volks.

4. Katalog

4.1 Teile-Arten

kind legt Platz und Darstellung fest. Der Platz ergibt sich aus kind: FLOOR_INSERT ist eine Einlage im Boden, EVAPORATOR ein Einsatz in einer Zarge, alles andere liegt im Stapel.

kind Platz Darstellung Regeln
FLOOR Stapel Kasten mit Gitterlinien Genau einer, immer ganz unten.
BOX Stapel durchsichtiger Kasten mit Rähmchen frameSlots Plätze (0 = Leerzarge). boxDepth: FULL oder SHALLOW.
DIVIDER_BOARD Stapel dünne Platte mit Löchern Zwischenboden.
QUEEN_EXCLUDER Stapel Gitter aus Linien —
FEEDER Stapel flacher Kasten mit Kammern Zustandsfelder je Kammer (Pegel).
COVER_SHEET Stapel sehr dünne Fläche Abdeckfolie.
PROPOLIS_SCREEN Stapel Gitter aus Linien —
INNER_LID Stapel flache Platte Holzdeckel.
INSULATION Stapel Platte mit Punktmuster Dämmung, z. B. Styropor-Einlage.
ROOF Stapel Platte mit Rand, über das Teil darunter gestülpt Nur ganz oben.
FLOOR_INSERT Einlage im Boden Gitterfläche im Boden Bausperre. Höchstens eine je Boden.
EVAPORATOR Einsatz in einer Zarge Flasche mit Pegel auf Docht-Platte Ameisensäure-Verdunster. Liegt auf den Rähmchen der Zarge darunter, darum meist in einer Leerzarge. Höchstens einer je Zarge.
FRAME_FEEDER Einsatz in einer Zarge, an Rähmchen-Plätzen Futtertasche mit Pegel Hängt anstelle von Rähmchen in der Zarge und belegt slotWidth Plätze (Teile-Art, Futtertasche doppelte Wabenbreite = 2) ab Platz slot (Feld am Einsatz). Die belegten Plätze müssen im frames-Array der Zarge den Typ NONE haben. Mehrere je Zarge erlaubt, ohne Überlappung. Zustandsfeld Flüssigfutter in l.

Alle anderen Reihenfolgen sind erlaubt. Ungewöhnliche Folgen (Absperrgitter direkt auf dem Boden, Dämmung unter der Zarge) sind Hinweise in der Oberfläche, keine Sperre, weil Imker umbauen, wie es die Lage verlangt.

4.2 Zustandsfelder

Jede Teile-Art trägt ein stateSchema als JSON-Liste. Zwei Feldarten:

[
  { "key": "syrupLiters", "label": "Flüssigfutter", "kind": "level", "unit": "l", "max": 3, "step": 0.5, "feedType": "SUGAR_WATER" },
  { "key": "fondantKg", "label": "Futterteig", "kind": "level", "unit": "kg", "max": 2.5, "step": 0.5, "feedType": "FEED_PASTE" },
  { "key": "entrance", "label": "Flugloch", "kind": "choice", "options": ["OPEN", "NARROW", "CLOSED"], "optionLabels": ["offen", "eng", "zu"] }
]
  • level: Zahl von 0 bis max in Schritten von step. Wird im Modell als Pegel gezeigt.
  • choice: einer der Werte aus options.
  • feedType (optional, nur bei level): Futterart der Fütterung (FeedType aus dem Fütterungs-Modul), die Abschnitt 8 vorbelegt.
  • Fehlt ein Feld im Zustand eines Teils, gilt 0 bzw. die erste Option.
  • Der Server prüft jeden Wert gegen das Schema der Teile-Art. Unbekannte Schlüssel sind ein Fehler.
  • Das Schema einer Teile-Art darf sich nur additiv ändern (neue Felder). Ein entferntes Feld bleibt in alten Ständen der Historie lesbar, weil jeder Stand sein JSON vollständig speichert.

4.3 Startdaten Liebig-Zander (Ausrüstung des Inhabers)

Maße aus den Holtermann-Produktseiten (abgerufen 2026-10-15). Alle Werte sind Katalogdaten und in den Einstellungen änderbar.

Teile-Art kind Maße (L × B × H, mm) Zustandsfelder
Liebig Wanderboden FLOOR 525 × 430 × 120 Flugloch (offen/eng/zu), Varroa-Schublade (drin/draußen)
Liebig Bausperre FLOOR_INSERT 475 × 375 × 5 —
Liebig Zander Ganzzarge, 10 Rähmchen BOX, FULL, 10 Plätze 525 × 430 × 230 —
Leerzarge (Liebig Zander Ganzzarge ohne Rähmchen) BOX, FULL, 0 Plätze 525 × 430 × 230 —
Liebig Zwischenboden DIVIDER_BOARD 525 × 430 × 50 Spundlöcher (offen/zu/Bienenflucht)
Niro-Absperrgitter Zander Liebig QUEEN_EXCLUDER 478 × 376 × 2 —
Liebig Zweikammer-Futterzarge FEEDER 520 × 420 × 70 Flüssigfutter 0–3 l (feedType SUGAR_WATER), Futterteig 0–2,5 kg (feedType FEED_PASTE)
Abdeck-Fix (Folie, klar, 0,4 mm) COVER_SHEET 478 × 378 × 1 —
Imgut Lotus-Abdeckung (0,5 mm) COVER_SHEET 478 × 378 × 1 —
Imgut Propolisgitter PROPOLIS_SCREEN 478 × 378 × 3 —
Liebig Holzdeckel INNER_LID 525 × 430 × 35 Futterloch (offen/zu)
Styropor-Einlage (selbst gebaut) INSULATION 465 × 365 × 20 —
Edelstahldeckel konisch Liebig ROOF 525 × 430 × 60 (Innenmaß) —
Nassenheider Verdunster Professional EVAPORATOR 200 × 140 × 120 Ameisensäure 0–290 ml
Holz-Futtertasche Zander, doppelte Wabenbreite (Inhaber 2026-10-16; Vorlage Holtermann Art. 7142 in Normalmaß: 370 × 221 mm, Oberträger 394 mm) FRAME_FEEDER, slotWidth 2 477 × 70 × 220 (Zander-Annahme) Flüssigfutter 0–2 l, Schritt 0,25 (Annahme, Holtermann nennt kein Volumen)

Die Startdaten kommen per Migration mit festen Ids (hive-system-zander-liebig-10, hive-part-zander-liebig-10-floor, …), damit die Vorlage stabile Verweise hat und eine Wiederherstellung aus dem Backup in eine frische Installation keine doppelten Namen erzeugt (Abschnitt 5.1).

Annahmen, die der Inhaber mit Fotos oder Zollstock bestätigt (Abschnitt 14): Außenmaß der Zarge 525 × 430 mm (abgeleitet aus dem Innenmaß des Edelstahldeckels), Dicke der Styropor-Einlage 20 mm, Maße des Verdunsters.

4.4 Beutensystem und Vorlage

HiveSystem „Zander nach Dr. Liebig, 10 Rähmchen“: Rähmchen 477 × 220 mm (Oberträger × Höhe), 10 Plätze je Ganzzarge, Grundfläche 525 × 430 mm.

Die Vorlage des Systems ist der Aufbau, mit dem ein neues Volk startet: Wanderboden, Ganzzarge mit 10 Plätzen, Abdeckfolie, Holzdeckel, Styropor-Einlage, Edelstahldeckel. Die Vorlage ist ein Aufbau-JSON wie in Abschnitt 5.2 (ohne Ids, die erzeugt der Server beim Anlegen) und in den Einstellungen änderbar. Die Rähmchen-Arten der Vorlage gelten; der Nutzer korrigiert vor „Übernehmen“. „Wie Volk B3 aufbauen“ kopiert stattdessen den aktuellen Stapel eines anderen Volks mit Zustandswerten 0, ohne Namen und mit allen Rähmchen als EMPTY_COMB außer Mittelwänden.

5. Datenmodell

5.1 Prisma

model HiveSystem {
  id             String   @id @default(cuid())
  name           String   @unique
  frameWidthMm   Int      // Oberträger
  frameHeightMm  Int
  footprintLMm   Int
  footprintWMm   Int
  template       Json     // Aufbau-JSON ohne Ids, Schema 5.2
  active         Boolean  @default(true)
  partTypes      HivePartType[]
  setups         HiveSetup[]
  createdAt      DateTime @default(now())
  updatedAt      DateTime @updatedAt
}

model HivePartType {
  id           String       @id @default(cuid())
  systemId     String
  system       HiveSystem   @relation(fields: [systemId], references: [id])
  kind         HivePartKind
  name         String
  articleRef   String?      // z. B. Holtermann-Art.-Nr.
  lengthMm     Int
  widthMm      Int
  heightMm     Int
  boxDepth     HiveBoxDepth?
  frameSlots   Int?         // nur BOX
  stateSchema  Json         // Abschnitt 4.2
  active       Boolean      @default(true)
  createdAt    DateTime     @default(now())
  updatedAt    DateTime     @updatedAt

  @@unique([systemId, name])
}

model HiveSetup {
  colonyId   String       @id
  colony     Colony       @relation(fields: [colonyId], references: [id])
  systemId   String
  system     HiveSystem   @relation(fields: [systemId], references: [id])
  version    Int          @default(1)
  state      Json         // Abschnitt 5.2, immer vollständig
  updatedAt  DateTime     @updatedAt
  changes    HiveChange[]
}

model HiveChange {
  id             String           @id @default(cuid())
  colonyId       String
  setup          HiveSetup        @relation(fields: [colonyId], references: [colonyId])
  version        Int              // Version nach der Änderung
  occurredOn     DateTime         // fachlicher Tag (Durchsicht oder Eingabe)
  source         HiveChangeSource
  inspectionId   String?          @unique
  inspection     Inspection?      @relation(fields: [inspectionId], references: [id])
  userId         String
  user           User             @relation(fields: [userId], references: [id])
  ops            Json             // angewandte Operationen, Abschnitt 6
  stateAfter     Json
  note           String?
  createdAt      DateTime         @default(now())

  @@unique([colonyId, version])
  @@index([colonyId, occurredOn])
}

enum HivePartKind { FLOOR BOX DIVIDER_BOARD QUEEN_EXCLUDER FEEDER COVER_SHEET PROPOLIS_SCREEN INNER_LID INSULATION ROOF FLOOR_INSERT EVAPORATOR FRAME_FEEDER }
// Nachtrag 2026-10-16: HivePartType.slotWidth Int? (nur FRAME_FEEDER), HivePart.slot (nur FRAME_FEEDER-Einsatz)
enum HiveBoxDepth { FULL SHALLOW }
enum HiveChangeSource { INIT CARD EDITOR INSPECTION AI_SUGGESTION }
  • Rück-Relationen: Colony.hiveSetup HiveSetup?, Inspection.hiveChange HiveChange?, User.hiveChanges HiveChange[].
  • Nichts wird hart gelöscht. Teile-Arten und Systeme werden mit active = false ausgeblendet; Aufbauten, die sie nutzen, bleiben gültig.
  • Anlegen eines Aufbaus schreibt Version 1 und eine HiveChange mit Quelle INIT, damit die Historie vollständig ist. Ein zweites POST /colonies/:id/hive antwortet 409 HIVE_EXISTS (geprüft unter Sperre, nicht über einen Unique-Fehler).
  • Sperren: HiveSetup kommt in LOCKABLE_TABLES direkt nach Colony. Jede Aufbau-Schreibung sperrt zuerst die Zeile Colony mit FOR UPDATE (und prüft dabei, dass das Volk lebt), dann HiveSetup.
  • Idempotenz: über den bestehenden @Idempotent()-Interceptor (Idempotency-Key je Nutzer, T3). Keine eigene Spalte.
  • Backup: Das In-App-Backup listet Modelle von Hand (backup.service.ts). Export von hiveSystem, hivePartType, hiveSetup, hiveChange; Import in der Reihenfolge hiveSystem → hivePartType → (nach colony) hiveSetup → (nach inspection) hiveChange mit userId über resolveUserId(...). Nur versiegelte Sicherungen, nicht in LEGACY_UNSEALED_TABLES. Gibt es beim Import für ein Volk schon einen Aufbau, gewinnt die Sicherung: Aufbau und Änderungen dieses Volks werden vorher entfernt (Import ist eine Wiederherstellung, kein Zusammenführen). Der Abdeckungstest des Backups wird ergänzt.

5.2 Aufbau-JSON

interface HiveState {
  schemaVersion: 1;
  stack: HivePart[];          // Index 0 = unten
}

interface HivePart {
  id: string;                 // UUID, im Client erzeugt
  partTypeId: string;
  label?: string;             // z. B. „Honigraum 1“, max. 40 Zeichen
  state: Record<string, number | string>;
  inserts?: HivePart[];       // FLOOR: höchstens eine Bausperre; BOX: höchstens ein Verdunster
  frames?: HiveFrame[];       // nur BOX, Länge = frameSlots der Teile-Art
}

interface HiveFrame {
  slot: number;               // 1 … frameSlots, von links mit Blick auf das Flugloch
  type: FrameType;
  name?: string;              // max. 40 Zeichen
}

type FrameType =
  | "BROOD" | "DRONE" | "HONEY" | "POLLEN" | "FEED"
  | "FOUNDATION" | "EMPTY_COMB" | "DRONE_FRAME" | "DIVIDER" | "NONE";

NONE ist ein leerer Platz (Lücke). Deutsche Namen: Brutwabe, Drohnenwabe, Honigwabe, Pollenwabe, Futterwabe, Mittelwand, Leerwabe, Baurahmen, Schied, leer. Höchstens 30 Teile im Stapel.

Ein Validator prüft nach jeder Operation das ganze JSON: Schema, Platzregeln aus 4.1, frames.length == frameSlots, eindeutige Ids, Zustandswerte gegen stateSchema, alle partTypeId gehören zum System des Aufbaus.

5.3 Abgeleitete Werte

Eine reine Funktion deriveHiveFacts(state, catalog) liefert:

  • boxCount, broodFrames (BROOD), honeyFrames (HONEY), builtFrames (alles außer NONE, FOUNDATION, DIVIDER), levels (alle level-Felder mit Teil, Einheit, Futterart und Wert), hasFloorInsert, evaporatorAcidMl.

Diese Werte schreiben nie in Colony oder Inspection. Sie dienen der Anzeige und als sichtbare Vorschläge (Abschnitt 7).

5.4 Ereignis

Neuer Eintrag im Ereignis-Katalog domain-event-types.ts: HiveChanged: { colonyId: string; version: number; source: HiveChangeSource; inspectionId: string | null }, aggregateType Colony. Der Service schreibt ihn nach jeder Änderung in derselben Transaktion in den Outbox. Keine Inhalte, keine Namen. Nicht in DOMAIN_EVENT_WEBHOOKS. Ein Ereignis ohne Handler wird als erledigt abgeschlossen; Handler kommen mit späteren Teilprojekten (z. B. Autopilot „Futter fast leer“). Die Typ-Liste in domain-events.service.spec.ts und der Test „nicht weitergeleitet“ in webhook.handlers.spec.ts werden ergänzt.

6. Operationen und API

6.1 Operationen

type HiveOp =
  | { op: "addPart"; part: HivePart; at: number }                    // Stapel-Index
  | { op: "addInsert"; hostId: string; part: HivePart }
  | { op: "removePart"; id: string }                                 // Stapel, Einlage oder Einsatz
  | { op: "movePart"; id: string; to: number }
  | { op: "setState"; id: string; key: string; value: number | string }
  | { op: "setFrame"; boxId: string; slot: number; type: FrameType; name?: string | null }
  | { op: "setFrames"; boxId: string; frames: HiveFrame[] }
  | { op: "setLabel"; id: string; label: string | null };

applyHiveOps(state, ops, catalog) ist eine reine Funktion. Sie gibt den neuen Zustand oder einen Fehler mit Index der Operation und Grund zurück. Ein at außerhalb des Stapels wird auf die nächste gültige Stelle begrenzt (Anfang oder Ende), damit offline erfasste Einfügungen nach einem fremden Entfernen nicht scheitern. Das Backend nutzt die Funktion verbindlich. Das Frontend nutzt eine eigene Fassung für die Vorschau. Beide werden gegen dieselben JSON-Fixtures geprüft (ApiCore-Backend/test/fixtures/hive-ops/*.json, Muster wie placeholder-keys, im Frontend mit skipIf, wenn die Datei im Image-Build fehlt).

Höchstens 100 Operationen je Anfrage.

6.2 Endpunkte

Rechte über den bestehenden PermissionsGuard (jede Route braucht eine Berechtigung):

Methode und Pfad Zweck Berechtigung
GET /hive-systems Systeme mit Teile-Arten colonies:read
POST /hive-systems, PATCH /hive-systems/:id System anlegen, ändern, ausblenden hive-systems:manage
POST /hive-systems/:id/part-types, PATCH /hive-part-types/:id Teile-Art anlegen, ändern, ausblenden hive-systems:manage
GET /hive-systems/:id/template Vorlage als Aufbau-JSON mit frischen Ids colonies:read
GET /colonies/:id/hive { version, systemId, state, facts }, 404 ohne Aufbau colonies:read
POST /colonies/:id/hive Aufbau anlegen: { systemId, state, occurredOn } (vom Client aus Vorlage oder anderem Volk vorbereitet und vom Nutzer bestätigt), @Idempotent() colonies:update
POST /colonies/:id/hive/changes { occurredOn, source, ops, note? }, @Idempotent() colonies:update
GET /colonies/:id/hive/changes?cursor=&limit= Historie, neueste zuerst, ohne stateAfter colonies:read
GET /colonies/:id/hive/at?day=YYYY-MM-DD Stand am Ende dieses Berliner Kalendertags colonies:read
  • hive-systems:manage kommt in den PERMISSION_CATALOG. Die Katalog-Synchronisierung beim Start gibt sie der Systemrolle ADMIN; keine Grant-Migration.
  • POST …/changes antwortet mit dem neuen Stand.
  • 400 bei Formfehlern: unbekannte op, falsche Typen, mehr als 100 Operationen (@IsArray @ArrayMaxSize(100) plus Validator).
  • 409 mit { code: "HIVE_CONFLICT", opIndex, reason, current: { version, state } } nur, wenn eine Operation gegen den aktuellen Stand scheitert (fehlende Id, Regel aus 4.1 verletzt). Der Code unterscheidet die Antwort von den 409 des Idempotenz-Interceptors; Client und Warteschlange verzweigen auf body.code.
  • source ist in dieser Spec CARD oder EDITOR. INIT und INSPECTION setzt nur der Server. AI_SUGGESTION steht im Enum, wird aber erst mit Teil 3 angenommen (vorher 400).
  • occurredOn ist ein Berliner Kalendertag und darf nicht in der Zukunft liegen (wie T6) und nicht vor dem Tag der INIT-Änderung. Es darf vor dem Tag der letzten Änderung liegen (Nachtrag).
  • Es gibt keine baseVersion: Operationen wirken immer auf den aktuellen Stand (Abschnitt 1). Die Antwort trägt die neue version, damit der Client weiß, ob er aktuell ist.
  • Stand am Tag X ist stateAfter der Änderung mit der höchsten Version unter allen Änderungen mit occurredOn <= X. Weil Operationen immer auf den aktuellen Stand wirken, enthält ein Nachtrag auch spätere Änderungen. Das ist eine bewusste Näherung; die Historie zeigt Nachträge mit dem Zusatz „nachgetragen am …“. Vor dem INIT-Tag antwortet der Endpunkt 404.
  • Völker, die nicht mehr leben (!isLivingColonyStatus(status)), sind schreibgeschützt: 409 wie assertActive im Volk-Service.
  • Audit-Log: Katalog-Änderungen wie alle Stammdaten. Aufbau-Änderungen haben ihr eigenes Protokoll (HiveChange) und gehen nicht zusätzlich ins Audit-Log.

7. Durchsicht

  • Das Durchsicht-Formular bekommt den Abschnitt „Beute“ mit der Karte im Bearbeiten-Modus (dieselbe Komponente wie auf der Volk-Seite). Alle Änderungen sammeln sich als Operationen im Formular-Zustand und erscheinen als Liste „Änderungen an der Beute (3)“ mit „Rückgängig“ je Eintrag.
  • CreateInspectionDto bekommt hiveChange?: { ops }. Der Service nimmt hiveChange wie applyToColony aus dem DTO heraus, bevor die Durchsicht geschrieben wird. Er legt Durchsicht und HiveChange (Quelle INSPECTION, occurredOn = Tag der Durchsicht) in einer Transaktion an, mit den Sperren aus 5.1. Scheitert der Aufbau-Teil, scheitert die ganze Anfrage.
  • Rechte: Eine Durchsicht mit hiveChange braucht inspections:create und colonies:update, geprüft vor der Transaktion (Muster der bestehenden Zusatzprüfung im Inspection-Service). Fehlt colonies:update, antwortet der Server 403 für die ganze Anfrage; das Formular blendet den Abschnitt „Beute“ für solche Nutzer aus und zeigt nur das Modell.
  • Aus dem Aufbau wird broodFrames der Durchsicht vorbelegt, sichtbar als Vorschlag „Laut Beute: 6 Brutwaben“ mit „Übernehmen“, nie still (Muster „Letztes Mal“ aus T3).
  • Offline: Die Durchsicht geht wie heute in pending-inspections, mit hiveChange im Input; gesendet wird sie über die Beute-Zeitleiste aus Abschnitt 10. Bei 409 HIVE_CONFLICT zeigt die Warteschlange „Beute hat sich inzwischen geändert“ mit den Knöpfen „Ohne Beute-Änderung senden“ (gleicher Idempotency-Key, den eine gescheiterte Anfrage freigibt) und „Verwerfen“.
  • Ändern einer gespeicherten Durchsicht (PATCH) ändert ihre Beute-Änderung nicht. Wer nachträglich den Aufbau korrigiert, legt in der Karte eine neue Änderung an.

8. Fütterung und Behandlung

Auslöser Vorschlag nach dem Speichern Vorbelegung
setState auf einem level-Feld eines FEEDER steigt Hinweis „3 l Flüssigfutter nachgefüllt. Als Fütterung eintragen?“ Fütterungs-Formular mit Volk, Datum, Menge = Zuwachs, Futterart = feedType des Felds
addInsert EVAPORATOR mit Säure > 0, oder Säure steigt Hinweis „Verdunster mit 250 ml eingesetzt. Als Behandlung eintragen?“ Behandlungs-Formular mit Volk, Datum, Menge; das Arzneimittel wählt der Nutzer (T6-Regeln, Charge, Bestandsbuch)
removePart EVAPORATOR Hinweis „Behandlung beenden?“, wenn eine offene Behandlung desselben Volks besteht Ende-Datum
  • Die Logik hiveFollowUps(opsApplied, before, after) ist eine reine Funktion im Frontend.
  • Der Hinweis ist ein Toast mit Aktion und steht zusätzlich als Knopf in der Historie-Zeile („Fütterung eintragen“), damit er nicht verloren geht.
  • In der Durchsicht erscheinen dieselben Hinweise nach dem Speichern der Durchsicht.
  • Es wird nie automatisch gebucht: Fütterung und Behandlung sind Nachweise mit eigener Verantwortung (T6).

9. 3D-Ansicht

9.1 Aufbau der Komponenten

components/hive/
  hive-card.tsx            Karte auf der Volk-Seite (lädt Daten, wählt 3D oder SVG)
  hive-scene/              alles, was three.js braucht, nur per dynamic() geladen
    hive-canvas.tsx        <Canvas>, Licht, Bloom, Kamera-Einpassung
    stack-part.tsx         ein Teil im Stapel (Kasten, Kanten, Hover, Flug)
    frame-plate.tsx        ein Rähmchen
    fill-volume.tsx        Pegel (Flüssigkeit, Futterteig, Säure)
    use-hive-motion.ts     Ziele für Position, Maßstab, Deckkraft je Zustand
  hive-svg.tsx             Rückfall und Druck, aus derselben Layout-Funktion
  hive-outline.tsx         Aufbau-Liste (Tastatur, Screenreader, Bearbeiten)
  frame-sheet.tsx          Sheet „Rähmchen 4“ (Art, Name)
  part-sheet.tsx           Sheet für Zustandsfelder eines Teils
lib/hive/
  types.ts, ops.ts (applyHiveOps), layout.ts (hiveLayout), facts.ts,
  follow-ups.ts, api.ts, labels.ts

hiveLayout(state, catalog) ist eine reine Funktion: Sie rechnet aus Maßen in mm die Lage jedes Teils, jeder Einlage, jedes Einsatzes und jedes Rähmchens (Mittelpunkt, Größe) und die Gesamt-Box. 3D, SVG und Tests nutzen sie gemeinsam. Maßstab: 1 Einheit = 100 mm.

Der Zustand der Ansicht (berührtes Teil, offene Zarge, berührtes Rähmchen) liegt in einem kleinen Store außerhalb von three.js (useHiveView), damit Liste, SVG und Szene denselben Zustand teilen.

9.2 Platz und Größe

  • Volk-Seite: Die heutige Zeile lg:grid-cols-3 items-start wird in zwei Zeilen geteilt. Zeile 1: Stammdaten (1 Spalte) und Beute-Karte (2 Spalten), ohne items-start, damit die Rasterzeile beide Karten auf dieselbe Höhe streckt. Die Beute-Karte bekommt über lg keine eigene Höhe, sie folgt den Stammdaten; der Canvas sitzt in einem Flex-Kind mit h-full min-h-0. Wächst die Stammdaten-Karte (Konflikt- oder Sperrbezirk-Hinweise), wächst die Beute mit und passt neu ein. Zeile 2 behält items-start. Zeile 2: Aktionen und QR (1 Spalte), die Karten Durchsicht, Varroa usw. (2 Spalten). Die Reihenfolge im DOM bleibt Stammdaten, Beute, Aktionen, Karten.
  • Unter lg (eine Spalte) hat die Beute-Karte eine feste Höhe von min(55svh, 520px), mindestens 320 px.
  • Einpassen: drei <Bounds fit clip observe margin={1.2}> um das Modell. Bei Änderung des Aufbaus oder der Kartengröße passt die Kamera neu ein, mit Dämpfung (Bewegungsstufe full) oder sofort. Die Kamera schaut leicht von oben-vorn (Höhenwinkel 20°, Seitenwinkel 30°). Im Fokus-Modus (9.4) rahmt sie die gewählte Zarge.

9.3 Stil

Inhaber-Entscheidung 2026-10-16 nach Bild-Entwürfen (.superpowers/mockups/, Richtung D/E/F, Kalibrierung G/H/I): Die Karte ist eine randlose, tiefe Holo-Bühne im warmen App-Rahmen. Qualitätsanspruch: wirkt wie ein Produkt-Render aus einer Keynote, nicht wie eine Linienzeichnung.

  • Bühne: füllt die ganze Karte (Titel „Beute“, Stand-Zeile und Knöpfe liegen als Milchglas-Elemente über der Bühne). Tiefes Mitternachtsblau mit Vignette, ein dezentes Waben-Raster als spiegelnder Boden, wenige langsam treibende Lichtpartikel. Licht nur kühl (Blau/Cyan/Weiß), kein goldenes oder warmes Leuchten in der Ruhe-Ansicht (Inhaber 2026-10-16); warme Farben nur als Inhalt (Futter, Rähmchen-Look).
  • Fuß der Beute ist der Liebig-Wanderboden (Inhaber 2026-10-16): flacher Kasten 525 × 430 × 120 mm mit Edelstahl-Gitter oben, darunter von hinten eingeschobene Varroa-Schublade, Kontrollklappe, Flugloch-Schieber vorn. Keine Füße, kein Sockel, kein vorstehendes Anflugbrett.
  • Ruhe-Ansicht: alles reines Glas. Jedes Teil (Dach, Deckel, Dämmung, Folie, Futterzarge, Zargen, Boden, Einlagen) ist klares Glas mit scharfen, blau leuchtenden Kanten (Bloom), leichter Lichtbrechung und Spiegelung. Kein Holz, keine Bienen, keine Farben. Rähmchen sind nur als feine Glas-Umrisse angedeutet, Füllstände nicht sichtbar.
  • Fokus-Ansicht: Das gewählte Teil zeigt seinen Inhalt: Futterzarge mit leuchtendem, leicht wogendem goldenem Flüssigfutter und einem Futterteig-Block mit Etiketten „Flüssigfutter 2,5 l von 3 l“, „Futterteig 1 kg“; Verdunster mit Säurepegel; Boden mit Bausperre bzw. Varroa-Schublade; Deckel und Dach mit ihren Zuständen. Zargen zeigen ihre Rähmchen als klare Glasplatten.
  • Rähmchen-Look: Ein Rähmchen bekommt seine Art als Farbe und Muster erst, wenn es angehoben (Hover/Tippen) oder ausgewählt ist: Brutwabe bernstein mit verdeckelter Brut-Wabenstruktur, Drohnenwabe dunkleres Bernstein mit gewölbten Zellen, Honigwabe gold glänzend, Pollenwabe orange-gefleckt, Futterwabe hellgold, Mittelwand helles Wachs-Raster, Leerwabe leere Zellen, Baurahmen offener Rahmen, Schied massive Glasplatte. Die Art steht zusätzlich als Wort im Etikett (T5: Zustand nie nur über Farbe).
  • Pegel: leuchtendes, halbtransparentes Volumen mit sanft wogender Oberfläche (nur bei Bewegung full).
  • Eigene Tokens --holo-* in :root und .dark; Sonne-Sätze ab T5.
  • Etiketten: drei <Html> mit DOM-Text in --holo-label-* (scharf, übersetzbar, gut lesbar). Höchstens drei Etiketten gleichzeitig.
  • Sonne-Modus (ab T5, solange es ihn nicht gibt, entfällt dieser Punkt): kein Bloom, Kanten 2 px in reinem Weiß auf Schwarz, Tönungen der Rähmchen durch Muster ersetzt (Schraffur-Textur je Art), Etiketten 16 px.
  • forced-colors: Die Karte zeigt den SVG-Rückfall mit CanvasText-Strichen.

9.4 Bedienung in der Karte

Aktion Maus Touch Ergebnis
Teil berühren Hover erstes Tippen Kanten leuchten stärker, Teil hebt sich 8 mm, Etikett mit Namen. Andere Teile bleiben.
Teil öffnen (jedes Teil: Zarge, Futterzarge, Boden, Deckel, Dach, Einlage, Verdunster) Klick zweites Tippen Fokus-Modus: Das Teil fliegt aus dem Stapel nach vorn in die Mitte und wird groß (Kamera passt auf das Teil ein). Alle anderen Teile bleiben an ihrem Platz als fast durchsichtiger Schatten der ganzen Beute (Deckkraft ≈ 0,08), damit man sieht, woher das Teil kommt. Das Teil zeigt seinen Inhalt (9.3). Knopf „Zurück“ und der Name des Teils erscheinen.
Rähmchen berühren (im Fokus einer Zarge) Hover erstes Tippen Rähmchen steigt um 40 % seiner Höhe aus der Zarge und bekommt seinen Rähmchen-Look (Farbe + Muster, 9.3), Etikett „R4 · Brutwabe · Name“. Beim Verlassen sinkt es zurück und wird wieder klares Glas, außer es ist ausgewählt.
Rähmchen ändern Klick zweites Tippen Sheet „Rähmchen 4“ (components/ui/sheet.tsx): Art als ChoiceGroup (components/form/choice-group.tsx), Name, „Speichern“. Speichert eine Änderung (Quelle CARD).
Teil mit Zustand ändern im Fokus: Klick auf den Inhalt (Pegel, Bausperre …) oder Glas-Knopf „Ändern“ Tippen Sheet mit Zustandsfeldern (NumberStepper für Pegel, ChoiceGroup für choice), beim Boden zusätzlich „Bausperre einlegen/entfernen“, bei einer Zarge „Verdunster einsetzen/entfernen“.
Drehen Ziehen Wischen mit einem Finger Drehung um die Hochachse, ±60°, federt zurück auf die Grundansicht, wenn 4 s nichts passiert. Kein Zoom in der Karte. Senkrechtes Wischen scrollt die Seite.
Fokus verlassen Klick ins Leere, Esc Tippen ins Leere, „Zurück“ Alles fliegt in den Stapel zurück.
Umbauen Knopf „Umbauen“ dito Vollbild (9.5).
  • Ruhebewegung (nur full): Das Modell schwebt kaum merklich (±4 mm, 6 s) und folgt dem Mauszeiger mit einer Neigung von höchstens 6°. Keine Ruhebewegung bei subtle/off, wenn die Karte nicht sichtbar ist oder der Tab im Hintergrund liegt.
  • frameloop="demand": Gezeichnet wird nur bei Bewegung oder Änderung. Ruhebewegung fordert Bilder an, solange sie läuft.
  • Die Karte zeigt unter dem Modell eine Zeile mit den abgeleiteten Werten: „2 Zargen · 6 Brutwaben · Futter 5 l · Bausperre drin“ und „Stand: 12.10. aus Durchsicht“.
  • Leser ohne colonies:update sehen alles, aber keine Sheets zum Ändern und keinen Knopf „Umbauen“.
  • NumberStepper kann heute nur ganze Zahlen. L2 erweitert ihn um Dezimalschritte (step 0,5) und Eingabe mit deutschem Komma; bestehende Aufrufe bleiben ganzzahlig.

9.5 Vollbild „Umbauen“

  • Eigene Route /colonies/[id]/hive (Zurück-Pfad zur Volk-Seite, kein Modal, damit „Zurück“ im Browser funktioniert). Nur online: Offline zeigt der Knopf „Umbauen“ den Hinweis „Umbauen geht nur mit Netz“; kleine Änderungen in der Karte gehen offline weiter (Abschnitt 10).
  • Links bzw. unten (Handy: Bottom-Sheet) die Teile-Leiste: aktive Teile-Arten des Systems, gruppiert (Zargen, Gitter und Böden, Fütterung und Behandlung, Abdeckung und Deckel). Jede Zeile hat ein kleines SVG-Symbol aus hiveLayout.
  • Drag & Drop in 3D: Beim Ziehen eines Teils öffnet sich der Stapel leicht (Abstand 60 mm zwischen den Teilen) und an jeder erlaubten Stelle leuchtet eine Einfügefläche. Über einer Fläche schnappt eine durchsichtige Vorschau ein. Loslassen fügt ein. Einlagen und Einsätze schnappen nur auf passende Wirte (Boden, Zarge). Zeiger-Ereignisse (Pointer Events), damit Maus, Stift und Finger gleich funktionieren.
  • Ohne Ziehen: Jede Zeile der Teile-Leiste hat „Hinzufügen …“ mit Auswahl der Stelle („über Zarge 1“). Das ist auch der Weg für Tastatur und Screenreader.
  • Verschieben und Entfernen: Teile im Stapel per Ziehen verschieben; zum Entfernen auf die Fläche „Entfernen“ ziehen oder im Sheet „Entfernen“. In der Aufbau-Liste mit „Nach oben“/„Nach unten“/„Entfernen“.
  • Neue Zargen starten mit allen Plätzen als FOUNDATION.
  • Änderungen sammeln sich lokal (mit Rückgängig/Wiederholen, Strg+Z/Strg+Umschalt+Z). „Speichern“ schickt sie als eine Änderung (Quelle EDITOR) mit Datum (Standard: heute, Berliner Tag). „Verwerfen“ fragt nach, wenn etwas offen ist. Verlassen der Seite mit offenen Änderungen fragt ebenfalls.
  • Zoom (Mausrad, zwei Finger) ist hier erlaubt, begrenzt auf 0,6–2,5.

9.6 Aufbau-Liste (Tastatur und Screenreader)

  • Neben dem Canvas liegt immer eine Liste als Baum (role="tree"), von oben nach unten so, wie man die Beute öffnet: Dach, Deckel, …, Boden; je Zarge die Rähmchen als Unterpunkte. In der Karte ist sie optisch versteckt, aber erreichbar; der sichtbare Knopf „Aufbau als Liste“ blendet sie ein. Im Vollbild ist sie sichtbar.
  • Der Canvas ist aria-hidden. Fokus in der Liste hebt das Teil in 3D hervor (wie Hover); Enter auf einer Zarge öffnet den Fokus-Modus; auf einem Rähmchen oder Teil mit Zustand das Sheet.
  • Jede Änderung meldet eine Live-Region: „Rähmchen 4 in Zarge 1 ist jetzt Brutwabe.“
  • Tab-Reihenfolge: Knopf „Umbauen“, „Aufbau als Liste“, Liste, „Zurück“ (im Fokus-Modus). Fokusstil aus T5.

9.7 Rückfall ohne WebGL

  • Vor dem Laden des 3D-Bundles zeigt die Karte das SVG aus hiveLayout (Schrägansicht, gleiche Farben). Ist der 3D-Teil geladen und WebGL verfügbar, blendet er über.
  • Kein WebGL, Kontextverlust ohne Wiederherstellung, Bewegungsstufe off mit schwachem Gerät (navigator.hardwareConcurrency <= 2) oder forced-colors: Die Karte bleibt beim SVG. Bedienung über SVG-Klicks und die Liste, gleiche Sheets.
  • Druck: SVG.

9.8 Bewegung

Stufe Flüge, Heben, Pegel-Wogen, Ruhebewegung Zustandswechsel
full (und kein prefers-reduced-motion) an, mit Federn (maath/easing damp3, ca. 350 ms) animiert
subtle aus Überblendung 150 ms (Deckkraft, Farbe); Teile springen an die Zielstelle
off aus sofort

Die Stufe kommt aus einem neuen Hook useEffectiveMotionLevel() in lib/motion: er nutzt effectiveMotionLevel() und hört auf matchMedia("(prefers-reduced-motion: reduce)") und auf Änderungen der gespeicherten Stufe, damit Szene und DOM sofort reagieren. Die DOM-Teile (Sheets, Liste, Teile-Leiste, Etiketten-Wechsel) nutzen motion (motion/react) mit diesem Hook.

9.9 Leistung

  • Das 3D-Bundle (three, fiber, drei-Teile, postprocessing) wird nur per next/dynamic mit ssr: false geladen, wenn die Karte in den Sichtbereich kommt. Budget: höchstens 320 kB gzip für diesen Chunk, gemessen im Build. Andere Seiten laden nichts davon.
  • Benannte drei-Importe mit Tree-Shaking, keine Sammelimporte.
  • Geometrien und Materialien werden je Teile-Art geteilt; Rähmchen als InstancedMesh je Zarge.
  • dpr={[1, 2]}, Bloom mit halber Auflösung. Ziel 60 fps während eines Flugs auf einem Mittelklasse-Handy, Mindestmaß 30 fps (Performance-Trace bei 4-facher CPU-Drosselung).
  • Der Canvas wird beim Verlassen der Seite freigegeben (dispose), kein Speicherleck beim Wechsel zwischen Völkern (Heap-Snapshot im Browser-Test).

9.10 Historie

  • In der Karte: „Verlauf“ öffnet eine Liste der Änderungen (Tag, Quelle, Kurztext wie „Bausperre entfernt, Zarge 2 hinzugefügt“). Der Kurztext wird aus den Operationen erzeugt (describeHiveOps, Frontend, reine Funktion).
  • Antippen einer Zeile zeigt den Stand danach im selben Modell, mit Leiste „Stand vom 12.08. · Zurück zu heute“. Bearbeiten ist dort aus.
  • Die Lebenslinie aus T5 kann die Änderungen später als Ereignisse zeigen; das ist nicht Teil dieser Spec.

10. Offline

  • Lesen: Das Stand-Paket (OfflinePackV1) bekommt die Aufbauten der Völker des Stands und den Katalog als optionale Felder (wie nextSteps), Backend- und Frontend-Typ gleich. GET /colonies/:id/hive kommt in NEVER_CACHED_API_PATHS (/\/api\/v1\/colonies\/[^/]+\/hive\/?$/i, SW_ROUTES_VERSION erhöhen) und wird über withPackFallback gelesen; sonst verdeckt eine alte zwischengespeicherte Antwort das neuere Paket. GET /hive-systems bleibt normal zwischengespeichert, mit dem Paket als Rückfall.
  • Die Offline-Volkseite /offline/colony zeigt die Beute schreibgeschützt als SVG mit Aufbau-Liste aus dem Paket.
  • Schreiben: Änderungen aus der Karte gehen offline in eine neue Warteschlange pending-hive-changes (Offline-DB Version 5, Eintrag in RecordQueueInputs und den Warteschlangen-Texten, mit Idempotency-Key, Besitzer, Fehleranzeige im Warteschlangen-Sheet wie die anderen).
  • Reihenfolge: Heute leert runSync alle Warteschlangen parallel. Neu ist ein Drain drainHiveTimeline: Er liest pending-hive-changes und die Einträge aus pending-inspections mit hiveChange, sortiert sie nach queuedAt und sendet sie nacheinander. Der normale Durchsicht-Drain überspringt Einträge mit hiveChange. Scheitert ein Eintrag nicht nur vorübergehend (Antwort ≠ Netzfehler/5xx), hält der Drain spätere Beute-Einträge desselben Volks zurück, bis der Nutzer den Fehler auflöst.
  • Anzeige: Die Karte zeigt den Stand mit allen wartenden Änderungen angewandt, aus beiden Quellen (pending-hive-changes und hiveChange wartender Durchsichten), und einen Hinweis „1 Änderung wartet auf Netz“.

11. Fehlerfälle

Fall Verhalten
409 beim Speichern online Sheet „Die Beute wurde inzwischen geändert“ zeigt den aktuellen Stand; die eigenen Operationen werden auf ihn neu angewandt und als Vorschau gezeigt. „Erneut speichern“ oder „Verwerfen“. Gelingt das Neu-Anwenden nicht, nennt das Sheet die Operation und den Grund.
409 HIVE_CONFLICT in der Warteschlange wie Abschnitt 7 bzw. 10, Knöpfe „Verwerfen“ und bei Durchsichten „Ohne Beute-Änderung senden“. Andere 409 (Idempotenz) behandelt die Warteschlange wie bisher.
403 bei Durchsicht mit Beute-Änderung Warteschlange zeigt „Keine Berechtigung, die Beute zu ändern“ mit „Ohne Beute-Änderung senden“.
Teile-Art ausgeblendet Bestehende Teile bleiben und zeigen „(ausgeblendet)“; die Teile-Leiste bietet sie nicht mehr an.
Unbekannte Teile-Art im JSON Darf nicht vorkommen (Validator). Falls doch (manuelle DB-Änderung): Teil als grauer Kasten mit „Unbekanntes Teil“, Fehler im Server-Log.
WebGL-Kontext verloren Einmal neu aufbauen; beim zweiten Verlust SVG (9.7).
Volk ohne Aufbau Karte „Noch keine Beute angelegt“ mit Vorschau der Vorlage und „Übernehmen“ / „Wie anderes Volk“. Leser ohne colonies:update sehen nur den Text.
Volk lebt nicht mehr (aufgelöst, eingegangen, …) Modell mit letztem Stand, keine Bearbeiten-Knöpfe.
Laden fehlgeschlagen Fehlermeldung in der Karte mit „Erneut versuchen“, Höhe bleibt (kein Springen).

12. Tests und Prüfung

12.1 Backend

  • Unit: applyHiveOps (jede Operation, jede Regel aus 4.1, Grenzen der Zustandsfelder, 100-Operationen- und 30-Teile-Grenze, Begrenzung von at), deriveHiveFacts, Validator. Fixtures in test/fixtures/hive-ops/.
  • Unit Service: Sperren (Colony, dann HiveSetup) und Version, 400 gegen 409 HIVE_CONFLICT, occurredOn-Regeln (Berliner Tag, nicht vor INIT), „Stand am Tag“ mit Nachtrag und vor INIT, zweites Anlegen 409 HIVE_EXISTS, Outbox-Ereignis HiveChanged in derselben Transaktion, Idempotenz.
  • e2e: Katalog lesen (colonies:read) und mit hive-systems:manage ändern (ohne: 403), Vorlage, Aufbau anlegen, Änderung, Historie mit Cursor, Stand am Tag, Durchsicht mit Beute-Änderung (gemeinsam gespeichert, gemeinsam abgelehnt; STAFF ohne colonies:update 403), nicht lebendes Volk schreibgeschützt, Backup-Export und -Import mit den neuen Modellen (auch in eine frische Installation mit Startdaten).

12.2 Frontend

  • Unit: hiveLayout (Höhen, Gesamt-Box, Rähmchen-Lagen, Einlagen, Einsätze), Frontend-applyHiveOps gegen dieselben Fixtures, hiveFollowUps, describeHiveOps.
  • Komponenten (Vitest + RTL): Aufbau-Liste (Tastatur, Live-Region, Sheets), SVG-Rückfall, Karte ohne Aufbau, 409-Ablauf, Durchsicht-Abschnitt „Beute“ mit Rückgängig, NumberStepper mit Dezimalschritten, useEffectiveMotionLevel, Volk-Seite mit neuem Raster, Offline-Volkseite mit Beute.
  • Offline: drainHiveTimeline (Reihenfolge über beide Warteschlangen, Zurückhalten nach Fehler, Durchsicht-Drain überspringt), DB-Migration auf Version 5, sw-routes mit dem neuen Pfad.
  • Szene: @react-three/test-renderer für Hover, Klick und Fokus-Modus (Zielwerte aus use-hive-motion), ohne echtes WebGL.
  • Playwright: Volk öffnen, Zarge über die Liste öffnen, Rähmchen-Art ändern, Seite neu laden, Änderung da; Umbauen mit „Hinzufügen …“ (ohne Ziehen) und Speichern.
  • Browser-Prüfung mit chrome-devtools: Desktop 1280 und 390 px, gleiche Höhe wie Stammdaten, 1 und 6 Zargen ohne Abschnitt, hell/dunkel, prefers-reduced-motion, Tastatur, Performance-Trace eines Flugs, Heap-Snapshot nach zehn Volk-Wechseln.

12.3 Abhängigkeiten

Neu im Frontend: three, @react-three/fiber, @react-three/drei, @react-three/postprocessing, postprocessing, motion; dev: @types/three, @react-three/test-renderer. Versionen exakt gepinnt, jede mindestens 14 Tage alt, Peer-Bereiche passend zu React 19.2 (fiber 9 verlangt react <19.4, postprocessing verlangt three <0.187). Lizenzen MIT bzw. Zlib. Vor dem Einbau Prüfung mit dem Skill supply-chain-risk-auditor.

12.4 Folgen für T5

T5 §1 schreibt „Keine neue Animation“ und „keine neue Laufzeit-Bibliothek“. Der Inhaber will seit 2026-10-15 ausdrücklich Bewegung und eine hochwertigere Optik. Diese Spec führt motion ein und begrenzt sich auf die Beute. Vor der Umsetzung von T5 wird die T5-Spec um eine Bewegungs-Sprache ergänzt (eigene Rückfrage an den Inhaber), statt die Regeln hier stillschweigend auf die ganze App auszudehnen. Eintrag in TASKS.md.

13. Lieferungen

Jede Lieferung ist für sich lauffähig und wird einzeln ausgerollt (nach Freigabe des Inhabers).

  1. L1 Daten und API: Prisma-Modelle und Migration, Katalog mit Startdaten und festen Ids (Abschnitt 4.3) per Migration, Berechtigung hive-systems:manage, applyHiveOps, Validator, Facts, Endpunkte 6.2, Durchsicht mit hiveChange (Backend), Ereignis HiveChanged, Backup. Keine Oberfläche.
  2. L2 Karte ohne 3D: Typen, Frontend-Operationen, hiveLayout, SVG, Aufbau-Liste, NumberStepper mit Dezimalschritten, Sheets für Rähmchen und Zustand, Karte auf der Volk-Seite (neues Raster, gleiche Höhe), „Aufbau anlegen“, Historie, Vorschläge Fütterung und Behandlung. Damit ist die Funktion online vollständig nutzbar, nur ohne 3D.
  3. L3 3D-Szene: Abhängigkeiten, Canvas, Stil, Einpassen, Hover, Fokus-Modus, Rähmchen-Heben, Pegel, Bewegungsstufen, Leistung.
  4. L4 Umbauen: Vollbild-Route, Teile-Leiste, Drag & Drop in 3D, „Hinzufügen …“, Rückgängig, Speichern.
  5. L5 Durchsicht und Offline: Abschnitt „Beute“ im Formular, Stand-Paket mit Aufbauten, Lesen über withPackFallback, Offline-Volkseite, Warteschlange pending-hive-changes, drainHiveTimeline.

14. Annahmen und offene Punkte

Nr. Annahme Prüfung
A1 Außenmaß der Liebig-Zander-Zarge 525 × 430 mm (aus Innenmaß Edelstahldeckel). Inhaber misst oder schickt Fotos; Katalogwert ändern.
A2 Styropor-Einlage 465 × 365 × 20 mm. Inhaber misst.
A3 Nassenheider Professional liegt in einer Leerzarge auf den Rähmchen der Zarge darunter; im Modell ein Einsatz der Leerzarge. Inhaber bestätigt mit Foto.
A4 Rähmchen-Plätze zählen von links, mit Blick auf das Flugloch. Inhaber bestätigt.
A5 Aufbau ändern mit colonies:update, Katalog mit hive-systems:manage (ADMIN). STAFF kann die Beute damit nicht ändern, auch nicht in der Durchsicht. Inhaber entscheidet, ob STAFF colonies:update bekommt (Rollenverwaltung, kein Code).
A6 Die Bausperre ist eine Einlage ohne Zustand; „Winter“ ist kein eigener Modus, sondern Einlegen und Entfernen. Inhaber.

Diese Annahmen kommen nach ARCHITECTURE.md „Annahmen & offene Fragen“, sobald L1 gemergt ist.