- 3D dependencies (three, react-three-fiber, drei, postprocessing, motion), exact versions, supply-chain checked; scene loaded only on demand on the colony page (283 kB gzip), SVG stays as fallback - glass hologram per owner design 2026-10-16: true Liebig Zander shapes (grips, rebate, Wanderboden foot), cool blue edge glow, click any part -> it flies to the front while the rest stays as a faint ghost; focus shows content (feed levels, Bausperre, pockets); frames rise on hover and take their type look (colour + comb pattern) - full-bleed card with frosted overlays and glass buttons - Futtertasche (FRAME_FEEDER): new part kind occupying frame slots with a fill level, catalog row, validator rules in both ports, outline, sheets, follow-ups, settings Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
50 KiB
Beute in 3D (Ist-Zustand, Umbau, Rähmchen) — Design
Stand: 2026-10-15 · 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:
- 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.
- 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.
- „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.
- Eine Durchsicht kann den Aufbau ändern, auch offline; nach dem Sync sind Durchsicht und Aufbau-Änderung gemeinsam gespeichert oder gemeinsam abgelehnt.
- Alles, was in 3D geht, geht auch per Tastatur und Screenreader über die Aufbau-Liste. Ohne WebGL erscheint dieselbe Beute als SVG.
- Bei Bewegungsstufe „ruhig“ oder „aus“ und bei
prefers-reduced-motionfliegt 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
HiveChangedim 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
equipmentverwaltet 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 bismaxin Schritten vonstep. Wird im Modell als Pegel gezeigt.choice: einer der Werte ausoptions.feedType(optional, nur beilevel): Futterart der Fütterung (FeedTypeaus 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 = falseausgeblendet; Aufbauten, die sie nutzen, bleiben gültig. - Anlegen eines Aufbaus schreibt Version 1 und eine
HiveChangemit QuelleINIT, damit die Historie vollständig ist. Ein zweitesPOST /colonies/:id/hiveantwortet 409HIVE_EXISTS(geprüft unter Sperre, nicht über einen Unique-Fehler). - Sperren:
HiveSetupkommt inLOCKABLE_TABLESdirekt nachColony. Jede Aufbau-Schreibung sperrt zuerst die ZeileColonymitFOR UPDATE(und prüft dabei, dass das Volk lebt), dannHiveSetup. - Idempotenz: über den bestehenden
@Idempotent()-Interceptor (Idempotency-Keyje Nutzer, T3). Keine eigene Spalte. - Backup: Das In-App-Backup listet Modelle von Hand (
backup.service.ts). Export vonhiveSystem,hivePartType,hiveSetup,hiveChange; Import in der ReihenfolgehiveSystem→hivePartType→ (nachcolony)hiveSetup→ (nachinspection)hiveChangemituserIdüberresolveUserId(...). Nur versiegelte Sicherungen, nicht inLEGACY_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(allelevel-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:managekommt in denPERMISSION_CATALOG. Die Katalog-Synchronisierung beim Start gibt sie der Systemrolle ADMIN; keine Grant-Migration.POST …/changesantwortet 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 aufbody.code. sourceist in dieser SpecCARDoderEDITOR.INITundINSPECTIONsetzt nur der Server.AI_SUGGESTIONsteht im Enum, wird aber erst mit Teil 3 angenommen (vorher 400).occurredOnist ein Berliner Kalendertag und darf nicht in der Zukunft liegen (wie T6) und nicht vor dem Tag derINIT-Ä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 neueversion, damit der Client weiß, ob er aktuell ist. - Stand am Tag X ist
stateAfterder Änderung mit der höchsten Version unter allen Änderungen mitoccurredOn <= 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 demINIT-Tag antwortet der Endpunkt 404. - Völker, die nicht mehr leben (
!isLivingColonyStatus(status)), sind schreibgeschützt: 409 wieassertActiveim 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.
CreateInspectionDtobekommthiveChange?: { ops }. Der Service nimmthiveChangewieapplyToColonyaus dem DTO heraus, bevor die Durchsicht geschrieben wird. Er legt Durchsicht undHiveChange(QuelleINSPECTION,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
hiveChangebrauchtinspections:createundcolonies:update, geprüft vor der Transaktion (Muster der bestehenden Zusatzprüfung im Inspection-Service). Fehltcolonies: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
broodFramesder 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, mithiveChangeim Input; gesendet wird sie über die Beute-Zeitleiste aus Abschnitt 10. Bei 409HIVE_CONFLICTzeigt die Warteschlange „Beute hat sich inzwischen geändert“ mit den Knöpfen „Ohne Beute-Änderung senden“ (gleicherIdempotency-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-startwird in zwei Zeilen geteilt. Zeile 1: Stammdaten (1 Spalte) und Beute-Karte (2 Spalten), ohneitems-start, damit die Rasterzeile beide Karten auf dieselbe Höhe streckt. Die Beute-Karte bekommt überlgkeine eigene Höhe, sie folgt den Stammdaten; der Canvas sitzt in einem Flex-Kind mith-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ältitems-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 vonmin(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 (Bewegungsstufefull) 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:rootund.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 mitCanvasText-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 beisubtle/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:updatesehen alles, aber keine Sheets zum Ändern und keinen Knopf „Umbauen“. NumberStepperkann heute nur ganze Zahlen. L2 erweitert ihn um Dezimalschritte (step0,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
offmit schwachem Gerät (navigator.hardwareConcurrency <= 2) oderforced-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/dynamicmitssr: falsegeladen, 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
InstancedMeshje 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 (wienextSteps), Backend- und Frontend-Typ gleich.GET /colonies/:id/hivekommt inNEVER_CACHED_API_PATHS(/\/api\/v1\/colonies\/[^/]+\/hive\/?$/i,SW_ROUTES_VERSIONerhöhen) und wird überwithPackFallbackgelesen; sonst verdeckt eine alte zwischengespeicherte Antwort das neuere Paket.GET /hive-systemsbleibt normal zwischengespeichert, mit dem Paket als Rückfall. - Die Offline-Volkseite
/offline/colonyzeigt 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 inRecordQueueInputsund den Warteschlangen-Texten, mitIdempotency-Key, Besitzer, Fehleranzeige im Warteschlangen-Sheet wie die anderen). - Reihenfolge: Heute leert
runSyncalle Warteschlangen parallel. Neu ist ein DraindrainHiveTimeline: Er liestpending-hive-changesund die Einträge auspending-inspectionsmithiveChange, sortiert sie nachqueuedAtund sendet sie nacheinander. Der normale Durchsicht-Drain überspringt Einträge mithiveChange. 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-changesundhiveChangewartender 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 vonat),deriveHiveFacts, Validator. Fixtures intest/fixtures/hive-ops/. - Unit Service: Sperren (Colony, dann HiveSetup) und Version, 400 gegen 409
HIVE_CONFLICT,occurredOn-Regeln (Berliner Tag, nicht vorINIT), „Stand am Tag“ mit Nachtrag und vorINIT, zweites Anlegen 409HIVE_EXISTS, Outbox-EreignisHiveChangedin derselben Transaktion, Idempotenz. - e2e: Katalog lesen (
colonies:read) und mithive-systems:manageändern (ohne: 403), Vorlage, Aufbau anlegen, Änderung, Historie mit Cursor, Stand am Tag, Durchsicht mit Beute-Änderung (gemeinsam gespeichert, gemeinsam abgelehnt; STAFF ohnecolonies:update403), 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-applyHiveOpsgegen 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,
NumberSteppermit 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-routesmit dem neuen Pfad. - Szene:
@react-three/test-rendererfür Hover, Klick und Fokus-Modus (Zielwerte aususe-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).
- 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 mithiveChange(Backend), EreignisHiveChanged, Backup. Keine Oberfläche. - L2 Karte ohne 3D: Typen, Frontend-Operationen,
hiveLayout, SVG, Aufbau-Liste,NumberSteppermit 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. - L3 3D-Szene: Abhängigkeiten, Canvas, Stil, Einpassen, Hover, Fokus-Modus, Rähmchen-Heben, Pegel, Bewegungsstufen, Leistung.
- L4 Umbauen: Vollbild-Route, Teile-Leiste, Drag & Drop in 3D, „Hinzufügen …“, Rückgängig, Speichern.
- L5 Durchsicht und Offline: Abschnitt „Beute“ im Formular, Stand-Paket mit Aufbauten, Lesen über
withPackFallback, Offline-Volkseite, Warteschlangepending-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.