# T5 Optik mit eigener Handschrift Implementation Plan **Status (2026-10-16):** Noch nicht begonnen. Plan-Review erledigt (Abschnitt „Review 2026-10-16“ direkt unten), Bewegungs-Sprache steht in Spec §17 und ist als Task A11 eingeplant. Offen beim Inhaber: Spec §16 Fragen 1–3 (vorläufig A/A/A) und die neue Frage 4 (Bewegungs-Sprache, Empfehlung A). Issue: [#26](https://git.encry.de/Nick.Rosenloecher/ApiCore/issues/26). ## Review 2026-10-16 Geprüft gegen den Code nach Beute L1–L5, T4a/T4b, T6 und T7 L1/L2/L3/L5. Die Korrekturen gelten vor dem jeweiligen Task-Text. Wo sie ihm widersprechen, gilt die Korrektur. **Allgemein** - G1 (Voraussetzungen): T4a **und T4b** sind gemergt (TASKS.md). T5b läuft damit ohne Kalender-Rückfall. - `PHASE_SOURCE_LABELS` liegt in `src/lib/phenology/labels.ts`; die lokale Ersatzdefinition entfällt. - `SeasonChip` liegt in `src/components/autopilot/season-chip.tsx`, `fetchAutopilotToday` in `src/lib/autopilot/api.ts`, `INTENT_ICONS` in `src/components/autopilot/next-steps-card.tsx`. - G2 (Global Constraints, Zeile „Bewegung“): ersetzt durch Spec §17. Ab sofort gilt: `motion-full:` statt `motion-safe:` für jede Verschiebung, Tokens aus `src/lib/motion/tokens.ts`, nichts Datenhaftes bewegt sich. Rad, Lebenslinie und Waben bleiben ohne Animation. - G3 (Bestand `motion`): Installiert ist `motion` **13.4.6**, nicht 12.x. `maath` ist da, `framer-motion` nur transitiv. Kein Quelltext importiert `motion/react` (Beute §9.8 sah es für die DOM-Teile vor, gebaut wurde CSS). Task A11 bringt die ersten DOM-Nutzer. Ohne A11 wäre die Abhängigkeit ungenutzt. - G4 (Aufwand): T5a wächst von ≈ 4,75 auf ≈ 6,4 PT. Gründe: A7 +0,4 (31 statt 17 Checkbox-Stellen), neue Tasks A11 (0,75) und A12 (0,5). T5b bleibt bei ≈ 5,25 PT. - G5 (Wellen T5a): A11 läuft in Welle 9 nach A8. Sie teilt `globals.css` mit A1/A4/A5, `today-panel.tsx` mit A7 und `ui/*` mit A5. A12 läuft in Welle 9 parallel; sie fasst nur `components/hive/**` an, `globals.css` nicht, weil die Holo-Sonne-Werte schon in A1 kommen. A10 rückt in Welle 10 und wartet auf A8, A9, A11 und A12. **Je Task** - A1: - `--holo-*` (in `:root`/`.dark` seit Beute L3) bekommt Werte in beiden Sonne-Sätzen: Bühne `oklch(0 0 0)`, Kanten und Etikett-Text `oklch(1 0 0)`, Etikett-Fläche `oklch(0.12 0 0)`. - Neu ist der Rahmen `.holo-stage { --ring: var(--holo-edge-strong) }` (Spec §17.8). - `contrast.spec.ts` bekommt zwei Zeilen: `holo-label-fg`/`holo-label-bg` ≥ 7 und `holo-edge-strong`/`holo-bg` ≥ 3, in allen vier Sätzen. - Grund: `--primary-ink` auf `--holo-bg` ≈ 2,8:1. - A2: - `motion-preference.ts` hat seit der Beute `useEffectiveMotionLevel` und `MOTION_EVENT`. Nutzer sind `hive-canvas.tsx`, `hive-visual-slot.tsx` und `today/use-task-actions.ts`. Die API bleibt; `useAppearance().setMotion` löst `MOTION_EVENT` aus. - Neu ist das Ereignis `apicore:appearance` bei jedem Wechsel von Schema oder Kontrast; A12 hört darauf. - Test dazu: `it('a motion change from the appearance section reaches useEffectiveMotionLevel in the same tab', ...)`. - Die Stufe heißt in der Oberfläche „Dezent“. - A3: - Verlauf-Verbraucher sind **33 Dateien**, nicht 31. `text-white` steht zusätzlich in `components/hive/editor/editor-stage-overlay.tsx` (auf der Bühne → `text-holo-label-fg`). - `AREAS` fehlen Bereiche aus T6/T7/Beute, die in `nav-config.ts`/`quick-actions.ts` stehen. Neue Schlüssel: - `compliance` (`ShieldCheck`, betrieb) - `medicines` (`Pill`, erfassung) - `voice` (`Mic`, erfassung) - `hive-systems` (`Box`, neutral) - `system` (`Activity`, neutral) - `areas.spec` prüft danach wieder jeden Nav- und Quick-Action-Eintrag. - Neue Icon-Abweichungen, die korrigiert werden müssen: - Arzneimittel-Packungen nutzen `Package` (= Abfüllungen) in `medicines/[id]/page.tsx` und `medicines/lots/[id]/page.tsx`. - Beutensysteme nutzen `Boxes` (= Rohstoffe) in `settings/hive-systems/page.tsx`, die Navigation dagegen `Box`. - `settings/voice/page.tsx` nutzt `ShieldCheck` (= Prüfbereitschaft). - Das Dashboard trägt seit T6 `ComplianceLine` und `StandNote`; beide bleiben unverändert. - A4: - Die Codemod-Trefferzahlen vor dem Lauf neu zählen (`grep`), die Zahlen im Task sind vom 14.10. - `dark:` steht weiter in genau 8 `ui/*`-Dateien. - Die Beute-Bühne (`bg-holo-*`) bleibt unberührt: Sie ist in hell und dunkel absichtlich dunkel. - A5: - `sm:h-`/`sm:min-h-` stehen in **11 Dateien**, nicht in 4. - Der Fokus-Codemod trifft 5 Stellen in `components/hive/**`. Dort entfällt der eigene Ring, und der Ring kommt aus `.holo-stage` (A1). - `.honey-check` hat `outline: none` plus eigenen Schatten-Ring in `globals.css`. Er nimmt die globale Regel, die Ausnahme entfällt. - A6: - `NumberStepper` hat seit Beute L2 `step` mit Dezimalschritten; die Umstellungen nutzen nur ganze Schritte. - Neue Radio-Eigenbauten → `ChoiceGroup`: `compliance/notices/create-notice-dialog.tsx` (`type="radio"`) und `components/phenology/phenology-report-sheet.tsx` (`type="radio"`). - Neues `aria-pressed` in `components/hive/hive-card.tsx` („Aufbau als Liste“) → `Button pressed`. - Ergänze die Dateien in der Files-Liste und im Testbefehl (`"src/app/(app)/compliance" src/components/phenology src/components/hive`). - A7: Heute sind es **31 Stellen in 27 Dateien** (13 `ToggleChip`, 13 native, 4 Base UI, 1 `today-panel`). - `ToggleChip`: 13 Stellen in 11 Dateien, neu dazugekommen: - `compliance/notices/create-notice-dialog.tsx` - `compliance/restrictions/create-restriction-dialog.tsx` - `compliance/settings/page.tsx` ×3 - `medicines/medicine-form-dialog.tsx` - `profile/telegram-link-card.tsx` - `settings/automation/automation-rules-tab.tsx` - `settings/automation/rule-forms/rule-form-parts.tsx` - Natives `type="checkbox"`, neu: `settings/hive-systems/system-dialog.tsx`, `settings/voice/page.tsx`, `components/colonies/colony-takeover-section.tsx`. - `components/form/switch-row.tsx` (`role="switch"`, Nutzer `profile/notification-settings-card.tsx`, `telegram-link-card.tsx`) geht in `CheckboxField` auf. Dafür bekommt `CheckboxField` das Prop `role?: "checkbox" | "switch"`, Umsetzung mit Base UI `Switch`. Danach wird `SwitchRow` gelöscht. Die Wächter-Regel erlaubt `role="switch"` nur in `ui/checkbox.tsx`/`ui/switch.tsx`. - `today-panel.tsx` (`task`) kommt zuletzt, weil A11 dieselbe Datei danach anfasst. - Aufwand 0,9 PT. - A8: - `ActiveHistoryTabs` hat jetzt **10 Nutzer**, neu ist `medicines/page.tsx`; sie kommt in die Files-Liste. - `TabSwitcher` hat unverändert 2 Nutzer. - A9: - `SHELL_CACHE` ist weiter `apicore-shell-v4` → v5 gilt. - `SW_ROUTES_VERSION` haben Beute L5 und T7 erhöht. „Bleibt unverändert“ heißt: A9 erhöht sie nicht noch einmal. - `/inspections/new`, `/varroa/new`, `/voice/new` stehen in `SHELL_ROUTES`. Ein vierter Shortcut „Sprachnotiz“ (`/voice/new`) ist möglich, aber nicht Spec; nur nach Freigabe. - A10: - `src/app/(app)/dashboard/page.spec.tsx` gibt es schon → Modify, nicht Create. - Die Screenshot-Matrix bekommt die Seiten aus T6, T7 und Beute: - Volk mit Beute-Karte (Bühne, Fokus, Sheet) - `/colonies/[id]/hive` (Umbauen) - `/voice` und `/voice/new` (Aufnahme-Punkt) - `/settings/voice` - `/offline`, `/offline/apiary` und `/offline/colony` - das Warteschlangen-Sheet (`components/offline/pending-queue-sheet.tsx`) und das Offline-Band - `/compliance`, `/medicines` - Die Matrix läuft je in Voll, Dezent und Aus, zusätzlich mit `prefers-reduced-motion: reduce` auf Start, Volk und `/voice/new`. - A11 **neu**: Bewegungs-Sprache durchsetzen (Task-Text unten nach A10). - A12 **neu**: Beute-Bühne an Sonne und Darstellungswechsel anschließen (Task-Text unten nach A11). - B1: Die Offline-Standseite `src/app/(app)/offline/apiary/page.tsx` zeigt die Wabe aus dem Paket mit demselben `ApiaryComb` (Modify ergänzen, Test `it('the offline apiary page shows the comb from the pack', ...)`). `apiary-colonies-card.tsx` nutzt `inspectionDueOn` schon. Die Tests für den Zweig ohne T4 bleiben für alte Pakete. - B3: Die Keys stimmen (`varroa-measurements`, `swarm-control-checks`, `next-steps`, `history`). `["colonies", id, "hive"]` gibt es neu. Beute-Änderungen als Ereignisse sind laut Beute §9.10 nicht Teil von T5; die Lebenslinie lässt sie aus. - B4: Die Volksseite hat seit Beute L2 ein neues Raster: - oben `NextStepsCard` über die volle Breite - Zeile 1: Stammdaten und Beute, gleich hoch, ohne `items-start` - Zeile 2: Aktionen | Karten - „Desktop rechte Spalte oben“ ist jetzt die Beute. Die Lebenslinie kommt in Zeile 2 als erstes Kind der 2-Spalten-Fläche, über dem `xl:grid-cols-2`-Raster. - Reihenfolge im DOM: Stammdaten, Beute, Aktionen, Lebenslinie, Karten. - Test `it('the colony page puts the lifeline under „Als Nächstes“', ...)` → `it('the lifeline opens row 2 above the record cards and leaves row 1 to Stammdaten and Beute', ...)`. - B5: Unverändert gültig. `AutopilotToday.seasons` hat weiter weder `segmentSince`, `reportedOn` und `note` noch `seasonCalendar`. Die Dateien sind `src/autopilot/next-steps.service.ts`, `src/autopilot/dto/next-steps-response.dto.ts` und `test/next-steps.e2e-spec.ts`. - B6: `season-defaults.spec.ts` liest nicht `default-rules.ts`, sondern `ApiCore-Backend/src/automation/rule-config/phenology.config.ts` (`calendarFallback`, Schwarmzeit) und `season-autopilot.config.ts` (`winterVarroaWindow`). - B7: Das Dashboard hat heute noch Honigbanner und `StatTile`; A3 und B7 sind also weiter nötig. `ComplianceLine` und `StandNote` bleiben über „Bienenjahr“ an ihrem Platz. > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** ApiCore ist draußen am Stand lesbar und bedienbar (Sonne, Handschuhe, eine Hand) und erkennbar eine Imker-App: vier geprüfte Farbsätze (hell, dunkel, Sonne hell, Sonne dunkel), ein durchgesetzter Baukasten und die Wabenzelle als einziges Motiv (Waben-Standkarte, Mini-Waben-Marker, Lebenslinie, Bienenjahr-Rad, Markenzeichen). **Architecture:** Farben stehen nur als Tokens in `globals.css`; ein Wächter-Test misst jedes Paar aus Spec 4.4 in allen vier Sätzen. Ein Inline-Skript im `` setzt Farbschema, Kontrast und Bewegung vor dem ersten Bild (`next-themes` entfällt). Die Umstellung läuft in der Reihenfolge der Spec (Tokens additiv, Darstellung, Bereichs-Icons, Farben, Fokus, Bausteine), sodass jede Seite nach jedem Task lesbar bleibt; ein Baukasten-Wächter hält das Ergebnis. Grafiken sind handgeschriebenes SVG über reine Modellfunktionen (`cell-state`, `combLayout`, `buildLifeline`, `buildYearWheel`), die Ansichten sind dünn und liefern zu jeder Grafik eine Tabelle. **Tech Stack:** Next.js 16 App Router, React 19, Tailwind 4 (`@theme inline`, `@custom-variant`), shadcn/Base UI, TanStack Query 5, react-leaflet/Leaflet 1.9, Vitest 4 + RTL, `sharp` (neue devDependency, nur für das Icon-Skript). Backend-Zusatz in NestJS 11/Jest 30 (Task B5). Keine neue Laufzeit-Bibliothek. **Spec:** `docs/superpowers/specs/2026-10-13-t5-optik-design.md` (bindend). Bausteine aus `docs/superpowers/plans/2026-10-13-t3-erfassung-am-stand.md` und `docs/superpowers/plans/2026-10-13-t4-logik-ketten.md`. **Lieferungen:** T5a „Sonnentauglich und Baukasten“ (Tasks A1–A12, ≈ 6,4 PT nach Review 2026-10-16, hängt nur an T3) und T5b „Handschrift“ (Tasks B1–B8, ≈ 5,25 PT, nach T4a; ohne T4 greift der Kalender-Rückfall). **Vorläufige Inhaber-Entscheidungen (Empfehlungen, vor der Ausführung noch überstimmbar):** Hauptknopf = heller Honig mit dunkler Tinte (Offene Frage 1, A, 9,4:1). Zellen der Waben-Standkarte nach Volkscode (Frage 2, A). Standard-Farbschema auf neuem Gerät = Hell (Frage 3, A). Kippt eine Antwort, ändert sich nur das Token bzw. eine Konstante (`DEFAULT_THEME` in Task A2, `--primary*` in Task A4, `sortCells` in Task B1); `contrast.spec.ts` hält jede Variante. --- ## Global Constraints **Voraussetzungen (nicht neu bauen, genau so nutzen)** - T3 ist gemergt: Tasks 7 (`nav-config.ts`, `quick-actions.ts`, Sheets), 8 (`NumberStepper`, `RatingScale` mit `RATING_TONE_CLASS`, `rating-contrast.spec.ts`), 11 (Varroa-Trend als handgeschriebenes SVG), 14 (`colonyStatusLight`, `StatusLightBadge`, `ApiaryColoniesCard`, `useColonyOverview`/`colonyOverviewKey`), 15 (Offline-Paket, `withPackFallback`, Service Worker mit `SHELL_ROUTES`). - T5b braucht T4a: Tasks 1 (`rule-config`), 15 (`ColonyOverviewRow.inspectionDueOn`, `NextStepsService.today`, `AutopilotToday`), 17, 18 (`SeasonChip`, `SEGMENT_LABELS`, Heute-Karten), 19 (`rating`/`band` je Messung). T4b (Task 31 `PHASE_SOURCE_LABELS`) ist optional; fehlt es, definiert T5b die drei Quellen-Texte lokal ('gemeldet', 'geschätzt', 'Kalender'). - Alle Pfade relativ zu `ApiCore-Frontend/`, sofern nicht anders angegeben; Backend-Pfade relativ zu `ApiCore-Backend/`. - Vor jedem Next.js-Code die passende Anleitung unter `node_modules/next/dist/docs/` lesen (`AGENTS.md`); für T5 vor allem `01-app/02-guides/preventing-flash-before-hydration.md`, `progressive-web-apps.md`, `01-app/03-api-reference/03-file-conventions/01-metadata/manifest.md`, `app-icons.md`. - Grafiken: den `dataviz`-Skill laden (wie T3/T4). Gestaltung: `impeccable` führt; keine neue Laufzeit-Bibliothek, `next-themes` fliegt raus. **Verhalten und Barrierefreiheit** - WCAG 2.2 AA. Text ≥ 4,5:1, Bedienrahmen, Fokus und Grafik ≥ 3:1; Fließtext und Hauptknopf Ziel ≥ 7:1. - Touch-Ziele ≥ 44 px, Sheet-Zeilen 56 px, benachbarte Ziele ≥ 4 px Abstand. Kompaktmaß (32/36 px) nur mit `pointer-fine:`, nie mit `sm:`. - Grundschrift 16 px (`text-base`) für Fließtext, Labels, Auswahl-Knöpfe, Listentitel, Knopftext, Eingabe; Nebentext 14 px (`text-sm`); `text-xs` = 13 px nur für Achsen, Legenden, Fußzeilen; in Sonne 16/14 px. Überschriften Space Grotesk, nie in Knöpfen oder Labels. - Ein Fokusstil: 2 px durchgehender Ring in `--ring`, 2 px Abstand (Sonne 3 px), als Basisregel auf `:where(:focus-visible)`. Einzige Ausnahme: `honeycomb-cell.tsx` (zweiter Sechseckpfad, Spec 6.6). - Zustand nie nur über Farbe: Icon plus Wort, Rand-Stufen über Strichstärke und Strichelung. Jede Grafik hat ``, `<desc>` und eine aufklappbare Tabelle aus demselben Modell. - Bewegung (geändert 2026-10-16): Spec §17 ist bindend. Stufen über `useEffectiveMotionLevel()` und die CSS-Variante `motion-full:` (nie `motion-safe:` für Verschiebung). Tokens kommen aus `src/lib/motion/tokens.ts` und `--motion-*`. Bewegt werden nur `transform` und `opacity`, Datenwerte, Fehlertexte und Eingaben nie. `motion` nur über `LazyMotion`/`m.*` im `(app)`-Layout. Rad, Lebenslinie und Waben erscheinen weiter sofort. Durchgesetzt wird das in Task A11, der Wächter prüft es. - Deutsche Oberfläche, kurze Sätze, ein Hauptknopf (Honig) je Ansicht, ADHS-freundlich: wenig kognitive Last. Code-Bezeichner englisch. - Verboten (Wächter prüft): Weiß auf Honig, `text-primary` als Schriftfarbe (nur `text-primary-ink`), `ring-ring/50`, `outline-none`, Farbverlauf als Hintergrund, `text-white`, `dark:`-Klassen, Text < 13 px, Zustand nur über Farbe, eigene `role="radio|checkbox|tab"`-Bauten außerhalb des Baukastens. Ein Bereichston ist nie eine Zustandsfarbe. - Wiederverwenden statt kopieren: `StatusLightBadge`, `colonyStatusLight`, `ChoiceGroup`, `NumberStepper`, `Field`, `QueryState`, `PageHeader`/`DetailHeader`/`EntityCard`, `useColonyOverview`, Query-Keys der bestehenden Karten (keine Doppel-Requests). **Tokens (exakte Werte, OKLCH ist die Quelle, alle im sRGB-Gamut)** | Token | Hell | Dunkel | |---|---|---| | `--background` | `oklch(0.968 0.014 85)` | `oklch(0.19 0.016 55)` | | `--foreground` | `oklch(0.21 0.03 55)` | `oklch(0.95 0.018 80)` | | `--card`, `--popover`, `--sidebar` | `oklch(0.995 0.004 90)` | `oklch(0.235 0.018 55)` | | `--muted` | `oklch(0.925 0.018 82)` | `oklch(0.28 0.018 55)` | | `--muted-foreground` | `oklch(0.44 0.03 60)` | `oklch(0.76 0.025 75)` | | `--primary` | `oklch(0.80 0.145 78)` | `oklch(0.80 0.14 78)` | | `--primary-foreground` | `oklch(0.21 0.035 55)` | `oklch(0.19 0.03 55)` | | `--primary-ink` neu | `oklch(0.47 0.115 55)` | `oklch(0.82 0.13 80)` | | `--ring` | = `--primary-ink` | = `--primary-ink` | | `--secondary` / `-foreground` | `oklch(0.44 0.07 145)` / `oklch(0.98 0.015 100)` | `oklch(0.66 0.08 145)` / `oklch(0.16 0.02 145)` | | `--accent` / `-foreground` | `oklch(0.935 0.03 78)` / `oklch(0.25 0.045 50)` | `oklch(0.31 0.03 60)` / `oklch(0.95 0.018 80)` | | `--destructive` | `oklch(0.50 0.17 28)` | `oklch(0.70 0.15 28)` | | `--destructive-foreground` neu | `oklch(0.985 0.005 30)` | `oklch(0.17 0.03 30)` | | `--border` (nur Trennlinien) | `oklch(0.86 0.022 78)` | `oklch(0.34 0.02 60)` | | `--input` (Rahmen jedes Bedienelements) | `oklch(0.60 0.035 65)` | `oklch(0.56 0.025 70)` | | `--warning` / `-foreground` | `oklch(0.79 0.15 80)` / `oklch(0.23 0.04 60)` | `oklch(0.80 0.14 80)` / `oklch(0.20 0.03 60)` | | `--warning-ink` neu | `oklch(0.50 0.11 65)` | `oklch(0.82 0.13 82)` | | `--success` / `-foreground` | `oklch(0.46 0.08 145)` / `oklch(0.98 0.015 100)` | `oklch(0.70 0.09 145)` / `oklch(0.16 0.02 145)` | | `--condition-excellent` / `-foreground` / `-ink` | `0.62 0.13 150` / `0.15 0.04 150` / `0.40 0.09 150` | `0.68 0.12 150` / `0.17 0.04 150` / `0.80 0.11 150` | | `--condition-good` | `0.645 0.13 125` / `0.17 0.04 125` / `0.41 0.09 125` | `0.72 0.12 125` / `0.18 0.04 125` / `0.83 0.11 125` | | `--condition-average` | `0.665 0.125 88` / `0.19 0.04 90` / `0.44 0.09 85` | `0.77 0.13 90` / `0.20 0.04 90` / `0.86 0.11 90` | | `--condition-weak` | `0.665 0.14 58` / `0.18 0.04 55` / `0.46 0.12 52` | `0.74 0.13 60` / `0.19 0.04 55` / `0.82 0.11 60` | | `--condition-critical` | `0.62 0.17 30` / `0.15 0.04 30` / `0.47 0.16 28` | `0.68 0.16 28` / `0.16 0.04 30` / `0.78 0.11 28` | | `--chart-1` … `--chart-5` | `0.56 0.13 55`, `0.46 0.08 145`, `0.60 0.12 85`, `0.50 0.17 28`, `0.52 0.08 250` | `0.78 0.13 70`, `0.70 0.09 145`, `0.82 0.12 90`, `0.70 0.15 28`, `0.72 0.08 250` | | `--area-betrieb` / `-erfassung` / `-arbeit` / `-ernte` / `-neutral` neu | `0.46 0.08 150`, `0.50 0.12 60`, `0.48 0.08 250`, `0.52 0.11 75`, `0.45 0.02 60` | `0.74 0.09 150`, `0.80 0.12 70`, `0.76 0.07 250`, `0.80 0.10 80`, `0.76 0.02 70` | | `--queen-white/-yellow/-red/-green/-blue` neu | `0.97 0.005 90`, `0.86 0.17 95`, `0.58 0.20 27`, `0.58 0.15 145`, `0.52 0.15 255` | gleich, außer rot `0.62 0.20 27`, grün `0.66 0.15 145`, blau `0.62 0.15 255` | | `--radius`, `--shadow-soft`, `--shadow-elevated` | unverändert | unverändert | (Condition-Zeilen: Fläche / Text auf Fläche / Tinte, jeweils `oklch(L C H)`.) Entfällt: `--gradient-honey/-sage/-sky`. Sätze in dieser Reihenfolge in `globals.css`: `:root`, `.dark`, `:root[data-contrast="sun"]`, `.dark[data-contrast="sun"]`; `@custom-variant dark (&:where(.dark, .dark *));` dazu `sun`, `pointer-fine`, `pointer-coarse`. `@media print` setzt den hellen Satz; `@media (forced-colors: active)` nutzt `CanvasText`-Striche. **Sonne (überschreibt nur diese Tokens; Rest erbt vom Grundsatz)** | Token | Sonne hell | Sonne dunkel | |---|---|---| | `--background`, `--card`, `--popover`, `--sidebar` | `oklch(1 0 0)` | `oklch(0.10 0.005 55)` | | `--foreground`, `--primary-foreground`, `--ring` | `oklch(0.13 0.02 55)` | `--foreground`/`--ring` `oklch(0.99 0.005 85)`, `--primary-foreground` wie dunkel | | `--muted` | `oklch(0.95 0.01 80)` | `oklch(0.20 0.01 55)` | | `--muted-foreground` | `oklch(0.32 0.03 60)` | `oklch(0.88 0.02 80)` | | `--border` | `oklch(0.40 0.03 60)` | `oklch(0.70 0.02 70)` | | `--input` | `oklch(0.20 0.03 55)` | `oklch(0.92 0.01 80)` | | `--primary-ink` | `oklch(0.38 0.10 52)` | wie dunkel | Regeln unter `[data-contrast="sun"]`: `--text-sm: 1rem`, `--text-xs: 0.875rem`, `--focus-width: 3px`, `--control-border-width: 2px`, `--shadow-*: none`, Kopfzeile undurchsichtig (`backdrop-filter: none`), Tönungen `bg-*/15` werden über die Hilfsklasse `.tint` zu `bg-card` mit 2-px-Rahmen in der Tinte, Wabenränder 3/5/7 px statt 2/4/6 px, keine Honig-Tönung unter der Stärkekurve. **Gemessene Kontraste (Soll, `contrast.spec.ts` prüft jede Zeile mit ihrer Grenze)** | Paar | hell | dunkel | Sonne hell | Sonne dunkel | Grenze | |---|---|---|---|---|---| | Text auf Hauptknopf (`--primary-foreground` / `--primary`) | 9,38 | 9,77 | 10,61 | 9,77 | 7 | | Grundtext / Seite | 16,23 | 15,98 | 20,15 | 20,01 | 7 | | Grundtext / Karte | 17,56 | 14,45 | 20,15 | 20,01 | 7 | | Nebentext / Seite | 7,13 | 8,60 | 12,77 | 14,33 | 4,5 | | Nebentext / `--muted` | 6,27 | 6,81 | 11,03 | 12,62 | 4,5 | | Link (`--primary-ink`) / Seite | 6,48 | 10,48 | 10,37 | 11,66 | 4,5 | | Eingaberahmen (`--input`) / Karte | 3,92 | 3,58 | 18,19 | 16,25 | 3 | | Eingaberahmen / Seite | 3,62 | 3,96 | 18,19 | 16,25 | 3 | | Fokusring / Karte (mit Abstand) | 7,01 | 9,47 | 20,15 | 20,01 | 3 | | Fokusring / Seite | 6,48 | 10,48 | 20,15 | 20,01 | 3 | | Warn-Icon/-Text (`--warning-ink`) / Karte | 6,09 | 9,49 | 6,18 | 11,68 | 4,5 | | Warn-Text / Warn-Tönung | 5,53 | 6,92 | 5,60 | 9,37 | 4,5 | | Fehlertext / Karte | 6,45 | 5,87 | 6,54 | 7,23 | 4,5 | | Fehlertext / Fehler-Tönung | 5,50 | 5,06 | 5,57 | 6,58 | 4,5 | | Salbei-Knopf | 7,11 | 6,43 | 7,11 | 6,43 | 4,5 | | Ampel-Abzeichen (Tinte / Tönung), schlechteste Stufe | 6,06 | 6,45 | 6,14 | 8,49 | 4,5 | | Text auf Ampelfläche, schlechteste Stufe | 5,02 | 6,32 | 5,02 | 6,32 | 4,5 | | Ampelfläche / Karte, schlechteste | 3,03 | 5,41 | 3,07 | 6,66 | 3 | | `chart-1…5` / Karte, schlechteste | 3,93 | 5,87 | 3,99 | 7,23 | 3 | | Bereichston / Karte, schlechtester | 5,54 | 7,52 | 5,54 | 7,52 | 3 | | Königinnenfarbe mit Tinten-Ring / Karte (Ring) | 17,56 | 14,45 | 20,15 | 20,01 | 3 | Die Honigfläche hat gegen die Karte nur 1,87:1; das ist zulässig, weil ein Knopf über seinen Text erkannt wird und gewählte Zustände Rahmen plus Haken tragen. **Arbeitsregeln für Ausführende** - Agents committen nie. Der Orchestrator prüft und committet pro Task einmal; Pushes gebündelt (CI nicht zuspammen). - Standard-Verifikation **V** (Befehle in `ApiCore-Frontend/`): `npm run lint && npm run typecheck && npx vitest run <Pfade> && npm run build`. In parallelen Wellen baut nur der Orchestrator (`.next` ist nicht parallelfähig); Agents lassen `npm run build` dort aus und melden es. - Standard-Browserprüfung **VB** (nur UI-Tasks): gegen `npm run build && npm run start` mit Demo-Daten, per Chrome DevTools (`emulate`, `resize_page`, `take_screenshot`): Desktop 1366 px und 390 px; Tastatur (Tab-Reihenfolge, sichtbarer Fokus, Pfeiltasten wo zutreffend); `prefers-reduced-motion: reduce`; Modi hell, dunkel, Sonne hell, Sonne dunkel (ab Task A2 schaltbar, dunkel ab A4 ohne Flag); die betroffenen Seiten sind je Task genannt. Befunde als Liste im Bericht, Bilder nicht im Repo. - `src/styles/baukasten-guard.spec.ts` (angelegt in Task A5) wächst task-weise: jeder Task trägt die Regel ein, die er erfüllt, samt Ausnahmen mit Begründung. --- ## Review Focus 1. **Flackern und blockierter Speicher:** Der erste Frame darf nie im falschen Modus erscheinen; ohne `localStorage` (Privatmodus) muss die App hell starten statt zu brechen. - Task A2 `the inline script sets class, data-contrast and data-motion before first paint` - Task A2 `blocked storage falls back to the default` 2. **Unlesbarer Dunkelmodus und Ampel nur über Farbe:** Abzeichen, Warnungen und Zellen müssen in allen vier Sätzen ≥ 4,5:1 (Text) bzw. ≥ 3:1 (Fläche) haben und Icon plus Wort tragen. - Task A4 `contrast table 4.4 holds in all four sets` - Task A4 `every condition tone has surface, text-on-surface and ink in all four sets` - Task B1 `a cell always shows icon and code, never colour alone` 3. **Fehlende Rechte, Offline und alte Backends:** Ohne `inspections:read`, mit 403 je Spur, ohne T4-Felder oder ohne `seasonCalendar` darf nichts brechen und nichts Falsches behaupten. - Task B1 `without inspections:read the rim is neutral and says nothing about inspections` - Task B3 `a 403 on a track drops the track without an error` - Task B6 `without seasonCalendar the wheel uses the season defaults` 4. **Extreme Daten:** 0, 1 und über 100 Völker; zwanzig und mehr Stände; sehr lange Codes; Lücke von über 45 Tagen; Messwerte vor dem Einheitenwechsel; Schaltjahr und Jahreswechsel im Rad. - Task B1 `an empty apiary shows the empty hint and no comb` - Task B1 `100 cells render with one shared symbol` - Task B3 `values before STRENGTH_IN_FRAMES_SINCE are not in the curve but in the table` - Task B6 `winter rest over the year change is two arcs` 5. **Migration reißt Seiten ein:** Entfernte Props (`gradient`), gelöschte Bausteine (`ToggleChip`, `TabSwitcher`) und verlorene Fokus-/Größenklassen dürfen keine Seite unlesbar oder unbedienbar zurücklassen. - Task A3 `no file passes a gradient prop any more` - Task A5 `no component removes the outline without the global rule` - Task A10 `baukasten guard passes with the documented exceptions only` - Task A9 `every manifest icon exists with the declared size` 6. **Tastatur und Forced Colors in eigenen Bedienelementen** (Wabenzelle, Tabs, Auswahlgruppe, Marker): Fokus sichtbar, Pfeiltasten, Namen mit sichtbarem Text am Anfang. - Task B1 `focus draws the ring on the outer hexagon path` - Task A8 `arrow keys switch the tab and the panel is labelled by it` --- ## Abhängigkeiten und Parallelisierung **T5a** (nur T3; Tasks A1–A5 laufen in einem kurzen Fenster ohne andere offene Frontend-Tasks, weil sie viele Dateien mechanisch berühren): | Welle | Tasks (parallel, verschiedene Dateien) | wartet auf | |---|---|---| | 1 | A1 | T3 | | 2 | A2 | A1 | | 3 | A3 | A1, A2 (gleiche Layout-Dateien) | | 4 | A4 | A3 (Verlauf-Verbraucher müssen vorher weg sein) | | 5 | A5, A9 | A5 → A4; A9 → A3 (`BrandMark`-Geometrie) | | 6 | A6 | A5 | | 7 | A7 | A6 (`winter-check-dialog`, `label-designer`, `swarm-control-edit-dialog` gemeinsam) | | 8 | A8 | A7 (`tasks/page.tsx`, `settings/automation/*` gemeinsam) | | 9 | A11, A12 | A11 → A8 (`today-panel.tsx`, `ui/*`, `globals.css`); A12 → A1, A2 | | 10 | A10 | A8, A9, A11, A12 | **T5b** (nach T4a-Frontend; ohne T4 mit Kalender-Rückfall lauffähig, aber dieselben Dateien wie T4 Tasks 17/18/20, darum danach): | Welle | Tasks (parallel, verschiedene Dateien) | wartet auf | |---|---|---| | 1 | B1, B3, B5 | T5a (A10), T4a | | 2 | B2, B4, B6 | B2 → B1 (`summarizeColonies`); B4 → B3; B6 → B5 (Vertrag) | | 3 | B7 | B6 | | 4 | B8 | B2, B4, B7 | Geteilte Dateien, deshalb je Welle nur ein Task: - `src/app/globals.css`: A1 (W1), A2 nur falls nötig nicht, A4 (W4), A5 (W5); T5b ändert sie nicht (Waben-CSS liegt in `src/styles/comb.css`, Leaflet-CSS in `src/styles/leaflet-theme.css`, jeweils aus der Komponente importiert). - `src/app/layout.tsx`, `src/app/providers.tsx`, `src/app/(app)/layout.tsx`, `src/app/(app)/nav-config.ts`, `src/app/(app)/quick-actions.ts`, `src/app/(app)/sidebar.tsx`, `src/app/(app)/bottom-nav.tsx`: A2 (W2), A3 (W3), A9 nur `layout.tsx` Metadaten nicht (liegt in `manifest.ts`), A10 nur Dashboard. - `src/app/(app)/dashboard/page.tsx`: A3 (W3, Banner raus), A10 (W9, Hinweis-Karte), B7 (W3 T5b). T4 Task 18 (Welle 6 von T4) ändert dieselbe Datei: wer später dran ist, rebased; kein Parallellauf. - `src/components/ui/*`: A4 (W4, `dark:` raus), A5 (W5), A7 (checkbox), A8 (tabs). - `src/components/form/choice-group.tsx`, `number-stepper.tsx`, `rating-scale.tsx`: nur A6 (W6) bzw. A4 (Farbklassen, W4). - `src/app/(app)/winter/winter-check-dialog.tsx`, `settings/label-templates/designer/label-designer.tsx`, `colonies/[id]/swarm-control-edit-dialog.tsx`: A6 (W6), A7 (W7). - `src/app/(app)/tasks/page.tsx`, `settings/automation/*`: A7 (W7), A8 (W8). - `src/styles/baukasten-guard.spec.ts`: A5, A6, A7, A8 (je eigene Welle), A10. - `package.json`/`package-lock.json`: A2 (W2, `next-themes` raus), A9 (W5, `sharp` rein). - `public/sw.js`, `src/proxy.ts`: nur A9. - `src/lib/honeycomb/*`: B1 (`cell-state`, `layout`), B2 (`mini-comb-svg`, je eigene Datei). - `src/app/(app)/apiaries/page.tsx`, `apiaries/[id]/*`: B1 (W1); `apiaries/map/page.tsx`, `src/components/apiary-map.tsx`: B2. - `src/app/(app)/colonies/[id]/page.tsx`: B4 (W2). `src/lib/autopilot/types.ts`: B6 (W2). - `ARCHITECTURE.md`, `TASKS.md`: nur der Orchestrator (Texte liefert B8). --- ## Tasks ## Lieferung T5a – Sonnentauglich und Baukasten ### Task A1: Tokens additiv und Kontrast-Wächter (Spec 10.8 Schritt 1) **Files** - Create: `src/styles/css-tokens.ts` (+ `css-tokens.spec.ts`), `src/styles/contrast.spec.ts` - Modify: `src/app/globals.css` **Interfaces** - Produces (nur für Tests): ```ts export type TokenSet = "light" | "dark" | "sun-light" | "sun-dark"; export function tokenSet(css: string, set: TokenSet): Record<string, string>; // Sonne-Sätze liegen auf ihrem Grundsatz export function oklchOf(value: string): [number, number, number]; export function contrastOf(a: [number, number, number], b: [number, number, number]): number; export function blend(fg: [number, number, number], alpha: number, bg: [number, number, number]): [number, number, number]; export function inGamut(color: [number, number, number]): boolean; ``` - `contrast.spec.ts` exportiert `VALUES_SWITCHED` (Konstante, hier `false`); Task A4 setzt sie auf `true`. Neue Tokens (`-ink`, `area-*`, `queen-*`, `--destructive-foreground`, `--primary-ink`, `--warning-ink`, `--condition-*-ink`) und die beiden Sonne-Sätze tragen hier schon ihre Endwerte; bestehende Werte bleiben unverändert, damit nichts bricht. - [ ] **Schritt 1: Failing Tests** ```ts // css-tokens.spec.ts it('reads oklch values with and without alpha', ...); it('contrast of black on white is 21', ...); it('blends a 15 % tint over its background before measuring', ...); it('sun-light layers over light and sun-dark over dark', ...); // contrast.spec.ts it('every token in @theme inline exists in light and dark', ...); it('all tokens are inside the sRGB gamut', ...); // alle vier Sätze, auch Bestand it.each(AREA_TONES)('area tone %s reaches 3:1 on the card in every set', ...); it('queen marking colours with the foreground ring reach 3:1 on the card', ...); it('the focus ring token equals --primary-ink in light and dark', ...); it('sun sets define every token listed in the sun table', ...); describe.skipIf(!VALUES_SWITCHED)('table 4.4', () => { it.each(ROWS)('$id reaches its limit and the measured ratio in $set', ...); // ROWS = Zeilen aus „Gemessene Kontraste“, |gemessen − Soll| ≤ 0,05 }); ``` - [ ] **Schritt 2:** `npx vitest run src/styles` → FAIL (Module und Tokens fehlen). - [ ] **Schritt 3: Implementieren.** `css-tokens.ts` übernimmt die OKLCH-Rechnung aus `src/lib/forms/rating-contrast.spec.ts` (Ottosson), ergänzt um Alpha-Mischung und Gamut-Prüfung; `globals.css`: neue Tokens in `:root` und `.dark` mit den Tabellenwerten, `@theme inline` mit `--color-primary-ink`, `-warning-ink`, `-destructive-foreground`, `-condition-*-ink`, `-area-*`, `-queen-*`, die zwei Sonne-Sätze samt Regeln aus der Sonne-Tabelle, `@custom-variant`s (`dark` mit `:where`, `sun`, `pointer-fine`, `pointer-coarse`), `.tint`, `@media print`, `@media (forced-colors: active)`. `--gradient-*` und alle alten Werte bleiben bis Task A4. `rating-contrast.spec.ts` bleibt bis A4 bestehen. - [ ] **Schritt 4:** `npx vitest run src/styles src/lib/forms` → PASS. - [ ] **Schritt 5: Verifikation** – V mit `src/styles src/lib/forms src/components`; VB nur ein Blick auf Start in hell (muss unverändert aussehen). Kein Commit. --- ### Task A2: Darstellung umschalten, ohne zu flackern (Spec 5, Schritt 2) **Files** - Create: `src/lib/appearance/appearance.ts` (+ Spec), `src/components/appearance-script.tsx` (+ Spec), `src/app/(app)/profile/appearance-section.tsx` (+ Spec) - Modify: `src/app/layout.tsx`, `src/app/providers.tsx` (+ `providers.spec.tsx`), `src/components/ui/sonner.tsx`, `src/lib/motion/motion-preference.ts`, `src/app/(app)/profile/page.tsx` (+ Spec), `src/app/(app)/layout.tsx` (+ `layout.spec.tsx`), `package.json`, `package-lock.json` - Delete: `src/app/(app)/profile/motion-section.tsx` **Interfaces** - Produces: ```ts export const APPEARANCE_KEYS: { theme: "apicore.theme"; contrast: "apicore.contrast"; motion: "apicore.motion" }; export type ThemeChoice = "light" | "dark" | "system"; export type ContrastChoice = "normal" | "sun"; export const DEFAULT_THEME: ThemeChoice; // "light" (Offene Frage 3, A) export function resolveAppearance(stored: { theme?: string | null; contrast?: string | null; motion?: string | null }, env: { prefersDark: boolean; prefersMore: boolean; darkEnabled: boolean }): { dark: boolean; sun: boolean; motion: MotionLevel }; export function useAppearance(): { theme: ThemeChoice; contrast: ContrastChoice; motion: MotionLevel; resolvedTheme: "light" | "dark"; sunActive: boolean; setTheme(v: ThemeChoice): void; setContrast(v: ContrastChoice): void; setMotion(v: MotionLevel): void }; export function appearanceScript(darkEnabled: boolean): string; // IIFE-Quelltext aus APPEARANCE_KEYS und resolveAppearance export function AppearanceScript(): JSX.Element; // <script dangerouslySetInnerHTML> im <head> ``` - `motion-preference.ts` behält API (`readMotionLevel`, `applyMotionLevel`, `useMotionPreference`, `effectiveMotionLevel`); der `useEffect` in `providers.tsx` entfällt. - Entwicklungs-Flag `NEXT_PUBLIC_APPEARANCE_DARK=1`: ohne Flag bleiben „Dunkel“ und „Wie Gerät“ ausgeblendet und ein gespeichertes `dark` wird nicht angewendet (Dark ist bis Task A4 mit alten Werten unlesbar). Task A4 entfernt das Flag. - [ ] **Schritt 1: Failing Tests** ```ts // appearance.spec.ts it('defaults to light', ...); it('system follows matchMedia and reacts to a change', ...); it('prefers-contrast: more without a stored value selects sun', ...); it('blocked storage falls back to the default', ...); it('a storage event from another tab is picked up', ...); it('dark is ignored without the dev flag', ...); // appearance-script.spec.ts it('uses the same storage keys as the hook', ...); it('the inline script sets class, data-contrast and data-motion before first paint', ...); // Skript in jsdom auswerten it('sets the theme-color meta to the page colour of the active set', ...); // #f9f4ea / #1a120d / #ffffff / #040302 // appearance-section.spec.tsx it('choosing Sonne sets data-contrast on the root and stores it', ...); it('says that the setting only applies to this device', ...); it('hides Dunkel without the dev flag', ...); // layout.spec.tsx it('the user menu offers Sonnenmodus as menuitemcheckbox and toasts Sonnenmodus an', ...); // providers.spec.tsx it('the toaster follows resolvedTheme and next-themes is not imported', ...); ``` - [ ] **Schritt 2:** `npx vitest run src/lib/appearance src/components/appearance-script.spec.tsx "src/app/(app)/profile" "src/app/(app)/layout.spec.tsx" src/app/providers.spec.tsx` → FAIL. - [ ] **Schritt 3: Implementieren.** Muster aus `preventing-flash-before-hydration.md`; `<html suppressHydrationWarning>` und `<AppearanceScript />` im `<head>`; `viewport.themeColor` mit den zwei `media`-Einträgen (`#f9f4ea` / `#1a120d`). `useAppearance` schreibt `localStorage` (in `try/catch`) und das Attribut, hört auf `matchMedia` (nur solange „Wie Gerät“ bzw. nichts gewählt) und auf `storage`. Profil-Karte „Darstellung“ mit drei `ChoiceGroup`-Feldern (Texte aus Spec 5.3) und „Gilt nur für dieses Gerät.“; Benutzermenü bekommt oben `DropdownMenuCheckboxItem` „Sonnenmodus“ (44 px, Sonnen-Icon) mit Toast „Sonnenmodus an“/„aus“. Abmelden löscht die Einstellungen nicht. `next-themes` aus `package.json` entfernen, `sonner.tsx` liest `useAppearance().resolvedTheme`. Login und `/herkunft/[lot]` folgen automatisch (Skript im Root-Layout). - [ ] **Schritt 4:** Befehl aus Schritt 2 → PASS. - [ ] **Schritt 5: Verifikation** – V mit denselben Pfaden; VB: Profil > Darstellung, Start, Login; Sonne an/aus mit einem Tipp im Benutzermenü, Seite neu laden (kein Aufblitzen des falschen Modus, auch mit gedrosselter CPU), `prefers-contrast: more` per `emulate` schaltet Sonne vor. Kein Commit. --- ### Task A3: `AREAS`, `AreaIcon`, `BrandMark`, Verläufe aus den Komponenten (Spec 11, 12.1; vor dem Werte-Wechsel) Reihenfolge-Abweichung zur Spec 10.8: Dieser Task läuft **vor** dem Werte-Wechsel (Task A4), weil A4 die `--gradient-*`-Tokens entfernt und deren 22 Verbraucher dann bereits weg sein müssen. **Files** - Create: `src/lib/areas.ts` (+ `areas.spec.ts`), `src/components/area-icon.tsx` (+ Spec), `src/components/brand-mark.tsx` (+ Spec) - Modify: `src/app/(app)/nav-config.ts` (+ Spec), `src/app/(app)/quick-actions.ts`, `src/app/(app)/sidebar.tsx`, `src/app/(app)/layout.tsx`, `src/components/page-header.tsx`, `src/components/detail-header.tsx` (+ Spec), `src/components/entity-card.tsx`, `src/app/(app)/dashboard/page.tsx`, die Login-Seite unter `src/app/(auth)/`, `src/app/herkunft/[lot]/page.tsx`, alle Seiten mit `gradient=`/`var(--gradient-` (`grep -rlE "gradient=|--gradient-" src`, 31 Dateien), die Icon-Abweichungen (Ernte-Detail `Scissors`, Schleuderung neu `FlaskConical`, Lager nur `Warehouse`, Inventar/Etikettenvorlagen/Königinnen/Kalender/Audit-Log/Profil aus `AREAS`), falls gemergt `INTENT_ICONS` (T4 Task 17) - Modify (Guard): `src/styles/baukasten-guard.spec.ts` entsteht erst in Task A5; die Regel „kein `gradient`“ trägt Task A10 nach. **Interfaces** - Produces: ```ts export type AreaKey = "dashboard" | "search" | "apiaries" | "colonies" | "queens" | "winter" | "phenology" | "inspections" | "varroa" | "swarm-control" | "treatments" | "feedings" | "tasks" | "calendar" | "suggestions" | "notifications" | "harvests" | "extractions" | "honey-batches" | "bottling" | "products" | "raw-materials" | "production-batches" | "storage" | "equipment" | "statistics" | "users-roles" | "automation" | "label-templates" | "backup" | "audit-log" | "profile"; export type AreaTone = "betrieb" | "erfassung" | "arbeit" | "ernte" | "neutral"; export interface Area { key: AreaKey; label: string; icon: LucideIcon; tone: AreaTone; href?: string } export const AREAS: Readonly<Record<AreaKey, Area>>; // Tabelle aus Spec 11, Icons exakt wie dort export function AreaIcon(props: { area: AreaKey; size?: "md" | "sm" }): JSX.Element; // Sechseck 40/32 px, Ton 14 % getönt, 1 px Rand im Ton; Sonne: --card, 2 px Rand export function BrandMark(props: { size?: 24 | 32; withWordmark?: boolean }): JSX.Element; // PageHeader({ area, title, description?, actions? }), DetailHeader/EntityCard: Prop `area?: AreaKey` statt `icon` + `gradient` ``` - Der Ton wird nur in `AreaIcon`, dem „+ Neu“-Sheet, den Ereignis-Icons der Lebenslinie (B4) und den Absichts-Icons der Vorschläge gezeigt; Navigationslisten bleiben neutral (Icon `--muted-foreground`, aktiv Honig). - [ ] **Schritt 1: Failing Tests** ```ts it('every NAV_GROUPS entry and QUICK_ACTION maps to an AREAS key with the same icon', ...); it('no area tone is a status colour', ...); it('AreaIcon draws the icon inside a hexagon tinted with the area tone and is aria-hidden', ...); it('PageHeader renders the AreaIcon of its area and has no gradient prop', ...); // plus @ts-expect-error auf gradient it('BrandMark renders the capped cell and the ApiCore wordmark', ...); it('harvest detail uses Scissors, extraction create FlaskConical and storage only Warehouse', ...); it('the dashboard header has no honey banner and no white text', ...); it('no file passes a gradient prop any more', ...); // liest src/**/*.tsx ``` - [ ] **Schritt 2:** `npx vitest run src/lib/areas.spec.ts src/components "src/app/(app)/nav-config.spec.ts"` → FAIL. - [ ] **Schritt 3: Implementieren.** `BrandMark` zeichnet die verdeckelte Wabenzelle (Sechseck mit spitzer Oberseite in `--primary` mit 6 % Rand in `--foreground`, darin kleineres Sechseck `oklch(0.88 0.11 85)` leicht nach oben); dieselbe Geometrie nutzt Task A9 für das Master-SVG (die Pfade stehen in `src/components/brand-mark.tsx` als exportierte Konstante `BRAND_CELL_PATHS`). Alle `gradient`-Props und `style={{ background: "var(--gradient-…)" }}` verschwinden; die Dashboard-Kopfzeile wird `h1` in Tinte auf dem Seitenhintergrund (Rad und Kacheln-Ersatz folgen erst in B7). Die Zählkacheln bleiben bis B7 bestehen, bekommen aber nur noch `AreaIcon`. - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – V mit `src`; VB: Start, Bienenstände, Völker, Königinnen, Kalender, Inventar, Login, Herkunft, Seitenleiste und Bottom-Nav (Icons neutral, aktiver Eintrag lesbar). Kein Commit. --- ### Task A4: Farb-Codemod und Tokenwerte (Spec 10.8 Schritte 3 und 4, Spec 4.2) **Files** - Create: `scripts/codemod-t5-colors.mjs` (+ `scripts/codemod-t5-colors.spec.mjs`) - Modify: `src/app/globals.css` (Werte aus der Token-Tabelle, `--gradient-*` entfernen), `src/styles/contrast.spec.ts` (`VALUES_SWITCHED = true`), `src/components/ui/*` (alle `dark:`-Klassen raus; `button.tsx` Standard ohne Verlauf), `src/lib/colonies/labels.ts` (`COLONY_CONDITION_TONE_CLASS`), `src/components/colonies/status-light-badge.tsx`, `src/components/form/rating-scale.tsx`, `src/lib/appearance/appearance.ts` (Flag entfällt), `appearance-section.tsx`, alle Treffer des Codemods (`grep -rlE "text-primary\b|text-warning\b|border-primary\b" src`, 22 + 6 Dateien) und die acht Dateien mit `dark:` - Delete: `src/lib/forms/rating-contrast.spec.ts` (aufgegangen in `contrast.spec.ts`) **Interfaces** - Produces: `export function transform(source: string): string` (rein, idempotent) in `scripts/codemod-t5-colors.mjs`; CLI `node scripts/codemod-t5-colors.mjs [--check] <glob…>`. - Regeln: `text-primary` → `text-primary-ink`; `text-warning` → `text-warning-ink`; `text-condition-x-foreground` in Abzeichen-Klassen → `text-condition-x-ink`; `border-primary` in Zeilen mit Auswahlzustand (`aria-pressed`, `aria-checked`, `data-checked`, `selected`) → `border-foreground`. `bg-primary`, `text-primary-foreground`, `bg-primary/…` bleiben. - Gleiche Reihenfolge einhalten: erst Codemod (Schritt 3), Ergebnis per Diff prüfen, V grün; dann Werte umstellen. - [ ] **Schritt 1: Failing Tests** ```js // codemod-t5-colors.spec.mjs it('text-primary becomes text-primary-ink', ...); it('text-primary-foreground, bg-primary and text-primary/80 stay unchanged', ...); it('text-warning becomes text-warning-ink but text-warning-foreground stays', ...); it('badge classes with text-condition-good-foreground become -ink', ...); it('border-primary on a selection state becomes border-foreground', ...); it('is idempotent', ...); // contrast.spec.ts (jetzt hart) it('table 4.4 holds in all four sets', ...); // VALUES_SWITCHED = true it('every condition tone has surface, text-on-surface and ink in all four sets', ...); it('no --gradient-* token remains', ...); // weitere it('the default button uses solid primary with primary-foreground and no gradient', ...); it('dark is selectable without a flag', ...); it('ui components contain no dark: classes', ...); ``` - [ ] **Schritt 2:** `npx vitest run scripts src/styles src/components/ui src/lib/appearance` → FAIL. - [ ] **Schritt 3: Implementieren.** (a) Codemod schreiben, ausführen, `git diff` auf Fehltreffer prüfen. (b) Werte aus der Token-Tabelle einsetzen (hell, dunkel, `--sidebar*`, `--chart-*`), `--primary-foreground` Standard-Knopf ohne Verlauf, `ui/*` ohne `dark:` (der Modus ist nur ein anderer Tokensatz). Einzige `dark:`-Ausnahme: Leaflet-Kachelfilter, nur in CSS (B2). (c) `NEXT_PUBLIC_APPEARANCE_DARK` aus Code und `.env`-Beispielen entfernen. - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – V mit `src`; VB in **allen vier Modi** auf Start, Bienenstände, Stand, Volk, Durchsicht neu, Varroa neu, Profil, Login, Herkunft: Ampel-Abzeichen, Warnungen, Links, Hauptknopf (dunkle Tinte auf Honig), Eingaberahmen sichtbar. Kein Commit. --- ### Task A5: Fokus, Größen, Schrift (Spec 4.5, 4.6, Schritt 5) **Files** - Create: `scripts/codemod-t5-focus.mjs` (+ Spec), `src/styles/baukasten-guard.spec.ts` - Modify: `src/app/globals.css` (`--text-xs: 0.8125rem`, Basisregel `:where(:focus-visible)`, `--focus-width/-offset`, Trefferfläche `pointer-coarse`), `src/components/ui/button.tsx`, `input.tsx`, `select.tsx`, `textarea.tsx`, `tabs.tsx`, `checkbox.tsx` (Fokus), `src/components/form/field.tsx`, `number-stepper.tsx`, `choice-group.tsx`, `src/components/empty-state.tsx`, `src/components/detail-header.tsx`, alle Treffer von `ring-ring/50`, `outline-none` (≈ 20 Dateien), `sm:h-` (4 Dateien), `md:text-sm` in Input/Textarea **Interfaces** - Produces: `transform(source)` im Fokus-Codemod entfernt `outline-none`, `focus-visible:ring-3`, `focus-visible:ring-ring/50`, `focus-visible:border-ring`, `has-[:focus-visible]:outline-*` und wandelt `sm:h-*`/`sm:min-h-*` in `pointer-fine:h-*` um; bei einem inneren Fokuselement (Label-Zeile mit verstecktem Input) bleibt `has-[:focus-visible]:outline`-Ersatz am äußeren Element. - Guard-Regelformat (alle folgenden Tasks tragen hier ihre Regel ein): `{ id: string; pattern: RegExp; scope: "src/**/*.tsx"; allow: { file: string; reason: string }[] }`. Regeln dieses Tasks: kein `ring-ring/50`, kein `outline-none` (Ausnahme `honeycomb-cell.tsx`, Grund in B1 eintragen), kein `sm:h-`, kein `md:text-sm` in `ui/input.tsx`/`ui/textarea.tsx`. - Größen: `Button`/`Input` `size="default"` 44 px (`min-h-11`), kompakt nur mit `pointer-fine:`; `icon`/`icon-sm` bekommen bei `pointer-coarse` eine 44-px-Trefferfläche über `after:`; `Field`-Label, `ChoiceGroup`-Option, `CheckboxField`-Label, Button `touch` (statt `text-[0.95rem]`), `Input`/`Textarea`, Listentitel, `DetailHeader`-Untertitel und `EmptyState`-Beschreibung auf 16 px (`text-base`). - [ ] **Schritt 1: Failing Tests** ```ts it('Button default is 44 px high and compact only with pointer-fine', ...); it('icon buttons expose a 44 px hit area under pointer-coarse', ...); it('Input and Textarea use text-base without md:text-sm', ...); it('NumberStepper buttons keep min-h-11 and drop their own ring classes', ...); it('the base layer draws the focus ring with --focus-width and --focus-offset', ...); // liest globals.css it('--text-xs is 0.8125rem', ...); it('no component removes the outline without the global rule', ...); // Guard-Regeln dieses Tasks it('the focus codemod is idempotent and keeps pointer-fine classes', ...); // scripts/codemod-t5-focus.spec.mjs ``` - [ ] **Schritt 2:** `npx vitest run scripts src/styles src/components` → FAIL. - [ ] **Schritt 3: Implementieren.** Codemod ausführen, Diff prüfen; Fehlerzustand (`aria-invalid`): Rahmen 2 px `--destructive`, Ring bleibt `--ring`. Bestehendes `scroll-padding` unverändert lassen. - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – V mit `src`; VB: Tab durch Durchsicht neu, Profil, Bienenstände-Filter; Fokus sichtbar auf Karte und Seite in allen vier Modi, Tablet 820 px mit Touch-Emulation hat 44-px-Knöpfe (`pointer-coarse`), Desktop-Maus bleibt kompakt. Kein Commit. --- ### Task A6: Auswahlgruppe und Plus/Minus (Spec 10.2, 10.5) **Files** - Modify: `src/components/form/choice-group.tsx` (+ Spec), `src/components/form/rating-scale.tsx` (+ Spec), `src/components/ui/button.tsx` (Prop `pressed`), `src/app/(app)/winter/winter-check-dialog.tsx` (+ Spec), `src/app/(app)/settings/label-templates/designer/label-designer.tsx` (+ Spec), `src/app/(app)/profile/appearance-section.tsx`, `src/app/(app)/colonies/[id]/varroa-edit-dialog.tsx`, `src/app/(app)/swarm-control/new/page.tsx`, `src/app/(app)/colonies/[id]/swarm-control-edit-dialog.tsx`, `src/components/stock/stock-card.tsx` (+ Spec), die Etikettendruck-Startposition (`grep -rn "startPosition\|Startposition" src`), `src/styles/baukasten-guard.spec.ts` - Unverändert: Kalender-Tageswahl (`calendar/page.tsx`, bekommt nur Fokus und Auswahl-Optik, Guard-Ausnahme) **Interfaces** - Produces: ```ts export interface ChoiceOption<T extends ChoiceValue> { /* wie bisher */ icon?: LucideIcon } // ChoiceGroupProps += appearance?: "buttons" | "cards"; // "cards": Label 16 px + Beschreibung 14 px, volle Breite, min-h-14 // ChoiceOption += description?: string (nur "cards") // Button += pressed?: boolean -> aria-pressed (Toolbar-Umschalter, kein Radio) ``` - Optik: nicht gewählt `--card` + 1 px `--input`; gewählt `--primary` + `--primary-foreground` halbfett + 2 px `--foreground` + Haken-Icon vor dem Label; „Nicht geprüft“ (`null`) `--muted` + 2 px gestrichelt `--foreground` + Strich-Icon; Skalen behalten `selectedClassName`, bekommen Rahmen und Haken vor der Zahl („✓ 4“). Tastatur unverändert (ein Tab-Halt, Pfeile, Home, End). - Umstellung `NumberStepper` (beim Bau je Feld die Regel 10.5 bestätigen: ganzzahlig, meist < 100, am Stand erfasst; Abweichungen im Bericht): besetzte Wabengassen (`winter-seams`), Milbenzahl im Varroa-Bearbeiten-Dialog, Weiselzellen in Schwarmkontrolle neu und bearbeiten, Entnahme und gezählter Bestand in `stock-card`, Startposition im Etikettendruck. Bleibt Eingabefeld: Wartezeit, Schlupfjahr, Mengen in Gramm, MFA-Codes, Spalten/Zeilen, Regel- und Vorlagenwerte. - Umstellung `ChoiceGroup`: Ausgang des Winters (mit `icon`, `appearance="cards"`), Linien-Ausrichtung im Etiketten-Designer, Bewegungsstufe, Farbschema und Kontrast in `appearance-section.tsx` (`appearance="cards"`). - [ ] **Schritt 1: Failing Tests** ```tsx it('the chosen option has aria-checked true, the tick icon and the ink border', ...); it('„Nicht geprüft“ chosen shows the dash icon and a dashed border', ...); it('appearance cards shows the description and is at least 56 px high', ...); it('an option icon renders before the label', ...); it('arrow keys, Home and End move the selection as before', ...); it('RatingScale shows the tick before the chosen number and keeps its tone', ...); it('Button pressed sets aria-pressed', ...); it('winter seams use NumberStepper', ...); it('the varroa edit dialog counts mites with NumberStepper', ...); it('swarm control new and edit count queen cells with NumberStepper', ...); it('stock card withdraw and counted stock use NumberStepper', ...); it('label print start position uses NumberStepper', ...); it('the label designer line alignment is a ChoiceGroup', ...); // Guard it('no role radio, radiogroup or type radio outside choice-group.tsx', ...); ``` - [ ] **Schritt 2:** `npx vitest run src/components/form src/components/stock src/styles "src/app/(app)/winter" "src/app/(app)/profile" "src/app/(app)/settings/label-templates" "src/app/(app)/colonies" "src/app/(app)/swarm-control"` → FAIL. - [ ] **Schritt 3: Implementieren** wie oben. - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – V mit denselben Pfaden; VB: Winterkontrolle, Profil > Darstellung, Varroa-Bearbeiten, Schwarmkontrolle neu, Etikettendruck; Brutwaben mit dem Stepper von 0 auf 6 (Handschuh-Größe 44 px) in allen vier Modi. Kein Commit. --- ### Task A7: Checkbox-Baustein und alle 17 Stellen (Spec 10.3) **Files** - Create: `src/components/form/checkbox-field.tsx` (+ Spec) - Modify: `src/components/ui/checkbox.tsx` (+ Spec), `src/components/form/colony-multi-select.tsx`, `src/components/harvest-select-field.tsx`, `src/components/today/today-panel.tsx` (+ `honey-check` in `globals.css` ist Tag-gebunden: Animation bleibt), `src/app/(app)/extractions/new/page.tsx`, `harvests/new/page.tsx`, `harvests/[id]/harvest-edit-dialog.tsx`, `swarm-control/new/page.tsx`, `colonies/[id]/swarm-control-edit-dialog.tsx`, `tasks/page.tsx`, `winter/winter-check-dialog.tsx`, `settings/automation/calendar-sync-tab.tsx`, `create-webhook-dialog.tsx`, `edit-webhook-dialog.tsx`, `edit-task-template-dialog.tsx`, `settings/label-templates/designer/label-designer.tsx`, `settings/users-roles/permission-matrix.tsx`, `src/styles/baukasten-guard.spec.ts` - Delete: `src/components/form/toggle-chip.tsx` (nach der Umstellung seiner vier Nutzer) **Interfaces** - Produces: ```ts // ui/checkbox.tsx (Base UI): 20 px, 2 px --input, Radius 6; gewählt --primary + 2 px --foreground + Haken 14 px (Strichstärke 3, --primary-foreground); indeterminate = Strich; disabled 50 % mit Hinweistext am Feld export interface CheckboxFieldProps { label: string; description?: string; checked: boolean | "indeterminate"; onCheckedChange(next: boolean): void; disabled?: boolean; disabledHint?: string; appearance?: "row" | "tile" | "task"; size?: "default" | "sheet" } export function CheckboxField(props: CheckboxFieldProps): JSX.Element; // row: ganze Zeile klickbar, min-h-11 (sheet: min-h-14), 16 px; tile: umrandete Kachel (gewählt: Honig-Tönung + 2 px Tinte; Sonne: --card + Tinten-Rand); task: Abhak-Zelle der Heute-Liste mit der honey-check-Bewegung auf dem Base-UI-Primitiv ``` - Reihenfolge: Bausteine, dann Formulare, dann Einstellungen, zuletzt `today-panel` (`task`). Native `type="checkbox"` (7 Dateien) → `CheckboxField appearance="row"`; `ToggleChip` (4 Nutzer) → `appearance="tile"`; Base-UI-Checkbox-Nutzer (3) → neue Optik. - [ ] **Schritt 1: Failing Tests** ```tsx it('the whole row toggles and the name comes from the label', ...); it('the row is at least 44 px high (min-h-11) and in a sheet 56 px (min-h-14)', ...); it('tile and task have role checkbox from the primitive', ...); it('checked shows the tick icon and a 2 px foreground border', ...); it('indeterminate shows the dash', ...); it('disabled shows its hint text', ...); it('today panel ticking a task keeps the honey-check moment only with full motion', ...); it('the harvest dialog, webhook dialogs and task template dialog use CheckboxField', ...); // Guard it('no role checkbox or type checkbox outside ui/checkbox.tsx', ...); it('ToggleChip is gone', ...); ``` - [ ] **Schritt 2:** `npx vitest run src/components src/styles "src/app/(app)"` → FAIL. - [ ] **Schritt 3: Implementieren** wie oben; `ui/checkbox` ist der einzige Ort mit `role="checkbox"`. - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – V mit `src`; VB: Heute (abhaken, Tastatur Leertaste, Bewegung nur bei „Voll“), Ernte neu, Webhook anlegen, Rollenmatrix, Etiketten-Designer in allen vier Modi. Kein Commit. --- ### Task A8: Tabs-Baustein (Spec 10.4) **Files** - Modify: `src/components/ui/tabs.tsx` (+ neue Spec), `src/components/active-history-tabs.tsx` (+ Spec), `src/app/(app)/apiaries/page.tsx`, `colonies/page.tsx`, `honey-batches/page.tsx`, `products/page.tsx`, `queens/page.tsx`, `storage/page.tsx`, `tasks/page.tsx`, `statistics/page.tsx`, `settings/automation/page.tsx`, `settings/automation/task-templates-tab.tsx`, `settings/users-roles/page.tsx`, `settings/users-roles/roles-tab.tsx`, `src/styles/baukasten-guard.spec.ts` - Delete: `src/components/tab-switcher.tsx` **Interfaces** - Produces: `Tabs`/`TabsList`/`TabsTrigger`/`TabsContent` auf Base UI, Variante `segment` (Standard; `line` entfällt): Leiste `--muted` mit 4 px Innenabstand, Reiter 44 px (`pointer-fine:h-9`), 16 px, aktiv `--card` + halbfett + 3 px Strich `--primary-ink`. ```ts export interface ActiveHistoryTabsProps { value: EntityLifecycleTab; onValueChange(v: EntityLifecycleTab): void; activeCount?: number; historyCount?: number; children: { active: ReactNode; history: ReactNode } } export function ActiveHistoryTabs(props: ActiveHistoryTabsProps): JSX.Element; // Vorlage auf Tabs, je Panel die Liste ``` - Guard-Regeln: kein `role="tablist"`/`role="tab"` außerhalb `ui/tabs.tsx`; `aria-pressed` nur in `ui/button.tsx` und `calendar/page.tsx`. - [ ] **Schritt 1: Failing Tests** ```tsx it('arrow keys switch the tab and the panel is labelled by it', ...); it('the active tab has the surface, bold text and the 3 px ink bar', ...); it('ActiveHistoryTabs shows the counts and renders the matching panel', ...); it('the statistics page uses the segment variant', ...); it('both settings pages use Tabs directly', ...); it('TabSwitcher is gone', ...); // Guard it('no tablist or tab role outside ui/tabs.tsx', ...); it('aria-pressed only in ui/button.tsx and the calendar page', ...); ``` - [ ] **Schritt 2:** `npx vitest run src/components src/styles "src/app/(app)"` → FAIL. - [ ] **Schritt 3: Implementieren;** die neun Listenseiten legen ihre Liste in das Panel, die Query-Logik bleibt. - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – V mit `src`; VB: Bienenstände Aktiv/Historie, Aufgaben, Statistik, Automatisierung, Benutzer & Rollen: Pfeiltasten, Fokus, 390 px (Leiste bricht um statt zu überlaufen), vier Modi. Kein Commit. --- ### Task A9: Manifest, Icons, Service Worker (Spec 12) **Files** - Create: `public/brand/apicore-mark.svg`, `scripts/generate-icons.mjs` (idempotent), erzeugte Dateien `public/icons/icon-192.png`, `icon-512.png`, `maskable-192.png`, `maskable-512.png`, `monochrome-512.png`, `shortcut-inspection-96.png`, `shortcut-varroa-96.png`, `shortcut-today-96.png`, `src/app/apple-icon.png`, `src/app/favicon.ico` (ersetzt), `src/app/manifest.spec.ts`, `public/sw-icons.spec.ts` bzw. Ergänzung in `src/lib/offline/sw.spec.ts` - Modify: `public/icon.svg`, `src/app/manifest.ts`, `public/sw.js` (`SHELL_CACHE = "apicore-shell-v5"`, neue Icons in den Shell-Cache), `src/proxy.ts` (`matcher` nimmt `/icons/` und `apple-icon.png` aus), `package.json` (`sharp` als devDependency, Skript `"icons": "node scripts/generate-icons.mjs"`) - Delete: `src/app/apple-icon.tsx` **Interfaces** - `manifest.ts` exakt wie Spec 12.3: `id "/"`, `lang "de"`, `scope "/"`, `background_color` und `theme_color` `#f9f4ea`, Icons (any 192/512, maskable 192/512, monochrome 512, `icon.svg` any), Shortcuts „Durchsicht erfassen“ (`/inspections/new`), „Varroa messen“ (`/varroa/new`), „Heute“ (`/dashboard`) mit den 96-px-Icons. `public/sw-routes.js` bleibt unverändert (die Shortcut-Routen stehen schon in `SHELL_ROUTES`), `SW_ROUTES_VERSION` bleibt. - App-Icon: Zelle auf Tinte `oklch(0.24 0.035 55)` (#2c1b0f); maskable randlos, Zelle höchstens 66 % der Kante; Monochrom weiß auf transparent; `favicon.ico` mit 16/32/48 px. `sharp` schreibt kein ICO: das Skript baut den ICO-Container von Hand (Header + PNG-Einträge), keine weitere Abhängigkeit. Die Master-Geometrie entspricht `BRAND_CELL_PATHS` aus Task A3. Die erzeugten Dateien werden committet, das Docker-Image hängt nicht vom Skript ab. - [ ] **Schritt 1: Failing Tests** ```ts it('every manifest icon exists under public with the declared size', ...); // PNG-IHDR lesen it('declares any and maskable icons at 192 and 512 and one monochrome icon', ...); it('background_color and theme_color equal the light --background', ...); // gegen globals.css it('every shortcut url is in SHELL_ROUTES', ...); it('the service worker cache version is apicore-shell-v5', ...); it('favicon.ico holds 16, 32 and 48 px entries', ...); it('the proxy matcher excludes icons and apple-icon.png', ...); it('running the icon script twice leaves the files byte-identical', ...); ``` - [ ] **Schritt 2:** `npx vitest run src/app/manifest.spec.ts src/lib/offline` → FAIL. - [ ] **Schritt 3: Implementieren;** Anleitungen `manifest.md`, `app-icons.md` vorher lesen; danach `npm run icons` einmal ausführen und die erzeugten Dateien prüfen (Ansicht des Lochs der maskable-Zone). - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – V mit `src/app src/lib/offline`; VB: Chrome DevTools > Application > Manifest (Icons, maskable-Vorschau, Shortcuts) und Service Worker nach Update (neuer Cache `apicore-shell-v5`, App startet offline). Kein Commit. --- ### Task A10: Wächter vollständig, Hinweis „Sonnenmodus“, Sichtprüfung T5a (Spec 10.7, 15.4) **Files** - Modify: `src/styles/baukasten-guard.spec.ts`, `src/app/(app)/dashboard/page.tsx`, `src/app/(app)/dashboard/page.spec.tsx` (neu) - Create: `src/components/appearance-notice.tsx` (+ Spec) **Interfaces** - Guard-Regeln, die noch fehlen: kein `text-primary` ohne `-foreground`/`-ink` (Regex `text-primary(?![\w-])` bzw. nur `-foreground`/`-ink`-Suffix erlaubt), kein `gradient-` und kein `text-white` (Ausnahme `herkunft/[lot]/page.tsx` nur für das Foto-Overlay, falls nötig), kein `dark:` (Ausnahme `apiary-map.tsx` entfällt, weil der Kachelfilter in CSS liegt), keine Schriftgröße unter 13 px (`text-[10px]`, `text-[11px]`, `text-[12px]`), `PageHeader`/`DetailHeader`/`EntityCard` ohne Prop `gradient`. Der Test liest `src/**/*.tsx` ohne `*.spec.tsx`; jede Ausnahme steht mit Grund in der Liste. - `AppearanceNotice`: einmalig auf Start „Neu: Sonnenmodus für draußen – im Benutzermenü.“ mit „Ausprobieren“ (schaltet Sonne) und „Schließen“; gemerkt je Gerät (`apicore.notice.sun`, `try/catch`). - [ ] **Schritt 1: Failing Tests** ```tsx it('baukasten guard passes with the documented exceptions only', ...); it('every guard exception has a reason', ...); it('the notice shows once, Ausprobieren enables sun and Schließen remembers it', ...); it('the notice works when storage is blocked', ...); it('the dashboard shows no white text on honey', ...); ``` - [ ] **Schritt 2:** `npx vitest run src/styles src/components/appearance-notice.spec.tsx "src/app/(app)/dashboard"` → FAIL. - [ ] **Schritt 3: Implementieren.** Danach Sichtprüfung T5a: Screenshot-Matrix (Start, Stand, Bienenstände, Volk, Durchsicht neu, Varroa neu, Profil > Darstellung, Login, Herkunft × vier Modi × 390/820/1366 px; `forced-colors: active` und `prefers-reduced-motion: reduce` je einmal auf Start und Durchsicht neu), Lighthouse Barrierefreiheit ≥ 95 auf Start, Volk, Durchsicht neu, Profil in hell und Sonne, `impeccable detect --json` über die geänderten Dateien (0 Befunde). Befunde als Liste an den Orchestrator; bei Tokenänderungen `contrast.spec.ts` mit anpassen. Der Draußen-Test mit dem Inhaber (Spec 13.4) ist keine Agent-Aufgabe und läuft nach dem Rollout. - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – V mit `src` (volle Suite) und `npm run build`; VB wie in Schritt 3. Kein Commit. --- ### Task A11: Bewegungs-Sprache durchsetzen (Spec §17, neu im Review 2026-10-16) Vorher laden: - `animate` bzw. `ecc:motion-foundations` für Konzept und Tokens - `motion` (Docs „Reduce bundle size“, `LazyMotion`, `layout`, `AnimatePresence`) - danach `fixing-motion-performance` Skill-Routing: erst Konzept, dann CSS, dann `motion`. GSAP nicht. **Files** - Create: - `src/lib/motion/tokens.ts` (+ `tokens.spec.ts`) - `src/components/app-motion.tsx` (+ Spec): `LazyMotion` mit `strict`, Features asynchron, `MotionConfig` je Stufe - `src/lib/motion/motion-features.ts`: `export default domMax`, nur per dynamischem Import - `scripts/codemod-t5-motion.mjs` (+ Spec) - Modify: - `src/app/globals.css`: - `--motion-fade`, `--motion-enter`, `--motion-settle`, `--motion-moment`, `--ease-in`, `--ease-spring`, `--motion-shift` - `--animate-page-in` auf `--motion-enter` - `next-step-in` ohne `filter: blur` - `.today-row`-Faltung entfernen - `src/app/(app)/layout.tsx` (`<AppMotion>` um den Inhalt) - `src/app/(app)/page-transition.tsx` - `src/components/ui/sheet.tsx`, `dialog.tsx`, `dropdown-menu.tsx`, `select.tsx` (Dauern und Kurven aus Tokens, Austritt `--ease-in`) - `src/components/ui/button.tsx` (`motion-full:active:scale-[0.97]`, kein `transition-all`) - `src/components/ui/badge.tsx`, `card.tsx`, `input.tsx`, `textarea.tsx`, `tabs.tsx` (`transition-all` raus; Karte ohne Hover-Heben) - `src/app/(app)/sidebar.tsx` (ohne `group-hover:scale-110`, ohne `transition-all`) - `src/components/stat-bar-list.tsx` (Balken ohne Übergang) - `src/components/today/today-panel.tsx` und `today/use-task-actions.ts`: Wegfalten über `AnimatePresence` und `m.li layout` mit `SPRING_FLIGHT`; `EXIT_DELAY_MS` bleibt - `src/components/offline/pending-queue-sheet.tsx` und `src/app/(app)/voice/page.tsx`: Entfernen über dasselbe Muster - alle `motion-safe:`-Treffer mit Verschiebung, Drehung, `animate-pulse` oder `animate-spin` (Codemod → `motion-full:`). Heute betroffen: - `rule-card-shell.tsx`, `suggestions/page.tsx`, `restriction-override.tsx`, `form-section.tsx`, `suggestion-group.tsx` - `hive-card.tsx`, `colonies/[id]/hive/page.tsx`, `voice-capture-card.tsx`, `voice-recorder.tsx` - `label-compliance-panel.tsx` (`motion-reduce:animate-none` → `motion-full:animate-spin`) - Beute als Verbraucher derselben Quelle: - `src/components/hive/hive-scene/quality.ts` (`SPRING_STIFFNESS`/`SPRING_DAMPING` aus `tokens.ts`) - `use-hive-motion.ts` (`SUBTLE_FADE_SECONDS` aus `tokens.ts`) - `hive-visual-slot.tsx` (`FADE_MS.subtle` = `--motion-fade`) - `src/styles/baukasten-guard.spec.ts`: Regelgruppe „Bewegung“ (Spec §17.7) - Unverändert: three.js-Szene (`use-animated-node.ts`, `model-rig.tsx`); `.honey-check` (nur Fokus-Ausnahme aus A5). **Interfaces** ```ts export const MOTION = { fadeMs: 150, enterMs: 200, settleMs: 350, momentMs: 420, shiftPx: 8 } as const; export const SPRING_FLIGHT = { type: "spring", stiffness: 210, damping: 21, mass: 1 } as const; // = Beute-Feder export const SUBTLE_FADE_SECONDS: number; // MOTION.fadeMs / 1000 export function springEasing(points?: number): string; // CSS linear(...) aus springStep; erzeugt --ease-spring export function transitionFor(level: MotionLevel, kind: "enter" | "exit" | "fade" | "layout"): Transition; // off → { duration: 0 } export function scrollBehaviorFor(level: MotionLevel, distancePx: number, viewportPx: number): ScrollBehavior; // smooth nur bei full und < 1 Bildschirm export function AppMotion(props: { children: ReactNode }): JSX.Element; // full: reducedMotion "never"; subtle: "always"; off: transition duration 0 ``` - [ ] **Schritt 1: Failing Tests** ```ts // tokens.spec.ts it('css motion variables equal the TS tokens', ...); // liest globals.css it('the flight spring equals the hive spring in quality.ts', ...); it('--ease-spring samples stay within 0.02 of springStep', ...); it('transitionFor off has zero duration and subtle has no spatial part', ...); it('scrollBehaviorFor is smooth only at full and below one viewport', ...); // app-motion.spec.tsx it('sets reducedMotion per level and follows a level change in the same tab', ...); // today-panel.spec.tsx (neu, gibt es noch nicht) it('a ticked task leaves via layout at full and instantly at off', ...); it('a ticked task never collapses grid-template-rows', ...); // guard (baukasten-guard.spec.ts, Gruppe Bewegung) it('no motion-safe: on translate, scale, rotate, slide, zoom or animate classes', ...); it('no transition-all and no duration above 420 ms outside components/hive', ...); it('hover translate or scale only inside components/hive', ...); it('motion/react is used only for LazyMotion, MotionConfig, AnimatePresence and hooks; elements come from motion/react-m', ...); it('globals.css keyframes and transitions do not animate width, height, grid-template-rows, filter or box-shadow', ...); it('stat bars and other data values have no transition', ...); ``` - [ ] **Schritt 2:** `npx vitest run src/lib/motion src/components/app-motion.spec.tsx src/components/today src/styles scripts` → FAIL. - [ ] **Schritt 3: Implementieren.** - Codemod (`motion-safe:` → `motion-full:` nur für Verschiebung, Pulsieren und Drehen; Farbübergänge bleiben ohne Variante) ausführen und den Diff prüfen. - Dann die Tokens setzen, `AppMotion` einbauen und die Listen umstellen. - Skelett-Verzögerung 150 ms und Spinner ab 300 ms mit Wort, wo `QueryState`/Knöpfe das heute nicht tun (Spec §17.4). - Toast bei „Dezent“ ohne Gleiten (Sichtprüfung; falls nötig CSS an `[data-sonner-toaster]`). - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – V mit `src scripts`; Bundle-Messung `motion`-Chunk ≤ 30 kB gzip, nicht im First Load von Login und Herkunft (Build-Ausgabe). - VB: - jedes Muster aus Spec §17.4 auf Start (Abhaken), Aufgaben, Warteschlangen-Sheet, Volk (Sheet, Beute), `/voice` (Entfernen), `/voice/new` (Aufnahme-Punkt) - je Voll, Dezent, Aus und `prefers-reduced-motion: reduce`, 390 px und 1366 px - Performance-Trace bei 4-facher CPU-Drosselung (390 px): - Sheet auf/zu, Abhaken mit Wegfalten, Seitenwechsel - kein Frame > 32 ms, kein Layout-Eintrag während der Bewegung außer der `layout`-Messung - Danach `review-animations` über den Diff. - Kein Commit. --- ### Task A12: Beute-Bühne an Sonne und Darstellungswechsel anschließen (Spec §17.8, Beute-Spec §9.3, neu im Review 2026-10-16) **Files** - Modify: - `src/components/hive/hive-scene/theme.ts` (Tokens bei `apicore:appearance` neu lesen statt einmalig) - `hive-canvas.tsx` (Bloom aus in Sonne) - `materials.ts` und `frame-textures.ts` (Sonne: Kanten 2 px reinweiß; Rähmchen-Arten als Schraffur je Art statt Tönung) - `hive-labels.tsx` und `src/components/hive/hive-glass.ts` (Sonne: Etiketten 16 px, kein `backdrop-blur`) - `src/components/hive/hive-card.tsx` und `src/app/(app)/colonies/[id]/hive/page.tsx` (Bühnen-Wurzel bekommt `.holo-stage`) - Specs dazu (`hive-scene-content.spec.tsx`, `frame-textures.spec.ts`) - Consumes: Holo-Sonne-Tokens und `.holo-stage` (A1), Ereignis `apicore:appearance` und `useAppearance().sunActive` (A2). **Interfaces** - `readHoloTheme(): HoloTheme` liest bei jedem Aufruf neu. `useHoloTheme()` abonniert `apicore:appearance` und ruft `invalidate()` auf. - `frameLookFor(type, { sun }): { color: Color | null; hatch: HatchKind | null }`. - [ ] **Schritt 1: Failing Tests** ```ts it('the scene re-reads the holo tokens when the appearance changes', ...); it('sun turns bloom off and draws white edges', ...); it('in sun every frame type has its own hatch and no tint', ...); it('labels are 16 px and without backdrop blur in sun', ...); it('focus inside the stage uses the holo ring token', ...); // .holo-stage setzt --ring ``` - [ ] **Schritt 2:** `npx vitest run src/components/hive` → FAIL. - [ ] **Schritt 3: Implementieren.** - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – V mit `src/components/hive`. VB: - Volk und `/colonies/[id]/hive` in hell, dunkel, Sonne hell und Sonne dunkel - Sonne live umschalten: Die Szene zieht ohne Neuladen mit - Tastatur-Fokus auf Glas-Knöpfen sichtbar - Budget des 3D-Chunks unverändert (CI-Prüfung) Kein Commit. --- ## Lieferung T5b – Handschrift ### Task B1: Waben-Standkarte (Spec 6) **Files** - Create: `src/lib/honeycomb/cell-state.ts` (+ Spec), `src/lib/honeycomb/layout.ts` (+ Spec), `src/components/honeycomb/honeycomb-cell.tsx`, `comb-symbols.tsx`, `comb-legend.tsx`, `apiary-comb.tsx` (+ Spec), `apiary-comb-card.tsx`, `use-container-width.ts`, `src/styles/comb.css` - Modify: `src/app/(app)/apiaries/[id]/apiary-colonies-card.tsx` (+ Spec; die T3/T4-Zeilenliste bleibt als aufklappbarer Rückfall), `src/app/(app)/apiaries/page.tsx` (+ Spec), `src/styles/baukasten-guard.spec.ts` (Ausnahme `honeycomb-cell.tsx` für `outline-none` mit Grund „zweiter Sechseckpfad zeichnet den Ring, Spec 6.6“) **Interfaces** - Consumes: `useColonyOverview(apiaryId)`, `colonyOverviewKey`, `colonyStatusLight(row, { today, inspectionsVisible })`, `StatusLightBadge`-Icons (`CheckCircle2`, `AlertTriangle`, `XCircle`), `todayLocalIsoDate`, `daysSince`, `startRound`; ab T4 `ColonyOverviewRow.inspectionDueOn?`, `segment`, `nextStepTitle` (optional typisiert, damit der Code vor T4 läuft). - Produces: ```ts export type RimStep = 1 | 2 | 3 | "neutral"; export const INSPECTION_STALE_DAYS: 14; // aus T3, unverändert genutzt; SOON_DAYS = 3 export function rimStepOf(row: ColonyOverviewRow, today: string, inspectionsVisible: boolean): RimStep; export interface CellModel { colonyId: string; code: string; light: StatusLight; reasons: string[]; rim: RimStep; daysSince: number | null; winterRest: boolean; name: string } export function cellModelOf(row: ColonyOverviewRow, ctx: { today: string; inspectionsVisible: boolean }): CellModel; export function summarizeColonies(models: readonly CellModel[]): { counts: Record<StatusLight, number>; dueCount: number; sentence: string }; export function sortCells(models: readonly CellModel[]): CellModel[]; // natürlich nach Code (V-2 vor V-10); Offene Frage 2, A export function columnsFor(containerWidth: number, cell: number): number; // floor((W − (b+4)/2)/(b+4)), 3..10 export function combLayout(count: number, columns: number, cell: number): { x: number; y: number; row: number }[]; // jede zweite Reihe um (b+4)/2 versetzt, um ein Viertel der Höhe verzahnt, 4 px Abstand export function ApiaryComb(props: { apiary: { id: string; name: string }; cell?: number }): JSX.Element; export function ApiaryCombCard(props: { apiary: Apiary }): JSX.Element; // Bienenstände-Liste, Zellen 56/48 px ``` - Zelle: Sechseck mit spitzer Oberseite, Breite 64 px (`pointer-coarse`) bzw. 56 px (`pointer-fine`), Liste 56/48 px; Füllung Grün `--condition-good`, Gelb `--condition-average`, Rot `--condition-critical`, Text/Icon `-foreground`; oben Zustands-Icon 16 px, darunter Code (16 px halbfett, > 6 Zeichen mit Auslassung), unten rechts Zähler bei > 1 Grund (13 px). Rand in `--foreground`, innen: Stufe 1 2 px, Stufe 2 4 px, Stufe 3 6 px gestrichelt (6 4) mit Uhr-Icon (Sonne 3/5/7 px); `neutral` 1 px `--input`. Regeln der Stufen: ab T4 (mit `inspectionDueOn`) 1 = Fälligkeit > 3 Tage entfernt oder Winterruhe (`null`), 2 = heute bis in 3 Tagen, 3 = überschritten oder nie; vor T4: ≤ 10 Tage, 11–14, > 14 oder nie. - Name (`aria-label`): „B3 – Achtung: Futter schwach. Letzte Durchsicht vor 16 Tagen.“ (ab T4 „Durchsicht seit 2 Tagen fällig.“, Winterruhe „keine Durchsicht nötig“). `<ul aria-label="Völker am Stand {Name}">` mit `<li><a>` je Zelle; das Sechseck ist ein `aria-hidden`-SVG mit `<use href="#comb-hex">`, Text und Icon liegen als HTML darüber; Fokus zeichnet ein zweiter Sechseckpfad 3 px außerhalb (`.comb-cell:focus-visible .comb-focus`); Hover nur bei `pointer-fine` (2 px `--primary-ink`, ohne Größenänderung). - Stand-Seite: Karte „Völker (n)“ mit Satz „5 Gut · 1 Achtung · 1 Kritisch · 2 Durchsichten fällig“, Wabe, Legende (eine Zeile, 13 px), Abschnitt „Braucht Blick“ (nur Gelb/Rot mit Gründen; sonst „Alle Völker sind in Ordnung.“), Knopf „Alle n Völker als Liste“ (`aria-expanded`, klappt die T3-Zeilenliste auf). Hauptknopf der Seite bleibt „Runde starten“. Bienenstände-Liste: jede aktive Stand-Karte mit `AreaIcon`, Name (Link), ab T4 `SeasonChip`, Zusammenfassung, Zellen; `useQueries` höchstens 20 Stände, darüber nur Zusammenfassung; Legende einmal oben; Tab „Historie“ ohne Wabe. - Keine Animation; ein `<symbol id="comb-hex">` je Seite; Budget ≤ 5 kB gzip. - [ ] **Schritt 1: Failing Tests** ```ts // cell-state.spec.ts it.each([[10,1],[11,2],[14,2],[15,3]])('before T4: %i days since the last inspection is rim step %i', ...); it('before T4: never inspected is step 3', ...); it.each([[4,1],[3,2],[0,2],[-1,3]])('with T4: due in %i days is step %i', ...); it('with T4: inspectionDueOn null (winter rest) is step 1 and named as such', ...); it('without inspections:read the rim is neutral and says nothing about inspections', ...); it('the cell name starts with the code and names light, reasons and days', ...); it('a cell always shows icon and code, never colour alone', ...); // cellModelOf liefert Icon-Schlüssel je Ampel it('summarizeColonies counts lights and due inspections', ...); // layout.spec.ts it('columns follow the container width between 3 and 10', ...); it('every second row is offset and interlocked by a quarter of the height', ...); it('cells are ordered naturally by code (V-2 before V-10)', ...); // apiary-comb.spec.tsx it('renders a list of named links in code order', ...); it('shows the legend and „Braucht Blick“ only with yellow and red colonies', ...); it('„Alle Völker als Liste“ opens the T3 list (aria-expanded)', ...); it('an empty apiary shows the empty hint and no comb', ...); it('100 cells render with one shared symbol', ...); it('works offline from the pack', ...); it('focus draws the ring on the outer hexagon path', ...); // apiaries/page.spec.tsx it('the apiaries list shows one comb card per active apiary and only the summary beyond 20', ...); ``` - [ ] **Schritt 2:** `npx vitest run src/lib/honeycomb src/components/honeycomb "src/app/(app)/apiaries"` → FAIL. - [ ] **Schritt 3: Implementieren.** `cell-state`/`layout` rein und ohne React; `ApiaryComb` misst mit `useContainerWidth` (`ResizeObserver`), `useMemo` für `combLayout`. - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – V mit denselben Pfaden; VB: Stand mit 7 Völkern und Stand ohne Völker, Bienenstände-Liste; vom Stand aus „welches Volk ist Kritisch, welches lange nicht durchgesehen“ auf 50 cm Abstand in Sonne hell; Tab-Reihenfolge Zeile für Zeile; `forced-colors: active` (Stufen über Strichstärke erkennbar); React-Profiler-Messung 100 Zellen bei 4-facher CPU-Drosselung < 30 ms Scripting. Kein Commit. --- ### Task B2: Mini-Waben-Marker auf der Karte (Spec 7) **Files** - Create: `src/lib/honeycomb/mini-comb-svg.ts` (+ Spec), `src/styles/leaflet-theme.css`, `src/components/apiary-map.spec.tsx` - Modify: `src/components/apiary-map.tsx`, `src/app/(app)/apiaries/map/page.tsx` (+ Spec falls vorhanden) - Delete: `public/leaflet/marker-icon.png`, `marker-icon-2x.png`, `marker-shadow.png` **Interfaces** - Consumes: `cellModelOf`, `summarizeColonies` (B1), `useQueries` über `colonyOverviewKey`, react-leaflet `Marker`/`Popup` (die Spec spricht von `L.marker`; im Bestand sind es die react-leaflet-Komponenten, `L.divIcon` geht über die Prop `icon`). - Produces: ```ts export interface MiniCombSummary { lights: readonly StatusLight[]; total: number } export function miniCombSvg(summary: MiniCombSummary): string; // 7-Zellen-Rosette in Sechseck-Hintergrund, nur var(--…)-Farben export function markerLabel(name: string, summary: MiniCombSummary): string; // „Stand Nordhang: 7 Völker, 1 kritisch, 1 Achtung“ ``` - Marker: `L.divIcon({ html, className: "stand-marker", iconSize: [48, 52], iconAnchor: [24, 26], popupAnchor: [0, -24] })`; Hintergrund-Sechseck (Radius 24) `--card` mit 2 px `--foreground`; schlechtestes Volk in der Mitte; freie Plätze als Wachs-Umriss (`--muted`, Rand `--input`); > 7 Völker: 7 schlechteste plus Zählchip (13 px fett); Standname als Chip ab Zoom 9; vor dem Laden eine einzelne Wachs-Zelle. `keyboard: true`, `title`, `aria-label` am Marker-Element nach dem Hinzufügen. Popup im Token-Stil (`.leaflet-popup-content-wrapper`, `-tip` mit `--popover` und Tinten-Rand, Schließknopf 44 px per Pseudo-Element) mit Name, Zusammenfassung wie 6.2 und Knopf „Stand öffnen“ (`size="touch"`, Umriss). Dunkel: `.dark .leaflet-tile-pane { filter: brightness(0.8) contrast(1.1) }`; Sonne: Marker-Rand 3 px. Unter der Karte die Liste „Stände auf der Karte“ (Name als Link, Zusammenfassung, „2 Durchsichten fällig“); offline der Hinweis „Die Karte braucht Internet. Die Stände stehen unten als Liste.“ (`role="status"`). Leaflet bleibt per `dynamic(..., { ssr: false })`. - [ ] **Schritt 1: Failing Tests** ```ts it('the worst colony sits in the middle', ...); it('free places are outlines', ...); it('more than seven colonies add a count chip', ...); it('uses only var(--…) colours and no hex', ...); it('markerLabel names colonies, critical and attention counts', ...); // apiary-map.spec.tsx (Leaflet gemockt) it('a marker carries an aria-label and opens the popup with „Stand öffnen“', ...); it('lists the apiaries below the map as the alternative', ...); it('shows the offline hint when the browser is offline', ...); it('shows a single wax cell before the overview has loaded', ...); ``` - [ ] **Schritt 2:** `npx vitest run src/lib/honeycomb src/components/apiary-map.spec.tsx "src/app/(app)/apiaries/map"` → FAIL. - [ ] **Schritt 3: Implementieren;** SVG-String je Stand gemerkt nach den Ampel-Zählern. - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – V mit denselben Pfaden; VB: Karte in allen vier Modi (Popup lesbar, Kachelfilter dunkel), Tastatur (Marker per Tab, Enter öffnet), 390 px, Netz aus (Hinweis und Liste). Kein Commit. --- ### Task B3: Lebenslinie – Modell und Daten (Spec 8.1) **Files** - Create: `src/lib/colonies/lifeline.ts` (+ Spec), `src/lib/colonies/use-colony-lifeline.ts` (+ Spec) **Interfaces** - Consumes (Keys des Bestands, keine Doppel-Requests): `["colonies", id]`, `["colonies", id, "history"]`, `["colonies", id, "inspections"]`, `["colonies", id, "varroa-measurements"]` (die Spec nennt `…, "varroa"`; im Bestand heißt der Key so), `["colonies", id, "treatments"]`, `["colonies", id, "feedings"]`, `["colonies", id, "swarm-control-checks"]`, `["queens", queenId]` (höchstens 5), `["varroa", "threshold"]`, `["tasks", …]`; ab T4 `["colonies", id, "next-steps"]` und `rating.light`/`rating.band.redAbove` je Messung. - Produces: ```ts export const STRENGTH_IN_FRAMES_SINCE: string; // Rollout-Datum von T3 Task 9 (YYYY-MM-DD); der Orchestrator liefert den Wert aus dem Deploy-Protokoll export interface LifelineOptions { strengthInFramesSince?: string; showAll?: boolean } // Standard: 12 Monate + 21 Tage Zukunft export interface LifelineModel { colonyCode: string; startDate: string; from: string; to: string; strength?: { points: { date: string; frames: number }[]; breaks: string[]; max: number }; mites?: { bars: { from: string; to: string; perDay: number; light: StatusLight; redAbove?: number; label?: string }[]; threshold?: number }; treatments: { from: string; to: string; product: string }[]; queen?: { spans: { from: string; to: string | null; code: string; color: QueenColor | null; colorWord: string; hatchYear: number | null }[] }; events: { date: string; kind: AreaKey; count: number }[]; future: { date: string; title: string }[]; // höchstens 3: T4 next-steps, sonst offene Aufgaben ≤ 21 Tage withdrawalUntil: string | null; missing: ("strength" | "mites" | "treatments" | "queen")[]; hasData: boolean; desc: string; table: { date: string; event: string; value: string }[] } export function buildLifeline(input: LifelineInput, today: string, options?: LifelineOptions): LifelineModel; export function useColonyLifeline(colonyId: string): { model: LifelineModel | undefined; isPending: boolean; isError: boolean; refetch(): void }; ``` - Regeln: Stärke = `Inspection.colonyStrength` ab `strengthInFramesSince` (Achse 0 bis `max(20, aufgerundet auf 5)`, bei T4-Cap 40), ältere Werte nur in der Tabelle mit „Skala (alt)“; Lücke > 45 Tage bricht die Linie; Milben nur `NATURAL_MITE_FALL` mit `calculatedValue`, Balken über die echte Liegezeit (1–7 Tage, mindestens 4 px); `rating.light` (T4) hat Vorrang vor `varroaLight` (T3); 403 auf `inspections:read`/`varroa:read`/`treatments:read`/`queens:read` lässt die Spur still entfallen (`missing`); unbekannte Zeichenfarbe → `color: null` (gestrichelt). `<desc>` höchstens 4 Sätze wie Spec 8.4. - [ ] **Schritt 1: Failing Tests** (`lifeline.spec.ts`, `use-colony-lifeline.spec.ts`) ```ts it('values before STRENGTH_IN_FRAMES_SINCE are not in the curve but in the table', ...); // Option injiziert it('a gap over 45 days breaks the line', ...); it('varroa bars span the real exposure time', ...); it('rating.light (T4) wins over varroaLight (T3)', ...); it('queen spans come from queenHistory and the colour from markingColor', ...); it('an unknown marking colour is dashed and named', ...); it('treatments become spans', ...); it('the future shows T4 next steps, otherwise open tasks, at most 3', ...); it('a 403 on a track drops the track without an error', ...); it('the default window is the last 12 months plus 21 days, showAll widens it', ...); it('desc has at most four sentences and names the last values', ...); it('the table comes from the same model and covers the same span', ...); it('useColonyLifeline reuses the page query keys and issues no duplicate request', ...); ``` - [ ] **Schritt 2:** `npx vitest run src/lib/colonies` → FAIL. - [ ] **Schritt 3: Implementieren;** reine Funktion, nur Modell (keine React-Komponente). - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – `npm run lint && npm run typecheck && npx vitest run src/lib/colonies && npm run build`; Rechenzeit `buildLifeline` bei 150 Durchsichten < 50 ms (4-fach gedrosselt, Messung im Bericht). Kein Commit. --- ### Task B4: Lebenslinie – Ansicht auf der Volksseite (Spec 8.2–8.5) **Files** - Create: `src/components/colonies/colony-lifeline.tsx` (+ Spec), `lifeline-svg.tsx`, `lifeline-table.tsx` - Modify: `src/app/(app)/colonies/[id]/page.tsx` (+ `page.spec.tsx`; Karte direkt unter „Als Nächstes“, Desktop rechte Spalte oben) **Interfaces** - Consumes: `useColonyLifeline`, `LifelineModel` (B3), `AreaIcon`-Töne (A3), `QueryState`. - Produces: `export function ColonyLifeline(props: { colonyId: string }): JSX.Element;` - Aufbau (handgeschriebenes SVG, Vorbild `colony-varroa.tsx`): Zeitmaß 1,6 px je Tag; Spalten Monat 44 px, Königin 16 px (Schiene 4 px in Zeichenfarbe mit 1-px-Tintenrand, Punkt Radius 6 mit 2-px-Ring `--foreground`), Ereignisse 24 px (16-px-Icons in 20-px-Kreis aus `AREAS`, ein Icon mit Zähler je Tag), Stärke (Linie 2 px `--foreground`, Punkte Radius 4, Fläche `--primary` 18 %, in Sonne ohne Fläche, Raster gepunktet), Milben 64 px (Desktop 96): Balken in Ampelfarbe, Wert als Text bei Gelb, Rot und der letzten Messung, Schwelle gestrichelt (vor T4) bzw. Strich bei `band.redAbove` (ab T4); Behandlungen schraffiert (`--chart-4` 30 %, 45°). Heute-Linie 2 px beschriftet „heute“, darüber die gestrichelte Zukunftszone mit hohlen Sechsecken und Titel/Datum; Wartezeit als Satz „Wartezeit bis 20.08.“ unter der Grafik; Wertbeschriftung nur letzter, höchster, niedrigster Wert und Punkte mit ≥ 24 px Abstand. Zwei Knöpfe: „Ganzes Leben zeigen“/„Letzte 12 Monate“ (`aria-pressed`) und „Als Tabelle“ (`aria-expanded`, Tabelle Datum | Ereignis | Wert). `<figure>` mit `<svg role="img" aria-labelledby>`, `<title>` „Lebenslinie {Code}“, `<desc>` aus dem Modell, Zeichenfarbe zusätzlich als Wort. Leer: „Noch keine Durchsicht. Die Lebenslinie wächst mit jeder Durchsicht.“ mit Umriss-Knopf „Durchsicht erfassen“; Laden: Skelett in voller Höhe; Fehler: `QueryState` mit „Erneut versuchen“. Forced Colors: `CanvasText`. Höchstens 600 Knoten, ≤ 8 kB gzip, keine Animation. Der Hauptknopf der Seite bleibt „Durchsicht erfassen“. - [ ] **Schritt 1: Failing Tests** ```tsx it('renders a figure with the title and the description', ...); it('„Als Tabelle“ opens the same entries as the graphic', ...); it('„Ganzes Leben zeigen“ widens the window and flips its label', ...); it('shows the empty state with the outline button when nothing was recorded', ...); it('without varroa:read there is no mite column', ...); it('a long gap leaves the line broken and says „keine Durchsicht“', ...); it('the colony page puts the lifeline under „Als Nächstes“', ...); it('shows a skeleton while loading and a retry on error', ...); ``` - [ ] **Schritt 2:** `npx vitest run src/components/colonies "src/app/(app)/colonies"` → FAIL. - [ ] **Schritt 3: Implementieren.** - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – V mit denselben Pfaden; VB: Volk mit viel Verlauf, neues Volk ohne Daten, Volk mit Königinnenwechsel und Behandlung; 390 px (Stärke-Spalte ≥ 160 px), vier Modi, Forced Colors, Bildschirmleser-Reihenfolge (Titel, Beschreibung, Tabelle). Kein Commit. --- ### Task B5: Backend-Zusatz an `GET /autopilot/today` (Spec 9.1) **Files** (in `ApiCore-Backend/`) - Modify: `src/autopilot/next-steps.service.ts` (+ Spec), der Typ `AutopilotToday` und sein Swagger-DTO (Datei aus T4 Task 15, `grep -rn "AutopilotToday" src`), `test/next-steps.e2e-spec.ts` - Keine Migration, keine neue Abhängigkeit, kein neues Recht (`colonies:read` wie bisher, keine Geheimnisse). **Interfaces** - Consumes: `SeasonService.statesFor` (hat `segmentSince`, `reportedOn`, `note` schon), `readRule(PHENOLOGY)`, `readRule(SEASON_AUTOPILOT)` aus `src/automation/rule-config/`. - Produces (additiv, alte Frontends ignorieren die Felder): ```ts interface AutopilotToday { // … unverändert aus T4 Task 15 … seasons: { apiaryId: string; apiaryName: string; segment: SeasonSegmentKey; phase: PhenologyPhaseKey | null; phaseSource: PhaseSourceKey | null; segmentSince: string; reportedOn: string | null; note: 'NO_COORDINATES' | 'WEATHER_GAPS' | 'WEATHER_STALE' | null }[]; seasonCalendar: { calendarFallback: Record<PhenologyPhaseKey, string>; // 'MM-DD' swarmSeason: { from: PhenologyPhaseKey; until: PhenologyPhaseKey }; winterVarroaWindow: { from: string; to: string } }; } ``` - [ ] **Schritt 1: Failing Tests** ```ts it('GET /autopilot/today carries seasonCalendar and segmentSince per apiary', ...); // e2e it('seasonCalendar follows a changed PHENOLOGY config', ...); // e2e it('a user without colonies:read still gets 403 and no calendar', ...); // e2e it('today() maps reportedOn and note from the season state', ...); // unit ``` - [ ] **Schritt 2:** in `ApiCore-Backend/`: `npx jest src/autopilot` und `npx jest --config ./test/jest-e2e.json test/next-steps.e2e-spec.ts --runInBand` → FAIL. - [ ] **Schritt 3: Implementieren** in `NextStepsService.today`. - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – `npm run typecheck && npx eslint "{src,test}/**/*.ts" && npm test -- --ci`; die e2e-Datei einzeln; Swagger zeigt die neuen Felder. Kein Commit. --- ### Task B6: Bienenjahr-Rad – Modell und Saison-Standards (Spec 9.1) **Files** - Create: `src/lib/season/season-defaults.ts` (+ `season-defaults.spec.ts`), `src/lib/season/year-wheel.ts` (+ Spec) - Modify: `src/lib/autopilot/types.ts` (optionale neue Felder wie B5) **Interfaces** - Consumes: `AutopilotToday` (B5, Felder optional), `SEGMENT_LABELS` (T4 Task 18), offene Aufgaben aus `["tasks", "dashboard"]`. - Produces: ```ts export const SEASON_DEFAULTS: { calendarFallback: Record<PhenologyPhaseKey, string>; swarmSeason: { from: PhenologyPhaseKey; until: PhenologyPhaseKey }; winterVarroaWindow: { from: string; to: string } }; // Salweide 03-20, Kirsche 04-15, Apfel 04-28, Holunder 05-30, Linde 06-20, Trachtende 08-01, Herbst 09-15, Winter 11-15; Schwarmzeit Kirsche bis Linde; Winter-Varroa 11-15 bis 12-20 export interface YearWheelModel { year: number; today: string; segments: { key: SeasonSegmentKey; from: string; to: string; source: "REPORT" | "GTS" | "CALENDAR"; current: boolean; estimated: boolean }[]; windows: { key: "TRACHT" | "VARROA_SUMMER" | "EINWINTERUNG" | "VARROA_WINTER"; from: string; to: string }[]; taskWeeks: { weekStart: string; count: number }[]; overdueCount: number; reference: { apiaryId: string; apiaryName: string } | null; deviations: { apiaryName: string; segment: SeasonSegmentKey }[] } export function buildYearWheel(input: { today: AutopilotToday | undefined; tasks: { dueDate: string | null; status: string }[] }, today: string): YearWheelModel; export function angleOf(date: string): number; // 360° × (Tag im Jahr − 1) / Tage im Jahr − 90°, Schaltjahr berücksichtigt export function arcsOf(from: string, to: string): { startAngle: number; endAngle: number }[]; // Winterruhe über den Jahreswechsel = zwei Bögen ``` - Regeln: Fenster Tracht = Salweide bis Trachtende, Varroa Sommer = Trachtende bis Herbst, Einwinterung = Herbst bis Winter, Varroa Winter = `winterVarroaWindow`; lückenlos und überlappungsfrei. Aktueller Abschnitt ab `segmentSince`; vergangene/künftige Grenzen sind Kalenderwerte mit `estimated = true`. Kalenderdatum vorbei, Abschnitt noch nicht erreicht → nächster Abschnitt „ab morgen, noch offen“. Bezugsstand = Abschnitt der meisten aktiven Stände, bei Gleichstand der fortgeschrittenere (T4 7.3); Abweichungen nennt `deviations`. Aufgaben: ein Punkt je Kalenderwoche, Überfällige gesammelt (`overdueCount`), Vorschläge zählt das Rad nicht. Fehlt `seasonCalendar` (älteres Backend), gilt `SEASON_DEFAULTS`. - `season-defaults.spec.ts` liest `ApiCore-Backend/src/automation/rule-config/default-rules.ts` per `readFileSync` (Datei aus T4 Task 1, darum liegt der Test hier und nicht in T5a) und vergleicht die `MM-DD`-Werte. - [ ] **Schritt 1: Failing Tests** ```ts it('defaults equal the T4 PHENOLOGY and SEASON_AUTOPILOT defaults', ...); it.each([["2028-01-01", -90], ...])('angleOf %s in a leap year', ...); // 01.01., 01.07., 31.12. im Schaltjahr it('winter rest over the year change is two arcs', ...); it('the current segment starts at segmentSince (cherry reported on 05.04. gives swarm season from 05.04.)', ...); it('a late bloom makes the next segment „ab morgen, noch offen“', ...); it('windows are gapless and do not overlap', ...); it('the reference apiary is the majority, a tie takes the more advanced segment', ...); it('deviations name the apiaries that differ', ...); it('tasks are counted per week and overdue ones are collected', ...); it('without seasonCalendar the wheel uses the season defaults', ...); ``` - [ ] **Schritt 2:** `npx vitest run src/lib/season` → FAIL. - [ ] **Schritt 3: Implementieren.** - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – `npm run lint && npm run typecheck && npx vitest run src/lib/season src/lib/autopilot && npm run build`. Kein Commit. --- ### Task B7: Bienenjahr-Rad – Ansicht und Dashboard-Umbau (Spec 9.2–9.5) **Files** - Create: `src/components/season/year-wheel.tsx` (+ Spec), `year-wheel-table.tsx` - Modify: `src/app/(app)/dashboard/page.tsx`, `src/app/(app)/dashboard/page.spec.tsx` (aus A10), `src/lib/dashboard/types.ts` falls Kachel-Typen entfallen **Interfaces** - Consumes: `buildYearWheel`, `angleOf`, `arcsOf` (B6), `fetchAutopilotToday` (T4), `SEGMENT_LABELS`, `["tasks", "dashboard"]`. - Produces: `export function YearWheel(): JSX.Element;` (Karte „Bienenjahr“ als `h2`, Textlink „Zum Kalender“ mit 44 px Trefferfläche, „Als Tabelle“ mit `aria-expanded`). - Aufbau: 320 × 320 px (skaliert unter 328 px mit, Text ≥ 13 px); Januar oben, im Uhrzeigersinn; Monatsinitialen r = 150, Außenring r = 124 / 26 px (Segmente mit 2 px Lücke in `--card`, Kontur 1,5 px `--input`, aktuelles Segment `--primary` + 2 px `--foreground`, geschätzte Grenzen gestrichelt, keine Beschriftung im Ring), Innenring r = 96 / 10 px (Tracht `--chart-3` voll, Varroa `--chart-4` mit 45°-Schraffur in zwei Fenstern, Einwinterung `--chart-5` voll), Aufgabenpunkte r = 78 (Radius 3 / 4,5 / 6 px bei 1 / 2–3 / ≥ 4, `--foreground`; überfällig ein Punkt `--condition-critical` mit 2-px-Tintenring 4° vor dem Zeiger), Zentrum r < 62 mit drei Zeilen (Abschnitt 22 px Space Grotesk halbfett; „seit {Datum} · {Quelle}“ 14 px, bei `note` zusätzlich „Wetterdaten lückenhaft“/„ohne Koordinaten“; laufendes Fenster mit Ende oder die Abweichung „Südhang: noch Frühjahr“ bzw. „2 Stände weichen ab – siehe Tabelle“), Zeiger von r = 66 bis 140, 3 px `--foreground`, Kappe Wabenzelle (Honig mit Tintenrand), springt und dreht nie. Darunter Legende (13 px) und „Jetzt … · Danach …“. Tabellen: Abschnitt | von | bis | Quelle (je Stand eine Spalte bei Abweichung), Fenster | von | bis, Zeile „Aufgaben je Woche“ für 4 Wochen. `<svg role="img">` mit `<title>` „Bienenjahr {Jahr}“ und `<desc>` wie Spec 9.4, keine interaktiven Teile im SVG, ≈ 150 Knoten, ≤ 6 kB gzip, keine Animation. - Dashboard (oben nach unten): Kopf (`h1` in Tinte, Datumszeile), „Als Nächstes“ (einziger Honigknopf), „Außerdem heute“, T4-Karte „Durchsicht fällig“, „Im Blick behalten“, „Bienenjahr“. Die Kacheln „Überblick“ (`StatTile`) entfallen ersatzlos, ebenso die T4-Chip-Zeile „Saison je Stand“ (zieht in Zentrum und Tabelle; `SeasonChip` bleibt auf der Stand-Seite). - [ ] **Schritt 1: Failing Tests** ```tsx it('the centre shows the segment, the source and the running window', ...); it('„Als Tabelle“ opens the segment and window tables with the weekly task row', ...); it('„Zum Kalender“ links to the calendar', ...); it('names deviating apiaries in the third centre line', ...); it('the dashboard has no count tiles and no white text on honey', ...); it('the dashboard no longer shows the season chip row', ...); it('the dashboard still works without T4 data', ...); it('the pointer is a fixed element without animation', ...); ``` - [ ] **Schritt 2:** `npx vitest run src/components/season "src/app/(app)/dashboard"` → FAIL. - [ ] **Schritt 3: Implementieren;** `useMemo` für das Modell. - [ ] **Schritt 4:** → PASS. - [ ] **Schritt 5: Verifikation** – V mit denselben Pfaden; VB: Start in vier Modi, 390 px (Rad skaliert, Text ≥ 13 px), Forced Colors, Dezember (Winterruhe über den Jahreswechsel, per Datums-Override im Test-Build), „im Rad sagen, welcher Abschnitt gerade ist“ auf 50 cm. Kein Commit. --- ### Task B8: Sichtprüfung T5b und Doku-Texte (Spec 13.4, 15.6) **Files** - Keine Code-Änderung außer Befund-Korrekturen in den Dateien der Tasks B1–B7; Texte für `ARCHITECTURE.md`/`TASKS.md` gehen im Bericht an den Orchestrator. **Interfaces** - Liefert dem Orchestrator: (1) die Screenshot-Matrix (Start, Stand mit Wabe, Bienenstände, Karte, Volk mit Lebenslinie, Durchsicht neu, Varroa neu, Profil > Darstellung, Login, Herkunft × hell/dunkel/Sonne hell/Sonne dunkel × 390/820 px (Touch-Emulation)/1366 px; Forced Colors und `prefers-reduced-motion: reduce` je einmal auf Start, Stand, Volk), (2) Lighthouse Barrierefreiheit ≥ 95 auf Start, Stand, Volk, Durchsicht neu, Profil in hell und Sonne, (3) `impeccable detect --json` mit 0 Befunden, (4) Doku-Texte: ARCHITECTURE neue Annahmen „Darstellung je Gerät“ (hell, dunkel, Sonne; Inline-Skript; keine Kontodaten) und „Ein Baukasten“ (Bausteine, Wächter, Ausnahmen) unter der nächsten freien Nummer, §2 `AREAS` als einzige Quelle und Wabenzelle als einziges Motiv, TASKS.md Abschnitt T5 mit Haken, Hinweis für das Changelog (iOS-Icon erst nach Entfernen und neu Hinzufügen), Empfehlung `DESIGN.md` per `/impeccable document`. - Der Draußen-Test (Spec 13.4: Hauptknopf finden, kritisches und lange nicht durchgesehenes Volk auf 50 cm nennen, Brutwaben 0 auf 6, Sonnenmodus einschalten, Abschnitt im Rad nennen) ist Sache des Inhabers in der ersten Woche; ein Fehlschlag führt zu Token- oder Größenänderung (mit `contrast.spec.ts`), nicht zu neuer Bedienung. - [ ] **Schritt 1: Failing Test** – falls die Prüfung eine Regression zeigt, zuerst ein Test dafür: `it('<beobachtete Regression>', ...)` in der betroffenen Spec; sonst entfällt der Schritt mit Vermerk „keine Befunde“. - [ ] **Schritt 2:** `npx vitest run src` → die Befund-Tests FAIL (falls vorhanden). - [ ] **Schritt 3: Prüfen und Befunde beheben** wie beschrieben. - [ ] **Schritt 4:** `npx vitest run src` → PASS. - [ ] **Schritt 5: Verifikation** – `npm run lint && npm run typecheck && npx vitest run src && npm run build` (gesamte Suite). Kein Commit. --- ## Abdeckung der Spec | Spec-Punkt | Tasks | |---|---| | 1 Ziel und Technik: vier geprüfte Sätze, Wächter, Inline-Skript, SVG, `next-themes` raus | A1, A2, A4 | | 1 Entscheidungen: Hauptknopf Honig + Tinte, `--primary-ink`, Auswahl-Optik, Ampel-Tokens, Sonne, Grundschrift, Kompaktmaße, Fokus, ein Motiv | A1, A4, A5, A6 | | 2.1 Waben-Standkarte | B1 | | 2.1 Mini-Waben-Marker | B2 | | 2.1 Lebenslinie | B3, B4 | | 2.1 Bienenjahr-Rad ersetzt Kacheln | B5, B6, B7 | | 2.1 Kontrast, Grundschrift, Kontrastmodus, Dark Mode | A1, A4, A5 | | 2.1 Manifest, PNG- und maskable Icons | A9 | | 2.1 Bausteine Auswahlgruppe, Checkbox, Tabs, Plus/Minus | A6, A7, A8 | | 2.1 Bereichs-Icons und -Farben | A3 | | 2.1 Fokusstil | A5 | | 2.2 Befunde (Kontraste, Fokus, Abzeichen, Warn-Badge, Navigation, Icons, `chart-3`, Tablet-Größen, Toaster, `data-motion`, Tabs, Verläufe, Icon-Abweichungen, Manifest, Leaflet-Popups, `dark:`) | A1–A5, A3, A9, B2 | | 2.4 Abgrenzung (Wächter ersetzt `rating-contrast.spec`, `StatusLightBadge`, `INTENT_ICONS`, `SeasonChip`, `PHASE_SOURCE_LABELS`) | A3, A4, B1, B7 | | 3 Gestaltungsgrundsätze und Verbotsliste | Global Constraints, A10 (Wächter) | | 4.1–4.4 Tokens, Sonne, gemessene Kontraste | A1, A4 | | 4.5 Schrift und Größen | A5 | | 4.6 Fokusstil | A5, B1 (Ausnahme Zelle) | | 5 Darstellung umschalten (Skript, Hook, Profil, Schnellschalter, Toaster, `theme-color`) | A2 | | 5.4 Seiten außerhalb der App, Druck hell | A1 (print), A2, A3 | | 6 Waben-Standkarte (Daten, Aufbau, Zelle, Bedienung, Barrierefreiheit, Fokus, Leistung) | B1 | | 7 Mini-Waben (Marker, Popup, Rückfall, Dunkel/Sonne, Leistung) | B2 | | 8 Lebenslinie (Daten, Aufbau, Platzierung, Barrierefreiheit, Leistung) | B3, B4 | | 9.1 Daten und Backend-Zusatz | B5, B6 | | 9.2–9.5 Rad, Dashboard nach T5, Barrierefreiheit, Leistung | B7 | | 10.1–10.4 Bausteine (Auswahlgruppe, Checkbox, Tabs) | A6, A7, A8 | | 10.5 Plus/Minus | A6 | | 10.6 Fokus für eigene Bedienelemente | A5, A6, A7, B1 | | 10.7 Wächter-Regeln | A5, A6, A7, A8, A10 | | 10.8 Migrationsreihenfolge (mit Abweichung: A3 vor A4) | Abhängigkeiten, A1–A10 | | 11 `AREAS`/`AreaIcon` | A3 | | 12 Manifest und Icons, Service-Worker-Version | A9 | | 13.1 Wächter (contrast, baukasten-guard, season-defaults, manifest) | A1, A4, A5–A10, B6, A9 | | 13.2 Unit-Tests | A2, A3, B1, B2, B3, B6 | | 13.3 Komponenten-Tests und Backend-e2e | A2, A6–A8, B1, B2, B4, B5, B7 | | 13.4 Sichtprüfung, Lighthouse, Impeccable, Draußen-Test | A10, B8 | | 14 Aufwand, Zwei Lieferungen | Kopf, Abhängigkeiten | | 15 Rollout (Reihenfolge, Service Worker, Nutzerhinweis, Doku) | Abhängigkeiten, A9, A10, B8 | | 16 Offene Fragen (vorläufig A/A/A, Frage 4 Bewegungs-Sprache neu, Empfehlung A) | Kopf, Status | | 17 Bewegungs-Sprache (Stufen, Tokens, Muster, Nie-Liste, Leistung, Abnahme) | A11; Beute-Bühne 17.8: A1, A12 | --- ## Self-Review **Spec-Lücken und Widersprüche, hier aufgelöst (bitte beim Review bestätigen)** - **Migrationsreihenfolge 10.8:** Schritt 4 entfernt die `--gradient-*`-Tokens vor Schritt 6, der deren 22+ Verbraucher (Köpfe, Karten, Dashboard-Banner, Markenzeichen) ablöst. Gelöst: `AREAS`/`AreaIcon`/`BrandMark` (Task A3) laufen vor dem Werte-Wechsel (Task A4); `AreaIcon` braucht nur die in A1 additiv angelegten Bereichs-Tokens. - **Wächter „ab Schritt 4 hart“ gegen Anlage in Schritt 1:** Gelöst über `VALUES_SWITCHED` in `contrast.spec.ts` (A1 `false`, A4 `true`): Neue-Token-Zeilen sind sofort hart, die Tabelle 4.4 erst mit den neuen Werten. - **Darstellung (Schritt 2) braucht `ChoiceGroup appearance="cards"` (Schritt 7):** Gelöst: A2 baut die Karte mit der bestehenden `ChoiceGroup`, A6 stellt sie auf `cards` um. - **Spec 8.1 nennt den Varroa-Key `["colonies", id, "varroa"]`:** im Bestand heißt er `["colonies", id, "varroa-measurements"]` (Schwarmkontrollen `…"swarm-control-checks"`); B3 nutzt die Bestands-Keys, damit keine Doppel-Requests entstehen. - **Spec 6.1 nennt den Key `["apiaries", id, "colony-overview"]`:** gelöst über `colonyOverviewKey`/`useColonyOverview` (T3), kein zweiter Schlüssel. - **Spec 12.2 `favicon.ico` (16/32/48) mit `sharp`:** `sharp` schreibt kein ICO. Gelöst: das Icon-Skript baut den ICO-Container selbst (PNG-Einträge), keine weitere Abhängigkeit (A9). - **Spec 7.1 spricht von `L.marker`:** der Bestand nutzt react-leaflet; gelöst über `divIcon` in der `icon`-Prop und `aria-label` am Marker-Element nach `add` (B2). - **`season-defaults.spec` liest Backend-Dateien aus T4 Task 1:** existiert vor T4 nicht. Gelöst: der Test gehört zu T5b (B6), nicht zu T5a. - **`STRENGTH_IN_FRAMES_SINCE` ist beim Planen unbekannt:** gelöst als injizierbare Option von `buildLifeline` (Tests unabhängig), der Konstantenwert kommt vom Orchestrator aus dem T3-Deploy-Protokoll (B3). - **Service Worker:** Cache-Version im Bestand ist `apicore-shell-v4`, die Spec nennt `v5` als Ziel; A9 hebt auf `v5`. `sw-routes.js` bleibt unverändert (Shortcut-Routen stehen schon in `SHELL_ROUTES`), `SW_ROUTES_VERSION` ändert sich nicht. - **Dashboard-Datei gleichzeitig in T5a, T5b und T4 Task 18:** gelöst über Wellen (A3, A10, B7) und die Regel „wer später dran ist, rebased“; T5b startet erst nach T4a. - **Entwicklungs-Flag `NEXT_PUBLIC_APPEARANCE_DARK`:** wird beim Build eingebettet; reicht, weil A2–A3 nie ausgerollt werden, bevor A4 das Flag entfernt (A2–A4 gehören in dasselbe Fenster). - **PT-Summen:** T5a ≈ 4,75 PT (A1–A9 je 0,5, A10 0,25), T5b ≈ 5,25 PT (1,25 + 0,5 + 0,75 + 0,75 + 0,25 + 0,5 + 0,75 + 0,5); einzelne Tasks (A7, B1) liegen am oberen Rand. **Geprüft** - Jeder Spec-Abschnitt hat einen Task (Tabelle oben). Kein Task enthält „TBD“; Rümpfe stehen nur dort, wo Signatur und Test sie nicht festlegen (Formeln, Geometrie, Layout-Maße). - Namen über die Tasks gleich: `AreaKey`/`AREAS`/`AreaIcon`/`BrandMark`/`BRAND_CELL_PATHS` (A3, A9, B4), `useAppearance`/`APPEARANCE_KEYS`/`DEFAULT_THEME` (A2, A4, A6, A10), `ChoiceOption.icon`/`appearance` (A6, A2), `CheckboxField` (A7), `ActiveHistoryTabs`-Props `onValueChange` (A8), `rimStepOf`/`cellModelOf`/`summarizeColonies`/`combLayout`/`columnsFor` (B1, B2), `miniCombSvg`/`markerLabel` (B2), `LifelineModel`/`buildLifeline`/`useColonyLifeline` (B3, B4), `AutopilotToday.seasonCalendar`/`segmentSince` (B5, B6), `YearWheelModel`/`buildYearWheel`/`angleOf`/`arcsOf` (B6, B7), `VALUES_SWITCHED` (A1, A4). - Jeder Task schließt mit Verifikation (V, bei UI-Tasks VB mit Desktop und 390 px, Tastaturfokus, `prefers-reduced-motion` und den Modi hell/dunkel/Sonne) und „Kein Commit“; Wächter-Regeln wandern mit dem Task, der sie erfüllt. - Geteilte Dateien je Welle höchstens einmal (Liste unter „Abhängigkeiten und Parallelisierung“); `globals.css` und Layout-/Navigationsdateien sind strikt serialisiert; T5b fasst `globals.css` nicht an. - Proportion: Code steht nur als Signaturen, Testnamen mit Kern-Assertions und gepinnten Spec-Werten; Geometrie und Texte verweisen auf die bindende Spec.