import { create } from "zustand" import { persist } from "zustand/middleware" // Der EINE Client-Speicher (v3-Umbau P3, Befund B-08/B-09). // // Vorher verteilte sich derselbe Zweck über fünf Dateien: zwei handgebaute // `useSyncExternalStore`-Speicher (Metrik-Verlauf, Expertenmodus) und drei // `CustomEvent`-Kanäle (`mc-navigate`, `open-system-drawer`, `mc-expert-mode-change`, // dazu in P2 `mc-palette-open`). Das funktionierte — aber ein Tippfehler im // Ereignisnamen fiel erst zur Laufzeit auf, und niemand konnte auf einen Blick sagen, // welcher Zustand es überhaupt gibt. // // DIE REGEL, nach der hier etwas landet: // · Steht es in der URL? → gehört in den Router (app/router.tsx), nicht hierher. // · Kommt es vom Server? → gehört in TanStack Query, nicht hierher. // · Ist es flüchtig (Hover, Entwurf im Eingabefeld)? → useState in der Komponente. // · Sonst: hier. // // Der Metrik-Verlauf bleibt bewusst DRAUSSEN (lib/metricsStore.ts). Er nimmt ab P4 // jede Sekunde einen Messpunkt entgegen; ein Store-Update pro Sekunde würde jede // abonnierende Komponente neu rendern. Ein eigener `useSyncExternalStore` außerhalb // von React ist dafür das richtige Werkzeug — und bleibt es. export type MeldungsArt = "info" | "erfolg" | "warnung" | "fehler" export interface Meldung { id: string art: MeldungsArt text: string /** Zeitpunkt in ms — die Meldungsliste blendet Alte selbst aus. */ seit: number } /** Zustand des Ereignisstroms. Ab P4 füttert ihn der Stream selbst; in P3 setzt ihn * lib/events.ts, damit die Statusleiste schon jetzt die Wahrheit zeigt. */ export type StromLage = "live" | "nachlauf" | "getrennt" interface MC { ui: { schieneEingeklappt: boolean paletteOffen: boolean expertenmodus: boolean } strom: { lage: StromLage letztesEreignis: number | null } meldungen: Meldung[] schieneUmschalten: () => void palette: (offen: boolean) => void expertenmodus: (an: boolean) => void stromLage: (lage: StromLage) => void stromEreignis: () => void melden: (art: MeldungsArt, text: string) => void meldungWeg: (id: string) => void } // Eigener Zähler statt Date.now()/Math.random(): zwei Meldungen in derselben // Millisekunde bekamen sonst denselben Schlüssel, und React zeigte nur eine an. let meldungsZaehler = 0 export const useMC = create()( persist( (set) => ({ ui: { schieneEingeklappt: false, paletteOffen: false, expertenmodus: false }, strom: { lage: "getrennt", letztesEreignis: null }, meldungen: [], schieneUmschalten: () => set((s) => ({ ui: { ...s.ui, schieneEingeklappt: !s.ui.schieneEingeklappt } })), palette: (offen) => set((s) => ({ ui: { ...s.ui, paletteOffen: offen } })), expertenmodus: (an) => set((s) => ({ ui: { ...s.ui, expertenmodus: an } })), stromLage: (lage) => set((s) => ({ strom: { ...s.strom, lage } })), stromEreignis: () => set((s) => ({ strom: { ...s.strom, letztesEreignis: Date.now() } })), melden: (art, text) => set((s) => ({ meldungen: [ ...s.meldungen, { id: `m${++meldungsZaehler}`, art, text, seit: Date.now() }, ].slice(-6), // mehr als sechs liest ohnehin niemand })), meldungWeg: (id) => set((s) => ({ meldungen: s.meldungen.filter((m) => m.id !== id) })), }), { name: "mc_ui", // NUR Bedienvorlieben werden gemerkt. Kein Geheimnis (siehe P1/B-01), kein // Serverdatum, kein Stromzustand — der wäre beim nächsten Start ohnehin gelogen. partialize: (s) => ({ ui: { schieneEingeklappt: s.ui.schieneEingeklappt, expertenmodus: s.ui.expertenmodus }, }), merge: (gespeichert, aktuell) => { const g = gespeichert as { ui?: Partial } | undefined return { ...aktuell, ui: { ...aktuell.ui, ...(g?.ui ?? {}) } } }, }, ), ) // ── Schmale Zugriffe ──────────────────────────────────────────────────────── // Komponenten sollen nur das abonnieren, was sie wirklich brauchen — sonst rendert // die Schiene neu, weil eine Meldung kam. export const useExpertenmodus = () => useMC((s) => s.ui.expertenmodus) export const useSchieneEingeklappt = () => useMC((s) => s.ui.schieneEingeklappt) export const usePaletteOffen = () => useMC((s) => s.ui.paletteOffen) export const useStromLage = () => useMC((s) => s.strom.lage) export const useMeldungen = () => useMC((s) => s.meldungen) /** Für Code außerhalb von React (lib/events.ts, api-Fehlerbehandlung). */ export const melden = (art: MeldungsArt, text: string) => useMC.getState().melden(art, text) export const stromLageSetzen = (lage: StromLage) => useMC.getState().stromLage(lage) export const stromEreignis = () => useMC.getState().stromEreignis()