/** * projectFocus — leichter Publish/Subscribe-Spiegel des aktuell fokussierten * Projekt-Kontexts. * * ChatScreen bleibt die Quelle der Wahrheit fuer sein eigenes Rendering und * publiziert hier bei jedem Focus-/Namens-/Kind-Wechsel EINWEG hinein. Der * Workspace-Canvas liest/abonniert das Singleton, um zu wissen welches Projekt * gerade aktiv ist und ob es ein Code-Projekt ist — ohne dass ChatScreen den * Workspace kennen oder umgebaut werden muss. * * Muster wie services/rvs.ts (Singleton mit Listener-Liste + Unsubscribe). */ export type ProjectKind = 'code' | 'chat'; export interface FocusSnapshot { /** '' = Hauptchat, sonst Projekt-ID */ focusedProjectId: string; projectNameById: Record; projectKindById: Record; } type Sub = (snap: FocusSnapshot) => void; class ProjectFocus { private snap: FocusSnapshot = { focusedProjectId: '', projectNameById: {}, projectKindById: {}, }; private subs: Sub[] = []; // --- Getter (synchron, fuer Nicht-Reaktive Leser) --- get(): FocusSnapshot { return this.snap; } getFocusedProjectId(): string { return this.snap.focusedProjectId; } getProjectName(id: string): string { return this.snap.projectNameById[id] || id; } /** Default 'chat' — ein Projekt ist erst 'code' wenn es explizit so * markiert wurde (set_project_kind) oder ein Code-/Desktop-Signal kam. */ getProjectKind(id: string): ProjectKind { return this.snap.projectKindById[id] || 'chat'; } // --- Publisher (von ChatScreen aufgerufen) --- setFocus(id: string): void { if (this.snap.focusedProjectId === id) return; this.snap = { ...this.snap, focusedProjectId: id }; this.emit(); } setNames(map: Record): void { // Flacher Merge — behaelt bereits bekannte Namen, ueberschreibt neue. this.snap = { ...this.snap, projectNameById: { ...this.snap.projectNameById, ...map }, }; this.emit(); } setKind(id: string, kind: ProjectKind): void { if (this.snap.projectKindById[id] === kind) return; this.snap = { ...this.snap, projectKindById: { ...this.snap.projectKindById, [id]: kind }, }; this.emit(); } setKinds(map: Record): void { this.snap = { ...this.snap, projectKindById: { ...this.snap.projectKindById, ...map }, }; this.emit(); } // --- Abo --- /** Registriert einen Listener und liefert sofort den aktuellen Snapshot. */ subscribe(cb: Sub): () => void { this.subs.push(cb); cb(this.snap); return () => { this.subs = this.subs.filter(s => s !== cb); }; } private emit(): void { const s = this.snap; this.subs.forEach(cb => cb(s)); } } const projectFocus = new ProjectFocus(); export default projectFocus;